diff --git a/autotests/addremoveannotationtest.cpp b/autotests/addremoveannotationtest.cpp index 10ce2b766..71931237a 100644 --- a/autotests/addremoveannotationtest.cpp +++ b/autotests/addremoveannotationtest.cpp @@ -1,191 +1,191 @@ /*************************************************************************** * Copyright (C) 2013 by Jon Mease * * * * 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 #include "../core/document.h" #include "../core/page.h" #include "../core/annotations.h" #include "../settings_core.h" #include "testingutils.h" class AddRemoveAnnotationTest : public QObject { Q_OBJECT private slots: void initTestCase(); void init(); void cleanup(); void testAddAnnotations(); void testAddAnnotationUndoWithRotate_Bug318091(); void testRemoveAnnotations(); private: Okular::Document *m_document; }; void AddRemoveAnnotationTest::initTestCase() { Okular::SettingsCore::instance( QStringLiteral("addannotationtest") ); m_document = new Okular::Document( nullptr ); } void AddRemoveAnnotationTest::init() { const QString testFile = QStringLiteral(KDESRCDIR "data/file1.pdf"); QMimeDatabase db; const QMimeType mime = db.mimeTypeForFile( testFile ); QCOMPARE( m_document->openDocument(testFile, QUrl(), mime), Okular::Document::OpenSuccess ); } void AddRemoveAnnotationTest::cleanup() { m_document->closeDocument(); } void AddRemoveAnnotationTest::testAddAnnotations() { // Undo and Redo should be unavailable when docuemnt is first opened. QVERIFY( !m_document->canUndo() ); QVERIFY( !m_document->canRedo() ); // Create two distinct text annotations Okular::Annotation *annot1 = new Okular::TextAnnotation(); annot1->setBoundingRectangle( Okular::NormalizedRect( 0.1, 0.1, 0.15, 0.15 ) ); annot1->setContents( QStringLiteral("annot contents") ); Okular::Annotation *annot2 = new Okular::TextAnnotation(); annot2->setBoundingRectangle( Okular::NormalizedRect( 0.2, 0.2, 0.3, 0.4 ) ); annot2->setContents( QStringLiteral("annot contents") ); // The two annotations shold have different properties XML strings QVERIFY( TestingUtils::getAnnotationXml( annot1 ) != TestingUtils::getAnnotationXml( annot2 ) ); // We start with no annotations in the docuemnt QVERIFY( m_document->page( 0 )->annotations().size() == 0 ); // After adding annot1 we should have one annotation in the page and it should be annot1. m_document->addPageAnnotation( 0, annot1 ); QVERIFY( m_document->page( 0 )->annotations().size() == 1 ); QCOMPARE( annot1, m_document->page( 0 )->annotations().first() ); // Record the properties and name of annot1 just after insertion for later comparisons QString origLine1Xml = TestingUtils::getAnnotationXml( annot1 ); QString annot1Name = annot1->uniqueName(); QVERIFY( !annot1Name.isEmpty() ); // Now undo the addition of annot1 and verify that annot1's properties haven't changed m_document->undo(); QVERIFY( m_document->page( 0 )->annotations().empty() ); QVERIFY( !m_document->canUndo() ); QVERIFY( m_document->canRedo() ); QCOMPARE( TestingUtils::getAnnotationXml( annot1 ), origLine1Xml ); // redo addition of annot1 m_document->redo(); QVERIFY( m_document->page( 0 )->annotations().size() == 1 ); QVERIFY( annot1 == m_document->page( 0 )->annotations().first() ); QCOMPARE( TestingUtils::getAnnotationXml( annot1 ), origLine1Xml ); // undo once more m_document->undo(); QVERIFY( m_document->page( 0 )->annotations().empty() ); QVERIFY( !m_document->canUndo() ); QVERIFY( m_document->canRedo() ); QCOMPARE( TestingUtils::getAnnotationXml( annot1 ), origLine1Xml ); // Set AnnotationDisposeWatcher dispose function on annot1 so we can detect // when it is deleted annot1->setDisposeDataFunction( TestingUtils::AnnotationDisposeWatcher::disposeAnnotation ); TestingUtils::AnnotationDisposeWatcher::resetDisposedAnnotationName(); QCOMPARE( TestingUtils::AnnotationDisposeWatcher::disposedAnnotationName(), QString() ); // now add annot2 m_document->addPageAnnotation( 0, annot2 ); QString annot2Name = annot2->uniqueName(); QVERIFY( !annot2Name.isEmpty() ); QVERIFY( annot1Name != annot2Name ); QVERIFY( m_document->page( 0 )->annotations().size() == 1 ); QCOMPARE( annot2, m_document->page( 0 )->annotations().first() ); // Check that adding annot2 while annot1 was in the unadded state triggered the deletion of annot1 QVERIFY( TestingUtils::AnnotationDisposeWatcher::disposedAnnotationName() == annot1Name ); } void AddRemoveAnnotationTest::testAddAnnotationUndoWithRotate_Bug318091() { Okular::Annotation *annot = new Okular::TextAnnotation(); annot->setBoundingRectangle( Okular::NormalizedRect( 0.1, 0.1, 0.15, 0.15 ) ); annot->setContents( QStringLiteral("annot contents") ); m_document->addPageAnnotation( 0, annot ); QString origAnnotXml = TestingUtils::getAnnotationXml( annot ); // Now undo annotation addition, rotate the page, and redo to annotation addition m_document->undo(); m_document->setRotation( 1 ); m_document->redo(); // Verify that annotation's properties remain unchanged // In Bug318091 the bounding rectangle was being rotated upon each redo QString newAnnotXml = TestingUtils::getAnnotationXml( annot ); QCOMPARE( origAnnotXml, newAnnotXml ); } void AddRemoveAnnotationTest::testRemoveAnnotations() { // Undo and Redo should be unavailable when docuemnt is first opened. QVERIFY( !m_document->canUndo() ); QVERIFY( !m_document->canRedo() ); // Create two distinct text annotations Okular::Annotation *annot1 = new Okular::TextAnnotation(); annot1->setBoundingRectangle( Okular::NormalizedRect( 0.1, 0.1, 0.15, 0.15 ) ); annot1->setContents( QStringLiteral("annot contents") ); Okular::Annotation *annot2 = new Okular::TextAnnotation(); annot2->setBoundingRectangle( Okular::NormalizedRect( 0.2, 0.2, 0.3, 0.4 ) ); annot2->setContents( QStringLiteral("annot contents") ); // Add annot1 and annot2 to document m_document->addPageAnnotation( 0, annot1 ); m_document->addPageAnnotation( 0, annot2 ); QVERIFY( m_document->page( 0 )->annotations().size() == 2 ); QVERIFY( m_document->page( 0 )->annotations().contains(annot1) ); QVERIFY( m_document->page( 0 )->annotations().contains(annot2) ); // Now remove annot1 m_document->removePageAnnotation( 0, annot1 ); QVERIFY( m_document->page( 0 )->annotations().size() == 1 ); QVERIFY( m_document->page( 0 )->annotations().contains(annot2) ); // Undo removal of annot1 m_document->undo(); QVERIFY( m_document->page( 0 )->annotations().size() == 2 ); QVERIFY( m_document->page( 0 )->annotations().contains(annot1) ); QVERIFY( m_document->page( 0 )->annotations().contains(annot2) ); // Redo removal m_document->redo(); QVERIFY( m_document->page( 0 )->annotations().size() == 1 ); QVERIFY( m_document->page( 0 )->annotations().contains(annot2) ); // Verify that annot1 is disposed of if document is closed with annot1 in removed state QString annot1Name = annot1->uniqueName(); annot1->setDisposeDataFunction( TestingUtils::AnnotationDisposeWatcher::disposeAnnotation ); TestingUtils::AnnotationDisposeWatcher::resetDisposedAnnotationName(); - QVERIFY( TestingUtils::AnnotationDisposeWatcher::disposedAnnotationName() == QString() ); + QVERIFY( TestingUtils::AnnotationDisposeWatcher::disposedAnnotationName().isEmpty() ); m_document->closeDocument(); QVERIFY( TestingUtils::AnnotationDisposeWatcher::disposedAnnotationName() == annot1Name ); } QTEST_MAIN( AddRemoveAnnotationTest ) #include "addremoveannotationtest.moc" diff --git a/autotests/generatorstest.cpp b/autotests/generatorstest.cpp index e8e196211..a8209dc99 100644 --- a/autotests/generatorstest.cpp +++ b/autotests/generatorstest.cpp @@ -1,75 +1,75 @@ /*************************************************************************** * Copyright (C) 2015 by Alex Richardson * * * * 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 +#include #include #include #include #include #include #include "../generator.h" class GeneratorsTest : public QObject { Q_OBJECT private slots: void testLoadsCorrectly(); }; void GeneratorsTest::testLoadsCorrectly() { QCoreApplication::setLibraryPaths(QStringList()); QVERIFY2(QDir(QStringLiteral(GENERATORS_BUILD_DIR)).exists(), GENERATORS_BUILD_DIR); // find all possible generators in $CMAKE_BINARY_DIR/generators // We can't simply hardcore the list of generators since some might not be built // depending on which dependencies were found by CMake QStringList generatorLibs; QDirIterator it(QStringLiteral(GENERATORS_BUILD_DIR), QDir::Files | QDir::Executable, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); if (QLibrary::isLibrary(it.fileName())) { if (it.fileName().startsWith(QLatin1String("kio_"))) { continue; // don't check kio_msits.so } generatorLibs << it.fileInfo().absoluteFilePath(); } } int failures = 0; int successful = 0; foreach (const QString& lib, generatorLibs) { KPluginLoader loader(lib); QVERIFY2(!loader.fileName().isEmpty(), qPrintable(lib)); qDebug() << loader.fileName(); auto factory = loader.factory(); if (!factory) { qWarning() << "Could not get KPluginFactory for" << lib; failures++; continue; } Okular::Generator* generator = factory->create(); if (!generator) { qWarning() << "Failed to cast" << lib << "to Okular::Generator"; // without the necessary Q_INTERFACES() qobject_cast fails! auto obj = factory->create(); qDebug() << "Object is of type " << obj->metaObject()->className(); qDebug() << "dynamic_cast:" << dynamic_cast(obj); qDebug() << "qobject_cast:" << qobject_cast(obj); failures++; continue; } successful++; } qDebug() << "Successfully loaded" << successful << "generators"; QCOMPARE(failures, 0); } QTEST_MAIN(GeneratorsTest) #include "generatorstest.moc" diff --git a/autotests/testingutils.cpp b/autotests/testingutils.cpp index 49ab04b31..59fc3a83b 100644 --- a/autotests/testingutils.cpp +++ b/autotests/testingutils.cpp @@ -1,54 +1,54 @@ /*************************************************************************** * Copyright (C) 2013 by Jon Mease * * * * 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 "testingutils.h" #include "core/annotations.h" #include namespace TestingUtils { QString getAnnotationXml(const Okular::Annotation* annotation) { QString annotXmlString; QTextStream stream(&annotXmlString, QIODevice::Append); annotation->getAnnotationPropertiesDomNode().save(stream, 0); return annotXmlString; } bool pointListsAlmostEqual( QLinkedList< Okular::NormalizedPoint > points1, QLinkedList< Okular::NormalizedPoint > points2 ) { QLinkedListIterator it1( points1 ); QLinkedListIterator it2( points2 ); while ( it1.hasNext() && it2.hasNext() ) { const Okular::NormalizedPoint& p1 = it1.next(); const Okular::NormalizedPoint& p2 = it2.next(); if ( !qFuzzyCompare( p1.x, p2.x ) || !qFuzzyCompare( p1.y, p2.y ) ) { return false; } } return !it1.hasNext() && !it2.hasNext(); } - QString AnnotationDisposeWatcher::m_disposedAnnotationName = QString(); + QString AnnotationDisposeWatcher::m_disposedAnnotationName = QString(); //krazy:exclude=nullstrassign QString AnnotationDisposeWatcher::disposedAnnotationName() { return m_disposedAnnotationName; } void AnnotationDisposeWatcher::resetDisposedAnnotationName() { m_disposedAnnotationName = QString(); } void AnnotationDisposeWatcher::disposeAnnotation( const Okular::Annotation *ann ) { m_disposedAnnotationName = ann->uniqueName(); } } diff --git a/conf/editannottooldialog.cpp b/conf/editannottooldialog.cpp index 18b81e1e5..cf79d83c9 100644 --- a/conf/editannottooldialog.cpp +++ b/conf/editannottooldialog.cpp @@ -1,491 +1,491 @@ #include "editannottooldialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include +#include +#include #include #include #include "core/annotations.h" #include "ui/annotationwidgets.h" #include "ui/pageviewannotator.h" EditAnnotToolDialog::EditAnnotToolDialog( QWidget *parent, const QDomElement &initialState ) : QDialog( parent ), m_stubann( nullptr ), m_annotationWidget( nullptr ) { QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &EditAnnotToolDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &EditAnnotToolDialog::reject); okButton->setDefault(true); QLabel * tmplabel; QWidget *widget = new QWidget( this ); QGridLayout * widgetLayout = new QGridLayout( widget ); mainLayout->addWidget(widget); mainLayout->addWidget(buttonBox); m_name = new KLineEdit( widget ); mainLayout->addWidget(m_name); tmplabel = new QLabel( i18n( "&Name:" ), widget ); mainLayout->addWidget(tmplabel); tmplabel->setBuddy( m_name ); widgetLayout->addWidget( tmplabel, 0, 0, Qt::AlignRight ); widgetLayout->addWidget( m_name, 0, 1 ); m_type = new KComboBox( false, widget ); mainLayout->addWidget(m_type); connect(m_type, static_cast(&KComboBox::currentIndexChanged), this, &EditAnnotToolDialog::slotTypeChanged); tmplabel = new QLabel( i18n( "&Type:" ), widget ); mainLayout->addWidget(tmplabel); tmplabel->setBuddy( m_type ); widgetLayout->addWidget( tmplabel, 1, 0, Qt::AlignRight ); widgetLayout->addWidget( m_type, 1, 1 ); m_toolIcon = new QLabel( widget ); mainLayout->addWidget(m_toolIcon); m_toolIcon->setAlignment( Qt::AlignRight | Qt::AlignTop ); m_toolIcon->setMinimumSize( 40, 32 ); widgetLayout->addWidget( m_toolIcon, 0, 2, 2, 1 ); m_appearanceBox = new QGroupBox( i18n( "Appearance" ), widget ); mainLayout->addWidget(m_appearanceBox); m_appearanceBox->setLayout( new QVBoxLayout( m_appearanceBox ) ); widgetLayout->addWidget( m_appearanceBox, 2, 0, 1, 3 ); // Populate combobox with annotation types m_type->addItem( i18n("Pop-up Note"), qVariantFromValue( ToolNoteLinked ) ); m_type->addItem( i18n("Inline Note"), qVariantFromValue( ToolNoteInline ) ); m_type->addItem( i18n("Freehand Line"), qVariantFromValue( ToolInk ) ); m_type->addItem( i18n("Straight Line"), qVariantFromValue( ToolStraightLine ) ); m_type->addItem( i18n("Polygon"), qVariantFromValue( ToolPolygon ) ); m_type->addItem( i18n("Text markup"), qVariantFromValue( ToolTextMarkup ) ); m_type->addItem( i18n("Geometrical shape"), qVariantFromValue( ToolGeometricalShape ) ); m_type->addItem( i18n("Stamp"), qVariantFromValue( ToolStamp ) ); createStubAnnotation(); if ( initialState.isNull() ) { setWindowTitle( i18n("Create annotation tool") ); } else { setWindowTitle( i18n("Edit annotation tool") ); loadTool( initialState ); } rebuildAppearanceBox(); updateDefaultNameAndIcon(); } EditAnnotToolDialog::~EditAnnotToolDialog() { delete m_annotationWidget; } QString EditAnnotToolDialog::name() const { return m_name->text(); } QDomDocument EditAnnotToolDialog::toolXml() const { const ToolType toolType = m_type->itemData( m_type->currentIndex() ).value(); QDomDocument doc; QDomElement toolElement = doc.createElement( QStringLiteral("tool") ); QDomElement engineElement = doc.createElement( QStringLiteral("engine") ); QDomElement annotationElement = doc.createElement( QStringLiteral("annotation") ); doc.appendChild( toolElement ); toolElement.appendChild( engineElement ); engineElement.appendChild( annotationElement ); const QString color = m_stubann->style().color().name(); const QString opacity = QString::number( m_stubann->style().opacity() ); const QString width = QString::number( m_stubann->style().width() ); if ( toolType == ToolNoteLinked ) { Okular::TextAnnotation * ta = static_cast( m_stubann ); toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("note-linked") ); engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("PickPoint") ); engineElement.setAttribute( QStringLiteral("color"), color ); engineElement.setAttribute( QStringLiteral("hoverIcon"), QStringLiteral("tool-note") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Text") ); annotationElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("icon"), ta->textIcon() ); } else if ( toolType == ToolNoteInline ) { Okular::TextAnnotation * ta = static_cast( m_stubann ); toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("note-inline") ); engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("PickPoint") ); engineElement.setAttribute( QStringLiteral("color"), color ); engineElement.setAttribute( QStringLiteral("hoverIcon"), QStringLiteral("tool-note-inline") ); engineElement.setAttribute( QStringLiteral("block"), QStringLiteral("true") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("FreeText") ); annotationElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("width"), width ); if ( ta->inplaceAlignment() != 0 ) annotationElement.setAttribute( QStringLiteral("align"), ta->inplaceAlignment() ); if ( ta->textFont() != QApplication::font() ) annotationElement.setAttribute( QStringLiteral("font"), ta->textFont().toString() ); } else if ( toolType == ToolInk ) { toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("ink") ); engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("SmoothLine") ); engineElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Ink") ); annotationElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("width"), width ); } else if ( toolType == ToolStraightLine ) { Okular::LineAnnotation * la = static_cast( m_stubann ); toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("straight-line") ); engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("PolyLine") ); engineElement.setAttribute( QStringLiteral("color"), color ); engineElement.setAttribute( QStringLiteral("points"), QStringLiteral("2") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Line") ); annotationElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("width"), width ); if ( la->lineLeadingForwardPoint() != 0 || la->lineLeadingBackwardPoint() != 0 ) { annotationElement.setAttribute( QStringLiteral("leadFwd"), QString::number( la->lineLeadingForwardPoint() ) ); annotationElement.setAttribute( QStringLiteral("leadBack"), QString::number( la->lineLeadingBackwardPoint() ) ); } } else if ( toolType == ToolPolygon ) { Okular::LineAnnotation * la = static_cast( m_stubann ); toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("polygon") ); engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("PolyLine") ); engineElement.setAttribute( QStringLiteral("color"), color ); engineElement.setAttribute( QStringLiteral("points"), QStringLiteral("-1") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Line") ); annotationElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("width"), width ); if ( la->lineInnerColor().isValid() ) { annotationElement.setAttribute( QStringLiteral("innerColor"), la->lineInnerColor().name() ); } } else if ( toolType == ToolTextMarkup ) { Okular::HighlightAnnotation * ha = static_cast( m_stubann ); switch ( ha->highlightType() ) { case Okular::HighlightAnnotation::Highlight: toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("highlight") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Highlight") ); break; case Okular::HighlightAnnotation::Squiggly: toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("squiggly") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Squiggly") ); break; case Okular::HighlightAnnotation::Underline: toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("underline") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Underline") ); break; case Okular::HighlightAnnotation::StrikeOut: toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("strikeout") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("StrikeOut") ); break; } engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("TextSelector") ); engineElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("color"), color ); } else if ( toolType == ToolGeometricalShape ) { Okular::GeomAnnotation * ga = static_cast( m_stubann ); if ( ga->geometricalType() == Okular::GeomAnnotation::InscribedCircle ) { toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("ellipse") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("GeomCircle") ); } else { toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("rectangle") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("GeomSquare") ); } engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("PickPoint") ); engineElement.setAttribute( QStringLiteral("color"), color ); engineElement.setAttribute( QStringLiteral("block"), QStringLiteral("true") ); annotationElement.setAttribute( QStringLiteral("color"), color ); annotationElement.setAttribute( QStringLiteral("width"), width ); if ( ga->geometricalInnerColor().isValid() ) annotationElement.setAttribute( QStringLiteral("innerColor"), ga->geometricalInnerColor().name() ); } else if ( toolType == ToolStamp ) { Okular::StampAnnotation * sa = static_cast( m_stubann ); toolElement.setAttribute( QStringLiteral("type"), QStringLiteral("stamp") ); engineElement.setAttribute( QStringLiteral("type"), QStringLiteral("PickPoint") ); engineElement.setAttribute( QStringLiteral("hoverIcon"), sa->stampIconName() ); engineElement.setAttribute( QStringLiteral("size"), QStringLiteral("64") ); engineElement.setAttribute( QStringLiteral("block"), QStringLiteral("true") ); annotationElement.setAttribute( QStringLiteral("type"), QStringLiteral("Stamp") ); annotationElement.setAttribute( QStringLiteral("icon"), sa->stampIconName() ); } if ( opacity != QStringLiteral("1") ) annotationElement.setAttribute( QStringLiteral("opacity"), opacity ); return doc; } void EditAnnotToolDialog::createStubAnnotation() { const ToolType toolType = m_type->itemData( m_type->currentIndex() ).value(); // Delete previous stub annotation, if any delete m_stubann; // Create stub annotation if ( toolType == ToolNoteLinked ) { Okular::TextAnnotation * ta = new Okular::TextAnnotation(); ta->setTextType( Okular::TextAnnotation::Linked ); ta->setTextIcon( QStringLiteral("Note") ); ta->style().setColor( Qt::yellow ); m_stubann = ta; } else if ( toolType == ToolNoteInline ) { Okular::TextAnnotation * ta = new Okular::TextAnnotation(); ta->setTextType( Okular::TextAnnotation::InPlace ); ta->style().setWidth( 1.0 ); ta->style().setColor( Qt::yellow ); m_stubann = ta; } else if ( toolType == ToolInk ) { m_stubann = new Okular::InkAnnotation(); m_stubann->style().setWidth( 2.0 ); m_stubann->style().setColor( Qt::green ); } else if ( toolType == ToolStraightLine ) { Okular::LineAnnotation * la = new Okular::LineAnnotation(); la->setLinePoints( QLinkedList() << Okular::NormalizedPoint(0,0) << Okular::NormalizedPoint(1,0) ); la->style().setColor( QColor( 0xff, 0xe0, 0x00 ) ); m_stubann = la; } else if ( toolType == ToolPolygon ) { Okular::LineAnnotation * la = new Okular::LineAnnotation(); la->setLinePoints( QLinkedList() << Okular::NormalizedPoint(0,0) << Okular::NormalizedPoint(1,0) << Okular::NormalizedPoint(1,1) ); la->setLineClosed( true ); la->style().setColor( QColor( 0x00, 0x7e, 0xee ) ); m_stubann = la; } else if ( toolType == ToolTextMarkup ) { m_stubann = new Okular::HighlightAnnotation(); m_stubann->style().setColor( Qt::yellow ); } else if ( toolType == ToolGeometricalShape ) { Okular::GeomAnnotation * ga = new Okular::GeomAnnotation(); ga->setGeometricalType( Okular::GeomAnnotation::InscribedCircle ); ga->style().setWidth( 5.0 ); ga->style().setColor( Qt::cyan ); m_stubann = ga; } else if ( toolType == ToolStamp ) { Okular::StampAnnotation * sa = new Okular::StampAnnotation(); sa->setStampIconName( QStringLiteral("okular") ); m_stubann = sa; } } void EditAnnotToolDialog::rebuildAppearanceBox() { // Remove previous widget (if any) if ( m_annotationWidget ) { delete m_annotationWidget->appearanceWidget(); delete m_annotationWidget; } m_annotationWidget = AnnotationWidgetFactory::widgetFor( m_stubann ); m_appearanceBox->layout()->addWidget( m_annotationWidget->appearanceWidget() ); connect(m_annotationWidget, &AnnotationWidget::dataChanged, this, &EditAnnotToolDialog::slotDataChanged); } void EditAnnotToolDialog::updateDefaultNameAndIcon() { QDomDocument doc = toolXml(); QDomElement toolElement = doc.documentElement(); m_name->setPlaceholderText( PageViewAnnotator::defaultToolName( toolElement ) ); m_toolIcon->setPixmap( PageViewAnnotator::makeToolPixmap( toolElement ) ); } void EditAnnotToolDialog::setToolType( ToolType newType ) { int idx = -1; for ( int i = 0; idx == -1 && i < m_type->count(); ++i ) { if ( m_type->itemData( i ).value() == newType ) idx = i; } // The following call also results in createStubAnnotation being called m_type->setCurrentIndex( idx ); } void EditAnnotToolDialog::loadTool( const QDomElement &toolElement ) { const QDomElement engineElement = toolElement.elementsByTagName( QStringLiteral("engine") ).item( 0 ).toElement(); const QDomElement annotationElement = engineElement.elementsByTagName( QStringLiteral("annotation") ).item( 0 ).toElement(); const QString annotType = toolElement.attribute( QStringLiteral("type") ); if ( annotType == QLatin1String("ellipse") ) { setToolType( ToolGeometricalShape ); Okular::GeomAnnotation * ga = static_cast( m_stubann ); ga->setGeometricalType( Okular::GeomAnnotation::InscribedCircle ); if ( annotationElement.hasAttribute( QStringLiteral("innerColor") ) ) ga->setGeometricalInnerColor( QColor( annotationElement.attribute( QStringLiteral("innerColor") ) ) ); } else if ( annotType == QLatin1String("highlight") ) { setToolType( ToolTextMarkup ); Okular::HighlightAnnotation * ha = static_cast( m_stubann ); ha->setHighlightType( Okular::HighlightAnnotation::Highlight ); } else if ( annotType == QLatin1String("ink") ) { setToolType( ToolInk ); } else if ( annotType == QLatin1String("note-inline") ) { setToolType( ToolNoteInline ); Okular::TextAnnotation * ta = static_cast( m_stubann ); if ( annotationElement.hasAttribute( QStringLiteral("align") ) ) ta->setInplaceAlignment( annotationElement.attribute( QStringLiteral("align") ).toInt() ); if ( annotationElement.hasAttribute( QStringLiteral("font") ) ) { QFont f; f.fromString( annotationElement.attribute( QStringLiteral("font") ) ); ta->setTextFont( f ); } } else if ( annotType == QLatin1String("note-linked") ) { setToolType( ToolNoteLinked ); Okular::TextAnnotation * ta = static_cast( m_stubann ); ta->setTextIcon( annotationElement.attribute( QStringLiteral("icon") ) ); } else if ( annotType == QLatin1String("polygon") ) { setToolType( ToolPolygon ); Okular::LineAnnotation * la = static_cast( m_stubann ); if ( annotationElement.hasAttribute( QStringLiteral("innerColor") ) ) la->setLineInnerColor( QColor( annotationElement.attribute( QStringLiteral("innerColor") ) ) ); } else if ( annotType == QLatin1String("rectangle") ) { setToolType( ToolGeometricalShape ); Okular::GeomAnnotation * ga = static_cast( m_stubann ); ga->setGeometricalType( Okular::GeomAnnotation::InscribedSquare ); if ( annotationElement.hasAttribute( QStringLiteral("innerColor") ) ) ga->setGeometricalInnerColor( QColor( annotationElement.attribute( QStringLiteral("innerColor") ) ) ); } else if ( annotType == QLatin1String("squiggly") ) { setToolType( ToolTextMarkup ); Okular::HighlightAnnotation * ha = static_cast( m_stubann ); ha->setHighlightType( Okular::HighlightAnnotation::Squiggly ); } else if ( annotType == QLatin1String("stamp") ) { setToolType( ToolStamp ); Okular::StampAnnotation * sa = static_cast( m_stubann ); sa->setStampIconName( annotationElement.attribute( QStringLiteral("icon") ) ); } else if ( annotType == QLatin1String("straight-line") ) { setToolType( ToolStraightLine ); Okular::LineAnnotation * la = static_cast( m_stubann ); if ( annotationElement.hasAttribute( QStringLiteral("leadFwd") ) ) la->setLineLeadingForwardPoint( annotationElement.attribute( QStringLiteral("leadFwd") ).toDouble() ); if ( annotationElement.hasAttribute( QStringLiteral("leadBack") ) ) la->setLineLeadingBackwardPoint( annotationElement.attribute( QStringLiteral("leadBack") ).toDouble() ); } else if ( annotType == QLatin1String("strikeout") ) { setToolType( ToolTextMarkup ); Okular::HighlightAnnotation * ha = static_cast( m_stubann ); ha->setHighlightType( Okular::HighlightAnnotation::StrikeOut ); } else if ( annotType == QLatin1String("underline") ) { setToolType( ToolTextMarkup ); Okular::HighlightAnnotation * ha = static_cast( m_stubann ); ha->setHighlightType( Okular::HighlightAnnotation::Underline ); } // Common properties if ( annotationElement.hasAttribute( QStringLiteral("color") ) ) m_stubann->style().setColor( QColor( annotationElement.attribute( QStringLiteral("color") ) ) ); if ( annotationElement.hasAttribute( QStringLiteral("opacity") ) ) m_stubann->style().setOpacity( annotationElement.attribute( QStringLiteral("opacity") ).toDouble() ); if ( annotationElement.hasAttribute( QStringLiteral("width") ) ) m_stubann->style().setWidth( annotationElement.attribute( QStringLiteral("width") ).toDouble() ); if ( toolElement.hasAttribute( QStringLiteral("name") ) ) m_name->setText( toolElement.attribute( QStringLiteral("name") ) ); } void EditAnnotToolDialog::slotTypeChanged() { createStubAnnotation(); rebuildAppearanceBox(); updateDefaultNameAndIcon(); } void EditAnnotToolDialog::slotDataChanged() { // Mirror changes back in the stub annotation m_annotationWidget->applyChanges(); updateDefaultNameAndIcon(); } diff --git a/conf/widgetannottools.cpp b/conf/widgetannottools.cpp index cbc32064b..f94278a8e 100644 --- a/conf/widgetannottools.cpp +++ b/conf/widgetannottools.cpp @@ -1,177 +1,177 @@ /*************************************************************************** * Copyright (C) 2012 by Fabio D'Urso * * * * 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 "widgetannottools.h" #include "editannottooldialog.h" #include #include #include #include #include #include -#include -#include +#include +#include #include #include #include #include #include "ui/pageviewannotator.h" // Used to store tools' XML description in m_list's items static const int ToolXmlRole = Qt::UserRole; WidgetAnnotTools::WidgetAnnotTools( QWidget * parent ) : WidgetConfigurationToolsBase( parent ) { } WidgetAnnotTools::~WidgetAnnotTools() { } /* Before returning the XML strings, this functions updates the id and * shortcut properties. * Note: The shortcut is only assigned to the first nine tools */ QStringList WidgetAnnotTools::tools() const { QStringList res; const int count = m_list->count(); for ( int i = 0; i < count; ++i ) { QListWidgetItem * listEntry = m_list->item(i); // Parse associated DOM data QDomDocument doc; doc.setContent( listEntry->data( ToolXmlRole ).value() ); // Set id QDomElement toolElement = doc.documentElement(); toolElement.setAttribute( QStringLiteral("id"), i+1 ); // Remove old shortcut, if any QDomNode oldShortcut = toolElement.elementsByTagName( QStringLiteral("shortcut") ).item( 0 ); if ( oldShortcut.isElement() ) toolElement.removeChild( oldShortcut ); // Create new shortcut element (only the first 9 tools are assigned a shortcut key) if ( i < 9 ) { QDomElement newShortcut = doc.createElement( QStringLiteral("shortcut") ); newShortcut.appendChild( doc.createTextNode(QString::number( i+1 )) ); toolElement.appendChild( newShortcut ); } // Append to output res << doc.toString(-1); } return res; } void WidgetAnnotTools::setTools(const QStringList& items) { m_list->clear(); // Parse each string and populate the list widget foreach ( const QString &toolXml, items ) { QDomDocument entryParser; if ( !entryParser.setContent( toolXml ) ) { qWarning() << "Skipping malformed tool XML string"; break; } QDomElement toolElement = entryParser.documentElement(); if ( toolElement.tagName() == QLatin1String("tool") ) { // Create list item and attach the source XML string as data QString itemText = toolElement.attribute( QStringLiteral("name") ); if ( itemText.isEmpty() ) itemText = PageViewAnnotator::defaultToolName( toolElement ); QListWidgetItem * listEntry = new QListWidgetItem( itemText, m_list ); listEntry->setData( ToolXmlRole, qVariantFromValue(toolXml) ); listEntry->setIcon( PageViewAnnotator::makeToolPixmap( toolElement ) ); } } updateButtons(); } void WidgetAnnotTools::slotEdit() { QListWidgetItem *listEntry = m_list->currentItem(); QDomDocument doc; doc.setContent( listEntry->data( ToolXmlRole ).value() ); QDomElement toolElement = doc.documentElement(); EditAnnotToolDialog t( this, toolElement ); if ( t.exec() != QDialog::Accepted ) return; doc = t.toolXml(); toolElement = doc.documentElement(); QString itemText = t.name(); // Store name attribute only if the user specified a customized name if ( !itemText.isEmpty() ) toolElement.setAttribute( QStringLiteral("name"), itemText ); else itemText = PageViewAnnotator::defaultToolName( toolElement ); // Edit list entry and attach XML string as data listEntry->setText( itemText ); listEntry->setData( ToolXmlRole, qVariantFromValue( doc.toString(-1) ) ); listEntry->setIcon( PageViewAnnotator::makeToolPixmap( toolElement ) ); // Select and scroll m_list->setCurrentItem( listEntry ); m_list->scrollToItem( listEntry ); updateButtons(); emit changed(); } void WidgetAnnotTools::slotAdd() { EditAnnotToolDialog t( this ); if ( t.exec() != QDialog::Accepted ) return; QDomDocument rootDoc = t.toolXml(); QDomElement toolElement = rootDoc.documentElement(); QString itemText = t.name(); // Store name attribute only if the user specified a customized name if ( !itemText.isEmpty() ) toolElement.setAttribute( QStringLiteral("name"), itemText ); else itemText = PageViewAnnotator::defaultToolName( toolElement ); // Create list entry and attach XML string as data QListWidgetItem * listEntry = new QListWidgetItem( itemText, m_list ); listEntry->setData( ToolXmlRole, qVariantFromValue( rootDoc.toString(-1) ) ); listEntry->setIcon( PageViewAnnotator::makeToolPixmap( toolElement ) ); // Select and scroll m_list->setCurrentItem( listEntry ); m_list->scrollToItem( listEntry ); updateButtons(); emit changed(); } diff --git a/conf/widgetdrawingtools.cpp b/conf/widgetdrawingtools.cpp index 0fa0e9b6a..79f1e3f15 100644 --- a/conf/widgetdrawingtools.cpp +++ b/conf/widgetdrawingtools.cpp @@ -1,212 +1,212 @@ /*************************************************************************** * Copyright (C) 2015 by Laurent Montel * * * * 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 "widgetdrawingtools.h" #include "editdrawingtooldialog.h" #include +#include #include #include #include -#include #include // Used to store tools' XML description in m_list's items static const int ToolXmlRole = Qt::UserRole; static QPixmap colorDecorationFromToolDescription( const QString &toolDescription ) { QDomDocument doc; doc.setContent( toolDescription, true ); const QDomElement toolElement = doc.documentElement(); const QDomElement engineElement = toolElement.elementsByTagName( QStringLiteral( "engine" ) ).at( 0 ).toElement(); const QDomElement annotationElement = engineElement.elementsByTagName( QStringLiteral( "annotation" ) ).at( 0 ).toElement(); QPixmap pm( 50, 20 ); pm.fill( QColor( annotationElement.attribute( QStringLiteral( "color" ) ) ) ); QPainter p( &pm ); p.setPen( Qt::black ); p.drawRect( QRect( 0, 0, pm.width() - 1, pm.height() - 1 ) ); return pm; } WidgetDrawingTools::WidgetDrawingTools( QWidget *parent ) : WidgetConfigurationToolsBase( parent ) { } WidgetDrawingTools::~WidgetDrawingTools() { } QStringList WidgetDrawingTools::tools() const { QStringList res; const int count = m_list->count(); for ( int i = 0; i < count; ++i ) { QListWidgetItem * listEntry = m_list->item( i ); // Parse associated DOM data QDomDocument doc; doc.setContent( listEntry->data( ToolXmlRole ).value() ); // Append to output res << doc.toString( -1 ); } return res; } void WidgetDrawingTools::setTools( const QStringList &items ) { m_list->clear(); // Parse each string and populate the list widget foreach ( const QString &toolXml, items ) { QDomDocument entryParser; if ( !entryParser.setContent( toolXml ) ) { qWarning() << "Skipping malformed tool XML string"; break; } const QDomElement toolElement = entryParser.documentElement(); if ( toolElement.tagName() == QLatin1String("tool") ) { const QString name = toolElement.attribute( QStringLiteral("name") ); QString itemText; if ( toolElement.attribute( QStringLiteral("default"), QStringLiteral("false") ) == QLatin1String("true") ) itemText = i18n( name.toLatin1().constData() ); else itemText = name; QListWidgetItem * listEntry = new QListWidgetItem( itemText, m_list ); listEntry->setData( ToolXmlRole, qVariantFromValue( toolXml ) ); listEntry->setData( Qt::DecorationRole, colorDecorationFromToolDescription( toolXml ) ); } } updateButtons(); } QString WidgetDrawingTools::defaultName() const { int nameIndex = 1; bool freeNameFound = false; QString candidateName; while (!freeNameFound) { candidateName = i18n("Default Drawing Tool #%1", nameIndex); int i = 0; for ( ; i < m_list->count(); ++i ) { QListWidgetItem * listEntry = m_list->item( i ); if (candidateName == listEntry->text()) { break; } } freeNameFound = i == m_list->count(); ++nameIndex; } return candidateName; } void WidgetDrawingTools::slotAdd() { EditDrawingToolDialog dlg( QDomElement(), this ); if ( dlg.exec() != QDialog::Accepted ) return; const QDomDocument rootDoc = dlg.toolXml(); QDomElement toolElement = rootDoc.documentElement(); QString itemText = dlg.name().trimmed(); if (itemText.isEmpty()) { itemText = defaultName(); } for ( int i = 0; i < m_list->count(); ++i ) { QListWidgetItem * listEntry = m_list->item( i ); if (itemText == listEntry->text()) { - QMessageBox::information( this, i18n("Duplicated Name"), i18n("There's already a tool with that name. Using a default one") ); + KMessageBox::information( this, i18n("There's already a tool with that name. Using a default one"), i18n("Duplicated Name") ); itemText = defaultName(); break; } } // Store name attribute only if the user specified a customized name toolElement.setAttribute( QStringLiteral("name"), itemText ); // Create list entry and attach XML string as data const QString toolXml = rootDoc.toString( -1 ); QListWidgetItem * listEntry = new QListWidgetItem( itemText, m_list ); listEntry->setData( ToolXmlRole, qVariantFromValue( toolXml ) ); listEntry->setData( Qt::DecorationRole, colorDecorationFromToolDescription( toolXml ) ); // Select and scroll m_list->setCurrentItem( listEntry ); m_list->scrollToItem( listEntry ); updateButtons(); emit changed(); } void WidgetDrawingTools::slotEdit() { QListWidgetItem *listEntry = m_list->currentItem(); QDomDocument doc; doc.setContent( listEntry->data( ToolXmlRole ).value() ); QDomElement toolElement = doc.documentElement(); EditDrawingToolDialog dlg( toolElement, this ); if ( dlg.exec() != QDialog::Accepted ) return; doc = dlg.toolXml(); toolElement = doc.documentElement(); QString itemText = dlg.name(); for ( int i = 0; i < m_list->count(); ++i ) { QListWidgetItem * auxListEntry = m_list->item( i ); if (itemText == auxListEntry->text() && auxListEntry != listEntry) { - QMessageBox::information( this, i18n("Duplicated Name"), i18n("There's already a tool with that name. Using a default one") ); + KMessageBox::information( this, i18n("There's already a tool with that name. Using a default one"), i18n("Duplicated Name") ); itemText = defaultName(); break; } } // Store name attribute only if the user specified a customized name toolElement.setAttribute( QStringLiteral("name"), itemText ); // Edit list entry and attach XML string as data const QString toolXml = doc.toString( -1 ); listEntry->setText( itemText ); listEntry->setData( ToolXmlRole, qVariantFromValue( toolXml ) ); listEntry->setData( Qt::DecorationRole, colorDecorationFromToolDescription( toolXml ) ); // Select and scroll m_list->setCurrentItem( listEntry ); m_list->scrollToItem( listEntry ); updateButtons(); emit changed(); } diff --git a/core/action.h b/core/action.h index b3bb770d9..0b3ad370d 100644 --- a/core/action.h +++ b/core/action.h @@ -1,597 +1,597 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef _OKULAR_ACTION_H_ #define _OKULAR_ACTION_H_ #include "global.h" #include "okularcore_export.h" -#include -#include +#include +#include namespace Okular { class ActionPrivate; class GotoActionPrivate; class ExecuteActionPrivate; class BrowseActionPrivate; class DocumentActionPrivate; class SoundActionPrivate; class ScriptActionPrivate; class MovieActionPrivate; class RenditionActionPrivate; class MovieAnnotation; class ScreenAnnotation; class Movie; class Sound; class DocumentViewport; /** * @short Encapsulates data that describes an action. * * This is the base class for actions. It makes mandatory for inherited * widgets to reimplement the 'actionType' method and return the type of * the action described by the reimplemented class. */ class OKULARCORE_EXPORT Action { public: /** * Describes the type of action. */ enum ActionType { Goto, ///< Goto a given page or external document Execute, ///< Execute a command or external application Browse, ///< Browse a given website DocAction, ///< Start a custom action Sound, ///< Play a sound Movie, ///< Play a movie Script, ///< Executes a Script code Rendition, ///< Play a movie and/or execute a Script code @since 0.16 (KDE 4.10) BackendOpaque ///< Calls back to the backend with the action @since 1.1 }; /** * Destroys the action. */ virtual ~Action(); /** * Returns the type of the action. Every inherited class must return * an unique identifier. * * @see ActionType */ virtual ActionType actionType() const = 0; /** * Returns a i18n'ed tip of the action that is presented to * the user. */ virtual QString actionTip() const; /** * Sets the "native" @p id of the action. * * This is for use of the Generator, that can optionally store an * handle (a pointer, an identifier, etc) of the "native" action * object, if any. * * @note Okular makes no use of this * * @since 0.15 (KDE 4.9) */ void setNativeId( const QVariant &id ); /** * Returns the "native" id of the action. * * @since 0.15 (KDE 4.9) */ QVariant nativeId() const; /** * Returns the next actions to be executed after. * * @since 1.5 */ QVector< Action * > nextActions() const; /** * Sets the next actions. * * Takes ownership of the objects in the actions vector. * @since 1.5 */ void setNextActions( const QVector< Action * > &actions ); protected: /// @cond PRIVATE Action( ActionPrivate &dd ); Q_DECLARE_PRIVATE( Action ) ActionPrivate *d_ptr; /// @endcond private: Q_DISABLE_COPY( Action ) }; /** * The Goto action changes the viewport to another page * or loads an external document. */ class OKULARCORE_EXPORT GotoAction : public Action { public: /** * Creates a new goto action. * * @p fileName The name of an external file that shall be loaded. * @p viewport The target viewport information of the current document. */ GotoAction( const QString& fileName, const DocumentViewport & viewport ); /** * Creates a new goto action. * * @p fileName The name of an external file that shall be loaded. * @p namedDestination The target named destination for the target document. * * @since 0.9 (KDE 4.3) */ GotoAction( const QString& fileName, const QString& namedDestination ); /** * Destroys the goto action. */ virtual ~GotoAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns whether the goto action points to an external document. */ bool isExternal() const; /** * Returns the filename of the external document. */ QString fileName() const; /** * Returns the document viewport the goto action points to. */ DocumentViewport destViewport() const; /** * Returns the document named destination the goto action points to. * * @since 0.9 (KDE 4.3) */ QString destinationName() const; private: Q_DECLARE_PRIVATE( GotoAction ) Q_DISABLE_COPY( GotoAction ) }; /** * The Execute action executes an external application. */ class OKULARCORE_EXPORT ExecuteAction : public Action { public: /** * Creates a new execute action. * * @param fileName The file name of the application to execute. * @param parameters The parameters of the application to execute. */ ExecuteAction( const QString &fileName, const QString ¶meters ); /** * Destroys the execute action. */ virtual ~ExecuteAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns the file name of the application to execute. */ QString fileName() const; /** * Returns the parameters of the application to execute. */ QString parameters() const; private: Q_DECLARE_PRIVATE( ExecuteAction ) Q_DISABLE_COPY( ExecuteAction ) }; /** * The Browse action browses an url by opening a web browser or * email client, depedning on the url protocol (e.g. http, mailto, etc.). */ class OKULARCORE_EXPORT BrowseAction : public Action { public: /** * Creates a new browse action. * * @param url The url to browse. */ BrowseAction( const QUrl &url ); /** * Destroys the browse action. */ virtual ~BrowseAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns the url to browse. */ QUrl url() const; private: Q_DECLARE_PRIVATE( BrowseAction ) Q_DISABLE_COPY( BrowseAction ) }; /** * The DocumentAction action contains an action that is performed on * the current document. */ class OKULARCORE_EXPORT DocumentAction : public Action { public: /** * Describes the possible action types. * * WARNING KEEP IN SYNC WITH POPPLER! */ enum DocumentActionType { PageFirst = 1, ///< Jump to first page PagePrev = 2, ///< Jump to previous page PageNext = 3, ///< Jump to next page PageLast = 4, ///< Jump to last page HistoryBack = 5, ///< Go back in page history HistoryForward = 6, ///< Go forward in page history Quit = 7, ///< Quit application Presentation = 8, ///< Start presentation EndPresentation = 9, ///< End presentation Find = 10, ///< Open find dialog GoToPage = 11, ///< Goto page Close = 12 ///< Close document }; /** * Creates a new document action. * * @param documentActionType The type of document action. */ explicit DocumentAction( enum DocumentActionType documentActionType ); /** * Destroys the document action. */ virtual ~DocumentAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns the type of action. */ DocumentActionType documentActionType() const; private: Q_DECLARE_PRIVATE( DocumentAction ) Q_DISABLE_COPY( DocumentAction ) }; /** * The Sound action plays a sound on activation. */ class OKULARCORE_EXPORT SoundAction : public Action { public: /** * Creates a new sound action. * * @param volume The volume of the sound. * @param synchronous Whether the sound shall be played synchronous. * @param repeat Whether the sound shall be repeated. * @param mix Whether the sound shall be mixed. * @param sound The sound object which contains the sound data. */ SoundAction( double volume, bool synchronous, bool repeat, bool mix, Okular::Sound *sound ); /** * Destroys the sound action. */ virtual ~SoundAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns the volume of the sound. */ double volume() const; /** * Returns whether the sound shall be played synchronous. */ bool synchronous() const; /** * Returns whether the sound shall be repeated. */ bool repeat() const; /** * Returns whether the sound shall be mixed. */ bool mix() const; /** * Returns the sound object which contains the sound data. */ Okular::Sound *sound() const; private: Q_DECLARE_PRIVATE( SoundAction ) Q_DISABLE_COPY( SoundAction ) }; /** * The Script action executes a Script code. * * @since 0.7 (KDE 4.1) */ class OKULARCORE_EXPORT ScriptAction : public Action { public: /** * Creates a new Script action. * * @param script The code to execute. */ ScriptAction( enum ScriptType type, const QString &script ); /** * Destroys the browse action. */ virtual ~ScriptAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns the type of action. */ ScriptType scriptType() const; /** * Returns the code. */ QString script() const; private: Q_DECLARE_PRIVATE( ScriptAction ) Q_DISABLE_COPY( ScriptAction ) }; /** * The Movie action executes an operation on a video on activation. * * @since 0.15 (KDE 4.9) */ class OKULARCORE_EXPORT MovieAction : public Action { public: /** * Describes the possible operation types. */ enum OperationType { Play, Stop, Pause, Resume }; /** * Creates a new movie action. */ MovieAction( OperationType operation ); /** * Destroys the movie action. */ virtual ~MovieAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns the operation type. */ OperationType operation() const; /** * Sets the @p annotation that is associated with the movie action. */ void setAnnotation( MovieAnnotation *annotation ); /** * Returns the annotation or @c 0 if no annotation has been set. */ MovieAnnotation* annotation() const; private: Q_DECLARE_PRIVATE( MovieAction ) Q_DISABLE_COPY( MovieAction ) }; /** * The Rendition action executes an operation on a video or * executes some JavaScript code on activation. * * @since 0.16 (KDE 4.10) */ class OKULARCORE_EXPORT RenditionAction : public Action { public: /** * Describes the possible operation types. */ enum OperationType { None, ///< Execute only the JavaScript Play, ///< Start playing the video Stop, ///< Stop playing the video Pause, ///< Pause the video Resume ///< Resume playing the video }; /** * Creates a new rendition action. * * @param operation The type of operation the action executes. * @param movie The movie object the action references. * @param scriptType The type of script the action executes. * @param script The actual script the action executes. */ RenditionAction( OperationType operation, Okular::Movie *movie, enum ScriptType scriptType, const QString &script ); /** * Destroys the rendition action. */ virtual ~RenditionAction(); /** * Returns the action type. */ ActionType actionType() const override; /** * Returns the action tip. */ QString actionTip() const override; /** * Returns the operation type. */ OperationType operation() const; /** * Returns the movie object or @c 0 if no movie object was set on construction time. */ Okular::Movie* movie() const; /** * Returns the type of script. */ ScriptType scriptType() const; /** * Returns the script code. */ QString script() const; /** * Sets the @p annotation that is associated with the rendition action. */ void setAnnotation( ScreenAnnotation *annotation ); /** * Returns the annotation or @c 0 if no annotation has been set. */ ScreenAnnotation* annotation() const; private: Q_DECLARE_PRIVATE( RenditionAction ) Q_DISABLE_COPY( RenditionAction ) }; class OKULARCORE_EXPORT BackendOpaqueAction : public Action { public: BackendOpaqueAction(); /** * Returns the action type. */ ActionType actionType() const override; private: Q_DISABLE_COPY( BackendOpaqueAction ) }; } #endif diff --git a/core/annotations.cpp b/core/annotations.cpp index d9f3fd439..3ac567063 100644 --- a/core/annotations.cpp +++ b/core/annotations.cpp @@ -1,3101 +1,3101 @@ /*************************************************************************** * Copyright (C) 2005 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 "annotations.h" #include "annotations_p.h" // qt/kde includes -#include -#include +#include +#include // DBL_MAX #include // local includes #include "action.h" #include "document.h" #include "document_p.h" #include "movie.h" #include "page_p.h" #include "sound.h" using namespace Okular; /** * True, if point @p c lies to the left of the vector from @p a to @p b * @internal */ static bool isLeftOfVector( const NormalizedPoint& a, const NormalizedPoint& b, const NormalizedPoint& c ) { //cross product return ( (b.x - a.x) * ( c.y - a.y) - ( b.y - a.y ) * ( c.x - a.x ) ) > 0; } /** * @brief Calculates distance of the given point @p x @p y @p xScale @p yScale to the @p path * * Does piecewise comparison and selects the distance to the closest segment */ static double distanceSqr( double x, double y, double xScale, double yScale, const QLinkedList& path ) { double distance = DBL_MAX; double thisDistance; QLinkedList::const_iterator i = path.constBegin(); NormalizedPoint lastPoint = *i; for (++i; i != path.constEnd(); ++i) { thisDistance = NormalizedPoint::distanceSqr( x, y, xScale, yScale, lastPoint, (*i) ); if ( thisDistance < distance ) distance = thisDistance; lastPoint = *i; } return distance; } /** * Given the squared @p distance from the idealized 0-width line and a pen width @p penWidth, * (not squared!), returns the final distance * * @warning The returned distance is not exact: * We calculate an (exact) squared distance to the ideal (centered) line, and then subtract * the squared width of the pen: * a^2 - b^2 where a = "distance from idealized 0-width line" b = "pen width" * For an exact result, we would want to calculate "(a - b)^2" but that would require * a square root operation because we only know the squared distance a^2. * * However, the approximation is feasible, because: * error = (a-b)^2 - (a^2 - b^2) = -2ab + 2b^2 = 2b(b - a) * Therefore: * lim_{a->b} a^2 - b^2 - a^2 + 2ab - b^2 --> 0 * * In other words, this approximation will estimate the distance to be slightly more than it actually is * for as long as we are far "outside" the line, becoming more accurate the closer we get to the line * boundary. Trivially, it also fulfils (a1 < a2) => ((a1^2 - b^2) < (a2^2 - b^2)) making it monotonic. * "Inside" of the drawn line, the distance is 0 anyway. */ static double strokeDistance( double distance, double penWidth ) { return fmax(distance - pow( penWidth, 2 ), 0); } //BEGIN AnnotationUtils implementation Annotation * AnnotationUtils::createAnnotation( const QDomElement & annElement ) { // safety check on annotation element if ( !annElement.hasAttribute( QStringLiteral("type") ) ) return nullptr; // build annotation of given type Annotation * annotation = nullptr; int typeNumber = annElement.attribute( QStringLiteral("type") ).toInt(); switch ( typeNumber ) { case Annotation::AText: annotation = new TextAnnotation( annElement ); break; case Annotation::ALine: annotation = new LineAnnotation( annElement ); break; case Annotation::AGeom: annotation = new GeomAnnotation( annElement ); break; case Annotation::AHighlight: annotation = new HighlightAnnotation( annElement ); break; case Annotation::AStamp: annotation = new StampAnnotation( annElement ); break; case Annotation::AInk: annotation = new InkAnnotation( annElement ); break; case Annotation::ACaret: annotation = new CaretAnnotation( annElement ); break; } // return created annotation return annotation; } void AnnotationUtils::storeAnnotation( const Annotation * ann, QDomElement & annElement, QDomDocument & document ) { // save annotation's type as element's attribute annElement.setAttribute( QStringLiteral("type"), (uint)ann->subType() ); // append all annotation data as children of this node ann->store( annElement, document ); } QDomElement AnnotationUtils::findChildElement( const QDomNode & parentNode, const QString & name ) { // loop through the whole children and return a 'name' named element QDomNode subNode = parentNode.firstChild(); while( subNode.isElement() ) { QDomElement element = subNode.toElement(); if ( element.tagName() == name ) return element; subNode = subNode.nextSibling(); } // if the name can't be found, return a dummy null element return QDomElement(); } QRect AnnotationUtils::annotationGeometry( const Annotation * ann, double scaledWidth, double scaledHeight ) { const QRect rect = ann->transformedBoundingRectangle().geometry( (int)scaledWidth, (int)scaledHeight ); if ( ann->subType() == Annotation::AText && ( ( (TextAnnotation*)ann )->textType() == TextAnnotation::Linked ) ) { // To be honest i have no clue of why the 24,24 is here, maybe to make sure it's not too small? // But why only for linked text? const QRect rect24 = QRect( (int)( ann->transformedBoundingRectangle().left * scaledWidth ), (int)( ann->transformedBoundingRectangle().top * scaledHeight ), 24, 24 ); return rect24.united(rect); } return rect; } //END AnnotationUtils implementation AnnotationProxy::~AnnotationProxy() { } //BEGIN Annotation implementation class Annotation::Style::Private { public: Private() : m_opacity( 1.0 ), m_width( 1.0 ), m_style( Solid ), m_xCorners( 0.0 ), m_yCorners( 0.0 ), m_marks( 3 ), m_spaces( 0 ), m_effect( NoEffect ), m_effectIntensity( 1.0 ) { } QColor m_color; double m_opacity; double m_width; LineStyle m_style; double m_xCorners; double m_yCorners; int m_marks; int m_spaces; LineEffect m_effect; double m_effectIntensity; }; Annotation::Style::Style() : d( new Private ) { } Annotation::Style::~Style() { delete d; } Annotation::Style::Style( const Style &other ) : d( new Private ) { *d = *other.d; } Annotation::Style& Annotation::Style::operator=( const Style &other ) { if ( this != &other ) *d = *other.d; return *this; } void Annotation::Style::setColor( const QColor &color ) { d->m_color = color; } QColor Annotation::Style::color() const { return d->m_color; } void Annotation::Style::setOpacity( double opacity ) { d->m_opacity = opacity; } double Annotation::Style::opacity() const { return d->m_opacity; } void Annotation::Style::setWidth( double width ) { d->m_width = width; } double Annotation::Style::width() const { return d->m_width; } void Annotation::Style::setLineStyle( LineStyle style ) { d->m_style = style; } Annotation::LineStyle Annotation::Style::lineStyle() const { return d->m_style; } void Annotation::Style::setXCorners( double xCorners ) { d->m_xCorners = xCorners; } double Annotation::Style::xCorners() const { return d->m_xCorners; } void Annotation::Style::setYCorners( double yCorners ) { d->m_yCorners = yCorners; } double Annotation::Style::yCorners() const { return d->m_yCorners; } void Annotation::Style::setMarks( int marks ) { d->m_marks = marks; } int Annotation::Style::marks() const { return d->m_marks; } void Annotation::Style::setSpaces( int spaces ) { d->m_spaces = spaces; } int Annotation::Style::spaces() const { return d->m_spaces; } void Annotation::Style::setLineEffect( LineEffect effect ) { d->m_effect = effect; } Annotation::LineEffect Annotation::Style::lineEffect() const { return d->m_effect; } void Annotation::Style::setEffectIntensity( double intensity ) { d->m_effectIntensity = intensity; } double Annotation::Style::effectIntensity() const { return d->m_effectIntensity; } class Annotation::Window::Private { public: Private() : m_flags( -1 ), m_width( 0 ), m_height( 0 ) { } int m_flags; NormalizedPoint m_topLeft; int m_width; int m_height; QString m_title; QString m_summary; }; Annotation::Window::Window() : d( new Private ) { } Annotation::Window::~Window() { delete d; } Annotation::Window::Window( const Window &other ) : d( new Private ) { *d = *other.d; } Annotation::Window& Annotation::Window::operator=( const Window &other ) { if ( this != &other ) *d = *other.d; return *this; } void Annotation::Window::setFlags( int flags ) { d->m_flags = flags; } int Annotation::Window::flags() const { return d->m_flags; } void Annotation::Window::setTopLeft( const NormalizedPoint &point ) { d->m_topLeft = point; } NormalizedPoint Annotation::Window::topLeft() const { return d->m_topLeft; } void Annotation::Window::setWidth( int width ) { d->m_width = width; } int Annotation::Window::width() const { return d->m_width; } void Annotation::Window::setHeight( int height ) { d->m_height = height; } int Annotation::Window::height() const { return d->m_height; } void Annotation::Window::setTitle( const QString &title ) { d->m_title = title; } QString Annotation::Window::title() const { return d->m_title; } void Annotation::Window::setSummary( const QString &summary ) { d->m_summary = summary; } QString Annotation::Window::summary() const { return d->m_summary; } class Annotation::Revision::Private { public: Private() : m_annotation( nullptr ), m_scope( Reply ), m_type( None ) { } Annotation *m_annotation; RevisionScope m_scope; RevisionType m_type; }; Annotation::Revision::Revision() : d( new Private ) { } Annotation::Revision::~Revision() { delete d; } Annotation::Revision::Revision( const Revision &other ) : d( new Private ) { *d = *other.d; } Annotation::Revision& Annotation::Revision::operator=( const Revision &other ) { if ( this != &other ) *d = *other.d; return *this; } void Annotation::Revision::setAnnotation( Annotation *annotation ) { d->m_annotation = annotation; } Annotation *Annotation::Revision::annotation() const { return d->m_annotation; } void Annotation::Revision::setScope( RevisionScope scope ) { d->m_scope = scope; } Annotation::RevisionScope Annotation::Revision::scope() const { return d->m_scope; } void Annotation::Revision::setType( RevisionType type ) { d->m_type = type; } Annotation::RevisionType Annotation::Revision::type() const { return d->m_type; } AnnotationPrivate::AnnotationPrivate() : m_page( nullptr ), m_flags( 0 ), m_disposeFunc( nullptr ) { } AnnotationPrivate::~AnnotationPrivate() { // delete all children revisions if ( m_revisions.isEmpty() ) return; QLinkedList< Annotation::Revision >::iterator it = m_revisions.begin(), end = m_revisions.end(); for ( ; it != end; ++it ) delete (*it).annotation(); } Annotation::Annotation( AnnotationPrivate &dd ) : d_ptr( &dd ) { } Annotation::Annotation( AnnotationPrivate &dd, const QDomNode & annNode ) : d_ptr( &dd ) { d_ptr->setAnnotationProperties( annNode ); } Annotation::~Annotation() { if ( d_ptr->m_disposeFunc ) d_ptr->m_disposeFunc( this ); delete d_ptr; } void Annotation::setAuthor( const QString &author ) { Q_D( Annotation ); d->m_author = author; } QString Annotation::author() const { Q_D( const Annotation ); return d->m_author; } void Annotation::setContents( const QString &contents ) { Q_D( Annotation ); d->m_contents = contents; } QString Annotation::contents() const { Q_D( const Annotation ); return d->m_contents; } void Annotation::setUniqueName( const QString &name ) { Q_D( Annotation ); d->m_uniqueName = name; } QString Annotation::uniqueName() const { Q_D( const Annotation ); return d->m_uniqueName; } void Annotation::setModificationDate( const QDateTime &date ) { Q_D( Annotation ); d->m_modifyDate = date; } QDateTime Annotation::modificationDate() const { Q_D( const Annotation ); return d->m_modifyDate; } void Annotation::setCreationDate( const QDateTime &date ) { Q_D( Annotation ); d->m_creationDate = date; } QDateTime Annotation::creationDate() const { Q_D( const Annotation ); return d->m_creationDate; } void Annotation::setFlags( int flags ) { Q_D( Annotation ); d->m_flags = flags; } int Annotation::flags() const { Q_D( const Annotation ); return d->m_flags; } void Annotation::setBoundingRectangle( const NormalizedRect &rectangle ) { Q_D( Annotation ); d->m_boundary = rectangle; d->resetTransformation(); if ( d->m_page ) { d->transform( d->m_page->rotationMatrix() ); } } NormalizedRect Annotation::boundingRectangle() const { Q_D( const Annotation ); return d->m_boundary; } NormalizedRect Annotation::transformedBoundingRectangle() const { Q_D( const Annotation ); return d->m_transformedBoundary; } void Annotation::translate( const NormalizedPoint &coord ) { Q_D( Annotation ); d->translate( coord ); d->resetTransformation(); if ( d->m_page ) { d->transform( d->m_page->rotationMatrix() ); } } void Annotation::adjust( const NormalizedPoint & deltaCoord1, const NormalizedPoint & deltaCoord2 ) { Q_D( Annotation ); d->adjust( deltaCoord1, deltaCoord2 ); d->resetTransformation(); if ( d->m_page ) { d->transform( d->m_page->rotationMatrix() ); } } bool Annotation::openDialogAfterCreation() const { Q_D( const Annotation ); return d->openDialogAfterCreation(); } Annotation::Style & Annotation::style() { Q_D( Annotation ); return d->m_style; } const Annotation::Style & Annotation::style() const { Q_D( const Annotation ); return d->m_style; } Annotation::Window & Annotation::window() { Q_D( Annotation ); return d->m_window; } const Annotation::Window & Annotation::window() const { Q_D( const Annotation ); return d->m_window; } QLinkedList< Annotation::Revision > & Annotation::revisions() { Q_D( Annotation ); return d->m_revisions; } const QLinkedList< Annotation::Revision > & Annotation::revisions() const { Q_D( const Annotation ); return d->m_revisions; } void Annotation::setNativeId( const QVariant &id ) { Q_D( Annotation ); d->m_nativeId = id; } QVariant Annotation::nativeId() const { Q_D( const Annotation ); return d->m_nativeId; } void Annotation::setDisposeDataFunction( DisposeDataFunction func ) { Q_D( Annotation ); d->m_disposeFunc = func; } bool Annotation::canBeMoved() const { Q_D( const Annotation ); // Don't move annotations if they cannot be modified if ( !d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this) ) return false; // highlight "requires" to be "bounded" to text, and that's tricky for now if ( subType() == AHighlight ) return false; return true; } bool Annotation::canBeResized() const { Q_D( const Annotation ); // Don't resize annotations if they cannot be modified if ( !d->m_page || !d->m_page->m_doc->m_parent->canModifyPageAnnotation(this) ) return false; return d->canBeResized(); } void Annotation::store( QDomNode & annNode, QDomDocument & document ) const { Q_D( const Annotation ); // create [base] element of the annotation node QDomElement e = document.createElement( QStringLiteral("base") ); annNode.appendChild( e ); // store -contents- attributes if ( !d->m_author.isEmpty() ) e.setAttribute( QStringLiteral("author"), d->m_author ); if ( !d->m_contents.isEmpty() ) e.setAttribute( QStringLiteral("contents"), d->m_contents ); if ( !d->m_uniqueName.isEmpty() ) e.setAttribute( QStringLiteral("uniqueName"), d->m_uniqueName ); if ( d->m_modifyDate.isValid() ) e.setAttribute( QStringLiteral("modifyDate"), d->m_modifyDate.toString(Qt::ISODate) ); if ( d->m_creationDate.isValid() ) e.setAttribute( QStringLiteral("creationDate"), d->m_creationDate.toString(Qt::ISODate) ); // store -other- attributes if ( d->m_flags ) // Strip internal flags e.setAttribute( QStringLiteral("flags"), d->m_flags & ~(External | ExternallyDrawn | BeingMoved | BeingResized ) ); if ( d->m_style.color().isValid() ) e.setAttribute( QStringLiteral("color"), d->m_style.color().name() ); if ( d->m_style.opacity() != 1.0 ) e.setAttribute( QStringLiteral("opacity"), QString::number( d->m_style.opacity() ) ); // Sub-Node-1 - boundary QDomElement bE = document.createElement( QStringLiteral("boundary") ); e.appendChild( bE ); bE.setAttribute( QStringLiteral("l"), QString::number( d->m_boundary.left ) ); bE.setAttribute( QStringLiteral("t"), QString::number( d->m_boundary.top ) ); bE.setAttribute( QStringLiteral("r"), QString::number( d->m_boundary.right ) ); bE.setAttribute( QStringLiteral("b"), QString::number( d->m_boundary.bottom ) ); // Sub-Node-2 - penStyle if ( d->m_style.width() != 1 || d->m_style.lineStyle() != Solid || d->m_style.xCorners() != 0 || d->m_style.yCorners() != 0.0 || d->m_style.marks() != 3 || d->m_style.spaces() != 0 ) { QDomElement psE = document.createElement( QStringLiteral("penStyle") ); e.appendChild( psE ); psE.setAttribute( QStringLiteral("width"), QString::number( d->m_style.width() ) ); psE.setAttribute( QStringLiteral("style"), (int)d->m_style.lineStyle() ); psE.setAttribute( QStringLiteral("xcr"), QString::number( d->m_style.xCorners() ) ); psE.setAttribute( QStringLiteral("ycr"), QString::number( d->m_style.yCorners() ) ); psE.setAttribute( QStringLiteral("marks"), d->m_style.marks() ); psE.setAttribute( QStringLiteral("spaces"), d->m_style.spaces() ); } // Sub-Node-3 - penEffect if ( d->m_style.lineEffect() != NoEffect || d->m_style.effectIntensity() != 1.0 ) { QDomElement peE = document.createElement( QStringLiteral("penEffect") ); e.appendChild( peE ); peE.setAttribute( QStringLiteral("effect"), (int)d->m_style.lineEffect() ); peE.setAttribute( QStringLiteral("intensity"), QString::number( d->m_style.effectIntensity() ) ); } // Sub-Node-4 - window if ( d->m_window.flags() != -1 || !d->m_window.title().isEmpty() || !d->m_window.summary().isEmpty() ) { QDomElement wE = document.createElement( QStringLiteral("window") ); e.appendChild( wE ); wE.setAttribute( QStringLiteral("flags"), d->m_window.flags() ); wE.setAttribute( QStringLiteral("top"), QString::number( d->m_window.topLeft().x ) ); wE.setAttribute( QStringLiteral("left"), QString::number( d->m_window.topLeft().y ) ); wE.setAttribute( QStringLiteral("width"), d->m_window.width() ); wE.setAttribute( QStringLiteral("height"), d->m_window.height() ); wE.setAttribute( QStringLiteral("title"), d->m_window.title() ); wE.setAttribute( QStringLiteral("summary"), d->m_window.summary() ); } // create [revision] element of the annotation node (if any) if ( d->m_revisions.isEmpty() ) return; // add all revisions as children of revisions element QLinkedList< Revision >::const_iterator it = d->m_revisions.begin(), end = d->m_revisions.end(); for ( ; it != end; ++it ) { // create revision element const Revision & revision = *it; QDomElement r = document.createElement( QStringLiteral("revision") ); annNode.appendChild( r ); // set element attributes r.setAttribute( QStringLiteral("revScope"), (int)revision.scope() ); r.setAttribute( QStringLiteral("revType"), (int)revision.type() ); // use revision as the annotation element, so fill it up AnnotationUtils::storeAnnotation( revision.annotation(), r, document ); } } QDomNode Annotation::getAnnotationPropertiesDomNode() const { QDomDocument doc( QStringLiteral("documentInfo") ); QDomElement node = doc.createElement( QStringLiteral("annotation") ); store(node, doc); return node; } void Annotation::setAnnotationProperties( const QDomNode& node ) { // Save off internal properties that aren't contained in node Okular::PagePrivate *p = d_ptr->m_page; QVariant nativeID = d_ptr->m_nativeId; const int internalFlags = d_ptr->m_flags & (External | ExternallyDrawn | BeingMoved | BeingResized ); Annotation::DisposeDataFunction disposeFunc = d_ptr->m_disposeFunc; // Replace AnnotationPrivate object with a fresh copy AnnotationPrivate *new_d_ptr = d_ptr->getNewAnnotationPrivate(); delete( d_ptr ); d_ptr = new_d_ptr; // Set the annotations properties from node d_ptr->setAnnotationProperties(node); // Restore internal properties d_ptr->m_page = p; d_ptr->m_nativeId = nativeID; d_ptr->m_flags = d_ptr->m_flags | internalFlags; d_ptr->m_disposeFunc = disposeFunc; // Transform annotation to current page rotation d_ptr->transform( d_ptr->m_page->rotationMatrix() ); } double AnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) { return m_transformedBoundary.distanceSqr( x, y, xScale, yScale ); } void AnnotationPrivate::annotationTransform( const QTransform &matrix ) { resetTransformation(); transform( matrix ); } void AnnotationPrivate::transform( const QTransform &matrix ) { m_transformedBoundary.transform( matrix ); } void AnnotationPrivate::baseTransform( const QTransform &matrix ) { m_boundary.transform( matrix ); } void AnnotationPrivate::resetTransformation() { m_transformedBoundary = m_boundary; } void AnnotationPrivate::translate( const NormalizedPoint &coord ) { m_boundary.left = m_boundary.left + coord.x; m_boundary.right = m_boundary.right + coord.x; m_boundary.top = m_boundary.top + coord.y; m_boundary.bottom = m_boundary.bottom + coord.y; } void AnnotationPrivate::adjust( const NormalizedPoint &deltaCoord1, const NormalizedPoint &deltaCoord2 ) { m_boundary.left = m_boundary.left + qBound( -m_boundary.left, deltaCoord1.x, m_boundary.right - m_boundary.left ); m_boundary.top = m_boundary.top + qBound( -m_boundary.top, deltaCoord1.y, m_boundary.bottom - m_boundary.top );; m_boundary.right = m_boundary.right + qBound( m_boundary.left - m_boundary.right, deltaCoord2.x, 1. - m_boundary.right ); m_boundary.bottom = m_boundary.bottom + qBound( m_boundary.top - m_boundary.bottom, deltaCoord2.y, 1. - m_boundary.bottom ); } bool AnnotationPrivate::openDialogAfterCreation() const { return false; } void AnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { // get the [base] element of the annotation node QDomElement e = AnnotationUtils::findChildElement( node, QStringLiteral("base") ); if ( e.isNull() ) return; // parse -contents- attributes if ( e.hasAttribute( QStringLiteral("author") ) ) m_author = e.attribute( QStringLiteral("author") ); if ( e.hasAttribute( QStringLiteral("contents") ) ) m_contents = e.attribute( QStringLiteral("contents") ); if ( e.hasAttribute( QStringLiteral("uniqueName") ) ) m_uniqueName = e.attribute( QStringLiteral("uniqueName") ); if ( e.hasAttribute( QStringLiteral("modifyDate") ) ) m_modifyDate = QDateTime::fromString( e.attribute(QStringLiteral("modifyDate")), Qt::ISODate ); if ( e.hasAttribute( QStringLiteral("creationDate") ) ) m_creationDate = QDateTime::fromString( e.attribute(QStringLiteral("creationDate")), Qt::ISODate ); // parse -other- attributes if ( e.hasAttribute( QStringLiteral("flags") ) ) m_flags = e.attribute( QStringLiteral("flags") ).toInt(); if ( e.hasAttribute( QStringLiteral("color") ) ) m_style.setColor( QColor( e.attribute( QStringLiteral("color") ) ) ); if ( e.hasAttribute( QStringLiteral("opacity") ) ) m_style.setOpacity( e.attribute( QStringLiteral("opacity") ).toDouble() ); // parse -the-subnodes- (describing Style, Window, Revision(s) structures) // Note: all subnodes if present must be 'attributes complete' QDomNode eSubNode = e.firstChild(); while ( eSubNode.isElement() ) { QDomElement ee = eSubNode.toElement(); eSubNode = eSubNode.nextSibling(); // parse boundary if ( ee.tagName() == QLatin1String("boundary") ) { m_boundary=NormalizedRect(ee.attribute( QStringLiteral("l") ).toDouble(), ee.attribute( QStringLiteral("t") ).toDouble(), ee.attribute( QStringLiteral("r") ).toDouble(), ee.attribute( QStringLiteral("b") ).toDouble()); } // parse penStyle if not default else if ( ee.tagName() == QLatin1String("penStyle") ) { m_style.setWidth( ee.attribute( QStringLiteral("width") ).toDouble() ); m_style.setLineStyle( (Annotation::LineStyle)ee.attribute( QStringLiteral("style") ).toInt() ); m_style.setXCorners( ee.attribute( QStringLiteral("xcr") ).toDouble() ); m_style.setYCorners( ee.attribute( QStringLiteral("ycr") ).toDouble() ); m_style.setMarks( ee.attribute( QStringLiteral("marks") ).toInt() ); m_style.setSpaces( ee.attribute( QStringLiteral("spaces") ).toInt() ); } // parse effectStyle if not default else if ( ee.tagName() == QLatin1String("penEffect") ) { m_style.setLineEffect( (Annotation::LineEffect)ee.attribute( QStringLiteral("effect") ).toInt() ); m_style.setEffectIntensity( ee.attribute( QStringLiteral("intensity") ).toDouble() ); } // parse window if present else if ( ee.tagName() == QLatin1String("window") ) { m_window.setFlags( ee.attribute( QStringLiteral("flags") ).toInt() ); m_window.setTopLeft( NormalizedPoint( ee.attribute( QStringLiteral("top") ).toDouble(), ee.attribute( QStringLiteral("left") ).toDouble() ) ); m_window.setWidth( ee.attribute( QStringLiteral("width") ).toInt() ); m_window.setHeight( ee.attribute( QStringLiteral("height") ).toInt() ); m_window.setTitle( ee.attribute( QStringLiteral("title") ) ); m_window.setSummary( ee.attribute( QStringLiteral("summary") ) ); } } // get the [revisions] element of the annotation node QDomNode revNode = node.firstChild(); for ( ; revNode.isElement(); revNode = revNode.nextSibling() ) { QDomElement revElement = revNode.toElement(); if ( revElement.tagName() != QLatin1String("revision") ) continue; // compile the Revision structure crating annotation Annotation::Revision revision; revision.setScope( (Annotation::RevisionScope)revElement.attribute( QStringLiteral("revScope") ).toInt() ); revision.setType( (Annotation::RevisionType)revElement.attribute( QStringLiteral("revType") ).toInt() ); revision.setAnnotation( AnnotationUtils::createAnnotation( revElement ) ); // if annotation is valid, add revision to internal list if ( revision.annotation() ) m_revisions.append( revision ); } m_transformedBoundary = m_boundary; } bool AnnotationPrivate::canBeResized() const { return false; } //END Annotation implementation /** TextAnnotation [Annotation] */ class Okular::TextAnnotationPrivate : public Okular::AnnotationPrivate { public: TextAnnotationPrivate() : AnnotationPrivate(), m_textType( TextAnnotation::Linked ), m_textIcon( QStringLiteral("Comment") ), m_inplaceAlign( 0 ), m_inplaceIntent( TextAnnotation::Unknown ) { } void transform( const QTransform &matrix ) override; void baseTransform( const QTransform &matrix ) override; void resetTransformation() override; void translate( const NormalizedPoint &coord ) override; bool openDialogAfterCreation() const override; void setAnnotationProperties( const QDomNode& node ) override; bool canBeResized() const override; AnnotationPrivate* getNewAnnotationPrivate() override; TextAnnotation::TextType m_textType; QString m_textIcon; QFont m_textFont; int m_inplaceAlign; NormalizedPoint m_inplaceCallout[3]; NormalizedPoint m_transformedInplaceCallout[3]; TextAnnotation::InplaceIntent m_inplaceIntent; }; /* The default textIcon for text annotation is Note as the PDF Reference says */ TextAnnotation::TextAnnotation() : Annotation( *new TextAnnotationPrivate() ) { } TextAnnotation::TextAnnotation( const QDomNode & node ) : Annotation( *new TextAnnotationPrivate(), node ) { } TextAnnotation::~TextAnnotation() { } void TextAnnotation::setTextType( TextType textType ) { Q_D( TextAnnotation ); d->m_textType = textType; } TextAnnotation::TextType TextAnnotation::textType() const { Q_D( const TextAnnotation ); return d->m_textType; } void TextAnnotation::setTextIcon( const QString &icon ) { Q_D( TextAnnotation ); d->m_textIcon = icon; } QString TextAnnotation::textIcon() const { Q_D( const TextAnnotation ); return d->m_textIcon; } void TextAnnotation::setTextFont( const QFont &font ) { Q_D( TextAnnotation ); d->m_textFont = font; } QFont TextAnnotation::textFont() const { Q_D( const TextAnnotation ); return d->m_textFont; } void TextAnnotation::setInplaceAlignment( int alignment ) { Q_D( TextAnnotation ); d->m_inplaceAlign = alignment; } int TextAnnotation::inplaceAlignment() const { Q_D( const TextAnnotation ); return d->m_inplaceAlign; } void TextAnnotation::setInplaceCallout( const NormalizedPoint &point, int index ) { if ( index < 0 || index > 2 ) return; Q_D( TextAnnotation ); d->m_inplaceCallout[ index ] = point; } NormalizedPoint TextAnnotation::inplaceCallout( int index ) const { if ( index < 0 || index > 2 ) return NormalizedPoint(); Q_D( const TextAnnotation ); return d->m_inplaceCallout[ index ]; } NormalizedPoint TextAnnotation::transformedInplaceCallout( int index ) const { if ( index < 0 || index > 2 ) return NormalizedPoint(); Q_D( const TextAnnotation ); return d->m_transformedInplaceCallout[ index ]; } void TextAnnotation::setInplaceIntent( InplaceIntent intent ) { Q_D( TextAnnotation ); d->m_inplaceIntent = intent; } TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const { Q_D( const TextAnnotation ); return d->m_inplaceIntent; } Annotation::SubType TextAnnotation::subType() const { return AText; } void TextAnnotation::store( QDomNode & node, QDomDocument & document ) const { Q_D( const TextAnnotation ); // recurse to parent objects storing properties Annotation::store( node, document ); // create [text] element QDomElement textElement = document.createElement( QStringLiteral("text") ); node.appendChild( textElement ); // store the optional attributes if ( d->m_textType != Linked ) textElement.setAttribute( QStringLiteral("type"), (int)d->m_textType ); if ( !d->m_textIcon.isEmpty() ) textElement.setAttribute( QStringLiteral("icon"), d->m_textIcon ); if ( d->m_textFont != QApplication::font() ) textElement.setAttribute( QStringLiteral("font"), d->m_textFont.toString() ); if ( d->m_inplaceAlign ) textElement.setAttribute( QStringLiteral("align"), d->m_inplaceAlign ); if ( d->m_inplaceIntent != Unknown ) textElement.setAttribute( QStringLiteral("intent"), (int)d->m_inplaceIntent ); // Sub-Node - callout if ( d->m_inplaceCallout[0].x != 0.0 ) { QDomElement calloutElement = document.createElement( QStringLiteral("callout") ); textElement.appendChild( calloutElement ); calloutElement.setAttribute( QStringLiteral("ax"), QString::number( d->m_inplaceCallout[0].x ) ); calloutElement.setAttribute( QStringLiteral("ay"), QString::number( d->m_inplaceCallout[0].y ) ); calloutElement.setAttribute( QStringLiteral("bx"), QString::number( d->m_inplaceCallout[1].x ) ); calloutElement.setAttribute( QStringLiteral("by"), QString::number( d->m_inplaceCallout[1].y ) ); calloutElement.setAttribute( QStringLiteral("cx"), QString::number( d->m_inplaceCallout[2].x ) ); calloutElement.setAttribute( QStringLiteral("cy"), QString::number( d->m_inplaceCallout[2].y ) ); } } void TextAnnotationPrivate::transform( const QTransform &matrix ) { AnnotationPrivate::transform( matrix ); for ( int i = 0; i < 3; ++i ) { m_transformedInplaceCallout[i].transform( matrix ); } } void TextAnnotationPrivate::baseTransform( const QTransform &matrix ) { AnnotationPrivate::baseTransform( matrix ); for ( int i = 0; i < 3; ++i ) { m_inplaceCallout[i].transform( matrix ); } } void TextAnnotationPrivate::resetTransformation() { AnnotationPrivate::resetTransformation(); for ( int i = 0; i < 3; ++i ) { m_transformedInplaceCallout[i] = m_inplaceCallout[i]; } } void TextAnnotationPrivate::translate( const NormalizedPoint &coord ) { AnnotationPrivate::translate( coord ); #define ADD_COORD( c1, c2 ) \ { \ c1.x = c1.x + c2.x; \ c1.y = c1.y + c2.y; \ } ADD_COORD( m_inplaceCallout[0], coord ) ADD_COORD( m_inplaceCallout[1], coord ) ADD_COORD( m_inplaceCallout[2], coord ) #undef ADD_COORD } bool TextAnnotationPrivate::openDialogAfterCreation() const { return ( m_textType == Okular::TextAnnotation::Linked ); } void TextAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'text' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("text") ) continue; // parse the attributes if ( e.hasAttribute( QStringLiteral("type") ) ) m_textType = (TextAnnotation::TextType)e.attribute( QStringLiteral("type") ).toInt(); if ( e.hasAttribute( QStringLiteral("icon") ) ) m_textIcon = e.attribute( QStringLiteral("icon") ); if ( e.hasAttribute( QStringLiteral("font") ) ) m_textFont.fromString( e.attribute( QStringLiteral("font") ) ); if ( e.hasAttribute( QStringLiteral("align") ) ) m_inplaceAlign = e.attribute( QStringLiteral("align") ).toInt(); if ( e.hasAttribute( QStringLiteral("intent") ) ) m_inplaceIntent = (TextAnnotation::InplaceIntent)e.attribute( QStringLiteral("intent") ).toInt(); // parse the subnodes QDomNode eSubNode = e.firstChild(); while ( eSubNode.isElement() ) { QDomElement ee = eSubNode.toElement(); eSubNode = eSubNode.nextSibling(); if ( ee.tagName() == QLatin1String("escapedText") ) { m_contents = ee.firstChild().toCDATASection().data(); } else if ( ee.tagName() == QLatin1String("callout") ) { m_inplaceCallout[0].x = ee.attribute( QStringLiteral("ax") ).toDouble(); m_inplaceCallout[0].y = ee.attribute( QStringLiteral("ay") ).toDouble(); m_inplaceCallout[1].x = ee.attribute( QStringLiteral("bx") ).toDouble(); m_inplaceCallout[1].y = ee.attribute( QStringLiteral("by") ).toDouble(); m_inplaceCallout[2].x = ee.attribute( QStringLiteral("cx") ).toDouble(); m_inplaceCallout[2].y = ee.attribute( QStringLiteral("cy") ).toDouble(); } } // loading complete break; } for ( int i = 0; i < 3; ++i ) m_transformedInplaceCallout[i] = m_inplaceCallout[i]; } bool TextAnnotationPrivate::canBeResized() const { if ( m_textType != TextAnnotation::Linked ) { return true; } return false; } AnnotationPrivate* TextAnnotationPrivate::getNewAnnotationPrivate() { return new TextAnnotationPrivate(); } /** LineAnnotation [Annotation] */ class Okular::LineAnnotationPrivate : public Okular::AnnotationPrivate { public: LineAnnotationPrivate() : AnnotationPrivate(), m_lineStartStyle( LineAnnotation::None ), m_lineEndStyle( LineAnnotation::None ), m_lineClosed( false ), m_lineShowCaption( false ), m_lineLeadingFwdPt( 0 ), m_lineLeadingBackPt( 0 ), m_lineIntent( LineAnnotation::Unknown ) { } void transform( const QTransform &matrix ) override; void baseTransform( const QTransform &matrix ) override; void resetTransformation() override; void translate( const NormalizedPoint &coord ) override; double distanceSqr( double x, double y, double xScale, double yScale ) override; void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; QLinkedList m_linePoints; QLinkedList m_transformedLinePoints; LineAnnotation::TermStyle m_lineStartStyle; LineAnnotation::TermStyle m_lineEndStyle; bool m_lineClosed : 1; bool m_lineShowCaption : 1; QColor m_lineInnerColor; double m_lineLeadingFwdPt; double m_lineLeadingBackPt; LineAnnotation::LineIntent m_lineIntent; }; LineAnnotation::LineAnnotation() : Annotation( *new LineAnnotationPrivate() ) { } LineAnnotation::LineAnnotation( const QDomNode & node ) : Annotation( *new LineAnnotationPrivate(), node ) { } LineAnnotation::~LineAnnotation() { } void LineAnnotation::setLinePoints( const QLinkedList &points ) { Q_D( LineAnnotation ); d->m_linePoints = points; } QLinkedList LineAnnotation::linePoints() const { Q_D( const LineAnnotation ); return d->m_linePoints; } QLinkedList LineAnnotation::transformedLinePoints() const { Q_D( const LineAnnotation ); return d->m_transformedLinePoints; } void LineAnnotation::setLineStartStyle( TermStyle style ) { Q_D( LineAnnotation ); d->m_lineStartStyle = style; } LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const { Q_D( const LineAnnotation ); return d->m_lineStartStyle; } void LineAnnotation::setLineEndStyle( TermStyle style ) { Q_D( LineAnnotation ); d->m_lineEndStyle = style; } LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const { Q_D( const LineAnnotation ); return d->m_lineEndStyle; } void LineAnnotation::setLineClosed( bool closed ) { Q_D( LineAnnotation ); d->m_lineClosed = closed; } bool LineAnnotation::lineClosed() const { Q_D( const LineAnnotation ); return d->m_lineClosed; } void LineAnnotation::setLineInnerColor( const QColor &color ) { Q_D( LineAnnotation ); d->m_lineInnerColor = color; } QColor LineAnnotation::lineInnerColor() const { Q_D( const LineAnnotation ); return d->m_lineInnerColor; } void LineAnnotation::setLineLeadingForwardPoint( double point ) { Q_D( LineAnnotation ); d->m_lineLeadingFwdPt = point; } double LineAnnotation::lineLeadingForwardPoint() const { Q_D( const LineAnnotation ); return d->m_lineLeadingFwdPt; } void LineAnnotation::setLineLeadingBackwardPoint( double point ) { Q_D( LineAnnotation ); d->m_lineLeadingBackPt = point; } double LineAnnotation::lineLeadingBackwardPoint() const { Q_D( const LineAnnotation ); return d->m_lineLeadingBackPt; } void LineAnnotation::setShowCaption( bool show ) { Q_D( LineAnnotation ); d->m_lineShowCaption = show; } bool LineAnnotation::showCaption() const { Q_D( const LineAnnotation ); return d->m_lineShowCaption; } void LineAnnotation::setLineIntent( LineIntent intent ) { Q_D( LineAnnotation ); d->m_lineIntent = intent; } LineAnnotation::LineIntent LineAnnotation::lineIntent() const { Q_D( const LineAnnotation ); return d->m_lineIntent; } Annotation::SubType LineAnnotation::subType() const { return ALine; } void LineAnnotation::store( QDomNode & node, QDomDocument & document ) const { Q_D( const LineAnnotation ); // recurse to parent objects storing properties Annotation::store( node, document ); // create [line] element QDomElement lineElement = document.createElement( QStringLiteral("line") ); node.appendChild( lineElement ); // store the attributes if ( d->m_lineStartStyle != None ) lineElement.setAttribute( QStringLiteral("startStyle"), (int)d->m_lineStartStyle ); if ( d->m_lineEndStyle != None ) lineElement.setAttribute( QStringLiteral("endStyle"), (int)d->m_lineEndStyle ); if ( d->m_lineClosed ) lineElement.setAttribute( QStringLiteral("closed"), d->m_lineClosed ); if ( d->m_lineInnerColor.isValid() ) lineElement.setAttribute( QStringLiteral("innerColor"), d->m_lineInnerColor.name() ); if ( d->m_lineLeadingFwdPt != 0.0 ) lineElement.setAttribute( QStringLiteral("leadFwd"), QString::number( d->m_lineLeadingFwdPt ) ); if ( d->m_lineLeadingBackPt != 0.0 ) lineElement.setAttribute( QStringLiteral("leadBack"), QString::number( d->m_lineLeadingBackPt ) ); if ( d->m_lineShowCaption ) lineElement.setAttribute( QStringLiteral("showCaption"), d->m_lineShowCaption ); if ( d->m_lineIntent != Unknown ) lineElement.setAttribute( QStringLiteral("intent"), d->m_lineIntent ); // append the list of points int points = d->m_linePoints.count(); if ( points > 1 ) { QLinkedList::const_iterator it = d->m_linePoints.begin(), end = d->m_linePoints.end(); while ( it != end ) { const NormalizedPoint & p = *it; QDomElement pElement = document.createElement( QStringLiteral("point") ); lineElement.appendChild( pElement ); pElement.setAttribute( QStringLiteral("x"), QString::number( p.x ) ); pElement.setAttribute( QStringLiteral("y"), QString::number( p.y ) ); it++; //to avoid loop } } } void LineAnnotationPrivate::transform( const QTransform &matrix ) { AnnotationPrivate::transform( matrix ); QMutableLinkedListIterator it( m_transformedLinePoints ); while ( it.hasNext() ) it.next().transform( matrix ); } void LineAnnotationPrivate::baseTransform( const QTransform &matrix ) { AnnotationPrivate::baseTransform( matrix ); QMutableLinkedListIterator it( m_linePoints ); while ( it.hasNext() ) it.next().transform( matrix ); } void LineAnnotationPrivate::resetTransformation() { AnnotationPrivate::resetTransformation(); m_transformedLinePoints = m_linePoints; } void LineAnnotationPrivate::translate( const NormalizedPoint &coord ) { AnnotationPrivate::translate( coord ); QMutableLinkedListIterator it( m_linePoints ); while ( it.hasNext() ) { NormalizedPoint& p = it.next(); p.x = p.x + coord.x; p.y = p.y + coord.y; } } void LineAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'line' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("line") ) continue; // parse the attributes if ( e.hasAttribute( QStringLiteral("startStyle") ) ) m_lineStartStyle = (LineAnnotation::TermStyle)e.attribute( QStringLiteral("startStyle") ).toInt(); if ( e.hasAttribute( QStringLiteral("endStyle") ) ) m_lineEndStyle = (LineAnnotation::TermStyle)e.attribute( QStringLiteral("endStyle") ).toInt(); if ( e.hasAttribute( QStringLiteral("closed") ) ) m_lineClosed = e.attribute( QStringLiteral("closed") ).toInt(); if ( e.hasAttribute( QStringLiteral("innerColor") ) ) m_lineInnerColor = QColor( e.attribute( QStringLiteral("innerColor") ) ); if ( e.hasAttribute( QStringLiteral("leadFwd") ) ) m_lineLeadingFwdPt = e.attribute( QStringLiteral("leadFwd") ).toDouble(); if ( e.hasAttribute( QStringLiteral("leadBack") ) ) m_lineLeadingBackPt = e.attribute( QStringLiteral("leadBack") ).toDouble(); if ( e.hasAttribute( QStringLiteral("showCaption") ) ) m_lineShowCaption = e.attribute( QStringLiteral("showCaption") ).toInt(); if ( e.hasAttribute( QStringLiteral("intent") ) ) m_lineIntent = (LineAnnotation::LineIntent)e.attribute( QStringLiteral("intent") ).toInt(); // parse all 'point' subnodes QDomNode pointNode = e.firstChild(); while ( pointNode.isElement() ) { QDomElement pe = pointNode.toElement(); pointNode = pointNode.nextSibling(); if ( pe.tagName() != QLatin1String("point") ) continue; NormalizedPoint p; p.x = pe.attribute( QStringLiteral("x"), QStringLiteral("0.0") ).toDouble(); p.y = pe.attribute( QStringLiteral("y"), QStringLiteral("0.0") ).toDouble(); m_linePoints.append( p ); } // loading complete break; } m_transformedLinePoints = m_linePoints; } AnnotationPrivate* LineAnnotationPrivate::getNewAnnotationPrivate() { return new LineAnnotationPrivate(); } double LineAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) { QLinkedList transformedLinePoints = m_transformedLinePoints; if ( m_lineClosed ) // Close the path transformedLinePoints.append( transformedLinePoints.first() ); if ( m_lineInnerColor.isValid() ) { QPolygonF polygon; foreach ( const NormalizedPoint &p, transformedLinePoints ) polygon.append( QPointF( p.x, p.y ) ); if ( polygon.containsPoint( QPointF( x, y ), Qt::WindingFill ) ) return 0; } return strokeDistance( ::distanceSqr( x, y, xScale, yScale, transformedLinePoints ), m_style.width() * xScale / ( m_page->m_width * 2 ) ); } /** GeomAnnotation [Annotation] */ class Okular::GeomAnnotationPrivate : public Okular::AnnotationPrivate { public: GeomAnnotationPrivate() : AnnotationPrivate(), m_geomType( GeomAnnotation::InscribedSquare ) { } void setAnnotationProperties( const QDomNode& node ) override; bool canBeResized() const override; AnnotationPrivate* getNewAnnotationPrivate() override; double distanceSqr( double x, double y, double xScale, double yScale ) override; GeomAnnotation::GeomType m_geomType; QColor m_geomInnerColor; }; GeomAnnotation::GeomAnnotation() : Annotation( *new GeomAnnotationPrivate() ) { } GeomAnnotation::GeomAnnotation( const QDomNode & node ) : Annotation( *new GeomAnnotationPrivate(), node ) { } GeomAnnotation::~GeomAnnotation() { } void GeomAnnotation::setGeometricalType( GeomType type ) { Q_D( GeomAnnotation ); d->m_geomType = type; } GeomAnnotation::GeomType GeomAnnotation::geometricalType() const { Q_D( const GeomAnnotation ); return d->m_geomType; } void GeomAnnotation::setGeometricalInnerColor( const QColor &color ) { Q_D( GeomAnnotation ); d->m_geomInnerColor = color; } QColor GeomAnnotation::geometricalInnerColor() const { Q_D( const GeomAnnotation ); return d->m_geomInnerColor; } Annotation::SubType GeomAnnotation::subType() const { return AGeom; } void GeomAnnotation::store( QDomNode & node, QDomDocument & document ) const { Q_D( const GeomAnnotation ); // recurse to parent objects storing properties Annotation::store( node, document ); // create [geom] element QDomElement geomElement = document.createElement( QStringLiteral("geom") ); node.appendChild( geomElement ); // append the optional attributes if ( d->m_geomType != InscribedSquare ) geomElement.setAttribute( QStringLiteral("type"), (int)d->m_geomType ); if ( d->m_geomInnerColor.isValid() ) geomElement.setAttribute( QStringLiteral("color"), d->m_geomInnerColor.name() ); } void GeomAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'geom' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("geom") ) continue; // parse the attributes if ( e.hasAttribute( QStringLiteral("type") ) ) m_geomType = (GeomAnnotation::GeomType)e.attribute( QStringLiteral("type") ).toInt(); if ( e.hasAttribute( QStringLiteral("color") ) ) m_geomInnerColor = QColor( e.attribute( QStringLiteral("color") ) ); // compatibility if ( e.hasAttribute( QStringLiteral("width") ) ) m_style.setWidth( e.attribute( QStringLiteral("width") ).toInt() ); // loading complete break; } } bool GeomAnnotationPrivate::canBeResized() const { return true; } AnnotationPrivate* GeomAnnotationPrivate::getNewAnnotationPrivate() { return new GeomAnnotationPrivate(); } double GeomAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) { double distance = 0; //the line thickness is applied unevenly (only on the "inside") - account for this bool withinShape = false; switch (m_geomType) { case GeomAnnotation::InscribedCircle: { //calculate the center point and focus lengths of the ellipse const double centerX = ( m_transformedBoundary.left + m_transformedBoundary.right ) / 2.0; const double centerY = ( m_transformedBoundary.top + m_transformedBoundary.bottom ) / 2.0; const double focusX = ( m_transformedBoundary.right - centerX); const double focusY = ( m_transformedBoundary.bottom - centerY); const double focusXSqr = pow( focusX, 2 ); const double focusYSqr = pow( focusY, 2 ); // to calculate the distance from the ellipse, we will first find the point "projection" // that lies on the ellipse and is closest to the point (x,y) // This point can obviously be written as "center + lambda(inputPoint - center)". // Because the point lies on the ellipse, we know that: // 1 = ((center.x - projection.x)/focusX)^2 + ((center.y - projection.y)/focusY)^2 // After filling in projection.x = center.x + lambda * (inputPoint.x - center.x) // and its y-equivalent, we can solve for lambda: const double lambda = sqrt( focusXSqr * focusYSqr / ( focusYSqr * pow( x - centerX, 2 ) + focusXSqr * pow( y - centerY, 2 ) ) ); // if the ellipse is filled, we treat all points within as "on" it if ( lambda > 1 ) { if ( m_geomInnerColor.isValid() ) return 0; else withinShape = true; } //otherwise we calculate the squared distance from the projected point on the ellipse NormalizedPoint projection( centerX, centerY ); projection.x += lambda * ( x - centerX ); projection.y += lambda * ( y - centerY ); distance = projection.distanceSqr( x, y, xScale, yScale ); break; } case GeomAnnotation::InscribedSquare: //if the square is filled, only check the bounding box if ( m_geomInnerColor.isValid() ) return AnnotationPrivate::distanceSqr( x, y, xScale, yScale ); QLinkedList edges; edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.top ); edges << NormalizedPoint( m_transformedBoundary.right, m_transformedBoundary.top ); edges << NormalizedPoint( m_transformedBoundary.right, m_transformedBoundary.bottom ); edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.bottom ); edges << NormalizedPoint( m_transformedBoundary.left, m_transformedBoundary.top ); distance = ::distanceSqr( x, y, xScale, yScale, edges ); if ( m_transformedBoundary.contains( x, y ) ) withinShape = true; break; } if ( withinShape ) distance = strokeDistance( distance, m_style.width() * xScale / m_page->m_width ); return distance; } /** HighlightAnnotation [Annotation] */ class HighlightAnnotation::Quad::Private { public: Private() { } NormalizedPoint m_points[4]; NormalizedPoint m_transformedPoints[4]; bool m_capStart : 1; bool m_capEnd : 1; double m_feather; }; HighlightAnnotation::Quad::Quad() : d( new Private ) { } HighlightAnnotation::Quad::~Quad() { delete d; } HighlightAnnotation::Quad::Quad( const Quad &other ) : d( new Private ) { *d = *other.d; } HighlightAnnotation::Quad& HighlightAnnotation::Quad::operator=( const Quad &other ) { if ( this != &other ) *d = *other.d; return *this; } void HighlightAnnotation::Quad::setPoint( const NormalizedPoint &point, int index ) { if ( index < 0 || index > 3 ) return; d->m_points[ index ] = point; } NormalizedPoint HighlightAnnotation::Quad::point( int index ) const { if ( index < 0 || index > 3 ) return NormalizedPoint(); return d->m_points[ index ]; } NormalizedPoint HighlightAnnotation::Quad::transformedPoint( int index ) const { if ( index < 0 || index > 3 ) return NormalizedPoint(); return d->m_transformedPoints[ index ]; } void HighlightAnnotation::Quad::setCapStart( bool value ) { d->m_capStart = value; } bool HighlightAnnotation::Quad::capStart() const { return d->m_capStart; } void HighlightAnnotation::Quad::setCapEnd( bool value ) { d->m_capEnd = value; } bool HighlightAnnotation::Quad::capEnd() const { return d->m_capEnd; } void HighlightAnnotation::Quad::setFeather( double width ) { d->m_feather = width; } double HighlightAnnotation::Quad::feather() const { return d->m_feather; } void HighlightAnnotation::Quad::transform( const QTransform &matrix ) { for ( int i = 0; i < 4; ++i ) { d->m_transformedPoints[ i ] = d->m_points[ i ]; d->m_transformedPoints[ i ].transform( matrix ); } } class Okular::HighlightAnnotationPrivate : public Okular::AnnotationPrivate { public: HighlightAnnotationPrivate() : AnnotationPrivate(), m_highlightType( HighlightAnnotation::Highlight ) { } void transform( const QTransform &matrix ) override; void baseTransform( const QTransform &matrix ) override; double distanceSqr( double x, double y, double xScale, double yScale ) override; void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; HighlightAnnotation::HighlightType m_highlightType; QList< HighlightAnnotation::Quad > m_highlightQuads; }; HighlightAnnotation::HighlightAnnotation() : Annotation( *new HighlightAnnotationPrivate() ) { } HighlightAnnotation::HighlightAnnotation( const QDomNode & node ) : Annotation( *new HighlightAnnotationPrivate(), node ) { } HighlightAnnotation::~HighlightAnnotation() { } void HighlightAnnotation::setHighlightType( HighlightType type ) { Q_D( HighlightAnnotation ); d->m_highlightType = type; } HighlightAnnotation::HighlightType HighlightAnnotation::highlightType() const { Q_D( const HighlightAnnotation ); return d->m_highlightType; } QList< HighlightAnnotation::Quad > & HighlightAnnotation::highlightQuads() { Q_D( HighlightAnnotation ); return d->m_highlightQuads; } void HighlightAnnotation::store( QDomNode & node, QDomDocument & document ) const { Q_D( const HighlightAnnotation ); // recurse to parent objects storing properties Annotation::store( node, document ); // create [hl] element QDomElement hlElement = document.createElement( QStringLiteral("hl") ); node.appendChild( hlElement ); // append the optional attributes if ( d->m_highlightType != Highlight ) hlElement.setAttribute( QStringLiteral("type"), (int)d->m_highlightType ); if ( d->m_highlightQuads.count() < 1 ) return; // append highlight quads, all children describe quads QList< Quad >::const_iterator it = d->m_highlightQuads.begin(), end = d->m_highlightQuads.end(); for ( ; it != end; ++it ) { QDomElement quadElement = document.createElement( QStringLiteral("quad") ); hlElement.appendChild( quadElement ); const Quad & q = *it; quadElement.setAttribute( QStringLiteral("ax"), QString::number( q.point( 0 ).x ) ); quadElement.setAttribute( QStringLiteral("ay"), QString::number( q.point( 0 ).y ) ); quadElement.setAttribute( QStringLiteral("bx"), QString::number( q.point( 1 ).x ) ); quadElement.setAttribute( QStringLiteral("by"), QString::number( q.point( 1 ).y ) ); quadElement.setAttribute( QStringLiteral("cx"), QString::number( q.point( 2 ).x ) ); quadElement.setAttribute( QStringLiteral("cy"), QString::number( q.point( 2 ).y ) ); quadElement.setAttribute( QStringLiteral("dx"), QString::number( q.point( 3 ).x ) ); quadElement.setAttribute( QStringLiteral("dy"), QString::number( q.point( 3 ).y ) ); if ( q.capStart() ) quadElement.setAttribute( QStringLiteral("start"), 1 ); if ( q.capEnd() ) quadElement.setAttribute( QStringLiteral("end"), 1 ); quadElement.setAttribute( QStringLiteral("feather"), QString::number( q.feather() ) ); } } Annotation::SubType HighlightAnnotation::subType() const { return AHighlight; } void HighlightAnnotationPrivate::transform( const QTransform &matrix ) { AnnotationPrivate::transform( matrix ); QMutableListIterator it( m_highlightQuads ); while ( it.hasNext() ) it.next().transform( matrix ); } void HighlightAnnotationPrivate::baseTransform( const QTransform &matrix ) { AnnotationPrivate::baseTransform( matrix ); QMutableListIterator it( m_highlightQuads ); while ( it.hasNext() ) it.next().transform( matrix ); } void HighlightAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); m_highlightQuads.clear(); // loop through the whole children looking for a 'hl' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("hl") ) continue; // parse the attributes if ( e.hasAttribute( QStringLiteral("type") ) ) m_highlightType = (HighlightAnnotation::HighlightType)e.attribute( QStringLiteral("type") ).toInt(); // parse all 'quad' subnodes QDomNode quadNode = e.firstChild(); for ( ; quadNode.isElement(); quadNode = quadNode.nextSibling() ) { QDomElement qe = quadNode.toElement(); if ( qe.tagName() != QLatin1String("quad") ) continue; HighlightAnnotation::Quad q; q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("ax"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("ay"), QStringLiteral("0.0") ).toDouble() ), 0 ); q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("bx"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("by"), QStringLiteral("0.0") ).toDouble() ), 1 ); q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("cx"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("cy"), QStringLiteral("0.0") ).toDouble() ), 2 ); q.setPoint( NormalizedPoint( qe.attribute( QStringLiteral("dx"), QStringLiteral("0.0") ).toDouble(), qe.attribute( QStringLiteral("dy"), QStringLiteral("0.0") ).toDouble() ), 3 ); q.setCapStart( qe.hasAttribute( QStringLiteral("start") ) ); q.setCapEnd( qe.hasAttribute( QStringLiteral("end") ) ); q.setFeather( qe.attribute( QStringLiteral("feather"), QStringLiteral("0.1") ).toDouble() ); q.transform( QTransform() ); m_highlightQuads.append( q ); } // loading complete break; } } AnnotationPrivate* HighlightAnnotationPrivate::getNewAnnotationPrivate() { return new HighlightAnnotationPrivate(); } double HighlightAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) { NormalizedPoint point( x, y ); double outsideDistance = DBL_MAX; foreach ( const HighlightAnnotation::Quad& quad, m_highlightQuads ) { QLinkedList pathPoints; //first, we check if the point is within the area described by the 4 quads //this is the case, if the point is always on one side of each segments delimiting the polygon: pathPoints << quad.transformedPoint( 0 ); int directionVote = 0; for ( int i = 1; i < 5; ++i ) { NormalizedPoint thisPoint = quad.transformedPoint( i % 4 ); directionVote += (isLeftOfVector( pathPoints.back(), thisPoint, point )) ? 1 : -1; pathPoints << thisPoint; } if ( abs( directionVote ) == 4 ) return 0; //if that's not the case, we treat the outline as path and simply determine //the distance from the path to the point const double thisOutsideDistance = ::distanceSqr( x, y, xScale, yScale, pathPoints ); if ( thisOutsideDistance < outsideDistance ) outsideDistance = thisOutsideDistance; } return outsideDistance; } /** StampAnnotation [Annotation] */ class Okular::StampAnnotationPrivate : public Okular::AnnotationPrivate { public: StampAnnotationPrivate() : AnnotationPrivate(), m_stampIconName( QStringLiteral("Draft") ) { } void setAnnotationProperties( const QDomNode& node ) override; bool canBeResized() const override; AnnotationPrivate* getNewAnnotationPrivate() override; QString m_stampIconName; }; StampAnnotation::StampAnnotation() : Annotation( *new StampAnnotationPrivate() ) { } StampAnnotation::StampAnnotation( const QDomNode & node ) : Annotation( *new StampAnnotationPrivate(), node ) { } StampAnnotation::~StampAnnotation() { } void StampAnnotation::setStampIconName( const QString &name ) { Q_D( StampAnnotation ); d->m_stampIconName = name; } QString StampAnnotation::stampIconName() const { Q_D( const StampAnnotation ); return d->m_stampIconName; } Annotation::SubType StampAnnotation::subType() const { return AStamp; } void StampAnnotation::store( QDomNode & node, QDomDocument & document ) const { Q_D( const StampAnnotation ); // recurse to parent objects storing properties Annotation::store( node, document ); // create [stamp] element QDomElement stampElement = document.createElement( QStringLiteral("stamp") ); node.appendChild( stampElement ); // append the optional attributes if ( d->m_stampIconName != QLatin1String("Draft") ) stampElement.setAttribute( QStringLiteral("icon"), d->m_stampIconName ); } void StampAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'stamp' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("stamp") ) continue; // parse the attributes if ( e.hasAttribute( QStringLiteral("icon") ) ) m_stampIconName = e.attribute( QStringLiteral("icon") ); // loading complete break; } } bool StampAnnotationPrivate::canBeResized() const { return true; } AnnotationPrivate* StampAnnotationPrivate::getNewAnnotationPrivate() { return new StampAnnotationPrivate(); } /** InkAnnotation [Annotation] */ class Okular::InkAnnotationPrivate : public Okular::AnnotationPrivate { public: InkAnnotationPrivate() : AnnotationPrivate() { } void transform( const QTransform &matrix ) override; void baseTransform( const QTransform &matrix ) override; void resetTransformation() override; double distanceSqr( double x, double y, double xScale, double yScale ) override; void translate( const NormalizedPoint &coord ) override; void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; QList< QLinkedList > m_inkPaths; QList< QLinkedList > m_transformedInkPaths; }; InkAnnotation::InkAnnotation() : Annotation( *new InkAnnotationPrivate() ) { } InkAnnotation::InkAnnotation( const QDomNode & node ) : Annotation( *new InkAnnotationPrivate(), node ) { } InkAnnotation::~InkAnnotation() { } void InkAnnotation::setInkPaths( const QList< QLinkedList > &paths ) { Q_D( InkAnnotation ); d->m_inkPaths = paths; } QList< QLinkedList > InkAnnotation::inkPaths() const { Q_D( const InkAnnotation ); return d->m_inkPaths; } QList< QLinkedList > InkAnnotation::transformedInkPaths() const { Q_D( const InkAnnotation ); return d->m_transformedInkPaths; } Annotation::SubType InkAnnotation::subType() const { return AInk; } void InkAnnotation::store( QDomNode & node, QDomDocument & document ) const { Q_D( const InkAnnotation ); // recurse to parent objects storing properties Annotation::store( node, document ); // create [ink] element QDomElement inkElement = document.createElement( QStringLiteral("ink") ); node.appendChild( inkElement ); // append the optional attributes if ( d->m_inkPaths.count() < 1 ) return; QList< QLinkedList >::const_iterator pIt = d->m_inkPaths.begin(), pEnd = d->m_inkPaths.end(); for ( ; pIt != pEnd; ++pIt ) { QDomElement pathElement = document.createElement( QStringLiteral("path") ); inkElement.appendChild( pathElement ); const QLinkedList & path = *pIt; QLinkedList::const_iterator iIt = path.begin(), iEnd = path.end(); for ( ; iIt != iEnd; ++iIt ) { const NormalizedPoint & point = *iIt; QDomElement pointElement = document.createElement( QStringLiteral("point") ); pathElement.appendChild( pointElement ); pointElement.setAttribute( QStringLiteral("x"), QString::number( point.x ) ); pointElement.setAttribute( QStringLiteral("y"), QString::number( point.y ) ); } } } double InkAnnotationPrivate::distanceSqr( double x, double y, double xScale, double yScale ) { double distance = DBL_MAX; foreach ( const QLinkedList& path, m_transformedInkPaths ) { const double thisDistance = ::distanceSqr( x, y, xScale, yScale, path ); if ( thisDistance < distance ) distance = thisDistance; } return strokeDistance( distance, m_style.width() * xScale / ( m_page->m_width * 2 ) ); } void InkAnnotationPrivate::transform( const QTransform &matrix ) { AnnotationPrivate::transform( matrix ); for ( int i = 0; i < m_transformedInkPaths.count(); ++i ) { QMutableLinkedListIterator it( m_transformedInkPaths[ i ] ); while ( it.hasNext() ) it.next().transform( matrix ); } } void InkAnnotationPrivate::baseTransform( const QTransform &matrix ) { AnnotationPrivate::baseTransform( matrix ); for ( int i = 0; i < m_inkPaths.count(); ++i ) { QMutableLinkedListIterator it( m_inkPaths[ i ] ); while ( it.hasNext() ) it.next().transform( matrix ); } } void InkAnnotationPrivate::resetTransformation() { AnnotationPrivate::resetTransformation(); m_transformedInkPaths = m_inkPaths; } void InkAnnotationPrivate::translate( const NormalizedPoint &coord ) { AnnotationPrivate::translate( coord ); for ( int i = 0; i < m_inkPaths.count(); ++i ) { QMutableLinkedListIterator it( m_inkPaths[ i ] ); while ( it.hasNext() ) { NormalizedPoint& p = it.next(); p.x = p.x + coord.x; p.y = p.y + coord.y; } } } void InkAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); m_inkPaths.clear(); // loop through the whole children looking for a 'ink' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("ink") ) continue; // parse the 'path' subnodes QDomNode pathNode = e.firstChild(); while ( pathNode.isElement() ) { QDomElement pathElement = pathNode.toElement(); pathNode = pathNode.nextSibling(); if ( pathElement.tagName() != QLatin1String("path") ) continue; // build each path parsing 'point' subnodes QLinkedList path; QDomNode pointNode = pathElement.firstChild(); while ( pointNode.isElement() ) { QDomElement pointElement = pointNode.toElement(); pointNode = pointNode.nextSibling(); if ( pointElement.tagName() != QLatin1String("point") ) continue; NormalizedPoint p; p.x = pointElement.attribute( QStringLiteral("x"), QStringLiteral("0.0") ).toDouble(); p.y = pointElement.attribute( QStringLiteral("y"), QStringLiteral("0.0") ).toDouble(); path.append( p ); } // add the path to the path list if it contains at least 2 nodes if ( path.count() >= 2 ) m_inkPaths.append( path ); } // loading complete break; } m_transformedInkPaths = m_inkPaths; } AnnotationPrivate* InkAnnotationPrivate::getNewAnnotationPrivate() { return new InkAnnotationPrivate(); } /** CaretAnnotation [Annotation] */ class Okular::CaretAnnotationPrivate : public Okular::AnnotationPrivate { public: CaretAnnotationPrivate() : AnnotationPrivate(), m_symbol( CaretAnnotation::None ) { } void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; CaretAnnotation::CaretSymbol m_symbol; }; static QString caretSymbolToString( CaretAnnotation::CaretSymbol symbol ) { switch ( symbol ) { case CaretAnnotation::None: return QStringLiteral( "None" ); case CaretAnnotation::P: return QStringLiteral( "P" ); } return QString(); } static CaretAnnotation::CaretSymbol caretSymbolFromString( const QString &symbol ) { if ( symbol == QLatin1String( "None" ) ) return CaretAnnotation::None; else if ( symbol == QLatin1String( "P" ) ) return CaretAnnotation::P; return CaretAnnotation::None; } void CaretAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'caret' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("caret") ) continue; // parse the attributes if ( e.hasAttribute( QStringLiteral("symbol") ) ) m_symbol = caretSymbolFromString( e.attribute( QStringLiteral("symbol") ) ); // loading complete break; } } AnnotationPrivate* CaretAnnotationPrivate::getNewAnnotationPrivate() { return new CaretAnnotationPrivate(); } CaretAnnotation::CaretAnnotation() : Annotation( *new CaretAnnotationPrivate() ) { } CaretAnnotation::CaretAnnotation( const QDomNode & node ) : Annotation( *new CaretAnnotationPrivate(), node ) { } CaretAnnotation::~CaretAnnotation() { } void CaretAnnotation::setCaretSymbol( CaretAnnotation::CaretSymbol symbol ) { Q_D( CaretAnnotation ); d->m_symbol = symbol; } CaretAnnotation::CaretSymbol CaretAnnotation::caretSymbol() const { Q_D( const CaretAnnotation ); return d->m_symbol; } Annotation::SubType CaretAnnotation::subType() const { return ACaret; } void CaretAnnotation::store( QDomNode & node, QDomDocument & document ) const { Q_D( const CaretAnnotation ); // recurse to parent objects storing properties Annotation::store( node, document ); // create [caret] element QDomElement caretElement = document.createElement( QStringLiteral("caret") ); node.appendChild( caretElement ); // append the optional attributes if ( d->m_symbol != None ) caretElement.setAttribute( QStringLiteral("symbol"), caretSymbolToString( d->m_symbol ) ); } /** FileAttachmentAnnotation [Annotation] */ class Okular::FileAttachmentAnnotationPrivate : public Okular::AnnotationPrivate { public: FileAttachmentAnnotationPrivate() : AnnotationPrivate(), icon( QStringLiteral("PushPin") ), embfile( nullptr ) { } ~FileAttachmentAnnotationPrivate() override { delete embfile; } void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; // data fields QString icon; EmbeddedFile *embfile; }; void FileAttachmentAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'fileattachment' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("fileattachment") ) continue; // loading complete break; } } AnnotationPrivate* FileAttachmentAnnotationPrivate::getNewAnnotationPrivate() { return new FileAttachmentAnnotationPrivate(); } FileAttachmentAnnotation::FileAttachmentAnnotation() : Annotation( *new FileAttachmentAnnotationPrivate() ) { } FileAttachmentAnnotation::FileAttachmentAnnotation( const QDomNode & node ) : Annotation( *new FileAttachmentAnnotationPrivate(), node ) { } FileAttachmentAnnotation::~FileAttachmentAnnotation() { } void FileAttachmentAnnotation::store( QDomNode & node, QDomDocument & document ) const { // recurse to parent objects storing properties Annotation::store( node, document ); // create [fileattachment] element QDomElement fileAttachmentElement = document.createElement( QStringLiteral("fileattachment") ); node.appendChild( fileAttachmentElement ); } Annotation::SubType FileAttachmentAnnotation::subType() const { return AFileAttachment; } QString FileAttachmentAnnotation::fileIconName() const { Q_D( const FileAttachmentAnnotation ); return d->icon; } void FileAttachmentAnnotation::setFileIconName( const QString &icon ) { Q_D( FileAttachmentAnnotation ); d->icon = icon; } EmbeddedFile* FileAttachmentAnnotation::embeddedFile() const { Q_D( const FileAttachmentAnnotation ); return d->embfile; } void FileAttachmentAnnotation::setEmbeddedFile( EmbeddedFile *ef ) { Q_D( FileAttachmentAnnotation ); d->embfile = ef; } /** SoundAnnotation [Annotation] */ class Okular::SoundAnnotationPrivate : public Okular::AnnotationPrivate { public: SoundAnnotationPrivate() : AnnotationPrivate(), icon( QStringLiteral("Speaker") ), sound( nullptr ) { } ~SoundAnnotationPrivate() override { delete sound; } void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; // data fields QString icon; Sound *sound; }; void SoundAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'sound' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("sound") ) continue; // loading complete break; } } AnnotationPrivate* SoundAnnotationPrivate::getNewAnnotationPrivate() { return new SoundAnnotationPrivate(); } SoundAnnotation::SoundAnnotation() : Annotation( *new SoundAnnotationPrivate() ) { } SoundAnnotation::SoundAnnotation( const QDomNode & node ) : Annotation( *new SoundAnnotationPrivate(), node ) { } SoundAnnotation::~SoundAnnotation() { } void SoundAnnotation::store( QDomNode & node, QDomDocument & document ) const { // recurse to parent objects storing properties Annotation::store( node, document ); // create [sound] element QDomElement soundElement = document.createElement( QStringLiteral("sound") ); node.appendChild( soundElement ); } Annotation::SubType SoundAnnotation::subType() const { return ASound; } QString SoundAnnotation::soundIconName() const { Q_D( const SoundAnnotation ); return d->icon; } void SoundAnnotation::setSoundIconName( const QString &icon ) { Q_D( SoundAnnotation ); d->icon = icon; } Sound* SoundAnnotation::sound() const { Q_D( const SoundAnnotation ); return d->sound; } void SoundAnnotation::setSound( Sound *s ) { Q_D( SoundAnnotation ); d->sound = s; } /** MovieAnnotation [Annotation] */ class Okular::MovieAnnotationPrivate : public Okular::AnnotationPrivate { public: MovieAnnotationPrivate() : AnnotationPrivate(), movie( nullptr ) { } ~MovieAnnotationPrivate() override { delete movie; } void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; // data fields Movie *movie; }; void MovieAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'movie' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("movie") ) continue; // loading complete break; } } AnnotationPrivate* MovieAnnotationPrivate::getNewAnnotationPrivate() { return new MovieAnnotationPrivate(); } MovieAnnotation::MovieAnnotation() : Annotation( *new MovieAnnotationPrivate() ) { } MovieAnnotation::MovieAnnotation( const QDomNode & node ) : Annotation( *new MovieAnnotationPrivate(), node ) { } MovieAnnotation::~MovieAnnotation() { } void MovieAnnotation::store( QDomNode & node, QDomDocument & document ) const { // recurse to parent objects storing properties Annotation::store( node, document ); // create [movie] element QDomElement movieElement = document.createElement( QStringLiteral("movie") ); node.appendChild( movieElement ); } Annotation::SubType MovieAnnotation::subType() const { return AMovie; } Movie* MovieAnnotation::movie() const { Q_D( const MovieAnnotation ); return d->movie; } void MovieAnnotation::setMovie( Movie *movie ) { Q_D( MovieAnnotation ); d->movie = movie; } /** ScreenAnnotation [Annotation] */ class Okular::ScreenAnnotationPrivate : public Okular::AnnotationPrivate { public: ScreenAnnotationPrivate(); ~ScreenAnnotationPrivate() override; void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; Okular::Action* m_action; QMap< Okular::Annotation::AdditionalActionType, Okular::Action* > m_additionalActions; }; ScreenAnnotationPrivate::ScreenAnnotationPrivate() : m_action( nullptr ) { } ScreenAnnotationPrivate::~ScreenAnnotationPrivate() { delete m_action; qDeleteAll( m_additionalActions ); } void ScreenAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'screen' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("screen") ) continue; // loading complete break; } } AnnotationPrivate* ScreenAnnotationPrivate::getNewAnnotationPrivate() { return new ScreenAnnotationPrivate(); } ScreenAnnotation::ScreenAnnotation() : Annotation( *new ScreenAnnotationPrivate() ) { } ScreenAnnotation::ScreenAnnotation( const QDomNode & node ) : Annotation( *new ScreenAnnotationPrivate(), node ) { } ScreenAnnotation::~ScreenAnnotation() { } void ScreenAnnotation::store( QDomNode & node, QDomDocument & document ) const { // recurse to parent objects storing properties Annotation::store( node, document ); // create [screen] element QDomElement movieElement = document.createElement( QStringLiteral("screen") ); node.appendChild( movieElement ); } Annotation::SubType ScreenAnnotation::subType() const { return AScreen; } void ScreenAnnotation::setAdditionalAction( AdditionalActionType type, Action *action ) { Q_D( ScreenAnnotation ); if ( d->m_additionalActions.contains( type ) ) delete d->m_additionalActions.value( type ); d->m_additionalActions.insert( type, action ); } Action* ScreenAnnotation::additionalAction( AdditionalActionType type ) const { Q_D( const ScreenAnnotation ); if ( !d->m_additionalActions.contains( type ) ) return nullptr; else return d->m_additionalActions.value( type ); } void ScreenAnnotation::setAction( Action *action ) { Q_D( ScreenAnnotation ); delete d->m_action; d->m_action = action; } Action* ScreenAnnotation::action() const { Q_D( const ScreenAnnotation ); return d->m_action; } /** WidgetAnnotation [Annotation] */ class Okular::WidgetAnnotationPrivate : public Okular::AnnotationPrivate { public: ~WidgetAnnotationPrivate() override; void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; QMap< Okular::Annotation::AdditionalActionType, Okular::Action* > m_additionalActions; }; WidgetAnnotationPrivate::~WidgetAnnotationPrivate() { qDeleteAll( m_additionalActions ); } void WidgetAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'widget' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("widget") ) continue; // loading complete break; } } AnnotationPrivate* WidgetAnnotationPrivate::getNewAnnotationPrivate() { return new WidgetAnnotationPrivate(); } WidgetAnnotation::WidgetAnnotation() : Annotation( *new WidgetAnnotationPrivate() ) { } WidgetAnnotation::WidgetAnnotation( const QDomNode & node ) : Annotation( *new WidgetAnnotationPrivate, node ) { } WidgetAnnotation::~WidgetAnnotation() { } void WidgetAnnotation::store( QDomNode & node, QDomDocument & document ) const { // recurse to parent objects storing properties Annotation::store( node, document ); // create [widget] element QDomElement movieElement = document.createElement( QStringLiteral("widget") ); node.appendChild( movieElement ); } Annotation::SubType WidgetAnnotation::subType() const { return AWidget; } void WidgetAnnotation::setAdditionalAction( AdditionalActionType type, Action *action ) { Q_D( WidgetAnnotation ); if ( d->m_additionalActions.contains( type ) ) delete d->m_additionalActions.value( type ); d->m_additionalActions.insert( type, action ); } Action* WidgetAnnotation::additionalAction( AdditionalActionType type ) const { Q_D( const WidgetAnnotation ); if ( !d->m_additionalActions.contains( type ) ) return nullptr; else return d->m_additionalActions.value( type ); } /** RichMediaAnnotation [Annotation] */ class Okular::RichMediaAnnotationPrivate : public Okular::AnnotationPrivate { public: RichMediaAnnotationPrivate(); ~RichMediaAnnotationPrivate() override; void setAnnotationProperties( const QDomNode& node ) override; AnnotationPrivate* getNewAnnotationPrivate() override; // data fields Movie *movie; EmbeddedFile *embeddedFile; }; RichMediaAnnotationPrivate::RichMediaAnnotationPrivate() : movie( nullptr ), embeddedFile( nullptr ) { } RichMediaAnnotationPrivate::~RichMediaAnnotationPrivate() { delete movie; delete embeddedFile; } void RichMediaAnnotationPrivate::setAnnotationProperties( const QDomNode& node ) { Okular::AnnotationPrivate::setAnnotationProperties(node); // loop through the whole children looking for a 'richMedia' element QDomNode subNode = node.firstChild(); while( subNode.isElement() ) { QDomElement e = subNode.toElement(); subNode = subNode.nextSibling(); if ( e.tagName() != QLatin1String("richMedia") ) continue; // loading complete break; } } AnnotationPrivate* RichMediaAnnotationPrivate::getNewAnnotationPrivate() { return new RichMediaAnnotationPrivate(); } RichMediaAnnotation::RichMediaAnnotation() : Annotation( *new RichMediaAnnotationPrivate() ) { } RichMediaAnnotation::RichMediaAnnotation( const QDomNode & node ) : Annotation( *new RichMediaAnnotationPrivate, node ) { } RichMediaAnnotation::~RichMediaAnnotation() { } void RichMediaAnnotation::store( QDomNode & node, QDomDocument & document ) const { // recurse to parent objects storing properties Annotation::store( node, document ); // create [richMedia] element QDomElement movieElement = document.createElement( QStringLiteral("richMedia") ); node.appendChild( movieElement ); } Annotation::SubType RichMediaAnnotation::subType() const { return ARichMedia; } void RichMediaAnnotation::setMovie( Movie *movie ) { Q_D( RichMediaAnnotation ); delete d->movie; d->movie = movie; } Movie* RichMediaAnnotation::movie() const { Q_D( const RichMediaAnnotation ); return d->movie; } EmbeddedFile* RichMediaAnnotation::embeddedFile() const { Q_D( const RichMediaAnnotation ); return d->embeddedFile; } void RichMediaAnnotation::setEmbeddedFile( EmbeddedFile *embeddedFile ) { Q_D( RichMediaAnnotation ); delete d->embeddedFile; d->embeddedFile = embeddedFile; } diff --git a/core/annotations.h b/core/annotations.h index d5771c8cc..5c9397cb1 100644 --- a/core/annotations.h +++ b/core/annotations.h @@ -1,1737 +1,1737 @@ /*************************************************************************** * Copyright (C) 2005 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. * ***************************************************************************/ #ifndef _OKULAR_ANNOTATIONS_H_ #define _OKULAR_ANNOTATIONS_H_ -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "okularcore_export.h" #include "area.h" namespace Okular { class Action; class Annotation; class AnnotationObjectRect; class AnnotationPrivate; class Document; class EmbeddedFile; class Page; class PagePrivate; class Sound; class Movie; class TextAnnotationPrivate; class LineAnnotationPrivate; class GeomAnnotationPrivate; class HighlightAnnotationPrivate; class StampAnnotationPrivate; class InkAnnotationPrivate; class CaretAnnotationPrivate; class FileAttachmentAnnotationPrivate; class SoundAnnotationPrivate; class MovieAnnotationPrivate; class ScreenAnnotationPrivate; class WidgetAnnotationPrivate; class RichMediaAnnotationPrivate; /** * @short Helper class for (recursive) annotation retrieval/storage. */ class OKULARCORE_EXPORT AnnotationUtils { public: /** * Restore an annotation (with revisions if needed) from the dom @p element. * * Returns a pointer to the complete annotation or 0 if element is invalid. */ static Annotation * createAnnotation( const QDomElement & element ); /** * Saves the @p annotation as a child of @p element taking * care of saving all revisions if it has any. */ static void storeAnnotation( const Annotation * annotation, QDomElement & element, QDomDocument & document ); /** * Returns the child element with the given @p name from the direct * children of @p parentNode or a null element if not found. */ static QDomElement findChildElement( const QDomNode & parentNode, const QString & name ); /** * Returns the geometry of the given @p annotation scaled by * @p scaleX and @p scaleY. */ static QRect annotationGeometry( const Annotation * annotation, double scaleX, double scaleY ); }; /** * @short Annotation struct holds properties shared by all annotations. * * An Annotation is an object (text note, highlight, sound, popup window, ..) * contained by a Page in the document. */ class OKULARCORE_EXPORT Annotation { /// @cond PRIVATE friend class AnnotationObjectRect; friend class Document; friend class DocumentPrivate; friend class ObjectRect; friend class Page; friend class PagePrivate; /// @endcond public: /** * Describes the type of annotation as defined in PDF standard. */ enum SubType { AText = 1, ///< A textual annotation ALine = 2, ///< A line annotation AGeom = 3, ///< A geometrical annotation AHighlight = 4, ///< A highlight annotation AStamp = 5, ///< A stamp annotation AInk = 6, ///< An ink annotation ACaret = 8, ///< A caret annotation AFileAttachment = 9, ///< A file attachment annotation ASound = 10, ///< A sound annotation AMovie = 11, ///< A movie annotation AScreen = 12, ///< A screen annotation AWidget = 13, ///< A widget annotation ARichMedia = 14,///< A rich media annotation A_BASE = 0 ///< The annotation base class }; /** * Describes additional properties of an annotation. */ enum Flag { Hidden = 1, ///< Is not shown in the document FixedSize = 2, ///< Has a fixed size FixedRotation = 4, ///< Has a fixed rotation DenyPrint = 8, ///< Cannot be printed DenyWrite = 16, ///< Cannot be changed DenyDelete = 32, ///< Cannot be deleted ToggleHidingOnMouse = 64, ///< Can be hidden/shown by mouse click External = 128, ///< Is stored external ExternallyDrawn = 256, ///< Is drawn externally (by the generator which provided it) @since 0.10 (KDE 4.4) BeingMoved = 512, ///< Is being moved (mouse drag and drop). If ExternallyDrawn, the generator must not draw it @since 0.15 (KDE 4.9) BeingResized = 1024 ///< Is being resized (mouse drag and drop). If ExternallyDrawn, the generator must not draw it @since 1.1.0 }; /** * Describes possible line styles for @see ALine annotation. */ enum LineStyle { Solid = 1, ///< A solid line Dashed = 2, ///< A dashed line Beveled = 4, ///< A beveled line Inset = 8, ///< A inseted line Underline = 16 ///< An underline }; /** * Describes possible line effects for @see ALine annotation. */ enum LineEffect { NoEffect = 1, ///< No effect Cloudy = 2 ///< The cloudy effect }; /** * Describes the scope of revision information. */ enum RevisionScope { Reply = 1, ///< Belongs to a reply Group = 2, ///< Belongs to a group Delete = 4 ///< Belongs to a deleted paragraph }; /** * Describes the type of revision information. */ enum RevisionType { None = 1, ///< Not specified Marked = 2, ///< Is marked Unmarked = 4, ///< Is unmarked Accepted = 8, ///< Has been accepted Rejected = 16, ///< Was rejected Cancelled = 32, ///< Has been cancelled Completed = 64 ///< Has been completed }; /** * Describes the type of additional actions. * * @since 0.16 (KDE 4.10) */ enum AdditionalActionType { PageOpening, ///< Performed when the page containing the annotation is opened. PageClosing, ///< Performed when the page containing the annotation is closed. CursorEntering, ///< Performed when the cursor enters the annotation's active area @since 1.5 CursorLeaving, ///< Performed when the cursor exists the annotation's active area @since 1.5 MousePressed, ///< Performed when the mouse button is pressed inside the annotation's active area @since 1.5 MouseReleased, ///< Performed when the mouse button is released inside the annotation's active area @since 1.5 FocusIn, ///< Performed when the annotation receives the input focus @since 1.5 FocusOut, ///< Performed when the annotation loses the input focus @since 1.5 }; /** * A function to be called when the annotation is destroyed. * * @warning the function must *not* call any virtual function, * nor subcast. * * @since 0.7 (KDE 4.1) */ typedef void ( * DisposeDataFunction )( const Okular::Annotation * ); /** * Destroys the annotation. */ virtual ~Annotation(); /** * Sets the @p author of the annotation. */ void setAuthor( const QString &author ); /** * Returns the author of the annotation. */ QString author() const; /** * Sets the @p contents of the annotation. */ void setContents( const QString &contents ); /** * Returns the contents of the annotation. */ QString contents() const; /** * Sets the unique @p name of the annotation. */ void setUniqueName( const QString &name ); /** * Returns the unique name of the annotation. */ QString uniqueName() const; /** * Sets the last modification @p date of the annotation. * * The date must be before or equal to QDateTime::currentDateTime() */ void setModificationDate( const QDateTime &date ); /** * Returns the last modification date of the annotation. */ QDateTime modificationDate() const; /** * Sets the creation @p date of the annotation. * * The date must be before or equal to @see modificationDate() */ void setCreationDate( const QDateTime &date ); /** * Returns the creation date of the annotation. */ QDateTime creationDate() const; /** * Sets the @p flags of the annotation. * @see @ref Flag */ void setFlags( int flags ); /** * Returns the flags of the annotation. * @see @ref Flag */ int flags() const; /** * Sets the bounding @p rectangle of the annotation. */ void setBoundingRectangle( const NormalizedRect &rectangle ); /** * Returns the bounding rectangle of the annotation. */ NormalizedRect boundingRectangle() const; /** * Returns the transformed bounding rectangle of the annotation. * * This rectangle must be used when showing annotations on screen * to have them rotated correctly. */ NormalizedRect transformedBoundingRectangle() const; /** * Move the annotation by the specified coordinates. * * @see canBeMoved() */ void translate( const NormalizedPoint &coord ); /** * Adjust the annotation by the specified coordinates. * Adds coordinates of @p deltaCoord1 to annotations top left corner, * and @p deltaCoord2 to the bottom right. * * @see canBeResized() */ void adjust( const NormalizedPoint & deltaCoord1, const NormalizedPoint & deltaCoord2 ); /** * The Style class contains all information about style of the * annotation. */ class OKULARCORE_EXPORT Style { public: /** * Creates a new style. */ Style(); /** * Destroys the style. */ ~Style(); Style( const Style &other ); Style& operator=( const Style &other ); /** * Sets the @p color of the style. */ void setColor( const QColor &color ); /** * Returns the color of the style. */ QColor color() const; /** * Sets the @p opacity of the style. */ void setOpacity( double opacity ); /** * Returns the opacity of the style. */ double opacity() const; /** * Sets the @p width of the style. */ void setWidth( double width ); /** * Returns the width of the style. */ double width() const; /** * Sets the line @p style of the style. */ void setLineStyle( LineStyle style ); /** * Returns the line style of the style. */ LineStyle lineStyle() const; /** * Sets the x-corners of the style. */ void setXCorners( double xCorners ); /** * Returns the x-corners of the style. */ double xCorners() const; /** * Sets the y-corners of the style. */ void setYCorners( double yCorners ); /** * Returns the y-corners of the style. */ double yCorners() const; /** * Sets the @p marks of the style. */ void setMarks( int marks ); /** * Returns the marks of the style. */ int marks() const; /** * Sets the @p spaces of the style. */ void setSpaces( int spaces ); /** * Returns the spaces of the style. */ int spaces() const; /** * Sets the line @p effect of the style. */ void setLineEffect( LineEffect effect ); /** * Returns the line effect of the style. */ LineEffect lineEffect() const; /** * Sets the effect @p intensity of the style. */ void setEffectIntensity( double intensity ); /** * Returns the effect intensity of the style. */ double effectIntensity() const; private: class Private; Private* const d; }; /** * Returns a reference to the style object of the annotation. */ Style & style(); /** * Returns a const reference to the style object of the annotation. */ const Style & style() const; /** * The Window class contains all information about the popup window * of the annotation that is used to edit the content and properties. */ class OKULARCORE_EXPORT Window { public: /** * Creates a new window. */ Window(); /** * Destroys the window. */ ~Window(); Window( const Window &other ); Window& operator=( const Window &other ); /** * Sets the @p flags of the window. */ void setFlags( int flags ); /** * Returns the flags of the window. */ int flags() const; /** * Sets the top-left @p point of the window. */ void setTopLeft( const NormalizedPoint &point ); /** * Returns the top-left point of the window. */ NormalizedPoint topLeft() const; /** * Sets the @p width of the window. */ void setWidth( int width ); /** * Returns the width of the window. */ int width() const; /** * Sets the @p height of the window. */ void setHeight( int height ); /** * Returns the height of the window. */ int height() const; /** * Sets the @p title of the window. */ void setTitle( const QString &title ); /** * Returns the title of the window. */ QString title() const; /** * Sets the @p summary of the window. */ void setSummary( const QString &summary ); /** * Returns the summary of the window. */ QString summary() const; private: class Private; Private* const d; }; /** * Returns a reference to the window object of the annotation. */ Window & window(); /** * Returns a const reference to the window object of the annotation. */ const Window & window() const; /** * The Revision class contains all information about the revision * of the annotation. */ class Revision { public: /** * Creates a new revision. */ Revision(); /** * Destroys the revision. */ ~Revision(); Revision( const Revision &other ); Revision& operator=( const Revision &other ); /** * Sets the @p annotation the revision belongs to. */ void setAnnotation( Annotation *annotation ); /** * Returns the annotation the revision belongs to. */ Annotation *annotation() const; /** * Sets the @p scope of the revision. * @see RevisionScope */ void setScope( RevisionScope scope ); /** * Returns the scope of the revision. */ RevisionScope scope() const; /** * Sets the @p type of the revision. * @see RevisionType */ void setType( RevisionType type ); /** * Returns the type of the revision. */ RevisionType type() const; private: class Private; Private* const d; }; /** * Returns a reference to the revision list of the annotation. */ QLinkedList< Revision > & revisions(); /** * Returns a reference to the revision list of the annotation. */ const QLinkedList< Revision > & revisions() const; /** * Sets the "native" @p id of the annotation. * * This is for use of the Generator, that can optionally store an * handle (a pointer, an identifier, etc) of the "native" annotation * object, if any. * * @note Okular makes no use of this * * @since 0.7 (KDE 4.1) */ void setNativeId( const QVariant &id ); /** * Returns the "native" id of the annotation. * * @since 0.7 (KDE 4.1) */ QVariant nativeId() const; /** * Sets a function to be called when the annotation is destroyed. * * @warning the function must *not* call any virtual function, * nor subcast. * * @since 0.7 (KDE 4.1) */ void setDisposeDataFunction( DisposeDataFunction func ); /** * Returns whether the annotation can be moved. * * @since 0.7 (KDE 4.1) */ bool canBeMoved() const; /** * Returns whether the annotation can be resized. */ bool canBeResized() const; /** * Returns whether the annotation dialog should be open after creation of the annotation or not * * @since 0.13 (KDE 4.7) */ bool openDialogAfterCreation() const; /** * Returns the sub type of the annotation. */ virtual SubType subType() const = 0; /** * Stores the annotation as xml in @p document under the given parent @p node. */ virtual void store( QDomNode & node, QDomDocument & document ) const; /** * Retrieve the QDomNode representing this annotation's properties * @since 0.17 (KDE 4.11) */ QDomNode getAnnotationPropertiesDomNode() const; /** * Sets annotations internal properties according to the contents of @p node * * @since 0.17 (KDE 4.11) */ void setAnnotationProperties( const QDomNode & node ); protected: /// @cond PRIVATE Annotation( AnnotationPrivate &dd ); Annotation( AnnotationPrivate &dd, const QDomNode &description ); Q_DECLARE_PRIVATE( Annotation ) AnnotationPrivate *d_ptr; /// @endcond private: Q_DISABLE_COPY( Annotation ) }; /** * @short Native annotation interface * * Generators can subclass it to provide native annotation support. * Generators can use Annotation::setNativeId to store per-annotation data. * * @since 0.15 (KDE 4.9) */ class OKULARCORE_EXPORT AnnotationProxy { public: enum Capability { Addition, ///< Generator can create native annotations Modification, ///< Generator can edit native annotations Removal ///< Generator can remove native annotations }; /** * Destroys the annotation proxy. */ virtual ~AnnotationProxy(); /** * Query for the supported capabilities. */ virtual bool supports( Capability capability ) const = 0; /** * Called when a new @p annotation is added to a @p page. * * @note Only called if supports(Addition) == true */ virtual void notifyAddition( Annotation *annotation, int page ) = 0; /** * Called after an existing @p annotation at a given @p page is modified. * * Generator can call @p annotation getters to get the new values. * @p appearanceChanged tells if a non-visible property was modifed * * @note Only called if supports(Modification) == true */ virtual void notifyModification( const Annotation *annotation, int page, bool appearanceChanged ) = 0; /** * Called when an existing @p annotation at a given @p page is removed. * * @note Only called if supports(Removal) == true */ virtual void notifyRemoval( Annotation *annotation, int page ) = 0; }; class OKULARCORE_EXPORT TextAnnotation : public Annotation { public: /** * Describes the type of the text. */ enum TextType { Linked, ///< The annotation is linked to a text InPlace ///< The annotation is located next to the text }; /** * Describes the style of the text. */ enum InplaceIntent { Unknown, ///< Unknown style Callout, ///< Callout style TypeWriter ///< Type writer style }; /** * Creates a new text annotation. */ TextAnnotation(); /** * Creates a new text annotation from the xml @p description */ TextAnnotation( const QDomNode &description ); /** * Destroys the text annotation. */ ~TextAnnotation(); /** * Sets the text @p type of the text annotation. * @see TextType */ void setTextType( TextType type ); /** * Returns the text type of the text annotation. */ TextType textType() const; /** * Sets the @p icon of the text annotation. */ void setTextIcon( const QString &icon ); /** * Returns the icon of the text annotation. */ QString textIcon() const; /** * Sets the @p font of the text annotation. */ void setTextFont( const QFont &font ); /** * Returns the font of the text annotation. */ QFont textFont() const; /** * Sets the inplace @p alignment of the text annotation. */ void setInplaceAlignment( int alignment ); /** * Returns the inplace alignment of the text annotation. */ int inplaceAlignment() const; /** * Sets the inplace callout @p point at @p index. * * @p index must be between 0 and 2. */ void setInplaceCallout( const NormalizedPoint &point, int index ); /** * Returns the inplace callout point for @p index. * * @p index must be between 0 and 2. */ NormalizedPoint inplaceCallout( int index ) const; /** * Returns the transformed (e.g. rotated) inplace callout point for @p index. * * @p index must be between 0 and 2. */ NormalizedPoint transformedInplaceCallout( int index ) const; /** * Returns the inplace @p intent of the text annotation. * @see InplaceIntent */ void setInplaceIntent( InplaceIntent intent ); /** * Returns the inplace intent of the text annotation. */ InplaceIntent inplaceIntent() const; /** * Returns the sub type of the text annotation. */ SubType subType() const override; /** * Stores the text annotation as xml in @p document under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( TextAnnotation ) Q_DISABLE_COPY( TextAnnotation ) }; class OKULARCORE_EXPORT LineAnnotation : public Annotation { public: /** * Describes the line ending style. */ enum TermStyle { Square, ///< Using a square Circle, ///< Using a circle Diamond, ///< Using a diamond OpenArrow, ///< Using an open arrow ClosedArrow, ///< Using a closed arrow None, ///< No special ending style Butt, ///< Using a butt ending ROpenArrow, ///< Using an arrow opened at the right side RClosedArrow, ///< Using an arrow closed at the right side Slash ///< Using a slash }; /** * Describes the line intent. */ enum LineIntent { Unknown, ///< Unknown intent Arrow, ///< Arrow intent Dimension, ///< Dimension intent PolygonCloud ///< Polygon cloud intent }; /** * Creates a new line annotation. */ LineAnnotation(); /** * Creates a new line annotation from the xml @p description */ explicit LineAnnotation( const QDomNode &description ); /** * Destroys the line annotation. */ ~LineAnnotation(); /** * Sets the normalized line @p points of the line annotation. */ void setLinePoints( const QLinkedList &points ); /** * Returns the normalized line points of the line annotation. */ QLinkedList linePoints() const; /** * Returns the transformed (e.g. rotated) normalized line points * of the line annotation. */ QLinkedList transformedLinePoints() const; /** * Sets the line starting @p style of the line annotation. * @see TermStyle */ void setLineStartStyle( TermStyle style ); /** * Returns the line starting style of the line annotation. */ TermStyle lineStartStyle() const; /** * Sets the line ending @p style of the line annotation. * @see TermStyle */ void setLineEndStyle( TermStyle style ); /** * Returns the line ending style of the line annotation. */ TermStyle lineEndStyle() const; /** * Sets whether the line shall be @p closed. */ void setLineClosed( bool closed ); /** * Returns whether the line shall be closed. */ bool lineClosed() const; /** * Sets the inner line @p color of the line annotation. */ void setLineInnerColor( const QColor &color ); /** * Returns the inner line color of the line annotation. */ QColor lineInnerColor() const; /** * Sets the leading forward @p point of the line annotation. */ void setLineLeadingForwardPoint( double point ); /** * Returns the leading forward point of the line annotation. */ double lineLeadingForwardPoint() const; /** * Sets the leading backward @p point of the line annotation. */ void setLineLeadingBackwardPoint( double point ); /** * Returns the leading backward point of the line annotation. */ double lineLeadingBackwardPoint() const; /** * Sets whether the caption shall be @p shown. */ void setShowCaption( bool shown ); /** * Returns whether the caption shall be shown. */ bool showCaption() const; /** * Sets the line @p intent of the line annotation. * @see LineIntent */ void setLineIntent( LineIntent intent ); /** * Returns the line intent of the line annotation. */ LineIntent lineIntent() const; /** * Returns the sub type of the line annotation. */ SubType subType() const override; /** * Stores the line annotation as xml in @p document under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( LineAnnotation ) Q_DISABLE_COPY( LineAnnotation ) }; class OKULARCORE_EXPORT GeomAnnotation : public Annotation { public: // common enums enum GeomType { InscribedSquare, ///< Draw a square InscribedCircle ///< Draw a circle }; /** * Creates a new geometrical annotation. */ GeomAnnotation(); /** * Creates a new geometrical annotation from the xml @p description */ GeomAnnotation( const QDomNode &description ); /** * Destroys the geometrical annotation. */ ~GeomAnnotation(); /** * Sets the geometrical @p type of the geometrical annotation. * @see GeomType */ void setGeometricalType( GeomType type ); /** * Returns the geometrical type of the geometrical annotation. */ GeomType geometricalType() const; /** * Sets the inner @p color of the geometrical annotation. */ void setGeometricalInnerColor( const QColor &color ); /** * Returns the inner color of the geometrical annotation. */ QColor geometricalInnerColor() const; /** * Returns the sub type of the geometrical annotation. */ SubType subType() const override; /** * Stores the geometrical annotation as xml in @p document * under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( GeomAnnotation ) Q_DISABLE_COPY( GeomAnnotation ) }; class OKULARCORE_EXPORT HighlightAnnotation : public Annotation { public: /** * Describes the highlighting style of the annotation. */ enum HighlightType { Highlight, ///< Highlights the text Squiggly, ///< Squiggles the text Underline, ///< Underlines the text StrikeOut ///< Strikes out the text }; /** * Creates a new highlight annotation. */ HighlightAnnotation(); /** * Creates a new highlight annotation from the xml @p description */ explicit HighlightAnnotation( const QDomNode &description ); /** * Destroys the highlight annotation. */ ~HighlightAnnotation(); /** * Sets the @p type of the highlight annotation. * @see HighlightType */ void setHighlightType( HighlightType type ); /** * Returns the type of the highlight annotation. */ HighlightType highlightType() const; /** * The Quad class contains 8 coordinates and style definitions * which describe a line part of the whole highlight annotation. */ class OKULARCORE_EXPORT Quad { public: /** * Creates a new quad. */ Quad(); /** * Destroys the quad. */ ~Quad(); Quad( const Quad &other ); Quad& operator=( const Quad &other ); /** * Sets the normalized @p point at @p index. * * @p index must be between 0 and 3. */ void setPoint( const NormalizedPoint &point, int index ); /** * Returns the normalized point at @p index. * * @p index must be between 0 and 3. */ NormalizedPoint point( int index ) const; /** * Returns the transformed (e.g. rotated) normalized point at @p index. * * @p index must be between 0 and 3. */ NormalizedPoint transformedPoint( int index ) const; /** * Sets whether a cap should be used at the start. */ void setCapStart( bool value ); /** * Returns whether a cap should be used at the start. */ bool capStart() const; /** * Sets whether a cap should be used at the end. */ void setCapEnd( bool value ); /** * Returns whether a cap should be used at the end. */ bool capEnd() const; /** * Sets the @p width of the drawing feather. */ void setFeather( double width ); /** * Returns the width of the drawing feather. */ double feather() const; /** * Transforms the quad coordinates with the transformation defined * by @p matrix. */ void transform( const QTransform &matrix ); private: class Private; Private* const d; }; /** * Returns a reference to the quad list of the highlight annotation. */ QList< Quad > & highlightQuads(); /** * Returns the sub type of the highlight annotation. */ SubType subType() const override; /** * Stores the highlight annotation as xml in @p document * under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( HighlightAnnotation ) Q_DISABLE_COPY( HighlightAnnotation ) }; class OKULARCORE_EXPORT StampAnnotation : public Annotation { public: /** * Creates a new stamp annotation. */ StampAnnotation(); /** * Creates a new stamp annotation from the xml @p description */ explicit StampAnnotation( const QDomNode &description ); /** * Destroys the stamp annotation. */ ~StampAnnotation(); /** * Sets the @p name of the icon for the stamp annotation. */ void setStampIconName( const QString &name ); /** * Returns the name of the icon. */ QString stampIconName() const; /** * Returns the sub type of the stamp annotation. */ SubType subType() const override; /** * Stores the stamp annotation as xml in @p document * under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( StampAnnotation ) Q_DISABLE_COPY( StampAnnotation ) }; class OKULARCORE_EXPORT InkAnnotation : public Annotation { public: /** * Creates a new ink annotation. */ InkAnnotation(); /** * Creates a new ink annotation from the xml @p description */ InkAnnotation( const QDomNode &description ); /** * Destroys the ink annotation. */ ~InkAnnotation(); /** * Sets the @p paths of points for the ink annotation. */ void setInkPaths( const QList< QLinkedList > &paths ); /** * Returns the paths of points of the ink annotation. */ QList< QLinkedList > inkPaths() const; /** * Returns the paths of transformed (e.g. rotated) points of * the ink annotation. */ QList< QLinkedList > transformedInkPaths() const; /** * Returns the sub type of the ink annotation. */ SubType subType() const override; /** * Stores the ink annotation as xml in @p document * under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( InkAnnotation ) Q_DISABLE_COPY( InkAnnotation ) }; class OKULARCORE_EXPORT CaretAnnotation : public Annotation { public: /** * Describes the highlighting style of the annotation. */ enum CaretSymbol { None, ///< No symbol to be associated with the text P ///< A 'paragraph' symbol }; /** * Creates a new caret annotation. */ CaretAnnotation(); /** * Creates a new caret annotation from the xml @p description */ explicit CaretAnnotation( const QDomNode &description ); /** * Destroys the caret annotation. */ ~CaretAnnotation(); /** * Sets the @p symbol for the caret annotation. */ void setCaretSymbol( CaretAnnotation::CaretSymbol symbol ); /** * Returns the symbol of the annotation. */ CaretAnnotation::CaretSymbol caretSymbol() const; /** * Returns the sub type of the caret annotation. */ SubType subType() const override; /** * Stores the caret annotation as xml in @p document * under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( CaretAnnotation ) Q_DISABLE_COPY( CaretAnnotation ) }; class OKULARCORE_EXPORT FileAttachmentAnnotation : public Annotation { public: /** * Creates a new file attachment annotation. */ FileAttachmentAnnotation(); /** * Creates a new file attachment annotation from the xml @p description */ explicit FileAttachmentAnnotation( const QDomNode &description ); /** * Destroys the file attachment annotation. */ virtual ~FileAttachmentAnnotation(); /** * Gets the name of the icon. */ QString fileIconName() const; /** * Sets the @p name of the icon for the file attachment annotation. */ void setFileIconName( const QString &name ); /** * Gets the embedded file object. */ EmbeddedFile* embeddedFile() const; /** * Sets the @p object representing the embedded file of the file * attachment annotation. */ void setEmbeddedFile( EmbeddedFile *object ); /** * Returns the sub type of the file attachment annotation. */ SubType subType() const override; /** * Stores the file attachment annotation as xml in @p document * under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( FileAttachmentAnnotation ) Q_DISABLE_COPY( FileAttachmentAnnotation ) }; /** * \short Sound annotation. * * The sound annotation represents a sound to be played when activated. * * @since 0.7 (KDE 4.1) */ class OKULARCORE_EXPORT SoundAnnotation : public Annotation { public: /** * Creates a new sound annotation. */ SoundAnnotation(); /** * Creates a new sound annotation from the xml @p description */ SoundAnnotation( const QDomNode &description ); /** * Destroys the sound annotation. */ virtual ~SoundAnnotation(); /** * Gets the name of the icon. */ QString soundIconName() const; /** * Sets the @p name of the icon for the sound annotation. */ void setSoundIconName( const QString &name ); /** * Gets the sound object. */ Sound* sound() const; /** * Sets the @p object representing the sound of the file * attachment annotation. */ void setSound( Sound *object ); /** * Returns the sub type of the sound annotation. */ SubType subType() const override; /** * Stores the sound annotation as xml in @p document * under the given parent @p node. */ void store( QDomNode &node, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( SoundAnnotation ) Q_DISABLE_COPY( SoundAnnotation ) }; /** * \short Movie annotation. * * The movie annotation represents a movie to be played when activated. * * @since 0.8 (KDE 4.2) */ class OKULARCORE_EXPORT MovieAnnotation : public Annotation { public: /** * Creates a new movie annotation. */ MovieAnnotation(); /** * Creates a new movie annotation from the xml @p description */ MovieAnnotation( const QDomNode &description ); /** * Destroys the movie annotation. */ virtual ~MovieAnnotation(); /** * Gets the movie object. */ Movie* movie() const; /** * Sets the new @p movie object. */ void setMovie( Movie *movie ); /** * Returns the sub type of the movie annotation. */ SubType subType() const override; /** * Stores the movie annotation as xml in @p document * under the given @p parentNode. */ void store( QDomNode &parentNode, QDomDocument &document ) const override; private: Q_DECLARE_PRIVATE( MovieAnnotation ) Q_DISABLE_COPY( MovieAnnotation ) }; /** * \short Screen annotation. * * The screen annotation specifies a region of a page upon which media clips * may be played. It also serves as an object from which actions can be triggered. * * @since 0.16 (KDE 4.10) */ class OKULARCORE_EXPORT ScreenAnnotation : public Annotation { public: /** * Creates a new screen annotation. */ ScreenAnnotation(); /** * Creates a new screen annotation from the xml @p description */ ScreenAnnotation( const QDomNode &description ); /** * Destroys the screen annotation. */ virtual ~ScreenAnnotation(); /** * Returns the sub type of the screen annotation. */ SubType subType() const override; /** * Stores the screen annotation as xml in @p document * under the given @p parentNode. */ void store( QDomNode &parentNode, QDomDocument &document ) const override; /** * Sets the @p action that is executed when the annotation is triggered. * * @since 0.16 (KDE 4.10) */ void setAction( Action *action ); /** * Returns the action that is executed when the annotation is triggered or @c 0 if not action has been defined. * * @since 0.16 (KDE 4.10) */ Action* action() const; /** * Sets the additional @p action of the given @p type. * * @since 0.16 (KDE 4.10) */ void setAdditionalAction( AdditionalActionType type, Action *action ); /** * Returns the additional action of the given @p type or @c 0 if no action has been defined. * * @since 0.16 (KDE 4.10) */ Action* additionalAction( AdditionalActionType type ) const; private: Q_DECLARE_PRIVATE( ScreenAnnotation ) Q_DISABLE_COPY( ScreenAnnotation ) }; /** * \short Widget annotation. * * The widget annotation represents a widget on a page. * * @since 0.16 (KDE 4.10) */ class OKULARCORE_EXPORT WidgetAnnotation : public Annotation { public: /** * Creates a new widget annotation. */ WidgetAnnotation(); /** * Creates a new widget annotation from the xml @p description */ WidgetAnnotation( const QDomNode &description ); /** * Destroys the widget annotation. */ virtual ~WidgetAnnotation(); /** * Returns the sub type of the widget annotation. */ SubType subType() const override; /** * Stores the widget annotation as xml in @p document * under the given @p parentNode. */ void store( QDomNode &parentNode, QDomDocument &document ) const override; /** * Sets the additional @p action of the given @p type. * * @since 0.16 (KDE 4.10) */ void setAdditionalAction( AdditionalActionType type, Action *action ); /** * Returns the additional action of the given @p type or @c 0 if no action has been defined. * * @since 0.16 (KDE 4.10) */ Action* additionalAction( AdditionalActionType type ) const; private: Q_DECLARE_PRIVATE( WidgetAnnotation ) Q_DISABLE_COPY( WidgetAnnotation ) }; /** * \short RichMedia annotation. * * The rich media annotation represents an video or sound on a page. * * @since 1.0 */ class OKULARCORE_EXPORT RichMediaAnnotation : public Annotation { public: /** * Creates a new rich media annotation. */ RichMediaAnnotation(); /** * Creates a new rich media annotation from the xml @p description */ RichMediaAnnotation( const QDomNode &description ); /** * Destroys the rich media annotation. */ virtual ~RichMediaAnnotation(); /** * Returns the sub type of the rich media annotation. */ SubType subType() const override; /** * Stores the rich media annotation as xml in @p document * under the given @p parentNode. */ void store( QDomNode &parentNode, QDomDocument &document ) const override; /** * Gets the movie object. */ Movie* movie() const; /** * Sets the new @p movie object. */ void setMovie( Movie *movie ); /** * Sets the @p object representing the embedded file. */ void setEmbeddedFile( EmbeddedFile *object ); /** * Gets the embedded file object. */ EmbeddedFile* embeddedFile() const; private: Q_DECLARE_PRIVATE( RichMediaAnnotation ) Q_DISABLE_COPY( RichMediaAnnotation ) }; } #endif diff --git a/core/annotations_p.h b/core/annotations_p.h index bb6492de7..25dee11be 100644 --- a/core/annotations_p.h +++ b/core/annotations_p.h @@ -1,80 +1,80 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_ANNOTATIONS_P_H #define OKULAR_ANNOTATIONS_P_H #include "area.h" #include "annotations.h" // qt/kde includes -#include -#include -#include -#include +#include +#include +#include +#include class QTransform; namespace Okular { class PagePrivate; class AnnotationPrivate { public: AnnotationPrivate(); virtual ~AnnotationPrivate(); /** * Transforms the annotation coordinates with the transformation * defined by @p matrix. */ void annotationTransform( const QTransform &matrix ); virtual void transform( const QTransform &matrix ); virtual void baseTransform( const QTransform &matrix ); virtual void resetTransformation(); virtual void translate( const NormalizedPoint &coord ); virtual void adjust( const NormalizedPoint & deltaCoord1, const NormalizedPoint & deltaCoord2 ); virtual bool openDialogAfterCreation() const; virtual void setAnnotationProperties( const QDomNode& node ); virtual bool canBeResized() const; virtual AnnotationPrivate* getNewAnnotationPrivate() = 0; /** * Determines the distance of the closest point of the annotation to the * given point @p x @p y @p xScale @p yScale * @since 0.17 */ virtual double distanceSqr( double x, double y, double xScale, double yScale ); PagePrivate * m_page; QString m_author; QString m_contents; QString m_uniqueName; QDateTime m_modifyDate; QDateTime m_creationDate; int m_flags; NormalizedRect m_boundary; NormalizedRect m_transformedBoundary; Okular::Annotation::Style m_style; Okular::Annotation::Window m_window; QLinkedList< Okular::Annotation::Revision > m_revisions; Annotation::DisposeDataFunction m_disposeFunc; QVariant m_nativeId; }; } #endif diff --git a/core/area.cpp b/core/area.cpp index 327d43099..2a97b6d72 100644 --- a/core/area.cpp +++ b/core/area.cpp @@ -1,498 +1,498 @@ /*************************************************************************** * Copyright (C) 2004-05 by Enrico Ros * * Copyright (C) 2005 by Piotr Szymanski * * 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 "area.h" -#include -#include -#include +#include +#include +#include #include #include "action.h" #include "annotations.h" #include "annotations_p.h" #include "debug_p.h" #include "sourcereference.h" using namespace Okular; /** class NormalizedPoint **/ NormalizedPoint::NormalizedPoint() : x( 0.0 ), y( 0.0 ) {} NormalizedPoint::NormalizedPoint( double dX, double dY ) : x( dX ), y( dY ) {} NormalizedPoint::NormalizedPoint( int iX, int iY, int xScale, int yScale ) : x( (double)iX / (double)xScale ), y( (double)iY / (double)yScale ) {} NormalizedPoint& NormalizedPoint::operator=( const NormalizedPoint & p ) { x = p.x; y = p.y; return *this; } void NormalizedPoint::transform( const QTransform &matrix ) { qreal tmp_x = (qreal)x; qreal tmp_y = (qreal)y; matrix.map( tmp_x, tmp_y, &tmp_x, &tmp_y ); x = tmp_x; y = tmp_y; } double NormalizedPoint::distanceSqr( double x, double y, double xScale, double yScale ) const { return pow( (this->x - x) * xScale, 2 ) + pow( (this->y - y) * yScale, 2 ); } /** * Returns a vector from the given points @p a and @p b * @internal */ NormalizedPoint operator-( const NormalizedPoint& a, const NormalizedPoint& b ) { return NormalizedPoint( a.x - b.x, a.y - b.y ); } /** * @brief Calculates distance of the point @p x @p y @p xScale @p yScale to the line segment from @p start to @p end */ double NormalizedPoint::distanceSqr( double x, double y, double xScale, double yScale, const NormalizedPoint& start, const NormalizedPoint& end ) { NormalizedPoint point( x, y ); double thisDistance; NormalizedPoint lineSegment( end - start ); const double lengthSqr = pow( lineSegment.x, 2 ) + pow( lineSegment.y, 2 ); //if the length of the current segment is null, we can just //measure the distance to either end point if ( lengthSqr == 0.0 ) { thisDistance = end.distanceSqr( x, y, xScale, yScale ); } else { //vector from the start point of the current line segment to the measurement point NormalizedPoint a = point - start; //vector from the same start point to the end point of the current line segment NormalizedPoint b = end - start; //we're using a * b (dot product) := |a| * |b| * cos(phi) and the knowledge //that cos(phi) is adjacent side / hypotenuse (hypotenuse = |b|) //therefore, t becomes the length of the vector that represents the projection of //the point p onto the current line segment //(hint: if this is still unclear, draw it!) float t = (a.x * b.x + a.y * b.y) / lengthSqr; if ( t < 0 ) { //projection falls outside the line segment on the side of "start" thisDistance = point.distanceSqr( start.x, start.y, xScale, yScale ); } else if ( t > 1 ) { //projection falls outside the line segment on the side of the current point thisDistance = point.distanceSqr( end.x, end.y, xScale, yScale ); } else { //projection is within [start, *i]; //determine the length of the perpendicular distance from the projection to the actual point NormalizedPoint direction = end - start; NormalizedPoint projection = start - NormalizedPoint( -t * direction.x, -t * direction.y ); thisDistance = projection.distanceSqr( x, y, xScale, yScale ); } } return thisDistance; } QDebug operator<<( QDebug str, const Okular::NormalizedPoint& p ) { str.nospace() << "NormPt(" << p.x << "," << p.y << ")"; return str.space(); } /** class NormalizedRect **/ NormalizedRect::NormalizedRect() : left( 0.0 ), top( 0.0 ), right( 0.0 ), bottom( 0.0 ) {} NormalizedRect::NormalizedRect( double l, double t, double r, double b ) // note: check for swapping coords? : left( l ), top( t ), right( r ), bottom( b ) {} NormalizedRect::NormalizedRect( const QRect & r, double xScale, double yScale ) : left( (double)r.left() / xScale ), top( (double)r.top() / yScale ), right( (double)r.right() / xScale ), bottom( (double)r.bottom() / yScale ) {} NormalizedRect::NormalizedRect( const NormalizedRect & rect ) : left( rect.left ), top( rect.top ), right( rect.right ), bottom( rect.bottom ) {} NormalizedRect NormalizedRect::fromQRectF( const QRectF &rect ) { QRectF nrect = rect.normalized(); NormalizedRect ret; ret.left = nrect.left(); ret.top = nrect.top(); ret.right = nrect.right(); ret.bottom = nrect.bottom(); return ret; } bool NormalizedRect::isNull() const { return left == 0 && top== 0 && right == 0 && bottom == 0; } bool NormalizedRect::contains( double x, double y ) const { return x >= left && x <= right && y >= top && y <= bottom; } bool NormalizedRect::intersects( const NormalizedRect & r ) const { return (r.left <= right) && (r.right >= left) && (r.top <= bottom) && (r.bottom >= top); } bool NormalizedRect::intersects( const NormalizedRect * r ) const { return (r->left <= right) && (r->right >= left) && (r->top <= bottom) && (r->bottom >= top); } bool NormalizedRect::intersects( double l, double t, double r, double b ) const { return (l <= right) && (r >= left) && (t <= bottom) && (b >= top); } NormalizedRect NormalizedRect::operator| (const NormalizedRect & r) const { NormalizedRect ret; // todo ! ret.left=qMin(left,r.left); ret.top=qMin(top,r.top); ret.bottom=qMax(bottom,r.bottom); ret.right=qMax(right,r.right); return ret; } NormalizedRect& NormalizedRect::operator|= (const NormalizedRect & r) { left = qMin( left, r.left ); top = qMin( top, r.top ); bottom = qMax( bottom, r.bottom ); right = qMax( right, r.right ); return *this; } NormalizedRect NormalizedRect::operator&( const NormalizedRect & r ) const { if ( isNull() || r.isNull() ) return NormalizedRect(); NormalizedRect ret; ret.left = qMax( left, r.left ); ret.top = qMax( top, r.top ); ret.bottom = qMin( bottom, r.bottom ); ret.right = qMin( right, r.right ); return ret; } NormalizedRect & NormalizedRect::operator=( const NormalizedRect & r ) { left = r.left; right = r.right; top = r.top; bottom = r.bottom; return *this; } bool NormalizedRect::operator==( const NormalizedRect & r ) const { return ( isNull() && r.isNull() ) || ( fabs( left - r.left ) < 1e-4 && fabs( right - r.right ) < 1e-4 && fabs( top - r.top ) < 1e-4 && fabs( bottom - r.bottom ) < 1e-4 ); } NormalizedPoint NormalizedRect::center() const { return NormalizedPoint((left+right)/2.0, (top+bottom)/2.0); } /* QDebug operator << (QDebug str , const NormalizedRect &r) { str << "[" <(), d( nullptr ) { } RegularAreaRect::RegularAreaRect( const RegularAreaRect& rar ) : RegularArea< NormalizedRect, QRect >( rar ), d( nullptr ) { } RegularAreaRect::~RegularAreaRect() { } RegularAreaRect& RegularAreaRect::operator=( const RegularAreaRect& rar ) { RegularArea< NormalizedRect, QRect >::operator=( rar ); return *this; } HighlightAreaRect::HighlightAreaRect( const RegularAreaRect *area ) : RegularAreaRect(), s_id( -1 ) { if ( area ) { RegularAreaRect::ConstIterator it = area->begin(); RegularAreaRect::ConstIterator itEnd = area->end(); for ( ; it != itEnd; ++it ) { append( NormalizedRect( *it ) ); } } } /** class ObjectRect **/ ObjectRect::ObjectRect( double l, double t, double r, double b, bool ellipse, ObjectType type, void * pnt ) : m_objectType( type ), m_object( pnt ) { // assign coordinates swapping them if negative width or height QRectF rect( r > l ? l : r, b > t ? t : b, fabs( r - l ), fabs( b - t ) ); if ( ellipse ) m_path.addEllipse( rect ); else m_path.addRect( rect ); m_transformedPath = m_path; } ObjectRect::ObjectRect( const NormalizedRect& x, bool ellipse, ObjectType type, void * pnt ) : m_objectType( type ), m_object( pnt ) { QRectF rect( x.left, x.top, fabs( x.right - x.left ), fabs( x.bottom - x.top ) ); if ( ellipse ) m_path.addEllipse( rect ); else m_path.addRect( rect ); m_transformedPath = m_path; } ObjectRect::ObjectRect( const QPolygonF &poly, ObjectType type, void * pnt ) : m_objectType( type ), m_object( pnt ) { m_path.addPolygon( poly ); m_transformedPath = m_path; } ObjectRect::ObjectType ObjectRect::objectType() const { return m_objectType; } const void * ObjectRect::object() const { return m_object; } const QPainterPath &ObjectRect::region() const { return m_transformedPath; } QRect ObjectRect::boundingRect( double xScale, double yScale ) const { const QRectF &br = m_transformedPath.boundingRect(); return QRect( (int)( br.left() * xScale ), (int)( br.top() * yScale ), (int)( br.width() * xScale ), (int)( br.height() * yScale ) ); } bool ObjectRect::contains( double x, double y, double, double ) const { return m_transformedPath.contains( QPointF( x, y ) ); } void ObjectRect::transform( const QTransform &matrix ) { m_transformedPath = matrix.map( m_path ); } double ObjectRect::distanceSqr( double x, double y, double xScale, double yScale ) const { switch ( m_objectType ) { case Action: case Image: { const QRectF& rect( m_transformedPath.boundingRect() ); return NormalizedRect( rect.x(), rect.y(), rect.right(), rect.bottom() ).distanceSqr( x, y, xScale, yScale ); } case OAnnotation: { return static_cast(m_object)->d_func()->distanceSqr( x, y, xScale, yScale ); } case SourceRef: { const SourceRefObjectRect * sr = static_cast< const SourceRefObjectRect * >( this ); const NormalizedPoint& point = sr->m_point; if ( point.x == -1.0 ) { return pow( ( y - point.y ) * yScale, 2 ); } else if ( point.y == -1.0 ) { return pow( ( x - point.x ) * xScale, 2 ); } else { return pow( ( x - point.x ) * xScale, 2 ) + pow( ( y - point.y ) * yScale, 2 ); } } } return 0.0; } ObjectRect::~ObjectRect() { if ( !m_object ) return; if ( m_objectType == Action ) delete static_cast( m_object ); else if ( m_objectType == SourceRef ) delete static_cast( m_object ); else qCDebug(OkularCoreDebug).nospace() << "Object deletion not implemented for type '" << m_objectType << "'."; } /** class AnnotationObjectRect **/ AnnotationObjectRect::AnnotationObjectRect( Annotation * annotation ) : ObjectRect( QPolygonF(), OAnnotation, annotation ), m_annotation( annotation ) { } Annotation *AnnotationObjectRect::annotation() const { return m_annotation; } QRect AnnotationObjectRect::boundingRect( double xScale, double yScale ) const { const QRect annotRect = AnnotationUtils::annotationGeometry( m_annotation, xScale, yScale ); const QPoint center = annotRect.center(); // Make sure that the rectangle has a minimum size, so that it's possible // to click on it const int minSize = 14; const QRect minRect( center.x()-minSize/2, center.y()-minSize/2, minSize, minSize ); return annotRect | minRect; } bool AnnotationObjectRect::contains( double x, double y, double xScale, double yScale ) const { return boundingRect( xScale, yScale ).contains( (int)( x * xScale ), (int)( y * yScale ), false ); } AnnotationObjectRect::~AnnotationObjectRect() { // the annotation pointer is kept elsewehere (in Page, most probably), // so just release its pointer m_object = nullptr; } void AnnotationObjectRect::transform( const QTransform &matrix ) { m_annotation->d_func()->annotationTransform( matrix ); } /** class SourceRefObjectRect **/ SourceRefObjectRect::SourceRefObjectRect( const NormalizedPoint& point, void * srcRef ) : ObjectRect( point.x, point.y, .0, .0, false, SourceRef, srcRef ), m_point( point ) { const double x = m_point.x < 0.0 ? 0.5 : m_point.x; const double y = m_point.y < 0.0 ? 0.5 : m_point.y; const QRectF rect( x - 2, y - 2, 5, 5 ); m_path.addRect( rect ); m_transformedPath = m_path; } QRect SourceRefObjectRect::boundingRect( double xScale, double yScale ) const { const double x = m_point.x < 0.0 ? 0.5 : m_point.x; const double y = m_point.y < 0.0 ? 0.5 : m_point.y; return QRect( x * xScale, y * yScale, 1, 1 ); } bool SourceRefObjectRect::contains( double x, double y, double xScale, double yScale ) const { return distanceSqr( x, y, xScale, yScale ) < ( pow( 7.0 / xScale, 2 ) + pow( 7.0 / yScale, 2 ) ); } diff --git a/core/area.h b/core/area.h index 4299b003c..964f9da65 100644 --- a/core/area.h +++ b/core/area.h @@ -1,903 +1,903 @@ /*************************************************************************** * Copyright (C) 2004-05 by Enrico Ros * * Copyright (C) 2005 by Piotr Szymanski * * 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_AREA_H_ #define _OKULAR_AREA_H_ #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "global.h" #include "okularcore_export.h" class QPolygonF; class QRect; namespace Okular { class Annotation; class Action; class NormalizedShape; /** * NormalizedPoint is a helper class which stores the coordinates * of a normalized point. Normalized means that the coordinates are * between 0 and 1 so that it is page size independent. * * Example: * The normalized point is (0.5, 0.3) * * If you want to draw it on a 800x600 page, just multiply the x coordinate (0.5) with * the page width (800) and the y coordinate (0.3) with the page height (600), so * the point will be drawn on the page at (400, 180). * * That allows you to zoom the page by just multiplying the normalized points with the * zoomed page size. */ class OKULARCORE_EXPORT NormalizedPoint { public: /** * Creates a new empty normalized point. */ NormalizedPoint(); /** * Creates a new normalized point with the normalized coordinates (@p x, @p y ). */ NormalizedPoint( double x, double y ); /** * Creates a new normalized point with the coordinates (@p x, @p y) which are normalized * by the scaling factors @p xScale and @p yScale. */ NormalizedPoint( int x, int y, int xScale, int yScale ); /** * @internal */ NormalizedPoint& operator=( const NormalizedPoint& ); /** * Transforms the normalized point with the operations defined by @p matrix. */ void transform( const QTransform &matrix ); /** * Returns squared distance to point @p x @p y @p xScale @p yScale * @since 0.17 (KDE 4.11) */ double distanceSqr( double x, double y, double xScale, double yScale ) const; /** * @brief Calculates distance of the point @p x @p y @p xScale @p yScale to the line segment from @p start to @p end * @since 0.17 (KDE 4.11) */ static double distanceSqr( double x, double y, double xScale, double yScale, const NormalizedPoint& start, const NormalizedPoint& end ); /** * The normalized x coordinate. */ double x; /** * The normalized y coordinate. */ double y; }; /** * NormalizedRect is a helper class which stores the coordinates * of a normalized rect, which is a rectangle of @see NormalizedPoints. */ class OKULARCORE_EXPORT NormalizedRect { public: /** * Creates a null normalized rectangle. * @see isNull() */ NormalizedRect(); /** * Creates a normalized rectangle with the normalized coordinates * @p left, @p top, @p right, @p bottom. * * If you need the x, y, width and height coordinates use the * following formulas: * * @li x = left * @li y = top * @li width = right - left * @li height = bottom - top */ NormalizedRect( double left, double top, double right, double bottom ); /** * Creates a normalized rectangle of the given @p rectangle which is normalized * by the scaling factors @p xScale and @p yScale. */ NormalizedRect( const QRect &rectangle, double xScale, double yScale ); /** * @internal */ NormalizedRect( const NormalizedRect& ); /** * @internal */ NormalizedRect& operator=( const NormalizedRect &other ); /** * Build a normalized rect from a QRectF. */ static NormalizedRect fromQRectF( const QRectF &rect ); /** * Returns whether this normalized rectangle is a null normalized rect. */ bool isNull() const; /** * Returns whether the normalized rectangle contains the normalized coordinates * @p x and @p y. */ bool contains( double x, double y ) const; /** * Returns whether the normalized rectangle intersects the @p other normalized * rectangle. */ bool intersects( const NormalizedRect &other ) const; /** * This is an overloaded member function, provided for convenience. It behaves essentially * like the above function. */ bool intersects( const NormalizedRect *other ) const; /** * Returns whether the normalized rectangle intersects an other normalized * rectangle, which is defined by @p left, @p top, @p right and @p bottom. */ bool intersects( double left, double top, double right, double bottom ) const; /** * Returns the rectangle that accrues when the normalized rectangle is multiplyed * with the scaling @p xScale and @p yScale. */ QRect geometry( int xScale, int yScale ) const; /** * Same functionality as geometry, but the output is now rounded before typecasting to int * @since 0.14 (KDE 4.8) */ QRect roundedGeometry( int xScale, int yScale ) const; /** * Returns the normalized bounding rectangle of the normalized rectangle * combined with the @p other normalized rectangle. */ NormalizedRect operator|( const NormalizedRect &other ) const; /** * Sets the normalized rectangle to the normalized bounding rectangle * of itself combined with the @p other normalized rectangle. */ NormalizedRect& operator|=( const NormalizedRect &other ); /** * Returns the intersection of this normalized rectangle with the specified * @p other. If the rects do not intersect then the result is null. * * @since 0.7 (KDE 4.1) */ NormalizedRect operator&( const NormalizedRect &other ) const; /** * Returns whether the normalized rectangle is equal to the @p other * normalized rectangle. */ bool operator==( const NormalizedRect &other ) const; /** * Returns the center of the rectangle * @since 0.10 (KDE 4.4) */ NormalizedPoint center() const; /** * Transforms the normalized rectangle with the operations defined by @p matrix. */ void transform( const QTransform &matrix ); /** * Returns true if the point pt is located to the bottom of the rectangle * @since 0.14 (KDE 4.8) */ bool isBottom(const NormalizedPoint& pt) const { return bottom < pt.y; } /** * Returns true if the point pt is located on the top of the rectangle * @since 0.14 (KDE 4.8) */ bool isTop(const NormalizedPoint& pt) const { return top > pt.y; } /** * Returns true if the point pt is located under the top of the rectangle * @since 0.14 (KDE 4.8) */ bool isBottomOrLevel(const NormalizedPoint& pt) const { return top < pt.y; } /** * Returns true if the point pt is located above the bottom of the rectangle * @since 0.14 (KDE 4.8) */ bool isTopOrLevel(const NormalizedPoint& pt) const { return bottom > pt.y; } /** * Returns true if the point pt is located to the right of the left arm of rectangle * @since 0.14 (KDE 4.8) */ bool isLeft(const NormalizedPoint& pt) const { return left < pt.x; } /** * Returns true if the point pt is located to the left of the right arm of rectangle * @since 0.14 (KDE 4.8) */ bool isRight(const NormalizedPoint& pt) const { return right > pt.x; } /** * Returns the distance of the point @p x @p y @p xScale @p yScale to the closest * edge or 0 if the point is within the rectangle * @since 0.17 (KDE 4.11) */ double distanceSqr(double x, double y, double xScale, double yScale) const { double distX = 0; if ( x < left ) distX = left - x; else if ( x > right ) distX = x - right; double distY = 0; if ( top > y ) distY = top - y; else if (bottom < y) distY = y - bottom; return pow( distX * xScale, 2 ) + pow( distY * yScale, 2 ); } /// @since 1.4 double width() const { return right - left; } /// @since 1.4 double height() const { return bottom - top; } /** * The normalized left coordinate. */ double left; /** * The normalized top coordinate. */ double top; /** * The normalized right coordinate. */ double right; /** * The normalized bottom coordinate. */ double bottom; }; //KDE_DUMMY_QHASH_FUNCTION(NormalizedRect) /** * @short NormalizedRect that contains a reference to an object. * * These rects contains a pointer to a okular object (such as an action or something * like that). The pointer is read and stored as 'void pointer' so cast is * performed by accessors based on the value returned by objectType(). Objects * are reparented to this class. * * Type / Class correspondency tab: * - Action : class Action: description of an action * - Image : class Image : description of an image (n/a) * - Annotation: class Annotation: description of an annotation */ class OKULARCORE_EXPORT ObjectRect { public: /** * Describes the type of storable object. */ enum ObjectType { Action, ///< An action Image, ///< An image OAnnotation, ///< An annotation SourceRef ///< A source reference }; /** * Creates a new object rectangle. * * @param left The left coordinate of the rectangle. * @param top The top coordinate of the rectangle. * @param right The right coordinate of the rectangle. * @param bottom The bottom coordinate of the rectangle. * @param ellipse If true the rectangle describes an ellipse. * @param type The type of the storable object @see ObjectType. * @param object The pointer to the storable object. */ ObjectRect( double left, double top, double right, double bottom, bool ellipse, ObjectType type, void *object ); /** * This is an overloaded member function, provided for convenience. */ ObjectRect( const NormalizedRect &rect, bool ellipse, ObjectType type, void *object ); /** * This is an overloaded member function, provided for convenience. */ ObjectRect( const QPolygonF &poly, ObjectType type, void *object ); /** * Destroys the object rectangle. */ virtual ~ObjectRect(); /** * Returns the object type of the object rectangle. * @see ObjectType */ ObjectType objectType() const; /** * Returns the storable object of the object rectangle. */ const void *object() const; /** * Returns the region that is covered by the object rectangle. */ const QPainterPath ®ion() const; /** * Returns the bounding rect of the object rectangle for the * scaling factor @p xScale and @p yScale. */ virtual QRect boundingRect( double xScale, double yScale ) const; /** * Returns whether the object rectangle contains the point @p x, @p y for the * scaling factor @p xScale and @p yScale. */ virtual bool contains( double x, double y, double xScale, double yScale ) const; /** * Transforms the object rectangle with the operations defined by @p matrix. */ virtual void transform( const QTransform &matrix ); /** * Returns the square of the distance between the object and the point @p x, @p y * for the scaling factor @p xScale and @p yScale. * * @since 0.8.2 (KDE 4.2.2) */ // FIXME this should most probably be a virtual method double distanceSqr( double x, double y, double xScale, double yScale ) const; protected: ObjectType m_objectType; void * m_object; QPainterPath m_path; QPainterPath m_transformedPath; }; /** * This class describes the object rectangle for an annotation. */ class OKULARCORE_EXPORT AnnotationObjectRect : public ObjectRect { public: /** * Creates a new annotation object rectangle with the * given @p annotation. */ AnnotationObjectRect( Annotation *annotation ); /** * Destroys the annotation object rectangle. */ virtual ~AnnotationObjectRect(); /** * Returns the annotation object of the annotation object rectangle. */ Annotation *annotation() const; /** * Returns the bounding rect of the annotation object rectangle for the * scaling factor @p xScale and @p yScale. */ QRect boundingRect( double xScale, double yScale ) const override; /** * Returns whether the annotation object rectangle contains the point @p x, @p y for the * scaling factor @p xScale and @p yScale. */ bool contains( double x, double y, double xScale, double yScale ) const override; /** * Transforms the annotation object rectangle with the operations defined by @p matrix. */ void transform( const QTransform &matrix ) override; private: Annotation * m_annotation; }; /** * This class describes the object rectangle for a source reference. */ class OKULARCORE_EXPORT SourceRefObjectRect : public ObjectRect { friend class ObjectRect; public: /** * Creates a new source reference object rectangle. * * @param point The point of the source reference. * @param reference The storable source reference object. */ SourceRefObjectRect( const NormalizedPoint& point, void *reference ); /** * Returns the bounding rect of the source reference object rectangle for the * scaling factor @p xScale and @p yScale. */ QRect boundingRect( double xScale, double yScale ) const override; /** * Returns whether the source reference object rectangle contains the point @p x, @p y for the * scaling factor @p xScale and @p yScale. */ bool contains( double x, double y, double xScale, double yScale ) const override; private: NormalizedPoint m_point; }; /// @cond PRIVATE /** @internal */ template void doDelete( T& t ) { (void)t; } /** @internal */ template T* givePtr( T& t ) { return &t; } /** @internal */ template T& deref( T& t ) { return t; } /** @internal */ template static void doDelete( T* t ) { delete t; } /** @internal */ template static T* givePtr( T* t ) { return t; } /** @internal */ template static T& deref( T* t ) { return *t; } /// @endcond /** * @short A regular area of NormalizedShape which normalizes a Shape * * Class NormalizedShape \b must have the following functions/operators defined: * - bool contains( double, double ) * - bool intersects( NormalizedShape ) * - bool isNull() * - Shape geometry( int, int ) * - operator|=( NormalizedShape ) which unite two NormalizedShape's */ template class RegularArea : public QList { public: /** * Destroys a regular area. */ ~RegularArea(); /** * Returns whether the regular area contains the * normalized point @p x, @p y. */ bool contains( double x, double y ) const; /** * Returns whether the regular area contains the * given @p shape. */ bool contains( const NormalizedShape& shape ) const; /** * Returns whether the regular area intersects with the given @p area. */ bool intersects( const RegularArea *area ) const; /** * Returns whether the regular area intersects with the given @p shape. */ bool intersects( const NormalizedShape& shape ) const; /** * Appends the given @p area to the regular area. */ void appendArea( const RegularArea *area ); /** * Appends the given @p shape to the regular area. */ void appendShape( const NormalizedShape& shape, MergeSide side = MergeAll ); /** * Simplifies the regular area by merging its intersecting subareas. */ void simplify(); /** * Returns whether the regular area is a null area. */ bool isNull() const; /** * Returns the subareas of the regular areas as shapes for the given scaling factor * @p xScale and @p yScale, translated by @p dx and @p dy. */ QList geometry( int xScale, int yScale, int dx = 0, int dy = 0 ) const; /** * Transforms the regular area with the operations defined by @p matrix. */ void transform( const QTransform &matrix ); }; template RegularArea::~RegularArea() { int size = this->count(); for ( int i = 0; i < size; ++i ) doDelete( (*this)[i] ); } template void RegularArea::simplify() { #ifdef DEBUG_REGULARAREA int prev_end = this->count(); #endif int end = this->count() - 1, x = 0; for ( int i = 0; i < end; ++i ) { if ( givePtr( (*this)[x] )->intersects( deref( (*this)[i+1] ) ) ) { deref((*this)[x]) |= deref((*this)[i+1]); NormalizedShape& tobedeleted = (*this)[i+1]; this->removeAt( i + 1 ); doDelete( tobedeleted ); --end; --i; } else { x=i+1; } } #ifdef DEBUG_REGULARAREA qCDebug(OkularCoreDebug) << "from" << prev_end << "to" << this->count(); #endif } template bool RegularArea::isNull() const { if ( this->isEmpty() ) return false; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) if ( !givePtr( *it )->isNull() ) return false; return true; } template bool RegularArea::intersects( const NormalizedShape& rect ) const { if ( this->isEmpty() ) return false; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) if ( !givePtr( *it )->isNull() && givePtr( *it )->intersects( rect ) ) return true; return false; } template bool RegularArea::intersects( const RegularArea *area ) const { if ( this->isEmpty() ) return false; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) { typename QList::const_iterator areaIt = area->begin(), areaItEnd = area->end(); for ( ; areaIt != areaItEnd; ++areaIt ) { if ( !( *it ).isNull() && ( *it ).intersects( *areaIt ) ) return true; } } return false; } template void RegularArea::appendArea( const RegularArea *area ) { typename QList::const_iterator areaIt = area->begin(), areaItEnd = area->end(); for ( ; areaIt != areaItEnd; ++areaIt ) this->append( *areaIt ); } template void RegularArea::appendShape( const NormalizedShape& shape, MergeSide side ) { int size = this->count(); // if the list is empty, adds the shape normally if ( size == 0 ) { this->append( shape ); } else { bool intersection = false; NormalizedShape& last = (*this)[size - 1]; #define O_LAST givePtr( last ) # define O_LAST_R O_LAST->right # define O_LAST_L O_LAST->left # define O_LAST_T O_LAST->top # define O_LAST_B O_LAST->bottom #define O_NEW givePtr( shape ) # define O_NEW_R O_NEW->right # define O_NEW_L O_NEW->left # define O_NEW_T O_NEW->top # define O_NEW_B O_NEW->bottom switch ( side ) { case MergeRight: intersection = ( O_LAST_R >= O_NEW_L ) && ( O_LAST_L <= O_NEW_R ) && ( ( O_LAST_T <= O_NEW_T && O_LAST_B >= O_NEW_B ) || ( O_LAST_T >= O_NEW_T && O_LAST_B <= O_NEW_B ) ); break; case MergeBottom: intersection = ( O_LAST_B >= O_NEW_T ) && ( O_LAST_T <= O_NEW_B ) && ( ( O_LAST_R <= O_NEW_R && O_LAST_L >= O_NEW_L ) || ( O_LAST_R >= O_NEW_R && O_LAST_L <= O_NEW_L ) ); break; case MergeLeft: intersection = ( O_LAST_L <= O_NEW_R ) && ( O_LAST_R >= O_NEW_L ) && ( ( O_LAST_T <= O_NEW_T && O_LAST_B >= O_NEW_B ) || ( O_LAST_T >= O_NEW_T && O_LAST_B <= O_NEW_B ) ); break; case MergeTop: intersection = ( O_LAST_T <= O_NEW_B ) && ( O_LAST_B >= O_NEW_T ) && ( ( O_LAST_R <= O_NEW_R && O_LAST_L >= O_NEW_L ) || ( O_LAST_R >= O_NEW_R && O_LAST_L <= O_NEW_L ) ); break; case MergeAll: intersection = O_LAST->intersects( shape ); break; } #undef O_LAST # undef O_LAST_R # undef O_LAST_L # undef O_LAST_T # undef O_LAST_B #undef O_NEW # undef O_NEW_R # undef O_NEW_L # undef O_NEW_T # undef O_NEW_B // if the new shape intersects with the last shape in the list, then // merge it with that and delete the shape if ( intersection ) { deref((*this)[size - 1]) |= deref( shape ); doDelete( const_cast( shape ) ); } else this->append( shape ); } } template bool RegularArea::contains( double x, double y ) const { if ( this->isEmpty() ) return false; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) if ( ( *it ).contains( x, y ) ) return true; return false; } template bool RegularArea::contains( const NormalizedShape& shape ) const { if ( this->isEmpty() ) return false; return QList::contains( shape ); } template QList RegularArea::geometry( int xScale, int yScale, int dx, int dy ) const { if ( this->isEmpty() ) return QList(); QList ret; Shape t; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) { t = givePtr( *it )->geometry( xScale, yScale ); t.translate( dx, dy ); ret.append( t ); } return ret; } template void RegularArea::transform( const QTransform &matrix ) { if ( this->isEmpty() ) return; for ( int i = 0; i < this->count(); ++i ) givePtr( (*this)[i] )->transform( matrix ); } class OKULARCORE_EXPORT RegularAreaRect : public RegularArea< NormalizedRect, QRect > { public: RegularAreaRect(); RegularAreaRect( const RegularAreaRect& rar ); ~RegularAreaRect(); RegularAreaRect& operator=( const RegularAreaRect& rar ); private: class Private; Private * const d; }; /** * This class stores the coordinates of a highlighting area * together with the id of the highlight owner and the color. */ class HighlightAreaRect : public RegularAreaRect { public: /** * Creates a new highlight area rect with the coordinates of * the given @p area. */ HighlightAreaRect( const RegularAreaRect *area = nullptr ); /** * The search ID of the highlight owner. */ int s_id; /** * The color of the highlight. */ QColor color; }; } uint qHash(const Okular::NormalizedRect& r, uint seed = 0); #ifndef QT_NO_DEBUG_STREAM /** * Debug operator for normalized @p point. */ OKULARCORE_EXPORT QDebug operator<<( QDebug str, const Okular::NormalizedPoint &point ); /** * Debug operator for normalized @p rect. */ OKULARCORE_EXPORT QDebug operator<<( QDebug str, const Okular::NormalizedRect &rect ); #endif #endif diff --git a/core/audioplayer.cpp b/core/audioplayer.cpp index f66132384..8a8975384 100644 --- a/core/audioplayer.cpp +++ b/core/audioplayer.cpp @@ -1,259 +1,259 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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 "audioplayer.h" #include "audioplayer_p.h" // qt/kde includes #include #include -#include +#include #include #include #include #include #include // local includes #include "action.h" #include "debug_p.h" #include "sound.h" using namespace Okular; // helper class used to store info about a sound to be played class SoundInfo { public: explicit SoundInfo( const Sound * s = nullptr, const SoundAction * ls = nullptr ) : sound( s ), volume( 0.5 ), synchronous( false ), repeat( false ), mix( false ) { if ( ls ) { volume = ls->volume(); synchronous = ls->synchronous(); repeat = ls->repeat(); mix = ls->mix(); } } const Sound * sound; double volume; bool synchronous; bool repeat; bool mix; }; class PlayData { public: PlayData() : m_mediaobject( nullptr ), m_output( nullptr ), m_buffer( nullptr ) { } void play() { if ( m_buffer ) { m_buffer->open( QIODevice::ReadOnly ); } m_mediaobject->play(); } ~PlayData() { m_mediaobject->stop(); delete m_mediaobject; delete m_output; delete m_buffer; } Phonon::MediaObject * m_mediaobject; Phonon::AudioOutput * m_output; QBuffer * m_buffer; SoundInfo m_info; }; AudioPlayerPrivate::AudioPlayerPrivate( AudioPlayer * qq ) : q( qq ), m_state( AudioPlayer::StoppedState ) { QObject::connect( &m_mapper, SIGNAL(mapped(int)), q, SLOT(finished(int)) ); } AudioPlayerPrivate::~AudioPlayerPrivate() { stopPlayings(); } int AudioPlayerPrivate::newId() const { int newid = 0; QHash< int, PlayData * >::const_iterator it; QHash< int, PlayData * >::const_iterator itEnd = m_playing.constEnd(); do { newid = KRandom::random(); it = m_playing.constFind( newid ); } while ( it != itEnd ); return newid; } bool AudioPlayerPrivate::play( const SoundInfo& si ) { qCDebug(OkularCoreDebug) ; PlayData * data = new PlayData(); data->m_output = new Phonon::AudioOutput( Phonon::NotificationCategory ); data->m_output->setVolume( si.volume ); data->m_mediaobject = new Phonon::MediaObject(); Phonon::createPath(data->m_mediaobject, data->m_output); data->m_info = si; bool valid = false; switch ( si.sound->soundType() ) { case Sound::External: { QString url = si.sound->url(); qCDebug(OkularCoreDebug) << "External," << url; if ( !url.isEmpty() ) { int newid = newId(); m_mapper.setMapping( data->m_mediaobject, newid ); QUrl newurl; if ( QUrl::fromUserInput(url).isRelative() ) { newurl = m_currentDocument.adjusted(QUrl::RemoveFilename); newurl.setPath(newurl.path() + url ); } else { newurl = QUrl::fromLocalFile(url); } data->m_mediaobject->setCurrentSource( newurl ); m_playing.insert( newid, data ); valid = true; } break; } case Sound::Embedded: { QByteArray filedata = si.sound->data(); qCDebug(OkularCoreDebug) << "Embedded," << filedata.length(); if ( !filedata.isEmpty() ) { qCDebug(OkularCoreDebug) << "Mediaobject:" << data->m_mediaobject; int newid = newId(); m_mapper.setMapping( data->m_mediaobject, newid ); data->m_buffer = new QBuffer(); data->m_buffer->setData( filedata ); data->m_mediaobject->setCurrentSource( Phonon::MediaSource( data->m_buffer ) ); m_playing.insert( newid, data ); valid = true; } break; } } if ( !valid ) { delete data; data = nullptr; } if ( data ) { QObject::connect( data->m_mediaobject, SIGNAL(finished()), &m_mapper, SLOT(map()) ); qCDebug(OkularCoreDebug) << "PLAY"; data->play(); m_state = AudioPlayer::PlayingState; } return valid; } void AudioPlayerPrivate::stopPlayings() { qDeleteAll( m_playing ); m_playing.clear(); m_state = AudioPlayer::StoppedState; } void AudioPlayerPrivate::finished( int id ) { QHash< int, PlayData * >::iterator it = m_playing.find( id ); if ( it == m_playing.end() ) return; SoundInfo si = it.value()->m_info; // if the sound must be repeated indefinitely, then start the playback // again, otherwise destroy the PlayData as it's no more useful if ( si.repeat ) { it.value()->play(); } else { m_mapper.removeMappings( it.value()->m_mediaobject ); delete it.value(); m_playing.erase( it ); m_state = AudioPlayer::StoppedState; } qCDebug(OkularCoreDebug) << "finished," << m_playing.count(); } AudioPlayer::AudioPlayer() : QObject(), d( new AudioPlayerPrivate( this ) ) { } AudioPlayer::~AudioPlayer() { delete d; } AudioPlayer * AudioPlayer::instance() { static AudioPlayer ap; return ≈ } void AudioPlayer::playSound( const Sound * sound, const SoundAction * linksound ) { // we can't play null pointers ;) if ( !sound ) return; // we don't play external sounds for remote documents if ( sound->soundType() == Sound::External && !d->m_currentDocument.isLocalFile() ) return; qCDebug(OkularCoreDebug) ; SoundInfo si( sound, linksound ); // if the mix flag of the new sound is false, then the currently playing // sounds must be stopped. if ( !si.mix ) d->stopPlayings(); d->play( si ); } void AudioPlayer::stopPlaybacks() { d->stopPlayings(); } AudioPlayer::State AudioPlayer::state() const { return d->m_state; } #include "moc_audioplayer.cpp" diff --git a/core/audioplayer.h b/core/audioplayer.h index a221b031b..9b28be260 100644 --- a/core/audioplayer.h +++ b/core/audioplayer.h @@ -1,89 +1,89 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_AUDIOPLAYER_H_ #define _OKULAR_AUDIOPLAYER_H_ #include "okularcore_export.h" -#include +#include namespace Okular { class AudioPlayerPrivate; class Document; class Sound; class SoundAction; /** * @short An audio player. * * Singleton utility class to play sounds in documents using the KDE sound * system. */ class OKULARCORE_EXPORT AudioPlayer : public QObject { Q_OBJECT public: /** * The state of AudioPlayer * @since 0.19 (KDE 4.13) */ enum State { /** * The AudioPlayer is playing a audio file. */ PlayingState, /** * The AudioPlayer isn't playing a audio file. */ StoppedState }; ~AudioPlayer(); /** * Gets the instance of the audio player. */ static AudioPlayer * instance(); /** * Enqueue the specified @p sound for playing, optionally taking more * information about the playing from the @p soundlink . */ void playSound( const Sound * sound, const SoundAction * linksound = nullptr ); /** * Tell the AudioPlayer to stop all the playbacks. */ void stopPlaybacks(); /** * Return state of sound (playing/stopped) * @since 0.19 (KDE 4.13) */ State state() const; private: AudioPlayer(); friend class AudioPlayerPrivate; AudioPlayerPrivate * const d; friend class Document; Q_DISABLE_COPY( AudioPlayer ) Q_PRIVATE_SLOT( d, void finished( int ) ) }; } #endif diff --git a/core/chooseenginedialog.cpp b/core/chooseenginedialog.cpp index 2800d989c..06f44db3c 100644 --- a/core/chooseenginedialog.cpp +++ b/core/chooseenginedialog.cpp @@ -1,56 +1,56 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 "chooseenginedialog_p.h" -#include -#include +#include +#include #include #include #include #include #include "ui_chooseenginewidget.h" ChooseEngineDialog::ChooseEngineDialog( const QStringList &generators, const QMimeType &mime, QWidget * parent ) : QDialog( parent ) { setWindowTitle( i18n( "Backend Selection" ) ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ChooseEngineDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ChooseEngineDialog::reject); okButton->setDefault(true); QWidget *main = new QWidget( this ); m_widget = new Ui_ChooseEngineWidget(); m_widget->setupUi( main ); mainLayout->addWidget(main); mainLayout->addWidget(buttonBox); m_widget->engineList->addItems(generators); m_widget->description->setText( i18n( "More than one backend found for the MIME type:
" "%1 (%2).

" "Please select which one to use:
", mime.comment(), mime.name() ) ); } ChooseEngineDialog::~ChooseEngineDialog() { delete m_widget; } int ChooseEngineDialog::selectedGenerator() const { return m_widget->engineList->currentIndex(); } diff --git a/core/chooseenginedialog_p.h b/core/chooseenginedialog_p.h index 9477ceb0f..f15cacee8 100644 --- a/core/chooseenginedialog_p.h +++ b/core/chooseenginedialog_p.h @@ -1,34 +1,34 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 _CHOOSEENGINEDIALOG_H #define _CHOOSEENGINEDIALOG_H -#include +#include #include #include class Ui_ChooseEngineWidget; class ChooseEngineDialog : public QDialog { Q_OBJECT public: ChooseEngineDialog( const QStringList &generators, const QMimeType &mime, QWidget * parent = nullptr ); ~ChooseEngineDialog(); int selectedGenerator() const; protected: Ui_ChooseEngineWidget * m_widget; }; #endif diff --git a/core/debug_p.h b/core/debug_p.h index ff302e4e8..e62f8f3a4 100644 --- a/core/debug_p.h +++ b/core/debug_p.h @@ -1,18 +1,18 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * Copyright (C) 2014 by Frederik Gladhorn * * * * 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_DEBUG_P_H #define OKULAR_DEBUG_P_H -#include +#include Q_DECLARE_LOGGING_CATEGORY(OkularCoreDebug) #endif diff --git a/core/document.cpp b/core/document.cpp index 5e3c169fc..bffbcf86c 100644 --- a/core/document.cpp +++ b/core/document.cpp @@ -1,5630 +1,5628 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2008 by Albert Astals Cid * * Copyright (C) 2017, 2018 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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 "document.h" #include "document_p.h" #include "documentcommands_p.h" #include #include #ifdef Q_OS_WIN #define _WIN32_WINNT 0x0500 #include #elif defined(Q_OS_FREEBSD) #include #include #include #endif // qt/kde/system 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include // local includes #include "action.h" #include "annotations.h" #include "annotations_p.h" #include "audioplayer.h" #include "audioplayer_p.h" #include "bookmarkmanager.h" #include "chooseenginedialog_p.h" #include "debug_p.h" #include "generator_p.h" #include "interfaces/configinterface.h" #include "interfaces/guiinterface.h" #include "interfaces/printinterface.h" #include "interfaces/saveinterface.h" #include "observer.h" #include "misc.h" #include "page.h" #include "page_p.h" #include "pagecontroller_p.h" #include "scripter.h" #include "script/event_p.h" #include "settings_core.h" #include "sourcereference.h" #include "sourcereference_p.h" #include "texteditors_p.h" #include "tile.h" #include "tilesmanager_p.h" #include "utils_p.h" #include "view.h" #include "view_p.h" #include "form.h" #include "utils.h" -#include - #include #if HAVE_MALLOC_TRIM #include "malloc.h" #endif using namespace Okular; struct AllocatedPixmap { // owner of the page DocumentObserver *observer; int page; qulonglong memory; // public constructor: initialize data AllocatedPixmap( DocumentObserver *o, int p, qulonglong m ) : observer( o ), page( p ), memory( m ) {} }; struct ArchiveData { ArchiveData() { } QString originalFileName; QTemporaryFile document; QTemporaryFile metadataFile; }; struct RunningSearch { // store search properties int continueOnPage; RegularAreaRect continueOnMatch; QSet< int > highlightedPages; // fields related to previous searches (used for 'continueSearch') QString cachedString; Document::SearchType cachedType; Qt::CaseSensitivity cachedCaseSensitivity; bool cachedViewportMove : 1; bool isCurrentlySearching : 1; QColor cachedColor; int pagesDone; }; #define foreachObserver( cmd ) {\ QSet< DocumentObserver * >::const_iterator it=d->m_observers.constBegin(), end=d->m_observers.constEnd();\ for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } #define foreachObserverD( cmd ) {\ QSet< DocumentObserver * >::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd();\ for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } #define OKULAR_HISTORY_MAXSTEPS 100 #define OKULAR_HISTORY_SAVEDSTEPS 10 /***** Document ******/ QString DocumentPrivate::pagesSizeString() const { if (m_generator) { if (m_generator->pagesSizeMetric() != Generator::None) { QSizeF size = m_parent->allPagesSize(); if (size.isValid()) return localizedSize(size); else return QString(); } else return QString(); } else return QString(); } QString DocumentPrivate::namePaperSize(double inchesWidth, double inchesHeight) const { const QPrinter::Orientation orientation = inchesWidth > inchesHeight ? QPrinter::Landscape : QPrinter::Portrait; const QSize pointsSize(inchesWidth *72.0, inchesHeight*72.0); const QPageSize::PageSizeId paperSize = QPageSize::id(pointsSize, QPageSize::FuzzyOrientationMatch); const QString paperName = QPageSize::name(paperSize); if (orientation == QPrinter::Portrait) { return i18nc("paper type and orientation (eg: Portrait A4)", "Portrait %1", paperName); } else { return i18nc("paper type and orientation (eg: Portrait A4)", "Landscape %1", paperName); } } QString DocumentPrivate::localizedSize(const QSizeF &size) const { double inchesWidth = 0, inchesHeight = 0; switch (m_generator->pagesSizeMetric()) { case Generator::Points: inchesWidth = size.width() / 72.0; inchesHeight = size.height() / 72.0; break; case Generator::Pixels: { const QSizeF dpi = m_generator->dpi(); inchesWidth = size.width() / dpi.width(); inchesHeight = size.height() / dpi.height(); } break; case Generator::None: break; } if (QLocale::system().measurementSystem() == QLocale::ImperialSystem) { return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight)); } else { return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 mm (%3)", QString::number(inchesWidth * 25.4, 'd', 0), QString::number(inchesHeight * 25.4, 'd', 0), namePaperSize(inchesWidth, inchesHeight)); } } qulonglong DocumentPrivate::calculateMemoryToFree() { // [MEM] choose memory parameters based on configuration profile qulonglong clipValue = 0; qulonglong memoryToFree = 0; switch ( SettingsCore::memoryLevel() ) { case SettingsCore::EnumMemoryLevel::Low: memoryToFree = m_allocatedPixmapsTotalMemory; break; case SettingsCore::EnumMemoryLevel::Normal: { qulonglong thirdTotalMemory = getTotalMemory() / 3; qulonglong freeMemory = getFreeMemory(); if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory; if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; } break; case SettingsCore::EnumMemoryLevel::Aggressive: { qulonglong freeMemory = getFreeMemory(); if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; } break; case SettingsCore::EnumMemoryLevel::Greedy: { qulonglong freeSwap; qulonglong freeMemory = getFreeMemory( &freeSwap ); const qulonglong memoryLimit = qMin( qMax( freeMemory, getTotalMemory()/2 ), freeMemory+freeSwap ); if (m_allocatedPixmapsTotalMemory > memoryLimit) clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2; } break; } if ( clipValue > memoryToFree ) memoryToFree = clipValue; return memoryToFree; } void DocumentPrivate::cleanupPixmapMemory() { cleanupPixmapMemory( calculateMemoryToFree() ); } void DocumentPrivate::cleanupPixmapMemory( qulonglong memoryToFree ) { if ( memoryToFree < 1 ) return; const int currentViewportPage = (*m_viewportIterator).pageNumber; // Create a QMap of visible rects, indexed by page number QMap< int, VisiblePageRect * > visibleRects; QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) visibleRects.insert( (*vIt)->pageNumber, (*vIt) ); // Free memory starting from pages that are farthest from the current one int pagesFreed = 0; while ( memoryToFree > 0 ) { AllocatedPixmap * p = searchLowestPriorityPixmap( true, true ); if ( !p ) // No pixmap to remove break; qCDebug(OkularCoreDebug).nospace() << "Evicting cache pixmap observer=" << p->observer << " page=" << p->page; // m_allocatedPixmapsTotalMemory can't underflow because we always add or remove // the memory used by the AllocatedPixmap so at most it can reach zero m_allocatedPixmapsTotalMemory -= p->memory; // Make sure memoryToFree does not underflow if ( p->memory > memoryToFree ) memoryToFree = 0; else memoryToFree -= p->memory; pagesFreed++; // delete pixmap m_pagesVector.at( p->page )->deletePixmap( p->observer ); // delete allocation descriptor delete p; } // If we're still on low memory, try to free individual tiles // Store pages that weren't completely removed QLinkedList< AllocatedPixmap * > pixmapsToKeep; while (memoryToFree > 0) { int clean_hits = 0; foreach (DocumentObserver *observer, m_observers) { AllocatedPixmap * p = searchLowestPriorityPixmap( false, true, observer ); if ( !p ) // No pixmap to remove continue; clean_hits++; TilesManager *tilesManager = m_pagesVector.at( p->page )->d->tilesManager( observer ); if ( tilesManager && tilesManager->totalMemory() > 0 ) { qulonglong memoryDiff = p->memory; NormalizedRect visibleRect; if ( visibleRects.contains( p->page ) ) visibleRect = visibleRects[ p->page ]->rect; // Free non visible tiles tilesManager->cleanupPixmapMemory( memoryToFree, visibleRect, currentViewportPage ); p->memory = tilesManager->totalMemory(); memoryDiff -= p->memory; memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0; m_allocatedPixmapsTotalMemory -= memoryDiff; if ( p->memory > 0 ) pixmapsToKeep.append( p ); else delete p; } else pixmapsToKeep.append( p ); } if (clean_hits == 0) break; } m_allocatedPixmaps += pixmapsToKeep; //p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmaps.count() + pagesFreed, pagesFreed, m_allocatedPixmaps.count() ); } /* Returns the next pixmap to evict from cache, or NULL if no suitable pixmap * if found. If unloadableOnly is set, only unloadable pixmaps are returned. If * thenRemoveIt is set, the pixmap is removed from m_allocatedPixmaps before * returning it */ AllocatedPixmap * DocumentPrivate::searchLowestPriorityPixmap( bool unloadableOnly, bool thenRemoveIt, DocumentObserver *observer ) { QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmaps.end(); QLinkedList< AllocatedPixmap * >::iterator farthestPixmap = pEnd; const int currentViewportPage = (*m_viewportIterator).pageNumber; /* Find the pixmap that is farthest from the current viewport */ int maxDistance = -1; while ( pIt != pEnd ) { const AllocatedPixmap * p = *pIt; // Filter by observer if ( observer == nullptr || p->observer == observer ) { const int distance = qAbs( p->page - currentViewportPage ); if ( maxDistance < distance && ( !unloadableOnly || p->observer->canUnloadPixmap( p->page ) ) ) { maxDistance = distance; farthestPixmap = pIt; } } ++pIt; } /* No pixmap to remove */ if ( farthestPixmap == pEnd ) return nullptr; AllocatedPixmap * selectedPixmap = *farthestPixmap; if ( thenRemoveIt ) m_allocatedPixmaps.erase( farthestPixmap ); return selectedPixmap; } qulonglong DocumentPrivate::getTotalMemory() { static qulonglong cachedValue = 0; if ( cachedValue ) return cachedValue; #if defined(Q_OS_LINUX) // if /proc/meminfo doesn't exist, return 128MB QFile memFile( QStringLiteral("/proc/meminfo") ); if ( !memFile.open( QIODevice::ReadOnly ) ) return (cachedValue = 134217728); QTextStream readStream( &memFile ); while ( true ) { QString entry = readStream.readLine(); if ( entry.isNull() ) break; if ( entry.startsWith( QLatin1String("MemTotal:") ) ) return (cachedValue = (Q_UINT64_C(1024) * entry.section( QLatin1Char ( ' ' ), -2, -2 ).toULongLong())); } #elif defined(Q_OS_FREEBSD) qulonglong physmem; int mib[] = {CTL_HW, HW_PHYSMEM}; size_t len = sizeof( physmem ); if ( sysctl( mib, 2, &physmem, &len, NULL, 0 ) == 0 ) return (cachedValue = physmem); #elif defined(Q_OS_WIN) MEMORYSTATUSEX stat; stat.dwLength = sizeof(stat); GlobalMemoryStatusEx (&stat); return ( cachedValue = stat.ullTotalPhys ); #endif return (cachedValue = 134217728); } qulonglong DocumentPrivate::getFreeMemory( qulonglong *freeSwap ) { static QTime lastUpdate = QTime::currentTime().addSecs(-3); static qulonglong cachedValue = 0; static qulonglong cachedFreeSwap = 0; if ( qAbs( lastUpdate.secsTo( QTime::currentTime() ) ) <= 2 ) { if (freeSwap) *freeSwap = cachedFreeSwap; return cachedValue; } /* Initialize the returned free swap value to 0. It is overwritten if the * actual value is available */ if (freeSwap) *freeSwap = 0; #if defined(Q_OS_LINUX) // if /proc/meminfo doesn't exist, return MEMORY FULL QFile memFile( QStringLiteral("/proc/meminfo") ); if ( !memFile.open( QIODevice::ReadOnly ) ) return 0; // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers' // and 'Cached' fields. consider swapped memory as used memory. qulonglong memoryFree = 0; QString entry; QTextStream readStream( &memFile ); static const int nElems = 5; QString names[nElems] = { QStringLiteral("MemFree:"), QStringLiteral("Buffers:"), QStringLiteral("Cached:"), QStringLiteral("SwapFree:"), QStringLiteral("SwapTotal:") }; qulonglong values[nElems] = { 0, 0, 0, 0, 0 }; bool foundValues[nElems] = { false, false, false, false, false }; while ( true ) { entry = readStream.readLine(); if ( entry.isNull() ) break; for ( int i = 0; i < nElems; ++i ) { if ( entry.startsWith( names[i] ) ) { values[i] = entry.section( QLatin1Char ( ' ' ), -2, -2 ).toULongLong( &foundValues[i] ); } } } memFile.close(); bool found = true; for ( int i = 0; found && i < nElems; ++i ) found = found && foundValues[i]; if ( found ) { /* MemFree + Buffers + Cached - SwapUsed = * = MemFree + Buffers + Cached - (SwapTotal - SwapFree) = * = MemFree + Buffers + Cached + SwapFree - SwapTotal */ memoryFree = values[0] + values[1] + values[2] + values[3]; if ( values[4] > memoryFree ) memoryFree = 0; else memoryFree -= values[4]; } else { return 0; } lastUpdate = QTime::currentTime(); if (freeSwap) *freeSwap = ( cachedFreeSwap = (Q_UINT64_C(1024) * values[3]) ); return ( cachedValue = (Q_UINT64_C(1024) * memoryFree) ); #elif defined(Q_OS_FREEBSD) qulonglong cache, inact, free, psize; size_t cachelen, inactlen, freelen, psizelen; cachelen = sizeof( cache ); inactlen = sizeof( inact ); freelen = sizeof( free ); psizelen = sizeof( psize ); // sum up inactive, cached and free memory if ( sysctlbyname( "vm.stats.vm.v_cache_count", &cache, &cachelen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_inactive_count", &inact, &inactlen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_free_count", &free, &freelen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0 ) == 0 ) { lastUpdate = QTime::currentTime(); return (cachedValue = (cache + inact + free) * psize); } else { return 0; } #elif defined(Q_OS_WIN) MEMORYSTATUSEX stat; stat.dwLength = sizeof(stat); GlobalMemoryStatusEx (&stat); lastUpdate = QTime::currentTime(); if (freeSwap) *freeSwap = ( cachedFreeSwap = stat.ullAvailPageFile ); return ( cachedValue = stat.ullAvailPhys ); #else // tell the memory is full.. will act as in LOW profile return 0; #endif } bool DocumentPrivate::loadDocumentInfo( LoadDocumentInfoFlags loadWhat ) // note: load data and stores it internally (document or pages). observers // are still uninitialized at this point so don't access them { //qCDebug(OkularCoreDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file."; if ( m_xmlFileName.isEmpty() ) return false; QFile infoFile( m_xmlFileName ); return loadDocumentInfo( infoFile, loadWhat ); } bool DocumentPrivate::loadDocumentInfo( QFile &infoFile, LoadDocumentInfoFlags loadWhat ) { if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) ) return false; // Load DOM from XML file QDomDocument doc( QStringLiteral("documentInfo") ); if ( !doc.setContent( &infoFile ) ) { qCDebug(OkularCoreDebug) << "Can't load XML pair! Check for broken xml."; infoFile.close(); return false; } infoFile.close(); QDomElement root = doc.documentElement(); if ( root.tagName() != QLatin1String("documentInfo") ) return false; QUrl documentUrl( root.attribute( "url" ) ); bool loadedAnything = false; // set if something gets actually loaded // Parse the DOM tree QDomNode topLevelNode = root.firstChild(); while ( topLevelNode.isElement() ) { QString catName = topLevelNode.toElement().tagName(); // Restore page attributes (bookmark, annotations, ...) from the DOM if ( catName == QLatin1String("pageList") && ( loadWhat & LoadPageInfo ) ) { QDomNode pageNode = topLevelNode.firstChild(); while ( pageNode.isElement() ) { QDomElement pageElement = pageNode.toElement(); if ( pageElement.hasAttribute( QStringLiteral("number") ) ) { // get page number (node's attribute) bool ok; int pageNumber = pageElement.attribute( QStringLiteral("number") ).toInt( &ok ); // pass the domElement to the right page, to read config data from if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() ) { if ( m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement ) ) loadedAnything = true; } } pageNode = pageNode.nextSibling(); } } // Restore 'general info' from the DOM else if ( catName == QLatin1String("generalInfo") && ( loadWhat & LoadGeneralInfo ) ) { QDomNode infoNode = topLevelNode.firstChild(); while ( infoNode.isElement() ) { QDomElement infoElement = infoNode.toElement(); // restore viewports history if ( infoElement.tagName() == QLatin1String("history") ) { // clear history m_viewportHistory.clear(); // append old viewports QDomNode historyNode = infoNode.firstChild(); while ( historyNode.isElement() ) { QDomElement historyElement = historyNode.toElement(); if ( historyElement.hasAttribute( QStringLiteral("viewport") ) ) { QString vpString = historyElement.attribute( QStringLiteral("viewport") ); m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport( vpString ) ); loadedAnything = true; } historyNode = historyNode.nextSibling(); } // consistancy check if ( m_viewportHistory.isEmpty() ) m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport() ); } else if ( infoElement.tagName() == QLatin1String("rotation") ) { QString str = infoElement.text(); bool ok = true; int newrotation = !str.isEmpty() ? ( str.toInt( &ok ) % 4 ) : 0; if ( ok && newrotation != 0 ) { setRotationInternal( newrotation, false ); loadedAnything = true; } } else if ( infoElement.tagName() == QLatin1String("views") ) { QDomNode viewNode = infoNode.firstChild(); while ( viewNode.isElement() ) { QDomElement viewElement = viewNode.toElement(); if ( viewElement.tagName() == QLatin1String("view") ) { const QString viewName = viewElement.attribute( QStringLiteral("name") ); Q_FOREACH ( View * view, m_views ) { if ( view->name() == viewName ) { loadViewsInfo( view, viewElement ); loadedAnything = true; break; } } } viewNode = viewNode.nextSibling(); } } infoNode = infoNode.nextSibling(); } } topLevelNode = topLevelNode.nextSibling(); } // return loadedAnything; } void DocumentPrivate::loadViewsInfo( View *view, const QDomElement &e ) { QDomNode viewNode = e.firstChild(); while ( viewNode.isElement() ) { QDomElement viewElement = viewNode.toElement(); if ( viewElement.tagName() == QLatin1String("zoom") ) { const QString valueString = viewElement.attribute( QStringLiteral("value") ); bool newzoom_ok = true; const double newzoom = !valueString.isEmpty() ? valueString.toDouble( &newzoom_ok ) : 1.0; if ( newzoom_ok && newzoom != 0 && view->supportsCapability( View::Zoom ) && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { view->setCapability( View::Zoom, newzoom ); } const QString modeString = viewElement.attribute( QStringLiteral("mode") ); bool newmode_ok = true; const int newmode = !modeString.isEmpty() ? modeString.toInt( &newmode_ok ) : 2; if ( newmode_ok && view->supportsCapability( View::ZoomModality ) && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { view->setCapability( View::ZoomModality, newmode ); } } viewNode = viewNode.nextSibling(); } } void DocumentPrivate::saveViewsInfo( View *view, QDomElement &e ) const { if ( view->supportsCapability( View::Zoom ) && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) && view->supportsCapability( View::ZoomModality ) && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { QDomElement zoomEl = e.ownerDocument().createElement( QStringLiteral("zoom") ); e.appendChild( zoomEl ); bool ok = true; const double zoom = view->capability( View::Zoom ).toDouble( &ok ); if ( ok && zoom != 0 ) { zoomEl.setAttribute( QStringLiteral("value"), QString::number(zoom) ); } const int mode = view->capability( View::ZoomModality ).toInt( &ok ); if ( ok ) { zoomEl.setAttribute( QStringLiteral("mode"), mode ); } } } QUrl DocumentPrivate::giveAbsoluteUrl( const QString & fileName ) const { if ( !QDir::isRelativePath( fileName ) ) return QUrl::fromLocalFile(fileName); if ( !m_url.isValid() ) return QUrl(); return QUrl(KIO::upUrl(m_url).toString() + fileName); } bool DocumentPrivate::openRelativeFile( const QString & fileName ) { QUrl url = giveAbsoluteUrl( fileName ); if ( url.isEmpty() ) return false; qCDebug(OkularCoreDebug).nospace() << "openRelativeFile: '" << url << "'"; emit m_parent->openUrl( url ); return true; } Generator * DocumentPrivate::loadGeneratorLibrary( const KPluginMetaData &service ) { KPluginLoader loader( service.fileName() ); qCDebug(OkularCoreDebug) << service.fileName(); KPluginFactory *factory = loader.factory(); if ( !factory ) { qCWarning(OkularCoreDebug).nospace() << "Invalid plugin factory for " << service.fileName() << ":" << loader.errorString(); return nullptr; } Generator * plugin = factory->create(); GeneratorInfo info( plugin, service ); m_loadedGenerators.insert( service.pluginId(), info ); return plugin; } void DocumentPrivate::loadAllGeneratorLibraries() { if ( m_generatorsLoaded ) return; loadServiceList( availableGenerators() ); m_generatorsLoaded = true; } void DocumentPrivate::loadServiceList( const QVector& offers ) { int count = offers.count(); if ( count <= 0 ) return; for ( int i = 0; i < count; ++i ) { QString id = offers.at(i).pluginId(); // don't load already loaded generators QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( id ); if ( !m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd() ) continue; Generator * g = loadGeneratorLibrary( offers.at(i) ); (void)g; } } void DocumentPrivate::unloadGenerator( const GeneratorInfo& info ) { delete info.generator; } void DocumentPrivate::cacheExportFormats() { if ( m_exportCached ) return; const ExportFormat::List formats = m_generator->exportFormats(); for ( int i = 0; i < formats.count(); ++i ) { if ( formats.at( i ).mimeType().name() == QLatin1String( "text/plain" ) ) m_exportToText = formats.at( i ); else m_exportFormats.append( formats.at( i ) ); } m_exportCached = true; } ConfigInterface* DocumentPrivate::generatorConfig( GeneratorInfo& info ) { if ( info.configChecked ) return info.config; info.config = qobject_cast< Okular::ConfigInterface * >( info.generator ); info.configChecked = true; return info.config; } SaveInterface* DocumentPrivate::generatorSave( GeneratorInfo& info ) { if ( info.saveChecked ) return info.save; info.save = qobject_cast< Okular::SaveInterface * >( info.generator ); info.saveChecked = true; return info.save; } Document::OpenResult DocumentPrivate::openDocumentInternal( const KPluginMetaData& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ) { QString propName = offer.pluginId(); QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName ); m_walletGenerator = nullptr; if ( genIt != m_loadedGenerators.constEnd() ) { m_generator = genIt.value().generator; } else { m_generator = loadGeneratorLibrary( offer ); if ( !m_generator ) return Document::OpenError; genIt = m_loadedGenerators.constFind( propName ); Q_ASSERT( genIt != m_loadedGenerators.constEnd() ); } Q_ASSERT_X( m_generator, "Document::load()", "null generator?!" ); m_generator->d_func()->m_document = this; // connect error reporting signals QObject::connect( m_generator, &Generator::error, m_parent, &Document::error ); QObject::connect( m_generator, &Generator::warning, m_parent, &Document::warning ); QObject::connect( m_generator, &Generator::notice, m_parent, &Document::notice ); QApplication::setOverrideCursor( Qt::WaitCursor ); const QSizeF dpi = Utils::realDpi(m_widget); qCDebug(OkularCoreDebug) << "Output DPI:" << dpi; m_generator->setDPI(dpi); Document::OpenResult openResult = Document::OpenError; if ( !isstdin ) { openResult = m_generator->loadDocumentWithPassword( docFile, m_pagesVector, password ); } else if ( !filedata.isEmpty() ) { if ( m_generator->hasFeature( Generator::ReadRawData ) ) { openResult = m_generator->loadDocumentFromDataWithPassword( filedata, m_pagesVector, password ); } else { m_tempFile = new QTemporaryFile(); if ( !m_tempFile->open() ) { delete m_tempFile; m_tempFile = nullptr; } else { m_tempFile->write( filedata ); QString tmpFileName = m_tempFile->fileName(); m_tempFile->close(); openResult = m_generator->loadDocumentWithPassword( tmpFileName, m_pagesVector, password ); } } } QApplication::restoreOverrideCursor(); if ( openResult != Document::OpenSuccess || m_pagesVector.size() <= 0 ) { m_generator->d_func()->m_document = nullptr; QObject::disconnect( m_generator, nullptr, m_parent, nullptr ); // TODO this is a bit of a hack, since basically means that // you can only call walletDataForFile after calling openDocument // but since in reality it's what happens I've decided not to refactor/break API // One solution is just kill walletDataForFile and make OpenResult be an object // where the wallet data is also returned when OpenNeedsPassword m_walletGenerator = m_generator; m_generator = nullptr; qDeleteAll( m_pagesVector ); m_pagesVector.clear(); delete m_tempFile; m_tempFile = nullptr; // TODO: emit a message telling the document is empty if ( openResult == Document::OpenSuccess ) openResult = Document::OpenError; } return openResult; } bool DocumentPrivate::savePageDocumentInfo( QTemporaryFile *infoFile, int what ) const { if ( infoFile->open() ) { // 1. Create DOM QDomDocument doc( QStringLiteral("documentInfo") ); QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"utf-8\"" ) ); doc.appendChild( xmlPi ); QDomElement root = doc.createElement( QStringLiteral("documentInfo") ); doc.appendChild( root ); // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM QDomElement pageList = doc.createElement( QStringLiteral("pageList") ); root.appendChild( pageList ); // .... save pages that hold data QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->saveLocalContents( pageList, doc, PageItems( what ) ); // 3. Save DOM to XML file QString xml = doc.toString(); QTextStream os( infoFile ); os.setCodec( "UTF-8" ); os << xml; return true; } return false; } DocumentViewport DocumentPrivate::nextDocumentViewport() const { DocumentViewport ret = m_nextDocumentViewport; if ( !m_nextDocumentDestination.isEmpty() && m_generator ) { DocumentViewport vp( m_parent->metaData( QStringLiteral("NamedViewport"), m_nextDocumentDestination ).toString() ); if ( vp.isValid() ) { ret = vp; } } return ret; } void DocumentPrivate::performAddPageAnnotation( int page, Annotation * annotation ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr; // find out the page to attach annotation Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; // the annotation belongs already to a page if ( annotation->d_ptr->m_page ) return; // add annotation to the page kp->addAnnotation( annotation ); // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Addition) ) proxy->notifyAddition( annotation, page ); // notify observers about the change notifyAnnotationChanges( page ); if ( annotation->flags() & Annotation::ExternallyDrawn ) { // Redraw everything, including ExternallyDrawn annotations refreshPixmaps( page ); } } void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annotation ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr; bool isExternallyDrawn; // find out the page Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; if ( annotation->flags() & Annotation::ExternallyDrawn ) isExternallyDrawn = true; else isExternallyDrawn = false; // try to remove the annotation if ( m_parent->canRemovePageAnnotation( annotation ) ) { // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Removal) ) proxy->notifyRemoval( annotation, page ); kp->removeAnnotation( annotation ); // Also destroys the object // in case of success, notify observers about the change notifyAnnotationChanges( page ); if ( isExternallyDrawn ) { // Redraw everything, including ExternallyDrawn annotations refreshPixmaps( page ); } } } void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr; // find out the page Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Modification) ) { proxy->notifyModification( annotation, page, appearanceChanged ); } // notify observers about the change notifyAnnotationChanges( page ); if ( appearanceChanged && (annotation->flags() & Annotation::ExternallyDrawn) ) { /* When an annotation is being moved, the generator will not render it. * Therefore there's no need to refresh pixmaps after the first time */ if ( annotation->flags() & (Annotation::BeingMoved | Annotation::BeingResized) ) { if ( m_annotationBeingModified ) return; else // First time: take note m_annotationBeingModified = true; } else { m_annotationBeingModified = false; } // Redraw everything, including ExternallyDrawn annotations qCDebug(OkularCoreDebug) << "Refreshing Pixmaps"; refreshPixmaps( page ); } } void DocumentPrivate::performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber ) { bool appearanceChanged = false; // Check if appearanceChanged should be true switch ( annot->subType() ) { // If it's an in-place TextAnnotation, set the inplace text case Okular::Annotation::AText: { Okular::TextAnnotation * txtann = static_cast< Okular::TextAnnotation * >( annot ); if ( txtann->textType() == Okular::TextAnnotation::InPlace ) { appearanceChanged = true; } break; } // If it's a LineAnnotation, check if caption text is visible case Okular::Annotation::ALine: { Okular::LineAnnotation * lineann = static_cast< Okular::LineAnnotation * >( annot ); if ( lineann->showCaption() ) appearanceChanged = true; break; } default: break; } // Set contents annot->setContents( newContents ); // Tell the document the annotation has been modified performModifyPageAnnotation( pageNumber, annot, appearanceChanged ); } void DocumentPrivate::recalculateForms() { const QVariant fco = m_parent->metaData(QLatin1String("FormCalculateOrder")); const QVector formCalculateOrder = fco.value>(); foreach(int formId, formCalculateOrder) { for ( uint pageIdx = 0; pageIdx < m_parent->pages(); pageIdx++ ) { const Page *p = m_parent->page( pageIdx ); if (p) { bool pageNeedsRefresh = false; foreach( FormField *form, p->formFields() ) { if ( form->id() == formId ) { Action *action = form->additionalAction( FormField::CalculateField ); if (action) { FormFieldText *fft = dynamic_cast< FormFieldText * >( form ); std::shared_ptr event; QString oldVal; if ( fft ) { // Pepare text calculate event event = Event::createFormCalculateEvent( fft, m_pagesVector[pageIdx] ); if ( !m_scripter ) m_scripter = new Scripter( this ); m_scripter->setEvent( event.get() ); // The value maybe changed in javascript so save it first. oldVal = fft->text(); } m_parent->processAction( action ); if ( event && fft ) { // Update text field from calculate m_scripter->setEvent( nullptr ); const QString newVal = event->value().toString(); if ( newVal != oldVal ) { fft->setText( newVal ); emit m_parent->refreshFormWidget( fft ); pageNeedsRefresh = true; } } } else { qWarning() << "Form that is part of calculate order doesn't have a calculate action"; } } } if ( pageNeedsRefresh ) { refreshPixmaps( p->number() ); } } } } } void DocumentPrivate::saveDocumentInfo() const { if ( m_xmlFileName.isEmpty() ) return; QFile infoFile( m_xmlFileName ); qCDebug(OkularCoreDebug) << "About to save document info to" << m_xmlFileName; if (!infoFile.open( QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(OkularCoreDebug) << "Failed to open docdata file" << m_xmlFileName; return; } // 1. Create DOM QDomDocument doc( QStringLiteral("documentInfo") ); QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"utf-8\"" ) ); doc.appendChild( xmlPi ); QDomElement root = doc.createElement( QStringLiteral("documentInfo") ); root.setAttribute( QStringLiteral("url"), m_url.toDisplayString(QUrl::PreferLocalFile) ); doc.appendChild( root ); // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM // -> do this if there are not-yet-migrated annots or forms in docdata/ if ( m_docdataMigrationNeeded ) { QDomElement pageList = doc.createElement( "pageList" ); root.appendChild( pageList ); // OriginalAnnotationPageItems and OriginalFormFieldPageItems tell to // store the same unmodified annotation list and form contents that we // read when we opened the file and ignore any change made by the user. // Since we don't store annotations and forms in docdata/ any more, this is // necessary to preserve annotations/forms that previous Okular version // had stored there. const PageItems saveWhat = AllPageItems | OriginalAnnotationPageItems | OriginalFormFieldPageItems; // .... save pages that hold data QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->saveLocalContents( pageList, doc, saveWhat ); } // 2.2. Save document info (current viewport, history, ... ) to DOM QDomElement generalInfo = doc.createElement( QStringLiteral("generalInfo") ); root.appendChild( generalInfo ); // create rotation node if ( m_rotation != Rotation0 ) { QDomElement rotationNode = doc.createElement( QStringLiteral("rotation") ); generalInfo.appendChild( rotationNode ); rotationNode.appendChild( doc.createTextNode( QString::number( (int)m_rotation ) ) ); } // ... save history up to OKULAR_HISTORY_SAVEDSTEPS viewports QLinkedList< DocumentViewport >::const_iterator backIterator = m_viewportIterator; if ( backIterator != m_viewportHistory.constEnd() ) { // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator int backSteps = OKULAR_HISTORY_SAVEDSTEPS; while ( backSteps-- && backIterator != m_viewportHistory.constBegin() ) --backIterator; // create history root node QDomElement historyNode = doc.createElement( QStringLiteral("history") ); generalInfo.appendChild( historyNode ); // add old[backIterator] and present[viewportIterator] items QLinkedList< DocumentViewport >::const_iterator endIt = m_viewportIterator; ++endIt; while ( backIterator != endIt ) { QString name = (backIterator == m_viewportIterator) ? QStringLiteral ("current") : QStringLiteral ("oldPage"); QDomElement historyEntry = doc.createElement( name ); historyEntry.setAttribute( QStringLiteral("viewport"), (*backIterator).toString() ); historyNode.appendChild( historyEntry ); ++backIterator; } } // create views root node QDomElement viewsNode = doc.createElement( QStringLiteral("views") ); generalInfo.appendChild( viewsNode ); Q_FOREACH ( View * view, m_views ) { QDomElement viewEntry = doc.createElement( QStringLiteral("view") ); viewEntry.setAttribute( QStringLiteral("name"), view->name() ); viewsNode.appendChild( viewEntry ); saveViewsInfo( view, viewEntry ); } // 3. Save DOM to XML file QString xml = doc.toString(); QTextStream os( &infoFile ); os.setCodec( "UTF-8" ); os << xml; infoFile.close(); } void DocumentPrivate::slotTimedMemoryCheck() { // [MEM] clean memory (for 'free mem dependant' profiles only) if ( SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024*1024 ) cleanupPixmapMemory(); } void DocumentPrivate::sendGeneratorPixmapRequest() { /* If the pixmap cache will have to be cleaned in order to make room for the * next request, get the distance from the current viewport of the page * whose pixmap will be removed. We will ignore preload requests for pages * that are at the same distance or farther */ const qulonglong memoryToFree = calculateMemoryToFree(); const int currentViewportPage = (*m_viewportIterator).pageNumber; int maxDistance = INT_MAX; // Default: No maximum if ( memoryToFree ) { AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap( true ); if ( pixmapToReplace ) maxDistance = qAbs( pixmapToReplace->page - currentViewportPage ); } // find a request PixmapRequest * request = nullptr; m_pixmapRequestsMutex.lock(); while ( !m_pixmapRequestsStack.isEmpty() && !request ) { PixmapRequest * r = m_pixmapRequestsStack.last(); if (!r) { m_pixmapRequestsStack.pop_back(); continue; } QRect requestRect = r->isTile() ? r->normalizedRect().geometry( r->width(), r->height() ) : QRect( 0, 0, r->width(), r->height() ); TilesManager *tilesManager = r->d->tilesManager(); // If it's a preload but the generator is not threaded no point in trying to preload if ( r->preload() && !m_generator->hasFeature( Generator::Threaded ) ) { m_pixmapRequestsStack.pop_back(); delete r; } // request only if page isn't already present and request has valid id else if ( ( !r->d->mForce && r->page()->hasPixmap( r->observer(), r->width(), r->height(), r->normalizedRect() ) ) || !m_observers.contains(r->observer()) ) { m_pixmapRequestsStack.pop_back(); delete r; } else if ( !r->d->mForce && r->preload() && qAbs( r->pageNumber() - currentViewportPage ) >= maxDistance ) { m_pixmapRequestsStack.pop_back(); //qCDebug(OkularCoreDebug) << "Ignoring request that doesn't fit in cache"; delete r; } // Ignore requests for pixmaps that are already being generated else if ( tilesManager && tilesManager->isRequesting( r->normalizedRect(), r->width(), r->height() ) ) { m_pixmapRequestsStack.pop_back(); delete r; } // If the requested area is above 8000000 pixels, switch on the tile manager else if ( !tilesManager && m_generator->hasFeature( Generator::TiledRendering ) && (long)r->width() * (long)r->height() > 8000000L ) { // if the image is too big. start using tiles qCDebug(OkularCoreDebug).nospace() << "Start using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; // fill the tiles manager with the last rendered pixmap const QPixmap *pixmap = r->page()->_o_nearestPixmap( r->observer(), r->width(), r->height() ); if ( pixmap ) { tilesManager = new TilesManager( r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation() ); tilesManager->setPixmap( pixmap, NormalizedRect( 0, 0, 1, 1 ), true /*isPartialPixmap*/ ); tilesManager->setSize( r->width(), r->height() ); } else { // create new tiles manager tilesManager = new TilesManager( r->pageNumber(), r->width(), r->height(), r->page()->rotation() ); } tilesManager->setRequest( r->normalizedRect(), r->width(), r->height() ); r->page()->deletePixmap( r->observer() ); r->page()->d->setTilesManager( r->observer(), tilesManager ); r->setTile( true ); // Change normalizedRect to the smallest rect that contains all // visible tiles. if ( !r->normalizedRect().isNull() ) { NormalizedRect tilesRect; const QList tiles = tilesManager->tilesAt( r->normalizedRect(), TilesManager::TerminalTile ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { Tile tile = *tIt; if ( tilesRect.isNull() ) tilesRect = tile.rect(); else tilesRect |= tile.rect(); ++tIt; } r->setNormalizedRect( tilesRect ); request = r; } else { // Discard request if normalizedRect is null. This happens in // preload requests issued by PageView if the requested page is // not visible and the user has just switched from a non-tiled // zoom level to a tiled one m_pixmapRequestsStack.pop_back(); delete r; } } // If the requested area is below 6000000 pixels, switch off the tile manager else if ( tilesManager && (long)r->width() * (long)r->height() < 6000000L ) { qCDebug(OkularCoreDebug).nospace() << "Stop using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; // page is too small. stop using tiles. r->page()->deletePixmap( r->observer() ); r->setTile( false ); request = r; } else if ( (long)requestRect.width() * (long)requestRect.height() > 200000000L && (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Greedy ) ) { m_pixmapRequestsStack.pop_back(); if ( !m_warnedOutOfMemory ) { qCWarning(OkularCoreDebug).nospace() << "Running out of memory on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; qCWarning(OkularCoreDebug) << "this message will be reported only once."; m_warnedOutOfMemory = true; } delete r; } else { request = r; } } // if no request found (or already generated), return if ( !request ) { m_pixmapRequestsMutex.unlock(); return; } // [MEM] preventive memory freeing qulonglong pixmapBytes = 0; TilesManager * tm = request->d->tilesManager(); if ( tm ) pixmapBytes = tm->totalMemory(); else pixmapBytes = 4 * request->width() * request->height(); if ( pixmapBytes > (1024 * 1024) ) cleanupPixmapMemory( memoryToFree /* previously calculated value */ ); // submit the request to the generator if ( m_generator->canGeneratePixmap() ) { QRect requestRect = !request->isTile() ? QRect(0, 0, request->width(), request->height() ) : request->normalizedRect().geometry( request->width(), request->height() ); qCDebug(OkularCoreDebug).nospace() << "sending request observer=" << request->observer() << " " <pageNumber() << " async == " << request->asynchronous() << " isTile == " << request->isTile(); m_pixmapRequestsStack.removeAll ( request ); if ( tm ) tm->setRequest( request->normalizedRect(), request->width(), request->height() ); if ( (int)m_rotation % 2 ) request->d->swap(); if ( m_rotation != Rotation0 && !request->normalizedRect().isNull() ) request->setNormalizedRect( TilesManager::fromRotatedRect( request->normalizedRect(), m_rotation ) ); // If set elsewhere we already know we want it to be partial if ( !request->partialUpdatesWanted() ) { request->setPartialUpdatesWanted( request->asynchronous() && !request->page()->hasPixmap( request->observer() ) ); } // we always have to unlock _before_ the generatePixmap() because // a sync generation would end with requestDone() -> deadlock, and // we can not really know if the generator can do async requests m_executingPixmapRequests.push_back( request ); m_pixmapRequestsMutex.unlock(); m_generator->generatePixmap( request ); } else { m_pixmapRequestsMutex.unlock(); // pino (7/4/2006): set the polling interval from 10 to 30 QTimer::singleShot( 30, m_parent, SLOT(sendGeneratorPixmapRequest()) ); } } void DocumentPrivate::rotationFinished( int page, Okular::Page *okularPage ) { Okular::Page *wantedPage = m_pagesVector.value( page, 0 ); if ( !wantedPage || wantedPage != okularPage ) return; foreach(DocumentObserver *o, m_observers) o->notifyPageChanged( page, DocumentObserver::Pixmap | DocumentObserver::Annotations ); } void DocumentPrivate::slotFontReadingProgress( int page ) { emit m_parent->fontReadingProgress( page ); if ( page >= (int)m_parent->pages() - 1 ) { emit m_parent->fontReadingEnded(); m_fontThread = nullptr; m_fontsCached = true; } } void DocumentPrivate::fontReadingGotFont( const Okular::FontInfo& font ) { // Try to avoid duplicate fonts if (m_fontsCache.indexOf(font) == -1) { m_fontsCache.append( font ); emit m_parent->gotFont( font ); } } void DocumentPrivate::slotGeneratorConfigChanged( const QString& ) { if ( !m_generator ) return; // reparse generator config and if something changed clear Pages bool configchanged = false; QHash< QString, GeneratorInfo >::iterator it = m_loadedGenerators.begin(), itEnd = m_loadedGenerators.end(); for ( ; it != itEnd; ++it ) { Okular::ConfigInterface * iface = generatorConfig( it.value() ); if ( iface ) { bool it_changed = iface->reparseConfig(); if ( it_changed && ( m_generator == it.value().generator ) ) configchanged = true; } } if ( configchanged ) { // invalidate pixmaps QVector::const_iterator it = m_pagesVector.constBegin(), end = m_pagesVector.constEnd(); for ( ; it != end; ++it ) { (*it)->deletePixmaps(); } // [MEM] remove allocation descriptors qDeleteAll( m_allocatedPixmaps ); m_allocatedPixmaps.clear(); m_allocatedPixmapsTotalMemory = 0; // send reload signals to observers foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap ) ); } // free memory if in 'low' profile if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.isEmpty() && !m_pagesVector.isEmpty() ) cleanupPixmapMemory(); } void DocumentPrivate::refreshPixmaps( int pageNumber ) { Page* page = m_pagesVector.value( pageNumber, 0 ); if ( !page ) return; QMap< DocumentObserver*, PagePrivate::PixmapObject >::ConstIterator it = page->d->m_pixmaps.constBegin(), itEnd = page->d->m_pixmaps.constEnd(); QVector< Okular::PixmapRequest * > pixmapsToRequest; for ( ; it != itEnd; ++it ) { const QSize size = (*it).m_pixmap->size(); PixmapRequest * p = new PixmapRequest( it.key(), pageNumber, size.width() / qApp->devicePixelRatio(), size.height() / qApp->devicePixelRatio(), 1, PixmapRequest::Asynchronous ); p->d->mForce = true; pixmapsToRequest << p; } // Need to do this ↑↓ in two steps since requestPixmaps can end up calling cancelRenderingBecauseOf // which changes m_pixmaps and thus breaks the loop above for ( PixmapRequest *pr : qAsConst( pixmapsToRequest ) ) { QLinkedList< Okular::PixmapRequest * > requestedPixmaps; requestedPixmaps.push_back( pr ); m_parent->requestPixmaps( requestedPixmaps, Okular::Document::NoOption ); } foreach (DocumentObserver *observer, m_observers) { QLinkedList< Okular::PixmapRequest * > requestedPixmaps; TilesManager *tilesManager = page->d->tilesManager( observer ); if ( tilesManager ) { tilesManager->markDirty(); PixmapRequest * p = new PixmapRequest( observer, pageNumber, tilesManager->width() / qApp->devicePixelRatio(), tilesManager->height() / qApp->devicePixelRatio(), 1, PixmapRequest::Asynchronous ); NormalizedRect tilesRect; // Get the visible page rect NormalizedRect visibleRect; QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) { if ( (*vIt)->pageNumber == pageNumber ) { visibleRect = (*vIt)->rect; break; } } if ( !visibleRect.isNull() ) { p->setNormalizedRect( visibleRect ); p->setTile( true ); p->d->mForce = true; requestedPixmaps.push_back( p ); } else { delete p; } } m_parent->requestPixmaps( requestedPixmaps, Okular::Document::NoOption ); } } void DocumentPrivate::_o_configChanged() { // free text pages if needed calculateMaxTextPages(); while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) { int pageToKick = m_allocatedTextPagesFifo.takeFirst(); m_pagesVector.at(pageToKick)->setTextPage( nullptr ); // deletes the textpage } } void DocumentPrivate::doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) { DoContinueDirectionMatchSearchStruct *searchStruct = static_cast(doContinueDirectionMatchSearchStruct); RunningSearch *search = m_searches.value(searchStruct->searchID); if ((m_searchCancelled && !searchStruct->match) || !search) { // if the user cancelled but he just got a match, give him the match! QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchStruct->searchID, Document::SearchCancelled ); delete searchStruct->pagesToNotify; delete searchStruct; return; } const bool forward = search->cachedType == Document::NextMatch; bool doContinue = false; // if no match found, loop through the whole doc, starting from currentPage if ( !searchStruct->match ) { const int pageCount = m_pagesVector.count(); if (search->pagesDone < pageCount) { doContinue = true; if ( searchStruct->currentPage >= pageCount ) { searchStruct->currentPage = 0; emit m_parent->notice(i18n("Continuing search from beginning"), 3000); } else if ( searchStruct->currentPage < 0 ) { searchStruct->currentPage = pageCount - 1; emit m_parent->notice(i18n("Continuing search from bottom"), 3000); } } } if (doContinue) { // get page Page * page = m_pagesVector[ searchStruct->currentPage ]; // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( page->number() ); // if found a match on the current page, end the loop searchStruct->match = page->findText( searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity ); if ( !searchStruct->match ) { if (forward) searchStruct->currentPage++; else searchStruct->currentPage--; search->pagesDone++; } else { search->pagesDone = 1; } // Both of the previous if branches need to call doContinueDirectionMatchSearch QMetaObject::invokeMethod(m_parent, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); } else { doProcessSearchMatch( searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor ); delete searchStruct; } } void DocumentPrivate::doProcessSearchMatch( RegularAreaRect *match, RunningSearch *search, QSet< int > *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor & color ) { // reset cursor to previous shape QApplication::restoreOverrideCursor(); bool foundAMatch = false; search->isCurrentlySearching = false; // if a match has been found.. if ( match ) { // update the RunningSearch structure adding this match.. foundAMatch = true; search->continueOnPage = currentPage; search->continueOnMatch = *match; search->highlightedPages.insert( currentPage ); // ..add highlight to the page.. m_pagesVector[ currentPage ]->d->setHighlight( searchID, match, color ); // ..queue page for notifying changes.. pagesToNotify->insert( currentPage ); // Create a normalized rectangle around the search match that includes a 5% buffer on all sides. const Okular::NormalizedRect matchRectWithBuffer = Okular::NormalizedRect( match->first().left - 0.05, match->first().top - 0.05, match->first().right + 0.05, match->first().bottom + 0.05 ); const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible( matchRectWithBuffer, currentPage ); // ..move the viewport to show the first of the searched word sequence centered if ( moveViewport && !matchRectFullyVisible ) { DocumentViewport searchViewport( currentPage ); searchViewport.rePos.enabled = true; searchViewport.rePos.normalizedX = (match->first().left + match->first().right) / 2.0; searchViewport.rePos.normalizedY = (match->first().top + match->first().bottom) / 2.0; m_parent->setViewport( searchViewport, nullptr, true ); } delete match; } // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pagesToNotify; } void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) { QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); RunningSearch *search = m_searches.value(searchID); if (m_searchCancelled || !search) { typedef QVector MatchesVector; QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchID, Document::SearchCancelled ); foreach(const MatchesVector &mv, *pageMatches) qDeleteAll(mv); delete pageMatches; delete pagesToNotify; return; } if (currentPage < m_pagesVector.count()) { // get page (from the first to the last) Page *page = m_pagesVector.at(currentPage); int pageNumber = page->number(); // redundant? is it == currentPage ? // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( pageNumber ); // loop on a page adding highlights for all found items RegularAreaRect * lastMatch = nullptr; while ( 1 ) { if ( lastMatch ) lastMatch = page->findText( searchID, search->cachedString, NextResult, search->cachedCaseSensitivity, lastMatch ); else lastMatch = page->findText( searchID, search->cachedString, FromTop, search->cachedCaseSensitivity ); if ( !lastMatch ) break; // add highligh rect to the matches map (*pageMatches)[page].append(lastMatch); } delete lastMatch; QMetaObject::invokeMethod(m_parent, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotifySet), Q_ARG(void *, pageMatches), Q_ARG(int, currentPage + 1), Q_ARG(int, searchID)); } else { // reset cursor to previous shape QApplication::restoreOverrideCursor(); search->isCurrentlySearching = false; bool foundAMatch = pageMatches->count() != 0; QMap< Page *, QVector >::const_iterator it, itEnd; it = pageMatches->constBegin(); itEnd = pageMatches->constEnd(); for ( ; it != itEnd; ++it) { foreach(RegularAreaRect *match, it.value()) { it.key()->d->setHighlight( searchID, match, search->cachedColor ); delete match; } search->highlightedPages.insert( it.key()->number() ); pagesToNotify->insert( it.key()->number() ); } foreach(DocumentObserver *observer, m_observers) observer->notifySetup( m_pagesVector, 0 ); // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished(searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pageMatches; delete pagesToNotify; } } void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) { typedef QPair MatchColor; QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); RunningSearch *search = m_searches.value(searchID); if (m_searchCancelled || !search) { typedef QVector MatchesVector; QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchID, Document::SearchCancelled ); foreach(const MatchesVector &mv, *pageMatches) { foreach(const MatchColor &mc, mv) delete mc.first; } delete pageMatches; delete pagesToNotify; return; } const int wordCount = words.count(); const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60; int baseHue, baseSat, baseVal; search->cachedColor.getHsv( &baseHue, &baseSat, &baseVal ); if (currentPage < m_pagesVector.count()) { // get page (from the first to the last) Page *page = m_pagesVector.at(currentPage); int pageNumber = page->number(); // redundant? is it == currentPage ? // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( pageNumber ); // loop on a page adding highlights for all found items bool allMatched = wordCount > 0, anyMatched = false; for ( int w = 0; w < wordCount; w++ ) { const QString &word = words[ w ]; int newHue = baseHue - w * hueStep; if ( newHue < 0 ) newHue += 360; QColor wordColor = QColor::fromHsv( newHue, baseSat, baseVal ); RegularAreaRect * lastMatch = nullptr; // add all highlights for current word bool wordMatched = false; while ( 1 ) { if ( lastMatch ) lastMatch = page->findText( searchID, word, NextResult, search->cachedCaseSensitivity, lastMatch ); else lastMatch = page->findText( searchID, word, FromTop, search->cachedCaseSensitivity); if ( !lastMatch ) break; // add highligh rect to the matches map (*pageMatches)[page].append(MatchColor(lastMatch, wordColor)); wordMatched = true; } allMatched = allMatched && wordMatched; anyMatched = anyMatched || wordMatched; } // if not all words are present in page, remove partial highlights const bool matchAll = search->cachedType == Document::GoogleAll; if ( !allMatched && matchAll ) { QVector &matches = (*pageMatches)[page]; foreach(const MatchColor &mc, matches) delete mc.first; pageMatches->remove(page); } QMetaObject::invokeMethod(m_parent, "doContinueGooglesDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotifySet), Q_ARG(void *, pageMatches), Q_ARG(int, currentPage + 1), Q_ARG(int, searchID), Q_ARG(QStringList, words)); } else { // reset cursor to previous shape QApplication::restoreOverrideCursor(); search->isCurrentlySearching = false; bool foundAMatch = pageMatches->count() != 0; QMap< Page *, QVector >::const_iterator it, itEnd; it = pageMatches->constBegin(); itEnd = pageMatches->constEnd(); for ( ; it != itEnd; ++it) { foreach(const MatchColor &mc, it.value()) { it.key()->d->setHighlight( searchID, mc.first, mc.second ); delete mc.first; } search->highlightedPages.insert( it.key()->number() ); pagesToNotify->insert( it.key()->number() ); } // send page lists to update observers (since some filter on bookmarks) foreach(DocumentObserver *observer, m_observers) observer->notifySetup( m_pagesVector, 0 ); // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pageMatches; delete pagesToNotify; } } QVariant DocumentPrivate::documentMetaData( const Generator::DocumentMetaDataKey key, const QVariant &option ) const { switch ( key ) { case Generator::PaperColorMetaData: { bool giveDefault = option.toBool(); QColor color; if ( ( SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper ) && SettingsCore::changeColors() ) { color = SettingsCore::paperColor(); } else if ( giveDefault ) { color = Qt::white; } return color; } break; case Generator::TextAntialiasMetaData: switch ( SettingsCore::textAntialias() ) { case SettingsCore::EnumTextAntialias::Enabled: return true; break; #if 0 case Settings::EnumTextAntialias::UseKDESettings: // TODO: read the KDE configuration return true; break; #endif case SettingsCore::EnumTextAntialias::Disabled: return false; break; } break; case Generator::GraphicsAntialiasMetaData: switch ( SettingsCore::graphicsAntialias() ) { case SettingsCore::EnumGraphicsAntialias::Enabled: return true; break; case SettingsCore::EnumGraphicsAntialias::Disabled: return false; break; } break; case Generator::TextHintingMetaData: switch ( SettingsCore::textHinting() ) { case SettingsCore::EnumTextHinting::Enabled: return true; break; case SettingsCore::EnumTextHinting::Disabled: return false; break; } break; } return QVariant(); } bool DocumentPrivate::isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage ) { bool rectFullyVisible = false; const QVector & visibleRects = m_parent->visiblePageRects(); QVector::const_iterator vEnd = visibleRects.end(); QVector::const_iterator vIt = visibleRects.begin(); for ( ; ( vIt != vEnd ) && !rectFullyVisible; ++vIt ) { if ( (*vIt)->pageNumber == rectPage && (*vIt)->rect.contains( rectOfInterest.left, rectOfInterest.top ) && (*vIt)->rect.contains( rectOfInterest.right, rectOfInterest.bottom ) ) { rectFullyVisible = true; } } return rectFullyVisible; } struct pdfsyncpoint { QString file; qlonglong x; qlonglong y; int row; int column; int page; }; void DocumentPrivate::loadSyncFile( const QString & filePath ) { QFile f( filePath + QLatin1String( "sync" ) ); if ( !f.open( QIODevice::ReadOnly ) ) return; QTextStream ts( &f ); // first row: core name of the pdf output const QString coreName = ts.readLine(); // second row: version string, in the form 'Version %u' QString versionstr = ts.readLine(); QRegExp versionre( QStringLiteral("Version (\\d+)") ); versionre.setCaseSensitivity( Qt::CaseInsensitive ); if ( !versionre.exactMatch( versionstr ) ) return; QHash points; QStack fileStack; int currentpage = -1; const QLatin1String texStr( ".tex" ); const QChar spaceChar = QChar::fromLatin1( ' ' ); fileStack.push( coreName + texStr ); const QSizeF dpi = m_generator->dpi(); QString line; while ( !ts.atEnd() ) { line = ts.readLine(); const QStringList tokens = line.split( spaceChar, QString::SkipEmptyParts ); const int tokenSize = tokens.count(); if ( tokenSize < 1 ) continue; if ( tokens.first() == QLatin1String( "l" ) && tokenSize >= 3 ) { int id = tokens.at( 1 ).toInt(); QHash::const_iterator it = points.constFind( id ); if ( it == points.constEnd() ) { pdfsyncpoint pt; pt.x = 0; pt.y = 0; pt.row = tokens.at( 2 ).toInt(); pt.column = 0; // TODO pt.page = -1; pt.file = fileStack.top(); points[ id ] = pt; } } else if ( tokens.first() == QLatin1String( "s" ) && tokenSize >= 2 ) { currentpage = tokens.at( 1 ).toInt() - 1; } else if ( tokens.first() == QLatin1String( "p*" ) && tokenSize >= 4 ) { // TODO qCDebug(OkularCoreDebug) << "PdfSync: 'p*' line ignored"; } else if ( tokens.first() == QLatin1String( "p" ) && tokenSize >= 4 ) { int id = tokens.at( 1 ).toInt(); QHash::iterator it = points.find( id ); if ( it != points.end() ) { it->x = tokens.at( 2 ).toInt(); it->y = tokens.at( 3 ).toInt(); it->page = currentpage; } } else if ( line.startsWith( QLatin1Char( '(' ) ) && tokenSize == 1 ) { QString newfile = line; // chop the leading '(' newfile.remove( 0, 1 ); if ( !newfile.endsWith( texStr ) ) { newfile += texStr; } fileStack.push( newfile ); } else if ( line == QLatin1String( ")" ) ) { if ( !fileStack.isEmpty() ) { fileStack.pop(); } else qCDebug(OkularCoreDebug) << "PdfSync: going one level down too much"; } else qCDebug(OkularCoreDebug).nospace() << "PdfSync: unknown line format: '" << line << "'"; } QVector< QLinkedList< Okular::SourceRefObjectRect * > > refRects( m_pagesVector.size() ); foreach ( const pdfsyncpoint& pt, points ) { // drop pdfsync points not completely valid if ( pt.page < 0 || pt.page >= m_pagesVector.size() ) continue; // magic numbers for TeX's RSU's (Ridiculously Small Units) conversion to pixels Okular::NormalizedPoint p( ( pt.x * dpi.width() ) / ( 72.27 * 65536.0 * m_pagesVector[pt.page]->width() ), ( pt.y * dpi.height() ) / ( 72.27 * 65536.0 * m_pagesVector[pt.page]->height() ) ); QString file = pt.file; Okular::SourceReference * sourceRef = new Okular::SourceReference( file, pt.row, pt.column ); refRects[ pt.page ].append( new Okular::SourceRefObjectRect( p, sourceRef ) ); } for ( int i = 0; i < refRects.size(); ++i ) if ( !refRects.at(i).isEmpty() ) m_pagesVector[i]->setSourceReferences( refRects.at(i) ); } void DocumentPrivate::clearAndWaitForRequests() { m_pixmapRequestsMutex.lock(); QLinkedList< PixmapRequest * >::const_iterator sIt = m_pixmapRequestsStack.constBegin(); QLinkedList< PixmapRequest * >::const_iterator sEnd = m_pixmapRequestsStack.constEnd(); for ( ; sIt != sEnd; ++sIt ) delete *sIt; m_pixmapRequestsStack.clear(); m_pixmapRequestsMutex.unlock(); QEventLoop loop; bool startEventLoop = false; do { m_pixmapRequestsMutex.lock(); startEventLoop = !m_executingPixmapRequests.isEmpty(); if ( m_generator->hasFeature( Generator::SupportsCancelling ) ) { for ( PixmapRequest *executingRequest : qAsConst( m_executingPixmapRequests ) ) executingRequest->d->mShouldAbortRender = 1; if ( m_generator->d_ptr->mTextPageGenerationThread ) m_generator->d_ptr->mTextPageGenerationThread->abortExtraction(); } m_pixmapRequestsMutex.unlock(); if ( startEventLoop ) { m_closingLoop = &loop; loop.exec(); m_closingLoop = nullptr; } } while ( startEventLoop ); } Document::Document( QWidget *widget ) : QObject( nullptr ), d( new DocumentPrivate( this ) ) { d->m_widget = widget; d->m_bookmarkManager = new BookmarkManager( d ); d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), DocumentViewport() ); d->m_undoStack = new QUndoStack(this); connect( SettingsCore::self(), SIGNAL(configChanged()), this, SLOT(_o_configChanged()) ); connect(d->m_undoStack, &QUndoStack::canUndoChanged, this, &Document::canUndoChanged); connect(d->m_undoStack, &QUndoStack::canRedoChanged, this, &Document::canRedoChanged); connect(d->m_undoStack, &QUndoStack::cleanChanged, this, &Document::undoHistoryCleanChanged); qRegisterMetaType(); } Document::~Document() { // delete generator, pages, and related stuff closeDocument(); QSet< View * >::const_iterator viewIt = d->m_views.constBegin(), viewEnd = d->m_views.constEnd(); for ( ; viewIt != viewEnd; ++viewIt ) { View *v = *viewIt; v->d_func()->document = nullptr; } // delete the bookmark manager delete d->m_bookmarkManager; // delete the loaded generators QHash< QString, GeneratorInfo >::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd(); for ( ; it != itEnd; ++it ) d->unloadGenerator( it.value() ); d->m_loadedGenerators.clear(); // delete the private structure delete d; } QString DocumentPrivate::docDataFileName(const QUrl &url, qint64 document_size) { QString fn = url.fileName(); fn = QString::number( document_size ) + QLatin1Char('.') + fn + QStringLiteral(".xml"); QString docdataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/okular/docdata"); // make sure that the okular/docdata/ directory exists (probably this used to be handled by KStandardDirs) if (!QFileInfo::exists(docdataDir)) { qCDebug(OkularCoreDebug) << "creating docdata folder" << docdataDir; QDir().mkpath(docdataDir); } QString newokularfile = docdataDir + QLatin1Char('/') + fn; // we don't want to accidentally migrate old files when running unit tests if (!QFile::exists( newokularfile ) && !QStandardPaths::isTestModeEnabled()) { // see if an KDE4 file still exists static Kdelibs4Migration k4migration; QString oldfile = k4migration.locateLocal("data", QStringLiteral("okular/docdata/") + fn); if (oldfile.isEmpty()) { oldfile = k4migration.locateLocal("data", QStringLiteral("kpdf/") + fn); } if ( !oldfile.isEmpty() && QFile::exists( oldfile ) ) { // ### copy or move? if ( !QFile::copy( oldfile, newokularfile ) ) return QString(); } } return newokularfile; } QVector DocumentPrivate::availableGenerators() { static QVector result; if (result.isEmpty()) { result = KPluginLoader::findPlugins( QLatin1String ( "okular/generators" ) ); } return result; } KPluginMetaData DocumentPrivate::generatorForMimeType(const QMimeType& type, QWidget* widget, const QVector &triedOffers) { // First try to find an exact match, and then look for more general ones (e. g. the plain text one) // Ideally we would rank these by "closeness", but that might be overdoing it const QVector available = availableGenerators(); QVector offers; QVector exactMatches; QMimeDatabase mimeDatabase; for (const KPluginMetaData& md : available) { if (triedOffers.contains(md)) continue; foreach (const QString& supported, md.mimeTypes()) { QMimeType mimeType = mimeDatabase.mimeTypeForName(supported); if (mimeType == type && !exactMatches.contains(md)) { exactMatches << md; } if (type.inherits(supported) && !offers.contains(md)) { offers << md; } } } if (!exactMatches.isEmpty()) { offers = exactMatches; } if (offers.isEmpty()) { return KPluginMetaData(); } int hRank=0; // best ranked offer search int offercount = offers.size(); if (offercount > 1) { // sort the offers: the offers with an higher priority come before auto cmp = [](const KPluginMetaData& s1, const KPluginMetaData& s2) { const QString property = QStringLiteral("X-KDE-Priority"); return s1.rawData()[property].toInt() > s2.rawData()[property].toInt(); }; std::stable_sort(offers.begin(), offers.end(), cmp); if (SettingsCore::chooseGenerators()) { QStringList list; for (int i = 0; i < offercount; ++i) { list << offers.at(i).pluginId(); } ChooseEngineDialog choose(list, type, widget); if (choose.exec() == QDialog::Rejected) return KPluginMetaData(); hRank = choose.selectedGenerator(); } } Q_ASSERT(hRank < offers.size()); return offers.at(hRank); } Document::OpenResult Document::openDocument(const QString & docFile, const QUrl &url, const QMimeType &_mime, const QString & password ) { QMimeDatabase db; QMimeType mime = _mime; QByteArray filedata; int fd = -1; if (url.scheme() == QLatin1String("fd")) { bool ok; fd = url.path().mid(1).toInt(&ok); if (!ok) { return OpenError; } } else if (url.fileName() == QLatin1String( "-" )) { fd = 0; } bool triedMimeFromFileContent = false; if ( fd < 0 ) { if ( !mime.isValid() ) return OpenError; d->m_url = url; d->m_docFileName = docFile; if ( !d->updateMetadataXmlNameAndDocSize() ) return OpenError; } else { QFile qstdin; const bool ret = qstdin.open( fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle ); if (!ret) { qWarning() << "failed to read" << url << filedata; return OpenError; } filedata = qstdin.readAll(); mime = db.mimeTypeForData( filedata ); if ( !mime.isValid() || mime.isDefault() ) return OpenError; d->m_docSize = filedata.size(); triedMimeFromFileContent = true; } const bool fromFileDescriptor = fd >= 0; // 0. load Generator // request only valid non-disabled plugins suitable for the mimetype KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget); if ( !offer.isValid() && !triedMimeFromFileContent ) { QMimeType newmime = db.mimeTypeForFile(docFile, QMimeDatabase::MatchExtension); triedMimeFromFileContent = true; if ( newmime != mime ) { mime = newmime; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget); } if ( !offer.isValid() ) { // There's still no offers, do a final mime search based on the filename // We need this because sometimes (e.g. when downloading from a webserver) the mimetype we // use is the one fed by the server, that may be wrong newmime = db.mimeTypeForUrl( url ); if ( !newmime.isDefault() && newmime != mime ) { mime = newmime; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget); } } } if (!offer.isValid()) { emit error( i18n( "Can not find a plugin which is able to handle the document being passed." ), -1 ); qCWarning(OkularCoreDebug).nospace() << "No plugin for mimetype '" << mime.name() << "'."; return OpenError; } // 1. load Document OpenResult openResult = d->openDocumentInternal( offer, fromFileDescriptor, docFile, filedata, password ); if ( openResult == OpenError ) { QVector triedOffers; triedOffers << offer; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); while ( offer.isValid() ) { openResult = d->openDocumentInternal( offer, fromFileDescriptor, docFile, filedata, password ); if ( openResult == OpenError ) { triedOffers << offer; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); } else break; } if (openResult == OpenError && !triedMimeFromFileContent ) { QMimeType newmime = db.mimeTypeForFile(docFile, QMimeDatabase::MatchExtension); triedMimeFromFileContent = true; if ( newmime != mime ) { mime = newmime; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); while ( offer.isValid() ) { openResult = d->openDocumentInternal( offer, fromFileDescriptor, docFile, filedata, password ); if ( openResult == OpenError ) { triedOffers << offer; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); } else break; } } } if ( openResult == OpenSuccess ) { // Clear errors, since we're trying various generators, maybe one of them errored out // but we finally succeeded // TODO one can still see the error message animating out but since this is a very rare // condition we can leave this for future work emit error( QString(), -1 ); } } if ( openResult != OpenSuccess ) { return openResult; } // no need to check for the existence of a synctex file, no parser will be // created if none exists d->m_synctex_scanner = synctex_scanner_new_with_output_file( QFile::encodeName( docFile ).constData(), nullptr, 1); if ( !d->m_synctex_scanner && QFile::exists(docFile + QLatin1String( "sync" ) ) ) { d->loadSyncFile(docFile); } d->m_generatorName = offer.pluginId(); d->m_pageController = new PageController(); connect( d->m_pageController, SIGNAL(rotationFinished(int,Okular::Page*)), this, SLOT(rotationFinished(int,Okular::Page*)) ); foreach ( Page * p, d->m_pagesVector ) p->d->m_doc = d; d->m_metadataLoadingCompleted = false; d->m_docdataMigrationNeeded = false; // 2. load Additional Data (bookmarks, local annotations and metadata) about the document if ( d->m_archiveData ) { // QTemporaryFile is weird and will return false in exists if fileName wasn't called before d->m_archiveData->metadataFile.fileName(); d->loadDocumentInfo( d->m_archiveData->metadataFile, LoadPageInfo ); d->loadDocumentInfo( LoadGeneralInfo ); } else { if ( d->loadDocumentInfo( LoadPageInfo ) ) d->m_docdataMigrationNeeded = true; d->loadDocumentInfo( LoadGeneralInfo ); } d->m_metadataLoadingCompleted = true; d->m_bookmarkManager->setUrl( d->m_url ); // 3. setup observers inernal lists and data foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged | DocumentObserver::UrlChanged ) ); // 4. set initial page (restoring the page saved in xml if loaded) DocumentViewport loadedViewport = (*d->m_viewportIterator); if ( loadedViewport.isValid() ) { (*d->m_viewportIterator) = DocumentViewport(); if ( loadedViewport.pageNumber >= (int)d->m_pagesVector.size() ) loadedViewport.pageNumber = d->m_pagesVector.size() - 1; } else loadedViewport.pageNumber = 0; setViewport( loadedViewport ); // start bookmark saver timer if ( !d->m_saveBookmarksTimer ) { d->m_saveBookmarksTimer = new QTimer( this ); connect( d->m_saveBookmarksTimer, SIGNAL(timeout()), this, SLOT(saveDocumentInfo()) ); } d->m_saveBookmarksTimer->start( 5 * 60 * 1000 ); // start memory check timer if ( !d->m_memCheckTimer ) { d->m_memCheckTimer = new QTimer( this ); connect( d->m_memCheckTimer, SIGNAL(timeout()), this, SLOT(slotTimedMemoryCheck()) ); } d->m_memCheckTimer->start( 2000 ); const DocumentViewport nextViewport = d->nextDocumentViewport(); if ( nextViewport.isValid() ) { setViewport( nextViewport ); d->m_nextDocumentViewport = DocumentViewport(); d->m_nextDocumentDestination = QString(); } AudioPlayer::instance()->d->m_currentDocument = fromFileDescriptor ? QUrl() : d->m_url; const QStringList docScripts = d->m_generator->metaData( QStringLiteral("DocumentScripts"), QStringLiteral ( "JavaScript" ) ).toStringList(); if ( !docScripts.isEmpty() ) { d->m_scripter = new Scripter( d ); Q_FOREACH ( const QString &docscript, docScripts ) { d->m_scripter->execute( JavaScript, docscript ); } } return OpenSuccess; } bool DocumentPrivate::updateMetadataXmlNameAndDocSize() { // m_docFileName is always local so we can use QFileInfo on it QFileInfo fileReadTest( m_docFileName ); if ( !fileReadTest.isFile() && !fileReadTest.isReadable() ) return false; m_docSize = fileReadTest.size(); // determine the related "xml document-info" filename if ( m_url.isLocalFile() ) { const QString filePath = docDataFileName( m_url, m_docSize ); qCDebug(OkularCoreDebug) << "Metadata file is now:" << filePath; m_xmlFileName = filePath; } else { qCDebug(OkularCoreDebug) << "Metadata file: disabled"; m_xmlFileName = QString(); } return true; } KXMLGUIClient* Document::guiClient() { if ( d->m_generator ) { Okular::GuiInterface * iface = qobject_cast< Okular::GuiInterface * >( d->m_generator ); if ( iface ) return iface->guiClient(); } return nullptr; } void Document::closeDocument() { // check if there's anything to close... if ( !d->m_generator ) return; delete d->m_pageController; d->m_pageController = nullptr; delete d->m_scripter; d->m_scripter = nullptr; // remove requests left in queue d->clearAndWaitForRequests(); if ( d->m_fontThread ) { disconnect( d->m_fontThread, nullptr, this, nullptr ); d->m_fontThread->stopExtraction(); d->m_fontThread->wait(); d->m_fontThread = nullptr; } // stop any audio playback AudioPlayer::instance()->stopPlaybacks(); // close the current document and save document info if a document is still opened if ( d->m_generator && d->m_pagesVector.size() > 0 ) { d->saveDocumentInfo(); d->m_generator->closeDocument(); } if ( d->m_synctex_scanner ) { synctex_scanner_free( d->m_synctex_scanner ); d->m_synctex_scanner = nullptr; } // stop timers if ( d->m_memCheckTimer ) d->m_memCheckTimer->stop(); if ( d->m_saveBookmarksTimer ) d->m_saveBookmarksTimer->stop(); if ( d->m_generator ) { // disconnect the generator from this document ... d->m_generator->d_func()->m_document = nullptr; // .. and this document from the generator signals disconnect( d->m_generator, nullptr, this, nullptr ); QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() ); } d->m_generator = nullptr; d->m_generatorName = QString(); d->m_url = QUrl(); d->m_walletGenerator = nullptr; d->m_docFileName = QString(); d->m_xmlFileName = QString(); delete d->m_tempFile; d->m_tempFile = nullptr; delete d->m_archiveData; d->m_archiveData = nullptr; d->m_docSize = -1; d->m_exportCached = false; d->m_exportFormats.clear(); d->m_exportToText = ExportFormat(); d->m_fontsCached = false; d->m_fontsCache.clear(); d->m_rotation = Rotation0; // send an empty list to observers (to free their data) foreachObserver( notifySetup( QVector< Page * >(), DocumentObserver::DocumentChanged | DocumentObserver::UrlChanged ) ); // delete pages and clear 'd->m_pagesVector' container QVector< Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); QVector< Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) delete *pIt; d->m_pagesVector.clear(); // clear 'memory allocation' descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); // clear 'running searches' descriptors QMap< int, RunningSearch * >::const_iterator rIt = d->m_searches.constBegin(); QMap< int, RunningSearch * >::const_iterator rEnd = d->m_searches.constEnd(); for ( ; rIt != rEnd; ++rIt ) delete *rIt; d->m_searches.clear(); // clear the visible areas and notify the observers QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) delete *vIt; d->m_pageRects.clear(); foreachObserver( notifyVisibleRectsChanged() ); // reset internal variables d->m_viewportHistory.clear(); d->m_viewportHistory.append( DocumentViewport() ); d->m_viewportIterator = d->m_viewportHistory.begin(); d->m_allocatedPixmapsTotalMemory = 0; d->m_allocatedTextPagesFifo.clear(); d->m_pageSize = PageSize(); d->m_pageSizes.clear(); d->m_documentInfo = DocumentInfo(); d->m_documentInfoAskedKeys.clear(); AudioPlayer::instance()->d->m_currentDocument = QUrl(); d->m_undoStack->clear(); d->m_docdataMigrationNeeded = false; #if HAVE_MALLOC_TRIM // trim unused memory, glibc should do this but it seems it does not // this can greatly decrease the [perceived] memory consumption of okular // see: https://sourceware.org/bugzilla/show_bug.cgi?id=14827 malloc_trim(0); #endif } void Document::addObserver( DocumentObserver * pObserver ) { Q_ASSERT( !d->m_observers.contains( pObserver ) ); d->m_observers << pObserver; // if the observer is added while a document is already opened, tell it if ( !d->m_pagesVector.isEmpty() ) { pObserver->notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged | DocumentObserver::UrlChanged ); pObserver->notifyViewportChanged( false /*disables smoothMove*/ ); } } void Document::removeObserver( DocumentObserver * pObserver ) { // remove observer from the set. it won't receive notifications anymore if ( d->m_observers.contains( pObserver ) ) { // free observer's pixmap data QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); for ( ; it != end; ++it ) (*it)->deletePixmap( pObserver ); // [MEM] free observer's allocation descriptors QLinkedList< AllocatedPixmap * >::iterator aIt = d->m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator aEnd = d->m_allocatedPixmaps.end(); while ( aIt != aEnd ) { AllocatedPixmap * p = *aIt; if ( p->observer == pObserver ) { aIt = d->m_allocatedPixmaps.erase( aIt ); delete p; } else ++aIt; } for ( PixmapRequest *executingRequest : qAsConst( d->m_executingPixmapRequests ) ) { if ( executingRequest->observer() == pObserver ) { d->cancelRenderingBecauseOf( executingRequest, nullptr ); } } // remove observer entry from the set d->m_observers.remove( pObserver ); } } void Document::reparseConfig() { // reparse generator config and if something changed clear Pages bool configchanged = false; if ( d->m_generator ) { Okular::ConfigInterface * iface = qobject_cast< Okular::ConfigInterface * >( d->m_generator ); if ( iface ) configchanged = iface->reparseConfig(); } if ( configchanged ) { // invalidate pixmaps QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); for ( ; it != end; ++it ) { (*it)->deletePixmaps(); } // [MEM] remove allocation descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); d->m_allocatedPixmapsTotalMemory = 0; // send reload signals to observers foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) ); } // free memory if in 'low' profile if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.isEmpty() && !d->m_pagesVector.isEmpty() ) d->cleanupPixmapMemory(); } bool Document::isOpened() const { return d->m_generator; } bool Document::canConfigurePrinter( ) const { if ( d->m_generator ) { Okular::PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); return iface ? true : false; } else return 0; } DocumentInfo Document::documentInfo() const { QSet keys; for (Okular::DocumentInfo::Key ks = Okular::DocumentInfo::Title; ks < Okular::DocumentInfo::Invalid; ks = Okular::DocumentInfo::Key( ks+1 ) ) { keys << ks; } return documentInfo( keys ); } DocumentInfo Document::documentInfo( const QSet &keys ) const { DocumentInfo result = d->m_documentInfo; const QSet missingKeys = keys - d->m_documentInfoAskedKeys; if ( d->m_generator && !missingKeys.isEmpty() ) { DocumentInfo info = d->m_generator->generateDocumentInfo( missingKeys ); if ( missingKeys.contains( DocumentInfo::FilePath ) ) { info.set( DocumentInfo::FilePath, currentDocument().toDisplayString() ); } if ( d->m_docSize != -1 && missingKeys.contains( DocumentInfo::DocumentSize ) ) { const QString sizeString = KFormat().formatByteSize( d->m_docSize ); info.set( DocumentInfo::DocumentSize, sizeString ); } if ( missingKeys.contains( DocumentInfo::PagesSize ) ) { const QString pagesSize = d->pagesSizeString(); if ( !pagesSize.isEmpty() ) { info.set( DocumentInfo::PagesSize, pagesSize ); } } if ( missingKeys.contains( DocumentInfo::Pages ) && info.get( DocumentInfo::Pages ).isEmpty() ) { info.set( DocumentInfo::Pages, QString::number( this->pages() ) ); } d->m_documentInfo.d->values.unite(info.d->values); d->m_documentInfo.d->titles.unite(info.d->titles); result.d->values.unite(info.d->values); result.d->titles.unite(info.d->titles); } d->m_documentInfoAskedKeys += keys; return result; } const DocumentSynopsis * Document::documentSynopsis() const { return d->m_generator ? d->m_generator->generateDocumentSynopsis() : nullptr; } void Document::startFontReading() { if ( !d->m_generator || !d->m_generator->hasFeature( Generator::FontInfo ) || d->m_fontThread ) return; if ( d->m_fontsCached ) { // in case we have cached fonts, simulate a reading // this way the API is the same, and users no need to care about the // internal caching for ( int i = 0; i < d->m_fontsCache.count(); ++i ) { emit gotFont( d->m_fontsCache.at( i ) ); emit fontReadingProgress( i / pages() ); } emit fontReadingEnded(); return; } d->m_fontThread = new FontExtractionThread( d->m_generator, pages() ); connect( d->m_fontThread, SIGNAL(gotFont(Okular::FontInfo)), this, SLOT(fontReadingGotFont(Okular::FontInfo)) ); connect( d->m_fontThread.data(), SIGNAL(progress(int)), this, SLOT(slotFontReadingProgress(int)) ); d->m_fontThread->startExtraction( /*d->m_generator->hasFeature( Generator::Threaded )*/true ); } void Document::stopFontReading() { if ( !d->m_fontThread ) return; disconnect( d->m_fontThread, nullptr, this, nullptr ); d->m_fontThread->stopExtraction(); d->m_fontThread = nullptr; d->m_fontsCache.clear(); } bool Document::canProvideFontInformation() const { return d->m_generator ? d->m_generator->hasFeature( Generator::FontInfo ) : false; } const QList *Document::embeddedFiles() const { return d->m_generator ? d->m_generator->embeddedFiles() : nullptr; } const Page * Document::page( int n ) const { return ( n >= 0 && n < d->m_pagesVector.count() ) ? d->m_pagesVector.at(n) : nullptr; } const DocumentViewport & Document::viewport() const { return (*d->m_viewportIterator); } const QVector< VisiblePageRect * > & Document::visiblePageRects() const { return d->m_pageRects; } void Document::setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver ) { QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) delete *vIt; d->m_pageRects = visiblePageRects; // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) if ( o != excludeObserver ) o->notifyVisibleRectsChanged(); } uint Document::currentPage() const { return (*d->m_viewportIterator).pageNumber; } uint Document::pages() const { return d->m_pagesVector.size(); } QUrl Document::currentDocument() const { return d->m_url; } bool Document::isAllowed( Permission action ) const { if ( action == Okular::AllowNotes && ( d->m_docdataMigrationNeeded || !d->m_annotationEditingEnabled ) ) return false; if ( action == Okular::AllowFillForms && d->m_docdataMigrationNeeded ) return false; #if !OKULAR_FORCE_DRM if ( KAuthorized::authorize( QStringLiteral("skip_drm") ) && !SettingsCore::obeyDRM() ) return true; #endif return d->m_generator ? d->m_generator->isAllowed( action ) : false; } bool Document::supportsSearching() const { return d->m_generator ? d->m_generator->hasFeature( Generator::TextExtraction ) : false; } bool Document::supportsPageSizes() const { return d->m_generator ? d->m_generator->hasFeature( Generator::PageSizes ) : false; } bool Document::supportsTiles() const { return d->m_generator ? d->m_generator->hasFeature( Generator::TiledRendering ) : false; } PageSize::List Document::pageSizes() const { if ( d->m_generator ) { if ( d->m_pageSizes.isEmpty() ) d->m_pageSizes = d->m_generator->pageSizes(); return d->m_pageSizes; } return PageSize::List(); } bool Document::canExportToText() const { if ( !d->m_generator ) return false; d->cacheExportFormats(); return !d->m_exportToText.isNull(); } bool Document::exportToText( const QString& fileName ) const { if ( !d->m_generator ) return false; d->cacheExportFormats(); if ( d->m_exportToText.isNull() ) return false; return d->m_generator->exportTo( fileName, d->m_exportToText ); } ExportFormat::List Document::exportFormats() const { if ( !d->m_generator ) return ExportFormat::List(); d->cacheExportFormats(); return d->m_exportFormats; } bool Document::exportTo( const QString& fileName, const ExportFormat& format ) const { return d->m_generator ? d->m_generator->exportTo( fileName, format ) : false; } bool Document::historyAtBegin() const { return d->m_viewportIterator == d->m_viewportHistory.begin(); } bool Document::historyAtEnd() const { return d->m_viewportIterator == --(d->m_viewportHistory.end()); } QVariant Document::metaData( const QString & key, const QVariant & option ) const { // if option starts with "src:" assume that we are handling a // source reference if ( key == QLatin1String("NamedViewport") && option.toString().startsWith( QLatin1String("src:"), Qt::CaseInsensitive ) && d->m_synctex_scanner) { const QString reference = option.toString(); // The reference is of form "src:1111Filename", where "1111" // points to line number 1111 in the file "Filename". // Extract the file name and the numeral part from the reference string. // This will fail if Filename starts with a digit. QString name, lineString; // Remove "src:". Presence of substring has been checked before this // function is called. name = reference.mid( 4 ); // split int nameLength = name.length(); int i = 0; for( i = 0; i < nameLength; ++i ) { if ( !name[i].isDigit() ) break; } lineString = name.left( i ); name = name.mid( i ); // Remove spaces. name = name.trimmed(); lineString = lineString.trimmed(); // Convert line to integer. bool ok; int line = lineString.toInt( &ok ); if (!ok) line = -1; // Use column == -1 for now. if( synctex_display_query( d->m_synctex_scanner, QFile::encodeName(name).constData(), line, -1, 0 ) > 0 ) { synctex_node_p node; // For now use the first hit. Could possibly be made smarter // in case there are multiple hits. while( ( node = synctex_scanner_next_result( d->m_synctex_scanner ) ) ) { Okular::DocumentViewport viewport; // TeX pages start at 1. viewport.pageNumber = synctex_node_page( node ) - 1; if ( viewport.pageNumber >= 0 ) { const QSizeF dpi = d->m_generator->dpi(); // TeX small points ... double px = (synctex_node_visible_h( node ) * dpi.width()) / 72.27; double py = (synctex_node_visible_v( node ) * dpi.height()) / 72.27; viewport.rePos.normalizedX = px / page(viewport.pageNumber)->width(); viewport.rePos.normalizedY = ( py + 0.5 ) / page(viewport.pageNumber)->height(); viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::Center; return viewport.toString(); } } } } return d->m_generator ? d->m_generator->metaData( key, option ) : QVariant(); } Rotation Document::rotation() const { return d->m_rotation; } QSizeF Document::allPagesSize() const { bool allPagesSameSize = true; QSizeF size; for (int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) { const Page *p = d->m_pagesVector.at(i); if (i == 0) size = QSizeF(p->width(), p->height()); else { allPagesSameSize = (size == QSizeF(p->width(), p->height())); } } if (allPagesSameSize) return size; else return QSizeF(); } QString Document::pageSizeString(int page) const { if (d->m_generator) { if (d->m_generator->pagesSizeMetric() != Generator::None) { const Page *p = d->m_pagesVector.at( page ); return d->localizedSize(QSizeF(p->width(), p->height())); } } return QString(); } static bool shouldCancelRenderingBecauseOf( const PixmapRequest & executingRequest, const PixmapRequest & otherRequest ) { // New request has higher priority -> cancel if ( executingRequest.priority() > otherRequest.priority() ) return true; // New request has lower priority -> don't cancel if ( executingRequest.priority() < otherRequest.priority() ) return false; // New request has same priority and is from a different observer -> don't cancel // AFAIK this never happens since all observers have different priorities if ( executingRequest.observer() != otherRequest.observer() ) return false; // Same priority and observer, different page number -> don't cancel // may still end up cancelled later in the parent caller if none of the requests // is of the executingRequest page and RemoveAllPrevious is specified if ( executingRequest.pageNumber() != otherRequest.pageNumber() ) return false; // Same priority, observer, page, different size -> cancel if ( executingRequest.width() != otherRequest.width() ) return true; // Same priority, observer, page, different size -> cancel if ( executingRequest.height() != otherRequest.height() ) return true; // Same priority, observer, page, different tiling -> cancel if ( executingRequest.isTile() != otherRequest.isTile() ) return true; // Same priority, observer, page, different tiling -> cancel if ( executingRequest.isTile() ) { const NormalizedRect bothRequestsRect = executingRequest.normalizedRect() | otherRequest.normalizedRect(); if ( !( bothRequestsRect == executingRequest.normalizedRect() ) ) return true; } return false; } bool DocumentPrivate::cancelRenderingBecauseOf( PixmapRequest *executingRequest, PixmapRequest *newRequest ) { // No point in aborting the rendering already finished, let it go through if ( !executingRequest->d->mResultImage.isNull() ) return false; if ( newRequest && newRequest->asynchronous() && executingRequest->partialUpdatesWanted() ) { newRequest->setPartialUpdatesWanted( true ); } TilesManager *tm = executingRequest->d->tilesManager(); if ( tm ) { tm->setPixmap( nullptr, executingRequest->normalizedRect(), true /*isPartialPixmap*/ ); tm->setRequest( NormalizedRect(), 0, 0 ); } PagePrivate::PixmapObject object = executingRequest->page()->d->m_pixmaps.take( executingRequest->observer() ); delete object.m_pixmap; if ( executingRequest->d->mShouldAbortRender != 0) return false; executingRequest->d->mShouldAbortRender = 1; if ( m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->page() ) { m_generator->d_ptr->mTextPageGenerationThread->abortExtraction(); } return true; } void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests ) { requestPixmaps( requests, RemoveAllPrevious ); } void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests, PixmapRequestFlags reqOptions ) { if ( requests.isEmpty() ) return; if ( !d->m_pageController ) { // delete requests.. QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) delete *rIt; // ..and return return; } QSet< DocumentObserver * > observersPixmapCleared; // 1. [CLEAN STACK] remove previous requests of requesterID DocumentObserver *requesterObserver = requests.first()->observer(); QSet< int > requestedPages; { QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) { Q_ASSERT( (*rIt)->observer() == requesterObserver ); requestedPages.insert( (*rIt)->pageNumber() ); } } const bool removeAllPrevious = reqOptions & RemoveAllPrevious; d->m_pixmapRequestsMutex.lock(); QLinkedList< PixmapRequest * >::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end(); while ( sIt != sEnd ) { if ( (*sIt)->observer() == requesterObserver && ( removeAllPrevious || requestedPages.contains( (*sIt)->pageNumber() ) ) ) { // delete request and remove it from stack delete *sIt; sIt = d->m_pixmapRequestsStack.erase( sIt ); } else ++sIt; } // 1.B [PREPROCESS REQUESTS] tweak some values of the requests for ( PixmapRequest *request : requests ) { // set the 'page field' (see PixmapRequest) and check if it is valid qCDebug(OkularCoreDebug).nospace() << "request observer=" << request->observer() << " " <width() << "x" << request->height() << "@" << request->pageNumber(); if ( d->m_pagesVector.value( request->pageNumber() ) == 0 ) { // skip requests referencing an invalid page (must not happen) delete request; continue; } request->d->mPage = d->m_pagesVector.value( request->pageNumber() ); if ( request->isTile() ) { // Change the current request rect so that only invalid tiles are // requested. Also make sure the rect is tile-aligned. NormalizedRect tilesRect; const QList tiles = request->d->tilesManager()->tilesAt( request->normalizedRect(), TilesManager::TerminalTile ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Tile &tile = *tIt; if ( !tile.isValid() ) { if ( tilesRect.isNull() ) tilesRect = tile.rect(); else tilesRect |= tile.rect(); } tIt++; } request->setNormalizedRect( tilesRect ); } if ( !request->asynchronous() ) request->d->mPriority = 0; } // 1.C [CANCEL REQUESTS] cancel those requests that are running and should be cancelled because of the new requests coming in if ( d->m_generator->hasFeature( Generator::SupportsCancelling ) ) { for ( PixmapRequest *executingRequest : qAsConst( d->m_executingPixmapRequests ) ) { bool newRequestsContainExecutingRequestPage = false; bool requestCancelled = false; for ( PixmapRequest *newRequest : requests ) { if ( newRequest->pageNumber() == executingRequest->pageNumber() && requesterObserver == executingRequest->observer()) { newRequestsContainExecutingRequestPage = true; } if ( shouldCancelRenderingBecauseOf( *executingRequest, *newRequest ) ) { requestCancelled = d->cancelRenderingBecauseOf( executingRequest, newRequest ); } } // If we were told to remove all the previous requests and the executing request page is not part of the new requests, cancel it if ( !requestCancelled && removeAllPrevious && requesterObserver == executingRequest->observer() && !newRequestsContainExecutingRequestPage ) { requestCancelled = d->cancelRenderingBecauseOf( executingRequest, nullptr ); } if ( requestCancelled ) { observersPixmapCleared << executingRequest->observer(); } } } // 2. [ADD TO STACK] add requests to stack for ( PixmapRequest *request : requests ) { // add request to the 'stack' at the right place if ( !request->priority() ) // add priority zero requests to the top of the stack d->m_pixmapRequestsStack.append( request ); else { // insert in stack sorted by priority sIt = d->m_pixmapRequestsStack.begin(); sEnd = d->m_pixmapRequestsStack.end(); while ( sIt != sEnd && (*sIt)->priority() > request->priority() ) ++sIt; d->m_pixmapRequestsStack.insert( sIt, request ); } } d->m_pixmapRequestsMutex.unlock(); // 3. [START FIRST GENERATION] if generator is ready, start a new generation, // or else (if gen is running) it will be started when the new contents will //come from generator (in requestDone()) // all handling of requests put into sendGeneratorPixmapRequest // if ( generator->canRequestPixmap() ) d->sendGeneratorPixmapRequest(); for ( DocumentObserver *o : qAsConst( observersPixmapCleared ) ) o->notifyContentsCleared( Okular::DocumentObserver::Pixmap ); } void Document::requestTextPage( uint page ) { Page * kp = d->m_pagesVector[ page ]; if ( !d->m_generator || !kp ) return; // Memory management for TextPages d->m_generator->generateTextPage( kp ); } void DocumentPrivate::notifyAnnotationChanges( int page ) { foreachObserverD( notifyPageChanged( page, DocumentObserver::Annotations ) ); } void DocumentPrivate::notifyFormChanges( int /*page*/ ) { recalculateForms(); } void Document::addPageAnnotation( int page, Annotation * annotation ) { // Transform annotation's base boundary rectangle into unrotated coordinates Page *p = d->m_pagesVector[page]; QTransform t = p->d->rotationMatrix(); annotation->d_ptr->baseTransform(t.inverted()); QUndoCommand *uc = new AddAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } bool Document::canModifyPageAnnotation( const Annotation * annotation ) const { if ( !annotation || ( annotation->flags() & Annotation::DenyWrite ) ) return false; if ( !isAllowed(Okular::AllowNotes) ) return false; if ( ( annotation->flags() & Annotation::External ) && !d->canModifyExternalAnnotations() ) return false; switch ( annotation->subType() ) { case Annotation::AText: case Annotation::ALine: case Annotation::AGeom: case Annotation::AHighlight: case Annotation::AStamp: case Annotation::AInk: return true; default: return false; } } void Document::prepareToModifyAnnotationProperties( Annotation * annotation ) { Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull()); if (!d->m_prevPropsOfAnnotBeingModified.isNull()) { qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties"; return; } d->m_prevPropsOfAnnotBeingModified = annotation->getAnnotationPropertiesDomNode(); } void Document::modifyPageAnnotationProperties( int page, Annotation * annotation ) { Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull()); if (d->m_prevPropsOfAnnotBeingModified.isNull()) { qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified"; return; } QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified; QUndoCommand *uc = new Okular::ModifyAnnotationPropertiesCommand( d, annotation, page, prevProps, annotation->getAnnotationPropertiesDomNode() ); d->m_undoStack->push( uc ); d->m_prevPropsOfAnnotBeingModified.clear(); } void Document::translatePageAnnotation(int page, Annotation* annotation, const NormalizedPoint & delta ) { int complete = (annotation->flags() & Okular::Annotation::BeingMoved) == 0; QUndoCommand *uc = new Okular::TranslateAnnotationCommand( d, annotation, page, delta, complete ); d->m_undoStack->push(uc); } void Document::adjustPageAnnotation( int page, Annotation *annotation, const Okular::NormalizedPoint & delta1, const Okular::NormalizedPoint & delta2 ) { const bool complete = (annotation->flags() & Okular::Annotation::BeingResized) == 0; QUndoCommand *uc = new Okular::AdjustAnnotationCommand( d, annotation, page, delta1, delta2, complete ); d->m_undoStack->push(uc); } void Document::editPageAnnotationContents( int page, Annotation* annotation, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QString prevContents = annotation->contents(); QUndoCommand *uc = new EditAnnotationContentsCommand( d, annotation, page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); } bool Document::canRemovePageAnnotation( const Annotation * annotation ) const { if ( !annotation || ( annotation->flags() & Annotation::DenyDelete ) ) return false; if ( ( annotation->flags() & Annotation::External ) && !d->canRemoveExternalAnnotations() ) return false; switch ( annotation->subType() ) { case Annotation::AText: case Annotation::ALine: case Annotation::AGeom: case Annotation::AHighlight: case Annotation::AStamp: case Annotation::AInk: case Annotation::ACaret: return true; default: return false; } } void Document::removePageAnnotation( int page, Annotation * annotation ) { QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } void Document::removePageAnnotations( int page, const QList &annotations ) { d->m_undoStack->beginMacro(i18nc("remove a collection of annotations from the page", "remove annotations")); foreach(Annotation* annotation, annotations) { QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } d->m_undoStack->endMacro(); } bool DocumentPrivate::canAddAnnotationsNatively() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Addition) ) return true; return false; } bool DocumentPrivate::canModifyExternalAnnotations() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Modification) ) return true; return false; } bool DocumentPrivate::canRemoveExternalAnnotations() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Removal) ) return true; return false; } void Document::setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color ) { Page * kp = d->m_pagesVector[ page ]; if ( !d->m_generator || !kp ) return; // add or remove the selection basing whether rect is null or not if ( rect ) kp->d->setTextSelections( rect, color ); else kp->d->deleteTextSelections(); // notify observers about the change foreachObserver( notifyPageChanged( page, DocumentObserver::TextSelection ) ); } bool Document::canUndo() const { return d->m_undoStack->canUndo(); } bool Document::canRedo() const { return d->m_undoStack->canRedo(); } /* REFERENCE IMPLEMENTATION: better calling setViewport from other code void Document::setNextPage() { // advance page and set viewport on observers if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) ); } void Document::setPrevPage() { // go to previous page and set viewport on observers if ( (*d->m_viewportIterator).pageNumber > 0 ) setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) ); } */ void Document::setViewportPage( int page, DocumentObserver *excludeObserver, bool smoothMove ) { // clamp page in range [0 ... numPages-1] if ( page < 0 ) page = 0; else if ( page > (int)d->m_pagesVector.count() ) page = d->m_pagesVector.count() - 1; // make a viewport from the page and broadcast it setViewport( DocumentViewport( page ), excludeObserver, smoothMove ); } void Document::setViewport( const DocumentViewport & viewport, DocumentObserver *excludeObserver, bool smoothMove ) { if ( !viewport.isValid() ) { qCDebug(OkularCoreDebug) << "invalid viewport:" << viewport.toString(); return; } if ( viewport.pageNumber >= int(d->m_pagesVector.count()) ) { //qCDebug(OkularCoreDebug) << "viewport out of document:" << viewport.toString(); return; } // if already broadcasted, don't redo it DocumentViewport & oldViewport = *d->m_viewportIterator; // disabled by enrico on 2005-03-18 (less debug output) //if ( viewport == oldViewport ) // qCDebug(OkularCoreDebug) << "setViewport with the same viewport."; const int oldPageNumber = oldViewport.pageNumber; // set internal viewport taking care of history if ( oldViewport.pageNumber == viewport.pageNumber || !oldViewport.isValid() ) { // if page is unchanged save the viewport at current position in queue oldViewport = viewport; } else { // remove elements after viewportIterator in queue d->m_viewportHistory.erase( ++d->m_viewportIterator, d->m_viewportHistory.end() ); // keep the list to a reasonable size by removing head when needed if ( d->m_viewportHistory.count() >= OKULAR_HISTORY_MAXSTEPS ) d->m_viewportHistory.pop_front(); // add the item at the end of the queue d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), viewport ); } const int currentViewportPage = (*d->m_viewportIterator).pageNumber; const bool currentPageChanged = (oldPageNumber != currentViewportPage); // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) { if ( o != excludeObserver ) o->notifyViewportChanged( smoothMove ); if ( currentPageChanged ) o->notifyCurrentPageChanged( oldPageNumber, currentViewportPage ); } } void Document::setZoom(int factor, DocumentObserver *excludeObserver) { // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) if (o != excludeObserver) o->notifyZoom( factor ); } void Document::setPrevViewport() // restore viewport from the history { if ( d->m_viewportIterator != d->m_viewportHistory.begin() ) { const int oldViewportPage = (*d->m_viewportIterator).pageNumber; // restore previous viewport and notify it to observers --d->m_viewportIterator; foreachObserver( notifyViewportChanged( true ) ); const int currentViewportPage = (*d->m_viewportIterator).pageNumber; if (oldViewportPage != currentViewportPage) foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); } } void Document::setNextViewport() // restore next viewport from the history { QLinkedList< DocumentViewport >::const_iterator nextIterator = d->m_viewportIterator; ++nextIterator; if ( nextIterator != d->m_viewportHistory.end() ) { const int oldViewportPage = (*d->m_viewportIterator).pageNumber; // restore next viewport and notify it to observers ++d->m_viewportIterator; foreachObserver( notifyViewportChanged( true ) ); const int currentViewportPage = (*d->m_viewportIterator).pageNumber; if (oldViewportPage != currentViewportPage) foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); } } void Document::setNextDocumentViewport( const DocumentViewport & viewport ) { d->m_nextDocumentViewport = viewport; } void Document::setNextDocumentDestination( const QString &namedDestination ) { d->m_nextDocumentDestination = namedDestination; } void Document::searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor & color ) { d->m_searchCancelled = false; // safety checks: don't perform searches on empty or unsearchable docs if ( !d->m_generator || !d->m_generator->hasFeature( Generator::TextExtraction ) || d->m_pagesVector.isEmpty() ) { emit searchFinished( searchID, NoMatchFound ); return; } // if searchID search not recorded, create new descriptor and init params QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); if ( searchIt == d->m_searches.end() ) { RunningSearch * search = new RunningSearch(); search->continueOnPage = -1; searchIt = d->m_searches.insert( searchID, search ); } RunningSearch * s = *searchIt; // update search structure bool newText = text != s->cachedString; s->cachedString = text; s->cachedType = type; s->cachedCaseSensitivity = caseSensitivity; s->cachedViewportMove = moveViewport; s->cachedColor = color; s->isCurrentlySearching = true; // global data for search QSet< int > *pagesToNotify = new QSet< int >; // remove highlights from pages and queue them for notifying changes *pagesToNotify += s->highlightedPages; foreach(int pageNumber, s->highlightedPages) d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); s->highlightedPages.clear(); // set hourglass cursor QApplication::setOverrideCursor( Qt::WaitCursor ); // 1. ALLDOC - proces all document marking pages if ( type == AllDocument ) { QMap< Page *, QVector > *pageMatches = new QMap< Page *, QVector >; // search and highlight 'text' (as a solid phrase) on all pages QMetaObject::invokeMethod(this, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID)); } // 2. NEXTMATCH - find next matching item (or start from top) // 3. PREVMATCH - find previous matching item (or start from bottom) else if ( type == NextMatch || type == PreviousMatch ) { // find out from where to start/resume search from const bool forward = type == NextMatch; const int viewportPage = (*d->m_viewportIterator).pageNumber; const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1; int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage); Page * lastPage = fromStart ? 0 : d->m_pagesVector[ currentPage ]; int pagesDone = 0; // continue checking last TextPage first (if it is the current page) RegularAreaRect * match = nullptr; if ( lastPage && lastPage->number() == s->continueOnPage ) { if ( newText ) match = lastPage->findText( searchID, text, forward ? FromTop : FromBottom, caseSensitivity ); else match = lastPage->findText( searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch ); if ( !match ) { if (forward) currentPage++; else currentPage--; pagesDone++; } } s->pagesDone = pagesDone; DoContinueDirectionMatchSearchStruct *searchStruct = new DoContinueDirectionMatchSearchStruct(); searchStruct->pagesToNotify = pagesToNotify; searchStruct->match = match; searchStruct->currentPage = currentPage; searchStruct->searchID = searchID; QMetaObject::invokeMethod(this, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); } // 4. GOOGLE* - process all document marking pages else if ( type == GoogleAll || type == GoogleAny ) { QMap< Page *, QVector< QPair > > *pageMatches = new QMap< Page *, QVector > >; const QStringList words = text.split( QLatin1Char ( ' ' ), QString::SkipEmptyParts ); // search and highlight every word in 'text' on all pages QMetaObject::invokeMethod(this, "doContinueGooglesDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID), Q_ARG(QStringList, words)); } } void Document::continueSearch( int searchID ) { // check if searchID is present in runningSearches QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); if ( it == d->m_searches.constEnd() ) { emit searchFinished( searchID, NoMatchFound ); return; } // start search with cached parameters from last search by searchID RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor ); } void Document::continueSearch( int searchID, SearchType type ) { // check if searchID is present in runningSearches QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); if ( it == d->m_searches.constEnd() ) { emit searchFinished( searchID, NoMatchFound ); return; } // start search with cached parameters from last search by searchID RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor ); } void Document::resetSearch( int searchID ) { // if we are closing down, don't bother doing anything if ( !d->m_generator ) return; // check if searchID is present in runningSearches QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); if ( searchIt == d->m_searches.end() ) return; // get previous parameters for search RunningSearch * s = *searchIt; // unhighlight pages and inform observers about that foreach(int pageNumber, s->highlightedPages) { d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) ); } // send the setup signal too (to update views that filter on matches) foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); // remove serch from the runningSearches list and delete it d->m_searches.erase( searchIt ); delete s; } void Document::cancelSearch() { d->m_searchCancelled = true; } void Document::undo() { d->m_undoStack->undo(); } void Document::redo() { d->m_undoStack->redo(); } void Document::editFormText( int pageNumber, Okular::FormFieldText* form, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QUndoCommand *uc = new EditFormTextCommand( this->d, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); } void Document::editFormList( int pageNumber, FormFieldChoice* form, const QList< int > & newChoices ) { const QList< int > prevChoices = form->currentChoices(); QUndoCommand *uc = new EditFormListCommand( this->d, form, pageNumber, newChoices, prevChoices ); d->m_undoStack->push( uc ); } void Document::editFormCombo( int pageNumber, FormFieldChoice* form, const QString & newText, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QString prevText; if ( form->currentChoices().isEmpty() ) { prevText = form->editChoice(); } else { prevText = form->choices()[form->currentChoices().constFirst()]; } QUndoCommand *uc = new EditFormComboCommand( this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); } void Document::editFormButtons( int pageNumber, const QList< FormFieldButton* >& formButtons, const QList< bool >& newButtonStates ) { QUndoCommand *uc = new EditFormButtonsCommand( this->d, pageNumber, formButtons, newButtonStates ); d->m_undoStack->push( uc ); } void Document::reloadDocument() const { const int numOfPages = pages(); for( int i = currentPage(); i >= 0; i -- ) d->refreshPixmaps( i ); for( int i = currentPage() + 1; i < numOfPages; i ++ ) d->refreshPixmaps( i ); } BookmarkManager * Document::bookmarkManager() const { return d->m_bookmarkManager; } QList Document::bookmarkedPageList() const { QList list; uint docPages = pages(); //pages are 0-indexed internally, but 1-indexed externally for ( uint i = 0; i < docPages; i++ ) { if ( bookmarkManager()->isBookmarked( i ) ) { list << i + 1; } } return list; } QString Document::bookmarkedPageRange() const { // Code formerly in Part::slotPrint() // range detecting QString range; uint docPages = pages(); int startId = -1; int endId = -1; for ( uint i = 0; i < docPages; ++i ) { if ( bookmarkManager()->isBookmarked( i ) ) { if ( startId < 0 ) startId = i; if ( endId < 0 ) endId = startId; else ++endId; } else if ( startId >= 0 && endId >= 0 ) { if ( !range.isEmpty() ) range += QLatin1Char ( ',' ); if ( endId - startId > 0 ) range += QStringLiteral( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); else range += QString::number( startId + 1 ); startId = -1; endId = -1; } } if ( startId >= 0 && endId >= 0 ) { if ( !range.isEmpty() ) range += QLatin1Char ( ',' ); if ( endId - startId > 0 ) range += QStringLiteral( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); else range += QString::number( startId + 1 ); } return range; } void Document::processAction( const Action * action ) { if ( !action ) return; switch( action->actionType() ) { case Action::Goto: { const GotoAction * go = static_cast< const GotoAction * >( action ); d->m_nextDocumentViewport = go->destViewport(); d->m_nextDocumentDestination = go->destinationName(); // Explanation of why d->m_nextDocumentViewport is needed: // all openRelativeFile does is launch a signal telling we // want to open another URL, the problem is that when the file is // non local, the loading is done assynchronously so you can't // do a setViewport after the if as it was because you are doing the setViewport // on the old file and when the new arrives there is no setViewport for it and // it does not show anything // first open filename if link is pointing outside this document if ( go->isExternal() && !d->openRelativeFile( go->fileName() ) ) { qCWarning(OkularCoreDebug).nospace() << "Action: Error opening '" << go->fileName() << "'."; break; } else { const DocumentViewport nextViewport = d->nextDocumentViewport(); // skip local links that point to nowhere (broken ones) if ( !nextViewport.isValid() ) break; setViewport( nextViewport, nullptr, true ); d->m_nextDocumentViewport = DocumentViewport(); d->m_nextDocumentDestination = QString(); } } break; case Action::Execute: { const ExecuteAction * exe = static_cast< const ExecuteAction * >( action ); const QString fileName = exe->fileName(); if ( fileName.endsWith( QLatin1String(".pdf"), Qt::CaseInsensitive ) ) { d->openRelativeFile( fileName ); break; } // Albert: the only pdf i have that has that kind of link don't define // an application and use the fileName as the file to open QUrl url = d->giveAbsoluteUrl( fileName ); QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl( url ); // Check executables if ( KRun::isExecutableFile( url, mime.name() ) ) { // Don't have any pdf that uses this code path, just a guess on how it should work if ( !exe->parameters().isEmpty() ) { url = d->giveAbsoluteUrl( exe->parameters() ); mime = db.mimeTypeForUrl( url ); if ( KRun::isExecutableFile( url, mime.name() ) ) { // this case is a link pointing to an executable with a parameter // that also is an executable, possibly a hand-crafted pdf KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); break; } } else { // this case is a link pointing to an executable with no parameters // core developers find unacceptable executing it even after asking the user KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); break; } } KService::Ptr ptr = KMimeTypeTrader::self()->preferredService( mime.name(), QStringLiteral("Application") ); if ( ptr ) { QList lst; lst.append( url ); KRun::runService( *ptr, lst, nullptr ); } else KMessageBox::information( d->m_widget, i18n( "No application found for opening file of mimetype %1.", mime.name() ) ); } break; case Action::DocAction: { const DocumentAction * docaction = static_cast< const DocumentAction * >( action ); switch( docaction->documentActionType() ) { case DocumentAction::PageFirst: setViewportPage( 0 ); break; case DocumentAction::PagePrev: if ( (*d->m_viewportIterator).pageNumber > 0 ) setViewportPage( (*d->m_viewportIterator).pageNumber - 1 ); break; case DocumentAction::PageNext: if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) setViewportPage( (*d->m_viewportIterator).pageNumber + 1 ); break; case DocumentAction::PageLast: setViewportPage( d->m_pagesVector.count() - 1 ); break; case DocumentAction::HistoryBack: setPrevViewport(); break; case DocumentAction::HistoryForward: setNextViewport(); break; case DocumentAction::Quit: emit quit(); break; case DocumentAction::Presentation: emit linkPresentation(); break; case DocumentAction::EndPresentation: emit linkEndPresentation(); break; case DocumentAction::Find: emit linkFind(); break; case DocumentAction::GoToPage: emit linkGoToPage(); break; case DocumentAction::Close: emit close(); break; } } break; case Action::Browse: { const BrowseAction * browse = static_cast< const BrowseAction * >( action ); QString lilySource; int lilyRow = 0, lilyCol = 0; // if the url is a mailto one, invoke mailer if ( browse->url().scheme() == QLatin1String("mailto") ) { QDesktopServices::openUrl( browse->url() ); } else if ( extractLilyPondSourceReference( browse->url(), &lilySource, &lilyRow, &lilyCol ) ) { const SourceReference ref( lilySource, lilyRow, lilyCol ); processSourceReference( &ref ); } else { const QUrl url = browse->url(); // fix for #100366, documents with relative links that are the form of http:foo.pdf if ((url.scheme() == "http") && url.host().isEmpty() && url.fileName().endsWith("pdf")) { d->openRelativeFile(url.fileName()); break; } // handle documents with relative path if ( d->m_url.isValid() ) { const QUrl realUrl = KIO::upUrl(d->m_url).resolved(url); // KRun autodeletes new KRun( realUrl, d->m_widget ); } } } break; case Action::Sound: { const SoundAction * linksound = static_cast< const SoundAction * >( action ); AudioPlayer::instance()->playSound( linksound->sound(), linksound ); } break; case Action::Script: { const ScriptAction * linkscript = static_cast< const ScriptAction * >( action ); if ( !d->m_scripter ) d->m_scripter = new Scripter( d ); d->m_scripter->execute( linkscript->scriptType(), linkscript->script() ); } break; case Action::Movie: emit processMovieAction( static_cast< const MovieAction * >( action ) ); break; case Action::Rendition: { const RenditionAction * linkrendition = static_cast< const RenditionAction * >( action ); if ( !linkrendition->script().isEmpty() ) { if ( !d->m_scripter ) d->m_scripter = new Scripter( d ); d->m_scripter->execute( linkrendition->scriptType(), linkrendition->script() ); } emit processRenditionAction( static_cast< const RenditionAction * >( action ) ); } break; case Action::BackendOpaque: { d->m_generator->opaqueAction( static_cast< const BackendOpaqueAction * >( action ) ); } break; } for ( const Action *a : action->nextActions() ) { processAction( a ); } } void Document::processSourceReference( const SourceReference * ref ) { if ( !ref ) return; const QUrl url = d->giveAbsoluteUrl( ref->fileName() ); if ( !url.isLocalFile() ) { qCDebug(OkularCoreDebug) << url.url() << "is not a local file."; return; } const QString absFileName = url.toLocalFile(); if ( !QFile::exists( absFileName ) ) { qCDebug(OkularCoreDebug) << "No such file:" << absFileName; return; } bool handled = false; emit sourceReferenceActivated(absFileName, ref->row(), ref->column(), &handled); if(handled) { return; } static QHash< int, QString > editors; // init the editors table if empty (on first run, usually) if ( editors.isEmpty() ) { editors = buildEditorsMap(); } QHash< int, QString >::const_iterator it = editors.constFind( SettingsCore::externalEditor() ); QString p; if ( it != editors.constEnd() ) p = *it; else p = SettingsCore::externalEditorCommand(); // custom editor not yet configured if ( p.isEmpty() ) return; // manually append the %f placeholder if not specified if ( p.indexOf( QLatin1String( "%f" ) ) == -1 ) p.append( QLatin1String( " %f" ) ); // replacing the placeholders QHash< QChar, QString > map; map.insert( QLatin1Char ( 'f' ), absFileName ); map.insert( QLatin1Char ( 'c' ), QString::number( ref->column() ) ); map.insert( QLatin1Char ( 'l' ), QString::number( ref->row() ) ); const QString cmd = KMacroExpander::expandMacrosShellQuote( p, map ); if ( cmd.isEmpty() ) return; const QStringList args = KShell::splitArgs( cmd ); if ( args.isEmpty() ) return; KProcess::startDetached( args ); } const SourceReference * Document::dynamicSourceReference( int pageNr, double absX, double absY ) { if ( !d->m_synctex_scanner ) return nullptr; const QSizeF dpi = d->m_generator->dpi(); if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.width(), absY * 72. / dpi.height()) > 0) { synctex_node_p node; // TODO what should we do if there is really more than one node? while (( node = synctex_scanner_next_result( d->m_synctex_scanner ) )) { int line = synctex_node_line(node); int col = synctex_node_column(node); // column extraction does not seem to be implemented in synctex so far. set the SourceReference default value. if ( col == -1 ) { col = 0; } const char *name = synctex_scanner_get_name( d->m_synctex_scanner, synctex_node_tag( node ) ); return new Okular::SourceReference( QFile::decodeName( name ), line, col ); } } return nullptr; } Document::PrintingType Document::printingSupport() const { if ( d->m_generator ) { if ( d->m_generator->hasFeature( Generator::PrintNative ) ) { return NativePrinting; } #ifndef Q_OS_WIN if ( d->m_generator->hasFeature( Generator::PrintPostscript ) ) { return PostscriptPrinting; } #endif } return NoPrinting; } bool Document::supportsPrintToFile() const { return d->m_generator ? d->m_generator->hasFeature( Generator::PrintToFile ) : false; } bool Document::print( QPrinter &printer ) { return d->m_generator ? d->m_generator->print( printer ) : false; } QString Document::printError() const { Okular::Generator::PrintError err = Generator::UnknownPrintError; if ( d->m_generator ) { QMetaObject::invokeMethod( d->m_generator, "printError", Qt::DirectConnection, Q_RETURN_ARG(Okular::Generator::PrintError, err) ); } Q_ASSERT( err != Generator::NoPrintError ); switch ( err ) { case Generator::TemporaryFileOpenPrintError: return i18n( "Could not open a temporary file" ); case Generator::FileConversionPrintError: return i18n( "Print conversion failed" ); case Generator::PrintingProcessCrashPrintError: return i18n( "Printing process crashed" ); case Generator::PrintingProcessStartPrintError: return i18n( "Printing process could not start" ); case Generator::PrintToFilePrintError: return i18n( "Printing to file failed" ); case Generator::InvalidPrinterStatePrintError: return i18n( "Printer was in invalid state" ); case Generator::UnableToFindFilePrintError: return i18n( "Unable to find file to print" ); case Generator::NoFileToPrintError: return i18n( "There was no file to print" ); case Generator::NoBinaryToPrintError: return i18n( "Could not find a suitable binary for printing. Make sure CUPS lpr binary is available" ); case Generator::InvalidPageSizePrintError: return i18n( "The page print size is invalid" ); case Generator::NoPrintError: return QString(); case Generator::UnknownPrintError: return QString(); } return QString(); } QWidget* Document::printConfigurationWidget() const { if ( d->m_generator ) { PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); return iface ? iface->printConfigurationWidget() : nullptr; } else return nullptr; } void Document::fillConfigDialog( KConfigDialog * dialog ) { if ( !dialog ) return; // ensure that we have all the generators with settings loaded QVector offers = DocumentPrivate::configurableGenerators(); d->loadServiceList( offers ); // We want the generators to be sorted by name so let's fill in a QMap // this sorts by internal id which is not awesome, but at least the sorting // is stable between runs that before it wasn't QMap sortedGenerators; QHash< QString, GeneratorInfo >::iterator it = d->m_loadedGenerators.begin(); QHash< QString, GeneratorInfo >::iterator itEnd = d->m_loadedGenerators.end(); for ( ; it != itEnd; ++it ) { sortedGenerators.insert(it.key(), it.value()); } bool pagesAdded = false; QMap< QString, GeneratorInfo >::iterator sit = sortedGenerators.begin(); QMap< QString, GeneratorInfo >::iterator sitEnd = sortedGenerators.end(); for ( ; sit != sitEnd; ++sit ) { Okular::ConfigInterface * iface = d->generatorConfig( sit.value() ); if ( iface ) { iface->addPages( dialog ); pagesAdded = true; } } if ( pagesAdded ) { connect( dialog, SIGNAL(settingsChanged(QString)), this, SLOT(slotGeneratorConfigChanged(QString)) ); } } QVector DocumentPrivate::configurableGenerators() { const QVector available = availableGenerators(); QVector result; for (const KPluginMetaData& md : available) { if (md.rawData()[QStringLiteral("X-KDE-okularHasInternalSettings")].toBool()) { result << md; } } return result; } KPluginMetaData Document::generatorInfo() const { if (!d->m_generator) return KPluginMetaData(); auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName); Q_ASSERT(genIt != d->m_loadedGenerators.constEnd()); return genIt.value().metadata; } int Document::configurableGenerators() const { return DocumentPrivate::configurableGenerators().size(); } QStringList Document::supportedMimeTypes() const { // TODO: make it a static member of DocumentPrivate? QStringList result = d->m_supportedMimeTypes; if (result.isEmpty()) { const QVector available = DocumentPrivate::availableGenerators(); for (const KPluginMetaData& md : available) { result << md.mimeTypes(); } // Remove duplicate mimetypes represented by different names QMimeDatabase mimeDatabase; QSet uniqueMimetypes; for (const QString &mimeName : result) { uniqueMimetypes.insert(mimeDatabase.mimeTypeForName(mimeName)); } result.clear(); for (const QMimeType &mimeType : uniqueMimetypes) { result.append(mimeType.name()); } // Add the Okular archive mimetype result << QStringLiteral("application/vnd.kde.okular-archive"); // Sorting by mimetype name doesn't make a ton of sense, // but ensures that the list is ordered the same way every time qSort(result); d->m_supportedMimeTypes = result; } return result; } bool Document::canSwapBackingFile() const { if ( !d->m_generator ) return false; return d->m_generator->hasFeature( Generator::SwapBackingFile ); } bool Document::swapBackingFile( const QString &newFileName, const QUrl &url ) { if ( !d->m_generator ) return false; if ( !d->m_generator->hasFeature( Generator::SwapBackingFile ) ) return false; // Save metadata about the file we're about to close d->saveDocumentInfo(); d->clearAndWaitForRequests(); qCDebug(OkularCoreDebug) << "Swapping backing file to" << newFileName; QVector< Page * > newPagesVector; Generator::SwapBackingFileResult result = d->m_generator->swapBackingFile( newFileName, newPagesVector ); if (result != Generator::SwapBackingFileError) { QLinkedList< ObjectRect* > rectsToDelete; QLinkedList< Annotation* > annotationsToDelete; QSet< PagePrivate* > pagePrivatesToDelete; if (result == Generator::SwapBackingFileReloadInternalData) { // Here we need to replace everything that the old generator // had created with what the new one has without making it look like // we have actually closed and opened the file again // Simple sanity check if (newPagesVector.count() != d->m_pagesVector.count()) return false; // Update the undo stack contents for (int i = 0; i < d->m_undoStack->count(); ++i) { // Trust me on the const_cast ^_^ QUndoCommand *uc = const_cast( d->m_undoStack->command( i ) ); if (OkularUndoCommand *ouc = dynamic_cast( uc )) { const bool success = ouc->refreshInternalPageReferences( newPagesVector ); if ( !success ) { qWarning() << "Document::swapBackingFile: refreshInternalPageReferences failed" << ouc; return false; } } else { qWarning() << "Document::swapBackingFile: Unhandled undo command" << uc; return false; } } for (int i = 0; i < d->m_pagesVector.count(); ++i) { // switch the PagePrivate* from newPage to oldPage // this way everyone still holding Page* doesn't get // disturbed by it Page *oldPage = d->m_pagesVector[i]; Page *newPage = newPagesVector[i]; newPage->d->adoptGeneratedContents(oldPage->d); pagePrivatesToDelete << oldPage->d; oldPage->d = newPage->d; oldPage->d->m_page = oldPage; oldPage->d->m_doc = d; newPage->d = nullptr; annotationsToDelete << oldPage->m_annotations; rectsToDelete << oldPage->m_rects; oldPage->m_annotations = newPage->m_annotations; oldPage->m_rects = newPage->m_rects; } qDeleteAll( newPagesVector ); } d->m_url = url; d->m_docFileName = newFileName; d->updateMetadataXmlNameAndDocSize(); d->m_bookmarkManager->setUrl( d->m_url ); d->m_documentInfo = DocumentInfo(); d->m_documentInfoAskedKeys.clear(); if ( d->m_synctex_scanner ) { synctex_scanner_free( d->m_synctex_scanner ); d->m_synctex_scanner = synctex_scanner_new_with_output_file( QFile::encodeName( newFileName ).constData(), nullptr, 1); if ( !d->m_synctex_scanner && QFile::exists(newFileName + QLatin1String( "sync" ) ) ) { d->loadSyncFile(newFileName); } } foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::UrlChanged ) ); qDeleteAll( annotationsToDelete ); qDeleteAll( rectsToDelete ); qDeleteAll( pagePrivatesToDelete ); return true; } else { return false; } } bool Document::swapBackingFileArchive( const QString &newFileName, const QUrl &url ) { qCDebug(OkularCoreDebug) << "Swapping backing archive to" << newFileName; ArchiveData *newArchive = DocumentPrivate::unpackDocumentArchive( newFileName ); if ( !newArchive ) return false; const QString tempFileName = newArchive->document.fileName(); const bool success = swapBackingFile( tempFileName, url ); if ( success ) { delete d->m_archiveData; d->m_archiveData = newArchive; } return success; } void Document::setHistoryClean( bool clean ) { if ( clean ) d->m_undoStack->setClean(); // Since we only use the resetClean // in some cases and we're past the dependency freeze // if you happen to compile with an old Qt you will miss // some extra nicety when saving an okular file with annotations to png file // it's quite corner case compared to how important the whole save feature // is so you'll have to live without it #if QT_VERSION > QT_VERSION_CHECK(5, 8, 0) else d->m_undoStack->resetClean(); #endif } bool Document::canSaveChanges() const { if ( !d->m_generator ) return false; Q_ASSERT( !d->m_generatorName.isEmpty() ); QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.end() ); SaveInterface* saveIface = d->generatorSave( genIt.value() ); if ( !saveIface ) return false; return saveIface->supportsOption( SaveInterface::SaveChanges ); } bool Document::canSaveChanges( SaveCapability cap ) const { switch ( cap ) { case SaveFormsCapability: /* Assume that if the generator supports saving, forms can be saved. * We have no means to actually query the generator at the moment * TODO: Add some method to query the generator in SaveInterface */ return canSaveChanges(); case SaveAnnotationsCapability: return d->canAddAnnotationsNatively(); } return false; } bool Document::saveChanges( const QString &fileName ) { QString errorText; return saveChanges( fileName, &errorText ); } bool Document::saveChanges( const QString &fileName, QString *errorText ) { if ( !d->m_generator || fileName.isEmpty() ) return false; Q_ASSERT( !d->m_generatorName.isEmpty() ); QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.end() ); SaveInterface* saveIface = d->generatorSave( genIt.value() ); if ( !saveIface || !saveIface->supportsOption( SaveInterface::SaveChanges ) ) return false; return saveIface->save( fileName, SaveInterface::SaveChanges, errorText ); } void Document::registerView( View *view ) { if ( !view ) return; Document *viewDoc = view->viewDocument(); if ( viewDoc ) { // check if already registered for this document if ( viewDoc == this ) return; viewDoc->unregisterView( view ); } d->m_views.insert( view ); view->d_func()->document = d; } void Document::unregisterView( View *view ) { if ( !view ) return; Document *viewDoc = view->viewDocument(); if ( !viewDoc || viewDoc != this ) return; view->d_func()->document = nullptr; d->m_views.remove( view ); } QByteArray Document::fontData(const FontInfo &font) const { QByteArray result; if (d->m_generator) { QMetaObject::invokeMethod(d->m_generator, "requestFontData", Qt::DirectConnection, Q_ARG(Okular::FontInfo, font), Q_ARG(QByteArray *, &result)); } return result; } ArchiveData *DocumentPrivate::unpackDocumentArchive( const QString &archivePath ) { QMimeDatabase db; const QMimeType mime = db.mimeTypeForFile( archivePath, QMimeDatabase::MatchExtension ); if ( !mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) ) return nullptr; KZip okularArchive( archivePath ); if ( !okularArchive.open( QIODevice::ReadOnly ) ) return nullptr; const KArchiveDirectory * mainDir = okularArchive.directory(); const KArchiveEntry * mainEntry = mainDir->entry( QStringLiteral("content.xml") ); if ( !mainEntry || !mainEntry->isFile() ) return nullptr; std::unique_ptr< QIODevice > mainEntryDevice( static_cast< const KZipFileEntry * >( mainEntry )->createDevice() ); QDomDocument doc; if ( !doc.setContent( mainEntryDevice.get() ) ) return nullptr; mainEntryDevice.reset(); QDomElement root = doc.documentElement(); if ( root.tagName() != QLatin1String("OkularArchive") ) return nullptr; QString documentFileName; QString metadataFileName; QDomElement el = root.firstChild().toElement(); for ( ; !el.isNull(); el = el.nextSibling().toElement() ) { if ( el.tagName() == QLatin1String("Files") ) { QDomElement fileEl = el.firstChild().toElement(); for ( ; !fileEl.isNull(); fileEl = fileEl.nextSibling().toElement() ) { if ( fileEl.tagName() == QLatin1String("DocumentFileName") ) documentFileName = fileEl.text(); else if ( fileEl.tagName() == QLatin1String("MetadataFileName") ) metadataFileName = fileEl.text(); } } } if ( documentFileName.isEmpty() ) return nullptr; const KArchiveEntry * docEntry = mainDir->entry( documentFileName ); if ( !docEntry || !docEntry->isFile() ) return nullptr; std::unique_ptr< ArchiveData > archiveData( new ArchiveData() ); const int dotPos = documentFileName.indexOf( QLatin1Char('.') ); if ( dotPos != -1 ) archiveData->document.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX") + documentFileName.mid(dotPos)); if ( !archiveData->document.open() ) return nullptr; archiveData->originalFileName = documentFileName; { std::unique_ptr< QIODevice > docEntryDevice( static_cast< const KZipFileEntry * >( docEntry )->createDevice() ); copyQIODevice( docEntryDevice.get(), &archiveData->document ); archiveData->document.close(); } const KArchiveEntry * metadataEntry = mainDir->entry( metadataFileName ); if ( metadataEntry && metadataEntry->isFile() ) { std::unique_ptr< QIODevice > metadataEntryDevice( static_cast< const KZipFileEntry * >( metadataEntry )->createDevice() ); archiveData->metadataFile.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX.xml")); if ( archiveData->metadataFile.open() ) { copyQIODevice( metadataEntryDevice.get(), &archiveData->metadataFile ); archiveData->metadataFile.close(); } } return archiveData.release(); } Document::OpenResult Document::openDocumentArchive( const QString & docFile, const QUrl & url, const QString & password ) { d->m_archiveData = DocumentPrivate::unpackDocumentArchive( docFile ); if ( !d->m_archiveData ) return OpenError; const QString tempFileName = d->m_archiveData->document.fileName(); QMimeDatabase db; const QMimeType docMime = db.mimeTypeForFile( tempFileName, QMimeDatabase::MatchContent ); const OpenResult ret = openDocument( tempFileName, url, docMime, password ); if ( ret != OpenSuccess ) { delete d->m_archiveData; d->m_archiveData = nullptr; } return ret; } bool Document::saveDocumentArchive( const QString &fileName ) { if ( !d->m_generator ) return false; /* If we opened an archive, use the name of original file (eg foo.pdf) * instead of the archive's one (eg foo.okular) */ QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName(); if ( docFileName == QLatin1String( "-" ) ) return false; QString docPath = d->m_docFileName; const QFileInfo fi( docPath ); if ( fi.isSymLink() ) docPath = fi.symLinkTarget(); KZip okularArchive( fileName ); if ( !okularArchive.open( QIODevice::WriteOnly ) ) return false; const KUser user; #ifndef Q_OS_WIN const KUserGroup userGroup( user.groupId() ); #else const KUserGroup userGroup( QString( "" ) ); #endif QDomDocument contentDoc( QStringLiteral("OkularArchive") ); QDomProcessingInstruction xmlPi = contentDoc.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"utf-8\"" ) ); contentDoc.appendChild( xmlPi ); QDomElement root = contentDoc.createElement( QStringLiteral("OkularArchive") ); contentDoc.appendChild( root ); QDomElement filesNode = contentDoc.createElement( QStringLiteral("Files") ); root.appendChild( filesNode ); QDomElement fileNameNode = contentDoc.createElement( QStringLiteral("DocumentFileName") ); filesNode.appendChild( fileNameNode ); fileNameNode.appendChild( contentDoc.createTextNode( docFileName ) ); QDomElement metadataFileNameNode = contentDoc.createElement( QStringLiteral("MetadataFileName") ); filesNode.appendChild( metadataFileNameNode ); metadataFileNameNode.appendChild( contentDoc.createTextNode( QStringLiteral("metadata.xml") ) ); // If the generator can save annotations natively, do it QTemporaryFile modifiedFile; bool annotationsSavedNatively = false; bool formsSavedNatively = false; if ( d->canAddAnnotationsNatively() || canSaveChanges( SaveFormsCapability ) ) { if ( !modifiedFile.open() ) return false; const QString modifiedFileName = modifiedFile.fileName(); modifiedFile.close(); // We're only interested in the file name QString errorText; if ( saveChanges( modifiedFileName, &errorText ) ) { docPath = modifiedFileName; // Save this instead of the original file annotationsSavedNatively = d->canAddAnnotationsNatively(); formsSavedNatively = canSaveChanges( SaveFormsCapability ); } else { qCWarning(OkularCoreDebug) << "saveChanges failed: " << errorText; qCDebug(OkularCoreDebug) << "Falling back to saving a copy of the original file"; } } PageItems saveWhat = None; if ( !annotationsSavedNatively ) saveWhat |= AnnotationPageItems; if ( !formsSavedNatively ) saveWhat |= FormFieldPageItems; QTemporaryFile metadataFile; if ( !d->savePageDocumentInfo( &metadataFile, saveWhat ) ) return false; const QByteArray contentDocXml = contentDoc.toByteArray(); const mode_t perm = 0100644; okularArchive.writeFile( QStringLiteral("content.xml"), contentDocXml, perm, user.loginName(), userGroup.name() ); okularArchive.addLocalFile( docPath, docFileName ); okularArchive.addLocalFile( metadataFile.fileName(), QStringLiteral("metadata.xml") ); if ( !okularArchive.close() ) return false; return true; } bool Document::extractArchivedFile( const QString &destFileName ) { if ( !d->m_archiveData ) return false; // Remove existing file, if present (QFile::copy doesn't overwrite by itself) QFile::remove( destFileName ); return d->m_archiveData->document.copy( destFileName ); } QPrinter::Orientation Document::orientation() const { double width, height; int landscape, portrait; const Okular::Page *currentPage; // if some pages are landscape and others are not, the most common wins, as // QPrinter does not accept a per-page setting landscape = 0; portrait = 0; for (uint i = 0; i < pages(); i++) { currentPage = page(i); width = currentPage->width(); height = currentPage->height(); if (currentPage->orientation() == Okular::Rotation90 || currentPage->orientation() == Okular::Rotation270) qSwap(width, height); if (width > height) landscape++; else portrait++; } return (landscape > portrait) ? QPrinter::Landscape : QPrinter::Portrait; } void Document::setAnnotationEditingEnabled( bool enable ) { d->m_annotationEditingEnabled = enable; foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); } void Document::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const { if (d->m_generator) { d->m_generator->walletDataForFile( fileName, walletName, walletFolder, walletKey ); } else if (d->m_walletGenerator) { d->m_walletGenerator->walletDataForFile( fileName, walletName, walletFolder, walletKey ); } } bool Document::isDocdataMigrationNeeded() const { return d->m_docdataMigrationNeeded; } void Document::docdataMigrationDone() { if (d->m_docdataMigrationNeeded) { d->m_docdataMigrationNeeded = false; foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); } } QAbstractItemModel * Document::layersModel() const { return d->m_generator ? d->m_generator->layersModel() : nullptr; } void DocumentPrivate::requestDone( PixmapRequest * req ) { if ( !req ) return; if ( !m_generator || m_closingLoop ) { m_pixmapRequestsMutex.lock(); m_executingPixmapRequests.removeAll( req ); m_pixmapRequestsMutex.unlock(); delete req; if ( m_closingLoop ) m_closingLoop->exit(); return; } #ifndef NDEBUG if ( !m_generator->canGeneratePixmap() ) qCDebug(OkularCoreDebug) << "requestDone with generator not in READY state."; #endif if ( !req->shouldAbortRender() ) { // [MEM] 1.1 find and remove a previous entry for the same page and id QLinkedList< AllocatedPixmap * >::iterator aIt = m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator aEnd = m_allocatedPixmaps.end(); for ( ; aIt != aEnd; ++aIt ) if ( (*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer() ) { AllocatedPixmap * p = *aIt; m_allocatedPixmaps.erase( aIt ); m_allocatedPixmapsTotalMemory -= p->memory; delete p; break; } DocumentObserver *observer = req->observer(); if ( m_observers.contains(observer) ) { // [MEM] 1.2 append memory allocation descriptor to the FIFO qulonglong memoryBytes = 0; const TilesManager *tm = req->d->tilesManager(); if ( tm ) memoryBytes = tm->totalMemory(); else memoryBytes = 4 * req->width() * req->height(); AllocatedPixmap * memoryPage = new AllocatedPixmap( req->observer(), req->pageNumber(), memoryBytes ); m_allocatedPixmaps.append( memoryPage ); m_allocatedPixmapsTotalMemory += memoryBytes; // 2. notify an observer that its pixmap changed observer->notifyPageChanged( req->pageNumber(), DocumentObserver::Pixmap ); } #ifndef NDEBUG else qCWarning(OkularCoreDebug) << "Receiving a done request for the defunct observer" << observer; #endif } // 3. delete request m_pixmapRequestsMutex.lock(); m_executingPixmapRequests.removeAll( req ); m_pixmapRequestsMutex.unlock(); delete req; // 4. start a new generation if some is pending m_pixmapRequestsMutex.lock(); bool hasPixmaps = !m_pixmapRequestsStack.isEmpty(); m_pixmapRequestsMutex.unlock(); if ( hasPixmaps ) sendGeneratorPixmapRequest(); } void DocumentPrivate::setPageBoundingBox( int page, const NormalizedRect& boundingBox ) { Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; if ( kp->boundingBox() == boundingBox ) return; kp->setBoundingBox( boundingBox ); // notify observers about the change foreachObserverD( notifyPageChanged( page, DocumentObserver::BoundingBox ) ); // TODO: For generators that generate the bbox by pixmap scanning, if the first generated pixmap is very small, the bounding box will forever be inaccurate. // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away. // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker. // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off). } void DocumentPrivate::calculateMaxTextPages() { int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB switch (SettingsCore::memoryLevel()) { case SettingsCore::EnumMemoryLevel::Low: m_maxAllocatedTextPages = multipliers * 2; break; case SettingsCore::EnumMemoryLevel::Normal: m_maxAllocatedTextPages = multipliers * 50; break; case SettingsCore::EnumMemoryLevel::Aggressive: m_maxAllocatedTextPages = multipliers * 250; break; case SettingsCore::EnumMemoryLevel::Greedy: m_maxAllocatedTextPages = multipliers * 1250; break; } } void DocumentPrivate::textGenerationDone( Page *page ) { if ( !m_pageController ) return; // 1. If we reached the cache limit, delete the first text page from the fifo if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) { int pageToKick = m_allocatedTextPagesFifo.takeFirst(); if (pageToKick != page->number()) // this should never happen but better be safe than sorry { m_pagesVector.at(pageToKick)->setTextPage( nullptr ); // deletes the textpage } } // 2. Add the page to the fifo of generated text pages m_allocatedTextPagesFifo.append( page->number() ); } void Document::setRotation( int r ) { d->setRotationInternal( r, true ); } void DocumentPrivate::setRotationInternal( int r, bool notify ) { Rotation rotation = (Rotation)r; if ( !m_generator || ( m_rotation == rotation ) ) return; // tell the pages to rotate QVector< Okular::Page * >::const_iterator pIt = m_pagesVector.constBegin(); QVector< Okular::Page * >::const_iterator pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->rotateAt( rotation ); if ( notify ) { // notify the generator that the current rotation has changed m_generator->rotationChanged( rotation, m_rotation ); } // set the new rotation m_rotation = rotation; if ( notify ) { foreachObserverD( notifySetup( m_pagesVector, DocumentObserver::NewLayoutForPages ) ); foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations ) ); } qCDebug(OkularCoreDebug) << "Rotated:" << r; } void Document::setPageSize( const PageSize &size ) { if ( !d->m_generator || !d->m_generator->hasFeature( Generator::PageSizes ) ) return; if ( d->m_pageSizes.isEmpty() ) d->m_pageSizes = d->m_generator->pageSizes(); int sizeid = d->m_pageSizes.indexOf( size ); if ( sizeid == -1 ) return; // tell the pages to change size QVector< Okular::Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); QVector< Okular::Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->changeSize( size ); // clear 'memory allocation' descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); d->m_allocatedPixmapsTotalMemory = 0; // notify the generator that the current page size has changed d->m_generator->pageSizeChanged( size, d->m_pageSize ); // set the new page size d->m_pageSize = size; foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::NewLayoutForPages ) ); foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights ) ); qCDebug(OkularCoreDebug) << "New PageSize id:" << sizeid; } /** DocumentViewport **/ DocumentViewport::DocumentViewport( int n ) : pageNumber( n ) { // default settings rePos.enabled = false; rePos.normalizedX = 0.5; rePos.normalizedY = 0.0; rePos.pos = Center; autoFit.enabled = false; autoFit.width = false; autoFit.height = false; } DocumentViewport::DocumentViewport( const QString & xmlDesc ) : pageNumber( -1 ) { // default settings (maybe overridden below) rePos.enabled = false; rePos.normalizedX = 0.5; rePos.normalizedY = 0.0; rePos.pos = Center; autoFit.enabled = false; autoFit.width = false; autoFit.height = false; // check for string presence if ( xmlDesc.isEmpty() ) return; // decode the string bool ok; int field = 0; QString token = xmlDesc.section( QLatin1Char(';'), field, field ); while ( !token.isEmpty() ) { // decode the current token if ( field == 0 ) { pageNumber = token.toInt( &ok ); if ( !ok ) return; } else if ( token.startsWith( QLatin1String("C1") ) ) { rePos.enabled = true; rePos.normalizedX = token.section( QLatin1Char(':'), 1, 1 ).toDouble(); rePos.normalizedY = token.section( QLatin1Char(':'), 2, 2 ).toDouble(); rePos.pos = Center; } else if ( token.startsWith( QLatin1String("C2") ) ) { rePos.enabled = true; rePos.normalizedX = token.section( QLatin1Char(':'), 1, 1 ).toDouble(); rePos.normalizedY = token.section( QLatin1Char(':'), 2, 2 ).toDouble(); if (token.section( QLatin1Char(':'), 3, 3 ).toInt() == 1) rePos.pos = Center; else rePos.pos = TopLeft; } else if ( token.startsWith( QLatin1String("AF1") ) ) { autoFit.enabled = true; autoFit.width = token.section( QLatin1Char(':'), 1, 1 ) == QLatin1String("T"); autoFit.height = token.section( QLatin1Char(':'), 2, 2 ) == QLatin1String("T"); } // proceed tokenizing string field++; token = xmlDesc.section( QLatin1Char(';'), field, field ); } } QString DocumentViewport::toString() const { // start string with page number QString s = QString::number( pageNumber ); // if has center coordinates, save them on string if ( rePos.enabled ) s += QStringLiteral( ";C2:" ) + QString::number( rePos.normalizedX ) + QLatin1Char(':') + QString::number( rePos.normalizedY ) + QLatin1Char(':') + QString::number( rePos.pos ); // if has autofit enabled, save its state on string if ( autoFit.enabled ) s += QStringLiteral( ";AF1:" ) + (autoFit.width ? QLatin1Char('T') : QLatin1Char('F')) + QLatin1Char(':') + (autoFit.height ? QLatin1Char('T') : QLatin1Char('F')); return s; } bool DocumentViewport::isValid() const { return pageNumber >= 0; } bool DocumentViewport::operator==( const DocumentViewport & vp ) const { bool equal = ( pageNumber == vp.pageNumber ) && ( rePos.enabled == vp.rePos.enabled ) && ( autoFit.enabled == vp.autoFit.enabled ); if ( !equal ) return false; if ( rePos.enabled && (( rePos.normalizedX != vp.rePos.normalizedX) || ( rePos.normalizedY != vp.rePos.normalizedY ) || rePos.pos != vp.rePos.pos) ) return false; if ( autoFit.enabled && (( autoFit.width != vp.autoFit.width ) || ( autoFit.height != vp.autoFit.height )) ) return false; return true; } bool DocumentViewport::operator<( const DocumentViewport & vp ) const { // TODO: Check autoFit and Position if ( pageNumber != vp.pageNumber ) return pageNumber < vp.pageNumber; if ( !rePos.enabled && vp.rePos.enabled ) return true; if ( !vp.rePos.enabled ) return false; if ( rePos.normalizedY != vp.rePos.normalizedY ) return rePos.normalizedY < vp.rePos.normalizedY; return rePos.normalizedX < vp.rePos.normalizedX; } /** DocumentInfo **/ DocumentInfo::DocumentInfo() : d(new DocumentInfoPrivate()) { } DocumentInfo::DocumentInfo(const DocumentInfo &info) : d(new DocumentInfoPrivate()) { *this = info; } DocumentInfo& DocumentInfo::operator=(const DocumentInfo &info) { d->values = info.d->values; d->titles = info.d->titles; return *this; } DocumentInfo::~DocumentInfo() { delete d; } void DocumentInfo::set( const QString &key, const QString &value, const QString &title ) { d->values[ key ] = value; d->titles[ key ] = title; } void DocumentInfo::set( Key key, const QString &value ) { d->values[ getKeyString( key ) ] = value; } QStringList DocumentInfo::keys() const { return d->values.keys(); } QString DocumentInfo::get( Key key ) const { return get( getKeyString( key ) ); } QString DocumentInfo::get( const QString &key ) const { return d->values[ key ]; } QString DocumentInfo::getKeyString( Key key ) //const { switch ( key ) { case Title: return QStringLiteral("title"); break; case Subject: return QStringLiteral("subject"); break; case Description: return QStringLiteral("description"); break; case Author: return QStringLiteral("author"); break; case Creator: return QStringLiteral("creator"); break; case Producer: return QStringLiteral("producer"); break; case Copyright: return QStringLiteral("copyright"); break; case Pages: return QStringLiteral("pages"); break; case CreationDate: return QStringLiteral("creationDate"); break; case ModificationDate: return QStringLiteral("modificationDate"); break; case MimeType: return QStringLiteral("mimeType"); break; case Category: return QStringLiteral("category"); break; case Keywords: return QStringLiteral("keywords"); break; case FilePath: return QStringLiteral("filePath"); break; case DocumentSize: return QStringLiteral("documentSize"); break; case PagesSize: return QStringLiteral("pageSize"); break; default: qCWarning(OkularCoreDebug) << "Unknown" << key; return QString(); break; } } DocumentInfo::Key DocumentInfo::getKeyFromString( const QString &key ) //const { if (key == QLatin1String("title")) return Title; else if (key == QLatin1String("subject")) return Subject; else if (key == QLatin1String("description")) return Description; else if (key == QLatin1String("author")) return Author; else if (key == QLatin1String("creator")) return Creator; else if (key == QLatin1String("producer")) return Producer; else if (key == QLatin1String("copyright")) return Copyright; else if (key == QLatin1String("pages")) return Pages; else if (key == QLatin1String("creationDate")) return CreationDate; else if (key == QLatin1String("modificationDate")) return ModificationDate; else if (key == QLatin1String("mimeType")) return MimeType; else if (key == QLatin1String("category")) return Category; else if (key == QLatin1String("keywords")) return Keywords; else if (key == QLatin1String("filePath")) return FilePath; else if (key == QLatin1String("documentSize")) return DocumentSize; else if (key == QLatin1String("pageSize")) return PagesSize; else return Invalid; } QString DocumentInfo::getKeyTitle( Key key ) //const { switch ( key ) { case Title: return i18n( "Title" ); break; case Subject: return i18n( "Subject" ); break; case Description: return i18n( "Description" ); break; case Author: return i18n( "Author" ); break; case Creator: return i18n( "Creator" ); break; case Producer: return i18n( "Producer" ); break; case Copyright: return i18n( "Copyright" ); break; case Pages: return i18n( "Pages" ); break; case CreationDate: return i18n( "Created" ); break; case ModificationDate: return i18n( "Modified" ); break; case MimeType: return i18n( "Mime Type" ); break; case Category: return i18n( "Category" ); break; case Keywords: return i18n( "Keywords" ); break; case FilePath: return i18n( "File Path" ); break; case DocumentSize: return i18n( "File Size" ); break; case PagesSize: return i18n("Page Size"); break; default: return QString(); break; } } QString DocumentInfo::getKeyTitle( const QString &key ) const { QString title = getKeyTitle ( getKeyFromString( key ) ); if ( title.isEmpty() ) title = d->titles[ key ]; return title; } /** DocumentSynopsis **/ DocumentSynopsis::DocumentSynopsis() : QDomDocument( QStringLiteral("DocumentSynopsis") ) { // void implementation, only subclassed for naming } DocumentSynopsis::DocumentSynopsis( const QDomDocument &document ) : QDomDocument( document ) { } /** EmbeddedFile **/ EmbeddedFile::EmbeddedFile() { } EmbeddedFile::~EmbeddedFile() { } VisiblePageRect::VisiblePageRect( int page, const NormalizedRect &rectangle ) : pageNumber( page ), rect( rectangle ) { } #undef foreachObserver #undef foreachObserverD #include "moc_document.cpp" /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/document.h b/core/document.h index 3354e55ae..7651291b5 100644 --- a/core/document.h +++ b/core/document.h @@ -1,1430 +1,1430 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2008 by Albert Astals Cid * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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_DOCUMENT_H_ #define _OKULAR_DOCUMENT_H_ #include "okularcore_export.h" #include "area.h" #include "global.h" #include "pagesize.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include class QPrintDialog; class KBookmark; class KConfigDialog; class KPluginMetaData; class KXMLGUIClient; class DocumentItem; class QAbstractItemModel; namespace Okular { class Annotation; class BookmarkManager; class DocumentInfoPrivate; class DocumentObserver; class DocumentPrivate; class DocumentSynopsis; class DocumentViewport; class EmbeddedFile; class ExportFormat; class FontInfo; class FormField; class FormFieldText; class FormFieldButton; class FormFieldChoice; class Generator; class Action; class MovieAction; class Page; class PixmapRequest; class RenditionAction; class SourceReference; class View; class VisiblePageRect; /** IDs for seaches. Globally defined here. **/ #define PART_SEARCH_ID 1 #define PAGEVIEW_SEARCH_ID 2 #define SW_SEARCH_ID 3 #define PRESENTATION_SEARCH_ID 4 /** * The DocumentInfo structure can be filled in by generators to display * metadata about the currently opened file. */ class OKULARCORE_EXPORT DocumentInfo { friend class Document; public: /** * The list of predefined keys. */ enum Key { Title, ///< The title of the document Subject, ///< The subject of the document Description, ///< The description of the document Author, ///< The author of the document Creator, ///< The creator of the document (this can be different from the author) Producer, ///< The producer of the document (e.g. some software) Copyright, ///< The copyright of the document Pages, ///< The number of pages of the document CreationDate, ///< The date of creation of the document ModificationDate, ///< The date of last modification of the document MimeType, ///< The mime type of the document Category, ///< The category of the document Keywords, ///< The keywords which describe the content of the document FilePath, ///< The path of the file @since 0.10 (KDE 4.4) DocumentSize, ///< The size of the document @since 0.10 (KDE 4.4) PagesSize, ///< The size of the pages (if all pages have the same size) @since 0.10 (KDE 4.4) CustomKeys, ///< All the custom keys the generator supports @since 0.21 Invalid ///< An invalid key @since 0.21. It will always be the last element in the enum }; /** * Creates a new document info. */ DocumentInfo(); DocumentInfo(const DocumentInfo &info); DocumentInfo& operator=( const DocumentInfo& ); ~DocumentInfo(); /** * Returns all the keys present in this DocumentInfo * * @since 0.21 */ QStringList keys() const; /** * Returns the value for a given key or an null string when the * key doesn't exist. */ QString get( Key key ) const; /** * Returns the value for a given key or an null string when the * key doesn't exist. */ QString get( const QString &key ) const; /** * Sets a value for a custom key. The title should be an i18n'ed * string, since it's used in the document information dialog. */ void set( const QString &key, const QString &value, const QString &title = QString() ); /** * Sets a value for a special key. The title should be an i18n'ed * string, since it's used in the document information dialog. */ void set( Key key, const QString &value ); /** * Returns the user visible string for the given key * Takes into account keys added by the set() that takes a QString * * @since 0.21 */ QString getKeyTitle( const QString &key ) const; /** * Returns the internal string for the given key * @since 0.10 (KDE 4.4) */ static QString getKeyString( Key key ); /** * Returns the user visible string for the given key * @since 0.10 (KDE 4.4) */ static QString getKeyTitle( Key key ); /** * Returns the Key from a string key * @since 0.21 */ static Key getKeyFromString( const QString &key ); private: DocumentInfoPrivate *d; }; /** * @short The Document. Heart of everything. Actions take place here. * * The Document is the main object in Okular. All views query the Document to * get data/properties or even for accessing pages (in a 'const' way). * * It is designed to keep it detached from the document type (pdf, ps, you * name it..) so whenever you want to get some data, it asks its internal * generators to do the job and return results in a format-indepedent way. * * Apart from the generator (the currently running one) the document stores * all the Pages ('Page' class) of the current document in a vector and * notifies all the registered DocumentObservers when some content changes. * * For a better understanding of hierarchies @see README.internals.png * @see DocumentObserver, Page */ class OKULARCORE_EXPORT Document : public QObject { Q_OBJECT public: /** * Creates a new document with the given @p widget as widget to relay GUI things (messageboxes, ...). */ explicit Document( QWidget *widget ); /** * Destroys the document. */ ~Document(); /** * Describes the result of an open document operation. * @since 0.20 (KDE 4.14) */ enum OpenResult { OpenSuccess, //< The document was opened successfully OpenError, //< The document failed to open OpenNeedsPassword //< The document needs a password to be opened or the one provided is not the correct }; /** * Opens the document. * @since 0.20 (KDE 4.14) */ OpenResult openDocument( const QString & docFile, const QUrl & url, const QMimeType &mime, const QString &password = QString() ); /** * Closes the document. */ void closeDocument(); /** * Registers a new @p observer for the document. */ void addObserver( DocumentObserver *observer ); /** * Unregisters the given @p observer for the document. */ void removeObserver( DocumentObserver *observer ); /** * Reparses and applies the configuration. */ void reparseConfig(); /** * Returns whether the document is currently opened. */ bool isOpened() const; /** * Returns the meta data of the document. */ DocumentInfo documentInfo() const; /** * Returns the asked set of meta data of the document. The result may contain more * metadata than the one asked for. */ DocumentInfo documentInfo( const QSet &keys ) const; /** * Returns the table of content of the document or 0 if no * table of content is available. */ const DocumentSynopsis * documentSynopsis() const; /** * Starts the reading of the information about the fonts in the * document, if available. * * The results as well the end of the reading is notified using the * signals gotFont(), fontReadingProgress() and fontReadingEnded() */ void startFontReading(); /** * Force the termination of the reading of the information about the * fonts in the document, if running. */ void stopFontReading(); /** * Whether the current document can provide information about the * fonts used in it. */ bool canProvideFontInformation() const; /** * Returns the list of embedded files or 0 if no embedded files * are available. */ const QList *embeddedFiles() const; /** * Returns the page object for the given page @p number or 0 * if the number is out of range. */ const Page * page( int number ) const; /** * Returns the current viewport of the document. */ const DocumentViewport & viewport() const; /** * Sets the list of visible page rectangles. * @see VisiblePageRect */ void setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver = nullptr ); /** * Returns the list of visible page rectangles. */ const QVector< VisiblePageRect * > & visiblePageRects() const; /** * Returns the number of the current page. */ uint currentPage() const; /** * Returns the number of pages of the document. */ uint pages() const; /** * Returns the url of the currently opened document. */ QUrl currentDocument() const; /** * Returns whether the given @p action is allowed in the document. * @see @ref Permission */ bool isAllowed( Permission action ) const; /** * Returns whether the document supports searching. */ bool supportsSearching() const; /** * Returns whether the document supports the listing of page sizes. */ bool supportsPageSizes() const; /** * Returns whether the current document supports tiles * * @since 0.16 (KDE 4.10) */ bool supportsTiles() const; /** * Returns the list of supported page sizes or an empty list if this * feature is not available. * @see supportsPageSizes() */ PageSize::List pageSizes() const; /** * Returns whether the document supports the export to ASCII text. */ bool canExportToText() const; /** * Exports the document as ASCII text and saves it under @p fileName. */ bool exportToText( const QString& fileName ) const; /** * Returns the list of supported export formats. * @see ExportFormat */ QList exportFormats() const; /** * Exports the document in the given @p format and saves it under @p fileName. */ bool exportTo( const QString& fileName, const ExportFormat& format ) const; /** * Returns whether the document history is at the begin. */ bool historyAtBegin() const; /** * Returns whether the document history is at the end. */ bool historyAtEnd() const; /** * Returns the meta data for the given @p key and @p option or an empty variant * if the key doesn't exists. */ QVariant metaData( const QString & key, const QVariant & option = QVariant() ) const; /** * Returns the current rotation of the document. */ Rotation rotation() const; /** * If all pages have the same size this method returns it, if the page sizes * differ an empty size object is returned. */ QSizeF allPagesSize() const; /** * Returns the size string for the given @p page or an empty string * if the page is out of range. */ QString pageSizeString( int page ) const; /** * Returns the gui client of the generator, if it provides one. */ KXMLGUIClient* guiClient(); /** * Sets the current document viewport to the given @p page. * * @param excludeObserver The observer ids which shouldn't be effected by this change. * @param smoothMove Whether the move shall be animated smoothly. */ void setViewportPage( int page, DocumentObserver *excludeObserver = nullptr, bool smoothMove = false ); /** * Sets the current document viewport to the given @p viewport. * * @param excludeObserver The observer which shouldn't be effected by this change. * @param smoothMove Whether the move shall be animated smoothly. */ void setViewport( const DocumentViewport &viewport, DocumentObserver *excludeObserver = nullptr, bool smoothMove = false ); /** * Sets the current document viewport to the next viewport in the * viewport history. */ void setPrevViewport(); /** * Sets the current document viewport to the previous viewport in the * viewport history. */ void setNextViewport(); /** * Sets the next @p viewport in the viewport history. */ void setNextDocumentViewport( const DocumentViewport &viewport ); /** * Sets the next @p namedDestination in the viewport history. * * @since 0.9 (KDE 4.3) */ void setNextDocumentDestination( const QString &namedDestination ); /** * Sets the zoom for the current document. */ void setZoom( int factor, DocumentObserver *excludeObserver = nullptr ); /** * Describes the possible options for the pixmap requests. */ enum PixmapRequestFlag { NoOption = 0, ///< No options RemoveAllPrevious = 1 ///< Remove all the previous requests, even for non requested page pixmaps }; Q_DECLARE_FLAGS( PixmapRequestFlags, PixmapRequestFlag ) /** * Sends @p requests for pixmap generation. * * The same as requestPixmaps( requests, RemoveAllPrevious ); */ void requestPixmaps( const QLinkedList &requests ); /** * Sends @p requests for pixmap generation. * * @param reqOptions the options for the request * * @since 0.7 (KDE 4.1) */ void requestPixmaps( const QLinkedList &requests, PixmapRequestFlags reqOptions ); /** * Sends a request for text page generation for the given page @p number. */ void requestTextPage( uint number ); /** * Adds a new @p annotation to the given @p page. */ void addPageAnnotation( int page, Annotation *annotation ); /** * Tests if the @p annotation can be modified * * @since 0.15 (KDE 4.9) */ bool canModifyPageAnnotation( const Annotation * annotation ) const; /** * Prepares to modify the properties of the given @p annotation. * Must be called before the annotation's properties are modified * * @since 0.17 (KDE 4.11) */ void prepareToModifyAnnotationProperties( Annotation * annotation ); /** * Modifies the given @p annotation on the given @p page. * Must be preceded by a call to prepareToModifyAnnotationProperties before * the annotation's properties are modified * * @since 0.17 (KDE 4.11) */ void modifyPageAnnotationProperties( int page, Annotation * annotation ); /** * Translates the position of the given @p annotation on the given @p page by a distance @p delta in normalized coordinates. * * Consecutive translations applied to the same @p annotation are merged together on the undo stack if the * BeingMoved flag is set on the @P annotation. * * @since 0.17 (KDE 4.11) */ void translatePageAnnotation( int page, Annotation *annotation, const Okular::NormalizedPoint & delta ); /** * Adjusts the position of the top-left and bottom-right corners of given @p annotation on the given @p page. * * Can be used to implement resize functionality. * @p delta1 in normalized coordinates is added to top-left. * @p delta2 in normalized coordinates is added to bottom-right. * * Consecutive adjustments applied to the same @p annotation are merged together on the undo stack if the * BeingResized flag is set on the @P annotation. * * @since 1.1.0 */ void adjustPageAnnotation( int page, Annotation * annotation, const Okular::NormalizedPoint & delta1, const Okular::NormalizedPoint & delta2 ); /** * Edits the plain text contents of the given @p annotation on the given @p page. * * The contents are set to @p newContents with cursor position @p newCursorPos. * The previous cursor position @p prevCursorPos and previous anchor position @p prevAnchorPos * must also be supplied so that they can be restored if the edit action is undone. * * The Annotation's internal contents should not be modified prior to calling this method. * * @since 0.17 (KDE 4.11) */ void editPageAnnotationContents( int page, Annotation* annotation, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ); /** * Tests if the @p annotation can be removed * * @since 0.15 (KDE 4.9) */ bool canRemovePageAnnotation( const Annotation * annotation ) const; /** * Removes the given @p annotation from the given @p page. */ void removePageAnnotation( int page, Annotation *annotation ); /** * Removes the given @p annotations from the given @p page. */ void removePageAnnotations( int page, const QList &annotations ); /** * Sets the text selection for the given @p page. * * @param rect The rectangle of the selection. * @param color The color of the selection. */ void setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color ); /** * Returns true if there is an undo command available; otherwise returns false. * @since 0.17 (KDE 4.11) */ bool canUndo() const; /** * Returns true if there is a redo command available; otherwise returns false. * @since 0.17 (KDE 4.11) */ bool canRedo() const; /** * Describes the possible search types. */ enum SearchType { NextMatch, ///< Search next match PreviousMatch, ///< Search previous match AllDocument, ///< Search complete document GoogleAll, ///< Search complete document (all words in google style) GoogleAny ///< Search complete document (any words in google style) }; /** * Describes how search ended */ // TODO remove EndOfDocumentReached when we break API enum SearchStatus { MatchFound, ///< Any match was found NoMatchFound, ///< No match was found SearchCancelled, ///< The search was cancelled EndOfDocumentReached ///< This is not ever emitted since 1.3. The end of document was reached without any match @since 0.20 (KDE 4.14) }; /** * Searches the given @p text in the document. * * @param searchID The unique id for this search request. * @param fromStart Whether the search should be started at begin of the document. * @param caseSensitivity Whether the search is case sensitive. * @param type The type of the search. @ref SearchType * @param moveViewport Whether the viewport shall be moved to the position of the matches. * @param color The highlighting color of the matches. */ void searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor & color ); /** * Continues the search for the given @p searchID. */ void continueSearch( int searchID ); /** * Continues the search for the given @p searchID, optionally specifying * a new type for the search. * * @since 0.7 (KDE 4.1) */ void continueSearch( int searchID, SearchType type ); /** * Resets the search for the given @p searchID. */ void resetSearch( int searchID ); /** * Returns the bookmark manager of the document. */ BookmarkManager * bookmarkManager() const; /** * Processes the given @p action. */ void processAction( const Action *action ); /** * Returns a list of the bookmarked.pages */ QList bookmarkedPageList() const; /** * Returns the range of the bookmarked.pages */ QString bookmarkedPageRange() const; /** * Processes/Executes the given source @p reference. */ void processSourceReference( const SourceReference *reference ); /** * Returns whether the document can configure the printer itself. */ bool canConfigurePrinter() const; /** * What type of printing a document supports */ enum PrintingType { NoPrinting, ///< Printing Not Supported NativePrinting, ///< Native Cross-Platform Printing PostscriptPrinting ///< Postscript file printing }; /** * Returns what sort of printing the document supports: * Native, Postscript, None */ PrintingType printingSupport() const; /** * Returns whether the document supports printing to both PDF and PS files. */ bool supportsPrintToFile() const; /** * Prints the document to the given @p printer. */ bool print( QPrinter &printer ); /** * Returns the last print error in case print() failed * @since 0.11 (KDE 4.5) */ QString printError() const; /** * Returns a custom printer configuration page or 0 if no * custom printer configuration page is available. */ QWidget* printConfigurationWidget() const; /** * Fill the KConfigDialog @p dialog with the setting pages of the * generators. */ void fillConfigDialog( KConfigDialog * dialog ); /** * Returns the number of generators that have a configuration widget. */ int configurableGenerators() const; /** * Returns the list with the supported MIME types. */ QStringList supportedMimeTypes() const; /** * Returns the metadata associated with the generator. May be invalid. */ KPluginMetaData generatorInfo() const; /** * Returns whether the generator supports hot-swapping the current file * with another identical file * * @since 1.3 */ bool canSwapBackingFile() const; /** * Reload the document from a new location, without any visible effect * to the user. * * The new file must be identical to the current one or, if the document * has been modified (eg the user edited forms and annotations), the new * document must have these changes too. For example, you can call * saveChanges first to write changes to a file and then swapBackingFile * to switch to the new location. * * @since 1.3 */ bool swapBackingFile( const QString &newFileName, const QUrl &url ); /** * Same as swapBackingFile, but newFileName must be a .okular file. * * The new file must be identical to the current one or, if the document * has been modified (eg the user edited forms and annotations), the new * document must have these changes too. For example, you can call * saveDocumentArchive first to write changes to a file and then * swapBackingFileArchive to switch to the new location. * * @since 1.3 */ bool swapBackingFileArchive( const QString &newFileName, const QUrl &url ); /** * Sets the history to be clean * * @since 1.3 */ void setHistoryClean( bool clean ); /** * Saving capabilities. Their availability varies according to the * underlying generator and/or the document type. * * @see canSaveChanges (SaveCapability) * @since 0.15 (KDE 4.9) */ enum SaveCapability { SaveFormsCapability = 1, ///< Can save form changes SaveAnnotationsCapability = 2 ///< Can save annotation changes }; /** * Returns whether it's possible to save a given category of changes to * another document. * * @since 0.15 (KDE 4.9) */ bool canSaveChanges( SaveCapability cap ) const; /** * Returns whether the changes to the document (modified annotations, * values in form fields, etc) can be saved to another document. * * Equivalent to the logical OR of canSaveChanges(SaveCapability) for * each capability. * * @since 0.7 (KDE 4.1) */ bool canSaveChanges() const; /** * Save the document and the optional changes to it to the specified * @p fileName. * * @since 0.7 (KDE 4.1) */ bool saveChanges( const QString &fileName ); /** * Save the document and the optional changes to it to the specified * @p fileName and returns a @p errorText if fails. * * @since 0.10 (KDE 4.4) */ bool saveChanges( const QString &fileName, QString *errorText ); /** * Register the specified @p view for the current document. * * It is unregistered from the previous document, if any. * * @since 0.7 (KDE 4.1) */ void registerView( View *view ); /** * Unregister the specified @p view from the current document. * * @since 0.7 (KDE 4.1) */ void unregisterView( View *view ); /** * Gets the font data for the given font * * @since 0.8 (KDE 4.2) */ QByteArray fontData(const FontInfo &font) const; /** * Opens a document archive. * * @since 0.20 (KDE 4.14) */ OpenResult openDocumentArchive( const QString & docFile, const QUrl & url, const QString &password = QString() ); /** * Saves a document archive. * * @since 0.8 (KDE 4.2) */ bool saveDocumentArchive( const QString &fileName ); /** * Extract the document file from the current archive. * * @warning This function only works if the current file is a document archive * * @since 1.3 */ bool extractArchivedFile( const QString &destFileName ); /** * Asks the generator to dynamically generate a SourceReference for a given * page number and absolute X and Y position on this page. * * @attention Ownership of the returned SourceReference is transferred to the caller. * @note This method does not call processSourceReference( const SourceReference * ) * * @since 0.10 (KDE 4.4) */ const SourceReference * dynamicSourceReference( int pageNr, double absX, double absY ); /** * Returns the orientation of the document (for printing purposes). This * is used in the KPart to initialize the print dialog and in the * generators to check whether the document needs to be rotated or not. * * @since 0.14 (KDE 4.8) */ QPrinter::Orientation orientation() const; /** * Control annotation editing (creation, modification and removal), * which is enabled by default. * * @since 0.15 (KDE 4.9) */ void setAnnotationEditingEnabled( bool enable ); /** * Returns which wallet data to use to read/write the password for the given fileName * * @since 0.20 (KDE 4.14) */ void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const; /** * Since version 0.21, okular does not allow editing annotations and * form data if they are stored in the docdata directory (like older * okular versions did by default). * If this flag is set, then annotations and forms cannot be edited. * * @since 1.3 */ bool isDocdataMigrationNeeded() const; /** * Delete annotations and form data from the docdata folder. Call it if * isDocdataMigrationNeeded() was true and you've just saved them to an * external file. * * @since 1.3 */ void docdataMigrationDone(); /** * Returns the model for rendering layers (NULL if the document has no layers) * * @since 0.24 */ QAbstractItemModel * layersModel() const; public Q_SLOTS: /** * This slot is called whenever the user changes the @p rotation of * the document. */ void setRotation( int rotation ); /** * This slot is called whenever the user changes the page @p size * of the document. */ void setPageSize( const PageSize &size ); /** * Cancels the current search */ void cancelSearch(); /** * Undo last edit command * @since 0.17 (KDE 4.11) */ void undo(); /** * Redo last undone edit command * @since 0.17 (KDE 4.11) */ void redo(); /** * Edit the text contents of the specified @p form on page @p page to be @p newContents. * The new text cursor position (@p newCursorPos), previous text cursor position (@p prevCursorPos), * and previous cursor anchor position will be restored by the undo / redo commands. * @since 0.17 (KDE 4.11) */ void editFormText( int pageNumber, Okular::FormFieldText* form, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ); /** * Edit the selected list entries in @p form on page @p page to be @p newChoices. * @since 0.17 (KDE 4.11) */ void editFormList( int pageNumber, Okular::FormFieldChoice* form, const QList & newChoices ); /** * Set the active choice in the combo box @p form on page @p page to @p newText * The new cursor position (@p newCursorPos), previous cursor position * (@p prevCursorPos), and previous anchor position (@p prevAnchorPos) * will be restored by the undo / redo commands. * * @since 0.17 (KDE 4.11) */ void editFormCombo( int pageNumber, Okular::FormFieldChoice *form, const QString & newText, int newCursorPos, int prevCursorPos, int prevAnchorPos ); /** * Set the states of the group of form buttons @p formButtons on page @p page to @p newButtonStates. * The lists @p formButtons and @p newButtonStates should be the same length and true values * in @p newButtonStates indicate that the corresponding entry in @p formButtons should be enabled. */ void editFormButtons( int pageNumber, const QList< Okular::FormFieldButton* > & formButtons, const QList< bool > & newButtonStates ); /** * Reloads the pixmaps for whole document * * @since 0.24 */ void reloadDocument() const; Q_SIGNALS: /** * This signal is emitted whenever an action requests a * document close operation. */ void close(); /** * This signal is emitted whenever an action requests an * application quit operation. */ void quit(); /** * This signal is emitted whenever an action requests a * find operation. */ void linkFind(); /** * This signal is emitted whenever an action requests a * goto operation. */ void linkGoToPage(); /** * This signal is emitted whenever an action requests a * start presentation operation. */ void linkPresentation(); /** * This signal is emitted whenever an action requests an * end presentation operation. */ void linkEndPresentation(); /** * This signal is emitted whenever an action requests an * open url operation for the given document @p url. */ void openUrl( const QUrl &url ); /** * This signal is emitted whenever an error occurred. * * @param text The description of the error. * @param duration The time in milliseconds the message should be shown to the user. */ void error( const QString &text, int duration ); /** * This signal is emitted to signal a warning. * * @param text The description of the warning. * @param duration The time in milliseconds the message should be shown to the user. */ void warning( const QString &text, int duration ); /** * This signal is emitted to signal a notice. * * @param text The description of the notice. * @param duration The time in milliseconds the message should be shown to the user. */ void notice( const QString &text, int duration ); /** * Emitted when a new font is found during the reading of the fonts of * the document. */ void gotFont( const Okular::FontInfo& font ); /** * Reports the progress when reading the fonts in the document. * * \param page is the page that was just finished to scan for fonts */ void fontReadingProgress( int page ); /** * Reports that the reading of the fonts in the document is finished. */ void fontReadingEnded(); /** * Reports that the current search finished */ void searchFinished( int searchID, Okular::Document::SearchStatus endStatus ); /** * This signal is emitted whenever a source reference with the given parameters has been * activated. * * \param handled should be set to 'true' if a slot handles this source reference; the * default action to launch the configured editor will then not be performed * by the document * * @since 0.14 (KDE 4.8) */ void sourceReferenceActivated(const QString& absFileName, int line, int col, bool *handled); /** * This signal is emitted whenever an movie action is triggered and the UI should process it. */ void processMovieAction( const Okular::MovieAction *action ); /** * This signal is emmitted whenever the availability of the undo function changes * @since 0.17 (KDE 4.11) */ void canUndoChanged( bool undoAvailable ); /** * This signal is emmitted whenever the availability of the redo function changes * @since 0.17 (KDE 4.11) */ void canRedoChanged( bool redoAvailable ); /** * This signal is emmitted whenever the undo history is clean (i.e. the same status the last time it was saved) * @since 1.3 */ void undoHistoryCleanChanged( bool clean ); /** * This signal is emitted whenever an rendition action is triggered and the UI should process it. * * @since 0.16 (KDE 4.10) */ void processRenditionAction( const Okular::RenditionAction *action ); /** * This signal is emmitted whenever the contents of the given @p annotation are changed by an undo * or redo action. * * The new contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are * included * @since 0.17 (KDE 4.11) */ void annotationContentsChangedByUndoRedo( Okular::Annotation* annotation, const QString & contents, int cursorPos, int anchorPos ); /** * This signal is emmitted whenever the text contents of the given text @p form on the given @p page * are changed by an undo or redo action. * * The new text contents (@p contents), cursor position (@p cursorPos), and anchor position (@p anchorPos) are * included * @since 0.17 (KDE 4.11) */ void formTextChangedByUndoRedo( int page, Okular::FormFieldText* form, const QString & contents, int cursorPos, int anchorPos ); /** * This signal is emmitted whenever the selected @p choices for the given list @p form on the * given @p page are changed by an undo or redo action. * @since 0.17 (KDE 4.11) */ void formListChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QList< int > & choices ); /** * This signal is emmitted whenever the active @p text for the given combo @p form on the * given @p page is changed by an undo or redo action. * @since 0.17 (KDE 4.11) */ void formComboChangedByUndoRedo( int page, Okular::FormFieldChoice* form, const QString & text, int cursorPos, int anchorPos ); /** * This signal is emmitted whenever the state of the specified group of form buttons (@p formButtons) on the * given @p page is changed by an undo or redo action. * @since 0.17 (KDE 4.11) */ void formButtonsChangedByUndoRedo( int page, const QList< Okular::FormFieldButton* > & formButtons ); /** * This signal is emmitted whenever a FormField was changed programatically and the * according widget should be updated. * @since 1.4 */ void refreshFormWidget( Okular::FormField *field ); private: /// @cond PRIVATE friend class DocumentPrivate; friend class ::DocumentItem; friend class EditAnnotationContentsCommand; friend class EditFormTextCommand; friend class EditFormListCommand; friend class EditFormComboCommand; friend class EditFormButtonsCommand; /// @endcond DocumentPrivate *const d; Q_DISABLE_COPY( Document ) Q_PRIVATE_SLOT( d, void saveDocumentInfo() const ) Q_PRIVATE_SLOT( d, void slotTimedMemoryCheck() ) Q_PRIVATE_SLOT( d, void sendGeneratorPixmapRequest() ) Q_PRIVATE_SLOT( d, void rotationFinished( int page, Okular::Page *okularPage ) ) Q_PRIVATE_SLOT( d, void slotFontReadingProgress( int page ) ) Q_PRIVATE_SLOT( d, void fontReadingGotFont( const Okular::FontInfo& font ) ) Q_PRIVATE_SLOT( d, void slotGeneratorConfigChanged( const QString& ) ) Q_PRIVATE_SLOT( d, void refreshPixmaps( int ) ) Q_PRIVATE_SLOT( d, void _o_configChanged() ) // search thread simulators Q_PRIVATE_SLOT( d, void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) ) Q_PRIVATE_SLOT( d, void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) ) Q_PRIVATE_SLOT( d, void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) ) }; /** * @short A view on the document. * * The Viewport structure is the 'current view' over the document. Contained * data is broadcasted between observers to synchronize their viewports to get * the 'I scroll one view and others scroll too' views. */ class OKULARCORE_EXPORT DocumentViewport { public: /** * Creates a new viewport for the given page @p number. */ DocumentViewport( int number = -1 ); /** * Creates a new viewport from the given xml @p description. */ DocumentViewport( const QString &description ); /** * Returns the viewport as xml description. */ QString toString() const; /** * Returns whether the viewport is valid. */ bool isValid() const; /** * @internal */ bool operator==( const DocumentViewport &other ) const; bool operator<( const DocumentViewport &other ) const; /** * The number of the page nearest the center of the viewport. */ int pageNumber; /** * Describes the relative position of the viewport. */ enum Position { Center = 1, ///< Relative to the center of the page. TopLeft = 2 ///< Relative to the top left corner of the page. }; /** * If 'rePos.enabled == true' then this structure contains the * viewport center or top left depending on the value of pos. */ struct { bool enabled; double normalizedX; double normalizedY; Position pos; } rePos; /** * If 'autoFit.enabled == true' then the page must be autofitted in the viewport. */ struct { bool enabled; bool width; bool height; } autoFit; }; /** * @short A DOM tree that describes the Table of Contents. * * The Synopsis (TOC or Table Of Contents for friends) is represented via * a dom tree where each node has an internal name (displayed in the TOC) * and one or more attributes. * * In the tree the tag name is the 'screen' name of the entry. A tag can have * attributes. Here follows the list of tag attributes with meaning: * - Destination: A string description of the referred viewport * - DestinationName: A 'named reference' to the viewport that must be converted * using metaData( "NamedViewport", viewport_name ) * - ExternalFileName: A document to be opened, whose destination is specified * with Destination or DestinationName * - Open: a boolean saying whether its TOC branch is open or not (default: false) * - URL: a URL to be open as destination; if set, no other Destination* or * ExternalFileName entry is used */ class OKULARCORE_EXPORT DocumentSynopsis : public QDomDocument { public: /** * Creates a new document synopsis object. */ DocumentSynopsis(); /** * Creates a new document synopsis object with the given * @p document as parent node. */ DocumentSynopsis( const QDomDocument &document ); }; /** * @short An embedded file into the document. * * This class represents a sort of interface of an embedded file in a document. * * Generators \b must re-implement its members to give the all the information * about an embedded file, like its name, its description, the date of creation * and modification, and the real data of the file. */ class OKULARCORE_EXPORT EmbeddedFile { public: /** * Creates a new embedded file. */ EmbeddedFile(); /** * Destroys the embedded file. */ virtual ~EmbeddedFile(); /** * Returns the name of the file */ virtual QString name() const = 0; /** * Returns the description of the file, or an empty string if not * available */ virtual QString description() const = 0; /** * Returns the real data representing the file contents */ virtual QByteArray data() const = 0; /** * Returns the size (in bytes) of the file, if available, or -1 otherwise. * * @note this method should be a fast way to know the size of the file * with no need to extract all the data from it */ virtual int size() const = 0; /** * Returns the modification date of the file, or an invalid date * if not available */ virtual QDateTime modificationDate() const = 0; /** * Returns the creation date of the file, or an invalid date * if not available */ virtual QDateTime creationDate() const = 0; }; /** * @short An area of a specified page */ class OKULARCORE_EXPORT VisiblePageRect { public: /** * Creates a new visible page rectangle. * * @param pageNumber The page number where the rectangle is located. * @param rectangle The rectangle in normalized coordinates. */ explicit VisiblePageRect( int pageNumber = -1, const NormalizedRect &rectangle = NormalizedRect() ); /** * The page number where the rectangle is located. */ int pageNumber; /** * The rectangle in normalized coordinates. */ NormalizedRect rect; }; } Q_DECLARE_METATYPE( Okular::DocumentInfo::Key ) Q_DECLARE_OPERATORS_FOR_FLAGS( Okular::Document::PixmapRequestFlags ) #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/document_p.h b/core/document_p.h index 631b86fe8..c47229118 100644 --- a/core/document_p.h +++ b/core/document_p.h @@ -1,323 +1,323 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2007 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_DOCUMENT_P_H_ #define _OKULAR_DOCUMENT_P_H_ #include "document.h" #include "synctex/synctex_parser.h" // qt/kde/system includes -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include // local includes #include "fontinfo.h" #include "generator.h" class QUndoStack; class QEventLoop; class QFile; class QTimer; class QTemporaryFile; class KPluginMetaData; struct AllocatedPixmap; struct ArchiveData; struct RunningSearch; namespace Okular { class ConfigInterface; class PageController; class SaveInterface; class Scripter; class View; } struct GeneratorInfo { explicit GeneratorInfo( Okular::Generator *g, const KPluginMetaData &data) : generator( g ), metadata( data ), config( nullptr ), save( nullptr ), configChecked( false ), saveChecked( false ) {} Okular::Generator * generator; KPluginMetaData metadata; Okular::ConfigInterface * config; Okular::SaveInterface * save; bool configChecked : 1; bool saveChecked : 1; }; namespace Okular { class FontExtractionThread; struct DoContinueDirectionMatchSearchStruct { QSet< int > *pagesToNotify; RegularAreaRect *match; int currentPage; int searchID; }; enum LoadDocumentInfoFlag { LoadNone = 0, LoadPageInfo = 1, // Load annotations and forms LoadGeneralInfo = 2, // History, rotation, ... LoadAllInfo = 0xff }; Q_DECLARE_FLAGS(LoadDocumentInfoFlags, LoadDocumentInfoFlag) class DocumentPrivate { public: DocumentPrivate( Document *parent ) : m_parent( parent ), m_tempFile( nullptr ), m_docSize( -1 ), m_allocatedPixmapsTotalMemory( 0 ), m_maxAllocatedTextPages( 0 ), m_warnedOutOfMemory( false ), m_rotation( Rotation0 ), m_exportCached( false ), m_bookmarkManager( nullptr ), m_memCheckTimer( nullptr ), m_saveBookmarksTimer( nullptr ), m_generator( nullptr ), m_walletGenerator( nullptr ), m_generatorsLoaded( false ), m_pageController( nullptr ), m_closingLoop( nullptr ), m_scripter( nullptr ), m_archiveData( nullptr ), m_fontsCached( false ), m_annotationEditingEnabled ( true ), m_annotationBeingModified( false ), m_docdataMigrationNeeded( false ), m_synctex_scanner( nullptr ) { calculateMaxTextPages(); } // private methods bool updateMetadataXmlNameAndDocSize(); QString pagesSizeString() const; QString namePaperSize(double inchesWidth, double inchesHeight) const; QString localizedSize(const QSizeF &size) const; qulonglong calculateMemoryToFree(); void cleanupPixmapMemory(); void cleanupPixmapMemory( qulonglong memoryToFree ); AllocatedPixmap * searchLowestPriorityPixmap( bool unloadableOnly = false, bool thenRemoveIt = false, DocumentObserver *observer = nullptr /* any */ ); void calculateMaxTextPages(); qulonglong getTotalMemory(); qulonglong getFreeMemory( qulonglong *freeSwap = nullptr ); bool loadDocumentInfo( LoadDocumentInfoFlags loadWhat ); bool loadDocumentInfo( QFile &infoFile, LoadDocumentInfoFlags loadWhat ); void loadViewsInfo( View *view, const QDomElement &e ); void saveViewsInfo( View *view, QDomElement &e ) const; QUrl giveAbsoluteUrl( const QString & fileName ) const; bool openRelativeFile( const QString & fileName ); Generator * loadGeneratorLibrary( const KPluginMetaData& service ); void loadAllGeneratorLibraries(); void loadServiceList( const QVector& offers ); void unloadGenerator( const GeneratorInfo& info ); void cacheExportFormats(); void setRotationInternal( int r, bool notify ); ConfigInterface* generatorConfig( GeneratorInfo& info ); SaveInterface* generatorSave( GeneratorInfo& info ); Document::OpenResult openDocumentInternal( const KPluginMetaData& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ); static ArchiveData *unpackDocumentArchive( const QString &archivePath ); bool savePageDocumentInfo( QTemporaryFile *infoFile, int what ) const; DocumentViewport nextDocumentViewport() const; void notifyAnnotationChanges( int page ); void notifyFormChanges( int page ); bool canAddAnnotationsNatively() const; bool canModifyExternalAnnotations() const; bool canRemoveExternalAnnotations() const; OKULARCORE_EXPORT static QString docDataFileName(const QUrl &url, qint64 document_size); bool cancelRenderingBecauseOf( PixmapRequest *executingRequest, PixmapRequest *newRequest ); // Methods that implement functionality needed by undo commands void performAddPageAnnotation( int page, Annotation *annotation ); void performRemovePageAnnotation( int page, Annotation * annotation ); void performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ); void performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber ); void recalculateForms(); // private slots void saveDocumentInfo() const; void slotTimedMemoryCheck(); void sendGeneratorPixmapRequest(); void rotationFinished( int page, Okular::Page *okularPage ); void slotFontReadingProgress( int page ); void fontReadingGotFont( const Okular::FontInfo& font ); void slotGeneratorConfigChanged( const QString& ); void refreshPixmaps( int ); void _o_configChanged(); void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct); void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID); void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words); void doProcessSearchMatch( RegularAreaRect *match, RunningSearch *search, QSet< int > *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor & color ); // generators stuff /** * This method is used by the generators to signal the finish of * the pixmap generation @p request. */ void requestDone( PixmapRequest * request ); void textGenerationDone( Page *page ); /** * Sets the bounding box of the given @p page (in terms of upright orientation, i.e., Rotation0). */ void setPageBoundingBox( int page, const NormalizedRect& boundingBox ); /** * Request a particular metadata of the Document itself (ie, not something * depending on the document type/backend). */ QVariant documentMetaData( const Generator::DocumentMetaDataKey key, const QVariant &option ) const; /** * Return whether the normalized rectangle @p rectOfInterest on page number @p rectPage * is fully visible. */ bool isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage ); // For sync files void loadSyncFile( const QString & filePath ); void clearAndWaitForRequests(); // member variables Document *m_parent; QPointer m_widget; // find descriptors, mapped by ID (we handle multiple searches) QMap< int, RunningSearch * > m_searches; bool m_searchCancelled; // needed because for remote documents docFileName is a local file and // we want the remote url when the document refers to relativeNames QUrl m_url; // cached stuff QString m_docFileName; QString m_xmlFileName; QTemporaryFile *m_tempFile; qint64 m_docSize; // viewport stuff QLinkedList< DocumentViewport > m_viewportHistory; QLinkedList< DocumentViewport >::iterator m_viewportIterator; DocumentViewport m_nextDocumentViewport; // see Link::Goto for an explanation QString m_nextDocumentDestination; // observers / requests / allocator stuff QSet< DocumentObserver * > m_observers; QLinkedList< PixmapRequest * > m_pixmapRequestsStack; QLinkedList< PixmapRequest * > m_executingPixmapRequests; QMutex m_pixmapRequestsMutex; QLinkedList< AllocatedPixmap * > m_allocatedPixmaps; qulonglong m_allocatedPixmapsTotalMemory; QList< int > m_allocatedTextPagesFifo; int m_maxAllocatedTextPages; bool m_warnedOutOfMemory; // the rotation applied to the document Rotation m_rotation; // the current size of the pages (if available), and the cache of the // available page sizes PageSize m_pageSize; PageSize::List m_pageSizes; // cache of the export formats bool m_exportCached; ExportFormat::List m_exportFormats; ExportFormat m_exportToText; // our bookmark manager BookmarkManager *m_bookmarkManager; // timers (memory checking / info saver) QTimer *m_memCheckTimer; QTimer *m_saveBookmarksTimer; QHash m_loadedGenerators; Generator * m_generator; QString m_generatorName; Generator * m_walletGenerator; bool m_generatorsLoaded; QVector< Page * > m_pagesVector; QVector< VisiblePageRect * > m_pageRects; // cache of the mimetype we support QStringList m_supportedMimeTypes; PageController *m_pageController; QEventLoop *m_closingLoop; Scripter *m_scripter; ArchiveData *m_archiveData; QString m_archivedFileName; QPointer< FontExtractionThread > m_fontThread; bool m_fontsCached; QSet m_documentInfoAskedKeys; DocumentInfo m_documentInfo; FontInfo::List m_fontsCache; QSet< View * > m_views; bool m_annotationEditingEnabled; bool m_annotationBeingModified; // is an annotation currently being moved or resized? bool m_metadataLoadingCompleted; QUndoStack *m_undoStack; QDomNode m_prevPropsOfAnnotBeingModified; // Since 0.21, we no longer support saving annotations and form data in // the docdata/ directory and we ask the user to migrate them to an // external file as soon as possible, otherwise the document will be // shown in read-only mode. This flag is set if the docdata/ XML file // for the current document contains any annotation or form. bool m_docdataMigrationNeeded; synctex_scanner_p m_synctex_scanner; // generator selection static QVector availableGenerators(); static QVector configurableGenerators(); static KPluginMetaData generatorForMimeType(const QMimeType& type, QWidget* widget, const QVector &triedOffers = QVector()); }; class DocumentInfoPrivate { public: QMap values; // key -> value QMap titles; // key -> title For the custom keys }; } #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/documentcommands_p.h b/core/documentcommands_p.h index 2f4cc2d7a..b17abdc93 100644 --- a/core/documentcommands_p.h +++ b/core/documentcommands_p.h @@ -1,316 +1,316 @@ /*************************************************************************** * Copyright (C) 2013 Jon Mease * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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_DOCUMENT_COMMANDS_P_H_ #define _OKULAR_DOCUMENT_COMMANDS_P_H_ -#include +#include #include #include "area.h" namespace Okular { class Document; class Annotation; class DocumentPrivate; class FormFieldText; class FormFieldButton; class FormFieldChoice; class Page; class OkularUndoCommand : public QUndoCommand { public: virtual bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) = 0; }; class AddAnnotationCommand : public OkularUndoCommand { public: AddAnnotationCommand(Okular::DocumentPrivate * docPriv, Okular::Annotation* annotation, int pageNumber); virtual ~AddAnnotationCommand(); void undo() override; void redo() override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate * m_docPriv; Okular::Annotation* m_annotation; int m_pageNumber; bool m_done; }; class RemoveAnnotationCommand : public OkularUndoCommand { public: RemoveAnnotationCommand(Okular::DocumentPrivate * doc, Okular::Annotation* annotation, int pageNumber); virtual ~RemoveAnnotationCommand(); void undo() override; void redo() override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate * m_docPriv; Okular::Annotation* m_annotation; int m_pageNumber; bool m_done; }; class ModifyAnnotationPropertiesCommand : public OkularUndoCommand { public: ModifyAnnotationPropertiesCommand( Okular::DocumentPrivate* docPriv, Okular::Annotation* annotation, int pageNumber, QDomNode oldProperties, QDomNode newProperties ); void undo() override; void redo() override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate * m_docPriv; Okular::Annotation* m_annotation; int m_pageNumber; QDomNode m_prevProperties; QDomNode m_newProperties; }; class TranslateAnnotationCommand : public OkularUndoCommand { public: TranslateAnnotationCommand(Okular::DocumentPrivate* docPriv, Okular::Annotation* annotation, int pageNumber, const Okular::NormalizedPoint & delta, bool completeDrag ); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand *uc) override; Okular::NormalizedPoint minusDelta(); Okular::NormalizedRect translateBoundingRectangle( const Okular::NormalizedPoint & delta ); bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate * m_docPriv; Okular::Annotation* m_annotation; int m_pageNumber; Okular::NormalizedPoint m_delta; bool m_completeDrag; }; class AdjustAnnotationCommand : public OkularUndoCommand { public: AdjustAnnotationCommand(Okular::DocumentPrivate * docPriv, Okular::Annotation * annotation, int pageNumber, const Okular::NormalizedPoint & delta1, const Okular::NormalizedPoint & delta2, bool completeDrag ); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand * uc) override; Okular::NormalizedRect adjustBoundingRectangle( const Okular::NormalizedPoint & delta1, const Okular::NormalizedPoint & delta2 ); bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate * m_docPriv; Okular::Annotation* m_annotation; int m_pageNumber; Okular::NormalizedPoint m_delta1; Okular::NormalizedPoint m_delta2; bool m_completeDrag; }; class EditTextCommand : public OkularUndoCommand { public: EditTextCommand( const QString & newContents, int newCursorPos, const QString & prevContents, int prevCursorPos, int prevAnchorPos ); void undo() override = 0; void redo() override = 0; int id() const override = 0; bool mergeWith(const QUndoCommand *uc) override; private: enum EditType { CharBackspace, ///< Edit made up of one or more single character backspace operations CharDelete, ///< Edit made up of one or more single character delete operations CharInsert, ///< Edit made up of one or more single character insertion operations OtherEdit ///< All other edit operations (these will not be merged together) }; QString oldContentsLeftOfCursor(); QString newContentsLeftOfCursor(); QString oldContentsRightOfCursor(); QString newContentsRightOfCursor(); protected: QString m_newContents; int m_newCursorPos; QString m_prevContents; int m_prevCursorPos; int m_prevAnchorPos; EditType m_editType; }; class EditAnnotationContentsCommand : public EditTextCommand { public: EditAnnotationContentsCommand(Okular::DocumentPrivate* docPriv, Okular::Annotation* annotation, int pageNumber, const QString & newContents, int newCursorPos, const QString & prevContents, int prevCursorPos, int prevAnchorPos ); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand *uc) override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate * m_docPriv; Okular::Annotation* m_annotation; int m_pageNumber; }; class EditFormTextCommand : public EditTextCommand { public: EditFormTextCommand( Okular::DocumentPrivate* docPriv, Okular::FormFieldText* form, int pageNumber, const QString & newContents, int newCursorPos, const QString & prevContents, int prevCursorPos, int prevAnchorPos ); void undo() override; void redo() override; int id() const override; bool mergeWith( const QUndoCommand *uc ) override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate* m_docPriv; Okular::FormFieldText* m_form; int m_pageNumber; }; class EditFormListCommand : public OkularUndoCommand { public: EditFormListCommand( Okular::DocumentPrivate* docPriv, FormFieldChoice* form, int pageNumber, const QList< int > & newChoices, const QList< int > & prevChoices ); void undo() override; void redo() override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate* m_docPriv; FormFieldChoice* m_form; int m_pageNumber; QList< int > m_newChoices; QList< int > m_prevChoices; }; class EditFormComboCommand : public EditTextCommand { public: EditFormComboCommand( Okular::DocumentPrivate* docPriv, FormFieldChoice* form, int pageNumber, const QString & newText, int newCursorPos, const QString & prevText, int prevCursorPos, int prevAnchorPos ); void undo() override; void redo() override; int id() const override; bool mergeWith( const QUndoCommand *uc ) override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: Okular::DocumentPrivate* m_docPriv; FormFieldChoice* m_form; int m_pageNumber; int m_newIndex; int m_prevIndex; }; class EditFormButtonsCommand : public OkularUndoCommand { public: EditFormButtonsCommand( Okular::DocumentPrivate* docPriv, int pageNumber, const QList< FormFieldButton* > & formButtons, const QList< bool > & newButtonStates ); void undo() override; void redo() override; bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override; private: void clearFormButtonStates(); private: Okular::DocumentPrivate* m_docPriv; int m_pageNumber; QList< FormFieldButton* > m_formButtons; QList< bool > m_newButtonStates; QList< bool > m_prevButtonStates; }; } #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/fileprinter.cpp b/core/fileprinter.cpp index c4d8fb415..cc476a2f2 100644 --- a/core/fileprinter.cpp +++ b/core/fileprinter.cpp @@ -1,643 +1,643 @@ /*************************************************************************** * Copyright (C) 2007,2010 by John Layt * * * * FilePrinterPreview based on KPrintPreview (originally LGPL) * * Copyright (c) 2007 Alex Merry * * * * 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 "fileprinter.h" -#include +#include #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include -#include +#include #include #include "debug_p.h" using namespace Okular; int FilePrinter::printFile( QPrinter &printer, const QString file, QPrinter::Orientation documentOrientation, FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, const QString &pageRange ) { FilePrinter fp; return fp.doPrintFiles( printer, QStringList( file ), fileDeletePolicy, pageSelectPolicy, pageRange, documentOrientation ); } int FilePrinter::doPrintFiles( QPrinter &printer, QStringList fileList, FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, const QString &pageRange, QPrinter::Orientation documentOrientation ) { if ( fileList.size() < 1 ) { return -8; } for (QStringList::ConstIterator it = fileList.constBegin(); it != fileList.constEnd(); ++it) { if (!QFile::exists(*it)) { return -7; } } if ( printer.printerState() == QPrinter::Aborted || printer.printerState() == QPrinter::Error ) { return -6; } QString exe; QStringList argList; int ret; // Print to File if a filename set, assumes there must be only 1 file if ( !printer.outputFileName().isEmpty() ) { if ( QFile::exists( printer.outputFileName() ) ) { QFile::remove( printer.outputFileName() ); } QFileInfo inputFileInfo = QFileInfo( fileList[0] ); QFileInfo outputFileInfo = QFileInfo( printer.outputFileName() ); bool doDeleteFile = (fileDeletePolicy == FilePrinter::SystemDeletesFiles); if ( inputFileInfo.suffix() == outputFileInfo.suffix() ) { if ( doDeleteFile ) { bool res = QFile::rename( fileList[0], printer.outputFileName() ); if ( res ) { doDeleteFile = false; ret = 0; } else { ret = -5; } } else { bool res = QFile::copy( fileList[0], printer.outputFileName() ); if ( res ) { ret = 0; } else { ret = -5; } } } else if ( inputFileInfo.suffix() == QLatin1String("ps") && printer.outputFormat() == QPrinter::PdfFormat && ps2pdfAvailable() ) { exe = QStringLiteral("ps2pdf"); argList << fileList[0] << printer.outputFileName(); qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList; ret = KProcess::execute( exe, argList ); } else if ( inputFileInfo.suffix() == "pdf" && printer.outputFormat() == QPrinter::NativeFormat && pdf2psAvailable() ) { exe = "pdf2ps"; argList << fileList[0] << printer.outputFileName(); qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList; ret = KProcess::execute( exe, argList ); } else { ret = -5; } if ( doDeleteFile ) { QFile::remove( fileList[0] ); } } else { // Print to a printer via lpr command //Decide what executable to use to print with, need the CUPS version of lpr if available //Some distros name the CUPS version of lpr as lpr-cups or lpr.cups so try those first //before default to lpr, or failing that to lp if ( !QStandardPaths::findExecutable(QStringLiteral("lpr-cups")).isEmpty() ) { exe = QStringLiteral("lpr-cups"); } else if ( !QStandardPaths::findExecutable(QStringLiteral("lpr.cups")).isEmpty() ) { exe = QStringLiteral("lpr.cups"); } else if ( !QStandardPaths::findExecutable(QStringLiteral("lpr")).isEmpty() ) { exe = QStringLiteral("lpr"); } else if ( !QStandardPaths::findExecutable(QStringLiteral("lp")).isEmpty() ) { exe = QStringLiteral("lp"); } else { return -9; } bool useCupsOptions = cupsAvailable(); argList = printArguments( printer, fileDeletePolicy, pageSelectPolicy, useCupsOptions, pageRange, exe, documentOrientation ) << fileList; qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList; ret = KProcess::execute( exe, argList ); } return ret; } QList FilePrinter::pageList( QPrinter &printer, int lastPage, const QList &selectedPageList ) { return pageList( printer, lastPage, 0, selectedPageList ); } QList FilePrinter::pageList( QPrinter &printer, int lastPage, int currentPage, const QList &selectedPageList ) { if ( printer.printRange() == QPrinter::Selection) { return selectedPageList; } int startPage, endPage; QList list; if ( printer.printRange() == QPrinter::PageRange ) { startPage = printer.fromPage(); endPage = printer.toPage(); } else if ( printer.printRange() == QPrinter::CurrentPage) { startPage = currentPage; endPage = currentPage; } else { //AllPages startPage = 1; endPage = lastPage; } for (int i = startPage; i <= endPage; i++ ) { list << i; } return list; } QString FilePrinter::pageRange( QPrinter &printer, int lastPage, const QList &selectedPageList ) { if ( printer.printRange() == QPrinter::Selection) { return pageListToPageRange( selectedPageList ); } if ( printer.printRange() == QPrinter::PageRange ) { return QStringLiteral("%1-%2").arg(printer.fromPage()).arg(printer.toPage()); } return QStringLiteral("1-%2").arg( lastPage ); } QString FilePrinter::pageListToPageRange( const QList &pageList ) { QString pageRange; int count = pageList.count(); int i = 0; int seqStart = i; int seqEnd; while ( i != count ) { if ( i + 1 == count || pageList[i] + 1 != pageList[i+1] ) { seqEnd = i; if ( !pageRange.isEmpty() ) { pageRange.append(QLatin1Char(',')); } if ( seqStart == seqEnd ) { pageRange.append(pageList[i]); } else { pageRange.append(QStringLiteral("%1-%2").arg(seqStart).arg(seqEnd)); } seqStart = i + 1; } i++; } return pageRange; } bool FilePrinter::ps2pdfAvailable() { return ( !QStandardPaths::findExecutable(QStringLiteral("ps2pdf")).isEmpty() ); } bool FilePrinter::pdf2psAvailable() { return ( !QStandardPaths::findExecutable(QStringLiteral("pdf2ps")).isEmpty() ); } bool FilePrinter::cupsAvailable() { #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) // Ideally we would have access to the private Qt method // QCUPSSupport::cupsAvailable() to do this as it is very complex routine. // However, if CUPS is available then QPrinter::numCopies() will always return 1 // whereas if CUPS is not available it will return the real number of copies. // This behaviour is guaranteed never to change, so we can use it as a reliable substitute. QPrinter testPrinter; testPrinter.setNumCopies( 2 ); return ( testPrinter.numCopies() == 1 ); #else return false; #endif } bool FilePrinter::detectCupsService() { QTcpSocket qsock; qsock.connectToHost(QStringLiteral("localhost"), 631); bool rtn = qsock.waitForConnected() && qsock.isValid(); qsock.abort(); return rtn; } bool FilePrinter::detectCupsConfig() { if ( QFile::exists(QStringLiteral("/etc/cups/cupsd.conf")) ) return true; if ( QFile::exists(QStringLiteral("/usr/etc/cups/cupsd.conf")) ) return true; if ( QFile::exists(QStringLiteral("/usr/local/etc/cups/cupsd.conf")) ) return true; if ( QFile::exists(QStringLiteral("/opt/etc/cups/cupsd.conf")) ) return true; if ( QFile::exists(QStringLiteral("/opt/local/etc/cups/cupsd.conf")) ) return true; return false; } QSize FilePrinter::psPaperSize( QPrinter &printer ) { QSize size = printer.pageLayout().pageSize().sizePoints(); if ( printer.pageSize() == QPrinter::Custom ) { return QSize( (int) printer.widthMM() * ( 25.4 / 72 ), (int) printer.heightMM() * ( 25.4 / 72 ) ); } if ( printer.orientation() == QPrinter::Landscape ) { size.transpose(); } return size; } Generator::PrintError FilePrinter::printError( int c ) { Generator::PrintError pe; if ( c >= 0 ) { pe = Generator::NoPrintError; } else { switch ( c ) { case -1: pe = Generator::PrintingProcessCrashPrintError; break; case -2: pe = Generator::PrintingProcessStartPrintError; break; case -5: pe = Generator::PrintToFilePrintError; break; case -6: pe = Generator::InvalidPrinterStatePrintError; break; case -7: pe = Generator::UnableToFindFilePrintError; break; case -8: pe = Generator::NoFileToPrintError; break; case -9: pe = Generator::NoBinaryToPrintError; break; default: pe = Generator::UnknownPrintError; } } return pe; } QStringList FilePrinter::printArguments( QPrinter &printer, FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, bool useCupsOptions, const QString &pageRange, const QString &version, QPrinter::Orientation documentOrientation ) { QStringList argList; if ( ! destination( printer, version ).isEmpty() ) { argList << destination( printer, version ); } if ( ! copies( printer, version ).isEmpty() ) { argList << copies( printer, version ); } if ( ! jobname( printer, version ).isEmpty() ) { argList << jobname( printer, version ); } if ( ! pages( printer, pageSelectPolicy, pageRange, useCupsOptions, version ).isEmpty() ) { argList << pages( printer, pageSelectPolicy, pageRange, useCupsOptions, version ); } if ( useCupsOptions && ! cupsOptions( printer, documentOrientation ).isEmpty() ) { argList << cupsOptions( printer, documentOrientation); } if ( ! deleteFile( printer, fileDeletePolicy, version ).isEmpty() ) { argList << deleteFile( printer, fileDeletePolicy, version ); } if ( version == QLatin1String("lp") ) { argList << QStringLiteral("--"); } return argList; } QStringList FilePrinter::destination( QPrinter &printer, const QString &version ) { if ( version == QLatin1String("lp") ) { return QStringList(QStringLiteral("-d")) << printer.printerName(); } if ( version.startsWith( QLatin1String("lpr") ) ) { return QStringList(QStringLiteral("-P")) << printer.printerName(); } return QStringList(); } QStringList FilePrinter::copies( QPrinter &printer, const QString &version ) { int cp = printer.actualNumCopies(); if ( version == QLatin1String("lp") ) { return QStringList(QStringLiteral("-n")) << QStringLiteral("%1").arg( cp ); } if ( version.startsWith( QLatin1String("lpr") ) ) { return QStringList() << QStringLiteral("-#%1").arg( cp ); } return QStringList(); } QStringList FilePrinter::jobname( QPrinter &printer, const QString &version ) { if ( ! printer.docName().isEmpty() ) { if ( version == QLatin1String("lp") ) { return QStringList(QStringLiteral("-t")) << printer.docName(); } if ( version.startsWith( QLatin1String("lpr") ) ) { const QString shortenedDocName = QString::fromUtf8(printer.docName().toUtf8().left(255)); return QStringList(QStringLiteral("-J")) << shortenedDocName; } } return QStringList(); } QStringList FilePrinter::deleteFile( QPrinter &, FileDeletePolicy fileDeletePolicy, const QString &version ) { if ( fileDeletePolicy == FilePrinter::SystemDeletesFiles && version.startsWith( QLatin1String("lpr") ) ) { return QStringList(QStringLiteral("-r")); } return QStringList(); } QStringList FilePrinter::pages( QPrinter &printer, PageSelectPolicy pageSelectPolicy, const QString &pageRange, bool useCupsOptions, const QString &version ) { if ( pageSelectPolicy == FilePrinter::SystemSelectsPages ) { if ( printer.printRange() == QPrinter::Selection && ! pageRange.isEmpty() ) { if ( version == QLatin1String("lp") ) { return QStringList(QStringLiteral("-P")) << pageRange ; } if ( version.startsWith( QLatin1String("lpr") ) && useCupsOptions ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("page-ranges=%1").arg( pageRange ); } } if ( printer.printRange() == QPrinter::PageRange ) { if ( version == QLatin1String("lp") ) { return QStringList(QStringLiteral("-P")) << QStringLiteral("%1-%2").arg( printer.fromPage() ) .arg( printer.toPage() ); } if ( version.startsWith( QLatin1String("lpr") ) && useCupsOptions ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("page-ranges=%1-%2").arg( printer.fromPage() ) .arg( printer.toPage() ); } } } return QStringList(); // AllPages } QStringList FilePrinter::cupsOptions( QPrinter &printer, QPrinter::Orientation documentOrientation ) { QStringList optionList; if ( ! optionMedia( printer ).isEmpty() ) { optionList << optionMedia( printer ); } if ( ! optionOrientation( printer, documentOrientation ).isEmpty() ) { optionList << optionOrientation( printer, documentOrientation ); } if ( ! optionDoubleSidedPrinting( printer ).isEmpty() ) { optionList << optionDoubleSidedPrinting( printer ); } if ( ! optionPageOrder( printer ).isEmpty() ) { optionList << optionPageOrder( printer ); } if ( ! optionCollateCopies( printer ).isEmpty() ) { optionList << optionCollateCopies( printer ); } if ( ! optionPageMargins( printer ).isEmpty() ) { optionList << optionPageMargins( printer ); } optionList << optionCupsProperties( printer ); return optionList; } QStringList FilePrinter::optionMedia( QPrinter &printer ) { if ( ! mediaPageSize( printer ).isEmpty() && ! mediaPaperSource( printer ).isEmpty() ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1,%2").arg( mediaPageSize( printer ), mediaPaperSource( printer ) ); } if ( ! mediaPageSize( printer ).isEmpty() ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1").arg( mediaPageSize( printer ) ); } if ( ! mediaPaperSource( printer ).isEmpty() ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1").arg( mediaPaperSource( printer ) ); } return QStringList(); } QString FilePrinter::mediaPageSize( QPrinter &printer ) { switch ( printer.pageSize() ) { case QPrinter::A0: return QStringLiteral("A0"); case QPrinter::A1: return QStringLiteral("A1"); case QPrinter::A2: return QStringLiteral("A2"); case QPrinter::A3: return QStringLiteral("A3"); case QPrinter::A4: return QStringLiteral("A4"); case QPrinter::A5: return QStringLiteral("A5"); case QPrinter::A6: return QStringLiteral("A6"); case QPrinter::A7: return QStringLiteral("A7"); case QPrinter::A8: return QStringLiteral("A8"); case QPrinter::A9: return QStringLiteral("A9"); case QPrinter::B0: return QStringLiteral("B0"); case QPrinter::B1: return QStringLiteral("B1"); case QPrinter::B10: return QStringLiteral("B10"); case QPrinter::B2: return QStringLiteral("B2"); case QPrinter::B3: return QStringLiteral("B3"); case QPrinter::B4: return QStringLiteral("B4"); case QPrinter::B5: return QStringLiteral("B5"); case QPrinter::B6: return QStringLiteral("B6"); case QPrinter::B7: return QStringLiteral("B7"); case QPrinter::B8: return QStringLiteral("B8"); case QPrinter::B9: return QStringLiteral("B9"); case QPrinter::C5E: return QStringLiteral("C5"); //Correct Translation? case QPrinter::Comm10E: return QStringLiteral("Comm10"); //Correct Translation? case QPrinter::DLE: return QStringLiteral("DL"); //Correct Translation? case QPrinter::Executive: return QStringLiteral("Executive"); case QPrinter::Folio: return QStringLiteral("Folio"); case QPrinter::Ledger: return QStringLiteral("Ledger"); case QPrinter::Legal: return QStringLiteral("Legal"); case QPrinter::Letter: return QStringLiteral("Letter"); case QPrinter::Tabloid: return QStringLiteral("Tabloid"); case QPrinter::Custom: return QStringLiteral("Custom.%1x%2mm") .arg( printer.widthMM() ) .arg( printer.heightMM() ); default: return QString(); } } // What about Upper and MultiPurpose? And others in PPD??? QString FilePrinter::mediaPaperSource( QPrinter &printer ) { switch ( printer.paperSource() ) { case QPrinter::Auto: return QString(); case QPrinter::Cassette: return QStringLiteral("Cassette"); case QPrinter::Envelope: return QStringLiteral("Envelope"); case QPrinter::EnvelopeManual: return QStringLiteral("EnvelopeManual"); case QPrinter::FormSource: return QStringLiteral("FormSource"); case QPrinter::LargeCapacity: return QStringLiteral("LargeCapacity"); case QPrinter::LargeFormat: return QStringLiteral("LargeFormat"); case QPrinter::Lower: return QStringLiteral("Lower"); case QPrinter::MaxPageSource: return QStringLiteral("MaxPageSource"); case QPrinter::Middle: return QStringLiteral("Middle"); case QPrinter::Manual: return QStringLiteral("Manual"); case QPrinter::OnlyOne: return QStringLiteral("OnlyOne"); case QPrinter::Tractor: return QStringLiteral("Tractor"); case QPrinter::SmallFormat: return QStringLiteral("SmallFormat"); default: return QString(); } } QStringList FilePrinter::optionOrientation( QPrinter &printer, QPrinter::Orientation documentOrientation ) { // portrait and landscape options rotate the document according to the document orientation // If we want to print a landscape document as one would expect it, we have to pass the // portrait option so that the document is not rotated additionally if ( printer.orientation() == documentOrientation ) { // the user wants the document printed as is return QStringList(QStringLiteral("-o")) << QStringLiteral("portrait"); } else { // the user expects the document being rotated by 90 degrees return QStringList(QStringLiteral("-o")) << QStringLiteral("landscape"); } } QStringList FilePrinter::optionDoubleSidedPrinting( QPrinter &printer ) { switch ( printer.duplex() ) { case QPrinter::DuplexNone: return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=one-sided"); case QPrinter::DuplexAuto: if ( printer.orientation() == QPrinter::Landscape ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge"); } else { return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge"); } case QPrinter::DuplexLongSide: return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge"); case QPrinter::DuplexShortSide: return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge"); default: return QStringList(); //Use printer default } } QStringList FilePrinter::optionPageOrder( QPrinter &printer ) { if ( printer.pageOrder() == QPrinter::LastPageFirst ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=reverse"); } return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=normal"); } QStringList FilePrinter::optionCollateCopies( QPrinter &printer ) { if ( printer.collateCopies() ) { return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=True"); } return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=False"); } QStringList FilePrinter::optionPageMargins( QPrinter &printer ) { if (printer.printEngine()->property(QPrintEngine::PPK_PageMargins).isNull()) { return QStringList(); } else { qreal l, t, r, b; printer.getPageMargins( &l, &t, &r, &b, QPrinter::Point ); return QStringList(QStringLiteral("-o")) << QStringLiteral("page-left=%1").arg(l) << QStringLiteral("-o") << QStringLiteral("page-top=%1").arg(t) << QStringLiteral("-o") << QStringLiteral("page-right=%1").arg(r) << QStringLiteral("-o") << QStringLiteral("page-bottom=%1").arg(b) << QStringLiteral("-o") << QStringLiteral("fit-to-page"); } } QStringList FilePrinter::optionCupsProperties( QPrinter &printer ) { QStringList dialogOptions = printer.printEngine()->property(QPrintEngine::PrintEnginePropertyKey(0xfe00)).toStringList(); QStringList cupsOptions; for ( int i = 0; i < dialogOptions.count(); i = i + 2 ) { if ( dialogOptions[i+1].isEmpty() ) { cupsOptions << QStringLiteral("-o") << dialogOptions[i]; } else { cupsOptions << QStringLiteral("-o") << dialogOptions[i] + QLatin1Char('=') + dialogOptions[i+1]; } } return cupsOptions; } /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/fileprinter.h b/core/fileprinter.h index a60c2c0f5..8e687bad7 100644 --- a/core/fileprinter.h +++ b/core/fileprinter.h @@ -1,187 +1,187 @@ /*************************************************************************** * Copyright (C) 2007, 2010 by John Layt * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ // This Class is a temporary addition to Okular for the duration of KDE 4.0. // In KDE 4.1 this class will either be moved to kdelibs if still required, // or replaced with a Qt 4.4 based solution. #ifndef FILEPRINTER_H #define FILEPRINTER_H -#include -#include -#include +#include +#include +#include #include "okularcore_export.h" #include "generator.h" class QSize; namespace Okular { class OKULARCORE_EXPORT FilePrinter { public: /** Whether file(s) get deleted by the application or by the print system. * * You may need to chose system deletion if your temp file clean-up * deletes the file before the print system is finished with it. */ enum FileDeletePolicy { ApplicationDeletesFiles, SystemDeletesFiles }; /** Whether pages to be printed are selected by the application or the print system. * * If application side, then the generated file will only contain those pages * selected by the user, so FilePrinter will print all the pages in the file. * * If system side, then the file will contain all the pages in the document, and * the print system will print the users selected print range from out of the file. * * Note system side only works in CUPS, not LPR. */ enum PageSelectPolicy { ApplicationSelectsPages, SystemSelectsPages }; /** Print a file using the settings in QPrinter * * Only supports CUPS and LPR on *NIX. Page Range only supported in CUPS. * Most settings unsupported by LPR, some settings unsupported by CUPS. * * The documentOrientation parameter was added in version 0.14. * * @param printer the print settings to use * @param file the file to print * @param documentOrientation the orientation stored in the document itself * @param fileDeletePolicy if the application or system deletes the file * @param pageSelectPolicy if the application or system selects the pages to print * @param pageRange page range to print if SystemSelectsPages and user chooses Selection in Print Dialog * * @returns Returns exit code: * -9 if lpr not found * -8 if empty file name * -7 if unable to find file * -6 if invalid printer state * -5 if print to file copy failed * -2 if the KProcess could not be started * -1 if the KProcess crashed * otherwise the KProcess exit code * * @since 0.14 (KDE 4.8) */ static int printFile( QPrinter &printer, const QString file, QPrinter::Orientation documentOrientation, FileDeletePolicy fileDeletePolicy = FilePrinter::ApplicationDeletesFiles, PageSelectPolicy pageSelectPolicy = FilePrinter::ApplicationSelectsPages, const QString &pageRange = QString() ); /** Return the list of pages selected by the user in the Print Dialog * * @param printer the print settings to use * @param lastPage the last page number, needed if AllPages option is selected * @param currentPage the current page number, needed if CurrentPage option is selected * @param selectedPageList list of pages to use if Selection option is selected * @returns Returns list of pages to print */ static QList pageList( QPrinter &printer, int lastPage, int currentPage, const QList &selectedPageList ); /** Return the list of pages selected by the user in the Print Dialog * * @param printer the print settings to use * @param lastPage the last page number, needed if AllPages option is selected * @param selectedPageList list of pages to use if Selection option is selected * @returns Returns list of pages to print */ static QList pageList( QPrinter &printer, int lastPage, const QList &selectedPageList ); /** Return the range of pages selected by the user in the Print Dialog * * @param printer the print settings to use * @param lastPage the last page number, needed if AllPages option is selected * @param selectedPageList list of pages to use if Selection option is selected * @returns Returns range of pages to print */ static QString pageRange( QPrinter &printer, int lastPage, const QList &selectedPageList ); /** convert a Page List into a Page Range * * @param pageList list of pages to convert * @returns Returns equivalent page range */ static QString pageListToPageRange( const QList &pageList ); /** Return if Ghostscript ps2pdf is available on this system * * @returns Returns true if Ghostscript ps2pdf available */ static bool ps2pdfAvailable(); /** Return if Ghostscript pdf2ps is available on this system * * @returns Returns true if Ghostscript pdf2ps available */ static bool pdf2psAvailable(); /** Return if CUPS Print System is available on this system * * @returns Returns true if CUPS available */ static bool cupsAvailable(); /** Returns the postscript standard page size * * @returns Returns paper size in ps points */ static QSize psPaperSize( QPrinter &printer ); /** * Convert the code returned by printFile* to PrintError * @since 0.11 (KDE 4.5) */ static Generator::PrintError printError( int c ); protected: bool detectCupsService(); bool detectCupsConfig(); int doPrintFiles( QPrinter &printer, const QStringList fileList, FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, const QString &pageRange, QPrinter::Orientation documentOrientation ); QStringList printArguments( QPrinter &printer, FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, bool useCupsOptions, const QString &pageRange, const QString &version, QPrinter::Orientation documentOrientation ); QStringList destination( QPrinter &printer, const QString &version ); QStringList copies( QPrinter &printer, const QString &version ); QStringList jobname( QPrinter &printer, const QString &version ); QStringList deleteFile( QPrinter &printer, FileDeletePolicy fileDeletePolicy, const QString &version ); QStringList pages( QPrinter &printer, PageSelectPolicy pageSelectPolicy, const QString &pageRange, bool useCupsOptions, const QString &version ); QStringList cupsOptions( QPrinter &printer, QPrinter::Orientation documentOrientation ); QStringList optionMedia( QPrinter &printer ); QString mediaPageSize( QPrinter &printer ); QString mediaPaperSource( QPrinter &printer ); QStringList optionOrientation( QPrinter &printer, QPrinter::Orientation documentOrientation ); QStringList optionDoubleSidedPrinting( QPrinter &printer ); QStringList optionPageOrder( QPrinter &printer ); QStringList optionCollateCopies( QPrinter &printer ); QStringList optionPageMargins( QPrinter &printer ); QStringList optionCupsProperties( QPrinter &printer ); }; } #endif // FILEPRINTER_H diff --git a/core/fontinfo.h b/core/fontinfo.h index 42cfa56f3..b0f7fdb72 100644 --- a/core/fontinfo.h +++ b/core/fontinfo.h @@ -1,162 +1,162 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_FONTINFO_H_ #define _OKULAR_FONTINFO_H_ -#include -#include -#include -#include +#include +#include +#include +#include #include "okularcore_export.h" namespace Okular { class FontInfoPrivate; /** * @short A small class that represents the information of a font. */ class OKULARCORE_EXPORT FontInfo { public: typedef QVector List; /** * The possible kinds of fonts. */ enum FontType { Unknown, Type1, Type1C, Type1COT, Type3, TrueType, TrueTypeOT, CIDType0, CIDType0C, CIDType0COT, CIDTrueType, CIDTrueTypeOT, TeXPK, ///< @since 0.10 (KDE 4.4) TeXVirtual, ///< @since 0.10 (KDE 4.4) TeXFontMetric, ///< @since 0.10 (KDE 4.4) TeXFreeTypeHandled ///< @since 0.10 (KDE 4.4) }; /** * The possible kinds of embed. */ enum EmbedType { NotEmbedded, EmbeddedSubset, FullyEmbedded }; /** * Construct a new empty font info. */ FontInfo(); /** * Copy constructor. */ FontInfo( const FontInfo &fi ); /** * Destructor. */ ~FontInfo(); /** * Returns the name of the font. */ QString name() const; /** * Sets a new name for the font. */ void setName( const QString& name ); /** * Returns the type of the font. */ FontType type() const; /** * Change the type of the font. */ void setType( FontType type ); /** * Returns the type of font embedding. */ EmbedType embedType() const; /** * Sets the type of font embedding. */ void setEmbedType( EmbedType type ); /** * In case of not embedded font, returns the path of the font that * represents this font. */ QString file() const; void setFile( const QString& file ); /** * In case of embedded fonts, returns if the font can be extracted into a QByteArray * * @since 0.8 (KDE 4.2) */ bool canBeExtracted() const; /** * Sets if a font can be extracted or not. False by default */ void setCanBeExtracted( bool extractable ); /** * Sets the "native" @p id of the font info. * * This is for use of the Generator, that can optionally store an * handle (a pointer, an identifier, etc) of the "native" font * object, if any. * * @since 0.8 (KDE 4.2) */ void setNativeId( const QVariant &id ); /** * Returns the "native" id of the font info. * * @since 0.8 (KDE 4.2) */ QVariant nativeId() const; FontInfo& operator=( const FontInfo &fi ); /** * Comparison operator. */ bool operator==( const FontInfo &fi ) const; bool operator!=( const FontInfo &fi ) const; private: /// @cond PRIVATE friend class FontInfoPrivate; /// @endcond QSharedDataPointer d; }; } Q_DECLARE_METATYPE(Okular::FontInfo) #endif diff --git a/core/form.cpp b/core/form.cpp index 2071da0f2..3adceae27 100644 --- a/core/form.cpp +++ b/core/form.cpp @@ -1,296 +1,296 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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 "form.h" #include "form_p.h" // qt includes -#include +#include #include "action.h" using namespace Okular; FormFieldPrivate::FormFieldPrivate( FormField::FieldType type ) : m_type( type ), m_activateAction( nullptr ) { } FormFieldPrivate::~FormFieldPrivate() { delete m_activateAction; qDeleteAll( m_additionalActions.values() ); qDeleteAll( m_additionalAnnotActions.values() ); } void FormFieldPrivate::setDefault() { m_default = value(); } FormField::FormField( FormFieldPrivate &dd ) : d_ptr( &dd ) { d_ptr->q_ptr = this; } FormField::~FormField() { delete d_ptr; } FormField::FieldType FormField::type() const { Q_D( const FormField ); return d->m_type; } bool FormField::isReadOnly() const { return false; } void FormField::setReadOnly( bool ) { } bool FormField::isVisible() const { return true; } void FormField::setVisible( bool ) { } Action* FormField::activationAction() const { Q_D( const FormField ); return d->m_activateAction; } void FormField::setActivationAction( Action *action ) { Q_D( FormField ); delete d->m_activateAction; d->m_activateAction = action; } Action* FormField::additionalAction( AdditionalActionType type ) const { Q_D( const FormField ); return d->m_additionalActions.value(type); } void FormField::setAdditionalAction( AdditionalActionType type, Action *action ) { Q_D( FormField ); delete d->m_additionalActions.value(type); d->m_additionalActions[type] = action; } Action* FormField::additionalAction( Annotation::AdditionalActionType type ) const { Q_D( const FormField ); return d->m_additionalAnnotActions.value(type); } void FormField::setAdditionalAction( Annotation::AdditionalActionType type, Action *action ) { Q_D( FormField ); delete d->m_additionalAnnotActions.value(type); d->m_additionalAnnotActions[type] = action; } class Okular::FormFieldButtonPrivate : public Okular::FormFieldPrivate { public: FormFieldButtonPrivate() : FormFieldPrivate( FormField::FormButton ) { } Q_DECLARE_PUBLIC( FormFieldButton ) void setValue( const QString& v ) override { Q_Q( FormFieldButton ); q->setState( QVariant( v ).toBool() ); } QString value() const override { Q_Q( const FormFieldButton ); return qVariantFromValue( q->state() ).toString(); } }; FormFieldButton::FormFieldButton() : FormField( *new FormFieldButtonPrivate() ) { } FormFieldButton::~FormFieldButton() { } void FormFieldButton::setState( bool ) { } class Okular::FormFieldTextPrivate : public Okular::FormFieldPrivate { public: FormFieldTextPrivate() : FormFieldPrivate( FormField::FormText ) { } Q_DECLARE_PUBLIC( FormFieldText ) void setValue( const QString& v ) override { Q_Q( FormFieldText ); q->setText( v ); } QString value() const override { Q_Q( const FormFieldText ); return q->text(); } }; FormFieldText::FormFieldText() : FormField( *new FormFieldTextPrivate() ) { } FormFieldText::~FormFieldText() { } void FormFieldText::setText( const QString& ) { } bool FormFieldText::isPassword() const { return false; } bool FormFieldText::isRichText() const { return false; } int FormFieldText::maximumLength() const { return -1; } Qt::Alignment FormFieldText::textAlignment() const { return Qt::AlignVCenter | Qt::AlignLeft; } bool FormFieldText::canBeSpellChecked() const { return false; } class Okular::FormFieldChoicePrivate : public Okular::FormFieldPrivate { public: FormFieldChoicePrivate() : FormFieldPrivate( FormField::FormChoice ) { } Q_DECLARE_PUBLIC( FormFieldChoice ) void setValue( const QString& v ) override { Q_Q( FormFieldChoice ); QStringList choices = v.split( QLatin1Char (';'), QString::SkipEmptyParts ); QList newchoices; foreach ( const QString& str, choices ) { bool ok = true; int val = str.toInt( &ok ); if ( ok ) newchoices.append( val ); } if ( !newchoices.isEmpty() ) q->setCurrentChoices( newchoices ); } QString value() const override { Q_Q( const FormFieldChoice ); QList choices = q->currentChoices(); qSort( choices ); QStringList list; foreach ( int c, choices ) { list.append( QString::number( c ) ); } return list.join( QStringLiteral( ";" ) ); } }; FormFieldChoice::FormFieldChoice() : FormField( *new FormFieldChoicePrivate() ) { } FormFieldChoice::~FormFieldChoice() { } bool FormFieldChoice::isEditable() const { return false; } bool FormFieldChoice::multiSelect() const { return false; } void FormFieldChoice::setCurrentChoices( const QList< int >& ) { } QString FormFieldChoice::editChoice() const { return QString(); } void FormFieldChoice::setEditChoice( const QString& ) { } Qt::Alignment FormFieldChoice::textAlignment() const { return Qt::AlignVCenter | Qt::AlignLeft; } bool FormFieldChoice::canBeSpellChecked() const { return false; } diff --git a/core/form.h b/core/form.h index bcc41de43..16acb8ffd 100644 --- a/core/form.h +++ b/core/form.h @@ -1,392 +1,392 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_FORM_H_ #define _OKULAR_FORM_H_ #include "okularcore_export.h" #include "area.h" #include "annotations.h" -#include +#include namespace Okular { class Action; class Page; class PagePrivate; class FormFieldPrivate; class FormFieldButtonPrivate; class FormFieldTextPrivate; class FormFieldChoicePrivate; /** * @short The base interface of a form field. * * This is the very basic interface to represent a field in a form. * * This is not meant to be used as a direct base for the form fields in a * document, but its abstract subclasses are. */ class OKULARCORE_EXPORT FormField { /// @cond PRIVATE friend class Page; friend class PagePrivate; /// @endcond public: /** * The types of form field. */ enum FieldType { FormButton, ///< A "button". See @ref FormFieldButton::ButtonType. FormText, ///< A field of variable text. See @ref FormFieldText::TextType. FormChoice, ///< A choice field. See @ref FormFieldChoice::ChoiceType. FormSignature ///< A signature. }; virtual ~FormField(); /** * The type of the field. */ FieldType type() const; /** * The bouding rect of the field, in normalized coordinates. */ virtual NormalizedRect rect() const = 0; /** * The ID of the field. */ virtual int id() const = 0; /** * The internal name of the field, to be used when referring to the * field in eg scripts. */ virtual QString name() const = 0; /** * The visible name of the field, to be used in the user interface * (eg in error messages, etc). */ virtual QString uiName() const = 0; /** * Whether the field is read-only. */ virtual bool isReadOnly() const; /** * Whether the field is read-only. * * @since 1.4 */ virtual void setReadOnly( bool value ); /** * Whether this form field is visible. */ virtual bool isVisible() const; /** * Whether the field is visible. * * @since 1.5 */ virtual void setVisible( bool value ); Action* activationAction() const; /** * Describes the type of form additional action. * * @since 1.1 */ enum AdditionalActionType { FieldModified, ///< An action to be performed when the user modifies the field FormatField, ///< An action to be performed before the field is formatted to display its value ValidateField, ///< An action to be performed when the field value changes CalculateField, ///< An action to be performed when the field needs to be recalculated }; /** * Returns the additional action of the given @p type or @c nullptr if no action has been defined. * * @since 1.1 */ Action* additionalAction( AdditionalActionType type ) const; /* Returns the additional action of the given @p type or @c nullptr if no action has been defined. * * This is for actions of annotation widgets associated with the FormField * * @since 1.5 */ Action* additionalAction( Annotation::AdditionalActionType type ) const; protected: /// @cond PRIVATE FormField( FormFieldPrivate &dd ); Q_DECLARE_PRIVATE( FormField ) FormFieldPrivate *d_ptr; /// @endcond void setActivationAction( Action *action ); void setAdditionalAction( AdditionalActionType type, Action *action ); void setAdditionalAction( Annotation::AdditionalActionType type, Action *action ); private: Q_DISABLE_COPY( FormField ) }; /** * @short Interface of a button form field. * * This is the base interface to reimplement to represent a button field, like * a push button, a check box or a radio button. * * @since 0.7 (KDE 4.1) */ class OKULARCORE_EXPORT FormFieldButton : public FormField { public: /** * The types of button field. */ enum ButtonType { Push, ///< A simple push button. CheckBox, ///< A check box. Radio ///< A radio button. }; virtual ~FormFieldButton(); /** The particular type of the button field. */ virtual ButtonType buttonType() const = 0; /** * The caption to be used for the button. */ virtual QString caption() const = 0; /** * The state of the button. */ virtual bool state() const = 0; /** * Sets the state of the button to the new \p state . */ virtual void setState( bool state ); /** * The list with the IDs of siblings (ie, buttons belonging to the same * group as the current one. * * Valid only for \ref Radio buttons, an empty list otherwise. */ virtual QList< int > siblings() const = 0; protected: FormFieldButton(); private: Q_DECLARE_PRIVATE( FormFieldButton ) Q_DISABLE_COPY( FormFieldButton ) }; /** * @short Interface of a text form field. * * This is the base interface to reimplement to represent a text field, ie a * field where the user insert text. */ class OKULARCORE_EXPORT FormFieldText : public FormField { public: /** * The types of text field. */ enum TextType { Normal, ///< A simple singleline text field. Multiline, ///< A multiline text field. FileSelect ///< An input field to select the path of a file on disk. }; virtual ~FormFieldText(); /** * The particular type of the text field. */ virtual TextType textType() const = 0; /** * The text of text field. */ virtual QString text() const = 0; /** * Sets the new @p text in the text field. * * The default implementation does nothing. * * Reimplemented only if the setting of new text is supported. */ virtual void setText( const QString& text ); /** * Whether this text field is a password input, eg its text @b must be * replaced with asterisks. * * Always false for @ref FileSelect text fields. */ virtual bool isPassword() const; /** * Whether this text field should allow rich text. */ virtual bool isRichText() const; /** * The maximum length allowed for the text of text field, or -1 if * there is no limitation for the text. */ virtual int maximumLength() const; /** * The alignment of the text within the field. */ virtual Qt::Alignment textAlignment() const; /** * Whether the text inserted manually in the field (where possible) * can be spell-checked. * * @note meaningful only if the field is editable. */ virtual bool canBeSpellChecked() const; protected: FormFieldText(); private: Q_DECLARE_PRIVATE( FormFieldText ) Q_DISABLE_COPY( FormFieldText ) }; /** * @short Interface of a choice form field. * * This is the base interface to reimplement to represent a choice field, ie a * field where the user can select one (of more) element(s) among a set of * choices. */ class OKULARCORE_EXPORT FormFieldChoice : public FormField { public: /** * The types of choice field. */ enum ChoiceType { ComboBox, ///< A combo box choice field. ListBox ///< A list box choice field. }; virtual ~FormFieldChoice(); /** * The particular type of the choice field. */ virtual ChoiceType choiceType() const = 0; /** * The possible choices of the choice field. */ virtual QStringList choices() const = 0; /** * Whether this ComboBox is editable, ie the user can type in a custom * value. * * Always false for the other types of choices. */ virtual bool isEditable() const; /** * Whether more than one choice of this ListBox can be selected at the * same time. * * Always false for the other types of choices. */ virtual bool multiSelect() const; /** * The currently selected choices. * * Always one element in the list in case of single choice elements. */ virtual QList< int > currentChoices() const = 0; /** * Sets the selected choices to @p choices . */ virtual void setCurrentChoices( const QList< int >& choices ); /** The text entered into an editable combo box choice field @since 0.16 (KDE 4.10) */ virtual QString editChoice() const; /** Sets the text entered into an editable combo box choice field @since 0.16 (KDE 4.10) */ virtual void setEditChoice( const QString& text ); /** * The alignment of the text within the field. */ virtual Qt::Alignment textAlignment() const; /** * Whether the text inserted manually in the field (where possible) * can be spell-checked. * * @note meaningful only if the field is editable. */ virtual bool canBeSpellChecked() const; protected: FormFieldChoice(); private: Q_DECLARE_PRIVATE( FormFieldChoice ) Q_DISABLE_COPY( FormFieldChoice ) }; } #endif diff --git a/core/form_p.h b/core/form_p.h index ff4e1211e..7e8389bf2 100644 --- a/core/form_p.h +++ b/core/form_p.h @@ -1,45 +1,45 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_FORM_P_H #define OKULAR_FORM_P_H #include "form.h" -#include +#include namespace Okular { class Action; class FormField; class FormFieldPrivate { public: FormFieldPrivate( FormField::FieldType type ); virtual ~FormFieldPrivate(); void setDefault(); virtual void setValue( const QString& ) = 0; virtual QString value() const = 0; FormField::FieldType m_type; QString m_default; Action *m_activateAction; QHash m_additionalActions; QHash m_additionalAnnotActions; Q_DECLARE_PUBLIC( FormField ) FormField *q_ptr; }; } #endif diff --git a/core/generator.cpp b/core/generator.cpp index 4c05620d6..f2af4612f 100644 --- a/core/generator.cpp +++ b/core/generator.cpp @@ -1,821 +1,821 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymanski * * Copyright (C) 2008 by Albert Astals Cid * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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 "generator.h" #include "generator_p.h" #include "observer.h" #include #include -#include +#include -#include +#include #include #include #include #include #ifdef WITH_KWALLET #include #endif #include "document.h" #include "document_p.h" #include "page.h" #include "page_p.h" #include "textpage.h" #include "utils.h" using namespace Okular; GeneratorPrivate::GeneratorPrivate() : m_document( nullptr ), mPixmapGenerationThread( nullptr ), mTextPageGenerationThread( nullptr ), m_mutex( nullptr ), m_threadsMutex( nullptr ), mPixmapReady( true ), mTextPageReady( true ), m_closing( false ), m_closingLoop( nullptr ), m_dpi(72.0, 72.0) { qRegisterMetaType(); } GeneratorPrivate::~GeneratorPrivate() { if ( mPixmapGenerationThread ) mPixmapGenerationThread->wait(); delete mPixmapGenerationThread; if ( mTextPageGenerationThread ) mTextPageGenerationThread->wait(); delete mTextPageGenerationThread; delete m_mutex; delete m_threadsMutex; } PixmapGenerationThread* GeneratorPrivate::pixmapGenerationThread() { if ( mPixmapGenerationThread ) return mPixmapGenerationThread; Q_Q( Generator ); mPixmapGenerationThread = new PixmapGenerationThread( q ); QObject::connect( mPixmapGenerationThread, SIGNAL(finished()), q, SLOT(pixmapGenerationFinished()), Qt::QueuedConnection ); return mPixmapGenerationThread; } TextPageGenerationThread* GeneratorPrivate::textPageGenerationThread() { if ( mTextPageGenerationThread ) return mTextPageGenerationThread; Q_Q( Generator ); mTextPageGenerationThread = new TextPageGenerationThread( q ); QObject::connect( mTextPageGenerationThread, SIGNAL(finished()), q, SLOT(textpageGenerationFinished()), Qt::QueuedConnection ); return mTextPageGenerationThread; } void GeneratorPrivate::pixmapGenerationFinished() { Q_Q( Generator ); PixmapRequest *request = mPixmapGenerationThread->request(); const QImage& img = mPixmapGenerationThread->image(); mPixmapGenerationThread->endGeneration(); QMutexLocker locker( threadsLock() ); if ( m_closing ) { mPixmapReady = true; delete request; if ( mTextPageReady ) { locker.unlock(); m_closingLoop->quit(); } return; } if ( !request->shouldAbortRender() ) { request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); const int pageNumber = request->page()->number(); if ( mPixmapGenerationThread->calcBoundingBox() ) q->updatePageBoundingBox( pageNumber, mPixmapGenerationThread->boundingBox() ); } else { // Cancel the text page generation too if it's still running if ( mTextPageGenerationThread && mTextPageGenerationThread->isRunning() ) { mTextPageGenerationThread->abortExtraction(); mTextPageGenerationThread->wait(); } } mPixmapReady = true; q->signalPixmapRequestDone( request ); } void GeneratorPrivate::textpageGenerationFinished() { Q_Q( Generator ); Page *page = mTextPageGenerationThread->page(); mTextPageGenerationThread->endGeneration(); QMutexLocker locker( threadsLock() ); mTextPageReady = true; if ( m_closing ) { delete mTextPageGenerationThread->textPage(); if ( mPixmapReady ) { locker.unlock(); m_closingLoop->quit(); } return; } if ( mTextPageGenerationThread->textPage() ) { TextPage *tp = mTextPageGenerationThread->textPage(); page->setTextPage( tp ); q->signalTextGenerationDone( page, tp ); } } QMutex* GeneratorPrivate::threadsLock() { if ( !m_threadsMutex ) m_threadsMutex = new QMutex(); return m_threadsMutex; } QVariant GeneratorPrivate::metaData( const QString &, const QVariant & ) const { return QVariant(); } QImage GeneratorPrivate::image( PixmapRequest * ) { return QImage(); } Generator::Generator(QObject* parent, const QVariantList &args) : Generator( *new GeneratorPrivate(), parent, args ) { // the delegated constructor does it all } Generator::Generator(GeneratorPrivate &dd, QObject *parent, const QVariantList &args) : QObject(parent), d_ptr(&dd) { d_ptr->q_ptr = this; Q_UNUSED(args) } Generator::~Generator() { delete d_ptr; } bool Generator::loadDocument( const QString & fileName, QVector< Page * > & pagesVector ) { Q_UNUSED(fileName); Q_UNUSED(pagesVector); return false; } bool Generator::loadDocumentFromData( const QByteArray &, QVector< Page * > & ) { return false; } Document::OpenResult Generator::loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString & ) { return loadDocument( fileName, pagesVector ) ? Document::OpenSuccess : Document::OpenError; } Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString & ) { return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError; } Generator::SwapBackingFileResult Generator::swapBackingFile( QString const &/*newFileName */, QVector & /*newPagesVector*/ ) { return SwapBackingFileError; } bool Generator::closeDocument() { Q_D( Generator ); d->m_closing = true; d->threadsLock()->lock(); if ( !( d->mPixmapReady && d->mTextPageReady ) ) { QEventLoop loop; d->m_closingLoop = &loop; d->threadsLock()->unlock(); loop.exec(); d->m_closingLoop = nullptr; } else { d->threadsLock()->unlock(); } bool ret = doCloseDocument(); d->m_closing = false; return ret; } bool Generator::canGeneratePixmap() const { Q_D( const Generator ); return d->mPixmapReady; } void Generator::generatePixmap( PixmapRequest *request ) { Q_D( Generator ); d->mPixmapReady = false; const bool calcBoundingBox = !request->isTile() && !request->page()->isBoundingBoxKnown(); if ( request->asynchronous() && hasFeature( Threaded ) ) { if ( d->textPageGenerationThread()->isFinished() && !canGenerateTextPage() ) { // It can happen that the text generation has already finished but // mTextPageReady is still false because textpageGenerationFinished // didn't have time to run, if so queue ourselves QTimer::singleShot(0, this, [this, request] { generatePixmap(request); }); return; } d->pixmapGenerationThread()->startGeneration( request, calcBoundingBox ); /** * We create the text page for every page that is visible to the * user, so he can use the text extraction tools without a delay. */ if ( hasFeature( TextExtraction ) && !request->page()->hasTextPage() && canGenerateTextPage() && !d->m_closing ) { d->mTextPageReady = false; d->textPageGenerationThread()->setPage( request->page() ); // dummy is used as a way to make sure the lambda gets disconnected each time it is executed // since not all the times the pixmap generation thread starts we want the text generation thread to also start QObject *dummy = new QObject(); connect(d_ptr->pixmapGenerationThread(), &QThread::started, dummy, [this, dummy] { delete dummy; d_ptr->textPageGenerationThread()->startGeneration(); }); } return; } const QImage& img = image( request ); request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); const int pageNumber = request->page()->number(); d->mPixmapReady = true; signalPixmapRequestDone( request ); if ( calcBoundingBox ) updatePageBoundingBox( pageNumber, Utils::imageBoundingBox( &img ) ); } bool Generator::canGenerateTextPage() const { Q_D( const Generator ); return d->mTextPageReady; } void Generator::generateTextPage( Page *page ) { TextRequest treq( page ); TextPage *tp = textPage( &treq ); page->setTextPage( tp ); signalTextGenerationDone( page, tp ); } QImage Generator::image( PixmapRequest *request ) { Q_D( Generator ); return d->image( request ); } TextPage* Generator::textPage( TextRequest * ) { return nullptr; } DocumentInfo Generator::generateDocumentInfo(const QSet &keys) const { Q_UNUSED(keys); return DocumentInfo(); } const DocumentSynopsis * Generator::generateDocumentSynopsis() { return nullptr; } FontInfo::List Generator::fontsForPage( int ) { return FontInfo::List(); } const QList * Generator::embeddedFiles() const { return nullptr; } Generator::PageSizeMetric Generator::pagesSizeMetric() const { return None; } bool Generator::isAllowed( Permission ) const { return true; } void Generator::rotationChanged( Rotation, Rotation ) { } PageSize::List Generator::pageSizes() const { return PageSize::List(); } void Generator::pageSizeChanged( const PageSize &, const PageSize & ) { } bool Generator::print( QPrinter& ) { return false; } Generator::PrintError Generator::printError() const { return UnknownPrintError; } void Generator::opaqueAction( const BackendOpaqueAction * /*action*/ ) { } QVariant Generator::metaData( const QString &key, const QVariant &option ) const { Q_D( const Generator ); return d->metaData( key, option ); } ExportFormat::List Generator::exportFormats() const { return ExportFormat::List(); } bool Generator::exportTo( const QString&, const ExportFormat& ) { return false; } void Generator::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const { #ifdef WITH_KWALLET *walletKey = fileName.section( QLatin1Char('/'), -1, -1); *walletName = KWallet::Wallet::NetworkWallet(); *walletFolder = QStringLiteral("KPdf"); #endif } bool Generator::hasFeature( GeneratorFeature feature ) const { Q_D( const Generator ); return d->m_features.contains( feature ); } void Generator::signalPixmapRequestDone( PixmapRequest * request ) { Q_D( Generator ); if ( d->m_document ) d->m_document->requestDone( request ); else { delete request; } } void Generator::signalTextGenerationDone( Page *page, TextPage *textPage ) { Q_D( Generator ); if ( d->m_document ) d->m_document->textGenerationDone( page ); else delete textPage; } void Generator::signalPartialPixmapRequest( PixmapRequest *request, const QImage &image ) { if ( request->shouldAbortRender() ) return; PagePrivate *pagePrivate = PagePrivate::get( request->page() ); pagePrivate->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( image ) ), request->normalizedRect(), true /* isPartialPixmap */ ); const int pageNumber = request->page()->number(); request->observer()->notifyPageChanged( pageNumber, Okular::DocumentObserver::Pixmap ); } const Document * Generator::document() const { Q_D( const Generator ); if ( d->m_document ) { return d->m_document->m_parent; } return nullptr; } void Generator::setFeature( GeneratorFeature feature, bool on ) { Q_D( Generator ); if ( on ) d->m_features.insert( feature ); else d->m_features.remove( feature ); } QVariant Generator::documentMetaData( const QString &key, const QVariant &option ) const { Q_D( const Generator ); if ( !d->m_document ) return QVariant(); if (key == QLatin1String("PaperColor")) return documentMetaData(PaperColorMetaData, option); if (key == QLatin1String("GraphicsAntialias")) return documentMetaData(GraphicsAntialiasMetaData, option); if (key == QLatin1String("TextAntialias")) return documentMetaData(TextAntialiasMetaData, option); if (key == QLatin1String("TextHinting")) return documentMetaData(TextHintingMetaData, option); return QVariant(); } QVariant Generator::documentMetaData( const DocumentMetaDataKey key, const QVariant &option ) const { Q_D( const Generator ); if ( !d->m_document ) return QVariant(); return d->m_document->documentMetaData( key, option ); } QMutex* Generator::userMutex() const { Q_D( const Generator ); if ( !d->m_mutex ) { d->m_mutex = new QMutex(); } return d->m_mutex; } void Generator::updatePageBoundingBox( int page, const NormalizedRect & boundingBox ) { Q_D( Generator ); if ( d->m_document ) // still connected to document? d->m_document->setPageBoundingBox( page, boundingBox ); } void Generator::requestFontData(const Okular::FontInfo & /*font*/, QByteArray * /*data*/) { } void Generator::setDPI(const QSizeF & dpi) { Q_D( Generator ); d->m_dpi = dpi; } QSizeF Generator::dpi() const { Q_D( const Generator ); return d->m_dpi; } QAbstractItemModel * Generator::layersModel() const { return nullptr; } TextRequest::TextRequest() : d( new TextRequestPrivate ) { d->mPage = nullptr; d->mShouldAbortExtraction = 0; } TextRequest::TextRequest( Page *page ) : d( new TextRequestPrivate ) { d->mPage = page; d->mShouldAbortExtraction = 0; } TextRequest::~TextRequest() { delete d; } Page *TextRequest::page() const { return d->mPage; } bool TextRequest::shouldAbortExtraction() const { return d->mShouldAbortExtraction != 0; } TextRequestPrivate *TextRequestPrivate::get(const TextRequest *req) { return req->d; } PixmapRequest::PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ) : d( new PixmapRequestPrivate ) { d->mObserver = observer; d->mPageNumber = pageNumber; d->mWidth = ceil(width * qApp->devicePixelRatio()); d->mHeight = ceil(height * qApp->devicePixelRatio()); d->mPriority = priority; d->mFeatures = features; d->mForce = false; d->mTile = false; d->mNormalizedRect = NormalizedRect(); d->mPartialUpdatesWanted = false; d->mShouldAbortRender = 0; } PixmapRequest::~PixmapRequest() { delete d; } DocumentObserver *PixmapRequest::observer() const { return d->mObserver; } int PixmapRequest::pageNumber() const { return d->mPageNumber; } int PixmapRequest::width() const { return d->mWidth; } int PixmapRequest::height() const { return d->mHeight; } int PixmapRequest::priority() const { return d->mPriority; } bool PixmapRequest::asynchronous() const { return d->mFeatures & Asynchronous; } bool PixmapRequest::preload() const { return d->mFeatures & Preload; } Page* PixmapRequest::page() const { return d->mPage; } void PixmapRequest::setTile( bool tile ) { d->mTile = tile; } bool PixmapRequest::isTile() const { return d->mTile; } void PixmapRequest::setNormalizedRect( const NormalizedRect &rect ) { if ( d->mNormalizedRect == rect ) return; d->mNormalizedRect = rect; } const NormalizedRect& PixmapRequest::normalizedRect() const { return d->mNormalizedRect; } void PixmapRequest::setPartialUpdatesWanted(bool partialUpdatesWanted) { d->mPartialUpdatesWanted = partialUpdatesWanted; } bool PixmapRequest::partialUpdatesWanted() const { return d->mPartialUpdatesWanted; } bool PixmapRequest::shouldAbortRender() const { return d->mShouldAbortRender != 0; } Okular::TilesManager* PixmapRequestPrivate::tilesManager() const { return mPage->d->tilesManager(mObserver); } PixmapRequestPrivate *PixmapRequestPrivate::get(const PixmapRequest *req) { return req->d; } void PixmapRequestPrivate::swap() { qSwap( mWidth, mHeight ); } class Okular::ExportFormatPrivate : public QSharedData { public: ExportFormatPrivate( const QString &description, const QMimeType &mimeType, const QIcon &icon = QIcon() ) : QSharedData(), mDescription( description ), mMimeType( mimeType ), mIcon( icon ) { } ~ExportFormatPrivate() { } QString mDescription; QMimeType mMimeType; QIcon mIcon; }; ExportFormat::ExportFormat() : d( new ExportFormatPrivate( QString(), QMimeType() ) ) { } ExportFormat::ExportFormat( const QString &description, const QMimeType &mimeType ) : d( new ExportFormatPrivate( description, mimeType ) ) { } ExportFormat::ExportFormat( const QIcon &icon, const QString &description, const QMimeType &mimeType ) : d( new ExportFormatPrivate( description, mimeType, icon ) ) { } ExportFormat::~ExportFormat() { } ExportFormat::ExportFormat( const ExportFormat &other ) : d( other.d ) { } ExportFormat& ExportFormat::operator=( const ExportFormat &other ) { if ( this == &other ) return *this; d = other.d; return *this; } QString ExportFormat::description() const { return d->mDescription; } QMimeType ExportFormat::mimeType() const { return d->mMimeType; } QIcon ExportFormat::icon() const { return d->mIcon; } bool ExportFormat::isNull() const { return !d->mMimeType.isValid() || d->mDescription.isNull(); } ExportFormat ExportFormat::standardFormat( StandardExportFormat type ) { QMimeDatabase db; switch ( type ) { case PlainText: return ExportFormat( QIcon::fromTheme( QStringLiteral("text-x-generic") ), i18n( "Plain &Text..." ), db.mimeTypeForName( QStringLiteral("text/plain") ) ); break; case PDF: return ExportFormat( QIcon::fromTheme( QStringLiteral("application-pdf") ), i18n( "PDF" ), db.mimeTypeForName( QStringLiteral("application/pdf") ) ); break; case OpenDocumentText: return ExportFormat( QIcon::fromTheme( QStringLiteral("application-vnd.oasis.opendocument.text") ), i18nc( "This is the document format", "OpenDocument Text" ), db.mimeTypeForName( QStringLiteral("application/vnd.oasis.opendocument.text") ) ); break; case HTML: return ExportFormat( QIcon::fromTheme( QStringLiteral("text-html") ), i18nc( "This is the document format", "HTML" ), db.mimeTypeForName( QStringLiteral("text/html") ) ); break; } return ExportFormat(); } bool ExportFormat::operator==( const ExportFormat &other ) const { return d == other.d; } bool ExportFormat::operator!=( const ExportFormat &other ) const { return d != other.d; } QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ) { PixmapRequestPrivate *reqPriv = PixmapRequestPrivate::get(&req); str << "PixmapRequest:" << &req; str << "- observer:" << (qulonglong)req.observer(); str << "- page:" << req.pageNumber(); str << "- width:" << req.width(); str << "- height:" << req.height(); str << "- priority:" << req.priority(); str << "- async:" << ( req.asynchronous() ? "true" : "false" ); str << "- tile:" << ( req.isTile() ? "true" : "false" ); str << "- rect:" << req.normalizedRect(); str << "- preload:" << ( req.preload() ? "true" : "false" ); str << "- partialUpdates:" << ( req.partialUpdatesWanted() ? "true" : "false" ); str << "- shouldAbort:" << ( req.shouldAbortRender() ? "true" : "false" ); str << "- force:" << ( reqPriv->mForce ? "true" : "false" ); return str; } #include "moc_generator.cpp" /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/generator.h b/core/generator.h index 3146b6b07..ceaa43ced 100644 --- a/core/generator.h +++ b/core/generator.h @@ -1,824 +1,824 @@ /*************************************************************************** * Copyright (C) 2004-5 by Enrico Ros * * Copyright (C) 2005 by Piotr Szymanski * * Copyright (C) 2008 by Albert Astals Cid * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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_GENERATOR_H_ #define _OKULAR_GENERATOR_H_ #include "okularcore_export.h" #include "document.h" #include "fontinfo.h" #include "global.h" #include "pagesize.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #define OKULAR_EXPORT_PLUGIN(classname, json ) \ static_assert(json[0] != '\0', "arg2 must be a string literal"); \ K_PLUGIN_FACTORY_WITH_JSON(classname ## Factory, json, registerPlugin();) class QByteArray; class QMutex; class QPrinter; class QPrintDialog; class QIcon; namespace Okular { class BackendOpaqueAction; class DocumentFonts; class DocumentInfo; class DocumentObserver; class DocumentSynopsis; class EmbeddedFile; class ExportFormatPrivate; class FontInfo; class GeneratorPrivate; class Page; class PixmapRequest; class PixmapRequestPrivate; class TextPage; class TextRequest; class TextRequestPrivate; class NormalizedRect; class SourceReference; /* Note: on contents generation and asynchronous queries. * Many observers may want to request data syncronously or asynchronously. * - Sync requests. These should be done in-place. * - Async request must be done in real background. That usually means a * thread, such as QThread derived classes. * Once contents are available, they must be immediately stored in the * Page they refer to, and a signal is emitted as soon as storing * (even for sync or async queries) has been done. */ /** * @short Defines an entry for the export menu * * This class encapsulates information about an export format. * Every Generator can support 0 or more export formats which can be * queried with @ref Generator::exportFormats(). */ class OKULARCORE_EXPORT ExportFormat { public: typedef QList List; /** * Creates an empty export format. * * @see isNull() */ ExportFormat(); /** * Creates a new export format. * * @param description The i18n'ed description of the format. * @param mimeType The supported mime type of the format. */ ExportFormat( const QString &description, const QMimeType &mimeType ); /** * Creates a new export format. * * @param icon The icon used in the GUI for this format. * @param description The i18n'ed description of the format. * @param mimeType The supported mime type of the format. */ ExportFormat( const QIcon &icon, const QString &description, const QMimeType &mimeType ); /** * Destroys the export format. */ ~ExportFormat(); /** * @internal */ ExportFormat( const ExportFormat &other ); /** * @internal */ ExportFormat& operator=( const ExportFormat &other ); /** * Returns the description of the format. */ QString description() const; /** * Returns the mime type of the format. */ QMimeType mimeType() const; /** * Returns the icon for GUI representations of the format. */ QIcon icon() const; /** * Returns whether the export format is null/valid. * * An ExportFormat is null if the mimetype is not valid or the * description is empty, or both. */ bool isNull() const; /** * Type of standard export format. */ enum StandardExportFormat { PlainText, ///< Plain text PDF, ///< PDF, aka Portable Document Format OpenDocumentText, ///< OpenDocument Text format @since 0.8 (KDE 4.2) HTML ///< OpenDocument Text format @since 0.8 (KDE 4.2) }; /** * Builds a standard format for the specified @p type . */ static ExportFormat standardFormat( StandardExportFormat type ); bool operator==( const ExportFormat &other ) const; bool operator!=( const ExportFormat &other ) const; private: /// @cond PRIVATE friend class ExportFormatPrivate; /// @endcond QSharedDataPointer d; }; /** * @short [Abstract Class] The information generator. * * Most of class members are virtuals and some of them pure virtual. The pure * virtuals provide the minimal functionalities for a Generator, that is being * able to generate QPixmap for the Page 's of the Document. * * Implementing the other functions will make the Generator able to provide * more contents and/or functionalities (like text extraction). * * Generation/query is requested by the Document class only, and that * class stores the resulting data into Page s. The data will then be * displayed by the GUI components (PageView, ThumbnailList, etc..). * * @see PrintInterface, ConfigInterface, GuiInterface */ class OKULARCORE_EXPORT Generator : public QObject { /// @cond PRIVATE friend class PixmapGenerationThread; friend class TextPageGenerationThread; /// @endcond Q_OBJECT public: /** * Describe the possible optional features that a Generator can * provide. */ enum GeneratorFeature { Threaded, TextExtraction, ///< Whether the Generator can extract text from the document in the form of TextPage's ReadRawData, ///< Whether the Generator can read a document directly from its raw data. FontInfo, ///< Whether the Generator can provide information about the fonts used in the document PageSizes, ///< Whether the Generator can change the size of the document pages. PrintNative, ///< Whether the Generator supports native cross-platform printing (QPainter-based). PrintPostscript, ///< Whether the Generator supports postscript-based file printing. PrintToFile, ///< Whether the Generator supports export to PDF & PS through the Print Dialog TiledRendering, ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) SwapBackingFile, ///< Whether the Generator can hot-swap the file it's reading from @since 1.3 SupportsCancelling ///< Whether the Generator can cancel requests @since 1.4 }; /** * Creates a new generator. */ Generator(QObject* parent = nullptr, const QVariantList& args = QVariantList()); /** * Destroys the generator. */ virtual ~Generator(); /** * Loads the document with the given @p fileName and fills the * @p pagesVector with the parsed pages. * * @note If you implement the WithPassword variants you don't need to implement this one * * @returns true on success, false otherwise. */ virtual bool loadDocument( const QString & fileName, QVector< Page * > & pagesVector ); /** * Loads the document from the raw data @p fileData and fills the * @p pagesVector with the parsed pages. * * @note If you implement the WithPassword variants you don't need to implement this one * * @note the Generator has to have the feature @ref ReadRawData enabled * * @returns true on success, false otherwise. */ virtual bool loadDocumentFromData( const QByteArray & fileData, QVector< Page * > & pagesVector ); /** * Loads the document with the given @p fileName and @p password and fills the * @p pagesVector with the parsed pages. * * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocument() * * @since 0.20 (KDE 4.14) * * @returns a LoadResult defining the result of the operation */ virtual Document::OpenResult loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString &password ); /** * Loads the document from the raw data @p fileData and @p password and fills the * @p pagesVector with the parsed pages. * * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocumentFromData() * * @note the Generator has to have the feature @ref ReadRawData enabled * * @since 0.20 (KDE 4.14) * * @returns a LoadResult defining the result of the operation */ virtual Document::OpenResult loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString &password ); /** * Describes the result of an swap file operation. * * @since 1.3 */ enum SwapBackingFileResult { SwapBackingFileError, //< The document could not be swapped SwapBackingFileNoOp, //< The document was swapped and nothing needs to be done SwapBackingFileReloadInternalData //< The document was swapped and internal data (forms, annotations, etc) needs to be reloaded }; /** * Changes the path of the file we are reading from. The new path must * point to a copy of the same document. * * @note the Generator has to have the feature @ref SwapBackingFile enabled * * @since 1.3 */ virtual SwapBackingFileResult swapBackingFile( const QString & newFileName, QVector & newPagesVector ); /** * This method is called when the document is closed and not used * any longer. * * @returns true on success, false otherwise. */ bool closeDocument(); /** * This method returns whether the generator is ready to * handle a new pixmap request. */ virtual bool canGeneratePixmap() const; /** * This method can be called to trigger the generation of * a new pixmap as described by @p request. */ virtual void generatePixmap( PixmapRequest * request ); /** * This method returns whether the generator is ready to * handle a new text page request. */ virtual bool canGenerateTextPage() const; /** * This method can be called to trigger the generation of * a text page for the given @p page. * * The generation is done in the calling thread. * * @see TextPage */ void generateTextPage( Page * page ); /** * Returns the general information object of the document. * * Changed signature in okular version 0.21 */ virtual DocumentInfo generateDocumentInfo( const QSet &keys ) const; /** * Returns the 'table of content' object of the document or 0 if * no table of content is available. */ virtual const DocumentSynopsis * generateDocumentSynopsis(); /** * Returns the 'list of embedded fonts' object of the specified \page * of the document. * * \param page a page of the document, starting from 0 - -1 indicates all * the other fonts */ virtual FontInfo::List fontsForPage( int page ); /** * Returns the 'list of embedded files' object of the document or 0 if * no list of embedded files is available. */ virtual const QList * embeddedFiles() const; /** * This enum identifies the metric of the page size. */ enum PageSizeMetric { None, ///< The page size is not defined in a physical metric. Points, ///< The page size is given in 1/72 inches. Pixels ///< The page size is given in screen pixels @since 0.19 (KDE 4.13) }; /** * This method returns the metric of the page size. Default is @ref None. */ virtual PageSizeMetric pagesSizeMetric() const; /** * This method returns whether given @p action (@ref Permission) is * allowed in this document. */ virtual bool isAllowed( Permission action ) const; /** * This method is called when the orientation has been changed by the user. */ virtual void rotationChanged( Rotation orientation, Rotation oldOrientation ); /** * Returns the list of supported page sizes. */ virtual PageSize::List pageSizes() const; /** * This method is called when the page size has been changed by the user. */ virtual void pageSizeChanged( const PageSize &pageSize, const PageSize &oldPageSize ); /** * This method is called to print the document to the given @p printer. */ virtual bool print( QPrinter &printer ); /** * Possible print errors * @since 0.11 (KDE 4.5) */ enum PrintError { NoPrintError, ///< There was no print error UnknownPrintError, TemporaryFileOpenPrintError, FileConversionPrintError, PrintingProcessCrashPrintError, PrintingProcessStartPrintError, PrintToFilePrintError, InvalidPrinterStatePrintError, UnableToFindFilePrintError, NoFileToPrintError, NoBinaryToPrintError, InvalidPageSizePrintError ///< @since 0.18.2 (KDE 4.12.2) }; /** * This method returns the meta data of the given @p key with the given @p option * of the document. */ virtual QVariant metaData( const QString &key, const QVariant &option ) const; /** * Returns the list of additional supported export formats. */ virtual ExportFormat::List exportFormats() const; /** * This method is called to export the document in the given @p format and save it * under the given @p fileName. The format must be one of the supported export formats. */ virtual bool exportTo( const QString &fileName, const ExportFormat &format ); /** * This method is called to know which wallet data should be used for the given file name. * Unless you have very special requirements to where wallet data should be stored you * don't need to reimplement this method. */ virtual void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const; /** * Query for the specified @p feature. */ bool hasFeature( GeneratorFeature feature ) const; /** * Update DPI of the generator * * @since 0.19 (KDE 4.13) */ void setDPI(const QSizeF &dpi); /** * Returns the 'layers model' object of the document or NULL if * layers model is not available. * * @since 0.24 */ virtual QAbstractItemModel * layersModel() const; /** * Calls the backend to execute an BackendOpaqueAction */ virtual void opaqueAction( const BackendOpaqueAction *action ); Q_SIGNALS: /** * This signal should be emitted whenever an error occurred in the generator. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void error( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be warned. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void warning( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be noticed. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void notice( const QString &message, int duration ); protected: /** * This method must be called when the pixmap request triggered by generatePixmap() * has been finished. */ void signalPixmapRequestDone( PixmapRequest * request ); /** * This method must be called when a text generation has been finished. */ void signalTextGenerationDone( Page *page, TextPage *textPage ); /** * This method is called when the document is closed and not used * any longer. * * @returns true on success, false otherwise. */ virtual bool doCloseDocument() = 0; /** * Returns the image of the page as specified in * the passed pixmap @p request. * * Must return a null image if the request was cancelled and the generator supports cancelling * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! */ virtual QImage image( PixmapRequest *page ); /** * Returns the text page for the given @p request. * * Must return a null pointer if the request was cancelled and the generator supports cancelling * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! * * @since 1.4 */ virtual TextPage* textPage( TextRequest *request ); /** * Returns a pointer to the document. */ const Document * document() const; /** * Toggle the @p feature . */ void setFeature( GeneratorFeature feature, bool on = true ); /** * Internal document setting */ enum DocumentMetaDataKey { PaperColorMetaData, ///< Returns (QColor) the paper color if set in Settings or the default color (white) if option is true (otherwise returns a non initialized QColor) TextAntialiasMetaData, ///< Returns (bool) text antialias from Settings (option is not used) GraphicsAntialiasMetaData, ///< Returns (bool)graphic antialias from Settings (option is not used) TextHintingMetaData ///< Returns (bool)text hinting from Settings (option is not used) }; /** * Request a meta data of the Document, if available, like an internal * setting. * * @since 1.1 */ QVariant documentMetaData( const DocumentMetaDataKey key, const QVariant &option = QVariant() ) const; /** * Request a meta data of the Document, if available, like an internal * setting. */ OKULARCORE_DEPRECATED QVariant documentMetaData( const QString &key, const QVariant &option = QVariant() ) const; /** * Return the pointer to a mutex the generator can use freely. */ QMutex* userMutex() const; /** * Set the bounding box of a page after the page has already been handed * to the Document. Call this instead of Page::setBoundingBox() to ensure * that all observers are notified. * * @since 0.7 (KDE 4.1) */ void updatePageBoundingBox( int page, const NormalizedRect & boundingBox ); /** * Returns DPI, previously set via setDPI() * @since 0.19 (KDE 4.13) */ QSizeF dpi() const; protected Q_SLOTS: /** * Gets the font data for the given font * * @since 0.8 (KDE 4.1) */ void requestFontData(const Okular::FontInfo &font, QByteArray *data); /** * Returns the last print error in case print() failed * @since 0.11 (KDE 4.5) */ Okular::Generator::PrintError printError() const; /** * This method can be called to trigger a partial pixmap update for the given request * Make sure you call it in a way it's executed in the main thread. * @since 1.3 */ void signalPartialPixmapRequest( Okular::PixmapRequest *request, const QImage &image ); protected: /// @cond PRIVATE Generator(GeneratorPrivate &dd, QObject *parent, const QVariantList &args); Q_DECLARE_PRIVATE( Generator ) GeneratorPrivate *d_ptr; friend class Document; friend class DocumentPrivate; /// @endcond PRIVATE private: Q_DISABLE_COPY( Generator ) Q_PRIVATE_SLOT( d_func(), void pixmapGenerationFinished() ) Q_PRIVATE_SLOT( d_func(), void textpageGenerationFinished() ) }; /** * @short Describes a pixmap type request. */ class OKULARCORE_EXPORT PixmapRequest { friend class Document; friend class DocumentPrivate; public: enum PixmapRequestFeature { NoFeature = 0, Asynchronous = 1, Preload = 2 }; Q_DECLARE_FLAGS( PixmapRequestFeatures, PixmapRequestFeature ) /** * Creates a new pixmap request. * * @param observer The observer. * @param pageNumber The page number. * @param width The width of the page. * @param height The height of the page. * @param priority The priority of the request. * @param features The features of generation. */ PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ); /** * Destroys the pixmap request. */ ~PixmapRequest(); /** * Returns the observer of the request. */ DocumentObserver *observer() const; /** * Returns the page number of the request. */ int pageNumber() const; /** * Returns the page width of the requested pixmap. */ int width() const; /** * Returns the page height of the requested pixmap. */ int height() const; /** * Returns the priority (less it better, 0 is maximum) of the * request. */ int priority() const; /** * Returns whether the generation should be done synchronous or * asynchronous. * * If asynchronous, the pixmap is created in a thread and the observer * is notified when the job is done. */ bool asynchronous() const; /** * Returns whether the generation request is for a page that is not important * i.e. it's just for speeding up future rendering */ bool preload() const; /** * Returns a pointer to the page where the pixmap shall be generated for. */ Page *page() const; /** * Sets whether the generator should render only the given normalized * rect or the entire page * * @since 0.16 (KDE 4.10) */ void setTile( bool tile ); /** * Returns whether the generator should render just the region given by * normalizedRect() or the entire page. * * @since 0.16 (KDE 4.10) */ bool isTile() const; /** * Sets the region of the page to request. * * @since 0.16 (KDE 4.10) */ void setNormalizedRect( const NormalizedRect &rect ); /** * Returns the normalized region of the page to request. * * @since 0.16 (KDE 4.10) */ const NormalizedRect& normalizedRect() const; /** * Sets whether the request should report back updates if possible * * @since 1.3 */ void setPartialUpdatesWanted(bool partialUpdatesWanted); /** * Should the request report back updates if possible? * * @since 1.3 */ bool partialUpdatesWanted() const; /** * Should the request be aborted if possible? * * @since 1.4 */ bool shouldAbortRender() const; private: Q_DISABLE_COPY( PixmapRequest ) friend class PixmapRequestPrivate; PixmapRequestPrivate* const d; }; /** * @short Describes a text request. * * @since 1.4 */ class OKULARCORE_EXPORT TextRequest { public: /** * Creates a new text request. */ TextRequest( Page *page ); TextRequest(); /** * Destroys the pixmap request. */ ~TextRequest(); /** * Returns a pointer to the page where the pixmap shall be generated for. */ Page *page() const; /** * Should the request be aborted if possible? */ bool shouldAbortExtraction() const; private: Q_DISABLE_COPY( TextRequest ) friend TextRequestPrivate; TextRequestPrivate* const d; }; } Q_DECLARE_METATYPE(Okular::Generator::PrintError) Q_DECLARE_METATYPE(Okular::PixmapRequest*) #define OkularGeneratorInterface_iid "org.kde.okular.Generator" Q_DECLARE_INTERFACE(Okular::Generator, OkularGeneratorInterface_iid) #ifndef QT_NO_DEBUG_STREAM OKULARCORE_EXPORT QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ); #endif #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/generator_p.cpp b/core/generator_p.cpp index f884bf34e..57945c08d 100644 --- a/core/generator_p.cpp +++ b/core/generator_p.cpp @@ -1,178 +1,178 @@ /*************************************************************************** * Copyright (C) 2007 Tobias Koenig * * * * 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 "generator_p.h" -#include +#include #include "fontinfo.h" #include "generator.h" #include "utils.h" using namespace Okular; PixmapGenerationThread::PixmapGenerationThread( Generator *generator ) : mGenerator( generator ), mRequest( nullptr ), mCalcBoundingBox( false ) { } void PixmapGenerationThread::startGeneration( PixmapRequest *request, bool calcBoundingBox ) { mRequest = request; mCalcBoundingBox = calcBoundingBox; start( QThread::InheritPriority ); } void PixmapGenerationThread::endGeneration() { mRequest = nullptr; } PixmapRequest *PixmapGenerationThread::request() const { return mRequest; } QImage PixmapGenerationThread::image() const { return mRequest ? PixmapRequestPrivate::get(mRequest)->mResultImage : QImage(); } bool PixmapGenerationThread::calcBoundingBox() const { return mCalcBoundingBox; } NormalizedRect PixmapGenerationThread::boundingBox() const { return mBoundingBox; } void PixmapGenerationThread::run() { if ( mRequest ) { PixmapRequestPrivate::get(mRequest)->mResultImage = mGenerator->image( mRequest ); if ( mCalcBoundingBox ) mBoundingBox = Utils::imageBoundingBox( &PixmapRequestPrivate::get(mRequest)->mResultImage ); } } TextPageGenerationThread::TextPageGenerationThread( Generator *generator ) : mGenerator( generator ), mTextPage( nullptr ) { TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); treqPriv->mPage = nullptr; treqPriv->mShouldAbortExtraction = 0; } void TextPageGenerationThread::startGeneration() { if ( page() ) { start( QThread::InheritPriority ); } } void TextPageGenerationThread::endGeneration() { TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); treqPriv->mPage = nullptr; treqPriv->mShouldAbortExtraction = 0; } void TextPageGenerationThread::setPage( Page *page ) { TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); treqPriv->mPage = page; treqPriv->mShouldAbortExtraction = 0; } Page *TextPageGenerationThread::page() const { return mTextRequest.page(); } TextPage* TextPageGenerationThread::textPage() const { return mTextPage; } void TextPageGenerationThread::abortExtraction() { // If extraction already finished no point in aborting if ( !mTextPage ) { TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); treqPriv->mShouldAbortExtraction = 1; } } bool TextPageGenerationThread::shouldAbortExtraction() const { return mTextRequest.shouldAbortExtraction(); } void TextPageGenerationThread::run() { mTextPage = nullptr; Q_ASSERT ( page() ); mTextPage = mGenerator->textPage( &mTextRequest ); if ( mTextRequest.shouldAbortExtraction() ) { delete mTextPage; mTextPage = nullptr; } } FontExtractionThread::FontExtractionThread( Generator *generator, int pages ) : mGenerator( generator ), mNumOfPages( pages ), mGoOn( true ) { } void FontExtractionThread::startExtraction( bool async ) { if ( async ) { connect(this, &FontExtractionThread::finished, this, &FontExtractionThread::deleteLater); start( QThread::InheritPriority ); } else { run(); deleteLater(); } } void FontExtractionThread::stopExtraction() { mGoOn = false; } void FontExtractionThread::run() { for ( int i = -1; i < mNumOfPages && mGoOn; ++i ) { FontInfo::List list = mGenerator->fontsForPage( i ); foreach ( const FontInfo& fi, list ) { emit gotFont( fi ); } emit progress( i ); } } #include "moc_generator_p.cpp" diff --git a/core/generator_p.h b/core/generator_p.h index 8e1f05e47..62858a9eb 100644 --- a/core/generator_p.h +++ b/core/generator_p.h @@ -1,196 +1,196 @@ /*************************************************************************** * Copyright (C) 2007 Tobias Koenig * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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_THREADEDGENERATOR_P_H #define OKULAR_THREADEDGENERATOR_P_H #include "area.h" -#include -#include -#include +#include +#include +#include class QEventLoop; class QMutex; #include "generator.h" #include "page.h" namespace Okular { class DocumentObserver; class DocumentPrivate; class FontInfo; class Generator; class Page; class PixmapGenerationThread; class PixmapRequest; class TextPage; class TextPageGenerationThread; class TilesManager; class GeneratorPrivate { public: GeneratorPrivate(); virtual ~GeneratorPrivate(); Q_DECLARE_PUBLIC( Generator ) Generator *q_ptr; PixmapGenerationThread* pixmapGenerationThread(); TextPageGenerationThread* textPageGenerationThread(); void pixmapGenerationFinished(); void textpageGenerationFinished(); QMutex* threadsLock(); virtual QVariant metaData( const QString &key, const QVariant &option ) const; virtual QImage image( PixmapRequest * ); DocumentPrivate *m_document; // NOTE: the following should be a QSet< GeneratorFeature >, // but it is not to avoid #include'ing generator.h QSet< int > m_features; PixmapGenerationThread *mPixmapGenerationThread; TextPageGenerationThread *mTextPageGenerationThread; mutable QMutex *m_mutex; QMutex *m_threadsMutex; bool mPixmapReady : 1; bool mTextPageReady : 1; bool m_closing : 1; QEventLoop *m_closingLoop; QSizeF m_dpi; }; class PixmapRequestPrivate { public: void swap(); TilesManager *tilesManager() const; static PixmapRequestPrivate *get(const PixmapRequest *req); DocumentObserver *mObserver; int mPageNumber; int mWidth; int mHeight; int mPriority; int mFeatures; bool mForce : 1; bool mTile : 1; bool mPartialUpdatesWanted : 1; Page *mPage; NormalizedRect mNormalizedRect; QAtomicInt mShouldAbortRender; QImage mResultImage; }; class TextRequestPrivate { public: static TextRequestPrivate *get(const TextRequest *req); Page *mPage; QAtomicInt mShouldAbortExtraction; }; class PixmapGenerationThread : public QThread { Q_OBJECT public: PixmapGenerationThread( Generator *generator ); void startGeneration( PixmapRequest *request, bool calcBoundingRect ); void endGeneration(); PixmapRequest *request() const; QImage image() const; bool calcBoundingBox() const; NormalizedRect boundingBox() const; protected: void run() override; private: Generator *mGenerator; PixmapRequest *mRequest; NormalizedRect mBoundingBox; bool mCalcBoundingBox : 1; }; class TextPageGenerationThread : public QThread { Q_OBJECT public: TextPageGenerationThread( Generator *generator ); void endGeneration(); void setPage( Page *page ); Page *page() const; TextPage* textPage() const; void abortExtraction(); bool shouldAbortExtraction() const; public slots: void startGeneration(); protected: void run() override; private: Generator *mGenerator; TextPage *mTextPage; TextRequest mTextRequest; }; class FontExtractionThread : public QThread { Q_OBJECT public: FontExtractionThread( Generator *generator, int pages ); void startExtraction( bool async ); void stopExtraction(); Q_SIGNALS: void gotFont( const Okular::FontInfo& ); void progress( int page ); protected: void run() override; private: Generator *mGenerator; int mNumOfPages; bool mGoOn; }; } Q_DECLARE_METATYPE(Okular::Page*) #endif diff --git a/core/global.h b/core/global.h index 24cef77b4..93d76fe2f 100644 --- a/core/global.h +++ b/core/global.h @@ -1,83 +1,83 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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_GLOBAL_H #define OKULAR_GLOBAL_H -#include +#include namespace Okular { /** * Describes the DRM capabilities. */ enum Permission { AllowModify = 1, ///< Allows to modify the document AllowCopy = 2, ///< Allows to copy the document AllowPrint = 4, ///< Allows to print the document AllowNotes = 8, ///< Allows to add annotations to the document AllowFillForms = 16 ///< Allows to fill the forms in the document }; Q_DECLARE_FLAGS( Permissions, Permission ) /** * Describes the direction of searching. */ enum SearchDirection { FromTop, ///< Searching from top of the page, next result is to be found, there was no earlier search result. FromBottom, ///< Searching from bottom of the page, next result is to be found, there was no earlier search result. NextResult, ///< Searching for the next result on the page, earlier result should be located so we search from the last result not from the beginning of the page. PreviousResult ///< Searching for the previous result on the page, earlier result should be located so we search from the last result not from the beginning of the page. }; /** * A rotation. */ enum Rotation { Rotation0 = 0, ///< Not rotated. Rotation90 = 1, ///< Rotated 90 degrees clockwise. Rotation180 = 2, ///< Rotated 180 degrees clockwise. Rotation270 = 3 ///< Rotated 2700 degrees clockwise. }; /** * Describes the type of generation of objects */ enum GenerationType { Synchronous, ///< Will create the object in a synchronous way Asynchronous ///< Will create the object in an asynchronous way }; /** * The side(s) to be considered when merging areas. */ enum MergeSide { MergeRight = 0, ///< Merge only if the right side of the first area intersect. MergeBottom = 1, ///< Merge only if the bottom side of the first area intersect. MergeLeft = 2, ///< Merge only if the left side of the first area intersect. MergeTop = 3, ///< Merge only if the top side of the first area intersect. MergeAll = 4 ///< Merge if the areas intersects, no matter which side(s). }; /** * Describes the possible script types. */ enum ScriptType { JavaScript = 0 ///< JavaScript code }; } #endif diff --git a/core/misc.cpp b/core/misc.cpp index 36b99e28a..17bdb7966 100644 --- a/core/misc.cpp +++ b/core/misc.cpp @@ -1,89 +1,89 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymanski * * * * 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 "core/misc.h" -#include +#include #include "debug_p.h" using namespace Okular; class TextSelection::Private { public: int direction; int it[2]; NormalizedPoint cur[2]; }; TextSelection::TextSelection( const NormalizedPoint & a, const NormalizedPoint & b ) : d( new Private ) { if (b.y-a.y<0 || (b.y-a.y==0 && b.x-a.x <0)) d->direction = 1; else d->direction = 0; d->cur[0] = a; d->cur[1] = b; d->it[d->direction % 2] = -1; d->it[(d->direction + 1) % 2] = -1; } TextSelection::~TextSelection() { delete d; } void TextSelection::end( const NormalizedPoint & p ) { // changing direction as in 2b , assuming the bool->int conversion is correct int dir1 = d->direction; d->direction = (p.y - d->cur[0].y < 0 || (p.y - d->cur[0].y == 0 && p.x - d->cur[0].x < 0)); if (d->direction != dir1) qCDebug(OkularCoreDebug) << "changing direction in selection"; d->cur[1] = p; } void TextSelection::itE( int p ) { d->it[(d->direction + 1) % 2] = p; } void TextSelection::itB( int p ) { d->it[(d->direction) % 2] = p; } int TextSelection::direction() const { return d->direction; } NormalizedPoint TextSelection::start() const { return d->cur[d->direction % 2]; } NormalizedPoint TextSelection::end() const { return d->cur[(d->direction + 1) % 2]; } int TextSelection::itB() const { return d->it[d->direction % 2]; } int TextSelection::itE() const { return d->it[(d->direction + 1) % 2]; } diff --git a/core/movie.cpp b/core/movie.cpp index 55de744b7..bf792b15f 100644 --- a/core/movie.cpp +++ b/core/movie.cpp @@ -1,167 +1,167 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2012 by Guillermo A. Amaral B. * * * * 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 "movie.h" // qt/kde includes #include #include #include #include -#include +#include #include "debug_p.h" using namespace Okular; class Movie::Private { public: Private( const QString &url ) : m_url( url ), m_rotation( Rotation0 ), m_playMode( PlayLimited ), m_playRepetitions( 1.0 ), m_tmp( nullptr ), m_showControls( false ), m_autoPlay( false ), m_showPosterImage( false ) { } QString m_url; QSize m_aspect; Rotation m_rotation; PlayMode m_playMode; double m_playRepetitions; QTemporaryFile *m_tmp; QImage m_posterImage; bool m_showControls : 1; bool m_autoPlay : 1; bool m_showPosterImage : 1; }; Movie::Movie( const QString& fileName ) : d( new Private( fileName ) ) { } Movie::Movie( const QString& fileName, const QByteArray &data ) : d( new Private( fileName ) ) { /* Store movie data as temporary file. * * Originally loaded movie data directly using a QBuffer, but sadly phonon * fails to play on a few of my test systems (I think it's the Phonon * GStreamer backend). Storing the data in a temporary file works fine * though, not to mention, it releases much needed memory. (gamaral) */ d->m_tmp = new QTemporaryFile( QStringLiteral( "%1/okrXXXXXX" ).arg( QDir::tempPath() ) ); if ( d->m_tmp->open() ) { d->m_tmp->write( data ); d->m_tmp->flush(); } else qCDebug(OkularCoreDebug) << "Failed to create temporary file for video data."; } Movie::~Movie() { delete d->m_tmp; delete d; } QString Movie::url() const { if (d->m_tmp) return d->m_tmp->fileName(); else return d->m_url; } void Movie::setSize( const QSize &aspect ) { d->m_aspect = aspect; } QSize Movie::size() const { return d->m_aspect; } void Movie::setRotation( Rotation rotation ) { d->m_rotation = rotation; } Rotation Movie::rotation() const { return d->m_rotation; } void Movie::setShowControls( bool show ) { d->m_showControls = show; } bool Movie::showControls() const { return d->m_showControls; } void Movie::setPlayMode( Movie::PlayMode mode ) { d->m_playMode = mode; } Movie::PlayMode Movie::playMode() const { return d->m_playMode; } void Movie::setPlayRepetitions( double repetitions ) { d->m_playRepetitions = repetitions; } double Movie::playRepetitions() const { return d->m_playRepetitions; } void Movie::setAutoPlay( bool autoPlay ) { d->m_autoPlay = autoPlay; } bool Movie::autoPlay() const { return d->m_autoPlay; } void Movie::setShowPosterImage( bool show ) { d->m_showPosterImage = show; } bool Movie::showPosterImage() const { return d->m_showPosterImage; } void Movie::setPosterImage( const QImage &image ) { d->m_posterImage = image; } QImage Movie::posterImage() const { return d->m_posterImage; } diff --git a/core/movie.h b/core/movie.h index eecf0f573..1779347ec 100644 --- a/core/movie.h +++ b/core/movie.h @@ -1,161 +1,161 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2012 by Guillermo A. Amaral B. * * * * 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_MOVIE_H_ #define _OKULAR_MOVIE_H_ #include "global.h" #include "okularcore_export.h" -#include +#include class QImage; namespace Okular { /** * @short Contains information about a movie object. * * @since 0.8 (KDE 4.2) */ class OKULARCORE_EXPORT Movie { public: /** * The play mode for playing the movie */ enum PlayMode { PlayLimited, ///< Play a fixed amount of times, closing the movie controls at the end @since 0.24 PlayOpen, ///< Like PlayLimited, but leaving the controls open PlayRepeat, ///< Play continuously until stopped PlayPalindrome ///< Play forward, then backward, then again forward and so on until stopped }; /** * Creates a new movie object with the given external @p fileName. */ explicit Movie( const QString& fileName ); /** * Creates a new movie object with the given movie data. */ explicit Movie( const QString& fileName, const QByteArray &data ); /** * Destroys the movie object. */ ~Movie(); /** * Returns the url of the movie. */ QString url() const; /** * Sets the size for the movie. */ void setSize( const QSize &aspect ); /** * Returns the size of the movie. */ QSize size() const; /** * Sets the @p rotation of the movie. */ void setRotation( Rotation rotation ); /** * Returns the rotation of the movie. */ Rotation rotation() const; /** * Sets whether show a bar with movie controls */ void setShowControls( bool show ); /** * Whether show a bar with movie controls */ bool showControls() const; /** * Sets the way the movie should be played */ void setPlayMode( PlayMode mode ); /** * How to play the movie */ PlayMode playMode() const; /** * Sets how many times the movie should be played * @since 0.24 */ void setPlayRepetitions( double repetitions ); /** * How many times to play the movie * @since 0.24 */ double playRepetitions() const; /** * Sets whether to play the movie automatically */ void setAutoPlay( bool autoPlay ); /** * Whether to play the movie automatically */ bool autoPlay() const; /** * Sets whether to show a poster image. * * @since 4.10 */ void setShowPosterImage( bool show ); /** * Whether to show a poster image. * * @since 4.10 */ bool showPosterImage() const; /** * Sets the poster image. * * @since 4.10 */ void setPosterImage( const QImage &image ); /** * Returns the poster image. * * @since 4.10 */ QImage posterImage() const; private: class Private; Private* const d; Q_DISABLE_COPY( Movie ) }; } #endif diff --git a/core/observer.h b/core/observer.h index 9e01c8707..9474b5192 100644 --- a/core/observer.h +++ b/core/observer.h @@ -1,125 +1,125 @@ /*************************************************************************** * Copyright (C) 2005 by Enrico Ros * * Copyright (C) 2005 by Albert Astals Cid * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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_DOCUMENTOBSERVER_H_ #define _OKULAR_DOCUMENTOBSERVER_H_ -#include +#include #include "okularcore_export.h" namespace Okular { class Page; /** * @short Base class for objects being notified when something changes. * * Inherit this class and call Document->addObserver( yourClass ) to get * notified of asynchronous events (new pixmap generated, or changed, etc..). */ class OKULARCORE_EXPORT DocumentObserver { public: DocumentObserver(); /** * Destroys the document observer. */ virtual ~DocumentObserver(); /** * Flags that can be sent from the document to all observers to * inform them about the type of object that has been changed. */ enum ChangedFlags { Pixmap = 1, ///< Pixmaps has been changed Bookmark = 2, ///< Bookmarks has been changed Highlights = 4, ///< Highlighting information has been changed TextSelection = 8, ///< Text selection has been changed Annotations = 16, ///< Annotations have been changed BoundingBox = 32, ///< Bounding boxes have been changed NeedSaveAs = 64 ///< Set when "Save" is needed or annotation/form changes will be lost @since 0.15 (KDE 4.9) @deprecated }; /** * ... */ enum SetupFlags { DocumentChanged = 1, ///< The document is a new document. NewLayoutForPages = 2, ///< All the pages have UrlChanged = 4 ///< The URL has changed @since 1.3 }; /** * This method is called whenever the document is initialized or reconstructed. * * @param pages The vector of pages of the document. * @param setupFlags the flags with the information about the setup */ virtual void notifySetup( const QVector< Okular::Page * > &pages, int setupFlags ); /** * This method is called whenever the viewport has been changed. * * @param smoothMove If true, the move shall be animated. */ virtual void notifyViewportChanged( bool smoothMove ); /** * This method is called whenever the content on @p page described by the * passed @p flags has been changed. */ virtual void notifyPageChanged( int page, int flags ); /** * This method is called whenever the content described by the passed @p flags * has been cleared. */ virtual void notifyContentsCleared( int flags ); /** * This method is called whenever the visible rects have been changed. */ virtual void notifyVisibleRectsChanged(); /** * This method is called whenever the zoom of the document has been changed. */ virtual void notifyZoom( int factor ); /** * Returns whether the observer agrees that all pixmaps for the given * @p page can be unloaded to improve memory usage. * * Returns true per default. */ virtual bool canUnloadPixmap( int page ) const; /** * This method is called after the current page of the document has been entered. * * @param previous The number of the previous page (is @c -1 for the initial page change). * @param current The number of the current page. * * @since 0.16 (KDE 4.10) */ virtual void notifyCurrentPageChanged( int previous, int current ); private: class Private; const Private* d; }; } #endif diff --git a/core/page.cpp b/core/page.cpp index 7994cfe9f..c1410a597 100644 --- a/core/page.cpp +++ b/core/page.cpp @@ -1,1129 +1,1129 @@ /*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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 "page.h" #include "page_p.h" // qt/kde includes -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include // local includes #include "action.h" #include "annotations.h" #include "annotations_p.h" #include "area.h" #include "debug_p.h" #include "document.h" #include "document_p.h" #include "form.h" #include "form_p.h" #include "observer.h" #include "pagecontroller_p.h" #include "pagesize.h" #include "pagetransition.h" #include "rotationjob_p.h" #include "textpage.h" #include "textpage_p.h" #include "tile.h" #include "tilesmanager_p.h" #include "utils_p.h" #include #ifdef PAGE_PROFILE -#include +#include #endif using namespace Okular; static const double distanceConsideredEqual = 25; // 5px static void deleteObjectRects( QLinkedList< ObjectRect * >& rects, const QSet& which ) { QLinkedList< ObjectRect * >::iterator it = rects.begin(), end = rects.end(); for ( ; it != end; ) if ( which.contains( (*it)->objectType() ) ) { delete *it; it = rects.erase( it ); } else ++it; } PagePrivate::PagePrivate( Page *page, uint n, double w, double h, Rotation o ) : m_page( page ), m_number( n ), m_orientation( o ), m_width( w ), m_height( h ), m_doc( nullptr ), m_boundingBox( 0, 0, 1, 1 ), m_rotation( Rotation0 ), m_text( nullptr ), m_transition( nullptr ), m_textSelections( nullptr ), m_openingAction( nullptr ), m_closingAction( nullptr ), m_duration( -1 ), m_isBoundingBoxKnown( false ) { // avoid Division-By-Zero problems in the program if ( m_width <= 0 ) m_width = 1; if ( m_height <= 0 ) m_height = 1; } PagePrivate::~PagePrivate() { qDeleteAll( formfields ); delete m_openingAction; delete m_closingAction; delete m_text; delete m_transition; } PagePrivate *PagePrivate::get( Page * page ) { return page ? page->d : nullptr; } void PagePrivate::imageRotationDone( RotationJob * job ) { TilesManager *tm = tilesManager( job->observer() ); if ( tm ) { QPixmap *pixmap = new QPixmap( QPixmap::fromImage( job->image() ) ); tm->setPixmap( pixmap, job->rect(), job->isPartialUpdate() ); delete pixmap; return; } QMap< DocumentObserver*, PixmapObject >::iterator it = m_pixmaps.find( job->observer() ); if ( it != m_pixmaps.end() ) { PixmapObject &object = it.value(); (*object.m_pixmap) = QPixmap::fromImage( job->image() ); object.m_rotation = job->rotation(); } else { PixmapObject object; object.m_pixmap = new QPixmap( QPixmap::fromImage( job->image() ) ); object.m_rotation = job->rotation(); m_pixmaps.insert( job->observer(), object ); } } QTransform PagePrivate::rotationMatrix() const { return Okular::buildRotationMatrix( m_rotation ); } /** class Page **/ Page::Page( uint page, double w, double h, Rotation o ) : d( new PagePrivate( this, page, w, h, o ) ) { } Page::~Page() { if (d) { deletePixmaps(); deleteRects(); d->deleteHighlights(); deleteAnnotations(); d->deleteTextSelections(); deleteSourceReferences(); delete d; } } int Page::number() const { return d->m_number; } Rotation Page::orientation() const { return d->m_orientation; } Rotation Page::rotation() const { return d->m_rotation; } Rotation Page::totalOrientation() const { return (Rotation)( ( (int)d->m_orientation + (int)d->m_rotation ) % 4 ); } double Page::width() const { return d->m_width; } double Page::height() const { return d->m_height; } double Page::ratio() const { return d->m_height / d->m_width; } NormalizedRect Page::boundingBox() const { return d->m_boundingBox; } bool Page::isBoundingBoxKnown() const { return d->m_isBoundingBoxKnown; } void Page::setBoundingBox( const NormalizedRect& bbox ) { if ( d->m_isBoundingBoxKnown && d->m_boundingBox == bbox ) return; // Allow tiny rounding errors (happens during rotation) static const double epsilon = 0.00001; Q_ASSERT( bbox.left >= -epsilon && bbox.top >= -epsilon && bbox.right <= 1 + epsilon && bbox.bottom <= 1 + epsilon ); d->m_boundingBox = bbox & NormalizedRect( 0., 0., 1., 1. ); d->m_isBoundingBoxKnown = true; } bool Page::hasPixmap( DocumentObserver *observer, int width, int height, const NormalizedRect &rect ) const { TilesManager *tm = d->tilesManager( observer ); if ( tm ) { if ( width != tm->width() || height != tm->height() ) { // FIXME hasPixmap should not be calling setSize on the TilesManager this is not very "const" // as this function claims to be if ( width != -1 && height != -1 ) { tm->setSize( width, height ); } return false; } return tm->hasPixmap( rect ); } QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator it = d->m_pixmaps.constFind( observer ); if ( it == d->m_pixmaps.constEnd() ) return false; if ( width == -1 || height == -1 ) return true; const QPixmap *pixmap = it.value().m_pixmap; return (pixmap->width() == width && pixmap->height() == height); } bool Page::hasTextPage() const { return d->m_text != nullptr; } RegularAreaRect * Page::wordAt( const NormalizedPoint &p, QString *word ) const { if ( d->m_text ) return d->m_text->wordAt( p, word ); return nullptr; } RegularAreaRect * Page::textArea ( TextSelection * selection ) const { if ( d->m_text ) return d->m_text->textArea( selection ); return nullptr; } bool Page::hasObjectRect( double x, double y, double xScale, double yScale ) const { if ( m_rects.isEmpty() ) return false; QLinkedList< ObjectRect * >::const_iterator it = m_rects.begin(), end = m_rects.end(); for ( ; it != end; ++it ) if ( (*it)->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) return true; return false; } bool Page::hasHighlights( int s_id ) const { // simple case: have no highlights if ( m_highlights.isEmpty() ) return false; // simple case: we have highlights and no id to match if ( s_id == -1 ) return true; // iterate on the highlights list to find an entry by id QLinkedList< HighlightAreaRect * >::const_iterator it = m_highlights.begin(), end = m_highlights.end(); for ( ; it != end; ++it ) if ( (*it)->s_id == s_id ) return true; return false; } bool Page::hasTransition() const { return d->m_transition != nullptr; } bool Page::hasAnnotations() const { return !m_annotations.isEmpty(); } RegularAreaRect * Page::findText( int id, const QString & text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect ) const { RegularAreaRect* rect = nullptr; if ( text.isEmpty() || !d->m_text ) return rect; rect = d->m_text->findText( id, text, direction, caseSensitivity, lastRect ); return rect; } QString Page::text( const RegularAreaRect * area ) const { return text( area, TextPage::AnyPixelTextAreaInclusionBehaviour ); } QString Page::text( const RegularAreaRect * area, TextPage::TextAreaInclusionBehaviour b ) const { QString ret; if ( !d->m_text ) return ret; if ( area ) { RegularAreaRect rotatedArea = *area; rotatedArea.transform( d->rotationMatrix().inverted() ); ret = d->m_text->text( &rotatedArea, b ); } else ret = d->m_text->text( nullptr, b ); return ret; } TextEntity::List Page::words( const RegularAreaRect * area, TextPage::TextAreaInclusionBehaviour b ) const { TextEntity::List ret; if ( !d->m_text ) return ret; if ( area ) { RegularAreaRect rotatedArea = *area; rotatedArea.transform( d->rotationMatrix().inverted() ); ret = d->m_text->words( &rotatedArea, b ); } else ret = d->m_text->words( nullptr, b ); for (int i = 0; i < ret.length(); ++i) { const TextEntity * orig = ret[i]; ret[i] = new TextEntity( orig->text(), new Okular::NormalizedRect(orig->transformedArea ( d->rotationMatrix() )) ); delete orig; } return ret; } void PagePrivate::rotateAt( Rotation orientation ) { if ( orientation == m_rotation ) return; deleteTextSelections(); if ( ( (int)m_orientation + (int)m_rotation ) % 2 != ( (int)m_orientation + (int)orientation ) % 2 ) qSwap( m_width, m_height ); Rotation oldRotation = m_rotation; m_rotation = orientation; /** * Rotate the images of the page. */ QMapIterator< DocumentObserver*, PagePrivate::PixmapObject > it( m_pixmaps ); while ( it.hasNext() ) { it.next(); const PagePrivate::PixmapObject &object = it.value(); RotationJob *job = new RotationJob( object.m_pixmap->toImage(), object.m_rotation, m_rotation, it.key() ); job->setPage( this ); m_doc->m_pageController->addRotationJob(job); } /** * Rotate tiles manager */ QMapIterator i(m_tilesManagers); while (i.hasNext()) { i.next(); TilesManager *tm = i.value(); if ( tm ) tm->setRotation( m_rotation ); } /** * Rotate the object rects on the page. */ const QTransform matrix = rotationMatrix(); QLinkedList< ObjectRect * >::const_iterator objectIt = m_page->m_rects.begin(), end = m_page->m_rects.end(); for ( ; objectIt != end; ++objectIt ) (*objectIt)->transform( matrix ); const QTransform highlightRotationMatrix = Okular::buildRotationMatrix( (Rotation)(((int)m_rotation - (int)oldRotation + 4) % 4) ); QLinkedList< HighlightAreaRect* >::const_iterator hlIt = m_page->m_highlights.begin(), hlItEnd = m_page->m_highlights.end(); for ( ; hlIt != hlItEnd; ++hlIt ) { (*hlIt)->transform( highlightRotationMatrix ); } } void PagePrivate::changeSize( const PageSize &size ) { if ( size.isNull() || ( size.width() == m_width && size.height() == m_height ) ) return; m_page->deletePixmaps(); // deleteHighlights(); // deleteTextSelections(); m_width = size.width(); m_height = size.height(); if ( m_rotation % 2 ) qSwap( m_width, m_height ); } const ObjectRect * Page::objectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const { // Walk list in reverse order so that annotations in the foreground are preferred QLinkedListIterator< ObjectRect * > it( m_rects ); it.toBack(); while ( it.hasPrevious() ) { const ObjectRect *objrect = it.previous(); if ( ( objrect->objectType() == type ) && objrect->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) return objrect; } return nullptr; } QLinkedList< const ObjectRect * > Page::objectRects( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const { QLinkedList< const ObjectRect * > result; QLinkedListIterator< ObjectRect * > it( m_rects ); it.toBack(); while ( it.hasPrevious() ) { const ObjectRect *objrect = it.previous(); if ( ( objrect->objectType() == type ) && objrect->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) result.append( objrect ); } return result; } const ObjectRect* Page::nearestObjectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double * distance ) const { ObjectRect * res = nullptr; double minDistance = std::numeric_limits::max(); QLinkedList< ObjectRect * >::const_iterator it = m_rects.constBegin(), end = m_rects.constEnd(); for ( ; it != end; ++it ) { if ( (*it)->objectType() == type ) { double d = (*it)->distanceSqr( x, y, xScale, yScale ); if ( d < minDistance ) { res = (*it); minDistance = d; } } } if ( distance ) *distance = minDistance; return res; } const PageTransition * Page::transition() const { return d->m_transition; } QLinkedList< Annotation* > Page::annotations() const { return m_annotations; } Annotation * Page::annotation( const QString & uniqueName ) const { foreach(Annotation *a, m_annotations) { if ( a->uniqueName() == uniqueName ) return a; } return nullptr; } const Action * Page::pageAction( PageAction action ) const { switch ( action ) { case Page::Opening: return d->m_openingAction; break; case Page::Closing: return d->m_closingAction; break; } return nullptr; } QLinkedList< FormField * > Page::formFields() const { return d->formfields; } void Page::setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect ) { d->setPixmap( observer, pixmap, rect, false /*isPartialPixmap*/ ); } void PagePrivate::setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap ) { if ( m_rotation == Rotation0 ) { TilesManager *tm = tilesManager( observer ); if ( tm ) { tm->setPixmap( pixmap, rect, isPartialPixmap ); delete pixmap; return; } QMap< DocumentObserver*, PagePrivate::PixmapObject >::iterator it = m_pixmaps.find( observer ); if ( it != m_pixmaps.end() ) { delete it.value().m_pixmap; } else { it = m_pixmaps.insert( observer, PagePrivate::PixmapObject() ); } it.value().m_pixmap = pixmap; it.value().m_rotation = m_rotation; } else { // it can happen that we get a setPixmap while closing and thus the page controller is gone if ( m_doc->m_pageController ) { RotationJob *job = new RotationJob( pixmap->toImage(), Rotation0, m_rotation, observer ); job->setPage( this ); job->setRect( TilesManager::toRotatedRect( rect, m_rotation ) ); job->setIsPartialUpdate( isPartialPixmap ); m_doc->m_pageController->addRotationJob(job); } delete pixmap; } } void Page::setTextPage( TextPage * textPage ) { delete d->m_text; d->m_text = textPage; if ( d->m_text ) { d->m_text->d->m_page = this; /** * Correct text order for before text selection */ d->m_text->d->correctTextOrder(); } } void Page::setObjectRects( const QLinkedList< ObjectRect * > & rects ) { QSet which; which << ObjectRect::Action << ObjectRect::Image; deleteObjectRects( m_rects, which ); /** * Rotate the object rects of the page. */ const QTransform matrix = d->rotationMatrix(); QLinkedList< ObjectRect * >::const_iterator objectIt = rects.begin(), end = rects.end(); for ( ; objectIt != end; ++objectIt ) (*objectIt)->transform( matrix ); m_rects << rects; } void PagePrivate::setHighlight( int s_id, RegularAreaRect *rect, const QColor & color ) { HighlightAreaRect * hr = new HighlightAreaRect(rect); hr->s_id = s_id; hr->color = color; m_page->m_highlights.append( hr ); } void PagePrivate::setTextSelections( RegularAreaRect *r, const QColor & color ) { deleteTextSelections(); if ( r ) { HighlightAreaRect * hr = new HighlightAreaRect( r ); hr->s_id = -1; hr->color = color; m_textSelections = hr; delete r; } } void Page::setSourceReferences( const QLinkedList< SourceRefObjectRect * > & refRects ) { deleteSourceReferences(); foreach( SourceRefObjectRect * rect, refRects ) m_rects << rect; } void Page::setDuration( double seconds ) { d->m_duration = seconds; } double Page::duration() const { return d->m_duration; } void Page::setLabel( const QString& label ) { d->m_label = label; } QString Page::label() const { return d->m_label; } const RegularAreaRect * Page::textSelection() const { return d->m_textSelections; } QColor Page::textSelectionColor() const { return d->m_textSelections ? d->m_textSelections->color : QColor(); } void Page::addAnnotation( Annotation * annotation ) { // Generate uniqueName: okular-{UUID} if(annotation->uniqueName().isEmpty()) { QString uniqueName = QStringLiteral("okular-") + QUuid::createUuid().toString(); annotation->setUniqueName( uniqueName ); } annotation->d_ptr->m_page = d; m_annotations.append( annotation ); AnnotationObjectRect *rect = new AnnotationObjectRect( annotation ); // Rotate the annotation on the page. const QTransform matrix = d->rotationMatrix(); annotation->d_ptr->annotationTransform( matrix ); m_rects.append( rect ); } bool Page::removeAnnotation( Annotation * annotation ) { if ( !d->m_doc->m_parent->canRemovePageAnnotation(annotation) ) return false; QLinkedList< Annotation * >::iterator aIt = m_annotations.begin(), aEnd = m_annotations.end(); for ( ; aIt != aEnd; ++aIt ) { if((*aIt) && (*aIt)->uniqueName()==annotation->uniqueName()) { int rectfound = false; QLinkedList< ObjectRect * >::iterator it = m_rects.begin(), end = m_rects.end(); for ( ; it != end && !rectfound; ++it ) if ( ( (*it)->objectType() == ObjectRect::OAnnotation ) && ( (*it)->object() == (*aIt) ) ) { delete *it; it = m_rects.erase( it ); rectfound = true; } qCDebug(OkularCoreDebug) << "removed annotation:" << annotation->uniqueName(); annotation->d_ptr->m_page = nullptr; m_annotations.erase( aIt ); break; } } return true; } void Page::setTransition( PageTransition * transition ) { delete d->m_transition; d->m_transition = transition; } void Page::setPageAction( PageAction action, Action * link ) { switch ( action ) { case Page::Opening: delete d->m_openingAction; d->m_openingAction = link; break; case Page::Closing: delete d->m_closingAction; d->m_closingAction = link; break; } } void Page::setFormFields( const QLinkedList< FormField * >& fields ) { qDeleteAll( d->formfields ); d->formfields = fields; QLinkedList< FormField * >::const_iterator it = d->formfields.begin(), itEnd = d->formfields.end(); for ( ; it != itEnd; ++it ) { (*it)->d_ptr->setDefault(); } } void Page::deletePixmap( DocumentObserver *observer ) { TilesManager *tm = d->tilesManager( observer ); if ( tm ) { delete tm; d->m_tilesManagers.remove(observer); } else { PagePrivate::PixmapObject object = d->m_pixmaps.take( observer ); delete object.m_pixmap; } } void Page::deletePixmaps() { QMapIterator< DocumentObserver*, PagePrivate::PixmapObject > it( d->m_pixmaps ); while ( it.hasNext() ) { it.next(); delete it.value().m_pixmap; } d->m_pixmaps.clear(); qDeleteAll(d->m_tilesManagers); d->m_tilesManagers.clear(); } void Page::deleteRects() { // delete ObjectRects of type Link and Image QSet which; which << ObjectRect::Action << ObjectRect::Image; deleteObjectRects( m_rects, which ); } void PagePrivate::deleteHighlights( int s_id ) { // delete highlights by ID QLinkedList< HighlightAreaRect* >::iterator it = m_page->m_highlights.begin(), end = m_page->m_highlights.end(); while ( it != end ) { HighlightAreaRect* highlight = *it; if ( s_id == -1 || highlight->s_id == s_id ) { it = m_page->m_highlights.erase( it ); delete highlight; } else ++it; } } void PagePrivate::deleteTextSelections() { delete m_textSelections; m_textSelections = nullptr; } void Page::deleteSourceReferences() { deleteObjectRects( m_rects, QSet() << ObjectRect::SourceRef ); } void Page::deleteAnnotations() { // delete ObjectRects of type Annotation deleteObjectRects( m_rects, QSet() << ObjectRect::OAnnotation ); // delete all stored annotations QLinkedList< Annotation * >::const_iterator aIt = m_annotations.begin(), aEnd = m_annotations.end(); for ( ; aIt != aEnd; ++aIt ) delete *aIt; m_annotations.clear(); } bool PagePrivate::restoreLocalContents( const QDomNode & pageNode ) { bool loadedAnything = false; // set if something actually gets loaded // iterate over all chilren (annotationList, ...) QDomNode childNode = pageNode.firstChild(); while ( childNode.isElement() ) { QDomElement childElement = childNode.toElement(); childNode = childNode.nextSibling(); // parse annotationList child element if ( childElement.tagName() == QLatin1String("annotationList") ) { #ifdef PAGE_PROFILE QTime time; time.start(); #endif // Clone annotationList as root node in restoredLocalAnnotationList const QDomNode clonedNode = restoredLocalAnnotationList.importNode( childElement, true ); restoredLocalAnnotationList.appendChild( clonedNode ); // iterate over all annotations QDomNode annotationNode = childElement.firstChild(); while( annotationNode.isElement() ) { // get annotation element and advance to next annot QDomElement annotElement = annotationNode.toElement(); annotationNode = annotationNode.nextSibling(); // get annotation from the dom element Annotation * annotation = AnnotationUtils::createAnnotation( annotElement ); // append annotation to the list or show warning if ( annotation ) { m_doc->performAddPageAnnotation(m_number, annotation); qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName(); loadedAnything = true; } else qCWarning(OkularCoreDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML."; } #ifdef PAGE_PROFILE qCDebug(OkularCoreDebug).nospace() << "annots: XML Load time: " << time.elapsed() << "ms"; #endif } // parse formList child element else if ( childElement.tagName() == QLatin1String("forms") ) { // Clone forms as root node in restoredFormFieldList const QDomNode clonedNode = restoredFormFieldList.importNode( childElement, true ); restoredFormFieldList.appendChild( clonedNode ); if ( formfields.isEmpty() ) continue; QHash hashedforms; QLinkedList< FormField * >::const_iterator fIt = formfields.begin(), fItEnd = formfields.end(); for ( ; fIt != fItEnd; ++fIt ) { hashedforms[(*fIt)->id()] = (*fIt); } // iterate over all forms QDomNode formsNode = childElement.firstChild(); while( formsNode.isElement() ) { // get annotation element and advance to next annot QDomElement formElement = formsNode.toElement(); formsNode = formsNode.nextSibling(); if ( formElement.tagName() != QLatin1String("form") ) continue; bool ok = true; int index = formElement.attribute( QStringLiteral("id") ).toInt( &ok ); if ( !ok ) continue; QHash::const_iterator wantedIt = hashedforms.constFind( index ); if ( wantedIt == hashedforms.constEnd() ) continue; QString value = formElement.attribute( QStringLiteral("value") ); (*wantedIt)->d_ptr->setValue( value ); loadedAnything = true; } } } return loadedAnything; } void PagePrivate::saveLocalContents( QDomNode & parentNode, QDomDocument & document, PageItems what ) const { // create the page node and set the 'number' attribute QDomElement pageElement = document.createElement( QStringLiteral("page") ); pageElement.setAttribute( QStringLiteral("number"), m_number ); #if 0 // add bookmark info if is bookmarked if ( d->m_bookmarked ) { // create the pageElement's 'bookmark' child QDomElement bookmarkElement = document.createElement( "bookmark" ); pageElement.appendChild( bookmarkElement ); // add attributes to the element //bookmarkElement.setAttribute( "name", bookmark name ); } #endif // add annotations info if has got any if ( ( what & AnnotationPageItems ) && ( what & OriginalAnnotationPageItems ) ) { const QDomElement savedDocRoot = restoredLocalAnnotationList.documentElement(); if ( !savedDocRoot.isNull() ) { // Import and append node in target document const QDomNode importedNode = document.importNode( savedDocRoot, true ); pageElement.appendChild( importedNode ); } } else if ( ( what & AnnotationPageItems ) && !m_page->m_annotations.isEmpty() ) { // create the annotationList QDomElement annotListElement = document.createElement( QStringLiteral("annotationList") ); // add every annotation to the annotationList QLinkedList< Annotation * >::const_iterator aIt = m_page->m_annotations.constBegin(), aEnd = m_page->m_annotations.constEnd(); for ( ; aIt != aEnd; ++aIt ) { // get annotation const Annotation * a = *aIt; // only save okular annotations (not the embedded in file ones) if ( !(a->flags() & Annotation::External) ) { // append an filled-up element called 'annotation' to the list QDomElement annElement = document.createElement( QStringLiteral("annotation") ); AnnotationUtils::storeAnnotation( a, annElement, document ); annotListElement.appendChild( annElement ); qCDebug(OkularCoreDebug) << "save annotation:" << a->uniqueName(); } } // append the annotationList element if annotations have been set if ( annotListElement.hasChildNodes() ) pageElement.appendChild( annotListElement ); } // add forms info if has got any if ( ( what & FormFieldPageItems ) && ( what & OriginalFormFieldPageItems ) ) { const QDomElement savedDocRoot = restoredFormFieldList.documentElement(); if ( !savedDocRoot.isNull() ) { // Import and append node in target document const QDomNode importedNode = document.importNode( savedDocRoot, true ); pageElement.appendChild( importedNode ); } } else if ( ( what & FormFieldPageItems ) && !formfields.isEmpty() ) { // create the formList QDomElement formListElement = document.createElement( QStringLiteral("forms") ); // add every form data to the formList QLinkedList< FormField * >::const_iterator fIt = formfields.constBegin(), fItEnd = formfields.constEnd(); for ( ; fIt != fItEnd; ++fIt ) { // get the form field const FormField * f = *fIt; QString newvalue = f->d_ptr->value(); if ( f->d_ptr->m_default == newvalue ) continue; // append an filled-up element called 'annotation' to the list QDomElement formElement = document.createElement( QStringLiteral("form") ); formElement.setAttribute( QStringLiteral("id"), f->id() ); formElement.setAttribute( QStringLiteral("value"), newvalue ); formListElement.appendChild( formElement ); } // append the annotationList element if annotations have been set if ( formListElement.hasChildNodes() ) pageElement.appendChild( formListElement ); } // append the page element only if has children if ( pageElement.hasChildNodes() ) parentNode.appendChild( pageElement ); } const QPixmap * Page::_o_nearestPixmap( DocumentObserver *observer, int w, int h ) const { Q_UNUSED( h ) const QPixmap * pixmap = nullptr; // if a pixmap is present for given id, use it QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator itPixmap = d->m_pixmaps.constFind( observer ); if ( itPixmap != d->m_pixmaps.constEnd() ) pixmap = itPixmap.value().m_pixmap; // else find the closest match using pixmaps of other IDs (great optim!) else if ( !d->m_pixmaps.isEmpty() ) { int minDistance = -1; QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator it = d->m_pixmaps.constBegin(), end = d->m_pixmaps.constEnd(); for ( ; it != end; ++it ) { int pixWidth = (*it).m_pixmap->width(), distance = pixWidth > w ? pixWidth - w : w - pixWidth; if ( minDistance == -1 || distance < minDistance ) { pixmap = (*it).m_pixmap; minDistance = distance; } } } return pixmap; } bool Page::hasTilesManager( const DocumentObserver *observer ) const { return d->tilesManager( observer ) != nullptr; } QList Page::tilesAt( const DocumentObserver *observer, const NormalizedRect &rect ) const { TilesManager *tm = d->m_tilesManagers.value( observer ); if ( tm ) return tm->tilesAt( rect, TilesManager::PixmapTile ); else return QList(); } TilesManager *PagePrivate::tilesManager( const DocumentObserver *observer ) const { return m_tilesManagers.value( observer ); } void PagePrivate::setTilesManager( const DocumentObserver *observer, TilesManager *tm ) { TilesManager *old = m_tilesManagers.value( observer ); delete old; m_tilesManagers.insert(observer, tm); } void PagePrivate::adoptGeneratedContents( PagePrivate *oldPage ) { rotateAt( oldPage->m_rotation ); m_pixmaps = oldPage->m_pixmaps; oldPage->m_pixmaps.clear(); m_tilesManagers = oldPage->m_tilesManagers; oldPage->m_tilesManagers.clear(); m_boundingBox = oldPage->m_boundingBox; m_isBoundingBoxKnown = oldPage->m_isBoundingBoxKnown; m_text = oldPage->m_text; oldPage->m_text = nullptr; m_textSelections = oldPage->m_textSelections; oldPage->m_textSelections = nullptr; restoredLocalAnnotationList = oldPage->restoredLocalAnnotationList; restoredFormFieldList = oldPage->restoredFormFieldList; } FormField *PagePrivate::findEquivalentForm( const Page *p, FormField *oldField ) { // given how id is not very good of id (at least for pdf) we do a few passes // same rect, type and id foreach(FormField *f, p->d->formfields) { if (f->rect() == oldField->rect() && f->type() == oldField->type() && f->id() == oldField->id()) return f; } // same rect and type foreach(FormField *f, p->d->formfields) { if (f->rect() == oldField->rect() && f->type() == oldField->type()) return f; } // fuzzy rect, same type and id foreach(FormField *f, p->d->formfields) { if (f->type() == oldField->type() && f->id() == oldField->id() && qFuzzyCompare(f->rect().left, oldField->rect().left) && qFuzzyCompare(f->rect().top, oldField->rect().top) && qFuzzyCompare(f->rect().right, oldField->rect().right) && qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) { return f; } } // fuzzy rect and same type foreach(FormField *f, p->d->formfields) { if (f->type() == oldField->type() && qFuzzyCompare(f->rect().left, oldField->rect().left) && qFuzzyCompare(f->rect().top, oldField->rect().top) && qFuzzyCompare(f->rect().right, oldField->rect().right) && qFuzzyCompare(f->rect().bottom, oldField->rect().bottom)) { return f; } } return nullptr; } diff --git a/core/page.h b/core/page.h index 7324b2be4..b33f0e039 100644 --- a/core/page.h +++ b/core/page.h @@ -1,423 +1,423 @@ /*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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_PAGE_H_ #define _OKULAR_PAGE_H_ -#include +#include #include "okularcore_export.h" #include "area.h" #include "global.h" #include "textpage.h" class QPixmap; class PagePainter; namespace Okular { class Annotation; class Document; class DocumentObserver; class DocumentPrivate; class FormField; class PagePrivate; class PageTransition; class SourceReference; class TextSelection; class Tile; /** * @short Collector for all the data belonging to a page. * * The Page class contains pixmaps (referenced using observers id as key), * a search page (a class used internally for retrieving text), rect classes * (that describe links or other active areas in the current page) and more. * * All coordinates are normalized to the page, so {x,y} are valid in [0,1] * range as long as NormalizedRect components. * * Note: The class takes ownership of all objects. */ class OKULARCORE_EXPORT Page { public: /** * An action to be executed when particular events happen. */ enum PageAction { Opening, ///< An action to be executed when the page is "opened". Closing ///< An action to be executed when the page is "closed". }; /** * Creates a new page. * * @param number The number of the page in the document. * @param width The width of the page. * @param height The height of the page. * @param orientation The orientation of the page */ Page( uint number, double width, double height, Rotation orientation ); /** * Destroys the page. */ ~Page(); /** * Returns the number of the page in the document. */ int number() const; /** * Returns the orientation of the page as defined by the document. */ Rotation orientation() const; /** * Returns the rotation of the page as defined by the user. */ Rotation rotation() const; /** * Returns the total orientation which is the original orientation plus * the user defined rotation. */ Rotation totalOrientation() const; /** * Returns the width of the page. */ double width() const; /** * Returns the height of the page. */ double height() const; /** * Returns the ration (height / width) of the page. */ double ratio() const; /** * Returns the bounding box of the page content in normalized [0,1] coordinates, * in terms of the upright orientation (Rotation0). * If it has not been computed yet, returns the full page (i.e., (0, 0, 1, 1)). * Note that the bounding box may be null if the page is blank. * * @since 0.7 (KDE 4.1) */ NormalizedRect boundingBox() const; /** * Returns whether the bounding box of the page has been computed. * Note that even if the bounding box is computed, it may be null if the page is blank. * * @since 0.7 (KDE 4.1) */ bool isBoundingBoxKnown() const; /** * Sets the bounding box of the page content in normalized [0,1] coordinates, * in terms of the upright orientation (Rotation0). * (This does not inform the document's observers, call Document::SetPageBoundingBox * instead if you want that.) * * @since 0.7 (KDE 4.1) */ void setBoundingBox( const NormalizedRect& bbox ); /** * Returns whether the page of size @p width x @p height has a @p pixmap * in the region given by @p rect for the given @p observer */ bool hasPixmap( DocumentObserver *observer, int width = -1, int height = -1, const NormalizedRect &rect = NormalizedRect() ) const; /** * Returns whether the page provides a text page (@ref TextPage). */ bool hasTextPage() const; /** * Returns whether the page has an object rect which includes the point (@p x, @p y) * at scale (@p xScale, @p yScale). */ bool hasObjectRect( double x, double y, double xScale, double yScale ) const; /** * Returns whether the page provides highlighting for the observer with the * given @p id. */ bool hasHighlights( int id = -1 ) const; /** * Returns whether the page provides a transition effect. */ bool hasTransition() const; /** * Returns whether the page provides annotations. */ bool hasAnnotations() const; /** * Returns the bounding rect of the text which matches the following criteria * or 0 if the search is not successful. * * @param id An unique id for this search. * @param text The search text. * @param direction The direction of the search (@ref SearchDirection) * @param caseSensitivity If Qt::CaseSensitive, the search is case sensitive; otherwise * the search is case insensitive. * @param lastRect If 0 (default) the search starts at the beginning of the page, otherwise * right/below the coordinates of the given rect. */ RegularAreaRect* findText( int id, const QString & text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect * lastRect=nullptr) const; /** * Returns the page text (or part of it). * @see TextPage::text() */ QString text( const RegularAreaRect * rect = nullptr ) const; /** * Returns the page text (or part of it). * @see TextPage::text() * @since 0.10 (KDE 4.4) */ QString text( const RegularAreaRect * rect, TextPage::TextAreaInclusionBehaviour b ) const; /** * Returns the page text (or part of it) including the bounding * rectangles. Note that ownership of the contents of the returned * list belongs to the caller. * @see TextPage::words() * @since 0.14 (KDE 4.8) */ TextEntity::List words( const RegularAreaRect * rect, TextPage::TextAreaInclusionBehaviour b ) const; /** * Returns the area and text of the word at the given point * Note that ownership of the returned area belongs to the caller. * @see TextPage::wordAt() * @since 0.15 (KDE 4.9) */ RegularAreaRect * wordAt( const NormalizedPoint &p, QString *word = nullptr ) const; /** * Returns the rectangular area of the given @p selection. */ RegularAreaRect * textArea( TextSelection *selection ) const; /** * Returns the object rect of the given @p type which is at point (@p x, @p y) at scale (@p xScale, @p yScale). */ const ObjectRect * objectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const; /** * Returns all object rects of the given @p type which are at point (@p x, @p y) at scale (@p xScale, @p yScale). * @since 0.16 (KDE 4.10) */ QLinkedList< const ObjectRect * > objectRects( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const; /** * Returns the object rect of the given @p type which is nearest to the point (@p x, @p y) at scale (@p xScale, @p yScale). * * @since 0.8.2 (KDE 4.2.2) */ const ObjectRect * nearestObjectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double * distance ) const; /** * Returns the transition effect of the page or 0 if no transition * effect is set (see hasTransition()). */ const PageTransition * transition() const; /** * Returns the list of annotations of the page. */ QLinkedList< Annotation* > annotations() const; /** * Returns the annotation with the given unique name. * @since 1.3 */ Annotation * annotation( const QString & uniqueName ) const; /** * Returns the @ref Action object which is associated with the given page @p action * or 0 if no page action is set. */ const Action * pageAction( PageAction action ) const; /** * Returns the list of FormField of the page. */ QLinkedList< FormField * > formFields() const; /** * Sets the region described by @p rect with @p pixmap for the * given @p observer. * If @p rect is not set (default) the @p pixmap is set to the entire * page. */ void setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect = NormalizedRect() ); /** * Sets the @p text page. */ void setTextPage( TextPage * text ); /** * Sets the list of object @p rects of the page. */ void setObjectRects( const QLinkedList< ObjectRect * > & rects ); /** * Sets the list of source reference objects @p rects. */ void setSourceReferences( const QLinkedList< SourceRefObjectRect * > & rects ); /** * Sets the duration of the page to @p seconds when displayed in presentation mode. * * Setting a negative number disables the duration. */ void setDuration( double seconds ); /** * Returns the duration in seconds of the page when displayed in presentation mode. * * A negative number means that no time is set. */ double duration() const; /** * Sets the labels for the page to @p label . */ void setLabel( const QString& label ); /** * Returns the label of the page, or a null string if not set. */ QString label() const; /** * Returns the current text selection. */ const RegularAreaRect * textSelection() const; /** * Returns the color of the current text selection, or an invalid color * if no text selection has been set. */ QColor textSelectionColor() const; /** * Adds a new @p annotation to the page. */ void addAnnotation( Annotation * annotation ); /** * Removes the @p annotation from the page. */ bool removeAnnotation( Annotation * annotation ); /** * Sets the page @p transition effect. */ void setTransition( PageTransition * transition ); /** * Sets the @p link object for the given page @p action. */ void setPageAction( PageAction action, Action * link ); /** * Sets @p fields as list of FormField of the page. */ void setFormFields( const QLinkedList< FormField * >& fields ); /** * Deletes the pixmap for the given @p observer */ void deletePixmap( DocumentObserver *observer ); /** * Deletes all pixmaps of the page. */ void deletePixmaps(); /** * Deletes all object rects of the page. */ void deleteRects(); /** * Deletes all source reference objects of the page. */ void deleteSourceReferences(); /** * Deletes all annotations of the page. */ void deleteAnnotations(); /** * Returns whether pixmaps for the tiled observer are handled by a * tile manager. * * @since 0.19 (KDE 4.13) */ bool hasTilesManager( const DocumentObserver *observer ) const; /** * Returns a list of all tiles intersecting with @p rect. * * The list contains only tiles with a pixmap * * @since 0.19 (KDE 4.13) */ QList tilesAt( const DocumentObserver *observer, const NormalizedRect &rect ) const; private: PagePrivate* d; /// @cond PRIVATE friend class PagePrivate; friend class Document; friend class DocumentPrivate; friend class PixmapRequestPrivate; /** * To improve performance PagePainter accesses the following * member variables directly. */ friend class ::PagePainter; /// @endcond const QPixmap * _o_nearestPixmap( DocumentObserver *, int, int ) const; QLinkedList< ObjectRect* > m_rects; QLinkedList< HighlightAreaRect* > m_highlights; QLinkedList< Annotation* > m_annotations; Q_DISABLE_COPY( Page ) }; } #endif diff --git a/core/pagecontroller_p.h b/core/pagecontroller_p.h index 84beb5a05..fa1253beb 100644 --- a/core/pagecontroller_p.h +++ b/core/pagecontroller_p.h @@ -1,46 +1,46 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_PAGECONTROLLER_P_H_ #define _OKULAR_PAGECONTROLLER_P_H_ -#include +#include #include namespace Okular { class Page; class RotationJob; /* There is one PageController per document. It receives notifications of * completed RotationJobs */ class PageController : public QObject { Q_OBJECT public: PageController(); ~PageController(); void addRotationJob( RotationJob *job ); Q_SIGNALS: void rotationFinished( int page, Okular::Page *okularPage ); private Q_SLOTS: void imageRotationDone(const ThreadWeaver::JobPointer &job); private: ThreadWeaver::Queue m_weaver; }; } #endif diff --git a/core/pagesize.h b/core/pagesize.h index 96bebeaa8..1ad479f93 100644 --- a/core/pagesize.h +++ b/core/pagesize.h @@ -1,83 +1,83 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_PAGESIZE_H_ #define _OKULAR_PAGESIZE_H_ -#include -#include -#include +#include +#include +#include #include "okularcore_export.h" namespace Okular { class PageSizePrivate; /** * @short A small class that represents the size of a page. */ class OKULARCORE_EXPORT PageSize { public: typedef QList List; /** * Construct a null page size. * @see isNull() */ PageSize(); /** * Construct a page size with the specified @p width and @p height, * having the ID @p name. */ PageSize( double width, double height, const QString &name ); /** * Copy constructor. */ PageSize( const PageSize &pageSize ); ~PageSize(); /** * Returns the width of the page size. */ double width() const; /** * Returns the height of the page size. */ double height() const; /** * Returns the ID of the page size. */ QString name() const; /** * Whether the page size is null. */ bool isNull() const; PageSize& operator=( const PageSize &pageSize ); /** * Comparison operator. */ bool operator==( const PageSize &pageSize ) const; bool operator!=( const PageSize &pageSize ) const; private: /// @cond PRIVATE friend class PageSizePrivate; /// @endcond QSharedDataPointer d; }; } #endif diff --git a/core/rotationjob.cpp b/core/rotationjob.cpp index d34862446..e75942b06 100644 --- a/core/rotationjob.cpp +++ b/core/rotationjob.cpp @@ -1,126 +1,126 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 "rotationjob_p.h" -#include +#include using namespace Okular; RotationJob::RotationJob( const QImage &image, Rotation oldRotation, Rotation newRotation, DocumentObserver *observer ) : ThreadWeaver::QObjectDecorator( new RotationJobInternal( image, oldRotation, newRotation ) ) , mObserver( observer ), m_pd( nullptr ) , mRect( NormalizedRect() ) , mIsPartialUpdate( false ) { } void RotationJob::setPage( PagePrivate * pd ) { m_pd = pd; } void RotationJob::setRect( const NormalizedRect &rect ) { mRect = rect; } void RotationJob::setIsPartialUpdate( bool partialUpdate ) { mIsPartialUpdate = partialUpdate; } DocumentObserver * RotationJob::observer() const { return mObserver; } PagePrivate * RotationJob::page() const { return m_pd; } NormalizedRect RotationJob::rect() const { return mRect; } bool RotationJob::isPartialUpdate() const { return mIsPartialUpdate; } QTransform RotationJob::rotationMatrix( Rotation from, Rotation to ) { QTransform matrix; if ( from == Rotation0 ) { if ( to == Rotation90 ) matrix.rotate( 90 ); else if ( to == Rotation180 ) matrix.rotate( 180 ); else if ( to == Rotation270 ) matrix.rotate( 270 ); } else if ( from == Rotation90 ) { if ( to == Rotation180 ) matrix.rotate( 90 ); else if ( to == Rotation270 ) matrix.rotate( 180 ); else if ( to == Rotation0 ) matrix.rotate( 270 ); } else if ( from == Rotation180 ) { if ( to == Rotation270 ) matrix.rotate( 90 ); else if ( to == Rotation0 ) matrix.rotate( 180 ); else if ( to == Rotation90 ) matrix.rotate( 270 ); } else if ( from == Rotation270 ) { if ( to == Rotation0 ) matrix.rotate( 90 ); else if ( to == Rotation90 ) matrix.rotate( 180 ); else if ( to == Rotation180 ) matrix.rotate( 270 ); } return matrix; } RotationJobInternal::RotationJobInternal( const QImage &image, Rotation oldRotation, Rotation newRotation ) : mImage( image ), mOldRotation( oldRotation ), mNewRotation( newRotation ) { } QImage RotationJobInternal::image() const { return mRotatedImage; } Rotation RotationJobInternal::rotation() const { return mNewRotation; } void RotationJobInternal::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) { Q_UNUSED(self); Q_UNUSED(thread); if ( mOldRotation == mNewRotation ) { mRotatedImage = mImage; return; } const QTransform matrix = RotationJob::rotationMatrix( mOldRotation, mNewRotation ); mRotatedImage = mImage.transformed( matrix ); } #include "moc_rotationjob_p.cpp" diff --git a/core/rotationjob_p.h b/core/rotationjob_p.h index b0662f549..d33ab0cef 100644 --- a/core/rotationjob_p.h +++ b/core/rotationjob_p.h @@ -1,76 +1,76 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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_ROTATIONJOB_P_H_ #define _OKULAR_ROTATIONJOB_P_H_ -#include -#include +#include +#include #include #include #include "core/global.h" #include "core/area.h" namespace Okular { class DocumentObserver; class PagePrivate; class RotationJobInternal : public ThreadWeaver::Job { friend class RotationJob; public: QImage image() const; Rotation rotation() const; NormalizedRect rect() const; protected: void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) override; private: RotationJobInternal( const QImage &image, Rotation oldRotation, Rotation newRotation ); const QImage mImage; Rotation mOldRotation; Rotation mNewRotation; QImage mRotatedImage; }; class RotationJob : public ThreadWeaver::QObjectDecorator { Q_OBJECT public: RotationJob( const QImage &image, Rotation oldRotation, Rotation newRotation, DocumentObserver *observer ); void setPage( PagePrivate * pd ); void setRect( const NormalizedRect &rect ); void setIsPartialUpdate( bool partialUpdate ); QImage image() const { return static_cast(job())->image(); } Rotation rotation() const { return static_cast(job())->rotation(); } DocumentObserver *observer() const; PagePrivate * page() const; NormalizedRect rect() const; bool isPartialUpdate() const; static QTransform rotationMatrix( Rotation from, Rotation to ); private: DocumentObserver *mObserver; PagePrivate * m_pd; NormalizedRect mRect; bool mIsPartialUpdate; }; } #endif diff --git a/core/script/executor_kjs.cpp b/core/script/executor_kjs.cpp index b60e85170..8aab42b93 100644 --- a/core/script/executor_kjs.cpp +++ b/core/script/executor_kjs.cpp @@ -1,124 +1,124 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2008 by Harri Porten * * * * 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 "executor_kjs_p.h" #include #include #include #include -#include +#include #include "../debug_p.h" #include "../document_p.h" #include "event_p.h" #include "kjs_app_p.h" #include "kjs_console_p.h" #include "kjs_data_p.h" #include "kjs_document_p.h" #include "kjs_event_p.h" #include "kjs_field_p.h" #include "kjs_fullscreen_p.h" #include "kjs_spell_p.h" #include "kjs_util_p.h" using namespace Okular; class Okular::ExecutorKJSPrivate { public: ExecutorKJSPrivate( DocumentPrivate *doc ) : m_doc( doc ) { initTypes(); } ~ExecutorKJSPrivate() { delete m_interpreter; } void initTypes(); DocumentPrivate *m_doc; KJSInterpreter *m_interpreter; KJSGlobalObject m_docObject; }; void ExecutorKJSPrivate::initTypes() { m_docObject = JSDocument::wrapDocument( m_doc ); m_interpreter = new KJSInterpreter( m_docObject ); KJSContext *ctx = m_interpreter->globalContext(); JSApp::initType( ctx ); JSFullscreen::initType( ctx ); JSConsole::initType( ctx ); JSData::initType( ctx ); JSDocument::initType( ctx ); JSEvent::initType( ctx ); JSField::initType( ctx ); JSSpell::initType( ctx ); JSUtil::initType( ctx ); m_docObject.setProperty( ctx, QStringLiteral("app"), JSApp::object( ctx, m_doc ) ); m_docObject.setProperty( ctx, QStringLiteral("console"), JSConsole::object( ctx ) ); m_docObject.setProperty( ctx, QStringLiteral("Doc"), m_docObject ); m_docObject.setProperty( ctx, QStringLiteral("spell"), JSSpell::object( ctx ) ); m_docObject.setProperty( ctx, QStringLiteral("util"), JSUtil::object( ctx ) ); } ExecutorKJS::ExecutorKJS( DocumentPrivate *doc ) : d( new ExecutorKJSPrivate( doc ) ) { } ExecutorKJS::~ExecutorKJS() { delete d; } void ExecutorKJS::execute( const QString &script, Event *event ) { #if 0 QString script2; QString errMsg; int errLine; if ( !KJSInterpreter::normalizeCode( script, &script2, &errLine, &errMsg ) ) { qCWarning(OkularCoreDebug) << "Parse error during normalization!"; script2 = script; } #endif KJSContext* ctx = d->m_interpreter->globalContext(); d->m_docObject.setProperty( ctx, QStringLiteral("event"), event ? JSEvent::wrapEvent( ctx, event ) : KJSUndefined() ); KJSResult result = d->m_interpreter->evaluate( QStringLiteral("okular.js"), 1, script, &d->m_docObject ); if ( result.isException() || ctx->hasException() ) { qCDebug(OkularCoreDebug) << "JS exception" << result.errorMessage(); } else { qCDebug(OkularCoreDebug) << "result:" << result.value().toString( ctx ); if (event) { qCDebug(OkularCoreDebug) << "Event Result:" << event->name() << event->type() << "value:" << event->value(); } } JSField::clearCachedFields(); } diff --git a/core/script/kjs_console.cpp b/core/script/kjs_console.cpp index 55d1eb7f2..8412bcadc 100644 --- a/core/script/kjs_console.cpp +++ b/core/script/kjs_console.cpp @@ -1,150 +1,150 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2008 by Harri Porten * * * * 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 "kjs_console_p.h" #include #include #include -#include +#include #include "../debug_p.h" using namespace Okular; static KJSPrototype *g_consoleProto; #ifdef OKULAR_JS_CONSOLE #include #include #include #include K_GLOBAL_STATIC( KDialog, g_jsConsoleWindow ) static QPlainTextEdit *g_jsConsoleLog = 0; static void createConsoleWindow() { if ( g_jsConsoleWindow.exists() ) return; g_jsConsoleWindow->setButtons( KDialog::Close | KDialog::User1 ); g_jsConsoleWindow->setButtonGuiItem( KDialog::User1, KStandardGuiItem::clear() ); QVBoxLayout *mainLay = new QVBoxLayout( g_jsConsoleWindow->mainWidget() ); mainLay->setMargin( 0 ); g_jsConsoleLog = new QPlainTextEdit( g_jsConsoleWindow->mainWidget() ); g_jsConsoleLog->setReadOnly( true ); mainLay->addWidget( g_jsConsoleLog ); QObject::connect( g_jsConsoleWindow, SIGNAL(closeClicked()), g_jsConsoleWindow, SLOT(close()) ); QObject::connect( g_jsConsoleWindow, SIGNAL(user1Clicked()), g_jsConsoleLog, SLOT(clear()) ); } static void showConsole() { createConsoleWindow(); g_jsConsoleWindow->show(); } static void hideConsole() { if ( !g_jsConsoleWindow.exists() ) return; g_jsConsoleWindow->hide(); } static void clearConsole() { if ( !g_jsConsoleWindow.exists() ) return; g_jsConsoleLog->clear(); } static void outputToConsole( const QString &message ) { showConsole(); g_jsConsoleLog->appendPlainText( message ); } #else /* OKULAR_JS_CONSOLE */ static void showConsole() { } static void hideConsole() { } static void clearConsole() { } static void outputToConsole( const QString &cMessage ) { qCDebug(OkularCoreDebug) << "CONSOLE:" << cMessage; } #endif /* OKULAR_JS_CONSOLE */ static KJSObject consoleClear( KJSContext *, void *, const KJSArguments & ) { clearConsole(); return KJSUndefined(); } static KJSObject consoleHide( KJSContext *, void *, const KJSArguments & ) { hideConsole(); return KJSUndefined(); } static KJSObject consolePrintln( KJSContext *ctx, void *, const KJSArguments &arguments ) { QString cMessage = arguments.at( 0 ).toString( ctx ); outputToConsole( cMessage ); return KJSUndefined(); } static KJSObject consoleShow( KJSContext *, void *, const KJSArguments & ) { showConsole(); return KJSUndefined(); } void JSConsole::initType( KJSContext *ctx ) { static bool initialized = false; if ( initialized ) return; initialized = true; g_consoleProto = new KJSPrototype(); g_consoleProto->defineFunction( ctx, QStringLiteral("clear"), consoleClear ); g_consoleProto->defineFunction( ctx, QStringLiteral("hide"), consoleHide ); g_consoleProto->defineFunction( ctx, QStringLiteral("println"), consolePrintln ); g_consoleProto->defineFunction( ctx, QStringLiteral("hide"), consoleShow ); } KJSObject JSConsole::object( KJSContext *ctx ) { return g_consoleProto->constructObject( ctx, nullptr ); } diff --git a/core/script/kjs_document.cpp b/core/script/kjs_document.cpp index 097268ebe..f7971ebc2 100644 --- a/core/script/kjs_document.cpp +++ b/core/script/kjs_document.cpp @@ -1,291 +1,291 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2008 by Harri Porten * * * * 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 "kjs_document_p.h" #include #include #include #include -#include +#include #include #include "../document_p.h" #include "../page.h" #include "../form.h" #include "kjs_data_p.h" #include "kjs_field_p.h" using namespace Okular; static KJSPrototype *g_docProto; // Document.numPages static KJSObject docGetNumPages( KJSContext *, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); return KJSNumber( doc->m_pagesVector.count() ); } // Document.pageNum (getter) static KJSObject docGetPageNum( KJSContext *, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); return KJSNumber( doc->m_parent->currentPage() ); } // Document.pageNum (setter) static void docSetPageNum( KJSContext* ctx, void* object, KJSObject value ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); int page = value.toInt32( ctx ); if ( page == (int)doc->m_parent->currentPage() ) return; doc->m_parent->setViewportPage( page ); } // Document.documentFileName static KJSObject docGetDocumentFileName( KJSContext *, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); return KJSString( doc->m_url.fileName() ); } // Document.filesize static KJSObject docGetFilesize( KJSContext *, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); return KJSNumber( doc->m_docSize ); } // Document.path static KJSObject docGetPath( KJSContext *, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); return KJSString( doc->m_url.toDisplayString(QUrl::PreferLocalFile) ); } // Document.URL static KJSObject docGetURL( KJSContext *, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); return KJSString( doc->m_url.toDisplayString() ); } // Document.permStatusReady static KJSObject docGetPermStatusReady( KJSContext *, void * ) { return KJSBoolean( true ); } // Document.dataObjects static KJSObject docGetDataObjects( KJSContext *ctx, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); const QList< EmbeddedFile * > *files = doc->m_generator->embeddedFiles(); KJSArray dataObjects( ctx, files ? files->count() : 0 ); if ( files ) { QList< EmbeddedFile * >::ConstIterator it = files->begin(), itEnd = files->end(); for ( int i = 0; it != itEnd; ++it, ++i ) { KJSObject newdata = JSData::wrapFile( ctx, *it ); dataObjects.setProperty( ctx, QString::number( i ), newdata ); } } return dataObjects; } // Document.external static KJSObject docGetExternal( KJSContext *, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); QWidget *widget = doc->m_widget; const bool isShell = ( widget && widget->parentWidget() && widget->parentWidget()->objectName().startsWith( QLatin1String( "okular::Shell" ) ) ); return KJSBoolean( !isShell ); } static KJSObject docGetInfo( KJSContext *ctx, void *object ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); KJSObject obj; QSet keys; keys << DocumentInfo::Title << DocumentInfo::Author << DocumentInfo::Subject << DocumentInfo::Keywords << DocumentInfo::Creator << DocumentInfo::Producer; const DocumentInfo docinfo = doc->m_parent->documentInfo( keys ); #define KEY_GET( key, property ) \ do { \ const QString data = docinfo.get( key ); \ if ( !data.isEmpty() ) \ { \ const KJSString newval( data ); \ obj.setProperty( ctx, QStringLiteral(property), newval ); \ obj.setProperty( ctx, QStringLiteral( property ).toLower(), newval ); \ } \ } while ( 0 ); KEY_GET( DocumentInfo::Title, "Title" ); KEY_GET( DocumentInfo::Author, "Author" ); KEY_GET( DocumentInfo::Subject, "Subject" ); KEY_GET( DocumentInfo::Keywords, "Keywords" ); KEY_GET( DocumentInfo::Creator, "Creator" ); KEY_GET( DocumentInfo::Producer, "Producer" ); #undef KEY_GET return obj; } #define DOCINFO_GET_METHOD( key, name ) \ static KJSObject docGet ## name( KJSContext *, void *object ) \ { \ DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); \ const DocumentInfo docinfo = doc->m_parent->documentInfo(QSet() << key ); \ return KJSString( docinfo.get( key ) ); \ } DOCINFO_GET_METHOD( DocumentInfo::Author, Author ) DOCINFO_GET_METHOD( DocumentInfo::Creator, Creator ) DOCINFO_GET_METHOD( DocumentInfo::Keywords, Keywords ) DOCINFO_GET_METHOD( DocumentInfo::Producer, Producer ) DOCINFO_GET_METHOD( DocumentInfo::Title, Title ) DOCINFO_GET_METHOD( DocumentInfo::Subject, Subject ) #undef DOCINFO_GET_METHOD // Document.getField() static KJSObject docGetField( KJSContext *context, void *object, const KJSArguments &arguments ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); QString cName = arguments.at( 0 ).toString( context ); QVector< Page * >::const_iterator pIt = doc->m_pagesVector.constBegin(), pEnd = doc->m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) { const QLinkedList< Okular::FormField * > pageFields = (*pIt)->formFields(); QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd(); for ( ; ffIt != ffEnd; ++ffIt ) { if ( (*ffIt)->name() == cName ) { return JSField::wrapField( context, *ffIt, *pIt ); } } } return KJSUndefined(); } // Document.getPageLabel() static KJSObject docGetPageLabel( KJSContext *ctx,void *object, const KJSArguments &arguments ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); int nPage = arguments.at( 0 ).toInt32( ctx ); Page *p = doc->m_pagesVector.value( nPage ); return KJSString( p ? p->label() : QString() ); } // Document.getPageRotation() static KJSObject docGetPageRotation( KJSContext *ctx, void *object, const KJSArguments &arguments ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); int nPage = arguments.at( 0 ).toInt32( ctx ); Page *p = doc->m_pagesVector.value( nPage ); return KJSNumber( p ? p->orientation() * 90 : 0 ); } // Document.gotoNamedDest() static KJSObject docGotoNamedDest( KJSContext *ctx, void *object, const KJSArguments &arguments ) { DocumentPrivate *doc = reinterpret_cast< DocumentPrivate* >( object ); QString dest = arguments.at( 0 ).toString( ctx ); DocumentViewport viewport( doc->m_generator->metaData( QStringLiteral("NamedViewport"), dest ).toString() ); if ( !viewport.isValid() ) return KJSUndefined(); doc->m_parent->setViewport( viewport ); return KJSUndefined(); } // Document.syncAnnotScan() static KJSObject docSyncAnnotScan( KJSContext *, void *, const KJSArguments & ) { return KJSUndefined(); } void JSDocument::initType( KJSContext *ctx ) { assert( g_docProto ); static bool initialized = false; if ( initialized ) return; initialized = true; g_docProto->defineProperty( ctx, QStringLiteral("numPages"), docGetNumPages ); g_docProto->defineProperty( ctx, QStringLiteral("pageNum"), docGetPageNum, docSetPageNum ); g_docProto->defineProperty( ctx, QStringLiteral("documentFileName"), docGetDocumentFileName ); g_docProto->defineProperty( ctx, QStringLiteral("filesize"), docGetFilesize ); g_docProto->defineProperty( ctx, QStringLiteral("path"), docGetPath ); g_docProto->defineProperty( ctx, QStringLiteral("URL"), docGetURL ); g_docProto->defineProperty( ctx, QStringLiteral("permStatusReady"), docGetPermStatusReady ); g_docProto->defineProperty( ctx, QStringLiteral("dataObjects"), docGetDataObjects ); g_docProto->defineProperty( ctx, QStringLiteral("external"), docGetExternal ); // info properties g_docProto->defineProperty( ctx, QStringLiteral("info"), docGetInfo ); g_docProto->defineProperty( ctx, QStringLiteral("author"), docGetAuthor ); g_docProto->defineProperty( ctx, QStringLiteral("creator"), docGetCreator ); g_docProto->defineProperty( ctx, QStringLiteral("keywords"), docGetKeywords ); g_docProto->defineProperty( ctx, QStringLiteral("producer"), docGetProducer ); g_docProto->defineProperty( ctx, QStringLiteral("title"), docGetTitle ); g_docProto->defineProperty( ctx, QStringLiteral("subject"), docGetSubject ); g_docProto->defineFunction( ctx, QStringLiteral("getField"), docGetField ); g_docProto->defineFunction( ctx, QStringLiteral("getPageLabel"), docGetPageLabel ); g_docProto->defineFunction( ctx, QStringLiteral("getPageRotation"), docGetPageRotation ); g_docProto->defineFunction( ctx, QStringLiteral("gotoNamedDest"), docGotoNamedDest ); g_docProto->defineFunction( ctx, QStringLiteral("syncAnnotScan"), docSyncAnnotScan ); } KJSGlobalObject JSDocument::wrapDocument( DocumentPrivate *doc ) { if ( !g_docProto ) g_docProto = new KJSPrototype(); return g_docProto->constructGlobalObject( doc ); } diff --git a/core/script/kjs_field.cpp b/core/script/kjs_field.cpp index 8c98ca502..75f07b705 100644 --- a/core/script/kjs_field.cpp +++ b/core/script/kjs_field.cpp @@ -1,261 +1,261 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2008 by Harri Porten * * * * 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 "kjs_field_p.h" #include #include #include #include -#include +#include #include "../debug_p.h" #include "../document_p.h" #include "../form.h" #include "../page.h" #include "../page_p.h" using namespace Okular; static KJSPrototype *g_fieldProto; typedef QHash< FormField *, Page * > FormCache; Q_GLOBAL_STATIC( FormCache, g_fieldCache ) // Helper for modified fields static void updateField( FormField *field ) { Page *page = g_fieldCache->value( field ); if (page) { Document *doc = PagePrivate::get( page )->m_doc->m_parent; QMetaObject::invokeMethod( doc, "refreshPixmaps", Qt::QueuedConnection, Q_ARG( int, page->number() ) ); emit doc->refreshFormWidget( field ); } else { qWarning() << "Could not get page of field" << field; } } // Field.doc static KJSObject fieldGetDoc( KJSContext *context, void * ) { return context->interpreter().globalObject(); } // Field.name static KJSObject fieldGetName( KJSContext *, void *object ) { const FormField *field = reinterpret_cast< FormField * >( object ); return KJSString( field->name() ); } // Field.readonly (getter) static KJSObject fieldGetReadOnly( KJSContext *, void *object ) { const FormField *field = reinterpret_cast< FormField * >( object ); return KJSBoolean( field->isReadOnly() ); } // Field.readonly (setter) static void fieldSetReadOnly( KJSContext *context, void *object, KJSObject value ) { FormField *field = reinterpret_cast< FormField * >( object ); bool b = value.toBoolean( context ); field->setReadOnly( b ); updateField( field ); } static QString fieldGetTypeHelper( const FormField *field ) { switch ( field->type() ) { case FormField::FormButton: { const FormFieldButton *button = static_cast< const FormFieldButton * >( field ); switch ( button->buttonType() ) { case FormFieldButton::Push: return QStringLiteral("button"); case FormFieldButton::CheckBox: return QStringLiteral("checkbox"); case FormFieldButton::Radio: return QStringLiteral("radiobutton"); } break; } case FormField::FormText: return QStringLiteral("text"); case FormField::FormChoice: { const FormFieldChoice *choice = static_cast< const FormFieldChoice * >( field ); switch ( choice->choiceType() ) { case FormFieldChoice::ComboBox: return QStringLiteral("combobox"); case FormFieldChoice::ListBox: return QStringLiteral("listbox"); } break; } case FormField::FormSignature: return QStringLiteral("signature"); } return QString(); } // Field.type static KJSObject fieldGetType( KJSContext *, void *object ) { const FormField *field = reinterpret_cast< FormField * >( object ); return KJSString( fieldGetTypeHelper( field ) ); } // Field.value (getter) static KJSObject fieldGetValue( KJSContext */*context*/, void *object ) { FormField *field = reinterpret_cast< FormField * >( object ); switch ( field->type() ) { case FormField::FormButton: { const FormFieldButton *button = static_cast< const FormFieldButton * >( field ); if ( button->state() ) { return KJSString( QStringLiteral( "Yes" ) ); } return KJSString( QStringLiteral( "Off" ) ); } case FormField::FormText: { const FormFieldText *text = static_cast< const FormFieldText * >( field ); return KJSString( text->text() ); } case FormField::FormChoice: { const FormFieldChoice *choice = static_cast< const FormFieldChoice * >( field ); Q_UNUSED( choice ); // ### break; } case FormField::FormSignature: { break; } } return KJSUndefined(); } // Field.value (setter) static void fieldSetValue( KJSContext *context, void *object, KJSObject value ) { FormField *field = reinterpret_cast< FormField * >( object ); switch ( field->type() ) { case FormField::FormButton: { FormFieldButton *button = static_cast< FormFieldButton * >( field ); const QString text = value.toString( context ); if ( text == QStringLiteral( "Yes" ) ) { button->setState( true ); updateField( field ); } else if ( text == QStringLiteral( "Off" ) ) { button->setState( false ); updateField( field ); } break; } case FormField::FormText: { FormFieldText *textField = static_cast< FormFieldText * >( field ); const QString text = value.toString( context ); if ( text != textField->text() ) { textField->setText( text ); updateField( field ); } break; } case FormField::FormChoice: { FormFieldChoice *choice = static_cast< FormFieldChoice * >( field ); Q_UNUSED( choice ); // ### break; } case FormField::FormSignature: { break; } } } // Field.hidden (getter) static KJSObject fieldGetHidden( KJSContext *, void *object ) { const FormField *field = reinterpret_cast< FormField * >( object ); return KJSBoolean( !field->isVisible() ); } // Field.hidden (setter) static void fieldSetHidden( KJSContext *context, void *object, KJSObject value ) { FormField *field = reinterpret_cast< FormField * >( object ); bool b = value.toBoolean( context ); field->setVisible( !b ); updateField( field ); } void JSField::initType( KJSContext *ctx ) { static bool initialized = false; if ( initialized ) return; initialized = true; if ( !g_fieldProto ) g_fieldProto = new KJSPrototype(); g_fieldProto->defineProperty( ctx, QStringLiteral("doc"), fieldGetDoc ); g_fieldProto->defineProperty( ctx, QStringLiteral("name"), fieldGetName ); g_fieldProto->defineProperty( ctx, QStringLiteral("readonly"), fieldGetReadOnly, fieldSetReadOnly ); g_fieldProto->defineProperty( ctx, QStringLiteral("type"), fieldGetType ); g_fieldProto->defineProperty( ctx, QStringLiteral("value"), fieldGetValue, fieldSetValue ); g_fieldProto->defineProperty( ctx, QStringLiteral("hidden"), fieldGetHidden, fieldSetHidden ); } KJSObject JSField::wrapField( KJSContext *ctx, FormField *field, Page *page ) { // ### cache unique wrapper KJSObject f = g_fieldProto->constructObject( ctx, field ); f.setProperty( ctx, QStringLiteral("page"), page->number() ); g_fieldCache->insert( field, page ); return f; } void JSField::clearCachedFields() { if ( g_fieldCache.exists() ) { g_fieldCache->clear(); } } diff --git a/core/script/kjs_spell.cpp b/core/script/kjs_spell.cpp index 7f43c2d0a..db1a1603a 100644 --- a/core/script/kjs_spell.cpp +++ b/core/script/kjs_spell.cpp @@ -1,42 +1,42 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2008 by Harri Porten * * * * 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 "kjs_spell_p.h" #include #include -#include +#include using namespace Okular; static KJSPrototype *g_spellProto; // Spell.available static KJSObject spellGetAvailable( KJSContext *, void * ) { return KJSBoolean( false ); } void JSSpell::initType( KJSContext *ctx ) { static bool initialized = false; if ( initialized ) return; initialized = true; g_spellProto = new KJSPrototype(); g_spellProto->defineProperty( ctx, QStringLiteral( "available" ), spellGetAvailable ); } KJSObject JSSpell::object( KJSContext *ctx ) { return g_spellProto->constructObject( ctx, nullptr ); } diff --git a/core/scripter.cpp b/core/scripter.cpp index af499e098..46462793b 100644 --- a/core/scripter.cpp +++ b/core/scripter.cpp @@ -1,96 +1,96 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * * * 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 "scripter.h" -#include +#include #include #include "debug_p.h" #include "script/executor_kjs_p.h" using namespace Okular; class Okular::ScripterPrivate { public: ScripterPrivate( DocumentPrivate *doc ) : m_doc( doc ) #ifdef WITH_KJS , m_kjs( nullptr ) #endif , m_event( nullptr ) { } DocumentPrivate *m_doc; #ifdef WITH_KJS QScopedPointer m_kjs; #endif Event *m_event; }; Scripter::Scripter( DocumentPrivate *doc ) : d( new ScripterPrivate( doc ) ) { } Scripter::~Scripter() { delete d; } QString Scripter::execute( ScriptType type, const QString &script ) { qCDebug(OkularCoreDebug) << "executing the script:"; #ifdef WITH_KJS #if 0 if ( script.length() < 1000 ) qDebug() << script; else qDebug() << script.left( 1000 ) << "[...]"; #endif static QString builtInScript; if ( builtInScript.isNull() ) { QFile builtInResource ( ":/script/builtin.js" ); if (!builtInResource.open( QIODevice::ReadOnly )) { qCDebug(OkularCoreDebug) << "failed to load builtin script"; } else { builtInScript = QString::fromUtf8( builtInResource.readAll() ); builtInResource.close(); } } switch ( type ) { case JavaScript: if ( !d->m_kjs ) { d->m_kjs.reset(new ExecutorKJS( d->m_doc )); } d->m_kjs->execute( builtInScript + script, d->m_event ); break; } #endif return QString(); } void Scripter::setEvent( Event *event ) { d->m_event = event; } Event *Scripter::event() const { return d->m_event; } diff --git a/core/sound.cpp b/core/sound.cpp index f1e3b7c52..50d18dafd 100644 --- a/core/sound.cpp +++ b/core/sound.cpp @@ -1,117 +1,117 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 "sound.h" -#include +#include using namespace Okular; class Sound::Private { public: Private( const QByteArray &data ) : m_data( QVariant( data ) ), m_type( Sound::Embedded ) { init(); } Private( const QString &url ) : m_data( QVariant( url ) ), m_type( Sound::External ) { init(); } void init() { m_samplingRate = 44100.0; m_channels = 1; m_bitsPerSample = 8; m_soundEncoding = Sound::Raw; } QVariant m_data; Sound::SoundType m_type; double m_samplingRate; int m_channels; int m_bitsPerSample; SoundEncoding m_soundEncoding; }; Sound::Sound( const QByteArray& data ) : d( new Private( data ) ) { } Sound::Sound( const QString& url ) : d( new Private( url ) ) { } Sound::~Sound() { delete d; } Sound::SoundType Sound::soundType() const { return d->m_type; } QString Sound::url() const { return d->m_type == Sound::External ? d->m_data.toString() : QString(); } QByteArray Sound::data() const { return d->m_type == Sound::Embedded ? d->m_data.toByteArray() : QByteArray(); } double Sound::samplingRate() const { return d->m_samplingRate; } void Sound::setSamplingRate( double samplingRate ) { d->m_samplingRate = samplingRate; } int Sound::channels() const { return d->m_channels; } void Sound::setChannels( int channels ) { d->m_channels = channels; } int Sound::bitsPerSample() const { return d->m_bitsPerSample; } void Sound::setBitsPerSample( int bitsPerSample ) { d->m_bitsPerSample = bitsPerSample; } Sound::SoundEncoding Sound::soundEncoding() const { return d->m_soundEncoding; } void Sound::setSoundEncoding( Sound::SoundEncoding soundEncoding ) { d->m_soundEncoding = soundEncoding; } diff --git a/core/sound.h b/core/sound.h index f412b6873..e71dbe8c0 100644 --- a/core/sound.h +++ b/core/sound.h @@ -1,127 +1,127 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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_SOUND_H_ #define _OKULAR_SOUND_H_ #include "okularcore_export.h" -#include -#include +#include +#include namespace Okular { /** * @short Contains information about a sound object. * * This class encapsulates the information about a sound object * which is used for links on enter/leave page event. */ class OKULARCORE_EXPORT Sound { public: /** * Describes where the sound is stored. */ enum SoundType { External, ///< Is stored at external resource (e.g. url) Embedded ///< Is stored embedded in the document }; /** * Describes the encoding of the sound data. */ enum SoundEncoding { Raw, ///< Is not encoded Signed, ///< Is encoded with twos-complement values muLaw, ///< Is µ-law encoded ALaw ///< Is A-law encoded }; /** * Creates a new sound object with the given embedded * sound @p data. */ explicit Sound( const QByteArray& data ); /** * Creates a new sound object with the given external @p filename. */ explicit Sound( const QString& filename ); /** * Destroys the sound object. */ ~Sound(); /** * Returns the type of the sound object. */ SoundType soundType() const; /** * Returns the external storage url of the sound data. */ QString url() const; /** * Returns the embedded sound data. */ QByteArray data() const; /** * Sets the sampling @p rate. */ void setSamplingRate( double rate ); /** * Returns the sampling rate. */ double samplingRate() const; /** * Sets the number of @p channels. */ void setChannels( int channels ); /** * Returns the number of channels. */ int channels() const; /** * Sets the bits per sample @p rate. */ void setBitsPerSample( int rate ); /** * Returns the bits per sample rate. */ int bitsPerSample() const; /** * Sets the type of sound @p encoding. */ void setSoundEncoding( SoundEncoding encoding ); /** * Returns the sound encoding. */ SoundEncoding soundEncoding() const; private: class Private; Private* const d; Q_DISABLE_COPY( Sound ) }; } #endif diff --git a/core/sourcereference.cpp b/core/sourcereference.cpp index bde8bc1bd..eb109de45 100644 --- a/core/sourcereference.cpp +++ b/core/sourcereference.cpp @@ -1,103 +1,103 @@ /*************************************************************************** * Copyright (C) 2007,2008 by Pino Toscano * * * * 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 "sourcereference.h" #include "sourcereference_p.h" -#include -#include +#include +#include #include using namespace Okular; class SourceReference::Private { public: Private() : row( 0 ), column( 0 ) { } QString filename; int row; int column; }; SourceReference::SourceReference( const QString &fileName, int row, int column ) : d( new Private ) { d->filename = fileName; d->row = row; d->column = column; } SourceReference::~SourceReference() { delete d; } QString SourceReference::fileName() const { return d->filename; } int SourceReference::row() const { return d->row; } int SourceReference::column() const { return d->column; } bool Okular::extractLilyPondSourceReference( const QUrl &url, QString *file, int *row, int *col ) { // Example URL is: textedit:///home/foo/bar.ly:42:42:42 // The three numbers are apparently: line:beginning of column:end of column if ( url.scheme() != QStringLiteral("textedit") ) return false; // There can be more, in case the filename contains : if (url.fileName().count(':') < 3) { return false; } QStringList parts(url.path().split(':')); bool ok; // Take out the things we need int columnEnd = parts.takeLast().toInt(&ok); // apparently we don't use this Q_UNUSED(columnEnd); if (!ok) { return false; } *col = parts.takeLast().toInt(&ok); if (!ok) { return false; } *row = parts.takeLast().toInt(&ok); if (!ok) { return false; } // In case the path itself contains :, we need to reconstruct it after removing all the numbers *file = parts.join(':'); return (!file->isEmpty()); } QString Okular::sourceReferenceToolTip( const QString &source, int row, int col ) { Q_UNUSED( row ); Q_UNUSED( col ); return i18nc( "'source' is a source file", "Source: %1", source ); } diff --git a/core/textdocumentgenerator.cpp b/core/textdocumentgenerator.cpp index 78a571425..37286789a 100644 --- a/core/textdocumentgenerator.cpp +++ b/core/textdocumentgenerator.cpp @@ -1,553 +1,553 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "textdocumentgenerator.h" #include "textdocumentgenerator_p.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "action.h" #include "annotations.h" #include "page.h" #include "textpage.h" #include "document.h" using namespace Okular; /** * Generic Converter Implementation */ TextDocumentConverter::TextDocumentConverter() : QObject( nullptr ), d_ptr( new TextDocumentConverterPrivate ) { } TextDocumentConverter::~TextDocumentConverter() { delete d_ptr; } QTextDocument *TextDocumentConverter::convert( const QString & ) { return nullptr; } Document::OpenResult TextDocumentConverter::convertWithPassword( const QString &fileName, const QString & ) { QTextDocument *doc = convert( fileName ); setDocument( doc ); return doc != nullptr ? Document::OpenSuccess : Document::OpenError; } QTextDocument *TextDocumentConverter::document() { return d_ptr->mDocument; } void TextDocumentConverter::setDocument( QTextDocument *document ) { d_ptr->mDocument = document; } DocumentViewport TextDocumentConverter::calculateViewport( QTextDocument *document, const QTextBlock &block ) { return TextDocumentUtils::calculateViewport( document, block ); } TextDocumentGenerator* TextDocumentConverter::generator() const { return d_ptr->mParent ? d_ptr->mParent->q_func() : nullptr; } /** * Generic Generator Implementation */ Okular::TextPage* TextDocumentGeneratorPrivate::createTextPage( int pageNumber ) const { #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( const TextDocumentGenerator ); #endif Okular::TextPage *textPage = new Okular::TextPage; int start, end; #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif TextDocumentUtils::calculatePositions( mDocument, pageNumber, start, end ); { QTextCursor cursor( mDocument ); for ( int i = start; i < end - 1; ++i ) { cursor.setPosition( i ); cursor.setPosition( i + 1, QTextCursor::KeepAnchor ); QString text = cursor.selectedText(); if ( text.length() == 1 ) { QRectF rect; TextDocumentUtils::calculateBoundingRect( mDocument, i, i + 1, rect, pageNumber ); if ( pageNumber == -1 ) text = QStringLiteral("\n"); textPage->append( text, new Okular::NormalizedRect( rect.left(), rect.top(), rect.right(), rect.bottom() ) ); } } } #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif return textPage; } void TextDocumentGeneratorPrivate::addAction( Action *action, int cursorBegin, int cursorEnd ) { if ( !action ) return; LinkPosition position; position.link = action; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mLinkPositions.append( position ); } void TextDocumentGeneratorPrivate::addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ) { if ( !annotation ) return; annotation->setFlags( annotation->flags() | Okular::Annotation::External ); AnnotationPosition position; position.annotation = annotation; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mAnnotationPositions.append( position ); } void TextDocumentGeneratorPrivate::addTitle( int level, const QString &title, const QTextBlock &block ) { TitlePosition position; position.level = level; position.title = title; position.block = block; mTitlePositions.append( position ); } void TextDocumentGeneratorPrivate::addMetaData( const QString &key, const QString &value, const QString &title ) { mDocumentInfo.set( key, value, title ); } void TextDocumentGeneratorPrivate::addMetaData( DocumentInfo::Key key, const QString &value ) { mDocumentInfo.set( key, value ); } void TextDocumentGeneratorPrivate::generateLinkInfos() { for ( int i = 0; i < mLinkPositions.count(); ++i ) { const LinkPosition &linkPosition = mLinkPositions[ i ]; LinkInfo info; info.link = linkPosition.link; TextDocumentUtils::calculateBoundingRect( mDocument, linkPosition.startPosition, linkPosition.endPosition, info.boundingRect, info.page ); if ( info.page >= 0 ) mLinkInfos.append( info ); } } void TextDocumentGeneratorPrivate::generateAnnotationInfos() { for ( int i = 0; i < mAnnotationPositions.count(); ++i ) { const AnnotationPosition &annotationPosition = mAnnotationPositions[ i ]; AnnotationInfo info; info.annotation = annotationPosition.annotation; TextDocumentUtils::calculateBoundingRect( mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page ); if ( info.page >= 0 ) mAnnotationInfos.append( info ); } } void TextDocumentGeneratorPrivate::generateTitleInfos() { QStack< QPair > parentNodeStack; QDomNode parentNode = mDocumentSynopsis; parentNodeStack.push( qMakePair( 0, parentNode ) ); for ( int i = 0; i < mTitlePositions.count(); ++i ) { const TitlePosition &position = mTitlePositions[ i ]; Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport( mDocument, position.block ); QDomElement item = mDocumentSynopsis.createElement( position.title ); item.setAttribute( QStringLiteral("Viewport"), viewport.toString() ); int headingLevel = position.level; // we need a parent, which has to be at a higher heading level than this heading level // so we just work through the stack while ( ! parentNodeStack.isEmpty() ) { int parentLevel = parentNodeStack.top().first; if ( parentLevel < headingLevel ) { // this is OK as a parent parentNode = parentNodeStack.top().second; break; } else { // we'll need to be further into the stack parentNodeStack.pop(); } } parentNode.appendChild( item ); parentNodeStack.push( qMakePair( headingLevel, QDomNode(item) ) ); } } void TextDocumentGeneratorPrivate::initializeGenerator() { Q_Q( TextDocumentGenerator ); mConverter->d_ptr->mParent = q->d_func(); if ( mGeneralSettings ) { mFont = mGeneralSettings->font(); } q->setFeature( Generator::TextExtraction ); q->setFeature( Generator::PrintNative ); q->setFeature( Generator::PrintToFile ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING if ( QFontDatabase::supportsThreadedFontRendering() ) q->setFeature( Generator::Threaded ); #endif QObject::connect( mConverter, SIGNAL(addAction(Action*,int,int)), q, SLOT(addAction(Action*,int,int)) ); QObject::connect( mConverter, SIGNAL(addAnnotation(Annotation*,int,int)), q, SLOT(addAnnotation(Annotation*,int,int)) ); QObject::connect( mConverter, SIGNAL(addTitle(int,QString,QTextBlock)), q, SLOT(addTitle(int,QString,QTextBlock)) ); QObject::connect( mConverter, SIGNAL(addMetaData(QString,QString,QString)), q, SLOT(addMetaData(QString,QString,QString)) ); QObject::connect( mConverter, SIGNAL(addMetaData(DocumentInfo::Key,QString)), q, SLOT(addMetaData(DocumentInfo::Key,QString)) ); QObject::connect( mConverter, &TextDocumentConverter::error, q, &Generator::error ); QObject::connect( mConverter, &TextDocumentConverter::warning, q, &Generator::warning ); QObject::connect( mConverter, &TextDocumentConverter::notice, q, &Generator::notice ); } TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString& configName , QObject *parent, const QVariantList &args) : Okular::Generator( *new TextDocumentGeneratorPrivate( converter ), parent, args ) { Q_D( TextDocumentGenerator ); d->mGeneralSettings = new TextDocumentSettings( configName, this ); d->initializeGenerator(); } TextDocumentGenerator::~TextDocumentGenerator() { } Document::OpenResult TextDocumentGenerator::loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString &password ) { Q_D( TextDocumentGenerator ); const Document::OpenResult openResult = d->mConverter->convertWithPassword( fileName, password ); if ( openResult != Document::OpenSuccess ) { d->mDocument = nullptr; // loading failed, cleanup all the stuff eventually gathered from the converter d->mTitlePositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::LinkPosition &linkPos, d->mLinkPositions ) { delete linkPos.link; } d->mLinkPositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::AnnotationPosition &annPos, d->mAnnotationPositions ) { delete annPos.annotation; } d->mAnnotationPositions.clear(); return openResult; } d->mDocument = d->mConverter->document(); d->generateTitleInfos(); d->generateLinkInfos(); d->generateAnnotationInfos(); pagesVector.resize( d->mDocument->pageCount() ); const QSize size = d->mDocument->pageSize().toSize(); QVector< QLinkedList > objects( d->mDocument->pageCount() ); for ( int i = 0; i < d->mLinkInfos.count(); ++i ) { const TextDocumentGeneratorPrivate::LinkInfo &info = d->mLinkInfos.at( i ); // in case that the converter report bogus link info data, do not assert here if ( info.page >= objects.count() ) continue; const QRectF rect = info.boundingRect; objects[ info.page ].append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link ) ); } QVector< QLinkedList > annots( d->mDocument->pageCount() ); for ( int i = 0; i < d->mAnnotationInfos.count(); ++i ) { const TextDocumentGeneratorPrivate::AnnotationInfo &info = d->mAnnotationInfos[ i ]; annots[ info.page ].append( info.annotation ); } for ( int i = 0; i < d->mDocument->pageCount(); ++i ) { Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); pagesVector[ i ] = page; if ( !objects.at( i ).isEmpty() ) { page->setObjectRects( objects.at( i ) ); } QLinkedList::ConstIterator annIt = annots.at( i ).begin(), annEnd = annots.at( i ).end(); for ( ; annIt != annEnd; ++annIt ) { page->addAnnotation( *annIt ); } } return openResult; } bool TextDocumentGenerator::doCloseDocument() { Q_D( TextDocumentGenerator ); delete d->mDocument; d->mDocument = nullptr; d->mTitlePositions.clear(); d->mLinkPositions.clear(); d->mLinkInfos.clear(); d->mAnnotationPositions.clear(); d->mAnnotationInfos.clear(); // do not use clear() for the following two, otherwise they change type d->mDocumentInfo = Okular::DocumentInfo(); d->mDocumentSynopsis = Okular::DocumentSynopsis(); return true; } bool TextDocumentGenerator::canGeneratePixmap() const { return Generator::canGeneratePixmap(); } void TextDocumentGenerator::generatePixmap( Okular::PixmapRequest * request ) { Generator::generatePixmap( request ); } QImage TextDocumentGeneratorPrivate::image( PixmapRequest * request ) { if ( !mDocument ) return QImage(); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( TextDocumentGenerator ); #endif QImage image( request->width(), request->height(), QImage::Format_ARGB32 ); image.fill( Qt::white ); QPainter p; p.begin( &image ); qreal width = request->width(); qreal height = request->height(); const QSize size = mDocument->pageSize().toSize(); p.scale( width / (qreal)size.width(), height / (qreal)size.height() ); QRect rect; rect = QRect( 0, request->pageNumber() * size.height(), size.width(), size.height() ); p.translate( QPoint( 0, request->pageNumber() * size.height() * -1 ) ); p.setClipRect( rect ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif QAbstractTextDocumentLayout::PaintContext context; context.palette.setColor( QPalette::Text, Qt::black ); // FIXME Fix Qt, this doesn't work, we have horrible hacks // in the generators that return html, remove that code // if Qt ever gets fixed // context.palette.setColor( QPalette::Link, Qt::blue ); context.clip = rect; mDocument->setDefaultFont( mFont ); mDocument->documentLayout()->draw( &p, context ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif p.end(); return image; } Okular::TextPage* TextDocumentGenerator::textPage( Okular::TextRequest * request ) { Q_D( TextDocumentGenerator ); return d->createTextPage( request->page()->number() ); } bool TextDocumentGenerator::print( QPrinter& printer ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; d->mDocument->print( &printer ); return true; } Okular::DocumentInfo TextDocumentGenerator::generateDocumentInfo( const QSet & /*keys*/ ) const { Q_D( const TextDocumentGenerator ); return d->mDocumentInfo; } const Okular::DocumentSynopsis* TextDocumentGenerator::generateDocumentSynopsis() { Q_D( TextDocumentGenerator ); if ( !d->mDocumentSynopsis.hasChildNodes() ) return nullptr; else return &d->mDocumentSynopsis; } QVariant TextDocumentGeneratorPrivate::metaData( const QString &key, const QVariant &option ) const { Q_UNUSED( option ) if ( key == QLatin1String("DocumentTitle") ) { return mDocumentInfo.get( DocumentInfo::Title ); } return QVariant(); } Okular::ExportFormat::List TextDocumentGenerator::exportFormats( ) const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PDF ) ); if ( QTextDocumentWriter::supportedDocumentFormats().contains( "ODF" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::OpenDocumentText ) ); } if ( QTextDocumentWriter::supportedDocumentFormats().contains( "HTML" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::HTML ) ); } } return formats; } bool TextDocumentGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; if ( format.mimeType().name() == QLatin1String( "application/pdf" ) ) { QFile file( fileName ); if ( !file.open( QIODevice::WriteOnly ) ) return false; QPrinter printer( QPrinter::HighResolution ); printer.setOutputFormat( QPrinter::PdfFormat ); printer.setOutputFileName( fileName ); d->mDocument->print( &printer ); return true; } else if ( format.mimeType().name() == QLatin1String( "text/plain" ) ) { QFile file( fileName ); if ( !file.open( QIODevice::WriteOnly ) ) return false; QTextStream out( &file ); out << d->mDocument->toPlainText(); return true; } else if ( format.mimeType().name() == QLatin1String( "application/vnd.oasis.opendocument.text" ) ) { QTextDocumentWriter odfWriter( fileName, "odf" ); return odfWriter.write( d->mDocument ); } else if ( format.mimeType().name() == QLatin1String( "text/html" ) ) { QTextDocumentWriter odfWriter( fileName, "html" ); return odfWriter.write( d->mDocument ); } return false; } bool TextDocumentGenerator::reparseConfig() { Q_D( TextDocumentGenerator ); const QFont newFont = d->mGeneralSettings->font(); if ( newFont != d->mFont ) { d->mFont = newFont; return true; } return false; } void TextDocumentGenerator::addPages( KConfigDialog* /*dlg*/ ) { qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator"; return; } TextDocumentSettings* TextDocumentGenerator::generalSettings() { Q_D( TextDocumentGenerator ); return d->mGeneralSettings; } #include "moc_textdocumentgenerator.cpp" diff --git a/core/textdocumentgenerator_p.h b/core/textdocumentgenerator_p.h index df5431c43..1a4b869c5 100644 --- a/core/textdocumentgenerator_p.h +++ b/core/textdocumentgenerator_p.h @@ -1,203 +1,203 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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_TEXTDOCUMENTGENERATOR_P_H_ #define _OKULAR_TEXTDOCUMENTGENERATOR_P_H_ -#include -#include -#include +#include +#include +#include #include "action.h" #include "document.h" #include "generator_p.h" #include "textdocumentgenerator.h" #include "debug_p.h" namespace Okular { namespace TextDocumentUtils { static void calculateBoundingRect( QTextDocument *document, int startPosition, int endPosition, QRectF &rect, int &page ) { const QSizeF pageSize = document->pageSize(); const QTextBlock startBlock = document->findBlock( startPosition ); const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect( startBlock ); const QTextBlock endBlock = document->findBlock( endPosition ); const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect( endBlock ); QTextLayout *startLayout = startBlock.layout(); QTextLayout *endLayout = endBlock.layout(); if (!startLayout || !endLayout) { qCWarning(OkularCoreDebug) << "Start or end layout not found" << startLayout << endLayout; page = -1; return; } int startPos = startPosition - startBlock.position(); int endPos = endPosition - endBlock.position(); const QTextLine startLine = startLayout->lineForTextPosition( startPos ); const QTextLine endLine = endLayout->lineForTextPosition( endPos ); double x = startBoundingRect.x() + startLine.cursorToX( startPos ); double y = startBoundingRect.y() + startLine.y(); double r = endBoundingRect.x() + endLine.cursorToX( endPos ); double b = endBoundingRect.y() + endLine.y() + endLine.height(); int offset = qRound( y ) % qRound( pageSize.height() ); if ( x > r ) { // line break, so return a pseudo character on the start line rect = QRectF( x / pageSize.width(), offset / pageSize.height(), 3 / pageSize.width(), startLine.height() / pageSize.height() ); page = -1; return; } page = qRound( y ) / qRound( pageSize.height() ); rect = QRectF( x / pageSize.width(), offset / pageSize.height(), (r - x) / pageSize.width(), (b - y) / pageSize.height() ); } static void calculatePositions( QTextDocument *document, int page, int &start, int &end ) { const QAbstractTextDocumentLayout *layout = document->documentLayout(); const QSizeF pageSize = document->pageSize(); double margin = document->rootFrame()->frameFormat().margin(); /** * Take the upper left and lower left corner including the margin */ start = layout->hitTest( QPointF( margin, (page * pageSize.height()) + margin ), Qt::FuzzyHit ); end = layout->hitTest( QPointF( margin, ((page + 1) * pageSize.height()) - margin ), Qt::FuzzyHit ); } static Okular::DocumentViewport calculateViewport( QTextDocument *document, const QTextBlock &block ) { const QSizeF pageSize = document->pageSize(); const QRectF rect = document->documentLayout()->blockBoundingRect( block ); int page = qRound( rect.y() ) / qRound( pageSize.height() ); int offset = qRound( rect.y() ) % qRound( pageSize.height() ); Okular::DocumentViewport viewport( page ); viewport.rePos.normalizedX = (double)rect.x() / (double)pageSize.width(); viewport.rePos.normalizedY = (double)offset / (double)pageSize.height(); viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::Center; return viewport; } } class TextDocumentConverterPrivate { public: TextDocumentConverterPrivate() : mParent( nullptr ) { } TextDocumentGeneratorPrivate *mParent; QTextDocument *mDocument; }; class TextDocumentGeneratorPrivate : public GeneratorPrivate { friend class TextDocumentConverter; public: TextDocumentGeneratorPrivate( TextDocumentConverter *converter ) : mConverter( converter ), mDocument( nullptr ), mGeneralSettings( nullptr ) { } virtual ~TextDocumentGeneratorPrivate() { delete mConverter; delete mDocument; } void initializeGenerator(); Q_DECLARE_PUBLIC( TextDocumentGenerator ) /* reimp */ QVariant metaData( const QString &key, const QVariant &option ) const override; /* reimp */ QImage image( PixmapRequest * ) override; void calculateBoundingRect( int startPosition, int endPosition, QRectF &rect, int &page ) const; void calculatePositions( int page, int &start, int &end ) const; Okular::TextPage* createTextPage( int ) const; void addAction( Action *action, int cursorBegin, int cursorEnd ); void addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ); void addTitle( int level, const QString &title, const QTextBlock &position ); void addMetaData( const QString &key, const QString &value, const QString &title ); void addMetaData( DocumentInfo::Key, const QString &value ); void generateLinkInfos(); void generateAnnotationInfos(); void generateTitleInfos(); TextDocumentConverter *mConverter; QTextDocument *mDocument; Okular::DocumentInfo mDocumentInfo; Okular::DocumentSynopsis mDocumentSynopsis; struct TitlePosition { int level; QString title; QTextBlock block; }; QList mTitlePositions; struct LinkPosition { int startPosition; int endPosition; Action *link; }; QList mLinkPositions; struct LinkInfo { int page; QRectF boundingRect; Action *link; }; QList mLinkInfos; struct AnnotationPosition { int startPosition; int endPosition; Annotation *annotation; }; QList mAnnotationPositions; struct AnnotationInfo { int page; QRectF boundingRect; Annotation *annotation; }; QList mAnnotationInfos; TextDocumentSettings *mGeneralSettings; QFont mFont; }; } #endif diff --git a/core/textdocumentsettings_p.h b/core/textdocumentsettings_p.h index 5f09ba605..998156b0f 100644 --- a/core/textdocumentsettings_p.h +++ b/core/textdocumentsettings_p.h @@ -1,46 +1,48 @@ /*************************************************************************** * Copyright (C) 2013 by Azat Khuzhin * * * * 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_TEXTDOCUMENTSETTINGS_P_H_ #define _OKULAR_TEXTDOCUMENTSETTINGS_P_H_ class KFontRequester; class Ui_TextDocumentSettings; namespace Okular { class TextDocumentSettingsWidgetPrivate { public: /** * @note the private class won't take ownership of the ui, so you * must delete it yourself */ TextDocumentSettingsWidgetPrivate(Ui_TextDocumentSettings *ui) : mUi(ui) {} KFontRequester *mFont; Ui_TextDocumentSettings *mUi; }; class TextDocumentSettingsPrivate : public QObject { + Q_OBJECT + public: TextDocumentSettingsPrivate(QObject *parent) : QObject(parent) {} QFont mFont; }; } #endif diff --git a/core/textpage.cpp b/core/textpage.cpp index 9622f5506..95de94762 100644 --- a/core/textpage.cpp +++ b/core/textpage.cpp @@ -1,2038 +1,2038 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymanski * * * * 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 "textpage.h" #include "textpage_p.h" -#include +#include #include "area.h" #include "debug_p.h" #include "misc.h" #include "page.h" #include "page_p.h" #include #include #include using namespace Okular; class SearchPoint { public: SearchPoint() : offset_begin( -1 ), offset_end( -1 ) { } /** The TinyTextEntity containing the first character of the match. */ TextList::ConstIterator it_begin; /** The TinyTextEntity containing the last character of the match. */ TextList::ConstIterator it_end; /** The index of the first character of the match in (*it_begin)->text(). * Satisfies 0 <= offset_begin < (*it_begin)->text().length(). */ int offset_begin; /** One plus the index of the last character of the match in (*it_end)->text(). * Satisfies 0 < offset_end <= (*it_end)->text().length(). */ int offset_end; }; /* text comparison functions */ static bool CaseInsensitiveCmpFn( const QStringRef & from, const QStringRef & to ) { return from.compare( to, Qt::CaseInsensitive ) == 0; } static bool CaseSensitiveCmpFn( const QStringRef & from, const QStringRef & to ) { return from.compare( to, Qt::CaseSensitive ) == 0; } /** * Returns true iff segments [@p left1, @p right1] and [@p left2, @p right2] on the real line * overlap within @p threshold percent, i. e. iff the ratio of the length of the * intersection of the segments to the length of the shortest of the two input segments * is not smaller than the threshold. */ static bool segmentsOverlap(double left1, double right1, double left2, double right2, int threshold) { // check if one consumes another fully (speed optimization) if (left1 <= left2 && right1 >= right2) return true; if (left1 >= left2 && right1 <= right2) return true; // check if there is overlap above threshold if (right2 >= left1 && right1 >= left2) { double overlap = (right2 >= right1) ? right1 - left2 : right2 - left1; double length1 = right1 - left1, length2 = right2 - left2; return overlap * 100 >= threshold * qMin(length1, length2); } return false; } static bool doesConsumeY(const QRect& first, const QRect& second, int threshold) { return segmentsOverlap(first.top(), first.bottom(), second.top(), second.bottom(), threshold); } static bool doesConsumeY(const NormalizedRect& first, const NormalizedRect& second, int threshold) { return segmentsOverlap(first.top, first.bottom, second.top, second.bottom, threshold); } /* Rationale behind TinyTextEntity: instead of storing directly a QString for the text of an entity, we store the UTF-16 data and their length. This way, we save about 4 int's wrt a QString, and we can create a new string from that raw data (that's the only penalty of that). Even better, if the string we need to store has at most MaxStaticChars characters, then we store those in place of the QChar* that would be used (with new[] + free[]) for the data. */ class TinyTextEntity { static const int MaxStaticChars = sizeof( QChar * ) / sizeof( QChar ); public: TinyTextEntity( const QString &text, const NormalizedRect &rect ) : area( rect ) { Q_ASSERT_X( !text.isEmpty(), "TinyTextEntity", "empty string" ); Q_ASSERT_X( sizeof( d ) == sizeof( QChar * ), "TinyTextEntity", "internal storage is wider than QChar*, fix it!" ); length = text.length(); switch ( length ) { #if QT_POINTER_SIZE >= 8 case 4: d.qc[3] = text.at( 3 ).unicode(); // fall through case 3: d.qc[2] = text.at( 2 ).unicode(); #endif // fall through case 2: d.qc[1] = text.at( 1 ).unicode(); // fall through case 1: d.qc[0] = text.at( 0 ).unicode(); break; default: d.data = new QChar[ length ]; std::memcpy( d.data, text.constData(), length * sizeof( QChar ) ); } } ~TinyTextEntity() { if ( length > MaxStaticChars ) { delete [] d.data; } } inline QString text() const { return length <= MaxStaticChars ? QString::fromRawData( ( const QChar * )&d.qc[0], length ) : QString::fromRawData( d.data, length ); } inline NormalizedRect transformedArea( const QTransform &matrix ) const { NormalizedRect transformed_area = area; transformed_area.transform( matrix ); return transformed_area; } NormalizedRect area; private: Q_DISABLE_COPY( TinyTextEntity ) union { QChar *data; ushort qc[MaxStaticChars]; } d; int length; }; TextEntity::TextEntity( const QString &text, NormalizedRect *area ) : m_text( text ), m_area( area ), d( nullptr ) { } TextEntity::~TextEntity() { delete m_area; } QString TextEntity::text() const { return m_text; } NormalizedRect* TextEntity::area() const { return m_area; } NormalizedRect TextEntity::transformedArea(const QTransform &matrix) const { NormalizedRect transformed_area = *m_area; transformed_area.transform( matrix ); return transformed_area; } TextPagePrivate::TextPagePrivate() : m_page( nullptr ) { } TextPagePrivate::~TextPagePrivate() { qDeleteAll( m_searchPoints ); qDeleteAll( m_words ); } TextPage::TextPage() : d( new TextPagePrivate() ) { } TextPage::TextPage( const TextEntity::List &words ) : d( new TextPagePrivate() ) { TextEntity::List::ConstIterator it = words.constBegin(), itEnd = words.constEnd(); for ( ; it != itEnd; ++it ) { TextEntity *e = *it; if ( !e->text().isEmpty() ) d->m_words.append( new TinyTextEntity( e->text(), *e->area() ) ); delete e; } } TextPage::~TextPage() { delete d; } void TextPage::append( const QString &text, NormalizedRect *area ) { if ( !text.isEmpty() ) d->m_words.append( new TinyTextEntity( text.normalized(QString::NormalizationForm_KC), *area ) ); delete area; } struct WordWithCharacters { WordWithCharacters(TinyTextEntity *w, const TextList &c) : word(w), characters(c) { } inline QString text() const { return word->text(); } inline const NormalizedRect &area() const { return word->area; } TinyTextEntity *word; TextList characters; }; typedef QList WordsWithCharacters; /** * We will divide the whole page in some regions depending on the horizontal and * vertical spacing among different regions. Each region will have an area and an * associated WordsWithCharacters in sorted order. */ class RegionText { public: RegionText() { }; RegionText(const WordsWithCharacters &wordsWithCharacters, const QRect &area) : m_region_wordWithCharacters(wordsWithCharacters), m_area(area) { } inline QString string() const { QString res; foreach(const WordWithCharacters &word, m_region_wordWithCharacters) res += word.text(); return res; } inline WordsWithCharacters text() const { return m_region_wordWithCharacters; } inline QRect area() const { return m_area; } inline void setArea(const QRect &area) { m_area = area; } inline void setText(const WordsWithCharacters &wordsWithCharacters) { m_region_wordWithCharacters = wordsWithCharacters; } private: WordsWithCharacters m_region_wordWithCharacters; QRect m_area; }; RegularAreaRect * TextPage::textArea ( TextSelection * sel) const { if ( d->m_words.isEmpty() ) return new RegularAreaRect(); /** It works like this: There are two cursors, we need to select all the text between them. The coordinates are normalised, leftTop is (0,0) rightBottom is (1,1), so for cursors start (sx,sy) and end (ex,ey) we start with finding text rectangles under those points, if not we search for the first that is to the right to it in the same baseline, if none found, then we search for the first rectangle with a baseline under the cursor, having two points that are the best rectangles to both of the cursors: (rx,ry)x(tx,ty) for start and (ux,uy)x(vx,vy) for end, we do a 1. (rx,ry)x(1,ty) 2. (0,ty)x(1,uy) 3. (0,uy)x(vx,vy) To find the closest rectangle to cursor (cx,cy) we search for a rectangle that either contains the cursor or that has a left border >= cx and bottom border >= cy. */ RegularAreaRect * ret= new RegularAreaRect; PagePrivate *pagePrivate = PagePrivate::get(d->m_page); const QTransform matrix = pagePrivate ? pagePrivate->rotationMatrix() : QTransform(); #if 0 int it = -1; int itB = -1; int itE = -1; // ending cursor is higher than start cursor, we need to find positions in reverse NormalizedRect tmp; NormalizedRect start; NormalizedRect end; NormalizedPoint startC = sel->start(); double startCx = startC.x; double startCy = startC.y; NormalizedPoint endC = sel->end(); double endCx = endC.x; double endCy = endC.y; if ( sel->direction() == 1 || ( sel->itB() == -1 && sel->direction() == 0 ) ) { #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "running first loop"; #endif const int count = d->m_words.count(); for ( it = 0; it < count; it++ ) { tmp = *d->m_words[ it ]->area(); if ( tmp.contains( startCx, startCy ) || ( tmp.top <= startCy && tmp.bottom >= startCy && tmp.left >= startCx ) || ( tmp.top >= startCy)) { /// we have found the (rx,ry)x(tx,ty) itB = it; #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "start is" << itB << "count is" << d->m_words.count(); #endif break; } } sel->itB( itB ); } itB = sel->itB(); #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "direction is" << sel->direction(); qCWarning(OkularCoreDebug) << "reloaded start is" << itB << "against" << sel->itB(); #endif if ( sel->direction() == 0 || ( sel->itE() == -1 && sel->direction() == 1 ) ) { #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "running second loop"; #endif for ( it = d->m_words.count() - 1; it >= itB; it-- ) { tmp = *d->m_words[ it ]->area(); if ( tmp.contains( endCx, endCy ) || ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx ) || ( tmp.bottom <= endCy ) ) { /// we have found the (ux,uy)x(vx,vy) itE = it; #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "ending is" << itE << "count is" << d->m_words.count(); qCWarning(OkularCoreDebug) << "conditions" << tmp.contains( endCx, endCy ) << " " << ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx ) << " " << ( tmp.top >= endCy); #endif break; } } sel->itE( itE ); } #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "reloaded ending is" << itE << "against" << sel->itE(); #endif if ( sel->itB() != -1 && sel->itE() != -1 ) { start = *d->m_words[ sel->itB() ]->area(); end = *d->m_words[ sel->itE() ]->area(); NormalizedRect first, second, third; /// finding out if there is more than one baseline between them is a hard and discussable task /// we will create a rectangle (rx,0)x(tx,1) and will check how many times does it intersect the /// areas, if more than one -> we have a three or over line selection first = start; second.top = start.bottom; first.right = second.right = 1; third = end; third.left = second.left = 0; second.bottom = end.top; int selMax = qMax( sel->itB(), sel->itE() ); for ( it = qMin( sel->itB(), sel->itE() ); it <= selMax; ++it ) { tmp = *d->m_words[ it ]->area(); if ( tmp.intersects( &first ) || tmp.intersects( &second ) || tmp.intersects( &third ) ) ret->appendShape( d->m_words.at( it )->transformedArea( matrix ) ); } } #else const double scaleX = d->m_page->width(); const double scaleY = d->m_page->height(); NormalizedPoint startC = sel->start(); NormalizedPoint endC = sel->end(); NormalizedPoint temp; // if startPoint is right to endPoint swap them if(startC.x > endC.x) { temp = startC; startC = endC; endC = temp; } // minX,maxX,minY,maxY gives the bounding rectangle coordinates of the document const NormalizedRect boundingRect = d->m_page->boundingBox(); const QRect content = boundingRect.geometry(scaleX,scaleY); const double minX = content.left(); const double maxX = content.right(); const double minY = content.top(); const double maxY = content.bottom(); /** * We will now find out the TinyTextEntity for the startRectangle and TinyTextEntity for * the endRectangle. We have four cases: * * Case 1(a): both startpoint and endpoint are out of the bounding Rectangle and at one side, so the rectangle made of start * and endPoint are outof the bounding rect (do not intersect) * * Case 1(b): both startpoint and endpoint are out of bounding rect, but they are in different side, so is their rectangle * * Case 2(a): find the rectangle which contains start and endpoint and having some * TextEntity * * Case 2(b): if 2(a) fails (if startPoint and endPoint both are unchanged), then we check whether there is any * TextEntity within the rect made by startPoint and endPoint * * Case 3: Now, we may have two type of selection. * 1. startpoint is left-top of start_end and endpoint is right-bottom * 2. startpoint is left-bottom of start_end and endpoint is top-right * * Also, as 2(b) is passed, we might have it,itEnd or both unchanged, but the fact is that we have * text within them. so, we need to search for the best suitable textposition for start and end. * * Case 3(a): We search the nearest rectangle consisting of some * TinyTextEntity right to or bottom of the startPoint for selection 01. * And, for selection 02, we have to search for right and top * * Case 3(b): For endpoint, we have to find the point top of or left to * endpoint if we have selection 01. * Otherwise, the search will be left and bottom */ // we know that startC.x > endC.x, we need to decide which is top and which is bottom const NormalizedRect start_end = (startC.y < endC.y) ? NormalizedRect(startC.x, startC.y, endC.x, endC.y) : NormalizedRect(startC.x, endC.y, endC.x, startC.y); // Case 1(a) if(!boundingRect.intersects(start_end)) return ret; // case 1(b) /** note that, after swapping of start and end, we know that, start is always left to end. but, we cannot say start is positioned upper than end. **/ else { // if start is left to content rect take it to content rect boundary if(startC.x * scaleX < minX) startC.x = minX/scaleX; if(endC.x * scaleX > maxX) endC.x = maxX/scaleX; // if start is top to end (selection type 01) if(startC.y * scaleY < minY) startC.y = minY/scaleY; if(endC.y * scaleY > maxY) endC.y = maxY/scaleY; // if start is bottom to end (selection type 02) if(startC.y * scaleY > maxY) startC.y = maxY/scaleY; if(endC.y * scaleY < minY) endC.y = minY/scaleY; } TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); TextList::ConstIterator start = it, end = itEnd, tmpIt = it; //, tmpItEnd = itEnd; const MergeSide side = d->m_page ? (MergeSide)d->m_page->totalOrientation() : MergeRight; NormalizedRect tmp; //case 2(a) for ( ; it != itEnd; ++it ) { tmp = (*it)->area; if(tmp.contains(startC.x,startC.y)){ start = it; } if(tmp.contains(endC.x,endC.y)){ end = it; } } //case 2(b) it = tmpIt; if(start == it && end == itEnd) { for ( ; it != itEnd; ++it ) { // is there any text reactangle within the start_end rect tmp = (*it)->area; if(start_end.intersects(tmp)) break; } // we have searched every text entities, but none is within the rectangle created by start and end // so, no selection should be done if(it == itEnd) { return ret; } } it = tmpIt; bool selection_two_start = false; //case 3.a if(start == it) { bool flagV = false; NormalizedRect rect; // selection type 01 if(startC.y <= endC.y) { for ( ; it != itEnd; ++it ) { rect= (*it)->area; rect.isBottom(startC) ? flagV = false: flagV = true; if(flagV && rect.isRight(startC)) { start = it; break; } } } //selection type 02 else { selection_two_start = true; int distance = scaleX + scaleY + 100; int count = 0; for ( ; it != itEnd; ++it ) { rect= (*it)->area; if(rect.isBottomOrLevel(startC) && rect.isRight(startC)) { count++; QRect entRect = rect.geometry(scaleX,scaleY); int xdist, ydist; xdist = entRect.center().x() - startC.x * scaleX; ydist = entRect.center().y() - startC.y * scaleY; //make them positive if(xdist < 0) xdist = -xdist; if(ydist < 0) ydist = -ydist; if( (xdist + ydist) < distance) { distance = xdist+ ydist; start = it; } } } } } //case 3.b if(end == itEnd) { it = tmpIt; itEnd = itEnd-1; bool flagV = false; NormalizedRect rect; if(startC.y <= endC.y) { for ( ; itEnd >= it; itEnd-- ) { rect= (*itEnd)->area; rect.isTop(endC) ? flagV = false: flagV = true; if(flagV && rect.isLeft(endC)) { end = itEnd; break; } } } else { int distance = scaleX + scaleY + 100; for ( ; itEnd >= it; itEnd-- ) { rect= (*itEnd)->area; if(rect.isTopOrLevel(endC) && rect.isLeft(endC)) { QRect entRect = rect.geometry(scaleX,scaleY); int xdist, ydist; xdist = entRect.center().x() - endC.x * scaleX; ydist = entRect.center().y() - endC.y * scaleY; //make them positive if(xdist < 0) xdist = -xdist; if(ydist < 0) ydist = -ydist; if( (xdist + ydist) < distance) { distance = xdist+ ydist; end = itEnd; } } } } } /* if start and end in selection 02 are in the same column, and we start at an empty space we have to remove the selection of last character */ if(selection_two_start) { if(start > end) { start = start - 1; } } // if start is less than end swap them if(start > end) { it = start; start = end; end = it; } // removes the possibility of crash, in case none of 1 to 3 is true if(end == d->m_words.constEnd()) end--; for( ;start <= end ; start++) { ret->appendShape( (*start)->transformedArea( matrix ), side ); } #endif return ret; } RegularAreaRect* TextPage::findText( int searchID, const QString &query, SearchDirection direct, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *area ) { SearchDirection dir=direct; // invalid search request if ( d->m_words.isEmpty() || query.isEmpty() || ( area && area->isNull() ) ) return nullptr; TextList::ConstIterator start; int start_offset = 0; TextList::ConstIterator end; const QMap< int, SearchPoint* >::const_iterator sIt = d->m_searchPoints.constFind( searchID ); if ( sIt == d->m_searchPoints.constEnd() ) { // if no previous run of this search is found, then set it to start // from the beginning (respecting the search direction) if ( dir == NextResult ) dir = FromTop; else if ( dir == PreviousResult ) dir = FromBottom; } bool forward = true; switch ( dir ) { case FromTop: start = d->m_words.constBegin(); start_offset = 0; end = d->m_words.constEnd(); break; case FromBottom: start = d->m_words.constEnd(); start_offset = 0; end = d->m_words.constBegin(); forward = false; break; case NextResult: start = (*sIt)->it_end; start_offset = (*sIt)->offset_end; end = d->m_words.constEnd(); break; case PreviousResult: start = (*sIt)->it_begin; start_offset = (*sIt)->offset_begin; end = d->m_words.constBegin(); forward = false; break; }; RegularAreaRect* ret = nullptr; const TextComparisonFunction cmpFn = caseSensitivity == Qt::CaseSensitive ? CaseSensitiveCmpFn : CaseInsensitiveCmpFn; if ( forward ) { ret = d->findTextInternalForward( searchID, query, cmpFn, start, start_offset, end ); } else { ret = d->findTextInternalBackward( searchID, query, cmpFn, start, start_offset, end ); } return ret; } // hyphenated '-' must be at the end of a word, so hyphenation means // we have a '-' just followed by a '\n' character // check if the string contains a '-' character // if the '-' is the last entry static int stringLengthAdaptedWithHyphen(const QString &str, const TextList::ConstIterator &it, const TextList::ConstIterator &textListEnd) { int len = str.length(); // hyphenated '-' must be at the end of a word, so hyphenation means // we have a '-' just followed by a '\n' character // check if the string contains a '-' character // if the '-' is the last entry if ( str.endsWith( QLatin1Char('-') ) ) { // validity chek of it + 1 if ( ( it + 1 ) != textListEnd ) { // 1. if the next character is '\n' const QString &lookahedStr = (*(it+1))->text(); if (lookahedStr.startsWith(QLatin1Char('\n'))) { len -= 1; } else { // 2. if the next word is in a different line or not const NormalizedRect& hyphenArea = (*it)->area; const NormalizedRect& lookaheadArea = (*(it + 1))->area; // lookahead to check whether both the '-' rect and next character rect overlap if( !doesConsumeY( hyphenArea, lookaheadArea, 70 ) ) { len -= 1; } } } } // else if it is the second last entry - for example in pdf format else if (str.endsWith(QLatin1String("-\n"))) { len -= 2; } return len; } RegularAreaRect* TextPagePrivate::searchPointToArea(const SearchPoint* sp) { PagePrivate *pagePrivate = PagePrivate::get(m_page); const QTransform matrix = pagePrivate ? pagePrivate->rotationMatrix() : QTransform(); RegularAreaRect* ret=new RegularAreaRect; for (TextList::ConstIterator it = sp->it_begin; ; it++) { const TinyTextEntity* curEntity = *it; ret->append( curEntity->transformedArea( matrix ) ); if (it == sp->it_end) { break; } } ret->simplify(); return ret; } RegularAreaRect* TextPagePrivate::findTextInternalForward( int searchID, const QString &_query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end) { // normalize query search all unicode (including glyphs) const QString query = _query.normalized(QString::NormalizationForm_KC); // j is the current position in our query // len is the length of the string in TextEntity // queryLeft is the length of the query we have left int j=0, queryLeft=query.length(); TextList::ConstIterator it = start; int offset = start_offset; TextList::ConstIterator it_begin = TextList::ConstIterator(); int offset_begin = 0; //dummy initial value to suppress compiler warnings while ( it != end ) { const TinyTextEntity* curEntity = *it; const QString& str = curEntity->text(); int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd()); if (offset >= len) { it++; offset = 0; continue; } if ( it_begin == TextList::ConstIterator() ) { it_begin = it; offset_begin = offset; } int min=qMin(queryLeft,len-offset); { #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << str.midRef(offset, min) << ":" << _query.midRef(j, min); #endif // we have equal (or less than) area of the query left as the length of the current // entity if ( !comparer( str.midRef( offset, min ), query.midRef( j, min ) ) ) { // we have not matched // this means we do not have a complete match // we need to get back to query start // and continue the search from this place #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tnot matched"; #endif j = 0; queryLeft=query.length(); it = it_begin; offset = offset_begin+1; it_begin = TextList::ConstIterator(); } else { // we have a match // move the current position in the query // to the position after the length of this string // we matched // subtract the length of the current entity from // the left length of the query #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tmatched"; #endif j += min; queryLeft -= min; if (queryLeft==0) { // save or update the search point for the current searchID QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt == m_searchPoints.end() ) { sIt = m_searchPoints.insert( searchID, new SearchPoint ); } SearchPoint* sp = *sIt; sp->it_begin = it_begin; sp->it_end = it; sp->offset_begin = offset_begin; sp->offset_end = offset + min; return searchPointToArea(sp); } it++; offset = 0; } } } // end of loop - it means that we've ended the textentities const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt != m_searchPoints.end() ) { SearchPoint* sp = *sIt; m_searchPoints.erase( sIt ); delete sp; } return nullptr; } RegularAreaRect* TextPagePrivate::findTextInternalBackward( int searchID, const QString &_query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end) { // normalize query to search all unicode (including glyphs) const QString query = _query.normalized(QString::NormalizationForm_KC); // j is the current position in our query // len is the length of the string in TextEntity // queryLeft is the length of the query we have left int j=query.length(), queryLeft=query.length(); TextList::ConstIterator it = start; int offset = start_offset; TextList::ConstIterator it_begin = TextList::ConstIterator(); int offset_begin = 0; //dummy initial value to suppress compiler warnings while ( true ) { if (offset <= 0) { if ( it == end ) { break; } it--; } const TinyTextEntity* curEntity = *it; const QString& str = curEntity->text(); int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd()); if (offset <= 0) { offset = len; } if ( it_begin == TextList::ConstIterator() ) { it_begin = it; offset_begin = offset; } int min=qMin(queryLeft,offset); { #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << str.midRef(offset-min, min) << " : " << _query.midRef(j-min, min); #endif // we have equal (or less than) area of the query left as the length of the current // entity // Note len is not str.length() so we can't use rightRef here if ( !comparer( str.midRef(offset-min, min ), query.midRef( j - min, min ) ) ) { // we have not matched // this means we do not have a complete match // we need to get back to query start // and continue the search from this place #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tnot matched"; #endif j = query.length(); queryLeft = query.length(); it = it_begin; offset = offset_begin-1; it_begin = TextList::ConstIterator(); } else { // we have a match // move the current position in the query // to the position after the length of this string // we matched // subtract the length of the current entity from // the left length of the query #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tmatched"; #endif j -= min; queryLeft -= min; if ( queryLeft == 0 ) { // save or update the search point for the current searchID QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt == m_searchPoints.end() ) { sIt = m_searchPoints.insert( searchID, new SearchPoint ); } SearchPoint* sp = *sIt; sp->it_begin = it; sp->it_end = it_begin; sp->offset_begin = offset - min; sp->offset_end = offset_begin; return searchPointToArea(sp); } offset = 0; } } } // end of loop - it means that we've ended the textentities const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt != m_searchPoints.end() ) { SearchPoint* sp = *sIt; m_searchPoints.erase( sIt ); delete sp; } return nullptr; } QString TextPage::text(const RegularAreaRect *area) const { return text(area, AnyPixelTextAreaInclusionBehaviour); } QString TextPage::text(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const { if ( area && area->isNull() ) return QString(); TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); QString ret; if ( area ) { for ( ; it != itEnd; ++it ) { if (b == AnyPixelTextAreaInclusionBehaviour) { if ( area->intersects( (*it)->area ) ) { ret += (*it)->text(); } } else { NormalizedPoint center = (*it)->area.center(); if ( area->contains( center.x, center.y ) ) { ret += (*it)->text(); } } } } else { for ( ; it != itEnd; ++it ) ret += (*it)->text(); } return ret; } static bool compareTinyTextEntityX(const WordWithCharacters &first, const WordWithCharacters &second) { QRect firstArea = first.area().roundedGeometry(1000,1000); QRect secondArea = second.area().roundedGeometry(1000,1000); return firstArea.left() < secondArea.left(); } static bool compareTinyTextEntityY(const WordWithCharacters &first, const WordWithCharacters &second) { const QRect firstArea = first.area().roundedGeometry(1000,1000); const QRect secondArea = second.area().roundedGeometry(1000,1000); return firstArea.top() < secondArea.top(); } /** * Sets a new world list. Deleting the contents of the old one */ void TextPagePrivate::setWordList(const TextList &list) { qDeleteAll(m_words); m_words = list; } /** * Remove all the spaces in between texts. It will make all the generators * same, whether they save spaces(like pdf) or not(like djvu). */ static void removeSpace(TextList *words) { TextList::Iterator it = words->begin(); const QString str(QLatin1Char(' ')); while ( it != words->end() ) { if((*it)->text() == str) { it = words->erase(it); } else { ++it; } } } /** * We will read the TinyTextEntity from characters and try to create words from there. * Note: characters might be already characters for some generators, but we will keep * the nomenclature characters for the generator produced data. The resulting * WordsWithCharacters memory has to be managed by the caller, both the * WordWithCharacters::word and WordWithCharacters::characters contents */ static WordsWithCharacters makeWordFromCharacters(const TextList &characters, int pageWidth, int pageHeight) { /** * We will traverse characters and try to create words from the TinyTextEntities in it. * We will search TinyTextEntity blocks and merge them until we get a * space between two consecutive TinyTextEntities. When we get a space * we can take it as a end of word. Then we store the word as a TinyTextEntity * and keep it in newList. * We create a RegionText named regionWord that contains the word and the characters associated with it and * a rectangle area of the element in newList. */ WordsWithCharacters wordsWithCharacters; TextList::ConstIterator it = characters.begin(), itEnd = characters.end(), tmpIt; int newLeft,newRight,newTop,newBottom; int index = 0; for( ; it != itEnd ; it++) { QString textString = (*it)->text(); QString newString; QRect lineArea = (*it)->area.roundedGeometry(pageWidth,pageHeight),elementArea; TextList wordCharacters; tmpIt = it; int space = 0; while (!space) { if (textString.length()) { newString.append(textString); // when textString is the start of the word if (tmpIt == it) { NormalizedRect newRect(lineArea,pageWidth,pageHeight); wordCharacters.append(new TinyTextEntity(textString.normalized (QString::NormalizationForm_KC), newRect)); } else { NormalizedRect newRect(elementArea,pageWidth,pageHeight); wordCharacters.append(new TinyTextEntity(textString.normalized (QString::NormalizationForm_KC), newRect)); } } ++it; /* we must have to put this line before the if condition of it==itEnd otherwise the last character can be missed */ if (it == itEnd) break; elementArea = (*it)->area.roundedGeometry(pageWidth,pageHeight); if (!doesConsumeY(elementArea, lineArea, 60)) { --it; break; } const int text_y1 = elementArea.top() , text_x1 = elementArea.left(), text_y2 = elementArea.y() + elementArea.height(), text_x2 = elementArea.x() + elementArea.width(); const int line_y1 = lineArea.top() ,line_x1 = lineArea.left(), line_y2 = lineArea.y() + lineArea.height(), line_x2 = lineArea.x() + lineArea.width(); space = elementArea.left() - lineArea.right(); if (space != 0) { it--; break; } newLeft = text_x1 < line_x1 ? text_x1 : line_x1; newRight = line_x2 > text_x2 ? line_x2 : text_x2; newTop = text_y1 > line_y1 ? line_y1 : text_y1; newBottom = text_y2 > line_y2 ? text_y2 : line_y2; lineArea.setLeft (newLeft); lineArea.setTop (newTop); lineArea.setWidth( newRight - newLeft ); lineArea.setHeight( newBottom - newTop ); textString = (*it)->text(); } // if newString is not empty, save it if (!newString.isEmpty()) { const NormalizedRect newRect(lineArea, pageWidth, pageHeight); TinyTextEntity *word = new TinyTextEntity(newString.normalized(QString::NormalizationForm_KC), newRect); wordsWithCharacters.append(WordWithCharacters(word, wordCharacters)); index++; } if(it == itEnd) break; } return wordsWithCharacters; } /** * Create Lines from the words and sort them */ QList< QPair > makeAndSortLines(const WordsWithCharacters &wordsTmp, int pageWidth, int pageHeight) { /** * We cannot assume that the generator will give us texts in the right order. * We can only assume that we will get texts in the page and their bounding * rectangle. The texts can be character, word, half-word anything. * So, we need to: ** * 1. Sort rectangles/boxes containing texts by y0(top) * 2. Create textline where there is y overlap between TinyTextEntity 's * 3. Within each line sort the TinyTextEntity 's by x0(left) */ QList< QPair > lines; /* Make a new copy of the TextList in the words, so that the wordsTmp and lines do not contain same pointers for all the TinyTextEntity. */ QList words = wordsTmp; // Step 1 qSort(words.begin(),words.end(),compareTinyTextEntityY); // Step 2 QList::Iterator it = words.begin(), itEnd = words.end(); //for every non-space texts(characters/words) in the textList for( ; it != itEnd ; it++) { const QRect elementArea = (*it).area().roundedGeometry(pageWidth,pageHeight); bool found = false; for( int i = 0 ; i < lines.length() ; i++) { /* the line area which will be expanded line_rects is only necessary to preserve the topmin and bottommax of all the texts in the line, left and right is not necessary at all */ QRect &lineArea = lines[i].second; const int text_y1 = elementArea.top() , text_y2 = elementArea.top() + elementArea.height() , text_x1 = elementArea.left(), text_x2 = elementArea.left() + elementArea.width(); const int line_y1 = lineArea.top() , line_y2 = lineArea.top() + lineArea.height(), line_x1 = lineArea.left(), line_x2 = lineArea.left() + lineArea.width(); /* if the new text and the line has y overlapping parts of more than 70%, the text will be added to this line */ if(doesConsumeY(elementArea,lineArea,70)) { WordsWithCharacters &line = lines[i].first; line.append(*it); const int newLeft = line_x1 < text_x1 ? line_x1 : text_x1; const int newRight = line_x2 > text_x2 ? line_x2 : text_x2; const int newTop = line_y1 < text_y1 ? line_y1 : text_y1; const int newBottom = text_y2 > line_y2 ? text_y2 : line_y2; lineArea = QRect( newLeft,newTop, newRight - newLeft, newBottom - newTop ); found = true; } if(found) break; } /* when we have found a new line create a new TextList containing only one element and append it to the lines */ if(!found) { WordsWithCharacters tmp; tmp.append((*it)); lines.append(QPair(tmp, elementArea)); } } // Step 3 for(int i = 0 ; i < lines.length() ; i++) { WordsWithCharacters &list = lines[i].first; qSort(list.begin(), list.end(), compareTinyTextEntityX); } return lines; } /** * Calculate Statistical information from the lines we made previously */ static void calculateStatisticalInformation(const QList &words, int pageWidth, int pageHeight, int *word_spacing, int *line_spacing, int *col_spacing) { /** * For the region, defined by line_rects and lines * 1. Make line statistical analysis to find the line spacing * 2. Make character statistical analysis to differentiate between * word spacing and column spacing. */ /** * Step 0 */ const QList< QPair > sortedLines = makeAndSortLines(words, pageWidth, pageHeight); /** * Step 1 */ QMap line_space_stat; for(int i = 0 ; i < sortedLines.length(); i++) { const QRect rectUpper = sortedLines.at(i).second; if(i+1 == sortedLines.length()) break; const QRect rectLower = sortedLines.at(i+1).second; int linespace = rectLower.top() - (rectUpper.top() + rectUpper.height()); if(linespace < 0) linespace =-linespace; if(line_space_stat.contains(linespace)) line_space_stat[linespace]++; else line_space_stat[linespace] = 1; } *line_spacing = 0; int weighted_count = 0; QMapIterator iterate_linespace(line_space_stat); while(iterate_linespace.hasNext()) { iterate_linespace.next(); *line_spacing += iterate_linespace.value() * iterate_linespace.key(); weighted_count += iterate_linespace.value(); } if (*line_spacing != 0) *line_spacing = (int) ( (double)*line_spacing / (double) weighted_count + 0.5); /** * Step 2 */ // We would like to use QMap instead of QHash as it will keep the keys sorted QMap hor_space_stat; QMap col_space_stat; QList< QList > space_rects; QList max_hor_space_rects; // Space in every line for(int i = 0 ; i < sortedLines.length() ; i++) { const WordsWithCharacters list = sortedLines.at(i).first; QList line_space_rects; int maxSpace = 0, minSpace = pageWidth; // for every TinyTextEntity element in the line WordsWithCharacters::ConstIterator it = list.begin(), itEnd = list.end(); QRect max_area1,max_area2; QString before_max, after_max; // for every line for( ; it != itEnd ; it++ ) { const QRect area1 = (*it).area().roundedGeometry(pageWidth,pageHeight); if( it+1 == itEnd ) break; const QRect area2 = (*(it+1)).area().roundedGeometry(pageWidth,pageHeight); int space = area2.left() - area1.right(); if(space > maxSpace) { max_area1 = area1; max_area2 = area2; maxSpace = space; before_max = (*it).text(); after_max = (*(it+1)).text(); } if(space < minSpace && space != 0) minSpace = space; //if we found a real space, whose length is not zero and also less than the pageWidth if(space != 0 && space != pageWidth) { // increase the count of the space amount if(hor_space_stat.contains(space)) hor_space_stat[space]++; else hor_space_stat[space] = 1; int left,right,top,bottom; left = area1.right(); right = area2.left(); top = area2.top() < area1.top() ? area2.top() : area1.top(); bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom(); QRect rect(left,top,right-left,bottom-top); line_space_rects.append(rect); } } space_rects.append(line_space_rects); if(hor_space_stat.contains(maxSpace)) { if(hor_space_stat[maxSpace] != 1) hor_space_stat[maxSpace]--; else hor_space_stat.remove(maxSpace); } if(maxSpace != 0) { if (col_space_stat.contains(maxSpace)) col_space_stat[maxSpace]++; else col_space_stat[maxSpace] = 1; //store the max rect of each line const int left = max_area1.right(); const int right = max_area2.left(); const int top = (max_area1.top() > max_area2.top()) ? max_area2.top() : max_area1.top(); const int bottom = (max_area1.bottom() < max_area2.bottom()) ? max_area2.bottom() : max_area1.bottom(); const QRect rect(left,top,right-left,bottom-top); max_hor_space_rects.append(rect); } else max_hor_space_rects.append(QRect(0,0,0,0)); } // All the between word space counts are in hor_space_stat *word_spacing = 0; weighted_count = 0; QMapIterator iterate(hor_space_stat); while (iterate.hasNext()) { iterate.next(); if(iterate.key() > 0) { *word_spacing += iterate.value() * iterate.key(); weighted_count += iterate.value(); } } if(weighted_count) *word_spacing = (int) ((double)*word_spacing / (double)weighted_count + 0.5); *col_spacing = 0; QMapIterator iterate_col(col_space_stat); while (iterate_col.hasNext()) { iterate_col.next(); if(iterate_col.value() > *col_spacing) *col_spacing = iterate_col.value(); } *col_spacing = col_space_stat.key(*col_spacing); // if there is just one line in a region, there is no point in dividing it if(sortedLines.length() == 1) *word_spacing = *col_spacing; } /** * Implements the XY Cut algorithm for textpage segmentation * The resulting RegionTextList will contain RegionText whose WordsWithCharacters::word and * WordsWithCharacters::characters are reused from wordsWithCharacters (i.e. no new nor delete happens in this function) */ static RegionTextList XYCutForBoundingBoxes(const QList &wordsWithCharacters, const NormalizedRect &boundingBox, int pageWidth, int pageHeight) { RegionTextList tree; QRect contentRect(boundingBox.geometry(pageWidth,pageHeight)); const RegionText root(wordsWithCharacters, contentRect); // start the tree with the root, it is our only region at the start tree.push_back(root); int i = 0; // while traversing the tree has not been ended while(i < tree.length()) { const RegionText node = tree.at(i); QRect regionRect = node.area(); /** * 1. calculation of projection profiles */ // allocate the size of proj profiles and initialize with 0 int size_proj_y = node.area().height(); int size_proj_x = node.area().width(); //dynamic memory allocation QVarLengthArray proj_on_xaxis(size_proj_x); QVarLengthArray proj_on_yaxis(size_proj_y); for( int j = 0 ; j < size_proj_y ; ++j ) proj_on_yaxis[j] = 0; for( int j = 0 ; j < size_proj_x ; ++j ) proj_on_xaxis[j] = 0; const QList list = node.text(); // Calculate tcx and tcy locally for each new region int word_spacing, line_spacing, column_spacing; calculateStatisticalInformation(list, pageWidth, pageHeight, &word_spacing, &line_spacing, &column_spacing); const int tcx = word_spacing * 2; const int tcy = line_spacing * 2; int maxX = 0 , maxY = 0; int avgX = 0; int count; // for every text in the region for(int j = 0 ; j < list.length() ; ++j ) { TinyTextEntity *ent = list.at(j).word; const QRect entRect = ent->area.geometry(pageWidth, pageHeight); // calculate vertical projection profile proj_on_xaxis1 for(int k = entRect.left() ; k <= entRect.left() + entRect.width() ; ++k) { if( ( k-regionRect.left() ) < size_proj_x && ( k-regionRect.left() ) >= 0 ) proj_on_xaxis[k - regionRect.left()] += entRect.height(); } // calculate horizontal projection profile in the same way for(int k = entRect.top() ; k <= entRect.top() + entRect.height() ; ++k) { if( ( k-regionRect.top() ) < size_proj_y && ( k-regionRect.top() ) >= 0 ) proj_on_yaxis[k - regionRect.top()] += entRect.width(); } } for( int j = 0 ; j < size_proj_y ; ++j ) { if (proj_on_yaxis[j] > maxY) maxY = proj_on_yaxis[j]; } avgX = count = 0; for( int j = 0 ; j < size_proj_x ; ++j ) { if(proj_on_xaxis[j] > maxX) maxX = proj_on_xaxis[j]; if(proj_on_xaxis[j]) { count++; avgX+= proj_on_xaxis[j]; } } if(count) avgX /= count; /** * 2. Cleanup Boundary White Spaces and removal of noise */ int xbegin = 0, xend = size_proj_x - 1; int ybegin = 0, yend = size_proj_y - 1; while(xbegin < size_proj_x && proj_on_xaxis[xbegin] <= 0) xbegin++; while(xend >= 0 && proj_on_xaxis[xend] <= 0) xend--; while(ybegin < size_proj_y && proj_on_yaxis[ybegin] <= 0) ybegin++; while(yend >= 0 && proj_on_yaxis[yend] <= 0) yend--; //update the regionRect int old_left = regionRect.left(), old_top = regionRect.top(); regionRect.setLeft(old_left + xbegin); regionRect.setRight(old_left + xend); regionRect.setTop(old_top + ybegin); regionRect.setBottom(old_top + yend); int tnx = (int)((double)avgX * 10.0 / 100.0 + 0.5), tny = 0; for( int j = 0 ; j < size_proj_x ; ++j ) proj_on_xaxis[j] -= tnx; for( int j = 0 ; j < size_proj_y ; ++j ) proj_on_yaxis[j] -= tny; /** * 3. Find the Widest gap */ int gap_hor = -1, pos_hor = -1; int begin = -1, end = -1; // find all hor_gaps and find the maximum between them for(int j = 1 ; j < size_proj_y ; ++j) { //transition from white to black if(begin >= 0 && proj_on_yaxis[j-1] <= 0 && proj_on_yaxis[j] > 0) end = j; //transition from black to white if(proj_on_yaxis[j-1] > 0 && proj_on_yaxis[j] <= 0) begin = j; if(begin > 0 && end > 0 && end-begin > gap_hor) { gap_hor = end - begin; pos_hor = (end + begin) / 2; begin = -1; end = -1; } } begin = -1, end = -1; int gap_ver = -1, pos_ver = -1; //find all the ver_gaps and find the maximum between them for(int j = 1 ; j < size_proj_x ; ++j) { //transition from white to black if(begin >= 0 && proj_on_xaxis[j-1] <= 0 && proj_on_xaxis[j] > 0){ end = j; } //transition from black to white if(proj_on_xaxis[j-1] > 0 && proj_on_xaxis[j] <= 0) begin = j; if(begin > 0 && end > 0 && end-begin > gap_ver) { gap_ver = end - begin; pos_ver = (end + begin) / 2; begin = -1; end = -1; } } int cut_pos_x = pos_ver, cut_pos_y = pos_hor; int gap_x = gap_ver, gap_y = gap_hor; /** * 4. Cut the region and make nodes (left,right) or (up,down) */ bool cut_hor = false, cut_ver = false; // For horizontal cut const int topHeight = cut_pos_y - (regionRect.top() - old_top); const QRect topRect(regionRect.left(), regionRect.top(), regionRect.width(), topHeight); const QRect bottomRect(regionRect.left(), regionRect.top() + topHeight, regionRect.width(), regionRect.height() - topHeight ); // For vertical Cut const int leftWidth = cut_pos_x - (regionRect.left() - old_left); const QRect leftRect(regionRect.left(), regionRect.top(), leftWidth, regionRect.height()); const QRect rightRect(regionRect.left() + leftWidth, regionRect.top(), regionRect.width() - leftWidth, regionRect.height()); if(gap_y >= gap_x && gap_y >= tcy) cut_hor = true; else if(gap_y >= gap_x && gap_y <= tcy && gap_x >= tcx) cut_ver = true; else if(gap_x >= gap_y && gap_x >= tcx) cut_ver = true; else if(gap_x >= gap_y && gap_x <= tcx && gap_y >= tcy) cut_hor = true; // no cut possible else { // we can now update the node rectangle with the shrinked rectangle RegionText tmpNode = tree.at(i); tmpNode.setArea(regionRect); tree.replace(i,tmpNode); i++; continue; } WordsWithCharacters list1,list2; // horizontal cut, topRect and bottomRect if(cut_hor) { for( int j = 0 ; j < list.length() ; ++j ) { const WordWithCharacters word = list.at(j); const QRect wordRect = word.area().geometry(pageWidth,pageHeight); if(topRect.intersects(wordRect)) list1.append(word); else list2.append(word); } RegionText node1(list1,topRect); RegionText node2(list2,bottomRect); tree.replace(i,node1); tree.insert(i+1,node2); } //vertical cut, leftRect and rightRect else if(cut_ver) { for( int j = 0 ; j < list.length() ; ++j ) { const WordWithCharacters word = list.at(j); const QRect wordRect = word.area().geometry(pageWidth,pageHeight); if(leftRect.intersects(wordRect)) list1.append(word); else list2.append(word); } RegionText node1(list1,leftRect); RegionText node2(list2,rightRect); tree.replace(i,node1); tree.insert(i+1,node2); } } return tree; } /** * Add spaces in between words in a line. It reuses the pointers passed in tree and might add new ones. You will need to take care of deleting them if needed */ WordsWithCharacters addNecessarySpace(RegionTextList tree, int pageWidth, int pageHeight) { /** * 1. Call makeAndSortLines before adding spaces in between words in a line * 2. Now add spaces between every two words in a line * 3. Finally, extract all the space separated texts from each region and return it */ // Only change the texts under RegionTexts, not the area for(int j = 0 ; j < tree.length() ; j++) { RegionText &tmpRegion = tree[j]; // Step 01 QList< QPair > sortedLines = makeAndSortLines(tmpRegion.text(), pageWidth, pageHeight); // Step 02 for(int i = 0 ; i < sortedLines.length() ; i++) { WordsWithCharacters &list = sortedLines[i].first; for(int k = 0 ; k < list.length() ; k++ ) { const QRect area1 = list.at(k).area().roundedGeometry(pageWidth,pageHeight); if( k+1 >= list.length() ) break; const QRect area2 = list.at(k+1).area().roundedGeometry(pageWidth,pageHeight); const int space = area2.left() - area1.right(); if(space != 0) { // Make a TinyTextEntity of string space and push it between it and it+1 const int left = area1.right(); const int right = area2.left(); const int top = area2.top() < area1.top() ? area2.top() : area1.top(); const int bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom(); const QString spaceStr(QStringLiteral(" ")); const QRect rect(QPoint(left,top),QPoint(right,bottom)); const NormalizedRect entRect(rect,pageWidth,pageHeight); TinyTextEntity *ent1 = new TinyTextEntity(spaceStr, entRect); TinyTextEntity *ent2 = new TinyTextEntity(spaceStr, entRect); WordWithCharacters word(ent1, QList() << ent2); list.insert(k+1, word); // Skip the space k++; } } } WordsWithCharacters tmpList; for(int i = 0 ; i < sortedLines.length() ; i++) { tmpList += sortedLines.at(i).first; } tmpRegion.setText(tmpList); } // Step 03 WordsWithCharacters tmp; for(int i = 0 ; i < tree.length() ; i++) { tmp += tree.at(i).text(); } return tmp; } /** * Correct the textOrder, all layout recognition works here */ void TextPagePrivate::correctTextOrder() { //m_page->m_page->width() and m_page->m_page->height() are in pixels at //100% zoom level, and thus depend on display DPI. We scale pageWidth and //pageHeight to remove the dependence. Otherwise bugs would be more difficult //to reproduce and Okular could fail in extreme cases like a large TV with low DPI. const double scalingFactor = 2000.0 / (m_page->width() + m_page->height()); const int pageWidth = (int) (scalingFactor * m_page->width() ); const int pageHeight = (int) (scalingFactor * m_page->height()); TextList characters = m_words; /** * Remove spaces from the text */ removeSpace(&characters); /** * Construct words from characters */ const QList wordsWithCharacters = makeWordFromCharacters(characters, pageWidth, pageHeight); /** * Make a XY Cut tree for segmentation of the texts */ const RegionTextList tree = XYCutForBoundingBoxes(wordsWithCharacters, m_page->boundingBox(), pageWidth, pageHeight); /** * Add spaces to the word */ const WordsWithCharacters listWithWordsAndSpaces = addNecessarySpace(tree, pageWidth, pageHeight); /** * Break the words into characters */ TextList listOfCharacters; foreach(const WordWithCharacters &word, listWithWordsAndSpaces) { delete word.word; listOfCharacters.append(word.characters); } setWordList(listOfCharacters); } TextEntity::List TextPage::words(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const { if ( area && area->isNull() ) return TextEntity::List(); TextEntity::List ret; if ( area ) { foreach (TinyTextEntity *te, d->m_words) { if (b == AnyPixelTextAreaInclusionBehaviour) { if ( area->intersects( te->area ) ) { ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); } } else { const NormalizedPoint center = te->area.center(); if ( area->contains( center.x, center.y ) ) { ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); } } } } else { foreach (TinyTextEntity *te, d->m_words) { ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); } } return ret; } RegularAreaRect * TextPage::wordAt( const NormalizedPoint &p, QString *word ) const { TextList::ConstIterator itBegin = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); TextList::ConstIterator it = itBegin; TextList::ConstIterator posIt = itEnd; for ( ; it != itEnd; ++it ) { if ( (*it)->area.contains( p.x, p.y ) ) { posIt = it; break; } } QString text; if ( posIt != itEnd ) { if ( (*posIt)->text().simplified().isEmpty() ) { return nullptr; } // Find the first TinyTextEntity of the word while ( posIt != itBegin ) { --posIt; const QString itText = (*posIt)->text(); if ( itText.right(1).at(0).isSpace() ) { if (itText.endsWith(QLatin1String("-\n"))) { // Is an hyphenated word // continue searching the start of the word back continue; } if (itText == QLatin1String("\n") && posIt != itBegin ) { --posIt; if ((*posIt)->text().endsWith(QLatin1String("-"))) { // Is an hyphenated word // continue searching the start of the word back continue; } ++posIt; } ++posIt; break; } } RegularAreaRect *ret = new RegularAreaRect(); for ( ; posIt != itEnd; ++posIt ) { const QString itText = (*posIt)->text(); if ( itText.simplified().isEmpty() ) { break; } ret->appendShape( (*posIt)->area ); text += (*posIt)->text(); if (itText.right(1).at(0).isSpace()) { if (!text.endsWith(QLatin1String("-\n"))) { break; } } } if (word) { *word = text; } return ret; } else { return nullptr; } } diff --git a/core/textpage.h b/core/textpage.h index f338d5a4f..a343f31e3 100644 --- a/core/textpage.h +++ b/core/textpage.h @@ -1,195 +1,195 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymanski * * * * 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_TEXTPAGE_H_ #define _OKULAR_TEXTPAGE_H_ -#include -#include +#include +#include #include "okularcore_export.h" #include "global.h" class QTransform; namespace Okular { class NormalizedPoint; class NormalizedRect; class Page; class PagePrivate; class TextPagePrivate; class TextSelection; class RegularAreaRect; /*! @class TextEntity * @short Abstract textentity of Okular * @par The context * A document can provide different forms of information about textual representation * of its contents. It can include information about positions of every character on the * page, this is the best possibility. * * But also it can provide information only about positions of every word on the page (not the character). * Furthermore it can provide information only about the position of the whole page's text on the page. * * Also some document types have glyphes - sets of characters rendered as one, so in search they should * appear as a text but are only one character when drawn on screen. We need to allow this. */ class OKULARCORE_EXPORT TextEntity { public: typedef QList List; /** * Creates a new text entity with the given @p text and the * given @p area. */ TextEntity( const QString &text, NormalizedRect *area ); /** * Destroys the text entity. */ ~TextEntity(); /** * Returns the text of the text entity. */ QString text() const; /** * Returns the bounding area of the text entity. */ NormalizedRect* area() const; /** * Returns the transformed area of the text entity. */ NormalizedRect transformedArea(const QTransform &matrix) const; private: QString m_text; NormalizedRect* m_area; class Private; const Private *d; Q_DISABLE_COPY( TextEntity ) }; /** * The TextPage class represents the text of a page by * providing @see TextEntity items for every word/character of * the page. */ class OKULARCORE_EXPORT TextPage { /// @cond PRIVATE friend class Page; friend class PagePrivate; /// @endcond public: /** * Defines the behaviour of adding characters to text() result * @since 0.10 (KDE 4.4) */ enum TextAreaInclusionBehaviour { AnyPixelTextAreaInclusionBehaviour, ///< A character is included into text() result if any pixel of his bounding box is in the given area CentralPixelTextAreaInclusionBehaviour ///< A character is included into text() result if the central pixel of his bounding box is in the given area }; /** * Creates a new text page. */ TextPage(); /** * Creates a new text page with the given @p words. */ TextPage( const TextEntity::List &words ); /** * Destroys the text page. */ ~TextPage(); /** * Appends the given @p text with the given @p area as new * @ref TextEntity to the page. */ void append( const QString &text, NormalizedRect *area ); /** * Returns the bounding rect of the text which matches the following criteria * or 0 if the search is not successful. * * @param id An unique id for this search. * @param text The search text. * @param direction The direction of the search (@ref SearchDirection) * @param caseSensitivity If Qt::CaseSensitive, the search is case sensitive; otherwise * the search is case insensitive. * @param lastRect If 0 the search starts at the beginning of the page, otherwise * right/below the coordinates of the given rect. */ RegularAreaRect* findText( int id, const QString &text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect ); /** * Text extraction function. * * Returns: * - a null string if @p rect is a valid pointer to a null area * - the whole page text if @p rect is a null pointer * - the text which is included by rectangular area @p rect otherwise * Uses AnyPixelTextAreaInclusionBehaviour */ QString text( const RegularAreaRect *rect = nullptr ) const; /** * Text extraction function. * * Returns: * - a null string if @p rect is a valid pointer to a null area * - the whole page text if @p rect is a null pointer * - the text which is included by rectangular area @p rect otherwise * @since 0.10 (KDE 4.4) */ QString text( const RegularAreaRect * rect, TextAreaInclusionBehaviour b ) const; /** * Text entity extraction function. Similar to text() but returns * the words including their bounding rectangles. Note that * ownership of the contents of the returned list belongs to the * caller. * @since 0.14 (KDE 4.8) */ TextEntity::List words( const RegularAreaRect * rect, TextAreaInclusionBehaviour b ) const; /** * Returns the area and text of the word at the given point * Note that ownership of the returned area belongs to the caller. * @since 0.15 (KDE 4.9) */ RegularAreaRect * wordAt( const NormalizedPoint &p, QString *word = nullptr ) const; /** * Returns the rectangular area of the given @p selection. */ RegularAreaRect *textArea( TextSelection *selection ) const; private: TextPagePrivate* const d; Q_DISABLE_COPY( TextPage ) }; } #endif diff --git a/core/textpage_p.h b/core/textpage_p.h index 6b68b51b3..758671241 100644 --- a/core/textpage_p.h +++ b/core/textpage_p.h @@ -1,79 +1,79 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * Copyright (C) 2007 by Pino Toscano * * * * 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_TEXTPAGE_P_H_ #define _OKULAR_TEXTPAGE_P_H_ -#include -#include -#include -#include +#include +#include +#include +#include class SearchPoint; class TinyTextEntity; class RegionText; namespace Okular { class PagePrivate; typedef QList< TinyTextEntity* > TextList; /** * Returns whether the two strings match. * Satisfies the condition that if two strings match then their lengths are equal. */ typedef bool ( *TextComparisonFunction )( const QStringRef & from, const QStringRef & to ); /** * A list of RegionText. It keeps a bunch of TextList with their bounding rectangles */ typedef QList RegionTextList; class TextPagePrivate { public: TextPagePrivate(); ~TextPagePrivate(); RegularAreaRect * findTextInternalForward( int searchID, const QString &query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end); RegularAreaRect * findTextInternalBackward( int searchID, const QString &query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end ); /** * Copy a TextList to m_words, the pointers of list are adopted */ void setWordList(const TextList &list); /** * Make necessary modifications in the TextList to make the text order correct, so * that textselection works fine */ void correctTextOrder(); // variables those can be accessed directly from TextPage TextList m_words; QMap< int, SearchPoint* > m_searchPoints; Page *m_page; private: RegularAreaRect * searchPointToArea(const SearchPoint* sp); }; } #endif diff --git a/core/tilesmanager.cpp b/core/tilesmanager.cpp index d20c1b1e0..7f3c7f2ac 100644 --- a/core/tilesmanager.cpp +++ b/core/tilesmanager.cpp @@ -1,727 +1,727 @@ /*************************************************************************** * Copyright (C) 2012 by Mailson Menezes * * 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 "tilesmanager_p.h" #include -#include +#include #include #include #include "tile.h" #define TILES_MAXSIZE 2000000 using namespace Okular; static bool rankedTilesLessThan( TileNode *t1, TileNode *t2 ) { // Order tiles by its dirty state and then by distance from the viewport. if ( t1->dirty == t2->dirty ) return t1->distance < t2->distance; return !t1->dirty; } class TilesManager::Private { public: Private(); bool hasPixmap( const NormalizedRect &rect, const TileNode &tile ) const; void tilesAt( const NormalizedRect &rect, TileNode &tile, QList &result, TileLeaf tileLeaf ); void setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile, bool isPartialPixmap ); /** * Mark @p tile and all its children as dirty */ static void markDirty( TileNode &tile ); /** * Deletes all tiles, recursively */ void deleteTiles( const TileNode &tile ); void markParentDirty( const TileNode &tile ); void rankTiles( TileNode &tile, QList &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber ); /** * Since the tile can be large enough to occupy a significant amount of * space, they may be split in more tiles. This operation is performed * when the tiles of a certain region is requested and they are bigger * than an arbitrary value. Only tiles intersecting the desired region * are split. There's no need to do this for the entire page. */ void split( TileNode &tile, const NormalizedRect &rect ); /** * Checks whether the tile's size is bigger than an arbitrary value and * performs the split operation returning true. * Otherwise it just returns false, without performing any operation. */ bool splitBigTiles( TileNode &tile, const NormalizedRect &rect ); // The page is split in a 4x4 grid of tiles TileNode tiles[16]; int width; int height; int pageNumber; qulonglong totalPixels; Rotation rotation; NormalizedRect visibleRect; NormalizedRect requestRect; int requestWidth; int requestHeight; }; TilesManager::Private::Private() : width( 0 ) , height( 0 ) , pageNumber( 0 ) , totalPixels( 0 ) , rotation( Rotation0 ) , requestRect( NormalizedRect() ) , requestWidth( 0 ) , requestHeight( 0 ) { } TilesManager::TilesManager( int pageNumber, int width, int height, Rotation rotation ) : d( new Private ) { d->pageNumber = pageNumber; d->width = width; d->height = height; d->rotation = rotation; // The page is split in a 4x4 grid of tiles const double dim = 0.25; for ( int i = 0; i < 16; ++i ) { int x = i % 4; int y = i / 4; d->tiles[ i ].rect = NormalizedRect( x*dim, y*dim, x*dim+dim, y*dim+dim ); } } TilesManager::~TilesManager() { for ( int i = 0; i < 16; ++i ) d->deleteTiles( d->tiles[ i ] ); delete d; } void TilesManager::Private::deleteTiles( const TileNode &tile ) { if ( tile.pixmap ) { totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; } if ( tile.nTiles > 0 ) { for ( int i = 0; i < tile.nTiles; ++i ) deleteTiles( tile.tiles[ i ] ); delete [] tile.tiles; } } void TilesManager::setSize( int width, int height ) { if ( width == d->width && height == d->height ) return; d->width = width; d->height = height; markDirty(); } int TilesManager::width() const { return d->width; } int TilesManager::height() const { return d->height; } void TilesManager::setRotation( Rotation rotation ) { if ( rotation == d->rotation ) return; d->rotation = rotation; } Rotation TilesManager::rotation() const { return d->rotation; } void TilesManager::markDirty() { for ( int i = 0; i < 16; ++i ) { TilesManager::Private::markDirty( d->tiles[ i ] ); } } void TilesManager::Private::markDirty( TileNode &tile ) { tile.dirty = true; for ( int i = 0; i < tile.nTiles; ++i ) { markDirty( tile.tiles[ i ] ); } } void TilesManager::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap ) { const NormalizedRect rotatedRect = TilesManager::fromRotatedRect( rect, d->rotation ); if ( !d->requestRect.isNull() ) { if ( !(d->requestRect == rect) ) return; if ( pixmap ) { // Check whether the pixmap has the same absolute size of the expected // request. // If the document is rotated, rotate requestRect back to the original // rotation before comparing to pixmap's size. This is to avoid // conversion issues. The pixmap request was made using an unrotated // rect. QSize pixmapSize = pixmap->size(); int w = width(); int h = height(); if ( d->rotation % 2 ) { qSwap(w, h); pixmapSize.transpose(); } if ( rotatedRect.geometry( w, h ).size() != pixmapSize ) return; } d->requestRect = NormalizedRect(); } for ( int i = 0; i < 16; ++i ) { d->setPixmap( pixmap, rotatedRect, d->tiles[ i ], isPartialPixmap ); } } void TilesManager::Private::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile, bool isPartialPixmap ) { QRect pixmapRect = TilesManager::toRotatedRect( rect, rotation ).geometry( width, height ); // Exclude tiles outside the viewport if ( !tile.rect.intersects( rect ) ) return; // if the tile is not entirely within the viewport (the tile intersects an // edged of the viewport), attempt to set the pixmap in the children tiles if ( !((tile.rect & rect) == tile.rect) ) { // paint children tiles if ( tile.nTiles > 0 ) { for ( int i = 0; i < tile.nTiles; ++i ) setPixmap( pixmap, rect, tile.tiles[ i ], isPartialPixmap ); delete tile.pixmap; tile.pixmap = nullptr; } return; } // the tile lies entirely within the viewport if ( tile.nTiles == 0 ) { tile.dirty = isPartialPixmap; // check whether the tile size is big and split it if necessary if ( !splitBigTiles( tile, rect ) ) { if ( tile.pixmap ) { totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; } tile.rotation = rotation; if ( pixmap ) { const NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); totalPixels += tile.pixmap->width()*tile.pixmap->height(); } else { tile.pixmap = nullptr; } } else { if ( tile.pixmap ) { totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; tile.pixmap = nullptr; } for ( int i = 0; i < tile.nTiles; ++i ) setPixmap( pixmap, rect, tile.tiles[ i ], isPartialPixmap ); } } else { QRect tileRect = tile.rect.geometry( width, height ); // sets the pixmap of the children tiles. if the tile's size is too // small, discards the children tiles and use the current one if ( tileRect.width()*tileRect.height() >= TILES_MAXSIZE ) { tile.dirty = isPartialPixmap; if ( tile.pixmap ) { totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; tile.pixmap = nullptr; } for ( int i = 0; i < tile.nTiles; ++i ) setPixmap( pixmap, rect, tile.tiles[ i ], isPartialPixmap ); } else { // remove children tiles for ( int i = 0; i < tile.nTiles; ++i ) { deleteTiles( tile.tiles[ i ] ); tile.tiles[ i ].pixmap = nullptr; } delete [] tile.tiles; tile.tiles = nullptr; tile.nTiles = 0; // paint tile if ( tile.pixmap ) { totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; } tile.rotation = rotation; if ( pixmap ) { const NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); totalPixels += tile.pixmap->width()*tile.pixmap->height(); } else { tile.pixmap = nullptr; } tile.dirty = isPartialPixmap; } } } bool TilesManager::hasPixmap( const NormalizedRect &rect ) { NormalizedRect rotatedRect = fromRotatedRect( rect, d->rotation ); for ( int i = 0; i < 16; ++i ) { if ( !d->hasPixmap( rotatedRect, d->tiles[ i ] ) ) return false; } return true; } bool TilesManager::Private::hasPixmap( const NormalizedRect &rect, const TileNode &tile ) const { const NormalizedRect rectIntersection = tile.rect & rect; if ( rectIntersection.width() <= 0 || rectIntersection.height() <= 0 ) return true; if ( tile.nTiles == 0 ) return tile.isValid(); // all children tiles are clean. doesn't need to go deeper if ( !tile.dirty ) return true; for ( int i = 0; i < tile.nTiles; ++i ) { if ( !hasPixmap( rect, tile.tiles[ i ] ) ) return false; } return true; } QList TilesManager::tilesAt( const NormalizedRect &rect, TileLeaf tileLeaf ) { QList result; NormalizedRect rotatedRect = fromRotatedRect( rect, d->rotation ); for ( int i = 0; i < 16; ++i ) { d->tilesAt( rotatedRect, d->tiles[ i ], result, tileLeaf ); } return result; } void TilesManager::Private::tilesAt( const NormalizedRect &rect, TileNode &tile, QList &result, TileLeaf tileLeaf ) { if ( !tile.rect.intersects( rect ) ) return; // split big tiles before the requests are made, otherwise we would end up // requesting huge areas unnecessarily splitBigTiles( tile, rect ); if ( ( tileLeaf == TerminalTile && tile.nTiles == 0 ) || ( tileLeaf == PixmapTile && tile.pixmap ) ) { NormalizedRect rotatedRect; if ( rotation != Rotation0 ) rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); else rotatedRect = tile.rect; if ( tile.pixmap && tileLeaf == PixmapTile && tile.rotation != rotation ) { // Lazy tiles rotation int angleToRotate = (rotation - tile.rotation)*90; int xOffset = 0, yOffset = 0; int w = 0, h = 0; switch( angleToRotate ) { case 0: xOffset = 0; yOffset = 0; w = tile.pixmap->width(); h = tile.pixmap->height(); break; case 90: case -270: xOffset = 0; yOffset = -tile.pixmap->height(); w = tile.pixmap->height(); h = tile.pixmap->width(); break; case 180: case -180: xOffset = -tile.pixmap->width(); yOffset = -tile.pixmap->height(); w = tile.pixmap->width(); h = tile.pixmap->height(); break; case 270: case -90: xOffset = -tile.pixmap->width(); yOffset = 0; w = tile.pixmap->height(); h = tile.pixmap->width(); break; } QPixmap *rotatedPixmap = new QPixmap( w, h ); QPainter p( rotatedPixmap ); p.rotate( angleToRotate ); p.translate( xOffset, yOffset ); p.drawPixmap( 0, 0, *tile.pixmap ); p.end(); delete tile.pixmap; tile.pixmap = rotatedPixmap; tile.rotation = rotation; } result.append( Tile( rotatedRect, tile.pixmap, tile.isValid() ) ); } else { for ( int i = 0; i < tile.nTiles; ++i ) tilesAt( rect, tile.tiles[ i ], result, tileLeaf ); } } qulonglong TilesManager::totalMemory() const { return 4*d->totalPixels; } void TilesManager::cleanupPixmapMemory( qulonglong numberOfBytes, const NormalizedRect &visibleRect, int visiblePageNumber ) { QList rankedTiles; for ( int i = 0; i < 16; ++i ) { d->rankTiles( d->tiles[ i ], rankedTiles, visibleRect, visiblePageNumber ); } qSort( rankedTiles.begin(), rankedTiles.end(), rankedTilesLessThan ); while ( numberOfBytes > 0 && !rankedTiles.isEmpty() ) { TileNode *tile = rankedTiles.takeLast(); if ( !tile->pixmap ) continue; // do not evict visible pixmaps if ( tile->rect.intersects( visibleRect ) ) continue; qulonglong pixels = tile->pixmap->width()*tile->pixmap->height(); d->totalPixels -= pixels; if ( numberOfBytes < 4*pixels ) numberOfBytes = 0; else numberOfBytes -= 4*pixels; delete tile->pixmap; tile->pixmap = nullptr; d->markParentDirty( *tile ); } } void TilesManager::Private::markParentDirty( const TileNode &tile ) { if ( !tile.parent ) return; if ( !tile.parent->dirty ) { tile.parent->dirty = true; markParentDirty( *tile.parent ); } } void TilesManager::Private::rankTiles( TileNode &tile, QList &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber ) { // If the page is visible, visibleRect is not null. // Otherwise we use the number of one of the visible pages to calculate the // distance. // Note that the current page may be visible and yet its pageNumber is // different from visiblePageNumber. Since we only use this value on hidden // pages, any visible page number will fit. if ( visibleRect.isNull() && visiblePageNumber < 0 ) return; if ( tile.pixmap ) { // Update distance if ( !visibleRect.isNull() ) { NormalizedPoint viewportCenter = visibleRect.center(); NormalizedPoint tileCenter = tile.rect.center(); // Manhattan distance. It's a good and fast approximation. tile.distance = qAbs(viewportCenter.x - tileCenter.x) + qAbs(viewportCenter.y - tileCenter.y); } else { // For non visible pages only the vertical distance is used if ( pageNumber < visiblePageNumber ) tile.distance = 1 - tile.rect.bottom; else tile.distance = tile.rect.top; } rankedTiles.append( &tile ); } else { for ( int i = 0; i < tile.nTiles; ++i ) { rankTiles( tile.tiles[ i ], rankedTiles, visibleRect, visiblePageNumber ); } } } bool TilesManager::isRequesting( const NormalizedRect &rect, int pageWidth, int pageHeight ) const { return rect == d->requestRect && pageWidth == d->requestWidth && pageHeight == d->requestHeight; } void TilesManager::setRequest( const NormalizedRect &rect, int pageWidth, int pageHeight ) { d->requestRect = rect; d->requestWidth = pageWidth; d->requestHeight = pageHeight; } bool TilesManager::Private::splitBigTiles( TileNode &tile, const NormalizedRect &rect ) { QRect tileRect = tile.rect.geometry( width, height ); if ( tileRect.width()*tileRect.height() < TILES_MAXSIZE ) return false; split( tile, rect ); return true; } void TilesManager::Private::split( TileNode &tile, const NormalizedRect &rect ) { if ( tile.nTiles != 0 ) return; if ( rect.isNull() || !tile.rect.intersects( rect ) ) return; tile.nTiles = 4; tile.tiles = new TileNode[4]; double hCenter = (tile.rect.left + tile.rect.right)/2; double vCenter = (tile.rect.top + tile.rect.bottom)/2; tile.tiles[0].rect = NormalizedRect( tile.rect.left, tile.rect.top, hCenter, vCenter ); tile.tiles[1].rect = NormalizedRect( hCenter, tile.rect.top, tile.rect.right, vCenter ); tile.tiles[2].rect = NormalizedRect( tile.rect.left, vCenter, hCenter, tile.rect.bottom ); tile.tiles[3].rect = NormalizedRect( hCenter, vCenter, tile.rect.right, tile.rect.bottom ); for ( int i = 0; i < tile.nTiles; ++i ) { tile.tiles[ i ].parent = &tile; splitBigTiles( tile.tiles[ i ], rect ); } } NormalizedRect TilesManager::fromRotatedRect( const NormalizedRect &rect, Rotation rotation ) { if ( rotation == Rotation0 ) return rect; NormalizedRect newRect; switch ( rotation ) { case Rotation90: newRect = NormalizedRect( rect.top, 1 - rect.right, rect.bottom, 1 - rect.left ); break; case Rotation180: newRect = NormalizedRect( 1 - rect.right, 1 - rect.bottom, 1 - rect.left, 1 - rect.top ); break; case Rotation270: newRect = NormalizedRect( 1 - rect.bottom, rect.left, 1 - rect.top, rect.right ); break; default: newRect = rect; break; } return newRect; } NormalizedRect TilesManager::toRotatedRect( const NormalizedRect &rect, Rotation rotation ) { if ( rotation == Rotation0 ) return rect; NormalizedRect newRect; switch ( rotation ) { case Rotation90: newRect = NormalizedRect( 1 - rect.bottom, rect.left, 1 - rect.top, rect.right ); break; case Rotation180: newRect = NormalizedRect( 1 - rect.right, 1 - rect.bottom, 1 - rect.left, 1 - rect.top ); break; case Rotation270: newRect = NormalizedRect( rect.top, 1 - rect.right, rect.bottom, 1 - rect.left ); break; default: newRect = rect; break; } return newRect; } TileNode::TileNode() : pixmap( nullptr ) , rotation( Rotation0 ) , dirty ( true ) , distance( -1 ) , tiles( nullptr ) , nTiles( 0 ) , parent( nullptr ) { } bool TileNode::isValid() const { return pixmap && !dirty; } class Tile::Private { public: Private(); NormalizedRect rect; QPixmap *pixmap; bool isValid; }; Tile::Private::Private() : pixmap( nullptr ) , isValid( false ) { } Tile::Tile( const NormalizedRect &rect, QPixmap *pixmap, bool isValid ) : d( new Tile::Private ) { d->rect = rect; d->pixmap = pixmap; d->isValid = isValid; } Tile::Tile( const Tile &t ) : d( new Tile::Private ) { d->rect = t.d->rect; d->pixmap = t.d->pixmap; d->isValid = t.d->isValid; } Tile& Tile::operator=( const Tile &other ) { if ( this == &other ) return *this; d->rect = other.d->rect; d->pixmap = other.d->pixmap; d->isValid = other.d->isValid; return *this; } Tile::~Tile() { delete d; } NormalizedRect Tile::rect() const { return d->rect; } QPixmap * Tile::pixmap() const { return d->pixmap; } bool Tile::isValid() const { return d->isValid; } diff --git a/core/utils.cpp b/core/utils.cpp index 0789132ac..8733982d0 100644 --- a/core/utils.cpp +++ b/core/utils.cpp @@ -1,172 +1,172 @@ /*************************************************************************** * Copyright (C) 2006 by Luigi Toscano * * Copyright (C) 2008 by Pino Toscano * * * * 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 "utils.h" #include "utils_p.h" #include "debug_p.h" #include "settings_core.h" -#include +#include #include #include #include #include #include #include using namespace Okular; QRect Utils::rotateRect( const QRect & source, int width, int height, int orientation ) { QRect ret; // adapt the coordinates of the boxes to the rotation switch ( orientation ) { case 1: ret = QRect( width - source.y() - source.height(), source.x(), source.height(), source.width() ); break; case 2: ret = QRect( width - source.x() - source.width(), height - source.y() - source.height(), source.width(), source.height() ); break; case 3: ret = QRect( source.y(), height - source.x() - source.width(), source.height(), source.width() ); break; case 0: // no modifications default: // other cases ret = source; } return ret; } QSizeF Utils::realDpi(QWidget* widgetOnScreen) { const QScreen* screen = widgetOnScreen && widgetOnScreen->window() && widgetOnScreen->window()->windowHandle() ? widgetOnScreen->window()->windowHandle()->screen() : qGuiApp->primaryScreen(); if (screen) { const QSizeF res(screen->physicalDotsPerInchX(), screen->physicalDotsPerInchY()); if (res.width() > 0 && res.height() > 0) { if (qAbs(res.width() - res.height()) / qMin(res.height(), res.width()) < 0.05) { return res; } else { qCDebug(OkularCoreDebug) << "QScreen calculation returned a non square dpi." << res << ". Falling back"; } } } return QSizeF(72, 72); } inline static bool isPaperColor( QRgb argb, QRgb paperColor ) { return ( argb & 0xFFFFFF ) == ( paperColor & 0xFFFFFF); // ignore alpha } NormalizedRect Utils::imageBoundingBox( const QImage * image ) { if ( !image ) return NormalizedRect(); const int width = image->width(); const int height = image->height(); const QRgb paperColor = SettingsCore::paperColor().rgb(); int left, top, bottom, right, x, y; #ifdef BBOX_DEBUG QTime time; time.start(); #endif // Scan pixels for top non-white for ( top = 0; top < height; ++top ) for ( x = 0; x < width; ++x ) if ( !isPaperColor( image->pixel( x, top ), paperColor ) ) goto got_top; return NormalizedRect( 0, 0, 0, 0 ); // the image is blank got_top: left = right = x; // Scan pixels for bottom non-white for ( bottom = height-1; bottom >= top; --bottom ) for ( x = width-1; x >= 0; --x ) if ( !isPaperColor( image->pixel( x, bottom ), paperColor ) ) goto got_bottom; Q_ASSERT( 0 ); // image changed?! got_bottom: if ( x < left ) left = x; if ( x > right ) right = x; // Scan for leftmost and rightmost (we already found some bounds on these): for ( y = top; y <= bottom && ( left > 0 || right < width-1 ); ++y ) { for ( x = 0; x < left; ++x ) if ( !isPaperColor( image->pixel( x, y ), paperColor ) ) left = x; for ( x = width-1; x > right+1; --x ) if ( !isPaperColor( image->pixel( x, y ), paperColor ) ) right = x; } NormalizedRect bbox( QRect( left, top, ( right - left + 1), ( bottom - top + 1 ) ), image->width(), image->height() ); #ifdef BBOX_DEBUG qCDebug(OkularCoreDebug) << "Computed bounding box" << bbox << "in" << time.elapsed() << "ms"; #endif return bbox; } void Okular::copyQIODevice( QIODevice *from, QIODevice *to ) { QByteArray buffer( 65536, '\0' ); qint64 read = 0; qint64 written = 0; while ( ( read = from->read( buffer.data(), buffer.size() ) ) > 0 ) { written = to->write( buffer.constData(), read ); if ( read != written ) break; } } QTransform Okular::buildRotationMatrix(Rotation rotation) { QTransform matrix; matrix.rotate( (int)rotation * 90 ); switch ( rotation ) { case Rotation90: matrix.translate( 0, -1 ); break; case Rotation180: matrix.translate( -1, -1 ); break; case Rotation270: matrix.translate( -1, 0 ); break; default: ; } return matrix; } /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/view_p.h b/core/view_p.h index 17a8b6999..53602803b 100644 --- a/core/view_p.h +++ b/core/view_p.h @@ -1,33 +1,33 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * * * 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_VIEW_P_H #define OKULAR_VIEW_P_H -#include -#include +#include +#include namespace Okular { class DocumentPrivate; class View; class ViewPrivate { public: ViewPrivate(); virtual ~ViewPrivate(); QString name; DocumentPrivate *document; }; } #endif diff --git a/generators/chm/generator_chm.h b/generators/chm/generator_chm.h index 13fa7108f..728feeee4 100644 --- a/generators/chm/generator_chm.h +++ b/generators/chm/generator_chm.h @@ -1,71 +1,71 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymański * * Copyright (C) 2008 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_CHMGENERATOR_H_ -#define _OKULAR_CHMGENERATOR_H_ +#ifndef _OKULAR_GENERATOR_CHM_H_ +#define _OKULAR_GENERATOR_CHM_H_ #include #include #include "lib/ebook_chm.h" #include class KHTMLPart; namespace Okular { class TextPage; } namespace DOM { class Node; } class CHMGenerator : public Okular::Generator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: CHMGenerator( QObject *parent, const QVariantList &args ); ~CHMGenerator(); bool loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector ) override; Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override; const Okular::DocumentSynopsis * generateDocumentSynopsis() override; bool canGeneratePixmap() const override; void generatePixmap( Okular::PixmapRequest * request ) override; QVariant metaData( const QString & key, const QVariant & option ) const override; public Q_SLOTS: void slotCompleted(); protected: bool doCloseDocument() override; Okular::TextPage* textPage( Okular::TextRequest *request ) override; private: void additionalRequestData(); void recursiveExploreNodes( DOM::Node node, Okular::TextPage *tp ); void preparePageForSyncOperation( const QString &url ); QMap m_urlPage; QVector m_pageUrl; Okular::DocumentSynopsis m_docSyn; EBook* m_file; KHTMLPart *m_syncGen; QString m_fileName; QString m_chmUrl; Okular::PixmapRequest* m_request; QBitArray m_textpageAddedList; QBitArray m_rectsGenerated; }; #endif diff --git a/generators/chm/lib/ebook_epub.cpp b/generators/chm/lib/ebook_epub.cpp index 83a777339..f3d14402f 100644 --- a/generators/chm/lib/ebook_epub.cpp +++ b/generators/chm/lib/ebook_epub.cpp @@ -1,380 +1,380 @@ /* * Kchmviewer - a CHM and EPUB file viewer with broad language support * Copyright (C) 2004-2014 George Yunaev, gyunaev@ulduzsoft.com * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #if defined (WIN32) #include // dup #else #include #endif #include -#include +#include #include "ebook_epub.h" #include "helperxmlhandler_epubcontainer.h" #include "helperxmlhandler_epubcontent.h" #include "helperxmlhandler_epubtoc.h" static const char * URL_SCHEME_EPUB = "epub"; EBook_EPUB::EBook_EPUB() : EBook() { m_zipFile = 0; } EBook_EPUB::~EBook_EPUB() { close(); } bool EBook_EPUB::load(const QString &archiveName) { close(); // We use QFile and zip_fdopen instead of zip_open because latter does not support Unicode file names m_epubFile.setFileName( archiveName ); if ( !m_epubFile.open( QIODevice::ReadOnly ) ) { qWarning("Could not open file %s: %s", qPrintable(archiveName), qPrintable( m_epubFile.errorString())); return false; } // Open the ZIP archive: http://www.nih.at/libzip/zip_fdopen.html // Note that zip_fdopen takes control over the passed descriptor, // so we need to pass a duplicate of it for this to work correctly int fdcopy = dup( m_epubFile.handle() ); if ( fdcopy < 0 ) { qWarning("Could not duplicate descriptor" ); return false; } int errcode; m_zipFile = zip_fdopen( fdcopy, 0, &errcode ); if ( !m_zipFile ) { qWarning("Could not open file %s: error %d", qPrintable(archiveName), errcode); return false; } // Parse the book descriptor file if ( !parseBookinfo() ) return false; return true; } void EBook_EPUB::close() { if ( m_zipFile ) { zip_close( m_zipFile ); m_zipFile = 0; } //if ( m_epubFile.isOpen() ) // m_epubFile.close(); } bool EBook_EPUB::getFileContentAsString(QString &str, const QUrl &url) const { return getFileAsString( str, urlToPath( url ) ); } bool EBook_EPUB::getFileContentAsBinary(QByteArray &data, const QUrl &url) const { return getFileAsBinary( data, urlToPath( url ) ); } bool EBook_EPUB::enumerateFiles(QList &files) { files = m_ebookManifest; return true; } QString EBook_EPUB::title() const { return m_title; } QUrl EBook_EPUB::homeUrl() const { return m_tocEntries[0].url; } bool EBook_EPUB::hasFeature(EBook::Feature code) const { switch ( code ) { case FEATURE_TOC: return true; case FEATURE_INDEX: return false; case FEATURE_ENCODING: return false; } return false; } bool EBook_EPUB::getTableOfContents( QList &toc ) const { toc = m_tocEntries; return true; } bool EBook_EPUB::getIndex(QList &) const { return false; } QString EBook_EPUB::getTopicByUrl(const QUrl& url) { if ( m_urlTitleMap.contains( url ) ) return m_urlTitleMap[ url ]; return ""; } QString EBook_EPUB::currentEncoding() const { return "UTF-8"; } bool EBook_EPUB::setCurrentEncoding(const char *) { abort(); } bool EBook_EPUB::isSupportedUrl(const QUrl &url) { return url.scheme() == URL_SCHEME_EPUB; } bool EBook_EPUB::parseXML(const QString &uri, QXmlDefaultHandler * parser) { QByteArray container; if ( !getFileAsBinary( container, uri ) ) { qDebug("Failed to retrieve XML file %s", qPrintable( uri ) ); return false; } // Use it as XML source QXmlInputSource source; source.setData( container ); // Init the reader QXmlSimpleReader reader; reader.setContentHandler( parser ); reader.setErrorHandler( parser ); return reader.parse( source ); } bool EBook_EPUB::parseBookinfo() { // Parse the container.xml to find the content descriptor HelperXmlHandler_EpubContainer container_parser; if ( !parseXML( "META-INF/container.xml", &container_parser ) || container_parser.contentPath.isEmpty() ) return false; // Parse the content.opf HelperXmlHandler_EpubContent content_parser; if ( !parseXML( container_parser.contentPath, &content_parser ) ) return false; // At least title and the TOC must be present if ( !content_parser.metadata.contains("title") || content_parser.tocname.isEmpty() ) return false; // All the files, including TOC, are relative to the container_parser.contentPath m_documentRoot.clear(); int sep = container_parser.contentPath.lastIndexOf( '/' ); if ( sep != -1 ) m_documentRoot = container_parser.contentPath.left( sep + 1 ); // Keep the trailing slash // Parse the TOC HelperXmlHandler_EpubTOC toc_parser( this ); if ( !parseXML( content_parser.tocname, &toc_parser ) ) return false; // Get the data m_title = content_parser.metadata[ "title" ]; // Move the manifest entries into the list Q_FOREACH( QString f, content_parser.manifest.values() ) m_ebookManifest.push_back( pathToUrl( f ) ); // Copy the manifest information and fill up the other maps if we have it if ( !toc_parser.entries.isEmpty() ) { Q_FOREACH( EBookTocEntry e, toc_parser.entries ) { // Add into url-title map m_urlTitleMap[ e.url ] = e.name; m_tocEntries.push_back( e ); } } else { // Copy them from spline Q_FOREACH( QString u, content_parser.spine ) { EBookTocEntry e; QString url = u; if ( content_parser.manifest.contains( u ) ) url = content_parser.manifest[ u ]; e.name = url; e.url= pathToUrl( url ); e.iconid = EBookTocEntry::IMAGE_NONE; e.indent = 0; // Add into url-title map m_urlTitleMap[ pathToUrl( url ) ] = url; m_tocEntries.push_back( e ); } } // EPub with an empty TOC is not valid if ( m_tocEntries.isEmpty() ) return false; return true; } QUrl EBook_EPUB::pathToUrl(const QString &link) const { QUrl url; url.setScheme( URL_SCHEME_EPUB ); url.setHost( URL_SCHEME_EPUB ); // Does the link contain the fragment as well? int off = link.indexOf( '#' ); QString path; if ( off != -1 ) { path = link.left( off ); url.setFragment( link.mid( off + 1 ) ); } else path = link; if ( !path.startsWith( '/' ) ) path.prepend( '/' ); url.setPath( QUrl::fromPercentEncoding( path.toUtf8() ) ); return url; } QString EBook_EPUB::urlToPath(const QUrl &link) const { if ( link.scheme() == URL_SCHEME_EPUB ) return link.path(); return ""; } bool EBook_EPUB::getFileAsString(QString &str, const QString &path) const { QByteArray data; if ( !getFileAsBinary( data, path ) ) return false; // I have never seen yet an UTF16 epub if ( data.startsWith("" ); int utf16 = data.indexOf("UTF-16"); if ( utf16 > 0 && utf16 < endxmltag ) { QMessageBox::critical( 0, ("Unsupported encoding"), ("The encoding of this ebook is not supported yet. Please send it to gyunaev@ulduzsoft.com for support to be added") ); return false; } } str = QString::fromUtf8( data ); return true; } bool EBook_EPUB::getFileAsBinary(QByteArray &data, const QString &path) const { // Retrieve the file size struct zip_stat fileinfo; QString completeUrl; if ( !path.isEmpty() && path[0] == '/' ) completeUrl = m_documentRoot + path.mid( 1 ); else completeUrl = m_documentRoot + path; //qDebug("URL requested: %s (%s)", qPrintable(path), qPrintable(completeUrl)); // http://www.nih.at/libzip/zip_stat.html if ( zip_stat( m_zipFile, completeUrl.toUtf8().constData(), 0, &fileinfo) != 0 ) { qDebug("File %s is not found in the archive", qPrintable(completeUrl)); return false; } // Make sure the size field is valid if ( (fileinfo.valid & ZIP_STAT_SIZE) == 0 || (fileinfo.valid & ZIP_STAT_INDEX) == 0 ) return false; // Open the file struct zip_file * file = zip_fopen_index( m_zipFile, fileinfo.index, 0 ); if ( !file ) return false; // Allocate the memory and read the file data.resize( fileinfo.size ); // Could it return a positive number but not fileinfo.size??? int ret = zip_fread( file, data.data(), fileinfo.size ); if ( ret != (int) fileinfo.size ) { zip_fclose( file ); return false; } zip_fclose( file ); return true; } diff --git a/generators/comicbook/debug_comicbook.h b/generators/comicbook/debug_comicbook.h index 6abd4b3d9..69005c780 100644 --- a/generators/comicbook/debug_comicbook.h +++ b/generators/comicbook/debug_comicbook.h @@ -1,17 +1,17 @@ /*************************************************************************** * Copyright (C) 2014 by Frederik Gladhorn * * * * 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_COMICBOOK_DEBUG_P_H -#define OKULAR_COMICBOOK_DEBUG_P_H +#ifndef OKULAR_DEBUG_COMICBOOK_H +#define OKULAR_DEBUG_COMICBOOK_H -#include +#include Q_DECLARE_LOGGING_CATEGORY(OkularComicbookDebug) #endif diff --git a/generators/comicbook/directory.cpp b/generators/comicbook/directory.cpp index 6ad1bab6c..cc705c986 100644 --- a/generators/comicbook/directory.cpp +++ b/generators/comicbook/directory.cpp @@ -1,67 +1,67 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * Copyright (C) 2011 by David Palacio * * * * 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 "directory.h" -#include -#include -#include -#include +#include +#include +#include +#include #include Directory::Directory() { } Directory::~Directory() { } bool Directory::open( const QString &dirName ) { mDir = dirName; QFileInfo dirTest( dirName ); return dirTest.isDir() && dirTest.isReadable(); } QStringList Directory::recurseDir( const QString &dirPath, int curDepth ) const { QDir dir( dirPath ); dir.setFilter( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot ); QStringList fileList; QDirIterator it( dir ); QFileInfo info; while( it.hasNext() ) { it.next(); info = it.fileInfo(); if ( info.isDir() && curDepth < staticMaxDepth ) { fileList.append( recurseDir( info.filePath(), curDepth + 1 ) ); } else if ( info.isFile() ) { fileList.append( info.filePath() ); } } return fileList; } QStringList Directory::list() const { return recurseDir( mDir, 0 ); } QIODevice* Directory::createDevice( const QString &path ) const { std::unique_ptr file( new QFile( path ) ); if ( !file->open( QIODevice::ReadOnly ) ) return nullptr; return file.release(); } diff --git a/generators/comicbook/directory.h b/generators/comicbook/directory.h index 01dd473de..c0bd20b39 100644 --- a/generators/comicbook/directory.h +++ b/generators/comicbook/directory.h @@ -1,57 +1,57 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * Copyright (C) 2011 by David Palacio * * * * 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 DIRECTORY_H #define DIRECTORY_H -#include +#include class QIODevice; class Directory { public: /** * Creates a new directory object. */ Directory(); /** * Destroys the directory object. */ ~Directory(); /** * Opens given directory. */ bool open( const QString &fileName ); /** * Returns the list of files from the directory. */ QStringList list() const; /** * Returns a new device for reading the file with the given path. */ QIODevice* createDevice( const QString &path ) const; private: /** * Iterates over a directory and returns a file list. */ QStringList recurseDir( const QString &dir, int curDepth ) const; static const int staticMaxDepth = 1; QString mDir; }; #endif diff --git a/generators/comicbook/document.cpp b/generators/comicbook/document.cpp index 8ba2d89c9..c59874427 100644 --- a/generators/comicbook/document.cpp +++ b/generators/comicbook/document.cpp @@ -1,232 +1,232 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "document.h" -#include -#include -#include +#include +#include +#include #include #include #include #include #include #include #include #include "debug_comicbook.h" #include "directory.h" #include "qnatsort.h" #include "unrar.h" using namespace ComicBook; static void imagesInArchive( const QString &prefix, const KArchiveDirectory* dir, QStringList *entries ) { Q_FOREACH ( const QString &entry, dir->entries() ) { const KArchiveEntry *e = dir->entry( entry ); if ( e->isDirectory() ) { imagesInArchive( prefix + entry + QLatin1Char('/'), static_cast( e ), entries ); } else if ( e->isFile() ) { entries->append( prefix + entry ); } } } Document::Document() : mDirectory( nullptr ), mUnrar( nullptr ), mArchive( nullptr ) { } Document::~Document() { } bool Document::open( const QString &fileName ) { close(); QMimeDatabase db; const QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent); /** * We have a zip archive */ if ( mime.inherits(QStringLiteral("application/x-cbz") ) || mime.inherits( QStringLiteral("application/zip") ) ) { mArchive = new KZip( fileName ); if ( !processArchive() ) { return false; } /** * We have a TAR archive */ } else if ( mime.inherits( QStringLiteral("application/x-cbt") ) || mime.inherits( QStringLiteral("application/x-gzip") ) || mime.inherits( QStringLiteral("application/x-tar") ) || mime.inherits( QStringLiteral("application/x-bzip") ) ) { mArchive = new KTar( fileName ); if ( !processArchive() ) { return false; } } else if ( mime.inherits( QStringLiteral("application/x-cbr") ) || mime.inherits( QStringLiteral("application/x-rar") ) || mime.inherits( QStringLiteral("application/vnd.rar") ) ) { if ( !Unrar::isAvailable() ) { mLastErrorString = i18n( "Cannot open document, unrar was not found." ); return false; } if ( !Unrar::isSuitableVersionAvailable() ) { mLastErrorString = i18n( "The version of unrar on your system is not suitable for opening comicbooks." ); return false; } /** * We have a rar archive */ mUnrar = new Unrar(); if ( !mUnrar->open( fileName ) ) { delete mUnrar; mUnrar = nullptr; return false; } mEntries = mUnrar->list(); } else if ( mime.inherits( QStringLiteral("inode/directory") ) ) { mDirectory = new Directory(); if ( !mDirectory->open( fileName ) ) { delete mDirectory; mDirectory = nullptr; return false; } mEntries = mDirectory->list(); } else { mLastErrorString = i18n( "Unknown ComicBook format." ); return false; } return true; } void Document::close() { mLastErrorString.clear(); if ( !( mArchive || mUnrar || mDirectory ) ) return; delete mArchive; mArchive = nullptr; delete mDirectory; mDirectory = nullptr; delete mUnrar; mUnrar = nullptr; mPageMap.clear(); mEntries.clear(); } bool Document::processArchive() { if ( !mArchive->open( QIODevice::ReadOnly ) ) { delete mArchive; mArchive = nullptr; return false; } const KArchiveDirectory *directory = mArchive->directory(); if ( !directory ) { delete mArchive; mArchive = nullptr; return false; } mArchiveDir = const_cast( directory ); imagesInArchive( QString(), mArchiveDir, &mEntries ); return true; } void Document::pages( QVector * pagesVector ) { qSort( mEntries.begin(), mEntries.end(), caseSensitiveNaturalOrderLessThen ); QScopedPointer< QIODevice > dev; int count = 0; pagesVector->clear(); pagesVector->resize( mEntries.size() ); QImageReader reader; foreach(const QString &file, mEntries) { if ( mArchive ) { const KArchiveFile *entry = static_cast( mArchiveDir->entry( file ) ); if ( entry ) { dev.reset( entry->createDevice() ); } } else if ( mDirectory ) { dev.reset( mDirectory->createDevice( file ) ); } else { dev.reset( mUnrar->createDevice( file ) ); } if ( ! dev.isNull() ) { reader.setDevice( dev.data() ); if ( reader.canRead() ) { QSize pageSize = reader.size(); if ( !pageSize.isValid() ) { const QImage i = reader.read(); if ( !i.isNull() ) pageSize = i.size(); } if ( pageSize.isValid() ) { pagesVector->replace( count, new Okular::Page( count, pageSize.width(), pageSize.height(), Okular::Rotation0 ) ); mPageMap.append(file); count++; } else { qCDebug(OkularComicbookDebug) << "Ignoring" << file << "doesn't seem to be an image even if QImageReader::canRead returned true"; } } } } pagesVector->resize( count ); } QStringList Document::pageTitles() const { return QStringList(); } QImage Document::pageImage( int page ) const { if ( mArchive ) { const KArchiveFile *entry = static_cast( mArchiveDir->entry( mPageMap[ page ] ) ); if ( entry ) return QImage::fromData( entry->data() ); } else if ( mDirectory ) { return QImage( mPageMap[ page ] ); } else { return QImage::fromData( mUnrar->contentOf( mPageMap[ page ] ) ); } return QImage(); } QString Document::lastErrorString() const { return mLastErrorString; } diff --git a/generators/comicbook/document.h b/generators/comicbook/document.h index 22c321342..a7fe034c0 100644 --- a/generators/comicbook/document.h +++ b/generators/comicbook/document.h @@ -1,58 +1,58 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 COMICBOOK_DOCUMENT_H #define COMICBOOK_DOCUMENT_H -#include +#include class KArchiveDirectory; class KArchive; class QImage; class QSize; class Unrar; class Directory; namespace Okular { class Page; } namespace ComicBook { class Document { public: Document(); ~Document(); bool open( const QString &fileName ); void close(); void pages( QVector * pagesVector ); QStringList pageTitles() const; QImage pageImage( int page ) const; QString lastErrorString() const; private: bool processArchive(); QStringList mPageMap; Directory *mDirectory; Unrar *mUnrar; KArchive *mArchive; KArchiveDirectory *mArchiveDir; QString mLastErrorString; QStringList mEntries; }; } #endif diff --git a/generators/comicbook/generator_comicbook.cpp b/generators/comicbook/generator_comicbook.cpp index 2687f9b59..2cc9063c8 100644 --- a/generators/comicbook/generator_comicbook.cpp +++ b/generators/comicbook/generator_comicbook.cpp @@ -1,98 +1,98 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "generator_comicbook.h" -#include -#include +#include +#include #include #include #include #include #include #include "debug_comicbook.h" OKULAR_EXPORT_PLUGIN(ComicBookGenerator, "libokularGenerator_comicbook.json") ComicBookGenerator::ComicBookGenerator( QObject *parent, const QVariantList &args ) : Generator( parent, args ) { setFeature( Threaded ); setFeature( PrintNative ); setFeature( PrintToFile ); } ComicBookGenerator::~ComicBookGenerator() { } bool ComicBookGenerator::loadDocument( const QString & fileName, QVector & pagesVector ) { if ( !mDocument.open( fileName ) ) { const QString errString = mDocument.lastErrorString(); if ( !errString.isEmpty() ) emit error( errString, -1 ); return false; } mDocument.pages( &pagesVector ); return true; } bool ComicBookGenerator::doCloseDocument() { mDocument.close(); return true; } QImage ComicBookGenerator::image( Okular::PixmapRequest * request ) { int width = request->width(); int height = request->height(); QImage image = mDocument.pageImage( request->pageNumber() ); return image.scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } bool ComicBookGenerator::print( QPrinter& printer ) { QPainter p( &printer ); QList pageList = Okular::FilePrinter::pageList( printer, document()->pages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); for ( int i = 0; i < pageList.count(); ++i ) { QImage image = mDocument.pageImage( pageList[i] - 1 ); if ( ( image.width() > printer.width() ) || ( image.height() > printer.height() ) ) image = image.scaled( printer.width(), printer.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); if ( i != 0 ) printer.newPage(); p.drawImage( 0, 0, image ); } return true; } Q_LOGGING_CATEGORY(OkularComicbookDebug, "org.kde.okular.generators.comicbook", QtWarningMsg) #include "generator_comicbook.moc" diff --git a/generators/comicbook/qnatsort.h b/generators/comicbook/qnatsort.h index eb63c79ac..00e7a697d 100644 --- a/generators/comicbook/qnatsort.h +++ b/generators/comicbook/qnatsort.h @@ -1,54 +1,54 @@ /** Natural order sorting of strings which contains numbers. Copyright 2007 Tobias Koenig based on the natural order code by Martin Pool This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 or at your option version 3 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef QNATSORT_H #define QNATSORT_H -#include +#include /** * The two methods can be used in qSort to sort strings which contain * numbers in natural order. * * Normally strings are ordered like this: fam10g fam1g fam2g fam3g * natural ordered it would look like this: fam1g fam2g fam3g fam10g * * Code: * * QStringList list; * list << "fam10g" << "fam1g" << "fam2g" << "fam5g"; * * qSort( list.begin(), list.end(), caseSensitiveNaturalOderLessThen ); */ /** * Returns whether the @p left string is lesser than the @p right string * in natural order. */ bool caseSensitiveNaturalOrderLessThen( const QString &left, const QString &right ); /** * Returns whether the @p left string is lesser than the @p right string * in natural order and case insensitive. */ bool caseInsensitiveNaturalOrderLessThen( const QString &left, const QString &right ); #endif diff --git a/generators/comicbook/unrar.cpp b/generators/comicbook/unrar.cpp index 587a3c125..fb8318af5 100644 --- a/generators/comicbook/unrar.cpp +++ b/generators/comicbook/unrar.cpp @@ -1,266 +1,266 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "unrar.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include +#include #if defined(WITH_KPTY) #include #include #endif #include "unrarflavours.h" #include "debug_comicbook.h" #include #include struct UnrarHelper { UnrarHelper(); ~UnrarHelper(); UnrarFlavour *kind; QString unrarPath; }; Q_GLOBAL_STATIC( UnrarHelper, helper ) static UnrarFlavour* detectUnrar( const QString &unrarPath, const QString &versionCommand ) { UnrarFlavour* kind = nullptr; QProcess proc; proc.start( unrarPath, QStringList() << versionCommand ); bool ok = proc.waitForFinished( -1 ); Q_UNUSED( ok ) const QStringList lines = QString::fromLocal8Bit( proc.readAllStandardOutput() ).split( QLatin1Char('\n'), QString::SkipEmptyParts ); if ( !lines.isEmpty() ) { if ( lines.first().startsWith( QLatin1String("UNRAR ") ) ) kind = new NonFreeUnrarFlavour(); else if ( lines.first().startsWith( QLatin1String("RAR ") ) ) kind = new NonFreeUnrarFlavour(); else if ( lines.first().startsWith( QLatin1String("unrar ") ) ) kind = new FreeUnrarFlavour(); } return kind; } UnrarHelper::UnrarHelper() : kind( nullptr ) { QString path = QStandardPaths::findExecutable( QStringLiteral("unrar-nonfree") ); if ( path.isEmpty() ) path = QStandardPaths::findExecutable( QStringLiteral("unrar") ); if ( path.isEmpty() ) path = QStandardPaths::findExecutable( QStringLiteral("rar") ); if ( !path.isEmpty() ) kind = detectUnrar( path, QStringLiteral("--version") ); if ( !kind ) kind = detectUnrar( path, QStringLiteral("-v") ); if ( !kind ) { // no luck, print that qWarning() << "No unrar detected."; } else { unrarPath = path; qCDebug(OkularComicbookDebug) << "detected:" << path << "(" << kind->name() << ")"; } } UnrarHelper::~UnrarHelper() { delete kind; } Unrar::Unrar() : QObject( nullptr ), mLoop( nullptr ), mTempDir( nullptr ) { } Unrar::~Unrar() { delete mTempDir; } bool Unrar::open( const QString &fileName ) { if ( !isSuitableVersionAvailable() ) return false; delete mTempDir; mTempDir = new QTemporaryDir(); mFileName = fileName; /** * Extract the archive to a temporary directory */ mStdOutData.clear(); mStdErrData.clear(); int ret = startSyncProcess( QStringList() << QStringLiteral("e") << mFileName << mTempDir->path() + QLatin1Char('/') ); bool ok = ret == 0; return ok; } QStringList Unrar::list() { mStdOutData.clear(); mStdErrData.clear(); if ( !isSuitableVersionAvailable() ) return QStringList(); startSyncProcess( QStringList() << QStringLiteral("lb") << mFileName ); const QStringList listFiles = helper->kind->processListing( QString::fromLocal8Bit( mStdOutData ).split( QLatin1Char('\n'), QString::SkipEmptyParts ) ); QStringList newList; Q_FOREACH ( const QString &f, listFiles ) { // Extract all the files to mTempDir regardless of their path inside the archive // This will break if ever an arvhice with two files with the same name in different subfolders QFileInfo fi( f ); if ( QFile::exists( mTempDir->path() + QLatin1Char('/') + fi.fileName() ) ) { newList.append( fi.fileName() ); } } return newList; } QByteArray Unrar::contentOf( const QString &fileName ) const { if ( !isSuitableVersionAvailable() ) return QByteArray(); QFile file( mTempDir->path() + QLatin1Char('/') + fileName ); if ( !file.open( QIODevice::ReadOnly ) ) return QByteArray(); return file.readAll(); } QIODevice* Unrar::createDevice( const QString &fileName ) const { if ( !isSuitableVersionAvailable() ) return nullptr; std::unique_ptr< QFile> file( new QFile( mTempDir->path() + QLatin1Char('/') + fileName ) ); if ( !file->open( QIODevice::ReadOnly ) ) return nullptr; return file.release(); } bool Unrar::isAvailable() { return helper->kind; } bool Unrar::isSuitableVersionAvailable() { if ( !isAvailable() ) return false; return dynamic_cast< NonFreeUnrarFlavour * >( helper->kind ); } void Unrar::readFromStdout() { if ( !mProcess ) return; mStdOutData += mProcess->readAllStandardOutput(); } void Unrar::readFromStderr() { if ( !mProcess ) return; mStdErrData += mProcess->readAllStandardError(); if ( !mStdErrData.isEmpty() ) { mProcess->kill(); return; } } void Unrar::finished( int exitCode, QProcess::ExitStatus exitStatus ) { Q_UNUSED( exitCode ) if ( mLoop ) { mLoop->exit( exitStatus == QProcess::CrashExit ? 1 : 0 ); } } int Unrar::startSyncProcess( const QStringList &args ) { int ret = 0; #if !defined(WITH_KPTY) mProcess = new QProcess( this ); connect(mProcess, &QProcess::readyReadStandardOutput, this, &Unrar::readFromStdout); connect(mProcess, &QProcess::readyReadStandardError, this, &Unrar::readFromStderr); connect(mProcess, static_cast(&QProcess::finished), this, &Unrar::finished); #else mProcess = new KPtyProcess( this ); mProcess->setOutputChannelMode( KProcess::SeparateChannels ); connect(mProcess, &KPtyProcess::readyReadStandardOutput, this, &Unrar::readFromStdout); connect(mProcess, &KPtyProcess::readyReadStandardError, this, &Unrar::readFromStderr); connect(mProcess, static_cast(&KPtyProcess::finished), this, &Unrar::finished); #endif #if !defined(WITH_KPTY) mProcess->start( helper->unrarPath, args, QIODevice::ReadWrite | QIODevice::Unbuffered ); ret = mProcess->waitForFinished( -1 ) ? 0 : 1; #else mProcess->setProgram( helper->unrarPath, args ); mProcess->setNextOpenMode( QIODevice::ReadWrite | QIODevice::Unbuffered ); mProcess->start(); QEventLoop loop; mLoop = &loop; ret = loop.exec( QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents ); mLoop = nullptr; #endif delete mProcess; mProcess = nullptr; return ret; } void Unrar::writeToProcess( const QByteArray &data ) { if ( !mProcess || data.isNull() ) return; #if !defined(WITH_KPTY) mProcess->write( data ); #else mProcess->pty()->write( data ); #endif } diff --git a/generators/comicbook/unrar.h b/generators/comicbook/unrar.h index e528b7ba8..cf5e7f220 100644 --- a/generators/comicbook/unrar.h +++ b/generators/comicbook/unrar.h @@ -1,84 +1,84 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 UNRAR_H #define UNRAR_H -#include -#include -#include +#include +#include +#include class QEventLoop; class QTemporaryDir; #if defined(WITH_KPTY) class KPtyProcess; #endif class Unrar : public QObject { Q_OBJECT public: /** * Creates a new unrar object. */ Unrar(); /** * Destroys the unrar object. */ ~Unrar(); /** * Opens given rar archive. */ bool open( const QString &fileName ); /** * Returns the list of files from the archive. */ QStringList list(); /** * Returns the content of the file with the given name. */ QByteArray contentOf( const QString &fileName ) const; /** * Returns a new device for reading the file with the given name. */ QIODevice* createDevice( const QString &fileName ) const; static bool isAvailable(); static bool isSuitableVersionAvailable(); private Q_SLOTS: void readFromStdout(); void readFromStderr(); void finished( int exitCode, QProcess::ExitStatus exitStatus ); private: int startSyncProcess( const QStringList &args ); void writeToProcess( const QByteArray &data ); #if defined(WITH_KPTY) KPtyProcess *mProcess; #else QProcess *mProcess; #endif QEventLoop *mLoop; QString mFileName; QByteArray mStdOutData; QByteArray mStdErrData; QTemporaryDir *mTempDir; }; #endif diff --git a/generators/comicbook/unrarflavours.cpp b/generators/comicbook/unrarflavours.cpp index 7119a46bd..9e591e453 100644 --- a/generators/comicbook/unrarflavours.cpp +++ b/generators/comicbook/unrarflavours.cpp @@ -1,73 +1,73 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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 "unrarflavours.h" -#include -#include +#include +#include UnrarFlavour::UnrarFlavour() { } UnrarFlavour::~UnrarFlavour() { } void UnrarFlavour::setFileName( const QString &fileName ) { mFileName = fileName; } QString UnrarFlavour::fileName() const { return mFileName; } NonFreeUnrarFlavour::NonFreeUnrarFlavour() : UnrarFlavour() { } QStringList NonFreeUnrarFlavour::processListing( const QStringList &data ) { // unrar-nonfree just lists the files return data; } QString NonFreeUnrarFlavour::name() const { return QStringLiteral("unrar-nonfree"); } FreeUnrarFlavour::FreeUnrarFlavour() : UnrarFlavour() { } QStringList FreeUnrarFlavour::processListing( const QStringList &data ) { QRegExp re( QStringLiteral("^ ([^/]+/([^\\s]+))$") ); QStringList newdata; foreach ( const QString &line, data ) { if ( re.exactMatch( line ) ) newdata.append( re.cap( 1 ) ); } return newdata; } QString FreeUnrarFlavour::name() const { return QStringLiteral("unrar-free"); } diff --git a/generators/comicbook/unrarflavours.h b/generators/comicbook/unrarflavours.h index f72bdc3e6..cb6333d92 100644 --- a/generators/comicbook/unrarflavours.h +++ b/generators/comicbook/unrarflavours.h @@ -1,55 +1,55 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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 UNRARFLAVOURS_H #define UNRARFLAVOURS_H -#include +#include class QStringList; class UnrarFlavour { public: virtual ~UnrarFlavour(); virtual QStringList processListing( const QStringList &data ) = 0; virtual QString name() const = 0; void setFileName( const QString &fileName ); protected: UnrarFlavour(); QString fileName() const; private: QString mFileName; }; class NonFreeUnrarFlavour : public UnrarFlavour { public: NonFreeUnrarFlavour(); QStringList processListing( const QStringList &data ) override; QString name() const override; }; class FreeUnrarFlavour : public UnrarFlavour { public: FreeUnrarFlavour(); QStringList processListing( const QStringList &data ) override; QString name() const override; }; #endif diff --git a/generators/djvu/generator_djvu.cpp b/generators/djvu/generator_djvu.cpp index 644f746ca..391210014 100644 --- a/generators/djvu/generator_djvu.cpp +++ b/generators/djvu/generator_djvu.cpp @@ -1,442 +1,442 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 "generator_djvu.h" #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 static void recurseCreateTOC( QDomDocument &maindoc, const QDomNode &parent, QDomNode &parentDestination, KDjVu *djvu ) { QDomNode n = parent.firstChild(); while( !n.isNull() ) { QDomElement el = n.toElement(); QDomElement newel = maindoc.createElement( el.attribute( QStringLiteral("title") ) ); parentDestination.appendChild( newel ); QString dest; if ( !( dest = el.attribute( QStringLiteral("PageNumber") ) ).isEmpty() ) { Okular::DocumentViewport vp; vp.pageNumber = dest.toInt() - 1; newel.setAttribute( QStringLiteral("Viewport"), vp.toString() ); } else if ( !( dest = el.attribute( QStringLiteral("PageName") ) ).isEmpty() ) { Okular::DocumentViewport vp; vp.pageNumber = djvu->pageNumber( dest ); newel.setAttribute( QStringLiteral("Viewport"), vp.toString() ); } else if ( !( dest = el.attribute( QStringLiteral("URL") ) ).isEmpty() ) { newel.setAttribute( QStringLiteral("URL"), dest ); } if ( el.hasChildNodes() ) { recurseCreateTOC( maindoc, n, newel, djvu ); } n = n.nextSibling(); } } OKULAR_EXPORT_PLUGIN(DjVuGenerator, "libokularGenerator_djvu.json") DjVuGenerator::DjVuGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ), m_docSyn( nullptr ) { setFeature( TextExtraction ); setFeature( Threaded ); setFeature( PrintPostscript ); if ( Okular::FilePrinter::ps2pdfAvailable() ) setFeature( PrintToFile ); m_djvu = new KDjVu(); m_djvu->setCacheEnabled( false ); } DjVuGenerator::~DjVuGenerator() { delete m_djvu; } bool DjVuGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector ) { QMutexLocker locker( userMutex() ); if ( !m_djvu->openFile( fileName ) ) return false; locker.unlock(); loadPages( pagesVector, 0 ); return true; } bool DjVuGenerator::doCloseDocument() { userMutex()->lock(); m_djvu->closeFile(); userMutex()->unlock(); delete m_docSyn; m_docSyn = nullptr; return true; } QImage DjVuGenerator::image( Okular::PixmapRequest *request ) { userMutex()->lock(); QImage img = m_djvu->image( request->pageNumber(), request->width(), request->height(), request->page()->rotation() ); userMutex()->unlock(); return img; } Okular::DocumentInfo DjVuGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; if ( keys.contains( Okular::DocumentInfo::MimeType ) ) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("image/vnd.djvu") ); if ( m_djvu ) { // compile internal structure reading properties from KDjVu if ( keys.contains( Okular::DocumentInfo::Author ) ) docInfo.set( Okular::DocumentInfo::Title, m_djvu->metaData( QStringLiteral("title") ).toString() ); if ( keys.contains( Okular::DocumentInfo::Author ) ) docInfo.set( Okular::DocumentInfo::Author, m_djvu->metaData( QStringLiteral("author") ).toString() ); if ( keys.contains( Okular::DocumentInfo::CreationDate ) ) docInfo.set( Okular::DocumentInfo::CreationDate, m_djvu->metaData( QStringLiteral("year") ).toString() ); if ( keys.contains( Okular::DocumentInfo::CustomKeys ) ) { docInfo.set( QStringLiteral("editor"), m_djvu->metaData( QStringLiteral("editor") ).toString(), i18n( "Editor" ) ); docInfo.set( QStringLiteral("publisher"), m_djvu->metaData( QStringLiteral("publisher") ).toString(), i18n( "Publisher" ) ); docInfo.set( QStringLiteral("volume"), m_djvu->metaData( QStringLiteral("volume") ).toString(), i18n( "Volume" ) ); docInfo.set( QStringLiteral("documentType"), m_djvu->metaData( QStringLiteral("documentType") ).toString(), i18n( "Type of document" ) ); QVariant numcomponents = m_djvu->metaData( QStringLiteral("componentFile") ); docInfo.set( QStringLiteral("componentFile"), numcomponents.type() != QVariant::Int ? i18nc( "Unknown number of component files", "Unknown" ) : numcomponents.toString(), i18n( "Component Files" ) ); } } return docInfo; } const Okular::DocumentSynopsis * DjVuGenerator::generateDocumentSynopsis() { QMutexLocker locker( userMutex() ); if ( m_docSyn ) return m_docSyn; const QDomDocument *doc = m_djvu->documentBookmarks(); if ( doc ) { m_docSyn = new Okular::DocumentSynopsis(); recurseCreateTOC( *m_docSyn, *doc, *m_docSyn, m_djvu ); } locker.unlock(); return m_docSyn; } bool DjVuGenerator::print( QPrinter& printer ) { bool result = false; // Create tempfile to write to QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); if ( !tf.open() ) return false; const QString fileName = tf.fileName(); QMutexLocker locker( userMutex() ); QList pageList = Okular::FilePrinter::pageList( printer, m_djvu->pages().count(), document()->currentPage() + 1, document()->bookmarkedPageList() ); if ( m_djvu->exportAsPostScript( &tf, pageList ) ) { tf.setAutoRemove( false ); tf.close(); int ret = Okular::FilePrinter::printFile( printer, fileName, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange() ); result = ( ret >=0 ); } return result; } QVariant DjVuGenerator::metaData( const QString &key, const QVariant &option ) const { Q_UNUSED( option ) if ( key == QLatin1String("DocumentTitle") ) { return m_djvu->metaData( QStringLiteral("title") ); } return QVariant(); } Okular::TextPage* DjVuGenerator::textPage( Okular::TextRequest *request ) { userMutex()->lock(); const Okular::Page *page = request->page(); QList te; #if 0 m_djvu->textEntities( page->number(), "char" ); #endif if ( te.isEmpty() ) te = m_djvu->textEntities( page->number(), QStringLiteral("word") ); if ( te.isEmpty() ) te = m_djvu->textEntities( page->number(), QStringLiteral("line") ); userMutex()->unlock(); QList::ConstIterator it = te.constBegin(); QList::ConstIterator itEnd = te.constEnd(); QList words; const KDjVu::Page* djvupage = m_djvu->pages().at( page->number() ); for ( ; it != itEnd; ++it ) { const KDjVu::TextEntity& cur = *it; words.append( new Okular::TextEntity( cur.text(), new Okular::NormalizedRect( cur.rect(), djvupage->width(), djvupage->height() ) ) ); } Okular::TextPage *textpage = new Okular::TextPage( words ); return textpage; } void DjVuGenerator::loadPages( QVector & pagesVector, int rotation ) { const QVector &djvu_pages = m_djvu->pages(); int numofpages = djvu_pages.count(); pagesVector.resize( numofpages ); for ( int i = 0; i < numofpages; ++i ) { const KDjVu::Page *p = djvu_pages.at( i ); if (pagesVector[i]) delete pagesVector[i]; int w = p->width(); int h = p->height(); if ( rotation % 2 == 1 ) qSwap( w, h ); Okular::Page *page = new Okular::Page( i, w, h, (Okular::Rotation)( p->orientation() + rotation ) ); pagesVector[i] = page; QList annots; QList links; userMutex()->lock(); m_djvu->linksAndAnnotationsForPage( i, &links, &annots ); userMutex()->unlock(); if ( !links.isEmpty() ) { QLinkedList rects; QList::ConstIterator it = links.constBegin(); QList::ConstIterator itEnd = links.constEnd(); for ( ; it != itEnd; ++it ) { KDjVu::Link *curlink = (*it); Okular::ObjectRect *newrect = convertKDjVuLink( i, curlink ); if ( newrect ) rects.append( newrect ); // delete the links as soon as we process them delete curlink; } if ( rects.count() > 0 ) page->setObjectRects( rects ); } if ( !annots.isEmpty() ) { QList::ConstIterator it = annots.constBegin(); QList::ConstIterator itEnd = annots.constEnd(); for ( ; it != itEnd; ++it ) { KDjVu::Annotation *ann = (*it); Okular::Annotation *newann = convertKDjVuAnnotation( w, h, ann ); if ( newann ) page->addAnnotation( newann ); // delete the annotations as soon as we process them delete ann; } } } } Okular::ObjectRect* DjVuGenerator::convertKDjVuLink( int page, KDjVu::Link * link ) const { int newpage = -1; Okular::Action *newlink = nullptr; Okular::ObjectRect *newrect = nullptr; switch ( link->type() ) { case KDjVu::Link::PageLink: { KDjVu::PageLink* l = static_cast( link ); bool ok = true; QString target = l->page(); if ( ( target.length() > 0 ) && target.at(0) == QLatin1Char( '#' ) ) target.remove( 0, 1 ); int tmppage = target.toInt( &ok ); if ( ok || target.isEmpty() ) { Okular::DocumentViewport vp; if ( !target.isEmpty() ) { vp.pageNumber = ( target.at(0) == QLatin1Char( '+' ) || target.at(0) == QLatin1Char( '-' ) ) ? page + tmppage : tmppage - 1; newpage = vp.pageNumber; } newlink = new Okular::GotoAction( QString(), vp ); } break; } case KDjVu::Link::UrlLink: { KDjVu::UrlLink* l = static_cast( link ); QString url = l->url(); newlink = new Okular::BrowseAction( QUrl(url) ); break; } } if ( newlink ) { const KDjVu::Page* p = m_djvu->pages().value( newpage == -1 ? page : newpage ); if ( !p ) p = m_djvu->pages().at( page ); int width = p->width(); int height = p->height(); bool scape_orientation = false; // hack by tokoe, should always create default page if ( scape_orientation ) qSwap( width, height ); switch ( link->areaType() ) { case KDjVu::Link::RectArea: case KDjVu::Link::EllipseArea: { QRect r( QPoint( link->point().x(), p->height() - link->point().y() - link->size().height() ), link->size() ); bool ellipse = ( link->areaType() == KDjVu::Link::EllipseArea ); newrect = new Okular::ObjectRect( Okular::NormalizedRect( Okular::Utils::rotateRect( r, width, height, 0 ), width, height ), ellipse, Okular::ObjectRect::Action, newlink ); break; } case KDjVu::Link::PolygonArea: { QPolygon poly = link->polygon(); QPolygonF newpoly; for ( int i = 0; i < poly.count(); ++i ) { int x = poly.at(i).x(); int y = poly.at(i).y(); if ( scape_orientation ) qSwap( x, y ); else { y = height - y; } newpoly << QPointF( (double)(x)/width, (double)(y)/height ); } if ( !newpoly.isEmpty() ) { newpoly << newpoly.first(); newrect = new Okular::ObjectRect( newpoly, Okular::ObjectRect::Action, newlink ); } break; } default: ; } if ( !newrect ) { delete newlink; } } return newrect; } Okular::Annotation* DjVuGenerator::convertKDjVuAnnotation( int w, int h, KDjVu::Annotation * ann ) const { Okular::Annotation *newann = nullptr; switch ( ann->type() ) { case KDjVu::Annotation::TextAnnotation: { KDjVu::TextAnnotation* txtann = static_cast( ann ); Okular::TextAnnotation * newtxtann = new Okular::TextAnnotation(); // boundary QRect rect( QPoint( txtann->point().x(), h - txtann->point().y() - txtann->size().height() ), txtann->size() ); newtxtann->setBoundingRectangle( Okular::NormalizedRect( Okular::Utils::rotateRect( rect, w, h, 0 ), w, h ) ); // type newtxtann->setTextType( txtann->inlineText() ? Okular::TextAnnotation::InPlace : Okular::TextAnnotation::Linked ); newtxtann->style().setOpacity( txtann->color().alphaF() ); // FIXME remove once the annotation text handling is fixed newtxtann->setContents( ann->comment() ); newann = newtxtann; break; } case KDjVu::Annotation::LineAnnotation: { KDjVu::LineAnnotation* lineann = static_cast( ann ); Okular::LineAnnotation * newlineann = new Okular::LineAnnotation(); // boundary QPoint a( lineann->point().x(), h - lineann->point().y() ); QPoint b( lineann->point2().x(), h - lineann->point2().y() ); QRect rect = QRect( a, b ).normalized(); newlineann->setBoundingRectangle( Okular::NormalizedRect( Okular::Utils::rotateRect( rect, w, h, 0 ), w, h ) ); // line points QLinkedList points; points.append( Okular::NormalizedPoint( a.x(), a.y(), w, h ) ); points.append( Okular::NormalizedPoint( b.x(), b.y(), w, h ) ); newlineann->setLinePoints( points ); // arrow? if ( lineann->isArrow() ) newlineann->setLineEndStyle( Okular::LineAnnotation::OpenArrow ); // width newlineann->style().setWidth( lineann->width() ); newann = newlineann; break; } } if ( newann ) { // setting the common parameters newann->style().setColor( ann->color() ); newann->setContents( ann->comment() ); // creating an id as name for the annotation QString uid = QUuid::createUuid().toString(); uid.remove( 0, 1 ); uid.chop( 1 ); uid.remove( QLatin1Char( '-' ) ); newann->setUniqueName( uid ); // is external newann->setFlags( newann->flags() | Okular::Annotation::External ); } return newann; } #include "generator_djvu.moc" diff --git a/generators/djvu/kdjvu.cpp b/generators/djvu/kdjvu.cpp index e107fc738..b629a6645 100644 --- a/generators/djvu/kdjvu.cpp +++ b/generators/djvu/kdjvu.cpp @@ -1,1162 +1,1162 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 "kdjvu.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include #include #include #include QDebug &operator<<( QDebug & s, const ddjvu_rect_t &r ) { s.nospace() << "[" << r.x << "," << r.y << " - " << r.w << "x" << r.h << "]"; return s.space(); } static void which_ddjvu_message( const ddjvu_message_t *msg ) { #ifdef KDJVU_DEBUG qDebug() << "which_djvu_message(...):" << msg->m_any.tag; switch( msg->m_any.tag ) { case DDJVU_ERROR: qDebug().nospace() << "ERROR: file " << msg->m_error.filename << ", line " << msg->m_error.lineno; qDebug().nospace() << "ERROR: function '" << msg->m_error.function << "'"; qDebug().nospace() << "ERROR: '" << msg->m_error.message << "'"; break; case DDJVU_INFO: qDebug().nospace() << "INFO: '" << msg->m_info.message << "'"; break; case DDJVU_CHUNK: qDebug().nospace() << "CHUNK: '" << QByteArray( msg->m_chunk.chunkid ) << "'"; break; case DDJVU_PROGRESS: qDebug().nospace() << "PROGRESS: '" << msg->m_progress.percent << "'"; break; default: ; } #else Q_UNUSED( msg ); #endif } /** * Explore the message queue until there are message left in it. */ static void handle_ddjvu_messages( ddjvu_context_t *ctx, int wait ) { const ddjvu_message_t *msg; if ( wait ) ddjvu_message_wait( ctx ); while ( ( msg = ddjvu_message_peek( ctx ) ) ) { which_ddjvu_message( msg ); ddjvu_message_pop( ctx ); } } /** * Explore the message queue until the message \p mid is found. */ static void wait_for_ddjvu_message( ddjvu_context_t *ctx, ddjvu_message_tag_t mid ) { ddjvu_message_wait( ctx ); const ddjvu_message_t *msg; while ( ( msg = ddjvu_message_peek( ctx ) ) && msg && ( msg->m_any.tag != mid ) ) { which_ddjvu_message( msg ); ddjvu_message_pop( ctx ); } } /** * Convert a clockwise coefficient \p r for a rotation to a counter-clockwise * and vice versa. */ static int flipRotation( int r ) { return ( 4 - r ) % 4; } static miniexp_t find_second_in_pair( miniexp_t theexp, const char* which ) { miniexp_t exp = theexp; while ( exp ) { miniexp_t cur = miniexp_car( exp ); if ( !miniexp_consp( cur ) || !miniexp_symbolp( miniexp_car( cur ) ) ) { exp = miniexp_cdr( exp ); continue; } const QString id = QString::fromUtf8( miniexp_to_name( miniexp_car( cur ) ) ); if ( id == QLatin1String( which ) ) return miniexp_cadr( cur ); exp = miniexp_cdr( exp ); } return miniexp_nil; } static bool find_replace_or_add_second_in_pair( miniexp_t theexp, const char* which, miniexp_t replacement ) { miniexp_t exp = miniexp_cdddr( theexp ); while ( exp ) { miniexp_t cur = miniexp_car( exp ); if ( !miniexp_consp( cur ) || !miniexp_symbolp( miniexp_car( cur ) ) ) { exp = miniexp_cdr( exp ); continue; } const QString id = QString::fromUtf8( miniexp_to_name( miniexp_car( cur ) ) ); if ( id == QLatin1String( which ) ) { miniexp_t reversed = miniexp_reverse( cur ); miniexp_rplaca( reversed, replacement ); cur = miniexp_reverse( reversed ); return true; } exp = miniexp_cdr( exp ); } // TODO add the new replacement ad the end of the list return false; } // ImageCacheItem class ImageCacheItem { public: ImageCacheItem( int p, int w, int h, const QImage& i ) : page( p ), width( w ), height( h ), img( i ) { } int page; int width; int height; QImage img; }; // KdjVu::Page KDjVu::Page::Page() { } KDjVu::Page::~Page() { } int KDjVu::Page::width() const { return m_width; } int KDjVu::Page::height() const { return m_height; } int KDjVu::Page::dpi() const { return m_dpi; } int KDjVu::Page::orientation() const { return m_orientation; } // KDjVu::Link KDjVu::Link::~Link() { } KDjVu::Link::LinkArea KDjVu::Link::areaType() const { return m_area; } QPoint KDjVu::Link::point() const { return m_point; } QSize KDjVu::Link::size() const { return m_size; } QPolygon KDjVu::Link::polygon() const { return m_poly; } // KDjVu::PageLink KDjVu::PageLink::PageLink() { } int KDjVu::PageLink::type() const { return KDjVu::Link::PageLink; } QString KDjVu::PageLink::page() const { return m_page; } // KDjVu::UrlLink KDjVu::UrlLink::UrlLink() { } int KDjVu::UrlLink::type() const { return KDjVu::Link::UrlLink; } QString KDjVu::UrlLink::url() const { return m_url; } // KDjVu::Annotation KDjVu::Annotation::Annotation( miniexp_t anno ) : m_anno( anno ) { } KDjVu::Annotation::~Annotation() { } QPoint KDjVu::Annotation::point() const { miniexp_t area = miniexp_nth( 3, m_anno ); int a = miniexp_to_int( miniexp_nth( 1, area ) ); int b = miniexp_to_int( miniexp_nth( 2, area ) ); return QPoint( a, b ); } QString KDjVu::Annotation::comment() const { return QString::fromUtf8( miniexp_to_str( miniexp_nth( 2, m_anno ) ) ); } void KDjVu::Annotation::setComment( const QString &comment ) { miniexp_t exp = m_anno; exp = miniexp_cdr( exp ); exp = miniexp_cdr( exp ); miniexp_rplaca( exp, miniexp_string( comment.toUtf8().constData() ) ); } QColor KDjVu::Annotation::color() const { return QColor(); } void KDjVu::Annotation::setColor( const QColor & ) { } // KDjVu::TextAnnotation KDjVu::TextAnnotation::TextAnnotation( miniexp_t anno ) : Annotation( anno ), m_inlineText( true ) { const int num = miniexp_length( m_anno ); for ( int j = 4; j < num; ++j ) { miniexp_t curelem = miniexp_nth( j, m_anno ); if ( !miniexp_listp( curelem ) ) continue; QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, curelem ) ) ); if ( id == QLatin1String( "pushpin" ) ) m_inlineText = false; } } QSize KDjVu::TextAnnotation::size() const { miniexp_t area = miniexp_nth( 3, m_anno ); int c = miniexp_to_int( miniexp_nth( 3, area ) ); int d = miniexp_to_int( miniexp_nth( 4, area ) ); return QSize( c, d ); } int KDjVu::TextAnnotation::type() const { return KDjVu::Annotation::TextAnnotation; } QColor KDjVu::TextAnnotation::color() const { miniexp_t col = find_second_in_pair( m_anno, "backclr" ); if ( !miniexp_symbolp( col ) ) return Qt::transparent; return QColor( QString::fromUtf8( miniexp_to_name( col ) ) ); } void KDjVu::TextAnnotation::setColor( const QColor &color ) { const QByteArray col = color.name().toLatin1(); find_replace_or_add_second_in_pair( m_anno, "backclr", miniexp_symbol( col.constData() ) ); } bool KDjVu::TextAnnotation::inlineText() const { return m_inlineText; } // KDjVu::LineAnnotation KDjVu::LineAnnotation::LineAnnotation( miniexp_t anno ) : Annotation( anno ), m_isArrow( false ), m_width( miniexp_nil ) { const int num = miniexp_length( m_anno ); for ( int j = 4; j < num; ++j ) { miniexp_t curelem = miniexp_nth( j, m_anno ); if ( !miniexp_listp( curelem ) ) continue; QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, curelem ) ) ); if ( id == QLatin1String( "arrow" ) ) m_isArrow = true; else if ( id == QLatin1String( "width" ) ) m_width = curelem; } } int KDjVu::LineAnnotation::type() const { return KDjVu::Annotation::LineAnnotation; } QColor KDjVu::LineAnnotation::color() const { miniexp_t col = find_second_in_pair( m_anno, "lineclr" ); if ( !miniexp_symbolp( col ) ) return Qt::black; return QColor( QString::fromUtf8( miniexp_to_name( col ) ) ); } void KDjVu::LineAnnotation::setColor( const QColor &color ) { const QByteArray col = color.name().toLatin1(); find_replace_or_add_second_in_pair( m_anno, "lineclr", miniexp_symbol( col.constData() ) ); } QPoint KDjVu::LineAnnotation::point2() const { miniexp_t area = miniexp_nth( 3, m_anno ); int c = miniexp_to_int( miniexp_nth( 3, area ) ); int d = miniexp_to_int( miniexp_nth( 4, area ) ); return QPoint( c, d ); } bool KDjVu::LineAnnotation::isArrow() const { return m_isArrow; } int KDjVu::LineAnnotation::width() const { if ( m_width == miniexp_nil ) return 1; return miniexp_to_int( miniexp_cadr( m_width ) ); } void KDjVu::LineAnnotation::setWidth( int width ) { find_replace_or_add_second_in_pair( m_anno, "width", miniexp_number( width ) ); } // KDjVu::TextEntity KDjVu::TextEntity::TextEntity() { } KDjVu::TextEntity::~TextEntity() { } QString KDjVu::TextEntity::text() const { return m_text; } QRect KDjVu::TextEntity::rect() const { return m_rect; } class KDjVu::Private { public: Private() : m_djvu_cxt( nullptr ), m_djvu_document( nullptr ), m_format( nullptr ), m_docBookmarks( nullptr ), m_cacheEnabled( true ) { } QImage generateImageTile( ddjvu_page_t *djvupage, int& res, int width, int row, int xdelta, int height, int col, int ydelta ); void readBookmarks(); void fillBookmarksRecurse( QDomDocument& maindoc, QDomNode& curnode, miniexp_t exp, int offset = -1 ); void readMetaData( int page ); int pageWithName( const QString & name ); ddjvu_context_t *m_djvu_cxt; ddjvu_document_t *m_djvu_document; ddjvu_format_t *m_format; QVector m_pages; QVector m_pages_cache; QList mImgCache; QHash m_metaData; QDomDocument * m_docBookmarks; QHash m_pageNamesCache; bool m_cacheEnabled; static unsigned int s_formatmask[4]; }; unsigned int KDjVu::Private::s_formatmask[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }; QImage KDjVu::Private::generateImageTile( ddjvu_page_t *djvupage, int& res, int width, int row, int xdelta, int height, int col, int ydelta ) { ddjvu_rect_t renderrect; renderrect.x = row * xdelta; renderrect.y = col * ydelta; int realwidth = qMin( width - renderrect.x, xdelta ); int realheight = qMin( height - renderrect.y, ydelta ); renderrect.w = realwidth; renderrect.h = realheight; #ifdef KDJVU_DEBUG qDebug() << "renderrect:" << renderrect; #endif ddjvu_rect_t pagerect; pagerect.x = 0; pagerect.y = 0; pagerect.w = width; pagerect.h = height; #ifdef KDJVU_DEBUG qDebug() << "pagerect:" << pagerect; #endif handle_ddjvu_messages( m_djvu_cxt, false ); QImage res_img( realwidth, realheight, QImage::Format_RGB32 ); // the following line workarounds a rare crash in djvulibre; // it should be fixed with >= 3.5.21 ddjvu_page_get_width( djvupage ); res = ddjvu_page_render( djvupage, DDJVU_RENDER_COLOR, &pagerect, &renderrect, m_format, res_img.bytesPerLine(), (char *)res_img.bits() ); if (!res) { res_img.fill(Qt::white); } #ifdef KDJVU_DEBUG qDebug() << "rendering result:" << res; #endif handle_ddjvu_messages( m_djvu_cxt, false ); return res_img; } void KDjVu::Private::readBookmarks() { if ( !m_djvu_document ) return; miniexp_t outline; while ( ( outline = ddjvu_document_get_outline( m_djvu_document ) ) == miniexp_dummy ) handle_ddjvu_messages( m_djvu_cxt, true ); if ( miniexp_listp( outline ) && ( miniexp_length( outline ) > 0 ) && miniexp_symbolp( miniexp_nth( 0, outline ) ) && ( QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, outline ) ) ) == QLatin1String( "bookmarks" ) ) ) { m_docBookmarks = new QDomDocument( QStringLiteral("KDjVuBookmarks") ); fillBookmarksRecurse( *m_docBookmarks, *m_docBookmarks, outline, 1 ); ddjvu_miniexp_release( m_djvu_document, outline ); } } void KDjVu::Private::fillBookmarksRecurse( QDomDocument& maindoc, QDomNode& curnode, miniexp_t exp, int offset ) { if ( !miniexp_listp( exp ) ) return; int l = miniexp_length( exp ); for ( int i = qMax( offset, 0 ); i < l; ++i ) { miniexp_t cur = miniexp_nth( i, exp ); if ( miniexp_consp( cur ) && ( miniexp_length( cur ) > 0 ) && miniexp_stringp( miniexp_nth( 0, cur ) ) && miniexp_stringp( miniexp_nth( 1, cur ) ) ) { QString title = QString::fromUtf8( miniexp_to_str( miniexp_nth( 0, cur ) ) ); QString dest = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) ); QDomElement el = maindoc.createElement( QStringLiteral("item") ); el.setAttribute( QStringLiteral("title"), title ); if ( !dest.isEmpty() ) { if ( dest.at( 0 ) == QLatin1Char( '#' ) ) { dest.remove( 0, 1 ); bool isNumber = false; dest.toInt( &isNumber ); if ( isNumber ) { // it might be an actual page number, but could also be a page label // so resolve the number, and get the real page number int pageNo = pageWithName( dest ); if ( pageNo != -1 ) { el.setAttribute( QStringLiteral("PageNumber"), QString::number( pageNo + 1 ) ); } else { el.setAttribute( QStringLiteral("PageNumber"), dest ); } } else { el.setAttribute( QStringLiteral("PageName"), dest ); } } else { el.setAttribute( QStringLiteral("URL"), dest ); } } curnode.appendChild( el ); if ( !el.isNull() && ( miniexp_length( cur ) > 2 ) ) { fillBookmarksRecurse( maindoc, el, cur, 2 ); } } } } void KDjVu::Private::readMetaData( int page ) { if ( !m_djvu_document ) return; miniexp_t annots; while ( ( annots = ddjvu_document_get_pageanno( m_djvu_document, page ) ) == miniexp_dummy ) handle_ddjvu_messages( m_djvu_cxt, true ); if ( !miniexp_listp( annots ) || miniexp_length( annots ) == 0 ) return; miniexp_t exp = miniexp_nth( 0, annots ); int size = miniexp_length( exp ); if ( size <= 1 || qstrncmp( miniexp_to_name( miniexp_nth( 0, exp ) ), "metadata", 8 ) ) return; for ( int i = 1; i < size; ++i ) { miniexp_t cur = miniexp_nth( i, exp ); if ( miniexp_length( cur ) != 2 ) continue; QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, cur ) ) ); QString value = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) ); m_metaData[ id.toLower() ] = value; } } int KDjVu::Private::pageWithName( const QString & name ) { const int pageNo = m_pageNamesCache.value( name, -1 ); if ( pageNo != -1 ) return pageNo; const QByteArray utfName = name.toUtf8(); const int fileNum = ddjvu_document_get_filenum( m_djvu_document ); ddjvu_fileinfo_t info; for ( int i = 0; i < fileNum; ++i ) { if ( DDJVU_JOB_OK != ddjvu_document_get_fileinfo( m_djvu_document, i, &info ) ) continue; if ( info.type != 'P' ) continue; if ( ( utfName == info.id ) || ( utfName == info.name ) || ( utfName == info.title ) ) { m_pageNamesCache.insert( name, info.pageno ); return info.pageno; } } return -1; } KDjVu::KDjVu() : d( new Private ) { // creating the djvu context d->m_djvu_cxt = ddjvu_context_create( "KDjVu" ); // creating the rendering format #if DDJVUAPI_VERSION >= 18 d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 4, Private::s_formatmask ); #else d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 3, Private::s_formatmask ); #endif ddjvu_format_set_row_order( d->m_format, 1 ); ddjvu_format_set_y_direction( d->m_format, 1 ); } KDjVu::~KDjVu() { closeFile(); ddjvu_format_release( d->m_format ); ddjvu_context_release( d->m_djvu_cxt ); delete d; } bool KDjVu::openFile( const QString & fileName ) { // first, close the old file if ( d->m_djvu_document ) closeFile(); // load the document... d->m_djvu_document = ddjvu_document_create_by_filename( d->m_djvu_cxt, QFile::encodeName( fileName ).constData(), true ); if ( !d->m_djvu_document ) return false; // ...and wait for its loading wait_for_ddjvu_message( d->m_djvu_cxt, DDJVU_DOCINFO ); if ( ddjvu_document_decoding_error( d->m_djvu_document ) ) { ddjvu_document_release( d->m_djvu_document ); d->m_djvu_document = nullptr; return false; } qDebug() << "# of pages:" << ddjvu_document_get_pagenum( d->m_djvu_document ); int numofpages = ddjvu_document_get_pagenum( d->m_djvu_document ); d->m_pages.clear(); d->m_pages.resize( numofpages ); d->m_pages_cache.clear(); d->m_pages_cache.resize( numofpages ); // get the document type QString doctype; switch ( ddjvu_document_get_type( d->m_djvu_document ) ) { case DDJVU_DOCTYPE_UNKNOWN: doctype = i18nc( "Type of DjVu document", "Unknown" ); break; case DDJVU_DOCTYPE_SINGLEPAGE: doctype = i18nc( "Type of DjVu document", "Single Page" ); break; case DDJVU_DOCTYPE_BUNDLED: doctype = i18nc( "Type of DjVu document", "Bundled" ); break; case DDJVU_DOCTYPE_INDIRECT: doctype = i18nc( "Type of DjVu document", "Indirect" ); break; case DDJVU_DOCTYPE_OLD_BUNDLED: doctype = i18nc( "Type of DjVu document", "Bundled (old)" ); break; case DDJVU_DOCTYPE_OLD_INDEXED: doctype = i18nc( "Type of DjVu document", "Indexed (old)" ); break; } if ( !doctype.isEmpty() ) d->m_metaData[ QStringLiteral("documentType") ] = doctype; // get the number of components d->m_metaData[ QStringLiteral("componentFile") ] = ddjvu_document_get_filenum( d->m_djvu_document ); // read the pages for ( int i = 0; i < numofpages; ++i ) { ddjvu_status_t sts; ddjvu_pageinfo_t info; while ( ( sts = ddjvu_document_get_pageinfo( d->m_djvu_document, i, &info ) ) < DDJVU_JOB_OK ) handle_ddjvu_messages( d->m_djvu_cxt, true ); if ( sts >= DDJVU_JOB_FAILED ) { qDebug().nospace() << "\t>>> page " << i << " failed: " << sts; return false; } KDjVu::Page *p = new KDjVu::Page(); p->m_width = info.width; p->m_height = info.height; p->m_dpi = info.dpi; #if DDJVUAPI_VERSION >= 18 p->m_orientation = flipRotation( info.rotation ); #else p->m_orientation = 0; #endif d->m_pages[i] = p; } // reading the metadata from the first page only should be enough if ( numofpages > 0 ) d->readMetaData( 0 ); return true; } void KDjVu::closeFile() { // deleting the old TOC delete d->m_docBookmarks; d->m_docBookmarks = nullptr; // deleting the pages qDeleteAll( d->m_pages ); d->m_pages.clear(); // releasing the djvu pages QVector::Iterator it = d->m_pages_cache.begin(), itEnd = d->m_pages_cache.end(); for ( ; it != itEnd; ++it ) ddjvu_page_release( *it ); d->m_pages_cache.clear(); // clearing the image cache qDeleteAll( d->mImgCache ); d->mImgCache.clear(); // clearing the old metadata d->m_metaData.clear(); // cleaing the page names mapping d->m_pageNamesCache.clear(); // releasing the old document if ( d->m_djvu_document ) ddjvu_document_release( d->m_djvu_document ); d->m_djvu_document = nullptr; } QVariant KDjVu::metaData( const QString & key ) const { QHash::ConstIterator it = d->m_metaData.constFind( key ); return it != d->m_metaData.constEnd() ? it.value() : QVariant(); } const QDomDocument * KDjVu::documentBookmarks() const { if ( !d->m_docBookmarks ) d->readBookmarks(); return d->m_docBookmarks; } void KDjVu::linksAndAnnotationsForPage( int pageNum, QList *links, QList *annotations ) const { if ( ( pageNum < 0 ) || ( pageNum >= d->m_pages.count() ) || ( !links && !annotations ) ) return; miniexp_t annots; while ( ( annots = ddjvu_document_get_pageanno( d->m_djvu_document, pageNum ) ) == miniexp_dummy ) handle_ddjvu_messages( d->m_djvu_cxt, true ); if ( !miniexp_listp( annots ) ) return; if ( links ) links->clear(); if ( annotations ) annotations->clear(); int l = miniexp_length( annots ); for ( int i = 0; i < l; ++i ) { miniexp_t cur = miniexp_nth( i, annots ); int num = miniexp_length( cur ); if ( ( num < 4 ) || !miniexp_symbolp( miniexp_nth( 0, cur ) ) || ( qstrncmp( miniexp_to_name( miniexp_nth( 0, cur ) ), "maparea", 7 ) != 0 ) ) continue; QString target; QString type; if ( miniexp_symbolp( miniexp_nth( 0, miniexp_nth( 3, cur ) ) ) ) type = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, miniexp_nth( 3, cur ) ) ) ); KDjVu::Link* link = nullptr; KDjVu::Annotation* ann = nullptr; miniexp_t urlexp = miniexp_nth( 1, cur ); if ( links && ( type == QLatin1String( "rect" ) || type == QLatin1String( "oval" ) || type == QLatin1String( "poly" ) ) ) { if ( miniexp_stringp( urlexp ) ) { target = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) ); } else if ( miniexp_listp( urlexp ) && ( miniexp_length( urlexp ) == 3 ) && miniexp_symbolp( miniexp_nth( 0, urlexp ) ) && ( qstrncmp( miniexp_to_name( miniexp_nth( 0, urlexp ) ), "url", 3 ) == 0 ) ) { target = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, urlexp ) ) ); } if ( target.isEmpty() || ( ( target.length() > 0 ) && target.at(0) == QLatin1Char( '#' ) ) ) { KDjVu::PageLink* plink = new KDjVu::PageLink(); plink->m_page = target; link = plink; } else { KDjVu::UrlLink* ulink = new KDjVu::UrlLink(); ulink->m_url = target; link = ulink; } } else if ( annotations && ( type == QLatin1String( "text" ) || type == QLatin1String( "line" ) ) ) { if ( type == QLatin1String( "text" ) ) { KDjVu::TextAnnotation * textann = new KDjVu::TextAnnotation( cur ); ann = textann; } else if ( type == QLatin1String( "line" ) ) { KDjVu::LineAnnotation * lineann = new KDjVu::LineAnnotation( cur ); ann = lineann; } } if ( link /* safety check */ && links ) { link->m_area = KDjVu::Link::UnknownArea; miniexp_t area = miniexp_nth( 3, cur ); int arealength = miniexp_length( area ); if ( ( arealength == 5 ) && ( type == QLatin1String( "rect" ) || type == QLatin1String( "oval" ) ) ) { link->m_point = QPoint( miniexp_to_int( miniexp_nth( 1, area ) ), miniexp_to_int( miniexp_nth( 2, area ) ) ); link->m_size = QSize( miniexp_to_int( miniexp_nth( 3, area ) ), miniexp_to_int( miniexp_nth( 4, area ) ) ); if ( type == QLatin1String( "rect" ) ) { link->m_area = KDjVu::Link::RectArea; } else { link->m_area = KDjVu::Link::EllipseArea; } } else if ( ( arealength > 0 ) && ( arealength % 2 == 1 ) && type == QLatin1String( "poly" ) ) { link->m_area = KDjVu::Link::PolygonArea; QPolygon poly; for ( int j = 1; j < arealength; j += 2 ) { poly << QPoint( miniexp_to_int( miniexp_nth( j, area ) ), miniexp_to_int( miniexp_nth( j + 1, area ) ) ); } link->m_poly = poly; } if ( link->m_area != KDjVu::Link::UnknownArea ) links->append( link ); } else if ( ann /* safety check */ && annotations ) { annotations->append( ann ); } } } const QVector &KDjVu::pages() const { return d->m_pages; } QImage KDjVu::image( int page, int width, int height, int rotation ) { if ( d->m_cacheEnabled ) { bool found = false; QList::Iterator it = d->mImgCache.begin(), itEnd = d->mImgCache.end(); for ( ; ( it != itEnd ) && !found; ++it ) { ImageCacheItem* cur = *it; if ( ( cur->page == page ) && ( rotation % 2 == 0 ? cur->width == width && cur->height == height : cur->width == height && cur->height == width ) ) found = true; } if ( found ) { // taking the element and pushing to the top of the list --it; ImageCacheItem* cur2 = *it; d->mImgCache.erase( it ); d->mImgCache.push_front( cur2 ); return cur2->img; } } if ( !d->m_pages_cache.at( page ) ) { ddjvu_page_t *newpage = ddjvu_page_create_by_pageno( d->m_djvu_document, page ); // wait for the new page to be loaded ddjvu_status_t sts; while ( ( sts = ddjvu_page_decoding_status( newpage ) ) < DDJVU_JOB_OK ) handle_ddjvu_messages( d->m_djvu_cxt, true ); d->m_pages_cache[page] = newpage; } ddjvu_page_t *djvupage = d->m_pages_cache[page]; /* if ( ddjvu_page_get_rotation( djvupage ) != flipRotation( rotation ) ) { // TODO: test documents with initial rotation != 0 // ddjvu_page_set_rotation( djvupage, m_pages.at( page )->orientation() ); ddjvu_page_set_rotation( djvupage, (ddjvu_page_rotation_t)flipRotation( rotation ) ); } */ static const int xdelta = 1500; static const int ydelta = 1500; int xparts = width / xdelta + 1; int yparts = height / ydelta + 1; QImage newimg; int res = 10000; if ( ( xparts == 1 ) && ( yparts == 1 ) ) { // only one part -- render at once with no need to auxiliary image newimg = d->generateImageTile( djvupage, res, width, 0, xdelta, height, 0, ydelta ); } else { // more than one part -- need to render piece-by-piece and to compose // the results newimg = QImage( width, height, QImage::Format_RGB32 ); QPainter p; p.begin( &newimg ); int parts = xparts * yparts; for ( int i = 0; i < parts; ++i ) { int row = i % xparts; int col = i / xparts; int tmpres = 0; QImage tempp = d->generateImageTile( djvupage, tmpres, width, row, xdelta, height, col, ydelta ); if ( tmpres ) { p.drawImage( row * xdelta, col * ydelta, tempp ); } res = qMin( tmpres, res ); } p.end(); } if ( res && d->m_cacheEnabled ) { // delete all the cached pixmaps for the current page with a size that // differs no more than 35% of the new pixmap size int imgsize = newimg.width() * newimg.height(); if ( imgsize > 0 ) { for( int i = 0; i < d->mImgCache.count(); ) { ImageCacheItem* cur = d->mImgCache.at(i); if ( ( cur->page == page ) && ( abs( cur->img.width() * cur->img.height() - imgsize ) < imgsize * 0.35 ) ) { d->mImgCache.removeAt( i ); delete cur; } else ++i; } } // the image cache has too many elements, remove the last if ( d->mImgCache.size() >= 10 ) { delete d->mImgCache.last(); d->mImgCache.removeLast(); } ImageCacheItem* ich = new ImageCacheItem( page, width, height, newimg ); d->mImgCache.push_front( ich ); } return newimg; } bool KDjVu::exportAsPostScript( const QString & fileName, const QList& pageList ) const { if ( !d->m_djvu_document || fileName.trimmed().isEmpty() || pageList.isEmpty() ) return false; QFile f( fileName ); f.open( QIODevice::ReadWrite ); bool ret = exportAsPostScript( &f, pageList ); if ( ret ) { f.close(); } return ret; } bool KDjVu::exportAsPostScript( QFile* file, const QList& pageList ) const { if ( !d->m_djvu_document || !file || pageList.isEmpty() ) return false; FILE* f = fdopen( file->handle(), "w+" ); if ( !f ) { qDebug() << "error while getting the FILE*"; return false; } QString pl; foreach ( int p, pageList ) { if ( !pl.isEmpty() ) pl += QLatin1String( "," ); pl += QString::number( p ); } pl.prepend( QStringLiteral ( "-page=" ) ); // setting the options static const int optc = 1; const char ** optv = (const char**)malloc( 1 * sizeof( char* ) ); QByteArray plb = pl.toLatin1(); optv[0] = plb.constData(); ddjvu_job_t *printjob = ddjvu_document_print( d->m_djvu_document, f, optc, optv ); while ( !ddjvu_job_done( printjob ) ) handle_ddjvu_messages( d->m_djvu_cxt, true ); free( optv ); return fclose( f ) == 0; } QList KDjVu::textEntities( int page, const QString & granularity ) const { if ( ( page < 0 ) || ( page >= d->m_pages.count() ) ) return QList(); miniexp_t r; while ( ( r = ddjvu_document_get_pagetext( d->m_djvu_document, page, nullptr ) ) == miniexp_dummy ) handle_ddjvu_messages( d->m_djvu_cxt, true ); if ( r == miniexp_nil ) return QList(); QList ret; int height = d->m_pages.at( page )->height(); QQueue queue; queue.enqueue( r ); while ( !queue.isEmpty() ) { miniexp_t cur = queue.dequeue(); if ( miniexp_listp( cur ) && ( miniexp_length( cur ) > 0 ) && miniexp_symbolp( miniexp_nth( 0, cur ) ) ) { int size = miniexp_length( cur ); QString sym = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, cur ) ) ); if ( sym == granularity ) { if ( size >= 6 ) { int xmin = miniexp_to_int( miniexp_nth( 1, cur ) ); int ymin = miniexp_to_int( miniexp_nth( 2, cur ) ); int xmax = miniexp_to_int( miniexp_nth( 3, cur ) ); int ymax = miniexp_to_int( miniexp_nth( 4, cur ) ); QRect rect( xmin, height - ymax, xmax - xmin, ymax - ymin ); KDjVu::TextEntity entity; entity.m_rect = rect; entity.m_text = QString::fromUtf8( miniexp_to_str( miniexp_nth( 5, cur ) ) ); ret.append( entity ); } } else { for ( int i = 5; i < size; ++i ) queue.enqueue( miniexp_nth( i, cur ) ); } } } return ret; } void KDjVu::setCacheEnabled( bool enable ) { if ( enable == d->m_cacheEnabled ) return; d->m_cacheEnabled = enable; if ( !d->m_cacheEnabled ) { qDeleteAll( d->mImgCache ); d->mImgCache.clear(); } } bool KDjVu::isCacheEnabled() const { return d->m_cacheEnabled; } int KDjVu::pageNumber( const QString & name ) const { if ( !d->m_djvu_document ) return -1; return d->pageWithName( name ); } diff --git a/generators/dvi/TeXFontDefinition.cpp b/generators/dvi/TeXFontDefinition.cpp index f15d8d1b0..78d7eda90 100644 --- a/generators/dvi/TeXFontDefinition.cpp +++ b/generators/dvi/TeXFontDefinition.cpp @@ -1,250 +1,250 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- #include #include "TeXFontDefinition.h" #include "dviRenderer.h" #include "debug_dvi.h" #ifdef HAVE_FREETYPE # include "TeXFont_PFB.h" #endif #include "TeXFont_PK.h" #include "TeXFont_TFM.h" #include "xdvi.h" #include "debug_dvi.h" #include -#include +#include #include extern const int MFResolutions[]; #define PK_PRE 247 #define PK_ID 89 #define PK_MAGIC (PK_PRE << 8) + PK_ID #define GF_PRE 247 #define GF_ID 131 #define GF_MAGIC (GF_PRE << 8) + GF_ID #define VF_PRE 247 #define VF_ID_BYTE 202 #define VF_MAGIC (VF_PRE << 8) + VF_ID_BYTE // #define DEBUG_FONT TeXFontDefinition::TeXFontDefinition(const QString &nfontname, double _displayResolution_in_dpi, quint32 chk, qint32 _scaled_size_in_DVI_units, class fontPool *pool, double _enlargement) { #ifdef DEBUG_FONT qCDebug(OkularDviDebug) << "TeXFontDefinition::TeXFontDefinition(...); fontname=" << nfontname << ", enlargement=" << _enlargement; #endif enlargement = _enlargement; font_pool = pool; fontname = nfontname; font = nullptr; displayResolution_in_dpi = _displayResolution_in_dpi; checksum = chk; flags = TeXFontDefinition::FONT_IN_USE; file = nullptr; filename.clear(); scaled_size_in_DVI_units = _scaled_size_in_DVI_units; macrotable = nullptr; // By default, this font contains only empty characters. After the // font has been loaded, this function pointer will be replaced by // another one. set_char_p = &dviRenderer::set_empty_char; } TeXFontDefinition::~TeXFontDefinition() { #ifdef DEBUG_FONT qCDebug(OkularDviDebug) << "discarding font " << fontname << " at " << (int)(enlargement * MFResolutions[font_pool->getMetafontMode()] + 0.5) << " dpi"; #endif if (font != nullptr) { delete font; font = nullptr; } if (macrotable != nullptr) { delete [] macrotable; macrotable = nullptr; } if (flags & FONT_LOADED) { if (file != nullptr) { fclose(file); file = nullptr; } if (flags & FONT_VIRTUAL) vf_table.clear(); } } void TeXFontDefinition::fontNameReceiver(const QString& fname) { #ifdef DEBUG_FONT qCDebug(OkularDviDebug) << "void TeXFontDefinition::fontNameReceiver( " << fname << " )"; #endif flags |= TeXFontDefinition::FONT_LOADED; filename = fname; #ifdef HAVE_FREETYPE fullFontName.clear(); fullEncodingName.clear(); #endif file = fopen(QFile::encodeName(filename).constData(), "r"); // Check if the file could be opened. If not, try to find the file // in the DVI file's directory. If that works, modify the filename // accordingly and go on. if (file == nullptr) { QString filename_test(font_pool->getExtraSearchPath() + QLatin1Char('/') + filename); file = fopen( QFile::encodeName(filename_test).constData(), "r"); if (file == nullptr) { qCCritical(OkularDviDebug) << i18n("Cannot find font %1, file %2.", fontname, filename) << endl; return; } else filename = filename_test; } set_char_p = &dviRenderer::set_char; int magic = two(file); if (fname.endsWith(QLatin1String("pk"))) if (magic == PK_MAGIC) { fclose(file); file = nullptr; font = new TeXFont_PK(this); set_char_p = &dviRenderer::set_char; if ((checksum != 0) && (checksum != font->checksum)) qCWarning(OkularDviDebug) << i18n("Checksum mismatch for font file %1", filename) ; fontType = TEX_PK; return; } if (fname.endsWith(QLatin1String(".vf"))) if (magic == VF_MAGIC) { read_VF_index(); set_char_p = &dviRenderer::set_vf_char; fontType = TEX_VIRTUAL; return; } if (fname.endsWith(QLatin1String(".tfm"))) { fclose(file); file = nullptr; font = new TeXFont_TFM(this); set_char_p = &dviRenderer::set_char; fontType = TEX_FONTMETRIC; return; } // None of these known types? Then it should be one of the font // formats that are handled by the FreeType library fclose(file); file = nullptr; #ifdef HAVE_FREETYPE // Find the encoding for that font const QString &enc = font_pool->fontsByTeXName.findEncoding(fontname); if (enc.isEmpty() == false) { #ifdef DEBUG_FONT qCDebug(OkularDviDebug) << "Font " << fontname << " uses encoding " << enc; #endif font = new TeXFont_PFB(this, font_pool->encodingPool.findByName(enc), font_pool->fontsByTeXName.findSlant(fontname) ); } else { #ifdef DEBUG_FONT qCDebug(OkularDviDebug) << "Font " << fontname << " does not have an encoding."; #endif font = new TeXFont_PFB(this); } set_char_p = &dviRenderer::set_char; fontType = FREETYPE; return; #else // If we don't have the FreeType library, we should never have // reached this point. Complain, and leave this font blank qCCritical(OkularDviDebug) << i18n("Cannot recognize format for font file %1", filename) << endl; #endif } void TeXFontDefinition::reset() { if (font != nullptr) { delete font; font = nullptr; } if (macrotable != nullptr) { delete [] macrotable; macrotable = nullptr; } if (flags & FONT_LOADED) { if (file != nullptr) { fclose(file); file = nullptr; } if (flags & FONT_VIRTUAL) vf_table.clear(); } filename.clear(); flags = TeXFontDefinition::FONT_IN_USE; set_char_p = &dviRenderer::set_empty_char; } void TeXFontDefinition::setDisplayResolution(double _displayResolution_in_dpi) { displayResolution_in_dpi = _displayResolution_in_dpi; if (font != nullptr) font->setDisplayResolution(); } /** mark_as_used marks the font, and all the fonts it referrs to, as used, i.e. their FONT_IN_USE-flag is set. */ void TeXFontDefinition::mark_as_used() { #ifdef DEBUG_FONT qCDebug(OkularDviDebug) << "TeXFontDefinition::mark_as_used()"; #endif if (flags & TeXFontDefinition::FONT_IN_USE) return; flags |= TeXFontDefinition::FONT_IN_USE; // For virtual fonts, also go through the list of referred fonts if (flags & TeXFontDefinition::FONT_VIRTUAL) { QHashIterator it(vf_table); while( it.hasNext() ) { it.next(); it.value()->flags |= TeXFontDefinition::FONT_IN_USE; } } } macro::macro() { pos = nullptr; /* address of first byte of macro */ end = nullptr; /* address of last+1 byte */ dvi_advance_in_units_of_design_size_by_2e20 = 0; /* DVI units to move reference point */ free_me = false; } macro::~macro() { if ((pos != nullptr) && (free_me == true)) delete [] pos; } diff --git a/generators/dvi/TeXFontDefinition.h b/generators/dvi/TeXFontDefinition.h index 4d0789281..0b209865a 100644 --- a/generators/dvi/TeXFontDefinition.h +++ b/generators/dvi/TeXFontDefinition.h @@ -1,147 +1,147 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- /* * The layout of a font information block. * There is one of these for every loaded font or magnification thereof. * Duplicates are eliminated: this is necessary because of possible recursion * in virtual fonts. * * Also note the strange units. The design size is in 1/2^20 point * units (also called micro-points), and the individual character widths * are in the TFM file in 1/2^20 ems units, i.e., relative to the design size. * * We then change the sizes to SPELL units (unshrunk pixel / 2^16). */ #ifndef _FONT_H #define _FONT_H #include -#include +#include class dviRenderer; class TeXFont; typedef void (dviRenderer::*set_char_proc)(unsigned int, unsigned int); // Per character information for virtual fonts class macro { public: macro(); ~macro(); // address of first byte of macro unsigned char* pos; // address of last+1 byte unsigned char* end; // DVI units to move reference point qint32 dvi_advance_in_units_of_design_size_by_2e20; // if memory at pos should be returned on destruction bool free_me; }; class TeXFontDefinition { public: // Currently, kdvi supports fonts with at most 256 characters to // comply with "The DVI Driver Standard, Level 0". If you change // this value here, make sure to go through all the source and // ensure that character numbers are stored in ints rather than // unsigned chars. static const unsigned int max_num_of_chars_in_font = 256; enum font_flags { // used for housekeeping FONT_IN_USE = 1, // if font file has been read FONT_LOADED = 2, // if font is virtual FONT_VIRTUAL = 4, // if kpathsea has already tried to find the font name FONT_KPSE_NAME = 8 }; enum font_type { TEX_PK, TEX_VIRTUAL, TEX_FONTMETRIC, FREETYPE }; TeXFontDefinition(const QString &nfontname, double _displayResolution_in_dpi, quint32 chk, qint32 _scaled_size_in_DVI_units, class fontPool *pool, double _enlargement); ~TeXFontDefinition(); void reset(); void fontNameReceiver(const QString&); // Members for character fonts void setDisplayResolution(double _displayResolution_in_dpi); bool isLocated() const {return ((flags & FONT_KPSE_NAME) != 0);} void markAsLocated() {flags |= FONT_KPSE_NAME;} void mark_as_used(); // Pointer to the pool that contains this font. class fontPool *font_pool; // name of font, such as "cmr10" QString fontname; // flags byte (see values below) unsigned char flags; double enlargement; // Scaled size from the font definition command; in DVI units qint32 scaled_size_in_DVI_units; // proc used to set char set_char_proc set_char_p; // Resolution of the display device (resolution will usually be // scaled, according to the zoom) double displayResolution_in_dpi; // open font file or NULL FILE* file; // name of font file QString filename; TeXFont* font; // used by (loaded) virtual fonts macro* macrotable; // used by (loaded) virtual fonts, list of fonts used by this vf, QHash vf_table; // acessible by number // used by (loaded) virtual fonts, list of fonts used by this vf TeXFontDefinition* first_font; #ifdef HAVE_FREETYPE const QString &getFullFontName() const {return fullFontName;} const QString &getFullEncodingName() const {return fullEncodingName;} #endif const font_type &getFontType() const {return fontType;}; #ifdef HAVE_FREETYPE /** For FREETYPE fonts, which use a map file, this field will contain the full name of the font (e.g. 'Computer Modern'). If the name does not exist, or cannot be found, this field will be QString(). Only subclasses of TeXFont should write into this field. */ QString fullFontName; /** For FREETYPE fonts, which use a map file, this field will contain the full name of the font encoding (e.g. 'TexBase1'). If the encoding name does not exist, or cannot be found, this field will be QString(). Only subclasses of TeXFont should write into this field. */ QString fullEncodingName; #endif private: quint32 checksum; font_type fontType; // Functions related to virtual fonts void read_VF_index(void ); }; #endif diff --git a/generators/dvi/TeXFont_PFB.cpp b/generators/dvi/TeXFont_PFB.cpp index 38cc7d1e6..d3cac2fed 100644 --- a/generators/dvi/TeXFont_PFB.cpp +++ b/generators/dvi/TeXFont_PFB.cpp @@ -1,293 +1,293 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // TeXFont_PFB.cpp // // Part of KDVI - A DVI previewer for the KDE desktop environment // // (C) 2003 Stefan Kebekus // Distributed under the GPL // This file is compiled only if the FreeType library is present on // the system #include #ifdef HAVE_FREETYPE #include "TeXFont_PFB.h" #include "fontpool.h" #include "debug_dvi.h" #include "debug_dvi.h" #include -#include +#include #include //#define DEBUG_PFB 1 TeXFont_PFB::TeXFont_PFB(TeXFontDefinition *parent, fontEncoding *enc, double slant) : TeXFont(parent), face(nullptr) { #ifdef DEBUG_PFB if (enc != 0) qCDebug(OkularDviDebug) << "TeXFont_PFB::TeXFont_PFB( parent=" << parent << ", encoding=" << enc->encodingFullName << " )"; else qCDebug(OkularDviDebug) << "TeXFont_PFB::TeXFont_PFB( parent=" << parent << ", encoding=0 )"; #endif fatalErrorInFontLoading = false; int error = FT_New_Face( parent->font_pool->FreeType_library, parent->filename.toLocal8Bit().constData(), 0, &face ); if ( error == FT_Err_Unknown_File_Format ) { errorMessage = i18n("The font file %1 could be opened and read, but its font format is unsupported.", parent->filename); qCCritical(OkularDviDebug) << errorMessage << endl; fatalErrorInFontLoading = true; return; } else if ( error ) { errorMessage = i18n("The font file %1 is broken, or it could not be opened or read.", parent->filename); qCCritical(OkularDviDebug) << errorMessage << endl; fatalErrorInFontLoading = true; return; } // Take care of slanting, and transform all characters in the font, if necessary. if (slant != 0.0) { // Construct a transformation matrix for vertical shear which will // be used to transform the characters. transformationMatrix.xx = 0x10000; transformationMatrix.xy = (FT_Fixed)(slant * 0x10000); transformationMatrix.yx = 0; transformationMatrix.yy = 0x10000; FT_Set_Transform( face, &transformationMatrix, nullptr); } if (face->family_name != nullptr) parent->fullFontName = QString::fromLocal8Bit(face->family_name); // Finally, we need to set up the charMap array, which maps TeX // character codes to glyph indices in the font. (Remark: the // charMap, and the font encoding procedure is necessary, because // TeX is only able to address character codes 0-255 while // e.g. Type1 fonts may contain several thousands of characters) if (enc != nullptr) { parent->fullEncodingName = enc->encodingFullName.remove(QStringLiteral( "Encoding" )); parent->fullEncodingName = enc->encodingFullName.remove(QStringLiteral( "encoding" )); // An encoding vector is given for this font, i.e. an array of // character names (such as: 'parenleft' or 'dotlessj'). We use // the FreeType library function 'FT_Get_Name_Index()' to // associate glyph indices to those names. #ifdef DEBUG_PFB qCDebug(OkularDviDebug) << "Trying to associate glyph indices to names from the encoding vector."; #endif for(int i=0; i<256; i++) { charMap[i] = FT_Get_Name_Index( face, (FT_String *)(enc->glyphNameVector[i].toLatin1().data()) ); #ifdef DEBUG_PFB qCDebug(OkularDviDebug) << i << ": " << enc->glyphNameVector[i] << ", GlyphIndex=" << charMap[i]; #endif } } else { // If there is no encoding vector available, we check if the font // itself contains a charmap that could be used. An admissible // charMap will be stored under platform_id=7 and encoding_id=2. FT_CharMap found = nullptr; for (int n = 0; nnum_charmaps; n++ ) { FT_CharMap charmap = face->charmaps[n]; if ( charmap->platform_id == 7 && charmap->encoding_id == 2 ) { found = charmap; break; } } if ((found != nullptr) && (FT_Set_Charmap( face, found ) == 0)) { // Feed the charMap array with the charmap data found in the // previous step. #ifdef DEBUG_PFB qCDebug(OkularDviDebug) << "No encoding given: using charmap platform=7, encoding=2 that is contained in the font."; #endif for(int i=0; i<256; i++) charMap[i] = FT_Get_Char_Index( face, i ); } else { if ((found == nullptr) && (face->charmap != nullptr)) { #ifdef DEBUG_PFB qCDebug(OkularDviDebug) << "No encoding given: using charmap platform=" << face->charmap->platform_id << ", encoding=" << face->charmap->encoding_id << " that is contained in the font." << endl; #endif for(int i=0; i<256; i++) charMap[i] = FT_Get_Char_Index( face, i ); } else { // As a last resort, we use the identity map. #ifdef DEBUG_PFB qCDebug(OkularDviDebug) << "No encoding given, no suitable charmaps found in the font: using identity charmap."; #endif for(int i=0; i<256; i++) charMap[i] = i; } } } } TeXFont_PFB::~TeXFont_PFB() { FT_Done_Face( face ); } glyph* TeXFont_PFB::getGlyph(quint16 ch, bool generateCharacterPixmap, const QColor& color) { #ifdef DEBUG_PFB qCDebug(OkularDviDebug) << "TeXFont_PFB::getGlyph( ch=" << ch << ", '" << (char)(ch) << "', generateCharacterPixmap=" << generateCharacterPixmap << " )"; #endif // Paranoia checks if (ch >= TeXFontDefinition::max_num_of_chars_in_font) { qCCritical(OkularDviDebug) << "TeXFont_PFB::getGlyph(): Argument is too big." << endl; return glyphtable; } // This is the address of the glyph that will be returned. glyph *g = glyphtable+ch; if (fatalErrorInFontLoading == true) return g; if ((generateCharacterPixmap == true) && ((g->shrunkenCharacter.isNull()) || (color != g->color)) ) { int error; unsigned int res = (unsigned int)(parent->displayResolution_in_dpi/parent->enlargement +0.5); g->color = color; // Character height in 1/64th of points (reminder: 1 pt = 1/72 inch) // Only approximate, may vary from file to file!!!! @@@@@ long int characterSize_in_printers_points_by_64 = (long int)((64.0*72.0*parent->scaled_size_in_DVI_units*parent->font_pool->getCMperDVIunit())/2.54 + 0.5 ); error = FT_Set_Char_Size(face, 0, characterSize_in_printers_points_by_64, res, res ); if (error) { QString msg = i18n("FreeType reported an error when setting the character size for font file %1.", parent->filename); if (errorMessage.isEmpty()) errorMessage = msg; qCCritical(OkularDviDebug) << msg << endl; g->shrunkenCharacter = QImage(1, 1, QImage::Format_RGB32); g->shrunkenCharacter.fill(qRgb(255, 255, 255)); return g; } // load glyph image into the slot and erase the previous one if (parent->font_pool->getUseFontHints() == true) error = FT_Load_Glyph(face, charMap[ch], FT_LOAD_DEFAULT ); else error = FT_Load_Glyph(face, charMap[ch], FT_LOAD_NO_HINTING ); if (error) { QString msg = i18n("FreeType is unable to load glyph #%1 from font file %2.", ch, parent->filename); if (errorMessage.isEmpty()) errorMessage = msg; qCCritical(OkularDviDebug) << msg << endl; g->shrunkenCharacter = QImage(1, 1, QImage::Format_RGB32); g->shrunkenCharacter.fill(qRgb(255, 255, 255)); return g; } // convert to an anti-aliased bitmap error = FT_Render_Glyph( face->glyph, ft_render_mode_normal ); if (error) { QString msg = i18n("FreeType is unable to render glyph #%1 from font file %2.", ch, parent->filename); if (errorMessage.isEmpty()) errorMessage = msg; qCCritical(OkularDviDebug) << msg << endl; g->shrunkenCharacter = QImage(1, 1, QImage::Format_RGB32); g->shrunkenCharacter.fill(qRgb(255, 255, 255)); return g; } FT_GlyphSlot slot = face->glyph; if ((slot->bitmap.width == 0) || (slot->bitmap.rows == 0)) { if (errorMessage.isEmpty()) errorMessage = i18n("Glyph #%1 is empty.", ch); qCCritical(OkularDviDebug) << i18n("Glyph #%1 from font file %2 is empty.", ch, parent->filename) << endl; g->shrunkenCharacter = QImage(15, 15 , QImage::Format_RGB32); g->shrunkenCharacter.fill(qRgb(255, 0, 0)); g->x2 = 0; g->y2 = 15; } else { QImage imgi(slot->bitmap.width, slot->bitmap.rows, QImage::Format_ARGB32); // Do QPixmaps fully support the alpha channel? If yes, we use // that. Otherwise, use other routines as a fallback if (parent->font_pool->QPixmapSupportsAlpha) { // If the alpha channel is properly supported, we set the // character glyph to a colored rectangle, and define the // character outline only using the alpha channel. That // ensures good quality rendering for overlapping characters. uchar *srcScanLine = slot->bitmap.buffer; for(unsigned int row=0; rowbitmap.rows; row++) { uchar *destScanLine = imgi.scanLine(row); for(unsigned int col=0; colbitmap.width; col++) { destScanLine[4*col+0] = color.blue(); destScanLine[4*col+1] = color.green(); destScanLine[4*col+2] = color.red(); destScanLine[4*col+3] = srcScanLine[col]; } srcScanLine += slot->bitmap.pitch; } } else { // If the alpha channel is not supported... QT seems to turn // the alpha channel into a crude bitmap which is used to mask // the resulting QPixmap. In this case, we define the // character outline using the image data, and use the alpha // channel only to store "maximally opaque" or "completely // transparent" values. When characters are rendered, // overlapping characters are no longer correctly drawn, but // quality is still sufficient for most purposes. One notable // exception is output from the gftodvi program, which will be // partially unreadable. quint16 rInv = 0xFF - color.red(); quint16 gInv = 0xFF - color.green(); quint16 bInv = 0xFF - color.blue(); for(quint16 y=0; ybitmap.rows; y++) { quint8 *srcScanLine = slot->bitmap.buffer + y*slot->bitmap.pitch; unsigned int *destScanLine = (unsigned int *)imgi.scanLine(y); for(quint16 col=0; colbitmap.width; col++) { quint16 data = *srcScanLine; // The value stored in "data" now has the following meaning: // data = 0 -> white; data = 0xff -> use "color" *destScanLine = qRgba(0xFF - (rInv*data + 0x7F) / 0xFF, 0xFF - (gInv*data + 0x7F) / 0xFF, 0xFF - (bInv*data + 0x7F) / 0xFF, (data > 0x03) ? 0xff : 0x00); destScanLine++; srcScanLine++; } } } g->shrunkenCharacter = imgi; g->x2 = -slot->bitmap_left; g->y2 = slot->bitmap_top; } } // Load glyph width, if that hasn't been done yet. if (g->dvi_advance_in_units_of_design_size_by_2e20 == 0) { int error = FT_Load_Glyph(face, charMap[ch], FT_LOAD_NO_SCALE); if (error) { QString msg = i18n("FreeType is unable to load metric for glyph #%1 from font file %2.", ch, parent->filename); if (errorMessage.isEmpty()) errorMessage = msg; qCCritical(OkularDviDebug) << msg << endl; g->dvi_advance_in_units_of_design_size_by_2e20 = 1; } g->dvi_advance_in_units_of_design_size_by_2e20 = (qint32)(((qint64)(1<<20) * (qint64)face->glyph->metrics.horiAdvance) / (qint64)face->units_per_EM); } return g; } #endif // HAVE_FREETYPE diff --git a/generators/dvi/TeXFont_TFM.cpp b/generators/dvi/TeXFont_TFM.cpp index a55c6deaf..14a31fdcb 100644 --- a/generators/dvi/TeXFont_TFM.cpp +++ b/generators/dvi/TeXFont_TFM.cpp @@ -1,161 +1,161 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // TeXFont_TFM.cpp // // Part of KDVI - A DVI previewer for the KDE desktop environment // // (C) 2003 Stefan Kebekus // Distributed under the GPL #include #include "TeXFont_TFM.h" #include "debug_dvi.h" #include "debug_dvi.h" -#include +#include #include #include //#define DEBUG_TFM TeXFont_TFM::TeXFont_TFM(TeXFontDefinition *parent) : TeXFont(parent) { #ifdef DEBUG_TFM qCDebug(OkularDviDebug) << "TeXFont_TFM::TeXFont_TFM( parent=" << parent << " )"; #endif QFile file( parent->filename ); if ( !file.open( QIODevice::ReadOnly ) ) { qCCritical(OkularDviDebug) << "TeXFont_TFM::TeXFont_TFM(): Could not read TFM file" << endl; return; } QDataStream stream( &file ); // Data from the very beginning of the TFM file, as specified in // "The DVI Driver Standard, Level 0", section D.2.1 quint16 lf, lh, bc, ec, nw, nh, nd; stream >> lf >> lh >> bc >> ec >> nw >> nh >> nd; #ifdef DEBUG_TFM qCDebug(OkularDviDebug) << "lf= " << lf << "lh= " << lh << endl << "bc= " << bc << endl << "ec= " << ec << endl << "nw= " << nw << endl << "nh= " << nh << endl << "nd= " << nd << endl; #endif if ((bc > ec) || (ec >= TeXFontDefinition::max_num_of_chars_in_font)) { qCCritical(OkularDviDebug) << "TeXFont_TFM::TeXFont_TFM( filename=" << parent->filename << " ): The font has an invalid bc and ec entries." << endl; file.close(); return; } // Data from the HEADER section of the TFM data. file.seek(24); stream >> checksum >> design_size_in_TeX_points.value; #ifdef DEBUG_TFM qCDebug(OkularDviDebug) << "checksum = " << checksum << "design_size = " << design_size_in_TeX_points.toDouble() << " TeX Points" << endl << " = " << design_size_in_TeX_points.toDouble()*254.0/7227.0 << " cm" << endl; #endif // Width table fix_word widthTable_in_units_of_design_size[TeXFontDefinition::max_num_of_chars_in_font]; for(unsigned int i=0; i> widthTable_in_units_of_design_size[i].value; // Some characters, which are used as parts of glyphs, have width // 0 --the real width is caculated in a lig_kern program and // depends on the preceding character. We cannot calculate the // real width here and take 0.4 times the design size as an // approximation. if (widthTable_in_units_of_design_size[i].value == 0) widthTable_in_units_of_design_size[i].fromDouble(0.4); } // Height table fix_word heightTable_in_units_of_design_size[16]; for(unsigned int i=0; i<16; i++) heightTable_in_units_of_design_size[i].value = 0; for(unsigned int i=0; i> heightTable_in_units_of_design_size[i].value; } // Char-Info table file.seek( 24 + 4*lh ); for(unsigned int characterCode=bc; characterCode> byte; if (byte >= nw) qCCritical(OkularDviDebug) << "TeXFont_TFM::TeXFont_TFM( filename=" << parent->filename << " ): The font has an invalid Char-Info table." << endl; else { characterWidth_in_units_of_design_size[characterCode] = widthTable_in_units_of_design_size[byte]; g->dvi_advance_in_units_of_design_size_by_2e20 = widthTable_in_units_of_design_size[byte].value; } stream >> byte; byte = byte >> 4; if (byte >= nh) qCCritical(OkularDviDebug) << "TeXFont_TFM::TeXFont_TFM( filename=" << parent->filename << " ): The font has an invalid Char-Info table." << endl; else characterHeight_in_units_of_design_size[characterCode] = heightTable_in_units_of_design_size[byte]; stream >> byte; stream >> byte; } file.close(); } TeXFont_TFM::~TeXFont_TFM() { } glyph* TeXFont_TFM::getGlyph(quint16 characterCode, bool generateCharacterPixmap, const QColor& color) { #ifdef DEBUG_TFM qCDebug(OkularDviDebug) << "TeXFont_TFM::getGlyph( ch=" << ch << ", generateCharacterPixmap=" << generateCharacterPixmap << " )"; #endif // Paranoia checks if (characterCode >= TeXFontDefinition::max_num_of_chars_in_font) { qCCritical(OkularDviDebug) << "TeXFont_TFM::getGlyph(): Argument is too big." << endl; return glyphtable; } // This is the address of the glyph that will be returned. class glyph *g = glyphtable+characterCode; if ((generateCharacterPixmap == true) && ((g->shrunkenCharacter.isNull()) || (color != g->color)) ) { g->color = color; quint16 pixelWidth = (quint16)(parent->displayResolution_in_dpi * design_size_in_TeX_points.toDouble() * characterWidth_in_units_of_design_size[characterCode].toDouble() * 100.0/7227.0 + 0.5); quint16 pixelHeight = (quint16)(parent->displayResolution_in_dpi * design_size_in_TeX_points.toDouble() * characterHeight_in_units_of_design_size[characterCode].toDouble() * 100.0/7227.0 + 0.5); // Just make sure that weired TFM files never lead to giant // pixmaps that eat all system memory... if (pixelWidth > 50) pixelWidth = 50; if (pixelHeight > 50) pixelHeight = 50; g->shrunkenCharacter = QImage(pixelWidth, pixelHeight, QImage::Format_RGB32); g->shrunkenCharacter.fill(color.rgba()); g->x2 = 0; g->y2 = pixelHeight; } return g; } diff --git a/generators/dvi/debug_dvi.h b/generators/dvi/debug_dvi.h index cae7b2f56..16b650795 100644 --- a/generators/dvi/debug_dvi.h +++ b/generators/dvi/debug_dvi.h @@ -1,19 +1,19 @@ /*************************************************************************** * Copyright (C) 2006 by Luigi Toscano * * Copyright (C) 2014 by Frederik Gladhorn * * * * 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_DVI_DEBUG_P_H #define OKULAR_DVI_DEBUG_P_H -#include +#include Q_DECLARE_LOGGING_CATEGORY(OkularDviDebug) Q_DECLARE_LOGGING_CATEGORY(OkularDviShellDebug) #endif diff --git a/generators/dvi/dviFile.cpp b/generators/dvi/dviFile.cpp index 0874ca667..75dfe7496 100644 --- a/generators/dvi/dviFile.cpp +++ b/generators/dvi/dviFile.cpp @@ -1,446 +1,446 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- /* * Copyright (c) 1994 Paul Vojta. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * NOTE: * xdvi is based on prior work as noted in the modification history, below. */ /* * DVI previewer for X. * * Eric Cooper, CMU, September 1985. * * Code derived from dvi-imagen.c. * * Modification history: * 1/1986 Modified for X.10 --Bob Scheifler, MIT LCS. * 7/1988 Modified for X.11 --Mark Eichin, MIT * 12/1988 Added 'R' option, toolkit, magnifying glass * --Paul Vojta, UC Berkeley. * 2/1989 Added tpic support --Jeffrey Lee, U of Toronto * 4/1989 Modified for System V --Donald Richardson, Clarkson Univ. * 3/1990 Added VMS support --Scott Allendorf, U of Iowa * 7/1990 Added reflection mode --Michael Pak, Hebrew U of Jerusalem * 1/1992 Added greyscale code --Till Brychcy, Techn. Univ. Muenchen * and Lee Hetherington, MIT * 4/1994 Added DPS support, bounding box * --Ricardo Telichevesky * and Luis Miguel Silveira, MIT RLE. */ #include #include "debug_dvi.h" #include "dviFile.h" #include "dvi.h" #include "fontpool.h" #include "debug_dvi.h" #include "pageSize.h" #include -#include +#include #include #include #include #include dvifile::dvifile(const dvifile *old, fontPool *fp) { errorMsg.clear(); errorCounter = 0; page_offset.clear(); suggestedPageSize = nullptr; numberOfExternalPSFiles = 0; numberOfExternalNONPSFiles = 0; sourceSpecialMarker = old->sourceSpecialMarker; have_complainedAboutMissingPDF2PS = false; dviData = old->dviData; filename = old->filename; size_of_file = old->size_of_file; end_pointer = dvi_Data()+size_of_file; if (dvi_Data() == nullptr) { qCCritical(OkularDviDebug) << "Not enough memory to copy the DVI-file." << endl; return; } font_pool = fp; filename = old->filename; generatorString = old->generatorString; total_pages = old->total_pages; tn_table.clear(); process_preamble(); find_postamble(); read_postamble(); prepare_pages(); } void dvifile::process_preamble() { command_pointer = dvi_Data(); quint8 magic_number = readUINT8(); if (magic_number != PRE) { errorMsg = i18n("The DVI file does not start with the preamble."); return; } magic_number = readUINT8(); if (magic_number != 2) { errorMsg = i18n("The DVI file contains the wrong version of DVI output for this program. " "Hint: If you use the typesetting system Omega, you have to use a special " "program, such as oxdvi."); return; } /** numerator, denominator and the magnification value that describe how many centimeters there are in one TeX unit, as explained in section A.3 of the DVI driver standard, Level 0, published by the TUG DVI driver standards committee. */ quint32 numerator = readUINT32(); quint32 denominator = readUINT32(); _magnification = readUINT32(); cmPerDVIunit = (double(numerator) / double(denominator)) * (double(_magnification) / 1000.0) * (1.0 / 1e5); // Read the generatorString (such as "TeX output ..." from the // DVI-File). The variable "magic_number" holds the length of the // string. char job_id[300]; magic_number = readUINT8(); strncpy(job_id, (char *)command_pointer, magic_number); job_id[magic_number] = '\0'; generatorString = QString::fromLocal8Bit(job_id); } /** find_postamble locates the beginning of the postamble and leaves the file ready to start reading at that location. */ void dvifile::find_postamble() { // Move backwards through the TRAILER bytes command_pointer = dvi_Data() + size_of_file - 1; while((*command_pointer == TRAILER) && (command_pointer > dvi_Data())) command_pointer--; if (command_pointer == dvi_Data()) { errorMsg = i18n("The DVI file is badly corrupted. Okular was not able to find the postamble."); return; } // And this is finally the pointer to the beginning of the postamble command_pointer -= 4; beginning_of_postamble = readUINT32(); command_pointer = dvi_Data() + beginning_of_postamble; } void dvifile::read_postamble() { quint8 magic_byte = readUINT8(); if (magic_byte != POST) { errorMsg = i18n("The postamble does not begin with the POST command."); return; } last_page_offset = readUINT32(); // Skip the numerator, denominator and magnification, the largest // box height and width and the maximal depth of the stack. These // are not used at the moment. command_pointer += 4 + 4 + 4 + 4 + 4 + 2; // The number of pages is more interesting for us. total_pages = readUINT16(); // As a next step, read the font definitions. quint8 cmnd = readUINT8(); while (cmnd >= FNTDEF1 && cmnd <= FNTDEF4) { quint32 TeXnumber = readUINT(cmnd-FNTDEF1+1); quint32 checksum = readUINT32(); // Checksum of the font, as found by TeX in the TFM file // Read scale and design factor, and the name of the font. All // these are explained in section A.4 of the DVI driver standard, // Level 0, published by the TUG DVI driver standards committee quint32 scale = readUINT32(); quint32 design = readUINT32(); quint16 len = readUINT8() + readUINT8(); // Length of the font name, including the directory name QByteArray fontname((char*)command_pointer, len); command_pointer += len; #ifdef DEBUG_FONTS qCDebug(OkularDviDebug) << "Postamble: define font \"" << fontname << "\" scale=" << scale << " design=" << design; #endif // According to section A.4 of the DVI driver standard, this font // shall be enlarged by the following factor before it is used. double enlargement_factor = (double(scale) * double(_magnification))/(double(design) * 1000.0); if (font_pool != nullptr) { TeXFontDefinition *fontp = font_pool->appendx(QString::fromLocal8Bit(fontname), checksum, scale, enlargement_factor); // Insert font in dictionary and make sure the dictionary is big // enough. if (tn_table.capacity()-2 <= tn_table.count()) // Not quite optimal. The size of the dictionary should be a // prime for optimal performance. I don't care. tn_table.reserve(tn_table.capacity()*2); tn_table.insert(TeXnumber, fontp); } // Read the next command cmnd = readUINT8(); } if (cmnd != POSTPOST) { errorMsg = i18n("The postamble contained a command other than FNTDEF."); return; } // Now we remove all those fonts from the memory which are no longer // in use. if (font_pool != nullptr) font_pool->release_fonts(); } void dvifile::prepare_pages() { #ifdef DEBUG_DVIFILE qCDebug(OkularDviDebug) << "prepare_pages"; #endif if (total_pages == 0) return; page_offset.resize(total_pages+1); if (page_offset.size() < (total_pages+1)) { qCCritical(OkularDviDebug) << "No memory for page list!" << endl; return; } for(int i=0; i<=total_pages; i++) page_offset[i] = 0; page_offset[int(total_pages)] = beginning_of_postamble; int j = total_pages-1; page_offset[j] = last_page_offset; // Follow back pointers through pages in the DVI file, storing the // offsets in the page_offset table. while (j > 0) { command_pointer = dvi_Data() + page_offset[j--]; if (readUINT8() != BOP) { errorMsg = i18n("The page %1 does not start with the BOP command.", j+1); return; } command_pointer += 10 * 4; page_offset[j] = readUINT32(); if ((dvi_Data()+page_offset[j] < dvi_Data())||(dvi_Data()+page_offset[j] > dvi_Data()+size_of_file)) break; } } dvifile::dvifile(const QString& fname, fontPool* pool) { #ifdef DEBUG_DVIFILE qCDebug(OkularDviDebug) << "init_dvi_file: " << fname; #endif errorMsg.clear(); errorCounter = 0; page_offset.clear(); suggestedPageSize = nullptr; numberOfExternalPSFiles = 0; numberOfExternalNONPSFiles = 0; font_pool = pool; sourceSpecialMarker = true; have_complainedAboutMissingPDF2PS = false; QFile file(fname); filename = file.fileName(); file.open( QIODevice::ReadOnly ); size_of_file = file.size(); dviData.resize(size_of_file); // Sets the end pointer for the bigEndianByteReader so that the // whole memory buffer is readable end_pointer = dvi_Data()+size_of_file; if (dvi_Data() == nullptr) { qCCritical(OkularDviDebug) << "Not enough memory to load the DVI-file."; return; } file.read((char *)dvi_Data(), size_of_file); file.close(); if (file.error() != QFile::NoError) { qCCritical(OkularDviDebug) << "Could not load the DVI-file."; return; } tn_table.clear(); total_pages = 0; process_preamble(); find_postamble(); read_postamble(); prepare_pages(); return; } dvifile::~dvifile() { #ifdef DEBUG_DVIFILE qCDebug(OkularDviDebug) << "destroy dvi-file"; #endif // Delete converted PDF files QMapIterator i(convertedFiles); while (i.hasNext()) { i.next(); QFile::remove(i.value()); } if (suggestedPageSize != nullptr) delete suggestedPageSize; if (font_pool != nullptr) font_pool->mark_fonts_as_unused(); } void dvifile::renumber() { dviData.detach(); // Write the page number to the file, taking good care of byte // orderings. bool bigEndian = (QSysInfo::ByteOrder == QSysInfo::BigEndian); for(int i=1; i<=total_pages; i++) { quint8 *ptr = dviData.data() + page_offset[i-1]+1; quint8 *num = (quint8 *)&i; for(quint8 j=0; j<4; j++) if (bigEndian) { *(ptr++) = num[0]; *(ptr++) = num[1]; *(ptr++) = num[2]; *(ptr++) = num[3]; } else { *(ptr++) = num[3]; *(ptr++) = num[2]; *(ptr++) = num[1]; *(ptr++) = num[0]; } } } QString dvifile::convertPDFtoPS(const QString &PDFFilename, QString *converrorms) { // Check if the PDFFile is known QMap::Iterator it = convertedFiles.find(PDFFilename); if (it != convertedFiles.end()) { // PDF-File is known. Good. return it.value(); } // Get the name of a temporary file. // Must open the QTemporaryFile to access the name. QTemporaryFile tmpfile; tmpfile.open(); const QString convertedFileName = tmpfile.fileName(); tmpfile.close(); // Use pdf2ps to do the conversion QProcess pdf2ps; pdf2ps.setReadChannelMode(QProcess::MergedChannels); pdf2ps.start(QStringLiteral("pdf2ps"), QStringList() << PDFFilename << convertedFileName, QIODevice::ReadOnly|QIODevice::Text); if (!pdf2ps.waitForStarted()) { // Indicates that conversion failed, won't try again. convertedFiles[PDFFilename].clear(); if (converrorms != nullptr && !have_complainedAboutMissingPDF2PS) { *converrorms = i18n("

The external program pdf2ps could not be started. As a result, " "the PDF-file %1 could not be converted to PostScript. Some graphic elements in your " "document will therefore not be displayed.

" "

Possible reason: The program pdf2ps may not be installed " "on your system, or cannot be found in the current search path.

" "

What you can do: The program pdf2ps is normally " "contained in distributions of the ghostscript PostScript interpreter system. If " "ghostscript is not installed on your system, you could install it now. " "If you are sure that ghostscript is installed, try to use pdf2ps " "from the command line to check if it really works.

PATH: %2

", PDFFilename, QString::fromLocal8Bit(qgetenv("PATH"))); have_complainedAboutMissingPDF2PS = true; } return QString(); } // We wait here while the external program runs concurrently. pdf2ps.waitForFinished(-1); if (!QFile::exists(convertedFileName) || pdf2ps.exitCode() != 0) { // Indicates that conversion failed, won't try again. convertedFiles[PDFFilename].clear(); if (converrorms != nullptr) { const QString output = QString::fromLocal8Bit(pdf2ps.readAll()); *converrorms = i18n("

The PDF-file %1 could not be converted to PostScript. Some graphic elements in your " "document will therefore not be displayed.

" "

Possible reason: The file %1 might be broken, or might not be a PDF-file at all. " "This is the output of the pdf2ps program that Okular used:

" "

%2

", PDFFilename, output); } return QString(); } // Save name of converted file to buffer, so PDF file won't be // converted again, and files can be deleted when *this is // deconstructed. convertedFiles[PDFFilename] = convertedFileName; tmpfile.setAutoRemove(false); return convertedFileName; } bool dvifile::saveAs(const QString &filename) { if (dvi_Data() == nullptr) return false; QFile out(filename); if (out.open( QIODevice::WriteOnly ) == false) return false; if (out.write ( (char *)(dvi_Data()), size_of_file ) == -1) return false; out.close(); return true; } diff --git a/generators/dvi/dviFile.h b/generators/dvi/dviFile.h index 1f65ff09b..6a0b0b56a 100644 --- a/generators/dvi/dviFile.h +++ b/generators/dvi/dviFile.h @@ -1,153 +1,153 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // // Class: dviFile // // Class that represents a DVI file. Part of KDVI - A DVI previewing // plugin for kviewshell. // // (C) 2004-2005 Stefan Kebekus. Distributed under the GPL. // #ifndef _DVIFILE_H #define _DVIFILE_H #include "bigEndianByteReader.h" #include #include #include -#include +#include class fontPool; class pageSize; class TeXFontDefinition; class dvifile : public bigEndianByteReader { public: /** Makes a deep copy of the old DVI file. */ dvifile(const dvifile *old, fontPool *fp ); dvifile(const QString& fname, class fontPool* pool); ~dvifile(); fontPool * font_pool; QString filename; QString generatorString; quint16 total_pages; QVector page_offset; /** Saves the DVI file. Returns true on success. */ bool saveAs(const QString &filename); // Returns a pointer to the DVI file's data, or 0 if no data has yet // been allocated. quint8 * dvi_Data() {return dviData.data();} qint64 size_of_file; QString errorMsg; /** This field is set to zero when the DVI file is constructed, and will be modified during the prescan phase (at this time the prescan code still resides in the dviRenderer class) */ quint16 numberOfExternalPSFiles; /** This field is set to zero when the DVI file is constructed, and will be modified during the prescan phase (at this time the prescan code still resides in the dviRenderer class) */ quint16 numberOfExternalNONPSFiles; quint32 beginning_of_postamble; /** This flag is set to "true" during the construction of the dvifile, and is never changed afterwards by the dvifile class. It is used in kdvi in conjuction with source-specials: the first time a page with source specials is rendered, KDVI shows an info dialog, and the flag is set to false. That way KDVI ensures that the user is only informed once. */ bool sourceSpecialMarker; QHash tn_table; /** Returns the number of centimeters per DVI unit in this DVI file. */ double getCmPerDVIunit() const {return cmPerDVIunit;} /** Returns the magnification of the DVI file, as described in the DVI Standard. */ quint32 getMagnification() const {return _magnification;} /** This member is set to zero on construction and can be used by other software to count error messages that were printed when the DVI-file was processed. Suggested application: limit the number of error messages to, say, 25. */ quint8 errorCounter; /** Papersize information read from the dvi-File */ pageSize *suggestedPageSize; /** Sets new DVI data; all old data is erased. EXPERIMENTAL, use with care. */ void setNewData(const QVector& newData) {dviData = newData;} /** Page numbers that appear in a DVI document need not be ordered. Worse, page numbers need not be unique. This method renumbers the pages. */ void renumber(); /** PDF to PS file conversion This utility method takes the name of a PDF-file, and attempts to convert it to a PS file. The dvifile internally keeps a list of converted files, to do two thigs: - convert files only once. - delete all converted files on destruction @warning The internal buffer can lead to difficulties if filenames of PDF-files are not unique: if the content of a PDF file is changed and this method is called a second time with the same file name, the method will then NOT convert the file, but simply return the name from the buffer @returns The name of the PS file, or QString() on failure. */ QString convertPDFtoPS(const QString &PDFFilename, QString *converrorms=nullptr); private: /** process_preamble reads the information in the preamble and stores it into global variables for later use. */ void process_preamble(); void find_postamble(); /** read_postamble reads the information in the postamble, storing it into global variables. It also takes care of reading in all of the pixel files for the fonts used in the job. */ void read_postamble(); void prepare_pages(); /** Offset in DVI file of last page, set in read_postamble(). */ quint32 last_page_offset; quint32 _magnification; double cmPerDVIunit; QVector dviData; /** Map of filenames for converted PDF files This map contains names of PDF files that were converted to PostScript. The key is the name of the PDF file, the data the name of the associated PS file, or QString(), if the file could not be converted. The PS files are deleted when the DVI-file is destructed. */ QMap convertedFiles; /** Flag, used so that KDVI complains only once about a missing "PDF2PS" utility. Set to "false" in the constructor. */ bool have_complainedAboutMissingPDF2PS; }; #endif //ifndef _DVIFILE_H diff --git a/generators/dvi/dviRenderer.h b/generators/dvi/dviRenderer.h index 8b62edbbf..189039ec2 100644 --- a/generators/dvi/dviRenderer.h +++ b/generators/dvi/dviRenderer.h @@ -1,332 +1,332 @@ // -*- 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-2006 Stefan Kebekus. Distributed under the GPL. #ifndef _dvirenderer_h_ #define _dvirenderer_h_ #include "bigEndianByteReader.h" //#include "documentRenderer.h" #include "dviexport.h" //#include "dvisourceeditor.h" #include "fontpool.h" #include "dviPageInfo.h" #include "pageSize.h" #include "anchor.h" #include "prebookmark.h" #include #include #include #include #include #include #include #include #include -#include +#include class Anchor; class DocumentWidget; class dvifile; class dviRenderer; class ghostscript_interface; class QEventLoop; class QProgressDialog; class PreBookmark; class TeXFontDefinition; extern const int MFResolutions[]; class DVI_SourceFileAnchor { public: DVI_SourceFileAnchor() {} DVI_SourceFileAnchor(const QString& name, quint32 ln, quint32 pg, const Length& _distance_from_top) : fileName(name), line(ln), page(pg), distance_from_top(_distance_from_top) {} QString fileName; quint32 line; quint32 page; Length distance_from_top; }; /** Compound of registers, as defined in section 2.6.2 of the DVI driver standard, Level 0, published by the TUG DVI driver standards committee. */ struct framedata { long dvi_h; long dvi_v; long w; long x; long y; long z; int pxl_v; }; /* this information is saved when using virtual fonts */ typedef void (dviRenderer::*set_char_proc)(unsigned int, unsigned int); typedef void (dviRenderer::*parseSpecials)(char *, quint8 *); struct drawinf { struct framedata data; TeXFontDefinition* fontp; set_char_proc set_char_p; QHash* fonttable; TeXFontDefinition* _virtual; }; class dviRenderer : public QObject /*: public DocumentRenderer*/, bigEndianByteReader { Q_OBJECT public: dviRenderer(bool useFontHinting); virtual ~dviRenderer(); virtual bool setFile(const QString &fname, const QUrl &base); dvifile* dviFile; #if 0 bool isModified() const {return _isModified;}; void setPrefs(bool flag_showPS, const QString &editorCommand, bool useFontHints ); #endif virtual bool supportsTextSearch() const {return true;} bool showPS() { return _postscript; } int curr_page() { return current_page+1; } virtual bool isValidFile(const QString& fileName) const; /** This method will try to parse the reference part of the DVI file's URL, (either a number, which is supposed to be a page number, or src:) and see if a corresponding section of the DVI file can be found. If so, it returns an anchor to that section. If not, it returns an invalid anchor. */ virtual Anchor parseReference(const QString &reference); Anchor findAnchor(const QString &); virtual PageNumber totalPages() const; void setEventLoop(QEventLoop *el); // These should not be public... only for the moment void read_postamble(); void draw_part(double current_dimconv, bool is_vfmacro); void set_vf_char(unsigned int cmd, unsigned int ch); void set_char(unsigned int cmd, unsigned int ch); void set_empty_char(unsigned int cmd, unsigned int ch); void set_no_char(unsigned int cmd, unsigned int ch); void applicationDoSpecial(char * cp); void special(long nbytes); void printErrorMsgForSpecials(const QString& msg); void color_special(const QString& msg); void html_href_special(const QString& msg); void html_anchor_end(); void draw_page(); void export_finished(const DVIExport*); //void editor_finished(const DVISourceEditor*); Q_SIGNALS: /** * The following three signals are modeleed on the corresponding signals * of the Document class. */ void error( const QString &message, int duration ); void warning( const QString &message, int duration ); void notice( const QString &message, int duration ); public Q_SLOTS: void exportPS(const QString& fname = QString(), const QStringList& options = QStringList(), QPrinter* printer = nullptr, QPrinter::Orientation orientation = QPrinter::Portrait); void exportPDF(); void handleSRCLink(const QString &linkText, const QPoint& point, DocumentWidget *widget); void embedPostScript(); virtual void drawPage(RenderedDocumentPagePixmap* page); virtual void getText(RenderedDocumentPagePixmap* page); SimplePageSize sizeOfPage(const PageNumber& page); QVector getPrebookmarks() const { return prebookmarks; } const QVector& sourceAnchors() { return sourceHyperLinkAnchors; } private Q_SLOTS: /** This method shows a dialog that tells the user that source information is present, and gives the opportunity to open the manual and learn more about forward and inverse search */ // void showThatSourceInformationIsPresent(); private: friend class DVIExportToPS; friend class DVIExport; // friend class DVISourceEditor; /** URL to the DVI file This field is initialized by the setFile() method. See the explanation there. */ QUrl baseURL; /** This method parses a color specification of type "gray 0.5", "rgb 0.5 0.7 1.0", "hsb ...", "cmyk .." or "PineGreen". See the source code for details. */ QColor parseColorSpecification(const QString& colorSpec); /** This map contains the colors which are known by name. This field is initialized in the method parseColorSpecification() as soon as it is needed. */ QMap namedColors; /** 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 PDFencodingToQString(const QString& pdfstring); void setResolution(double resolution_in_DPI); fontPool font_pool; double resolutionInDPI; // @@@ explanation void prescan(parseSpecials specialParser); void prescan_embedPS(char *cp, quint8 *); void prescan_removePageSizeInfo(char *cp, quint8 *); void prescan_parseSpecials(char *cp, quint8 *); void prescan_ParsePapersizeSpecial(const QString& cp); void prescan_ParseBackgroundSpecial(const QString& cp); void prescan_ParseHTMLAnchorSpecial(const QString& cp); void prescan_ParsePSHeaderSpecial(const QString& cp); void prescan_ParsePSBangSpecial(const QString& cp); void prescan_ParsePSQuoteSpecial(const QString& cp); void prescan_ParsePSSpecial(const QString& cp); void prescan_ParsePSFileSpecial(const QString& cp); void prescan_ParseSourceSpecial(const QString& cp); void prescan_setChar(unsigned int ch); /* */ QVector prebookmarks; /** Utility fields used by the embedPostScript method*/ QProgressDialog *embedPS_progress; quint16 embedPS_numOfProgressedFiles; /** Shrink factor. Units are not quite clear */ double shrinkfactor; QString errorMsg; /** Methods which handle certain special commands. */ void epsf_special(const QString& cp); void source_special(const QString& cp); /** TPIC specials */ void TPIC_setPen_special(const QString& cp); void TPIC_addPath_special(const QString& cp); void TPIC_flushPath_special(); // List of source-hyperlinks on all pages. This vector is generated // when the DVI-file is first loaded, i.e. when draw_part is called // with PostScriptOutPutString != NULL QVector sourceHyperLinkAnchors; // If not NULL, the text currently drawn represents a source // hyperlink to the (relative) URL given in the string; QString *source_href; // If not NULL, the text currently drawn represents a hyperlink to // the (relative) URL given in the string; QString *HTML_href; QString editorCommand; /** Stack for register compounds, used for the DVI-commands PUSH/POP as explained in section 2.5 and 2.6.2 of the DVI driver standard, Level 0, published by the TUG DVI driver standards committee. */ QStack stack; /** A stack where color are stored, according to the documentation of DVIPS */ QStack colorStack; /** The global color is to be used when the color stack is empty */ QColor globalColor; /** If PostScriptOutPutFile is non-zero, then no rendering takes place. Instead, the PostScript code which is generated by the \special-commands is written to the PostScriptString */ QString *PostScriptOutPutString; ghostscript_interface *PS_interface; /** true, if gs should be used, otherwise, only bounding boxes are drawn. */ bool _postscript; /** This flag is used when rendering a dvi-page. It is set to "true" when any dvi-command other than "set" or "put" series of commands is encountered. This is considered to mark the end of a word. */ bool line_boundary_encountered; bool word_boundary_encountered; unsigned int current_page; /** Data required for handling TPIC specials */ float penWidth_in_mInch; QPolygon TPIC_path; quint16 number_of_elements_in_path; drawinf currinf; RenderedDocumentPagePixmap* currentlyDrawnPage; QMap > all_exports_; //QExplicitlySharedDataPointer editor_; /** Flag if document is modified This flag indicates if the document was modified after it was loaded. It is set to 'false' in the constructor, in the clear() and setFile() method. It can be set to 'true' be methods that modify the document (e.g. the deletePages() method of the djvu implementation of this class). */ bool _isModified; QMutex mutex; quint16 numPages; //TODO: merge into dviPageInfo QVector pageSizes; QMap anchorList; QEventLoop* m_eventLoop; QPainter* foreGroundPainter; // was the locateFonts method of font pool executed? bool fontpoolLocateFontsDone; }; #endif diff --git a/generators/dvi/dviRenderer_draw.cpp b/generators/dvi/dviRenderer_draw.cpp index d6ed17dda..70e915fc4 100644 --- a/generators/dvi/dviRenderer_draw.cpp +++ b/generators/dvi/dviRenderer_draw.cpp @@ -1,717 +1,717 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- /* * Copyright (c) 1994 Paul Vojta. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * NOTE: * xdvi is based on prior work as noted in the modification history, below. */ /* * DVI previewer for X. * * Eric Cooper, CMU, September 1985. * * Code derived from dvi-imagen.c. * * Modification history: * 1/1986 Modified for X.10 --Bob Scheifler, MIT LCS. * 7/1988 Modified for X.11 --Mark Eichin, MIT * 12/1988 Added 'R' option, toolkit, magnifying glass * --Paul Vojta, UC Berkeley. * 2/1989 Added tpic support --Jeffrey Lee, U of Toronto * 4/1989 Modified for System V --Donald Richardson, Clarkson Univ. * 3/1990 Added VMS support --Scott Allendorf, U of Iowa * 7/1990 Added reflection mode --Michael Pak, Hebrew U of Jerusalem * 1/1992 Added greyscale code --Till Brychcy, Techn. Univ. Muenchen * and Lee Hetherington, MIT * 4/1994 Added DPS support, bounding box * --Ricardo Telichevesky * and Luis Miguel Silveira, MIT RLE. */ //#define DEBUG_RENDER 0 #include #include "debug_dvi.h" #include "dviRenderer.h" #include "dvi.h" #include "dviFile.h" #include "hyperlink.h" #include "debug_dvi.h" #include "psgs.h" //#include "renderedDviPagePixmap.h" #include "TeXFont.h" #include "textBox.h" #include "xdvi.h" #include #include -#include +#include /** Routine to print characters. */ void dviRenderer::set_char(unsigned int cmd, unsigned int ch) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "set_char #" << ch; #endif glyph *g; if (colorStack.isEmpty()) g = ((TeXFont *)(currinf.fontp->font))->getGlyph(ch, true, globalColor); else g = ((TeXFont *)(currinf.fontp->font))->getGlyph(ch, true, colorStack.top()); if (g == nullptr) return; long dvi_h_sav = currinf.data.dvi_h; QImage pix = g->shrunkenCharacter; int x = ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))) - g->x2; int y = currinf.data.pxl_v - g->y2; // Draw the character. foreGroundPainter->drawImage(x, y, pix); // Are we drawing text for a hyperlink? And are hyperlinks // enabled? if (HTML_href != nullptr) { // Now set up a rectangle which is checked against every mouse // event. if (line_boundary_encountered == true) { // Set up hyperlink Hyperlink dhl; dhl.baseline = currinf.data.pxl_v; dhl.box.setRect(x, y, pix.width(), pix.height()); dhl.linkText = *HTML_href; currentlyDrawnPage->hyperLinkList.push_back(dhl); } else { QRect dshunion = currentlyDrawnPage->hyperLinkList[currentlyDrawnPage->hyperLinkList.size()-1].box.united(QRect(x, y, pix.width(), pix.height())) ; currentlyDrawnPage->hyperLinkList[currentlyDrawnPage->hyperLinkList.size()-1].box = dshunion; } } // Are we drawing text for a source hyperlink? And are source // hyperlinks enabled? // If we are printing source hyperlinks are irrelevant, otherwise we // actually got a pointer to a RenderedDviPagePixmap. RenderedDviPagePixmap* currentDVIPage = dynamic_cast(currentlyDrawnPage); if (source_href != nullptr && currentDVIPage) { // Now set up a rectangle which is checked against every mouse // event. if (line_boundary_encountered == true) { // Set up source hyperlinks Hyperlink dhl; dhl.baseline = currinf.data.pxl_v; dhl.box.setRect(x, y, pix.width(), pix.height()); if (source_href != nullptr) dhl.linkText = *source_href; else dhl.linkText = QLatin1String(""); currentDVIPage->sourceHyperLinkList.push_back(dhl); } else { QRect dshunion = currentDVIPage->sourceHyperLinkList[currentDVIPage->sourceHyperLinkList.size()-1].box.united(QRect(x, y, pix.width(), pix.height())) ; currentDVIPage->sourceHyperLinkList[currentDVIPage->sourceHyperLinkList.size()-1].box = dshunion; } } // Code for DVI -> text functions (e.g. marking of text, full text // search, etc.). Set up the currentlyDrawnPage->textBoxList. TextBox link; link.box.setRect(x, y, pix.width(), pix.height()); link.text = QLatin1String(""); currentlyDrawnPage->textBoxList.push_back(link); switch(ch) { case 0x0b: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("ff"); break; case 0x0c: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("fi"); break; case 0x0d: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("fl"); break; case 0x0e: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("ffi"); break; case 0x0f: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("ffl"); break; case 0x7b: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('-'); break; case 0x7c: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("---"); break; case 0x7d: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("\""); break; case 0x7e: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('~'); break; case 0x7f: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("@@"); // @@@ check! break; default: if ((ch >= 0x21) && (ch <= 0x7a)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QChar(ch); else currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('?'); break; } if (cmd == PUT1) currinf.data.dvi_h = dvi_h_sav; else currinf.data.dvi_h += (int)(currinf.fontp->scaled_size_in_DVI_units * dviFile->getCmPerDVIunit() * (1200.0 / 2.54)/16.0 * g->dvi_advance_in_units_of_design_size_by_2e20 + 0.5); word_boundary_encountered = false; line_boundary_encountered = false; } void dviRenderer::set_empty_char(unsigned int, unsigned int) { return; } void dviRenderer::set_vf_char(unsigned int cmd, unsigned int ch) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "dviRenderer::set_vf_char( cmd=" << cmd << ", ch=" << ch << " )"; #endif static unsigned char c; macro *m = &currinf.fontp->macrotable[ch]; if (m->pos == nullptr) { qCCritical(OkularDviDebug) << "Character " << ch << " not defined in font " << currinf.fontp->fontname << endl; m->pos = m->end = &c; return; } long dvi_h_sav = currinf.data.dvi_h; struct drawinf oldinfo = currinf; currinf.data.w = 0; currinf.data.x = 0; currinf.data.y = 0; currinf.data.z = 0; currinf.fonttable = &(currinf.fontp->vf_table); currinf._virtual = currinf.fontp; quint8 *command_ptr_sav = command_pointer; quint8 *end_ptr_sav = end_pointer; command_pointer = m->pos; end_pointer = m->end; draw_part(currinf.fontp->scaled_size_in_DVI_units*(dviFile->getCmPerDVIunit() * 1200.0 / 2.54)/16.0, true); command_pointer = command_ptr_sav; end_pointer = end_ptr_sav; currinf = oldinfo; if (cmd == PUT1) currinf.data.dvi_h = dvi_h_sav; else currinf.data.dvi_h += (int)(currinf.fontp->scaled_size_in_DVI_units * dviFile->getCmPerDVIunit() * (1200.0 / 2.54)/16.0 * m->dvi_advance_in_units_of_design_size_by_2e20 + 0.5); } void dviRenderer::set_no_char(unsigned int cmd, unsigned int ch) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "dviRenderer::set_no_char( cmd=" << cmd << ", ch =" << ch << " )" ; #endif if (currinf._virtual) { currinf.fontp = currinf._virtual->first_font; if (currinf.fontp != nullptr) { currinf.set_char_p = currinf.fontp->set_char_p; (this->*currinf.set_char_p)(cmd, ch); return; } } errorMsg = i18n("The DVI code set a character of an unknown font."); return; } void dviRenderer::draw_part(double current_dimconv, bool is_vfmacro) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "draw_part"; #endif qint32 RRtmp=0, WWtmp=0, XXtmp=0, YYtmp=0, ZZtmp=0; quint8 ch; currinf.fontp = nullptr; currinf.set_char_p = &dviRenderer::set_no_char; int last_space_index = 0; bool space_encountered = false; bool after_space = false; for (;;) { space_encountered = false; ch = readUINT8(); if (ch <= (unsigned char) (SETCHAR0 + 127)) { (this->*currinf.set_char_p)(ch, ch); } else if (FNTNUM0 <= ch && ch <= (unsigned char) (FNTNUM0 + 63)) { currinf.fontp = currinf.fonttable->value(ch - FNTNUM0); if (currinf.fontp == nullptr) { errorMsg = i18n("The DVI code referred to font #%1, which was not previously defined.", ch - FNTNUM0); return; } currinf.set_char_p = currinf.fontp->set_char_p; } else { qint32 a, b; switch (ch) { case SET1: case PUT1: (this->*currinf.set_char_p)(ch, readUINT8()); break; case SETRULE: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; } /* Be careful, dvicopy outputs rules with height = 0x80000000. We don't want any SIGFPE here. */ a = readUINT32(); b = readUINT32(); b = ((long) (b * current_dimconv)); if (a > 0 && b > 0) { int h = ((int) ROUNDUP(((long) (a * current_dimconv)), shrinkfactor * 65536)); int w = ((int) ROUNDUP(b, shrinkfactor * 65536)); if (colorStack.isEmpty()) foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, globalColor ); else foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, colorStack.top() ); } currinf.data.dvi_h += b; break; case PUTRULE: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; } a = readUINT32(); b = readUINT32(); a = ((long) (a * current_dimconv)); b = ((long) (b * current_dimconv)); if (a > 0 && b > 0) { int h = ((int) ROUNDUP(a, shrinkfactor * 65536)); int w = ((int) ROUNDUP(b, shrinkfactor * 65536)); if (colorStack.isEmpty()) foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, globalColor ); else foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, colorStack.top() ); } break; case NOP: break; case BOP: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; } command_pointer += 11 * 4; currinf.data.dvi_h = 1200 << 16; // Reminder: DVI-coordinates start at (1",1") from top of page currinf.data.dvi_v = 1200; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); currinf.data.w = currinf.data.x = currinf.data.y = currinf.data.z = 0; break; case EOP: // Check if we are just at the end of a virtual font macro. if (is_vfmacro == false) { // This is really the end of a page, and not just the end // of a macro. Mark the end of the current word. word_boundary_encountered = true; line_boundary_encountered = true; // Sanity check for the dvi-file: The DVI-standard asserts // that at the end of a page, the stack should always be // empty. if (!stack.isEmpty()) { qCDebug(OkularDviDebug) << "DRAW: The stack was not empty when the EOP command was encountered."; errorMsg = i18n("The stack was not empty when the EOP command was encountered."); return; } } return; case PUSH: stack.push(currinf.data); break; case POP: if (stack.isEmpty()) { errorMsg = i18n("The stack was empty when a POP command was encountered."); return; } else currinf.data = stack.pop(); word_boundary_encountered = true; line_boundary_encountered = true; break; case RIGHT1: case RIGHT2: case RIGHT3: case RIGHT4: RRtmp = readINT(ch - RIGHT1 + 1); // A horizontal motion in the range 4 * font space [f] < h < // font space [f] will be treated as a kern that is not // indicated in the printouts that DVItype produces between // brackets. We allow a larger space in the negative // direction than in the positive one, because TEX makes // comparatively large backspaces when it positions // accents. (comments stolen from the source of dvitype) if ((is_vfmacro == false) && (currinf.fontp != nullptr) && ((RRtmp >= currinf.fontp->scaled_size_in_DVI_units/6) || (RRtmp <= -4*(currinf.fontp->scaled_size_in_DVI_units/6))) && (currentlyDrawnPage->textBoxList.size() > 0)) { //currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += ' '; space_encountered = true; } currinf.data.dvi_h += ((long) (RRtmp * current_dimconv)); break; case W1: case W2: case W3: case W4: WWtmp = readINT(ch - W0); currinf.data.w = ((long) (WWtmp * current_dimconv)); // fallthrough case W0: if ((is_vfmacro == false) && (currinf.fontp != nullptr) && ((WWtmp >= currinf.fontp->scaled_size_in_DVI_units/6) || (WWtmp <= -4*(currinf.fontp->scaled_size_in_DVI_units/6))) && (currentlyDrawnPage->textBoxList.size() > 0) ) { //currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += ' '; space_encountered = true; } currinf.data.dvi_h += currinf.data.w; break; case X1: case X2: case X3: case X4: XXtmp = readINT(ch - X0); currinf.data.x = ((long) (XXtmp * current_dimconv)); // fallthrough case X0: if ((is_vfmacro == false) && (currinf.fontp != nullptr) && ((XXtmp >= currinf.fontp->scaled_size_in_DVI_units/6) || (XXtmp <= -4*(currinf.fontp->scaled_size_in_DVI_units/6))) && (currentlyDrawnPage->textBoxList.size() > 0)) { //currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += ' '; space_encountered = true; } currinf.data.dvi_h += currinf.data.x; break; case DOWN1: case DOWN2: case DOWN3: case DOWN4: { qint32 DDtmp = readINT(ch - DOWN1 + 1); if ((is_vfmacro == false) && (currinf.fontp != nullptr) && (abs(DDtmp) >= 5*(currinf.fontp->scaled_size_in_DVI_units/6)) && (currentlyDrawnPage->textBoxList.size() > 0)) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; if (abs(DDtmp) >= 10*(currinf.fontp->scaled_size_in_DVI_units/6)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('\n'); } currinf.data.dvi_v += ((long) (DDtmp * current_dimconv))/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); } break; case Y1: case Y2: case Y3: case Y4: YYtmp = readINT(ch - Y0); currinf.data.y = ((long) (YYtmp * current_dimconv)); // fallthrough case Y0: if ((is_vfmacro == false) && (currinf.fontp != nullptr) && (abs(YYtmp) >= 5*(currinf.fontp->scaled_size_in_DVI_units/6)) && (currentlyDrawnPage->textBoxList.size() > 0)) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; if (abs(YYtmp) >= 10*(currinf.fontp->scaled_size_in_DVI_units/6)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('\n'); } currinf.data.dvi_v += currinf.data.y/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); break; case Z1: case Z2: case Z3: case Z4: ZZtmp = readINT(ch - Z0); currinf.data.z = ((long) (ZZtmp * current_dimconv)); // fallthrough case Z0: if ((is_vfmacro == false) && (currinf.fontp != nullptr) && (abs(ZZtmp) >= 5*(currinf.fontp->scaled_size_in_DVI_units/6)) && (currentlyDrawnPage->textBoxList.size() > 0)) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; if (abs(ZZtmp) >= 10*(currinf.fontp->scaled_size_in_DVI_units/6)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('\n'); } currinf.data.dvi_v += currinf.data.z/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); break; case FNT1: case FNT2: case FNT3: currinf.fontp = currinf.fonttable->value(readUINT(ch - FNT1 + 1)); if (currinf.fontp == nullptr) { errorMsg = i18n("The DVI code referred to a font which was not previously defined."); return; } currinf.set_char_p = currinf.fontp->set_char_p; break; case FNT4: currinf.fontp = currinf.fonttable->value(readINT(ch - FNT1 + 1)); if (currinf.fontp == nullptr) { errorMsg = i18n("The DVI code referred to a font which was not previously defined."); return; } currinf.set_char_p = currinf.fontp->set_char_p; break; case XXX1: case XXX2: case XXX3: case XXX4: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; } a = readUINT(ch - XXX1 + 1); if (a > 0) { char *cmd = new char[a+1]; strncpy(cmd, (char *)command_pointer, a); command_pointer += a; cmd[a] = '\0'; applicationDoSpecial(cmd); delete [] cmd; } break; case FNTDEF1: case FNTDEF2: case FNTDEF3: case FNTDEF4: command_pointer += 12 + ch - FNTDEF1 + 1; { quint8 tempa = readUINT8(); quint8 tempb = readUINT8(); command_pointer += tempa + tempb; } break; case PRE: case POST: case POSTPOST: errorMsg = i18n("An illegal command was encountered."); return; break; default: errorMsg = i18n("The unknown op-code %1 was encountered.", ch); return; } /* end switch*/ } /* end else (ch not a SETCHAR or FNTNUM) */ #ifdef DEBUG_RENDER if (currentlyDrawnPage->textBoxList.size() > 0) qCDebug(OkularDviDebug) << "Element:" << currentlyDrawnPage->textBoxList.last().box << currentlyDrawnPage->textBoxList.last().text << " ? s:" << space_encountered << " / nl:" << line_boundary_encountered << " / w:" << word_boundary_encountered << ", " << last_space_index << "/" << currentlyDrawnPage->textBoxList.size(); #endif /* heuristic to properly detect newlines; a space is needed */ if (after_space && line_boundary_encountered && word_boundary_encountered) { if (currentlyDrawnPage->textBoxList.last().text.endsWith(QLatin1Char('\n'))) currentlyDrawnPage->textBoxList.last().text.chop(1); currentlyDrawnPage->textBoxList.last().text += QLatin1String(" \n"); after_space = false; } /* a "space" has been found and there is some (new) character in the array */ if (space_encountered && (currentlyDrawnPage->textBoxList.size() > last_space_index)) { for (int lidx = last_space_index+1; lidxtextBoxList.size(); ++lidx) { // merge two adjacent boxes which are part of the same word currentlyDrawnPage->textBoxList[lidx-1].box.setRight(currentlyDrawnPage->textBoxList[lidx].box.x()); } #ifdef DEBUG_RENDER QString lastword(currentlyDrawnPage->textBoxList[last_space_index].text); for (int lidx = last_space_index+1; lidxtextBoxList.size(); ++lidx) lastword += currentlyDrawnPage->textBoxList[lidx].text; qCDebug(OkularDviDebug) << "space encountered: '" << lastword << "'"; #endif last_space_index = currentlyDrawnPage->textBoxList.size(); after_space = true; } } /* end for */ } void dviRenderer::draw_page() { // Reset a couple of variables HTML_href = nullptr; source_href = nullptr; penWidth_in_mInch = 0.0; // Calling resize() here rather than clear() means that the memory // taken up by the vector is not freed. This is faster than // constantly allocating/freeing memory. currentlyDrawnPage->textBoxList.resize(0); RenderedDviPagePixmap* currentDVIPage = dynamic_cast(currentlyDrawnPage); if (currentDVIPage) { currentDVIPage->sourceHyperLinkList.resize(0); } #ifdef PERFORMANCE_MEASUREMENT // If this is the first time a page is drawn, take the time that is // elapsed till the kdvi_multipage was constructed, and print // it. Set the flag so that is message will not be printed again. if (performanceFlag == 0) { qCDebug(OkularDviDebug) << "Time elapsed till the first page is drawn: " << performanceTimer.restart() << "ms"; performanceFlag = 1; } #endif #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) <<"draw_page"; #endif #if 0 if (!accessibilityBackground) { #endif foreGroundPainter->fillRect( foreGroundPainter->viewport(), PS_interface->getBackgroundColor(current_page) ); #if 0 } else { // In accessiblity mode use the custom background color foreGroundPainter->fillRect( foreGroundPainter->viewport(), accessibilityBackgroundColor ); } #endif // Render the PostScript background, if there is one. if (_postscript) { #if 0 // In accessiblity mode use the custom background color if (accessibilityBackground) { // Flag permanent is set to false because otherwise we would not be able to restore // the original background color. PS_interface->setBackgroundColor(current_page, accessibilityBackgroundColor, false); } else #endif PS_interface->restoreBackgroundColor(current_page); PS_interface->graphics(current_page, resolutionInDPI, dviFile->getMagnification(), foreGroundPainter); } // Now really write the text if (dviFile->page_offset.isEmpty() == true) return; 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; double fontPixelPerDVIunit = dviFile->getCmPerDVIunit() * 1200.0/2.54; draw_part(65536.0*fontPixelPerDVIunit, false); if (HTML_href != nullptr) { delete HTML_href; HTML_href = nullptr; } if (source_href != nullptr) { delete source_href; source_href = nullptr; } } diff --git a/generators/dvi/dviexport.cpp b/generators/dvi/dviexport.cpp index 63e8b766b..4e9d33dc6 100644 --- a/generators/dvi/dviexport.cpp +++ b/generators/dvi/dviexport.cpp @@ -1,323 +1,323 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- /** * \file dviexport.h * Distributed under the GNU GPL version 2 or (at your option) * any later version. See accompanying file COPYING or copy at * http://www.gnu.org/copyleft/gpl.html * * \author Angus Leeming * \author Stefan Kebekus * * Classes DVIExportToPDF and DVIExportToPS control the export * of a DVI file to PDF or PostScript format, respectively. * Common functionality is factored out into a common base class, * DVIExport which itself derives from QSharedData allowing easy, * polymorphic storage of multiple QExplicitlySharedDataPointer variables * in a container of all exported processes. */ #include #include #include "dviexport.h" #include "dviFile.h" #include "dviRenderer.h" #include "debug_dvi.h" #include #include #include #include #include #include -#include +#include #include #include #include DVIExport::DVIExport(dviRenderer& parent) : started_(false), process_(nullptr), parent_(&parent) { connect( this, &DVIExport::error, &parent, &dviRenderer::error ); } DVIExport::~DVIExport() { delete process_; } void DVIExport::start(const QString& command, const QStringList& args, const QString& working_directory, const QString& error_message) { assert(!process_); process_ = new KProcess; process_->setOutputChannelMode(KProcess::MergedChannels); process_->setNextOpenMode(QIODevice::Text); connect(process_, &KProcess::readyReadStandardOutput, this, &DVIExport::output_receiver); connect(process_, static_cast(&KProcess::finished), this, &DVIExport::finished); *process_ << command << args; if (!working_directory.isEmpty()) process_->setWorkingDirectory(working_directory); error_message_ = error_message; process_->start(); if (!process_->waitForStarted(-1)) qCCritical(OkularDviDebug) << command << " failed to start" << endl; else started_ = true; if (parent_->m_eventLoop) parent_->m_eventLoop->exec(); } void DVIExport::abort_process_impl() { // deleting process_ kills the external process itself // if it's still running. delete process_; process_ = nullptr; } void DVIExport::finished_impl(int exit_code) { if (process_ && exit_code != 0) emit error(error_message_, -1); // Remove this from the store of all export processes. parent_->m_eventLoop->exit( exit_code ); parent_->export_finished(this); } void DVIExport::output_receiver() { if (process_) { process_->readAllStandardOutput(); } } DVIExportToPDF::DVIExportToPDF(dviRenderer& parent, const QString& output_name) : DVIExport(parent) { // Neither of these should happen. Paranoia checks. if (!parent.dviFile) return; const dvifile& dvi = *(parent.dviFile); const QFileInfo input(dvi.filename); if (!input.exists() || !input.isReadable()) return; if (QStandardPaths::findExecutable(QStringLiteral("dvipdfm")).isEmpty()) { emit error(i18n("

Okular could not locate the program dvipdfm on your computer. This program is " "essential for the export function to work. You can, however, convert " "the DVI-file to PDF using the print function of Okular, but that will often " "produce documents which print okay, but are of inferior quality if viewed in " "Acrobat Reader. It may be wise to upgrade to a more recent version of your " "TeX distribution which includes the dvipdfm program.

" "

Hint to the perplexed system administrator: Okular uses the PATH environment variable " "when looking for programs.

"), -1); return; } if (output_name.isEmpty()) return; start(QStringLiteral("dvipdfm"), QStringList() << QStringLiteral("-o") << output_name << dvi.filename, QFileInfo(dvi.filename).absolutePath(), i18n("The external program 'dvipdfm', which was used to export the file, reported an error. " "You might wish to look at the document info dialog which you will " "find in the File-Menu for a precise error report.")); } DVIExportToPS::DVIExportToPS(dviRenderer& parent, const QString& output_name, const QStringList& options, QPrinter* printer, bool useFontHinting, QPrinter::Orientation orientation) : DVIExport(parent), printer_(printer), orientation_(orientation) { // None of these should happen. Paranoia checks. if (!parent.dviFile) return; const dvifile& dvi = *(parent.dviFile); const QFileInfo input(dvi.filename); if (!input.exists() || !input.isReadable()) return; if (dvi.page_offset.isEmpty()) return; if (dvi.numberOfExternalNONPSFiles != 0) { emit error(i18n("This DVI file refers to external graphic files which are not in PostScript format, and cannot be handled by the " "dvips program that Okular uses internally to print or to export to PostScript. The functionality that " "you require is therefore unavailable in this version of Okular."), -1); return; } if (QStandardPaths::findExecutable(QStringLiteral("dvips")).isEmpty()) { emit error(i18n("

Okular could not locate the program dvips on your computer. " "That program is essential for the export function to work.

" "

Hint to the perplexed system administrator: Okular uses the PATH environment " "variable when looking for programs.

"), -1); return; } if (output_name.isEmpty()) return; output_name_ = output_name; // There is a major problem with dvips, at least 5.86 and lower: the // arguments of the option "-pp" refer to TeX-pages, not to // sequentially numbered pages. For instance "-pp 7" may refer to 3 // or more pages: one page "VII" in the table of contents, a page // "7" in the text body, and any number of pages "7" in various // appendices, indices, bibliographies, and so forth. KDVI currently // uses the following disgusting workaround: if the "options" // variable is used, the DVI-file is copied to a temporary file, and // all the page numbers are changed into a sequential ordering // (using UNIX files, and taking manually care of CPU byte // ordering). Finally, dvips is then called with the new file, and // the file is afterwards deleted. Isn't that great? // A similar problem occurs with DVI files that contain page size // information. On these files, dvips pointblank refuses to change // the page orientation or set another page size. Thus, if the // DVI-file does contain page size information, we remove that // information first. // input_name is the name of the DVI which is used by dvips, either // the original file, or a temporary file with a new numbering. QString input_name = dvi.filename; if (!options.isEmpty() || dvi.suggestedPageSize != nullptr) { // Get a name for a temporary file. // Must open the QTemporaryFile to access the name. QTemporaryFile tmpfile; tmpfile.setAutoRemove(false); tmpfile.open(); tmpfile_name_ = tmpfile.fileName(); tmpfile.close(); input_name = tmpfile_name_; fontPool fp(useFontHinting); dvifile newFile(&dvi, &fp); // Renumber pages newFile.renumber(); const quint16 saved_current_page = parent.current_page; dvifile* saved_dvi = parent.dviFile; parent.dviFile = &newFile; parent.errorMsg = QString(); // Remove any page size information from the file for (parent.current_page = 0; parent.current_page < newFile.total_pages; parent.current_page++) { if (parent.current_page < newFile.total_pages) { parent.command_pointer = newFile.dvi_Data() + parent.dviFile->page_offset[int(parent.current_page)]; parent.end_pointer = newFile.dvi_Data() + parent.dviFile->page_offset[int(parent.current_page+1)]; } else { parent.command_pointer = nullptr; parent.end_pointer = nullptr; } memset((char*) &parent.currinf.data, 0, sizeof(parent.currinf.data)); parent.currinf.fonttable = &(parent.dviFile->tn_table); parent.currinf._virtual = nullptr; parent.prescan(&dviRenderer::prescan_removePageSizeInfo); } parent.current_page = saved_current_page; parent.dviFile = saved_dvi; newFile.saveAs(input_name); } QStringList args; if (!printer) // Export hyperlinks args << QStringLiteral("-z"); if (!options.isEmpty()) args += options; args << input_name << QStringLiteral("-o") << output_name_; start(QStringLiteral("dvips"), args, QFileInfo(dvi.filename).absolutePath(), i18n("The external program 'dvips', which was used to export the file, reported an error. " "You might wish to look at the document info dialog which you will " "find in the File-Menu for a precise error report.")); } void DVIExportToPS::finished_impl(int exit_code) { if (printer_ && !output_name_.isEmpty()) { const QFileInfo output(output_name_); if (output.exists() && output.isReadable()) { // I'm not 100% sure on this, think we still need to select pages in export to ps above Okular::FilePrinter::printFile( (*printer_), output_name_, orientation_, Okular::FilePrinter::ApplicationDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, QString() ); } } if (!tmpfile_name_.isEmpty()) { // Delete the file. QFile(tmpfile_name_).remove(); tmpfile_name_.clear(); } DVIExport::finished_impl(exit_code); } void DVIExportToPS::abort_process_impl() { if (!tmpfile_name_.isEmpty()) { // Delete the file. QFile(tmpfile_name_).remove(); tmpfile_name_.clear(); } printer_ = nullptr; DVIExport::abort_process_impl(); } diff --git a/generators/dvi/dviexport.h b/generators/dvi/dviexport.h index a15197e8d..63b6d3f07 100644 --- a/generators/dvi/dviexport.h +++ b/generators/dvi/dviexport.h @@ -1,140 +1,140 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- /** * \file dviexport.h * Distributed under the GNU GPL version 2 or (at your option) * any later version. See accompanying file COPYING or copy at * http://www.gnu.org/copyleft/gpl.html * * \author Angus Leeming * \author Stefan Kebekus * * Classes DVIExportToPDF and DVIExportToPS control the export * of a DVI file to PDF or PostScript format, respectively. * Common functionality is factored out into a common base class, * DVIExport which itself derives from QSharedData allowing easy, * polymorphic storage of multiple QExplicitlySharedDataPointer variables * in a container of all exported processes. */ #ifndef DVIEXPORT_H #define DVIEXPORT_H #include #include -#include +#include class dviRenderer; class KProcess; class QStringList; class DVIExport: public QObject, public QSharedData { Q_OBJECT public: virtual ~DVIExport(); /** @c started() Flags whether or not the external process was * spawned successfully. * Can be used to decide whether to discard the DVIExport variable, * or to store it and await notification that the external process * has finished. */ bool started() const { return started_; } Q_SIGNALS: void error( const QString &message, int duration ); protected: /** @param parent is stored internally in order to inform the parent * that the external process has finished and that this variable * can be removed from any stores. */ DVIExport(dviRenderer& parent); /** Spawns the external process having connected slots to the child * process's stdin and stdout streams. */ void start(const QString& command, const QStringList& args, const QString& working_directory, const QString& error_message); /** The real implementation of the abort_process() slot that is * called when the fontProcessDialog is closed by the user, * indicating that the export should be halted. */ virtual void abort_process_impl(); /** The real implementation of the finished() slot that is called * when the external process finishes. * @param exit_code the exit code retuned by the external process. */ virtual void finished_impl(int exit_code); private Q_SLOTS: /// Calls an impl() inline so that derived classes don't need slots. void abort_process() { abort_process_impl(); } void finished(int exit_code) { finished_impl(exit_code); } /** This slot receives all output from the child process's stdin * and stdout streams. */ void output_receiver(); private: QString error_message_; bool started_; KProcess* process_; dviRenderer* parent_; }; class DVIExportToPDF : public DVIExport { Q_OBJECT public: /** @param parent is stored internally in order to inform the parent * that the external process has finished. * @param output_name is the name of the PDF file that is * to contain the exported data. */ DVIExportToPDF(dviRenderer& parent, const QString& output_name); }; class DVIExportToPS : public DVIExport { Q_OBJECT public: /** @param parent is stored internally in order to inform the parent * that the external process has finished. * @param output_name is the name of the PostScript file that is * to contain the exported data. * @param options extra command line arguments that are to be * passed to the external process's argv command line. * @param printer having generated the PostScript file, it is passed * to @c printer (if not null). * @param orientation the original orientation of the document */ DVIExportToPS(dviRenderer& parent, const QString& output_name, const QStringList& options, QPrinter* printer, bool useFontHinting, QPrinter::Orientation orientation = QPrinter::Portrait); private: void abort_process_impl() override; void finished_impl(int exit_code) override; QPrinter* printer_; QString output_name_; QString tmpfile_name_; QPrinter::Orientation orientation_; }; #endif diff --git a/generators/dvi/fontEncoding.cpp b/generators/dvi/fontEncoding.cpp index b51ea363e..c3da59ebe 100644 --- a/generators/dvi/fontEncoding.cpp +++ b/generators/dvi/fontEncoding.cpp @@ -1,96 +1,96 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // fontEncoding.cpp // // Part of KDVI - A DVI previewer for the KDE desktop environment // // (C) 2003 Stefan Kebekus // Distributed under the GPL #include #ifdef HAVE_FREETYPE #include "fontEncoding.h" #include "debug_dvi.h" -#include +#include #include #include #include //#define DEBUG_FONTENC fontEncoding::fontEncoding(const QString &encName) { #ifdef DEBUG_FONTENC qCDebug(OkularDviDebug) << "fontEncoding( " << encName << " )"; #endif _isValid = false; // Use kpsewhich to find the encoding file. QProcess kpsewhich; kpsewhich.setReadChannelMode(QProcess::MergedChannels); kpsewhich.start(QStringLiteral("kpsewhich"), QStringList() << encName, QIODevice::ReadOnly|QIODevice::Text); if (!kpsewhich.waitForStarted()) { qCCritical(OkularDviDebug) << "fontEncoding::fontEncoding(...): kpsewhich could not be started." << endl; return; } // We wait here while the external program runs concurrently. kpsewhich.waitForFinished(-1); const QString encFileName = QString::fromLocal8Bit(kpsewhich.readAll()).trimmed(); if (encFileName.isEmpty()) { qCCritical(OkularDviDebug) << QStringLiteral("fontEncoding::fontEncoding(...): The file '%1' could not be found by kpsewhich.").arg(encName) << endl; return; } #ifdef DEBUG_FONTENC qCDebug(OkularDviDebug) << "FileName of the encoding: " << encFileName; #endif QFile file( encFileName ); if ( file.open( QIODevice::ReadOnly ) ) { // Read the file (excluding comments) into the QString variable // 'fileContent' QTextStream stream( &file ); QString fileContent; while ( !stream.atEnd() ) fileContent += stream.readLine().section(QLatin1Char('%'), 0, 0); // line of text excluding '\n' until first '%'-sign file.close(); fileContent = fileContent.trimmed(); // Find the name of the encoding encodingFullName = fileContent.section(QLatin1Char('['), 0, 0).simplified().mid(1); #ifdef DEBUG_FONTENC qCDebug(OkularDviDebug) << "encodingFullName: " << encodingFullName; #endif fileContent = fileContent.section(QLatin1Char('['), 1, 1).section(QLatin1Char(']'),0,0).simplified(); const QStringList glyphNameList = fileContent.split(QLatin1Char('/'), QString::SkipEmptyParts); int i = 0; for ( QStringList::ConstIterator it = glyphNameList.constBegin(); (it != glyphNameList.constEnd())&&(i<256); ++it ) { glyphNameVector[i] = (*it).simplified(); #ifdef DEBUG_FONTENC qCDebug(OkularDviDebug) << i << ": " << glyphNameVector[i]; #endif i++; } for(; i<256; i++) glyphNameVector[i] = QStringLiteral(".notdef"); } else { qCCritical(OkularDviDebug) << QStringLiteral("fontEncoding::fontEncoding(...): The file '%1' could not be opened.").arg(encFileName) << endl; return; } _isValid = true; } #endif // HAVE_FREETYPE diff --git a/generators/dvi/fontMap.cpp b/generators/dvi/fontMap.cpp index f6d19fc7a..f6d7c3864 100644 --- a/generators/dvi/fontMap.cpp +++ b/generators/dvi/fontMap.cpp @@ -1,173 +1,173 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // fontMap.cpp // // Part of KDVI - A DVI previewer for the KDE desktop environment // // (C) 2003 Stefan Kebekus // Distributed under the GPL #include #ifdef HAVE_FREETYPE #include "fontMap.h" #include "debug_dvi.h" -#include +#include #include #include #include //#define DEBUG_FONTMAP fontMap::fontMap() { // Read the map file of ps2pk which will provide us with a // dictionary "TeX Font names" <-> "Name of font files, Font Names // and Encodings" (example: the font "Times-Roman" is called // "ptmr8y" in the DVI file, but the Type1 font file name is // "utmr8a.pfb". We use the map file of "ps2pk" because that progam // has, like kdvi (and unlike dvips), no built-in fonts. // Finding ps2pk.map is not easy. In teTeX < 3.0, the kpsewhich // program REQUIRES the option "--format=dvips config". In teTeX = // 3.0, the option "--format=map" MUST be used. Since there is no // way to give both options at the same time, there is seemingly no // other way than to try both options one after another. We use the // teTeX 3.0 format first. QProcess kpsewhich; kpsewhich.start(QStringLiteral("kpsewhich"), QStringList() << QStringLiteral("--format=map") << QStringLiteral("ps2pk.map"), QIODevice::ReadOnly|QIODevice::Text); if (!kpsewhich.waitForStarted()) { qCCritical(OkularDviDebug) << "fontMap::fontMap(): kpsewhich could not be started." << endl; return; } // We wait here while the external program runs concurrently. kpsewhich.waitForFinished(-1); QString map_fileName = QString::fromLocal8Bit(kpsewhich.readAll()).trimmed(); if (map_fileName.isEmpty()) { // Map file not found? Then we try the teTeX < 3.0 way of finding // the file. kpsewhich.start(QStringLiteral("kpsewhich"), QStringList() << QStringLiteral("--format=dvips config") << QStringLiteral("ps2pk.map"), QIODevice::ReadOnly|QIODevice::Text); if (!kpsewhich.waitForStarted()) { qCCritical(OkularDviDebug) << "fontMap::fontMap(): kpsewhich could not be started." << endl; return; } kpsewhich.waitForFinished(-1); map_fileName = QString::fromLocal8Bit(kpsewhich.readAll()).trimmed(); // If both versions fail, then there is nothing left to do. if (map_fileName.isEmpty()) { qCCritical(OkularDviDebug) << "fontMap::fontMap(): The file 'ps2pk.map' could not be found by kpsewhich." << endl; return; } } QFile file( map_fileName ); if ( file.open( QIODevice::ReadOnly ) ) { QTextStream stream( &file ); QString line; while ( !stream.atEnd() ) { line = stream.readLine().simplified(); if (line.isEmpty() || (line.at(0) == QLatin1Char('%'))) continue; QString TeXName = line.section(QLatin1Char(' '), 0, 0); QString FullName = line.section(QLatin1Char(' '), 1, 1); QString fontFileName = line.section(QLatin1Char('<'), -1).trimmed().section(QLatin1Char(' '), 0, 0); QString encodingName = line.section(QLatin1Char('<'), -2, -2).trimmed().section(QLatin1Char(' '), 0, 0); // It seems that sometimes the encoding is prepended by the // letter '[', which we ignore if ((!encodingName.isEmpty()) && (encodingName[0] == QLatin1Char('['))) encodingName = encodingName.mid(1); double slant = 0.0; int i = line.indexOf(QStringLiteral("SlantFont")); if (i >= 0) { bool ok; slant = line.left(i).section(QLatin1Char(' '), -1, -1 ,QString::SectionSkipEmpty).toDouble(&ok); if (ok == false) slant = 0.0; } fontMapEntry &entry = fontMapEntries[TeXName]; entry.slant = slant; entry.fontFileName = fontFileName; entry.fullFontName = FullName; if (encodingName.endsWith(QLatin1String(".enc"))) entry.fontEncoding = encodingName; else entry.fontEncoding.clear(); } file.close(); } else qCCritical(OkularDviDebug) << QStringLiteral("fontMap::fontMap(): The file '%1' could not be opened.").arg(map_fileName) << endl; #ifdef DEBUG_FONTMAP qCDebug(OkularDviDebug) << "FontMap file parsed. Results:"; QMap::Iterator it; for ( it = fontMapEntries.begin(); it != fontMapEntries.end(); ++it ) qCDebug(OkularDviDebug) << "TeXName: " << it.key() << ", FontFileName=" << it.data().fontFileName << ", FullName=" << it.data().fullFontName << ", Encoding=" << it.data().fontEncoding << "." << endl;; #endif } const QString &fontMap::findFileName(const QString &TeXName) { QMap::Iterator it = fontMapEntries.find(TeXName); if (it != fontMapEntries.end()) return it.value().fontFileName; static const QString nullstring; return nullstring; } const QString &fontMap::findFontName(const QString &TeXName) { QMap::Iterator it = fontMapEntries.find(TeXName); if (it != fontMapEntries.end()) return it.value().fullFontName; static const QString nullstring; return nullstring; } const QString &fontMap::findEncoding(const QString &TeXName) { QMap::Iterator it = fontMapEntries.find(TeXName); if (it != fontMapEntries.end()) return it.value().fontEncoding; static const QString nullstring; return nullstring; } double fontMap::findSlant(const QString &TeXName) { QMap::Iterator it = fontMapEntries.find(TeXName); if (it != fontMapEntries.end()) return it.value().slant; else return 0.0; } #endif // HAVE_FREETYPE diff --git a/generators/dvi/generator_dvi.cpp b/generators/dvi/generator_dvi.cpp index 16a64f940..d9fe8341f 100644 --- a/generators/dvi/generator_dvi.cpp +++ b/generators/dvi/generator_dvi.cpp @@ -1,579 +1,579 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Luigi Toscano * * * * 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 #include #include #include #include #include #include "generator_dvi.h" #include "debug_dvi.h" #include "dviFile.h" #include "dviPageInfo.h" #include "dviRenderer.h" #include "pageSize.h" #include "dviexport.h" #include "TeXFont.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include #include #ifdef DVI_OPEN_BUSYLOOP #include #endif OKULAR_EXPORT_PLUGIN(DviGenerator, "libokularGenerator_dvi.json") DviGenerator::DviGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ), m_fontExtracted( false ), m_docSynopsis( nullptr ), m_dviRenderer( nullptr ) { setFeature( Threaded ); setFeature( TextExtraction ); setFeature( FontInfo ); setFeature( PrintPostscript ); if ( Okular::FilePrinter::ps2pdfAvailable() ) setFeature( PrintToFile ); } bool DviGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > &pagesVector ) { //qCDebug(OkularDviDebug) << "file:" << fileName; QUrl base( QUrl::fromLocalFile(fileName) ); (void)userMutex(); m_dviRenderer = new dviRenderer(documentMetaData(TextHintingMetaData, QVariant()).toBool()); connect(m_dviRenderer, &dviRenderer::error, this, &DviGenerator::error); connect(m_dviRenderer, &dviRenderer::warning, this, &DviGenerator::warning); connect(m_dviRenderer, &dviRenderer::notice, this, &DviGenerator::notice); #ifdef DVI_OPEN_BUSYLOOP static const ushort s_waitTime = 800; // milliseconds static const int s_maxIterations = 10; int iter = 0; for ( ; !m_dviRenderer->isValidFile( fileName ) && iter < s_maxIterations; ++iter ) { qCDebug(OkularDviDebug).nospace() << "file not valid after iteration #" << iter << "/" << s_maxIterations << ", waiting for " << s_waitTime; QThread::msleep(s_waitTime); } if ( iter >= s_maxIterations && !m_dviRenderer->isValidFile( fileName ) ) { qCDebug(OkularDviDebug) << "file still not valid after" << s_maxIterations; delete m_dviRenderer; m_dviRenderer = 0; return false; } #else if ( !m_dviRenderer->isValidFile( fileName ) ) { delete m_dviRenderer; m_dviRenderer = nullptr; return false; } #endif if ( ! m_dviRenderer->setFile( fileName, base ) ) { delete m_dviRenderer; m_dviRenderer = nullptr; return false; } qCDebug(OkularDviDebug) << "# of pages:" << m_dviRenderer->dviFile->total_pages; m_resolution = dpi().height(); loadPages( pagesVector ); return true; } bool DviGenerator::doCloseDocument() { delete m_docSynopsis; m_docSynopsis = nullptr; delete m_dviRenderer; m_dviRenderer = nullptr; m_linkGenerated.clear(); m_fontExtracted = false; return true; } void DviGenerator::fillViewportFromAnchor( Okular::DocumentViewport &vp, const Anchor &anch, const Okular::Page *page ) const { fillViewportFromAnchor( vp, anch, page->width(), page->height() ); } void DviGenerator::fillViewportFromAnchor( Okular::DocumentViewport &vp, const Anchor &anch, int pW, int pH ) const { vp.pageNumber = anch.page - 1; SimplePageSize ps = m_dviRenderer->sizeOfPage( vp.pageNumber ); double resolution = 0; if (ps.isValid()) resolution = (double)(pW)/ps.width().getLength_in_inch(); else resolution = m_resolution; double py = (double)anch.distance_from_top.getLength_in_inch()*resolution + 0.5; vp.rePos.normalizedX = 0.5; vp.rePos.normalizedY = py/(double)pH; vp.rePos.enabled = true; vp.rePos.pos = Okular::DocumentViewport::Center; } QLinkedList DviGenerator::generateDviLinks( const dviPageInfo *pageInfo ) { QLinkedList dviLinks; int pageWidth = pageInfo->width, pageHeight = pageInfo->height; foreach( const Hyperlink &dviLink, pageInfo->hyperLinkList ) { QRect boxArea = dviLink.box; double nl = (double)boxArea.left() / pageWidth, nt = (double)boxArea.top() / pageHeight, nr = (double)boxArea.right() / pageWidth, nb = (double)boxArea.bottom() / pageHeight; QString linkText = dviLink.linkText; if ( linkText.startsWith(QLatin1String("#")) ) linkText = linkText.mid( 1 ); Anchor anch = m_dviRenderer->findAnchor( linkText ); Okular::Action *okuLink = nullptr; /* distinguish between local (-> anchor) and remote links */ if (anch.isValid()) { /* internal link */ Okular::DocumentViewport vp; fillViewportFromAnchor( vp, anch, pageWidth, pageHeight ); okuLink = new Okular::GotoAction( QLatin1String(""), vp ); } else { okuLink = new Okular::BrowseAction( QUrl::fromUserInput( dviLink.linkText ) ); } if ( okuLink ) { Okular::ObjectRect *orlink = new Okular::ObjectRect( nl, nt, nr, nb, false, Okular::ObjectRect::Action, okuLink ); dviLinks.push_front( orlink ); } } return dviLinks; } QImage DviGenerator::image( Okular::PixmapRequest *request ) { dviPageInfo *pageInfo = new dviPageInfo(); pageSize ps; QImage ret; pageInfo->width = request->width(); pageInfo->height = request->height(); pageInfo->pageNumber = request->pageNumber() + 1; // pageInfo->resolution = m_resolution; QMutexLocker lock( userMutex() ); if ( m_dviRenderer ) { SimplePageSize s = m_dviRenderer->sizeOfPage( pageInfo->pageNumber ); /* if ( s.width() != pageInfo->width) */ // if (!useDocumentSpecifiedSize) // s = userPreferredSize; if (s.isValid()) { ps = s; /* it should be the user specified size, if any, instead */ } pageInfo->resolution = (double)(pageInfo->width)/ps.width().getLength_in_inch(); #if 0 qCDebug(OkularDviDebug) << *request << ", res:" << pageInfo->resolution << " - (" << pageInfo->width << "," << ps.width().getLength_in_inch() << ")," << ps.width().getLength_in_mm() << endl; #endif m_dviRenderer->drawPage( pageInfo ); if ( ! pageInfo->img.isNull() ) { qCDebug(OkularDviDebug) << "Image OK"; ret = pageInfo->img; if ( !m_linkGenerated[ request->pageNumber() ] ) { request->page()->setObjectRects( generateDviLinks( pageInfo ) ); m_linkGenerated[ request->pageNumber() ] = true; } } } lock.unlock(); delete pageInfo; return ret; } Okular::TextPage* DviGenerator::textPage( Okular::TextRequest *request ) { const Okular::Page *page = request->page(); qCDebug(OkularDviDebug); dviPageInfo *pageInfo = new dviPageInfo(); pageSize ps; pageInfo->width=page->width(); pageInfo->height=page->height(); pageInfo->pageNumber = page->number() + 1; pageInfo->resolution = m_resolution; QMutexLocker lock( userMutex() ); // get page text from m_dviRenderer Okular::TextPage *ktp = nullptr; if ( m_dviRenderer ) { SimplePageSize s = m_dviRenderer->sizeOfPage( pageInfo->pageNumber ); pageInfo->resolution = (double)(pageInfo->width)/ps.width().getLength_in_inch(); m_dviRenderer->getText( pageInfo ); lock.unlock(); ktp = extractTextFromPage( pageInfo ); } delete pageInfo; return ktp; } Okular::TextPage *DviGenerator::extractTextFromPage( dviPageInfo *pageInfo ) { QList textOfThePage; QVector::ConstIterator it = pageInfo->textBoxList.constBegin(); QVector::ConstIterator itEnd = pageInfo->textBoxList.constEnd(); QRect tmpRect; int pageWidth = pageInfo->width, pageHeight = pageInfo->height; for ( ; it != itEnd ; ++it ) { TextBox curTB = *it; #if 0 qCDebug(OkularDviDebug) << "orientation: " << orientation << ", curTB.box: " << curTB.box << ", tmpRect: " << tmpRect << ", ( " << pageWidth << "," << pageHeight << " )" < &keys ) const { Okular::DocumentInfo docInfo; if ( keys.contains( Okular::DocumentInfo::MimeType ) ) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/x-dvi") ); QMutexLocker lock( userMutex() ); if ( m_dviRenderer && m_dviRenderer->dviFile ) { dvifile *dvif = m_dviRenderer->dviFile; // read properties from dvif //docInfo.set( "filename", dvif->filename, i18n("Filename") ); if ( keys.contains( Okular::DocumentInfo::CustomKeys ) ) docInfo.set( QStringLiteral("generatorDate"), dvif->generatorString, i18n("Generator/Date") ); if ( keys.contains( Okular::DocumentInfo::Pages ) ) docInfo.set( Okular::DocumentInfo::Pages, QString::number( dvif->total_pages ) ); } return docInfo; } const Okular::DocumentSynopsis *DviGenerator::generateDocumentSynopsis() { if ( m_docSynopsis ) return m_docSynopsis; m_docSynopsis = new Okular::DocumentSynopsis(); userMutex()->lock(); QVector prebookmarks = m_dviRenderer->getPrebookmarks(); userMutex()->unlock(); if ( prebookmarks.isEmpty() ) return m_docSynopsis; QStack stack; QVector::ConstIterator it = prebookmarks.constBegin(); QVector::ConstIterator itEnd = prebookmarks.constEnd(); for( ; it != itEnd; ++it ) { QDomElement domel = m_docSynopsis->createElement( (*it).title ); Anchor a = m_dviRenderer->findAnchor((*it).anchorName); if ( a.isValid() ) { Okular::DocumentViewport vp; const Okular::Page *p = document()->page( a.page - 1 ); fillViewportFromAnchor( vp, a, (int)p->width(), (int)p->height() ); domel.setAttribute( QStringLiteral("Viewport"), vp.toString() ); } if ( stack.isEmpty() ) m_docSynopsis->appendChild( domel ); else { stack.top().appendChild( domel ); stack.pop(); } for ( int i = 0; i < (*it).noOfChildren; ++i ) stack.push( domel ); } return m_docSynopsis; } Okular::FontInfo::List DviGenerator::fontsForPage( int page ) { Q_UNUSED( page ); Okular::FontInfo::List list; // the list of the fonts is extracted once if ( m_fontExtracted ) return list; if ( m_dviRenderer && m_dviRenderer->dviFile && m_dviRenderer->dviFile->font_pool ) { QList fonts = m_dviRenderer->dviFile->font_pool->fontList; foreach (const TeXFontDefinition* font, fonts) { Okular::FontInfo of; QString name; int zoom = (int)(font->enlargement*100 + 0.5); #ifdef HAVE_FREETYPE if ( font->getFullFontName().isEmpty() ) { name = QStringLiteral( "%1, %2%" ) .arg( font->fontname ) .arg( zoom ); } else { name = QStringLiteral( "%1 (%2), %3%" ) .arg( font->fontname, font->getFullFontName(), QString::number(zoom) ); } #else name = QString( "%1, %2%" ) .arg( font->fontname ) .arg( zoom ); #endif of.setName( name ); QString fontFileName; if (!(font->flags & TeXFontDefinition::FONT_VIRTUAL)) { if ( font->font != nullptr ) fontFileName = font->font->errorMessage; else fontFileName = i18n("Font file not found"); if ( fontFileName.isEmpty() ) fontFileName = font->filename; } of.setFile( fontFileName ); Okular::FontInfo::FontType ft; switch ( font->getFontType() ) { case TeXFontDefinition::TEX_PK: ft = Okular::FontInfo::TeXPK; break; case TeXFontDefinition::TEX_VIRTUAL: ft = Okular::FontInfo::TeXVirtual; break; case TeXFontDefinition::TEX_FONTMETRIC: ft = Okular::FontInfo::TeXFontMetric; break; case TeXFontDefinition::FREETYPE: ft = Okular::FontInfo::TeXFreeTypeHandled; break; } of.setType( ft ); // DVI has not the concept of "font embedding" of.setEmbedType( Okular::FontInfo::NotEmbedded ); of.setCanBeExtracted( false ); list.append( of ); } m_fontExtracted = true; } return list; } void DviGenerator::loadPages( QVector< Okular::Page * > &pagesVector ) { QSize pageRequiredSize; int numofpages = m_dviRenderer->dviFile->total_pages; pagesVector.resize( numofpages ); m_linkGenerated.fill( false, numofpages ); //qCDebug(OkularDviDebug) << "resolution:" << m_resolution << ", dviFile->preferred?"; /* get the suggested size */ if ( m_dviRenderer->dviFile->suggestedPageSize ) { pageRequiredSize = m_dviRenderer->dviFile->suggestedPageSize->sizeInPixel( m_resolution ); } else { pageSize ps; pageRequiredSize = ps.sizeInPixel( m_resolution ); } for ( int i = 0; i < numofpages; ++i ) { //qCDebug(OkularDviDebug) << "getting status of page" << i << ":"; if ( pagesVector[i] ) { delete pagesVector[i]; } Okular::Page * page = new Okular::Page( i, pageRequiredSize.width(), pageRequiredSize.height(), Okular::Rotation0 ); pagesVector[i] = page; } qCDebug(OkularDviDebug) << "pagesVector successfully inizialized!"; // filling the pages with the source references rects const QVector& sourceAnchors = m_dviRenderer->sourceAnchors(); QVector< QLinkedList< Okular::SourceRefObjectRect * > > refRects( numofpages ); foreach ( const DVI_SourceFileAnchor& sfa, sourceAnchors ) { if ( sfa.page < 1 || (int)sfa.page > numofpages ) continue; Okular::NormalizedPoint p( -1.0, (double)sfa.distance_from_top.getLength_in_pixel( dpi().height() ) / (double)pageRequiredSize.height() ); Okular::SourceReference * sourceRef = new Okular::SourceReference( sfa.fileName, sfa.line ); refRects[ sfa.page - 1 ].append( new Okular::SourceRefObjectRect( p, sourceRef ) ); } for ( int i = 0; i < refRects.size(); ++i ) if ( !refRects.at(i).isEmpty() ) pagesVector[i]->setSourceReferences( refRects.at(i) ); } bool DviGenerator::print( QPrinter& printer ) { // Create tempfile to write to QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps" )); if ( !tf.open() ) return false; QList pageList = Okular::FilePrinter::pageList( printer, m_dviRenderer->totalPages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); QString pages; QStringList printOptions; // List of pages to print. foreach ( int p, pageList ) { pages += QStringLiteral(",%1").arg(p); } if ( !pages.isEmpty() ) printOptions << QStringLiteral("-pp") << pages.mid(1); QEventLoop el; m_dviRenderer->setEventLoop( &el ); m_dviRenderer->exportPS( tf.fileName(), printOptions, &printer, document()->orientation() ); tf.close(); // Error messages are handled by the generator - ugly, but it works. return true; } QVariant DviGenerator::metaData( const QString & key, const QVariant & option ) const { if ( key == QLatin1String("NamedViewport") && !option.toString().isEmpty() ) { const Anchor anchor = m_dviRenderer->parseReference( option.toString() ); if ( anchor.isValid() ) { const Okular::Page *page = document()->page( anchor.page - 1 ); Q_ASSERT_X( page, "DviGenerator::metaData()", "NULL page as result of valid Anchor" ); Okular::DocumentViewport viewport; fillViewportFromAnchor( viewport, anchor, page ); if ( viewport.isValid() ) { return viewport.toString(); } } } return QVariant(); } Q_LOGGING_CATEGORY(OkularDviDebug, "org.kde.okular.generators.dvi.core", QtWarningMsg) Q_LOGGING_CATEGORY(OkularDviShellDebug, "org.kde.okular.generators.dvi.shell", QtWarningMsg) #include "generator_dvi.moc" diff --git a/generators/dvi/pageSize.cpp b/generators/dvi/pageSize.cpp index 1ae72068d..6c380bc96 100644 --- a/generators/dvi/pageSize.cpp +++ b/generators/dvi/pageSize.cpp @@ -1,362 +1,362 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // pageSize.cpp // // Part of KVIEWSHELL - A framework for multipage text/gfx viewers // // (C) 2002-2003 Stefan Kebekus // Distributed under the GPL #include #include "pageSize.h" #include "debug_dvi.h" #include "length.h" #include -#include +#include struct pageSizeItem { const char *name; float width; // in mm float height; // in mm const char *preferredUnit; }; #define defaultMetricPaperSize 4 // Default paper size is "DIN A4" #define defaultImperialPaperSize 8 // Default paper size is "US Letter" static const pageSizeItem staticList[] = { {"DIN A0", 841.0f, 1189.0f, "mm"}, {"DIN A1", 594.0f, 841.0f, "mm"}, {"DIN A2", 420.0f, 594.0f, "mm"}, {"DIN A3", 297.0f, 420.0f, "mm"}, {"DIN A4", 210.0f, 297.0f, "mm"}, {"DIN A5", 148.5f, 210.0f, "mm"}, {"DIN B4", 250.0f, 353.0f, "mm"}, {"DIN B5", 176.0f, 250.0f, "mm"}, {"US Letter", 215.9f, 279.4f, "in"}, {"US Legal", 215.9f, 355.6f, "in"}, {nullptr, 0.0f, 0.0f, nullptr} // marks the end of the list. }; pageSize::pageSize() { currentSize = defaultPageSize(); pageWidth.setLength_in_mm(staticList[currentSize].width); pageHeight.setLength_in_mm(staticList[currentSize].height); } pageSize::pageSize(const SimplePageSize& s) { pageWidth = s.width(); pageHeight = s.height(); rectifySizes(); reconstructCurrentSize(); } bool pageSize::setPageSize(const QString& name) { // See if we can recognize the string QString currentName; for(int i=0; staticList[i].name != nullptr; i++) { currentName = QString::fromLocal8Bit(staticList[i].name); if (currentName == name) { currentSize = i; // Set page width/height accordingly pageWidth.setLength_in_mm(staticList[currentSize].width); pageHeight.setLength_in_mm(staticList[currentSize].height); emit(sizeChanged(*this)); return true; } } // Check if the string contains 'x'. If yes, we assume it is of type // "x". If yes, the first number is interpreted as // the width in mm, the second as the height in mm if (name.indexOf(QLatin1Char('x')) >= 0) { bool wok, hok; float pageWidth_tmp = name.section(QLatin1Char('x'),0,0).toFloat(&wok); float pageHeight_tmp = name.section(QLatin1Char('x'),1,1).toFloat(&hok); if (wok && hok) { pageWidth.setLength_in_mm(pageWidth_tmp); pageHeight.setLength_in_mm(pageHeight_tmp); rectifySizes(); reconstructCurrentSize(); emit(sizeChanged(*this)); return true; } } // Check if the string contains ','. If yes, we assume it is of type // ",". The first number is supposed to // be the width, the second the height. if (name.indexOf(QLatin1Char(',')) >= 0) { bool wok, hok; float pageWidth_tmp = Length::convertToMM(name.section(QLatin1Char(','),0,0), &wok); float pageHeight_tmp = Length::convertToMM(name.section(QLatin1Char(','),1,1), &hok); if (wok && hok) { pageWidth.setLength_in_mm(pageWidth_tmp); pageHeight.setLength_in_mm(pageHeight_tmp); rectifySizes(); reconstructCurrentSize(); emit(sizeChanged(*this)); return true; } } // Last resource. Set the default, in case the string is // unintelligible to us. currentSize = defaultPageSize(); pageWidth.setLength_in_mm(staticList[currentSize].width); pageHeight.setLength_in_mm(staticList[currentSize].height); qCCritical(OkularDviShellDebug) << "pageSize::setPageSize: could not parse '" << name << "'. Using " << staticList[currentSize].name << " as a default." << endl; emit(sizeChanged(*this)); return false; } void pageSize::setPageSize(double width, double height) { SimplePageSize oldPage = *this; pageWidth.setLength_in_mm(width); pageHeight.setLength_in_mm(height); rectifySizes(); reconstructCurrentSize(); if ( !isNearlyEqual(oldPage)) emit(sizeChanged(*this)); } void pageSize::setPageSize(const QString& width, const QString& _widthUnits, const QString& height, const QString& _heightUnits) { SimplePageSize oldPage = *this; double w = width.toFloat(); double h = height.toFloat(); QString widthUnits = _widthUnits; if ((widthUnits != QLatin1String("cm")) && (widthUnits != QLatin1String("mm")) && (widthUnits != QLatin1String("in"))) { qCCritical(OkularDviShellDebug) << "Unrecognized page width unit '" << widthUnits << "'. Assuming mm" << endl; widthUnits = QStringLiteral("mm"); } pageWidth.setLength_in_mm(w); if (widthUnits == QLatin1String("cm")) pageWidth.setLength_in_cm(w); if (widthUnits == QLatin1String("in")) pageWidth.setLength_in_inch(w); QString heightUnits = _heightUnits; if ((heightUnits != QLatin1String("cm")) && (heightUnits != QLatin1String("mm")) && (heightUnits != QLatin1String("in"))) { qCCritical(OkularDviShellDebug) << "Unrecognized page height unit '" << widthUnits << "'. Assuming mm" << endl; heightUnits = QStringLiteral("mm"); } pageHeight.setLength_in_mm(h); if (heightUnits == QLatin1String("cm")) pageHeight.setLength_in_cm(h); if (heightUnits == QLatin1String("in")) pageHeight.setLength_in_inch(h); rectifySizes(); reconstructCurrentSize(); if ( !isNearlyEqual(oldPage)) emit(sizeChanged(*this)); } pageSize &pageSize::operator= (const pageSize &src) { SimplePageSize oldPage = *this; currentSize = src.currentSize; pageWidth = src.pageWidth; pageHeight = src.pageHeight; if ( !isNearlyEqual(oldPage)) emit(sizeChanged(*this)); return *this; } void pageSize::rectifySizes() { // Now do some sanity checks to make sure that values are not // outrageous. We allow values between 5cm and 50cm. if (pageWidth.getLength_in_mm() < 50) pageWidth.setLength_in_mm(50.0); if (pageWidth.getLength_in_mm() > 1200) pageWidth.setLength_in_mm(1200); if (pageHeight.getLength_in_mm() < 50) pageHeight.setLength_in_mm(50); if (pageHeight.getLength_in_mm() > 1200) pageHeight.setLength_in_mm(1200); return; } QString pageSize::preferredUnit() const { if (currentSize >= 0) return QString::fromLocal8Bit(staticList[currentSize].preferredUnit); // User-defined size. Give a preferred unit depening on the locale. if (QLocale::system().measurementSystem() == QLocale::MetricSystem) return QStringLiteral("mm"); else return QStringLiteral("in"); } QString pageSize::widthString(const QString& unit) const { QString answer = QStringLiteral("--"); if (unit == QLatin1String("cm")) answer.setNum(pageWidth.getLength_in_cm()); if (unit == QLatin1String("mm")) answer.setNum(pageWidth.getLength_in_mm()); if (unit == QLatin1String("in")) answer.setNum(pageWidth.getLength_in_inch()); return answer; } QString pageSize::heightString(const QString& unit) const { QString answer = QStringLiteral("--"); if (unit == QLatin1String("cm")) answer.setNum(pageHeight.getLength_in_cm()); if (unit == QLatin1String("mm")) answer.setNum(pageHeight.getLength_in_mm()); if (unit == QLatin1String("in")) answer.setNum(pageHeight.getLength_in_inch()); return answer; } QStringList pageSize::pageSizeNames() { QStringList names; for(int i=0; staticList[i].name != nullptr; i++) names << QString::fromLocal8Bit(staticList[i].name); return names; } QString pageSize::formatName() const { if (currentSize >= 0) return QString::fromLocal8Bit(staticList[currentSize].name); else return QString(); } int pageSize::getOrientation() const { if (currentSize == -1) { qCCritical(OkularDviShellDebug) << "pageSize::getOrientation: getOrientation called for page format that does not have a name." << endl; return 0; } if (pageWidth.getLength_in_mm() == staticList[currentSize].width) return 0; else return 1; } void pageSize::setOrientation(int orient) { if (currentSize == -1) { qCCritical(OkularDviShellDebug) << "pageSize::setOrientation: setOrientation called for page format that does not have a name." << endl; return; } if (orient == 1) { pageWidth.setLength_in_mm(staticList[currentSize].height); pageHeight.setLength_in_mm(staticList[currentSize].width); } else { pageWidth.setLength_in_mm(staticList[currentSize].width); pageHeight.setLength_in_mm(staticList[currentSize].height); } emit(sizeChanged(*this)); } QString pageSize::serialize() const { if ((currentSize >= 0) && (fabs(staticList[currentSize].height-pageHeight.getLength_in_mm()) <= 0.5)) return QString::fromLocal8Bit(staticList[currentSize].name); else return QStringLiteral("%1x%2").arg(pageWidth.getLength_in_mm()).arg(pageHeight.getLength_in_mm()); } QString pageSize::description() const { if (!isValid()) return QString(); QString size = QStringLiteral(" "); if (formatNumber() == -1) { if (QLocale::system().measurementSystem() == QLocale::MetricSystem) size += QStringLiteral("%1x%2 mm").arg(width().getLength_in_mm(), 0, 'f', 0).arg(height().getLength_in_mm(), 0, 'f', 0); else size += QStringLiteral("%1x%2 in").arg(width().getLength_in_inch(), 0, 'g', 2).arg(height().getLength_in_inch(), 0, 'g', 2); } else { size += formatName() + QLatin1Char('/'); if (getOrientation() == 0) size += i18n("portrait"); else size += i18n("landscape"); } return size + QLatin1Char(' '); } void pageSize::reconstructCurrentSize() { for(int i=0; staticList[i].name != nullptr; i++) { if ((fabs(staticList[i].width - pageWidth.getLength_in_mm()) <= 2) && (fabs(staticList[i].height - pageHeight.getLength_in_mm()) <= 2)) { currentSize = i; pageWidth.setLength_in_mm(staticList[currentSize].width); pageHeight.setLength_in_mm(staticList[currentSize].height); return; } if ((fabs(staticList[i].height - pageWidth.getLength_in_mm()) <= 2) && (fabs(staticList[i].width - pageHeight.getLength_in_mm()) <= 2)) { currentSize = i; pageWidth.setLength_in_mm(staticList[currentSize].height); pageHeight.setLength_in_mm(staticList[currentSize].width); return; } } currentSize = -1; return; } int pageSize::defaultPageSize() { // FIXME: static_cast(KLocale::global()->pageSize()) // is the proper solution here. Then you can determine the values // without using your hardcoded table too! if (QLocale::system().measurementSystem() == QLocale::MetricSystem) return defaultMetricPaperSize; else return defaultImperialPaperSize; } diff --git a/generators/dvi/psgs.cpp b/generators/dvi/psgs.cpp index 7a404b9d7..fec91b97b 100644 --- a/generators/dvi/psgs.cpp +++ b/generators/dvi/psgs.cpp @@ -1,353 +1,353 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // // ghostscript_interface // // Part of KDVI - A framework for multipage text/gfx viewers // // (C) 2004 Stefan Kebekus // Distributed under the GPL #include #include "psgs.h" #include "psheader.cpp" #include "dviFile.h" #include "debug_dvi.h" #include "pageNumber.h" #include "debug_dvi.h" #include #include #include #include -#include +#include #include #include #include #include #include //#define DEBUG_PSGS //extern char psheader[]; pageInfo::pageInfo(const QString& _PostScriptString) { PostScriptString = new QString(_PostScriptString); background = Qt::white; permanentBackground = Qt::white; } pageInfo::~pageInfo() { if (PostScriptString != nullptr) delete PostScriptString; } // ====================================================== ghostscript_interface::ghostscript_interface() { PostScriptHeaderString = new QString(); knownDevices.append(QStringLiteral("png16m")); knownDevices.append(QStringLiteral("jpeg")); knownDevices.append(QStringLiteral("pnn")); knownDevices.append(QStringLiteral("pnnraw")); gsDevice = knownDevices.begin(); } ghostscript_interface::~ghostscript_interface() { if (PostScriptHeaderString != nullptr) delete PostScriptHeaderString; qDeleteAll(pageList); } void ghostscript_interface::setPostScript(const PageNumber& page, const QString& PostScript) { #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << "ghostscript_interface::setPostScript( " << page << ", ... )"; #endif if (pageList.value(page) == 0) { pageInfo *info = new pageInfo(PostScript); // Check if dict is big enough if (pageList.count() > pageList.capacity() -2) pageList.reserve(pageList.capacity()*2); pageList.insert(page, info); } else *(pageList.value(page)->PostScriptString) = PostScript; } void ghostscript_interface::setIncludePath(const QString &_includePath) { if (_includePath.isEmpty()) includePath = QLatin1Char('*'); // Allow all files else includePath = _includePath + QStringLiteral("/*"); } void ghostscript_interface::setBackgroundColor(const PageNumber& page, const QColor& background_color, bool permanent) { #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << "ghostscript_interface::setBackgroundColor( " << page << ", " << background_color << " )"; #endif if (pageList.value(page) == 0) { pageInfo *info = new pageInfo(QString::null); //krazy:exclude=nullstrassign for old broken gcc info->background = background_color; if (permanent) info->permanentBackground = background_color; // Check if dict is big enough if (pageList.count() > pageList.capacity() -2) pageList.reserve(pageList.capacity()*2); pageList.insert(page, info); } else { pageList.value(page)->background = background_color; if (permanent) pageList.value(page)->permanentBackground = background_color; } } void ghostscript_interface::restoreBackgroundColor(const PageNumber& page) { #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << "ghostscript_interface::restoreBackgroundColor( " << page << " )"; #endif if (pageList.value(page) == 0) return; pageInfo *info = pageList.value(page); info->background = info->permanentBackground; } // Returns the background color for a certain page. This color is // always guaranteed to be valid QColor ghostscript_interface::getBackgroundColor(const PageNumber& page) const { #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << "ghostscript_interface::getBackgroundColor( " << page << " )"; #endif if (pageList.value(page) == 0) return Qt::white; else return pageList.value(page)->background; } void ghostscript_interface::clear() { PostScriptHeaderString->truncate(0); // Deletes all items, removes temporary files, etc. qDeleteAll(pageList); pageList.clear(); } void ghostscript_interface::gs_generate_graphics_file(const PageNumber& page, const QString& filename, long magnification) { #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << "ghostscript_interface::gs_generate_graphics_file( " << page << ", " << filename << " )"; #endif if (knownDevices.isEmpty()) { qCCritical(OkularDviDebug) << "No known devices found" << endl; return; } pageInfo *info = pageList.value(page); // Generate a PNG-file // Step 1: Write the PostScriptString to a File QTemporaryFile PSfile(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); PSfile.setAutoRemove(false); PSfile.open(); const QString PSfileName = PSfile.fileName(); QTextStream os(&PSfile); os << "%!PS-Adobe-2.0\n" << "%%Creator: kdvi\n" << "%%Title: KDVI temporary PostScript\n" << "%%Pages: 1\n" << "%%PageOrder: Ascend\n" // HSize and VSize in 1/72 inch << "%%BoundingBox: 0 0 " << (qint32)(72*(pixel_page_w/resolution)) << ' ' << (qint32)(72*(pixel_page_h/resolution)) << '\n' << "%%EndComments\n" << "%!\n" << psheader << "TeXDict begin " // HSize in (1/(65781.76*72))inch << (qint32)(72*65781*(pixel_page_w/resolution)) << ' ' // VSize in (1/(65781.76*72))inch << (qint32)(72*65781*(pixel_page_h/resolution)) << ' ' // Magnification << (qint32)(magnification) // dpi and vdpi << " 300 300" // Name << " (test.dvi)" << " @start end\n" << "TeXDict begin\n" // Start page << "1 0 bop 0 0 a \n"; if (!PostScriptHeaderString->toLatin1().isNull()) os << PostScriptHeaderString->toLatin1(); if (info->background != Qt::white) { QString colorCommand = QStringLiteral("gsave %1 %2 %3 setrgbcolor clippath fill grestore\n"). arg(info->background.red()/255.0). arg(info->background.green()/255.0). arg(info->background.blue()/255.0); os << colorCommand.toLatin1(); } if (!info->PostScriptString->isNull()) os << *(info->PostScriptString); os << "end\n" << "showpage \n"; PSfile.close(); // Step 2: Call GS with the File QFile::remove(filename); KProcess proc; proc.setOutputChannelMode(KProcess::SeparateChannels); QStringList argus; argus << QStringLiteral("gs"); argus << QStringLiteral("-dSAFER") << QStringLiteral("-dPARANOIDSAFER") << QStringLiteral("-dDELAYSAFER") << QStringLiteral("-dNOPAUSE") << QStringLiteral("-dBATCH"); argus << QStringLiteral("-sDEVICE=%1").arg(*gsDevice); argus << QStringLiteral("-sOutputFile=%1").arg(filename); argus << QStringLiteral("-sExtraIncludePath=%1").arg(includePath); argus << QStringLiteral("-g%1x%2").arg(pixel_page_w).arg(pixel_page_h); // page size in pixels argus << QStringLiteral("-r%1").arg(resolution); // resolution in dpi argus << QStringLiteral("-dTextAlphaBits=4 -dGraphicsAlphaBits=2"); // Antialiasing argus << QStringLiteral("-c") << QStringLiteral("<< /PermitFileReading [ ExtraIncludePath ] /PermitFileWriting [] /PermitFileControl [] >> setuserparams .locksafe"); argus << QStringLiteral("-f") << PSfileName; #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << argus.join(" "); #endif proc << argus; int res = proc.execute(); if ( res ) { // Starting ghostscript did not work. // TODO: Issue error message, switch PS support off. qCCritical(OkularDviDebug) << "ghostview could not be started" << endl; } PSfile.remove(); // Check if gs has indeed produced a file. if (QFile::exists(filename) == false) { qCCritical(OkularDviDebug) << "GS did not produce output." << endl; // No. Check is the reason is that the device is not compiled into // ghostscript. If so, try again with another device. QString GSoutput; proc.setReadChannel(QProcess::StandardOutput); while(proc.canReadLine()) { GSoutput = QString::fromLocal8Bit(proc.readLine()); if (GSoutput.contains(QStringLiteral("Unknown device"))) { qCDebug(OkularDviDebug) << QString::fromLatin1("The version of ghostview installed on this computer does not support " "the '%1' ghostview device driver.").arg(*gsDevice) << endl; knownDevices.erase(gsDevice); gsDevice = knownDevices.begin(); if (knownDevices.isEmpty()) // TODO: show a requestor of some sort. emit error(i18n("The version of Ghostview that is installed on this computer does not contain " "any of the Ghostview device drivers that are known to Okular. PostScript " "support has therefore been turned off in Okular."), -1); #if 0 i18n("

The Ghostview program, which Okular uses internally to display the " "PostScript graphics that is included in this DVI file, is generally able to " "write its output in a variety of formats. The sub-programs that Ghostview uses " "for these tasks are called 'device drivers'; there is one device driver for " "each format that Ghostview is able to write. Different versions of Ghostview " "often have different sets of device drivers available. It seems that the " "version of Ghostview that is installed on this computer does not contain " "any of the device drivers that are known to Okular.

" "

It seems unlikely that a regular installation of Ghostview would not contain " "these drivers. This error may therefore point to a serious misconfiguration of " "the Ghostview installation on your computer.

" "

If you want to fix the problems with Ghostview, you can use the command " "gs --help to display the list of device drivers contained in " "Ghostview. Among others, Okular can use the 'png256', 'jpeg' and 'pnm' " "drivers. Note that Okular needs to be restarted to re-enable PostScript support." "

")); #endif else { qCDebug(OkularDviDebug) << QStringLiteral("Okular will now try to use the '%1' device driver.").arg(*gsDevice); gs_generate_graphics_file(page, filename, magnification); } return; } } } } void ghostscript_interface::graphics(const PageNumber& page, double dpi, long magnification, QPainter* paint) { #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << "ghostscript_interface::graphics( " << page << ", " << dpi << ", ... ) called."; #endif if (paint == nullptr) { qCCritical(OkularDviDebug) << "ghostscript_interface::graphics(PageNumber page, double dpi, long magnification, QPainter *paint) called with paint == 0" << endl; return; } resolution = dpi; pixel_page_w = paint->viewport().width(); pixel_page_h = paint->viewport().height(); pageInfo *info = pageList.value(page); // No PostScript? Then return immediately. if ((info == nullptr) || (info->PostScriptString->isEmpty())) { #ifdef DEBUG_PSGS qCDebug(OkularDviDebug) << "No PostScript found. Not drawing anything."; #endif return; } QTemporaryFile gfxFile; gfxFile.open(); const QString gfxFileName = gfxFile.fileName(); // We are want the filename, not the file. gfxFile.close(); gs_generate_graphics_file(page, gfxFileName, magnification); QImage MemoryCopy(gfxFileName); paint->drawImage(0, 0, MemoryCopy); return; } QString ghostscript_interface::locateEPSfile(const QString &filename, const QUrl &base) { // If the base URL indicates that the DVI file is local, try to find // the graphics file in the directory where the DVI file resides if (base.isLocalFile()) { QString path = base.path(); // -> "/bar/foo.dvi" QFileInfo fi1(path); QFileInfo fi2(fi1.dir(),filename); if (fi2.exists()) return fi2.absoluteFilePath(); } // Otherwise, use kpsewhich to find the eps file. KProcess proc; proc << QStringLiteral("kpsewhich") << filename; proc.execute(); return QString::fromLocal8Bit(proc.readLine().trimmed()); } diff --git a/generators/dvi/psgs.h b/generators/dvi/psgs.h index 14fe4384b..f021945fb 100644 --- a/generators/dvi/psgs.h +++ b/generators/dvi/psgs.h @@ -1,109 +1,109 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // // ghostscript_interface // // Part of KDVI - A framework for multipage text/gfx viewers // // (C) 2004 Stefan Kebekus // Distributed under the GPL #ifndef _PSGS_H_ #define _PSGS_H_ #include #include -#include +#include #include #include class QUrl; class PageNumber; class QPainter; class pageInfo { public: pageInfo(const QString& _PostScriptString); ~pageInfo(); QColor background; QColor permanentBackground; QString *PostScriptString; }; class ghostscript_interface : public QObject { Q_OBJECT public: ghostscript_interface(); ~ghostscript_interface(); void clear(); // sets the PostScript which is used on a certain page void setPostScript(const PageNumber& page, const QString& PostScript); // sets path from additional postscript files may be read void setIncludePath(const QString &_includePath); // Sets the background color for a certain page. If permanent is false then the original // background color can be restored by calling restoreBackground(page). // The Option permanent = false is used when we want to display a different paper // color as the one specified in the dvi file. void setBackgroundColor(const PageNumber& page, const QColor& background_color, bool permanent = true); // Restore the background to the color which was specified by the last call to setBackgroundColor() // With option permanent = true. void restoreBackgroundColor(const PageNumber& page); // Draws the graphics of the page into the painter, if possible. If // the page does not contain any graphics, nothing happens void graphics(const PageNumber& page, double dpi, long magnification, QPainter* paint); // Returns the background color for a certain page. If no color was // set, Qt::white is returned. QColor getBackgroundColor(const PageNumber& page) const; QString *PostScriptHeaderString; /** This method tries to find the PostScript file 'filename' in the DVI file's directory (if the base-URL indicates that the DVI file is local), and, if that fails, uses kpsewhich to find the file. If the file is found, the full path (including file name) is returned. Otherwise, the method returns the first argument. TODO: use the DVI file's baseURL, once this is implemented. */ static QString locateEPSfile(const QString &filename, const QUrl &base); private: void gs_generate_graphics_file(const PageNumber& page, const QString& filename, long magnification); QHash pageList; double resolution; // in dots per inch int pixel_page_w; // in pixels int pixel_page_h; // in pixels QString includePath; // Output device that ghostscript is supposed tp use. Default is // "png256". If that does not work, gs_generate_graphics_file will // automatically try other known device drivers. If no known output // device can be found, something is badly wrong. In that case, // "gsDevice" is set to an empty string, and // gs_generate_graphics_file will return immediately. QList::iterator gsDevice; // A list of known devices, set by the constructor. This includes // "png256", "pnm". If a device is found to not work, its name is // removed from the list, and another device name is tried. QStringList knownDevices; Q_SIGNALS: /** Passed through to the top-level kpart. */ void error( const QString &message, int duration ); }; #endif diff --git a/generators/dvi/simplePageSize.cpp b/generators/dvi/simplePageSize.cpp index 61687c6e6..e7e6f4d29 100644 --- a/generators/dvi/simplePageSize.cpp +++ b/generators/dvi/simplePageSize.cpp @@ -1,51 +1,51 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // SimplePageSize.cpp // // Part of KVIEWSHELL - A framework for multipage text/gfx viewers // // (C) 2002-2005 Stefan Kebekus // Distributed under the GPL #include #include "simplePageSize.h" #include "debug_dvi.h" -#include +#include #include double SimplePageSize::zoomForHeight(quint32 height, const QPaintDevice& pd) const { if (!isValid()) { qCCritical(OkularDviShellDebug) << "SimplePageSize::zoomForHeight() called when paper height was invalid" << endl; return 0.1; } return double(height) / (pd.logicalDpiY() * pageHeight.getLength_in_inch()); } double SimplePageSize::zoomForWidth(quint32 width, const QPaintDevice& pd) const { if (!isValid()) { qCCritical(OkularDviShellDebug) << "SimplePageSize::zoomForWidth() called when paper width was invalid" << endl; return 0.1; } return double(width) / (pd.logicalDpiX() * pageWidth.getLength_in_inch()); } double SimplePageSize::zoomToFitInto(const SimplePageSize &target) const { if (!isValid() || isSmall() || !target.isValid()) { qCWarning(OkularDviShellDebug) << "SimplePageSize::zoomToFitInto(...) with unsuitable source of target" ; return 1.0; } double z1 = target.width() / pageWidth; double z2 = target.height() / pageHeight; return qMin(z1,z2); } diff --git a/generators/dvi/util.cpp b/generators/dvi/util.cpp index 4557eaa88..7a993aa9e 100644 --- a/generators/dvi/util.cpp +++ b/generators/dvi/util.cpp @@ -1,116 +1,116 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- /* * Copyright (c) 1994 Paul Vojta. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * NOTE: * xdvi is based on prior work as noted in the modification history, below. */ /* * DVI previewer for X. * * Eric Cooper, CMU, September 1985. * * Code derived from dvi-imagen.c. * * Modification history: * 1/1986 Modified for X.10 --Bob Scheifler, MIT LCS. * 7/1988 Modified for X.11 --Mark Eichin, MIT * 12/1988 Added 'R' option, toolkit, magnifying glass * --Paul Vojta, UC Berkeley. * 2/1989 Added tpic support --Jeffrey Lee, U of Toronto * 4/1989 Modified for System V --Donald Richardson, Clarkson Univ. * 3/1990 Added VMS support --Scott Allendorf, U of Iowa * 7/1990 Added reflection mode --Michael Pak, Hebrew U of Jerusalem * 1/1992 Added greyscale code --Till Brychcy, Techn. Univ. Muenchen * and Lee Hetherington, MIT * 4/1994 Added DPS support, bounding box * --Ricardo Telichevesky * and Luis Miguel Silveira, MIT RLE. */ #include #include "debug_dvi.h" #include "xdvi.h" #include "debug_dvi.h" #include #include #include -#include +#include /* * General utility routines. */ /* * Print error message and quit. */ void oops(const QString& message) { qCCritical(OkularDviDebug) << "Fatal Error:" << message << endl; KMessageBox::error( nullptr, i18n("Fatal error.\n\n") + message + i18n("\n\n\ This probably means that either you found a bug in Okular,\n\ or that the DVI file, or auxiliary files (such as font files, \n\ or virtual font files) were really badly broken.\n\ Okular will abort after this message. If you believe that you \n\ found a bug, or that Okular should behave better in this situation\n\ please report the problem.")); exit(1); } /* * Read size bytes from the FILE fp, constructing them into a * signed/unsigned integer. */ unsigned long num(FILE *fp, int size) { long x = 0; while (size--) x = (x << 8) | one(fp); return x; } long snum(FILE *fp, int size) { long x; #ifdef __STDC__ x = (signed char) getc(fp); #else x = (unsigned char) getc(fp); if (x & 0x80) x -= 0x100; #endif while (--size) x = (x << 8) | one(fp); return x; } diff --git a/generators/epub/converter.cpp b/generators/epub/converter.cpp index ade26a049..071951800 100644 --- a/generators/epub/converter.cpp +++ b/generators/epub/converter.cpp @@ -1,457 +1,457 @@ /*************************************************************************** * Copyright (C) 2008 by Ely Levy * * * * 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 "converter.h" -#include -#include -#include +#include +#include +#include #include #include #include // Because of the HACK -#include +#include #include #include #include #include #include #include using namespace Epub; Converter::Converter() : mTextDocument(NULL) { } Converter::~Converter() { } // join the char * array into one QString QString _strPack(char **str, int size) { QString res; res = QString::fromUtf8(str[0]); for (int i=1;igetEpub(), type, &size); if (data) { emit addMetaData(key, _strPack((char **)data, size)); for (int i=0;iend(); bit = bit.next()) { for (QTextBlock::iterator fit = bit.begin(); !(fit.atEnd()); ++fit) { QTextFragment frag = fit.fragment(); if (frag.isValid() && frag.charFormat().isAnchor()) { QString hrefString = frag.charFormat().anchorHref(); // remove ./ or ../ // making it easier to compare, with links while(!hrefString.isNull() && ( hrefString.at(0) == QLatin1Char('.') || hrefString.at(0) == QLatin1Char('/')) ){ hrefString.remove(0,1); } QUrl href(hrefString); if (href.isValid() && !href.isEmpty()) { if (href.isRelative()) { // Inside document link if(!hrefString.indexOf(QLatin1Char('#'))) hrefString = name + hrefString; else if(QFileInfo(hrefString).path() == QLatin1String(".") && curDir != QLatin1String(".")) hrefString = curDir + QLatin1Char('/') + hrefString; // QTextCharFormat sometimes splits a link in two // if there's no white space between words & the first one is an anchor // consider whole word to be an anchor ++fit; int fragLen = frag.length(); if(!fit.atEnd() && ((fit.fragment().position() - frag.position()) == 1)) fragLen += fit.fragment().length(); --fit; _insert_local_links(hrefString, QPair(frag.position(), frag.position()+fragLen)); } else { // Outside document link Okular::BrowseAction *action = new Okular::BrowseAction(QUrl(href.toString())); emit addAction(action, frag.position(), frag.position() + frag.length()); } } const QStringList &names = frag.charFormat().anchorNames(); if (!names.empty()) { for (QStringList::const_iterator lit = names.constBegin(); lit != names.constEnd(); ++lit) { mSectionMap.insert(name + QLatin1Char('#') + *lit, bit); } } } // end anchor case } } } void Converter::_insert_local_links(const QString &key, const QPair &value) { if(mLocalLinks.contains(key)){ mLocalLinks[key].append(value); } else { QVector< QPair > vec; vec.append(value); mLocalLinks.insert(key,vec); } } static QPoint calculateXYPosition( QTextDocument *document, int startPosition ) { const QTextBlock startBlock = document->findBlock( startPosition ); const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect( startBlock ); QTextLayout *startLayout = startBlock.layout(); if (!startLayout) { qWarning() << "Start layout not found" << startLayout; return QPoint(); } int startPos = startPosition - startBlock.position(); const QTextLine startLine = startLayout->lineForTextPosition( startPos ); double x = startBoundingRect.x() ; double y = startBoundingRect.y() + startLine.y(); y = (int)y % 800; return QPoint(x,y); } QTextDocument* Converter::convert( const QString &fileName ) { EpubDocument *newDocument = new EpubDocument(fileName); if (!newDocument->isValid()) { emit error(i18n("Error while opening the EPub document."), -1); delete newDocument; return NULL; } mTextDocument = newDocument; QTextCursor *_cursor = new QTextCursor( mTextDocument ); mLocalLinks.clear(); mSectionMap.clear(); // Emit the document meta data _emitData(Okular::DocumentInfo::Title, EPUB_TITLE); _emitData(Okular::DocumentInfo::Author, EPUB_CREATOR); _emitData(Okular::DocumentInfo::Subject, EPUB_SUBJECT); _emitData(Okular::DocumentInfo::Creator, EPUB_PUBLISHER); _emitData(Okular::DocumentInfo::Description, EPUB_DESCRIPTION); _emitData(Okular::DocumentInfo::CreationDate, EPUB_DATE); _emitData(Okular::DocumentInfo::Category, EPUB_TYPE); _emitData(Okular::DocumentInfo::Copyright, EPUB_RIGHTS); emit addMetaData( Okular::DocumentInfo::MimeType, QStringLiteral("application/epub+zip")); struct eiterator *it; // iterate over the book it = epub_get_iterator(mTextDocument->getEpub(), EITERATOR_SPINE, 0); // if the background color of the document is non-white it will be handled by QTextDocument::setHtml() bool firstPage = true; QVector movieAnnots; QVector soundActions; const QSize videoSize(320, 240); do{ movieAnnots.clear(); soundActions.clear(); if(epub_it_get_curr(it)) { const QString link = QString::fromUtf8(epub_it_get_curr_url(it)); mTextDocument->setCurrentSubDocument(link); QString htmlContent = QString::fromUtf8(epub_it_get_curr(it)); // as QTextCharFormat::anchorNames() ignores sections, replace it with

htmlContent.replace(QRegExp(QStringLiteral("< *section")),QStringLiteral("maxContentHeight(); const int maxWidth = mTextDocument->maxContentWidth(); QDomDocument dom; if(dom.setContent(htmlContent)) { QDomNodeList svgs = dom.elementsByTagName(QStringLiteral("svg")); if(!svgs.isEmpty()) { QList< QDomNode > imgNodes; for (int i = 0; i < svgs.length(); ++i) { QDomNodeList images = svgs.at(i).toElement().elementsByTagName(QStringLiteral("image")); for (int j = 0; j < images.length(); ++j) { QString lnk = images.at(i).toElement().attribute(QStringLiteral("xlink:href")); int ht = images.at(i).toElement().attribute(QStringLiteral("height")).toInt(); int wd = images.at(i).toElement().attribute(QStringLiteral("width")).toInt(); QImage img = mTextDocument->loadResource(QTextDocument::ImageResource,QUrl(lnk)).value(); if(ht == 0) ht = img.height(); if(wd == 0) wd = img.width(); if(ht > maxHeight) ht = maxHeight; if(wd > maxWidth) wd = maxWidth; mTextDocument->addResource(QTextDocument::ImageResource,QUrl(lnk),img); QDomDocument newDoc; newDoc.setContent(QStringLiteral("").arg(lnk).arg(ht).arg(wd)); imgNodes.append(newDoc.documentElement()); } foreach (const QDomNode& nd, imgNodes) { svgs.at(i).parentNode().replaceChild(nd,svgs.at(i)); } } } // handle embedded videos QDomNodeList videoTags = dom.elementsByTagName(QStringLiteral("video")); while(!videoTags.isEmpty()) { QDomNodeList sourceTags = videoTags.at(0).toElement().elementsByTagName(QStringLiteral("source")); if(!sourceTags.isEmpty()) { QString lnk = sourceTags.at(0).toElement().attribute(QStringLiteral("src")); Okular::Movie *movie = new Okular::Movie(mTextDocument->loadResource(EpubDocument::MovieResource,QUrl(lnk)).toString()); movie->setSize(videoSize); movie->setShowControls(true); Okular::MovieAnnotation *annot = new Okular::MovieAnnotation; annot->setMovie(movie); movieAnnots.push_back(annot); QDomDocument tempDoc; tempDoc.setContent(QStringLiteral("

<video></video>
")); videoTags.at(0).parentNode().replaceChild(tempDoc.documentElement(),videoTags.at(0)); } } //handle embedded audio QDomNodeList audioTags = dom.elementsByTagName(QStringLiteral("audio")); while(!audioTags.isEmpty()) { QDomElement element = audioTags.at(0).toElement(); bool repeat = element.hasAttribute(QStringLiteral("loop")); QString lnk = element.attribute(QStringLiteral("src")); Okular::Sound *sound = new Okular::Sound(mTextDocument->loadResource( EpubDocument::AudioResource, QUrl(lnk)).toByteArray()); Okular::SoundAction *soundAction = new Okular::SoundAction(1.0,true,repeat,false,sound); soundActions.push_back(soundAction); QDomDocument tempDoc; tempDoc.setContent(QStringLiteral("
<audio></audio>
")); audioTags.at(0).parentNode().replaceChild(tempDoc.documentElement(),audioTags.at(0)); } htmlContent = dom.toString(); } // HACK BEGIN Get the links without CSS to be blue // Remove if Qt ever gets fixed and the code in textdocumentgenerator.cpp works const QPalette orig = qApp->palette(); QPalette p = orig; p.setColor(QPalette::Link, Qt::blue); qApp->setPalette(p); // HACK END QTextBlock before; if(firstPage) { // preHtml & postHtml make it possible to have a margin around the content of the page const QString preHtml = QString::fromLatin1("" "" "" "
").arg(mTextDocument->padding); const QString postHtml = QStringLiteral("
"); mTextDocument->setHtml(preHtml + htmlContent + postHtml); firstPage = false; before = mTextDocument->begin(); } else { before = _cursor->block(); _cursor->insertHtml(htmlContent); } // HACK BEGIN qApp->setPalette(orig); // HACK END QTextCursor csr(mTextDocument); // a temporary cursor csr.movePosition(QTextCursor::Start); int index = 0; while( !(csr = mTextDocument->find(QStringLiteral(""),csr)).isNull() ) { const int posStart = csr.position(); const QPoint startPoint = calculateXYPosition(mTextDocument, posStart); QImage img(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/okular-epub-movie.png"))); img = img.scaled(videoSize); csr.insertImage(img); const int posEnd = csr.position(); const QRect videoRect(startPoint,videoSize); movieAnnots[index]->setBoundingRectangle(Okular::NormalizedRect(videoRect,mTextDocument->pageSize().width(), mTextDocument->pageSize().height())); emit addAnnotation(movieAnnots[index++],posStart,posEnd); csr.movePosition(QTextCursor::NextWord); } csr.movePosition(QTextCursor::Start); index = 0; const QString keyToSearch(QStringLiteral("")); while( !(csr = mTextDocument->find(keyToSearch, csr)).isNull() ) { const int posStart = csr.position() - keyToSearch.size(); const QImage img(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/okular-epub-sound-icon.png"))); csr.insertImage(img); const int posEnd = csr.position(); qDebug() << posStart << posEnd;; emit addAction(soundActions[index++],posStart,posEnd); csr.movePosition(QTextCursor::NextWord); } mSectionMap.insert(link, before); _handle_anchors(before, link); const int page = mTextDocument->pageCount(); // it will clear the previous format // useful when the last line had a bullet _cursor->insertBlock(QTextBlockFormat()); while(mTextDocument->pageCount() == page) _cursor->insertText(QStringLiteral("\n")); } } while (epub_it_get_next(it)); epub_free_iterator(it); // handle toc struct titerator *tit; // FIXME: support other method beside NAVMAP and GUIDE tit = epub_get_titerator(mTextDocument->getEpub(), TITERATOR_NAVMAP, 0); if (!tit) tit = epub_get_titerator(mTextDocument->getEpub(), TITERATOR_GUIDE, 0); if (tit) { do { if (epub_tit_curr_valid(tit)) { char *clink = epub_tit_get_curr_link(tit); QString link = QString::fromUtf8(clink); char *label = epub_tit_get_curr_label(tit); QTextBlock block = mTextDocument->begin(); // must point somewhere if (mSectionMap.contains(link)) { block = mSectionMap.value(link); } else { // load missing resource char *data = 0; //epub_get_data can't handle whitespace url encodings QByteArray ba = link.replace("%20", " ").toLatin1(); const char *clinkClean = ba.data(); int size = epub_get_data(mTextDocument->getEpub(), clinkClean, &data); if (data) { _cursor->insertBlock(); // try to load as image and if not load as html block = _cursor->block(); QImage image; mSectionMap.insert(link, block); if (image.loadFromData((unsigned char *)data, size)) { mTextDocument->addResource(QTextDocument::ImageResource, QUrl(link), image); _cursor->insertImage(link); } else { _cursor->insertHtml(QString::fromUtf8(data)); // Add anchors to hashes _handle_anchors(block, link); } // Start new file in a new page int page = mTextDocument->pageCount(); while(mTextDocument->pageCount() == page) _cursor->insertText(QStringLiteral("\n")); } free(data); } if (block.isValid()) { // be sure we actually got a block emit addTitle(epub_tit_get_curr_depth(tit), QString::fromUtf8(label), block); } else { qDebug() << "Error: no block found for"<< link; } if (clink) free(clink); if (label) free(label); } } while (epub_tit_next(tit)); epub_free_titerator(tit); } else { qDebug() << "no toc found"; } // adding link actions QHashIterator > > hit(mLocalLinks); while (hit.hasNext()) { hit.next(); const QTextBlock block = mSectionMap.value(hit.key()); for (int i = 0; i < hit.value().size(); ++i) { if (block.isValid()) { // be sure we actually got a block Okular::DocumentViewport viewport = calculateViewport(mTextDocument, block); Okular::GotoAction *action = new Okular::GotoAction(QString(), viewport); emit addAction(action, hit.value()[i].first, hit.value()[i].second); } else { qDebug() << "Error: no block found for "<< hit.key(); } } } delete _cursor; return mTextDocument; } diff --git a/generators/epub/epubdocument.h b/generators/epub/epubdocument.h index da261ab6c..e823e3d06 100644 --- a/generators/epub/epubdocument.h +++ b/generators/epub/epubdocument.h @@ -1,53 +1,52 @@ /*************************************************************************** * Copyright (C) 2008 by Ely Levy * * * * 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 EPUB_DOCUMENT_H #define EPUB_DOCUMENT_H #include #include #include #include -#include #include -#include +#include namespace Epub { class EpubDocument : public QTextDocument { Q_OBJECT public: EpubDocument(const QString &fileName); bool isValid(); ~EpubDocument(); struct epub *getEpub(); void setCurrentSubDocument(const QString &doc); int maxContentHeight() const; int maxContentWidth() const; enum Multimedia { MovieResource = 4, AudioResource = 5 }; protected: QVariant loadResource(int type, const QUrl &name) override; private: void checkCSS(QString &css); struct epub *mEpub; QUrl mCurrentSubDocument; int padding; friend class Converter; }; } Q_DECLARE_LOGGING_CATEGORY(OkularEpuDebug) #endif diff --git a/generators/fax/faxdocument.cpp b/generators/fax/faxdocument.cpp index ad63273cf..cdeb41a6a 100644 --- a/generators/fax/faxdocument.cpp +++ b/generators/fax/faxdocument.cpp @@ -1,309 +1,310 @@ /*************************************************************************** * Copyright (C) 2008 by Tobias Koenig * * * * 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 "faxdocument.h" + #include -#include +#include #include "faxexpand.h" -#include "faxdocument.h" static const char FAXMAGIC[] = "\000PC Research, Inc\000\000\000\000\000\000"; #define FAX_DPI_FINE QPoint(203,196) /* rearrange input bits into t16bits lsb-first chunks */ static void normalize( pagenode *pn, int revbits, int swapbytes, size_t length ) { t32bits *p = (t32bits *) pn->data; switch ( (revbits<<1) | swapbytes ) { case 0: break; case 1: for ( ; length; length -= 4) { t32bits t = *p; *p++ = ((t & 0xff00ff00) >> 8) | ((t & 0x00ff00ff) << 8); } break; case 2: for ( ; length; length -= 4) { t32bits t = *p; t = ((t & 0xf0f0f0f0) >> 4) | ((t & 0x0f0f0f0f) << 4); t = ((t & 0xcccccccc) >> 2) | ((t & 0x33333333) << 2); *p++ = ((t & 0xaaaaaaaa) >> 1) | ((t & 0x55555555) << 1); } break; case 3: for ( ; length; length -= 4) { t32bits t = *p; t = ((t & 0xff00ff00) >> 8) | ((t & 0x00ff00ff) << 8); t = ((t & 0xf0f0f0f0) >> 4) | ((t & 0x0f0f0f0f) << 4); t = ((t & 0xcccccccc) >> 2) | ((t & 0x33333333) << 2); *p++ = ((t & 0xaaaaaaaa) >> 1) | ((t & 0x55555555) << 1); } } } static bool new_image( pagenode *pn, int width, int height ) { pn->image = QImage( width, height, QImage::Format_MonoLSB ); pn->image.setColor( 0, qRgb( 255, 255, 255 ) ); pn->image.setColor( 1, qRgb( 0, 0, 0 ) ); pn->bytes_per_line = pn->image.bytesPerLine(); pn->dpi = FAX_DPI_FINE; pn->imageData = new uchar[ width * height ]; return !pn->image.isNull(); } /* get compressed data into memory */ static unsigned char* getstrip( pagenode *pn, int strip ) { size_t offset, roundup; unsigned char *data; union { t16bits s; unsigned char b[2]; } so; #define ShortOrder so.b[1] so.s = 1; /* XXX */ QFile file( pn->filename ); if ( !file.open( QIODevice::ReadOnly ) ) return nullptr; if ( pn->strips == nullptr ) { offset = 0; pn->length = file.size(); } else if ( strip < pn->nstrips ) { offset = pn->strips[ strip ].offset; pn->length = pn->strips[ strip ].size; } else return nullptr; /* round size to full boundary plus t32bits */ roundup = (pn->length + 7) & ~3; data = new uchar[ roundup ]; /* clear the last 2 t32bits, to force the expander to terminate even if the file ends in the middle of a fax line */ *((t32bits *) data + roundup/4 - 2) = 0; *((t32bits *) data + roundup/4 - 1) = 0; /* we expect to get it in one gulp... */ if ( !file.seek(offset) || (size_t) file.read( (char *)data, pn->length ) != pn->length ) { delete [] data; return nullptr; } file.close(); pn->data = (t16bits *)data; if ( pn->strips == nullptr && memcmp( data, FAXMAGIC, sizeof( FAXMAGIC ) - 1 ) == 0 ) { /* handle ghostscript / PC Research fax file */ pn->length -= 64; pn->vres = data[29]; pn->data += 32; roundup -= 64; } normalize( pn, !pn->lsbfirst, ShortOrder, roundup ); if ( pn->size.height() == 0 ) pn->size.setHeight( G3count( pn, pn->expander == g32expand ) ); if ( pn->size.height() == 0 ) { delete [] data; pn->data = nullptr; return nullptr; } if ( pn->strips == nullptr ) pn->rowsperstrip = pn->size.height(); pn->dataOrig = (t16bits *)data; return data; } static void draw_line( pixnum *run, int lineNum, pagenode *pn ) { t32bits *p, *p1; /* p - current line, p1 - low-res duplicate */ pixnum *r; /* pointer to run-lengths */ t32bits pix; /* current pixel value */ t32bits acc; /* pixel accumulator */ int nacc; /* number of valid bits in acc */ int tot; /* total pixels in line */ int n; lineNum += pn->stripnum * pn->rowsperstrip; if ( lineNum >= pn->size.height() ) return; p = (t32bits *)(pn->imageData + lineNum*(2-pn->vres)*pn->bytes_per_line); p1 =(t32bits *)(pn->vres ? nullptr : p + pn->bytes_per_line/sizeof(*p)); r = run; acc = 0; nacc = 0; pix = pn->inverse ? ~0 : 0; tot = 0; while ( tot < pn->size.width() ) { n = *r++; tot += n; /* Watch out for buffer overruns, e.g. when n == 65535. */ if ( tot > pn->size.width() ) break; if ( pix ) acc |= (~(t32bits)0 >> nacc); else if ( nacc ) acc &= (~(t32bits)0 << (32 - nacc)); else acc = 0; if ( nacc + n < 32 ) { nacc += n; pix = ~pix; continue; } *p++ = acc; if ( p1 ) *p1++ = acc; n -= 32 - nacc; while ( n >= 32 ) { n -= 32; *p++ = pix; if ( p1 ) *p1++ = pix; } acc = pix; nacc = n; pix = ~pix; } if ( nacc ) { *p++ = acc; if ( p1 ) *p1++ = acc; } } static bool get_image( pagenode *pn ) { unsigned char *data = getstrip( pn, 0 ); if ( !data ) return false; if ( !new_image( pn, pn->size.width(), (pn->vres ? 1 : 2) * pn->size.height() ) ) return false; (*pn->expander)( pn, draw_line ); return true; } class FaxDocument::Private { public: Private( FaxDocument *parent ) : mParent( parent ) { mPageNode.size = QSize( 1728, 0 ); } FaxDocument *mParent; pagenode mPageNode; FaxDocument::DocumentType mType; }; FaxDocument::FaxDocument( const QString &fileName, DocumentType type ) : d( new Private( this ) ) { d->mPageNode.filename = fileName; d->mPageNode.strips = nullptr; d->mPageNode.stripnum = 0; d->mPageNode.lsbfirst = 0; d->mPageNode.vres = 1; d->mPageNode.inverse = 0; d->mPageNode.data = nullptr; d->mPageNode.dataOrig = nullptr; d->mPageNode.imageData = nullptr; d->mType = type; if ( d->mType == G3 ) d->mPageNode.expander = g31expand; // or g32expand?!? else if ( d->mType == G4 ) d->mPageNode.expander = g4expand; } FaxDocument::~FaxDocument() { delete [] d->mPageNode.dataOrig; delete [] d->mPageNode.imageData; delete d; } bool FaxDocument::load() { fax_init_tables(); bool ok = get_image( &(d->mPageNode) ); if ( !ok ) return false; // byte-swapping the image int height = d->mPageNode.size.height(); int bytes_per_line = d->mPageNode.size.width()/8; QByteArray bytes( height * bytes_per_line, 0 ); for ( int y= height - 1; y >= 0; --y ) { quint32 offset = y * bytes_per_line; quint32 *source = (quint32 *) (d->mPageNode.imageData + offset); quint32 *dest = (quint32 *) (bytes.data() + offset); for ( int x= (bytes_per_line/4) - 1; x >= 0; --x ) { quint32 dv = 0, sv = *source; for ( int bit = 32; bit > 0; --bit ) { dv <<= 1; dv |= sv&1; sv >>= 1; } *dest = dv; ++dest; ++source; } } // convert it into a QImage QImage img( (uchar*)bytes.data(), d->mPageNode.size.width(), d->mPageNode.size.height(), QImage::Format_MonoLSB ); img.setColor( 0, qRgb( 255, 255, 255 ) ); img.setColor( 1, qRgb( 0, 0, 0 ) ); d->mPageNode.image = img.copy().scaled( img.width(), img.height() * 1.5 ); return true; } QImage FaxDocument::image() const { return d->mPageNode.image; } diff --git a/generators/fax/faxdocument.h b/generators/fax/faxdocument.h index e917895a2..b45a37ddd 100644 --- a/generators/fax/faxdocument.h +++ b/generators/fax/faxdocument.h @@ -1,63 +1,63 @@ /*************************************************************************** * Copyright (C) 2008 by Tobias Koenig * * * * 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 FAXDOCUMENT_H #define FAXDOCUMENT_H -#include +#include /** * Loads a G3/G4 fax document and provides methods * to convert it into a QImage. */ class FaxDocument { public: /** * Describes the type of the fax document. */ enum DocumentType { G3, ///< G3 encoded fax document G4 ///< G4 encoded fax document }; /** * Creates a new fax document from the given @p fileName. * * @param type The type of the fax document. */ explicit FaxDocument( const QString &fileName, DocumentType type = G3 ); /** * Destroys the fax document. */ ~FaxDocument(); /** * Loads the document. * * @return @c true if the document can be loaded successfully, @c false otherwise. */ bool load(); /** * Returns the document as an image. */ QImage image() const; private: class Private; Private* const d; FaxDocument( const FaxDocument& ); FaxDocument& operator=( const FaxDocument& ); }; #endif diff --git a/generators/fax/faxexpand.cpp b/generators/fax/faxexpand.cpp index c551c7d60..11a1f68ea 100644 --- a/generators/fax/faxexpand.cpp +++ b/generators/fax/faxexpand.cpp @@ -1,746 +1,747 @@ /* Expand one page of fax data Copyright (C) 1990, 1995 Frank D. Cringle. This file is part of viewfax - g3/g4 fax processing software. viewfax is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "faxexpand.h" + #include #include #include -#include +#include -#include "faxexpand.h" #include "fax_debug.h" //Uncomment this for verbose debug output //#define DEBUG_FAX #define verbose false pagenode::pagenode() { } /* Note that NeedBits() only works for n <= 16 */ #define NeedBits(n) do { \ if (BitsAvail < (n)) { \ BitAcc |= *sp++ << BitsAvail; \ BitsAvail += 16; \ } \ } while (0) #define GetBits(n) (BitAcc & ((1<<(n))-1)) #define ClrBits(n) do { \ BitAcc >>= (n); \ BitsAvail -= (n); \ } while (0) #ifdef DEBUG_FAX #define DEBUG_SHOW putchar(BitAcc & (1 << t) ? '1' : '0') #define LOOKUP(wid,tab) do { \ int t; \ NeedBits(wid); \ TabEnt = tab + GetBits(wid); \ printf("%08lX/%d: %s%5d\t", BitAcc, BitsAvail, \ StateNames[TabEnt->State], TabEnt->Param); \ for (t = 0; t < TabEnt->Width; t++) \ DEBUG_SHOW; \ putchar('\n'); \ fflush(stdout); \ ClrBits(TabEnt->Width); \ } while (0) #define SETVAL(x) do { \ *pa++ = RunLength + (x); \ printf("SETVAL: %d\t%d\n", RunLength + (x), a0); \ a0 += x; \ RunLength = 0; \ } while (0) const char *StateNames[] = { "Null ", "Pass ", "Horiz ", "V0 ", "VR ", "VL ", "Ext ", "TermW ", "TermB ", "MakeUpW", "MakeUpB", "MakeUp ", "EOL ", }; #else #define LOOKUP(wid,tab) do { \ NeedBits(wid); \ TabEnt = tab + GetBits(wid); \ ClrBits(TabEnt->Width); \ } while (0) #define SETVAL(x) do { \ *pa++ = RunLength + (x); \ a0 += x; \ RunLength = 0; \ } while (0) #endif #define dumpruns(runs) do { \ printf("-------------------- %d\n", LineNum); \ for (pa = runs, a0 = 0; a0 < lastx; a0 += *pa++) \ printf("%4d %d\n", a0, *pa); \ } while (0) #define EndOfData(pn) (sp >= pn->data + pn->length/sizeof(*pn->data)) /* This macro handles coding errors in G3 data. We redefine it below for the G4 case */ #define SKIP_EOL do { \ while (!EndOfData(pn)) { \ NeedBits(11); \ if (GetBits(11) == 0) \ break; \ ClrBits(1); \ } \ ClrBits(11); \ goto EOL; \ } while (0) #define eol2lab EOL2: /* the line expanders are written as macros so that they can be reused (twice each) but still have direct access to the local variables of the "calling" function */ #define expand1d() do { \ while (a0 < lastx) { \ int done = 0; \ while (!done) { /* white first */ \ LOOKUP(12, WhiteTable); \ switch (TabEnt->State) { \ case S_EOL: \ EOLcnt = 1; \ goto EOL; \ case S_TermW: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpW: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ case S_Ext: \ unexpected("Extension code", LineNum); \ SKIP_EOL; \ break; \ default: \ unexpected("WhiteTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ done = a0 >= lastx; \ while (!done) { /* then black */ \ LOOKUP(13, BlackTable); \ switch (TabEnt->State) { \ case S_EOL: \ EOLcnt = 1; \ goto EOL; \ case S_TermB: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpB: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ case S_Ext: \ unexpected("Extension code", LineNum); \ SKIP_EOL; \ break; \ default: \ unexpected("BlackTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ } \ EOL: ; \ } while (0) #define CHECK_b1 do { \ if (pa != thisrun) while (b1 <= a0 && b1 < lastx) { \ b1 += pb[0] + pb[1]; \ pb += 2; \ } \ } while (0) #define expand2d(eolab) do { \ while (a0 < lastx) { \ LOOKUP(7, MainTable); \ switch (TabEnt->State) { \ case S_Pass: \ CHECK_b1; \ b1 += *pb++; \ RunLength += b1 - a0; \ a0 = b1; \ b1 += *pb++; \ break; \ case S_Horiz: \ if ((pa-run0)&1) { \ int done = 0; \ while (!done) { /* black first */ \ LOOKUP(13, BlackTable); \ switch (TabEnt->State) { \ case S_TermB: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpB: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("BlackTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ done = 0; \ while (!done) { /* then white */ \ LOOKUP(12, WhiteTable); \ switch (TabEnt->State) { \ case S_TermW: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpW: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("WhiteTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ } \ else { \ int done = 0; \ while (!done) { /* white first */ \ LOOKUP(12, WhiteTable); \ switch (TabEnt->State) { \ case S_TermW: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpW: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("WhiteTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ done = 0; \ while (!done) { /* then black */ \ LOOKUP(13, BlackTable); \ switch (TabEnt->State) { \ case S_TermB: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpB: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("BlackTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ } \ CHECK_b1; \ break; \ case S_V0: \ CHECK_b1; \ SETVAL(b1 - a0); \ b1 += *pb++; \ break; \ case S_VR: \ CHECK_b1; \ SETVAL(b1 - a0 + TabEnt->Param); \ b1 += *pb++; \ break; \ case S_VL: \ CHECK_b1; \ SETVAL(b1 - a0 - TabEnt->Param); \ b1 -= *--pb; \ break; \ case S_Ext: \ *pa++ = lastx - a0; \ if (verbose) \ qCDebug(FAX_LOG) << "Line " << LineNum << ": extension code\n";\ SKIP_EOL; \ break; \ case S_EOL: \ *pa++ = lastx - a0; \ NeedBits(4); \ if (GetBits(4) && verbose) /* already seen 7 zeros */ \ qCDebug(FAX_LOG) << "Line " << LineNum << ": Bad EOL\n"; \ ClrBits(4); \ EOLcnt = 1; \ goto eolab; \ break; \ default: \ unexpected("MainTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ if (RunLength) { \ if (RunLength + a0 < lastx) { \ /* expect a final V0 */ \ NeedBits(1); \ if (!GetBits(1)) { \ unexpected("MainTable", LineNum); \ SKIP_EOL; \ } \ ClrBits(1); \ } \ SETVAL(0); \ } \ eol2lab ; \ } while (0) static void unexpected(const char *what, int LineNum) { if (verbose) qCCritical(FAX_LOG) << "Line " << LineNum << ": Unexpected state in " << what << endl; } /* Expand tiff modified huffman data (g3-1d without EOLs) */ void MHexpand(pagenode *pn, drawfunc df) { int a0; /* reference element */ int lastx; /* copy line width to register */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int RunLength; /* Length of current run */ t16bits *sp; /* pointer into compressed data */ pixnum *pa; /* pointer into new line */ int EOLcnt; /* number of consecutive EOLs */ int LineNum; /* line number */ pixnum *runs; /* list of run lengths */ struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; lastx = pn->size.width(); runs = (pixnum *) malloc(lastx * sizeof(pixnum)); for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX printf("\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); printf("-------------------- %d\n", LineNum); fflush(stdout); #endif RunLength = 0; pa = runs; a0 = 0; EOLcnt = 0; if (BitsAvail & 7) /* skip to byte boundary */ ClrBits(BitsAvail & 7); expand1d(); if (RunLength) SETVAL(0); if (a0 != lastx) { if (verbose) qCWarning(FAX_LOG) << "Line " << LineNum << ": length is " << a0 << " (expected "<< lastx << ")\n"; while (a0 > lastx) a0 -= *--pa; if (a0 < lastx) { if ((pa - runs) & 1) SETVAL(0); SETVAL(lastx - a0); } } (*df)(runs, LineNum++, pn); } free(runs); (void)EOLcnt; // make gcc happy } /* Expand group-3 1-dimensional data */ void g31expand(pagenode *pn, drawfunc df) { int a0; /* reference element */ int lastx; /* copy line width to register */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int RunLength; /* Length of current run */ t16bits *sp; /* pointer into compressed data */ pixnum *pa; /* pointer into new line */ int EOLcnt; /* number of consecutive EOLs */ int LineNum; /* line number */ pixnum *runs; /* list of run lengths */ struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; lastx = pn->size.width(); runs = (pixnum *) malloc(lastx * sizeof(pixnum)); EOLcnt = 0; for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX fprintf(stderr,"\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); fprintf(stderr,"-------------------- %d\n", LineNum); fflush(stderr); #endif if (EOLcnt == 0) while (!EndOfData(pn)) { /* skip over garbage after a coding error */ NeedBits(11); if (GetBits(11) == 0) break; ClrBits(1); } for (EOLcnt = 1; !EndOfData(pn); EOLcnt++) { /* we have seen 11 zeros, which implies EOL, skip possible fill bits too */ while (1) { NeedBits(8); if (GetBits(8)) break; ClrBits(8); } while (GetBits(1) == 0) ClrBits(1); ClrBits(1); /* the eol flag */ NeedBits(11); if (GetBits(11)) break; ClrBits(11); } if (EOLcnt > 1 && EOLcnt != 6 && verbose) { qCCritical(FAX_LOG) << "Line " << LineNum << ": bad RTC (" << EOLcnt << " EOLs)\n"; } if (EOLcnt >= 6 || EndOfData(pn)) { free(runs); return; } RunLength = 0; pa = runs; a0 = 0; EOLcnt = 0; expand1d(); if (RunLength) SETVAL(0); if (a0 != lastx) { if (verbose) qCWarning(FAX_LOG) << "Line " << LineNum << ": length is " << a0 << " (expected "<< lastx << ")\n"; while (a0 > lastx) a0 -= *--pa; if (a0 < lastx) { if ((pa - runs) & 1) SETVAL(0); SETVAL(lastx - a0); } } (*df)(runs, LineNum++, pn); } free(runs); } /* Expand group-3 2-dimensional data */ void g32expand(pagenode *pn, drawfunc df) { int RunLength; /* Length of current run */ int a0; /* reference element */ int b1; /* next change on previous line */ int lastx = pn->size.width();/* copy line width to register */ pixnum *run0, *run1; /* run length arrays */ pixnum *thisrun, *pa, *pb; /* pointers into runs */ t16bits *sp; /* pointer into compressed data */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int EOLcnt; /* number of consecutive EOLs */ int refline = 0; /* 1D encoded reference line */ int LineNum; /* line number */ struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; /* allocate space for 2 runlength arrays */ run0 = (pixnum *) malloc(2 * ((lastx+5)&~1) * sizeof(pixnum)); run1 = run0 + ((lastx+5)&~1); run1[0] = lastx; run1[1] = 0; EOLcnt = 0; for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX printf("\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); printf("-------------------- %d\n", LineNum); fflush(stdout); #endif if (EOLcnt == 0) while (!EndOfData(pn)) { /* skip over garbage after a coding error */ NeedBits(11); if (GetBits(11) == 0) break; ClrBits(1); } for (EOLcnt = 1; !EndOfData(pn); EOLcnt++) { /* we have seen 11 zeros, which implies EOL, skip possible fill bits too */ while (1) { NeedBits(8); if (GetBits(8)) break; ClrBits(8); } while (GetBits(1) == 0) ClrBits(1); ClrBits(1); /* the eol flag */ NeedBits(12); refline = GetBits(1); /* 1D / 2D flag */ ClrBits(1); if (GetBits(11)) break; ClrBits(11); } if (EOLcnt > 1 && EOLcnt != 6 && verbose) qCCritical(FAX_LOG) << "Line " << LineNum << ": bad RTC (" << EOLcnt << " EOLs)\n"; if (EOLcnt >= 6 || EndOfData(pn)) { free(run0); return; } if (LineNum == 0 && refline == 0 && verbose) qCDebug(FAX_LOG) << "First line is 2-D encoded\n"; RunLength = 0; if (LineNum & 1) { pa = run1; pb = run0; } else { pa = run0; pb = run1; } thisrun = pa; EOLcnt = 0; a0 = 0; b1 = *pb++; if (refline) { expand1d(); } else { expand2d(EOL2); } if (RunLength) SETVAL(0); if (a0 != lastx) { if (verbose) qCWarning(FAX_LOG) << "Line " << LineNum << ": length is " << a0 << " (expected "<< lastx << ")\n"; while (a0 > lastx) a0 -= *--pa; if (a0 < lastx) { if ((pa - run0) & 1) SETVAL(0); SETVAL(lastx - a0); } } SETVAL(0); /* imaginary change at end of line for reference */ (*df)(thisrun, LineNum++, pn); } free(run0); } /* Redefine the "skip to eol" macro. We cannot recover from coding errors in G4 data */ #undef SKIP_EOL #undef eol2lab #define SKIP_EOL do { \ if (verbose) \ qCCritical(FAX_LOG) << "Line " << LineNum << ": G4 coding error\n"; \ free(run0); \ return; \ } while (0) #define eol2lab /* Expand group-4 data */ void g4expand(pagenode *pn, drawfunc df) { int RunLength; /* Length of current run */ int a0; /* reference element */ int b1; /* next change on previous line */ int lastx = pn->size.width();/* copy line width to register */ pixnum *run0, *run1; /* run length arrays */ pixnum *thisrun, *pa, *pb; /* pointers into runs */ t16bits *sp; /* pointer into compressed data */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int LineNum; /* line number */ int EOLcnt; struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; /* allocate space for 2 runlength arrays */ run0 = (pixnum *) malloc(2 * ((lastx+5)&~1) * sizeof(pixnum)); run1 = run0 + ((lastx+5)&~1); run1[0] = lastx; /* initial reference line */ run1[1] = 0; for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX printf("\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); printf("-------------------- %d\n", LineNum); fflush(stdout); #endif RunLength = 0; if (LineNum & 1) { pa = run1; pb = run0; } else { pa = run0; pb = run1; } thisrun = pa; a0 = 0; b1 = *pb++; expand2d(EOFB); if (a0 < lastx) { if ((pa - run0) & 1) SETVAL(0); SETVAL(lastx - a0); } SETVAL(0); /* imaginary change at end of line for reference */ (*df)(thisrun, LineNum++, pn); continue; EOFB: NeedBits(13); if (GetBits(13) != 0x1001 && verbose) qCCritical(FAX_LOG) << "Bad RTC\n"; break; } free(run0); (void)EOLcnt; // make gcc happy } static const unsigned char zerotab[256] = { 0x88, 0x07, 0x16, 0x06, 0x25, 0x05, 0x15, 0x05, 0x34, 0x04, 0x14, 0x04, 0x24, 0x04, 0x14, 0x04, 0x43, 0x03, 0x13, 0x03, 0x23, 0x03, 0x13, 0x03, 0x33, 0x03, 0x13, 0x03, 0x23, 0x03, 0x13, 0x03, 0x52, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x32, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x42, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x32, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x61, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x41, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x51, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x41, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x70, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x60, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00 }; #define check(v) do { \ prezeros = zerotab[v]; \ postzeros = prezeros & 15; \ prezeros >>= 4; \ if (prezeros == 8) { \ zeros += 8; \ continue; \ } \ if (zeros + prezeros < 11) { \ empty = 0; \ zeros = postzeros; \ continue; \ } \ zeros = postzeros; \ if (empty) \ EOLcnt++; \ lines++; \ empty = 1; \ } while (0) /* count fax lines */ int G3count(pagenode *pn, int twoD) { t16bits *p = pn->data; t16bits *end = p + pn->length/sizeof(*p); int lines = 0; /* lines seen so far */ int zeros = 0; /* number of consecutive zero bits seen */ int EOLcnt = 0; /* number of consecutive EOLs seen */ int empty = 1; /* empty line */ int prezeros, postzeros; while (p < end && EOLcnt < 6) { t16bits bits = *p++; check(bits&255); if (twoD && (prezeros + postzeros == 7)) { if (postzeros || ((bits & 0x100) == 0)) zeros--; } check(bits>>8); if (twoD && (prezeros + postzeros == 7)) { if (postzeros || ((p < end) && ((*p & 1) == 0))) zeros--; } } return lines - EOLcnt; /* don't count trailing EOLs */ } diff --git a/generators/fax/faxexpand.h b/generators/fax/faxexpand.h index 9203187c1..b874de32f 100644 --- a/generators/fax/faxexpand.h +++ b/generators/fax/faxexpand.h @@ -1,122 +1,122 @@ /* Include file for fax routines Copyright (C) 1990, 1995 Frank D. Cringle. Copyright (C) 2005 Helge Deller This file is part of viewfax - g3/g4 fax processing software. viewfax is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _faxexpand_h_ #define _faxexpand_h_ -#include +#include #include #ifndef Q_OS_WIN #include #endif #define t32bits quint32 #define t16bits quint16 typedef t16bits pixnum; class pagenode; /* drawfunc() points to a function which processes a line of the expanded image described as a list of run lengths. run is the base of an array of lengths, starting with a (possibly empty) white run for line number linenum. pn points to the page descriptor */ typedef void (*drawfunc)(pixnum *run, int linenum, class pagenode *pn); struct strip { /* tiff strip descriptor */ off_t offset; /* offset in file */ off_t size; /* size of this strip */ }; /* defines for the pagenode member: type */ #define FAX_TIFF 1 #define FAX_RAW 2 class pagenode { /* compressed page descriptor */ public: pagenode(); ~pagenode() { } int nstrips; /* number of strips */ int rowsperstrip; /* number of rows per strip */ int stripnum; /* current strip while expanding */ struct strip *strips; /* array of strips containing fax data in file */ t16bits *data; /* in-memory copy of strip */ t16bits *dataOrig; /* copy of `data', in case we shift it */ size_t length; /* length of data */ QSize size; /* width & height of page in pixels */ int inverse; /* black <=> white */ int lsbfirst; /* bit order is lsb first */ int orient; /* orientation - upsidedown, landscape, mirrored */ int vres; /* vertical resolution: 1 = fine */ QPoint dpi; /* DPI horz/vert */ void (*expander)(class pagenode *, drawfunc); unsigned int bytes_per_line; QString filename; /* The name of the file to be opened */ QImage image; /* The final image */ uchar *imageData; /* The temporary raw image data */ }; /* page orientation flags */ #define TURN_NONE 0 #define TURN_U 1 #define TURN_L 2 #define TURN_M 4 /* fsm state codes */ #define S_Null 0 #define S_Pass 1 #define S_Horiz 2 #define S_V0 3 #define S_VR 4 #define S_VL 5 #define S_Ext 6 #define S_TermW 7 #define S_TermB 8 #define S_MakeUpW 9 #define S_MakeUpB 10 #define S_MakeUp 11 #define S_EOL 12 /* state table entry */ struct tabent { unsigned char State; unsigned char Width; /* width of code in bits */ pixnum Param; /* run length */ }; extern struct tabent MainTable[]; /* 2-D state table */ extern struct tabent WhiteTable[]; /* White run lengths */ extern struct tabent BlackTable[]; /* Black run lengths */ void MHexpand(class pagenode *pn, drawfunc df); void g31expand(class pagenode *pn, drawfunc df); void g32expand(class pagenode *pn, drawfunc df); void g4expand(class pagenode *pn, drawfunc df); /* initialise code tables */ extern void fax_init_tables(void); /* count lines in image */ extern int G3count(class pagenode *pn, int twoD); #endif diff --git a/generators/fax/generator_fax.cpp b/generators/fax/generator_fax.cpp index 5bbbbd3cd..8e5eae062 100644 --- a/generators/fax/generator_fax.cpp +++ b/generators/fax/generator_fax.cpp @@ -1,108 +1,108 @@ /*************************************************************************** * Copyright (C) 2008 by Tobias Koenig * * * * 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 "generator_fax.h" -#include -#include +#include +#include #include #include #include #include OKULAR_EXPORT_PLUGIN(FaxGenerator, "libokularGenerator_fax.json") FaxGenerator::FaxGenerator( QObject *parent, const QVariantList &args ) : Generator( parent, args ) { setFeature( Threaded ); setFeature( PrintNative ); setFeature( PrintToFile ); } FaxGenerator::~FaxGenerator() { } bool FaxGenerator::loadDocument( const QString & fileName, QVector & pagesVector ) { if ( fileName.toLower().endsWith( QLatin1String(".g3") ) ) m_type = FaxDocument::G3; else m_type = FaxDocument::G4; FaxDocument faxDocument( fileName, m_type ); if ( !faxDocument.load() ) { emit error( i18n( "Unable to load document" ), -1 ); return false; } m_img = faxDocument.image(); pagesVector.resize( 1 ); Okular::Page * page = new Okular::Page( 0, m_img.width(), m_img.height(), Okular::Rotation0 ); pagesVector[0] = page; return true; } bool FaxGenerator::doCloseDocument() { m_img = QImage(); return true; } QImage FaxGenerator::image( Okular::PixmapRequest * request ) { // perform a smooth scaled generation int width = request->width(); int height = request->height(); if ( request->page()->rotation() % 2 == 1 ) qSwap( width, height ); return m_img.scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } Okular::DocumentInfo FaxGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; if ( keys.contains( Okular::DocumentInfo::MimeType ) ) { if ( m_type == FaxDocument::G3 ) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("image/fax-g3") ); else docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("image/fax-g4") ); } return docInfo; } bool FaxGenerator::print( QPrinter& printer ) { QPainter p( &printer ); QImage image( m_img ); if ( ( image.width() > printer.width() ) || ( image.height() > printer.height() ) ) image = image.scaled( printer.width(), printer.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); p.drawImage( 0, 0, image ); return true; } #include "generator_fax.moc" diff --git a/generators/fax/generator_fax.h b/generators/fax/generator_fax.h index 048d176d5..9a2179e55 100644 --- a/generators/fax/generator_fax.h +++ b/generators/fax/generator_fax.h @@ -1,43 +1,43 @@ /*************************************************************************** * Copyright (C) 2008 by Tobias Koenig * * * * 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_GENERATOR_FAX_H #define OKULAR_GENERATOR_FAX_H #include -#include +#include #include "faxdocument.h" class FaxGenerator : public Okular::Generator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: FaxGenerator( QObject *parent, const QVariantList &args ); virtual ~FaxGenerator(); bool loadDocument( const QString & fileName, QVector & pagesVector ) override; Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override; bool print( QPrinter& printer ) override; protected: bool doCloseDocument() override; QImage image( Okular::PixmapRequest * request ) override; private: QImage m_img; FaxDocument::DocumentType m_type; }; #endif diff --git a/generators/fictionbook/converter.cpp b/generators/fictionbook/converter.cpp index 1bdb4aa83..0cf2babcb 100644 --- a/generators/fictionbook/converter.cpp +++ b/generators/fictionbook/converter.cpp @@ -1,853 +1,853 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "converter.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include "document.h" using namespace FictionBook; class Converter::TitleInfo { public: QStringList mGenres; QString mAuthor; QString mTitle; QStringList mKeywords; QDate mDate; QDomElement mCoverPage; QString mLanguage; }; class Converter::DocumentInfo { public: QString mAuthor; QString mProducer; QDate mDate; QString mId; QString mVersion; }; Converter::Converter() : mTextDocument( nullptr ), mCursor( nullptr ), mTitleInfo( nullptr ), mDocumentInfo( nullptr ) { } Converter::~Converter() { delete mTitleInfo; delete mDocumentInfo; } QTextDocument* Converter::convert( const QString &fileName ) { Document fbDocument( fileName ); if ( !fbDocument.open() ) { emit error( fbDocument.lastErrorString(), -1 ); return nullptr; } mTextDocument = new QTextDocument; mCursor = new QTextCursor( mTextDocument ); mSectionCounter = 0; mLocalLinks.clear(); mSectionMap.clear(); const QDomDocument document = fbDocument.content(); /** * Set the correct page size */ mTextDocument->setPageSize( QSizeF( 600, 800 ) ); QTextFrameFormat frameFormat; frameFormat.setMargin( 20 ); QTextFrame *rootFrame = mTextDocument->rootFrame(); rootFrame->setFrameFormat( frameFormat ); /** * Parse the content of the document */ const QDomElement documentElement = document.documentElement(); if ( documentElement.tagName() != QLatin1String( "FictionBook" ) ) { emit error( i18n( "Document is not a valid FictionBook" ), -1 ); delete mCursor; return nullptr; } /** * First we read all images, so we can calculate the size later. */ QDomElement element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "binary" ) ) { if ( !convertBinary( element ) ) { delete mCursor; return nullptr; } } element = element.nextSiblingElement(); } /** * Read the rest... */ element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "description" ) ) { if ( !convertDescription( element ) ) { delete mCursor; return nullptr; } } else if ( element.tagName() == QLatin1String( "body" ) ) { if ( !mTitleInfo->mCoverPage.isNull() ) { convertCover( mTitleInfo->mCoverPage ); mCursor->insertBlock(); } QTextFrame *topFrame = mCursor->currentFrame(); QTextFrameFormat frameFormat; frameFormat.setBorder( 2 ); frameFormat.setPadding( 8 ); frameFormat.setBackground( Qt::lightGray ); if ( !mTitleInfo->mTitle.isEmpty() ) { mCursor->insertFrame( frameFormat ); QTextCharFormat charFormat; charFormat.setFontPointSize( 22 ); charFormat.setFontWeight( QFont::Bold ); mCursor->insertText( mTitleInfo->mTitle, charFormat ); mCursor->setPosition( topFrame->lastPosition() ); } if ( !mTitleInfo->mAuthor.isEmpty() ) { frameFormat.setBorder( 1 ); mCursor->insertFrame( frameFormat ); QTextCharFormat charFormat; charFormat.setFontPointSize( 10 ); mCursor->insertText( mTitleInfo->mAuthor, charFormat ); mCursor->setPosition( topFrame->lastPosition() ); mCursor->insertBlock(); } mCursor->insertBlock(); if ( !convertBody( element ) ) { delete mCursor; return nullptr; } } element = element.nextSiblingElement(); } /** * Add document info. */ if ( mTitleInfo ) { if ( !mTitleInfo->mTitle.isEmpty() ) emit addMetaData( Okular::DocumentInfo::Title, mTitleInfo->mTitle ); if ( !mTitleInfo->mAuthor.isEmpty() ) emit addMetaData( Okular::DocumentInfo::Author, mTitleInfo->mAuthor ); } if ( mDocumentInfo ) { if ( !mDocumentInfo->mProducer.isEmpty() ) emit addMetaData( Okular::DocumentInfo::Producer, mDocumentInfo->mProducer ); if ( mDocumentInfo->mDate.isValid() ) emit addMetaData( Okular::DocumentInfo::CreationDate, QLocale().toString( mDocumentInfo->mDate, QLocale::ShortFormat ) ); } QMapIterator > it( mLocalLinks ); while ( it.hasNext() ) { it.next(); const QTextBlock block = mSectionMap[ it.key() ]; if ( !block.isValid() ) // local link without existing target continue; Okular::DocumentViewport viewport = calculateViewport( mTextDocument, block ); Okular::GotoAction *action = new Okular::GotoAction( QString(), viewport ); emit addAction( action, it.value().first, it.value().second ); } delete mCursor; return mTextDocument; } bool Converter::convertBody( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "section" ) ) { mCursor->insertBlock(); if ( !convertSection( child ) ) return false; } else if ( child.tagName() == QLatin1String( "image" ) ) { if ( !convertImage( child ) ) return false; } else if ( child.tagName() == QLatin1String( "title" ) ) { if ( !convertTitle( child ) ) return false; } else if ( child.tagName() == QLatin1String( "epigraph" ) ) { if ( !convertEpigraph( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertDescription( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "title-info" ) ) { if ( !convertTitleInfo( child ) ) return false; } if ( child.tagName() == QLatin1String( "document-info" ) ) { if ( !convertDocumentInfo( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertTitleInfo( const QDomElement &element ) { delete mTitleInfo; mTitleInfo = new TitleInfo; QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "genre" ) ) { QString genre; if ( !convertTextNode( child, genre ) ) return false; if ( !genre.isEmpty() ) mTitleInfo->mGenres.append( genre ); } else if ( child.tagName() == QLatin1String( "author" ) ) { QString firstName, middleName, lastName, dummy; if ( !convertAuthor( child, firstName, middleName, lastName, dummy, dummy ) ) return false; mTitleInfo->mAuthor = QStringLiteral( "%1 %2 %3" ).arg( firstName, middleName, lastName ); } else if ( child.tagName() == QLatin1String( "book-title" ) ) { if ( !convertTextNode( child, mTitleInfo->mTitle ) ) return false; } else if ( child.tagName() == QLatin1String( "keywords" ) ) { QString keywords; if ( !convertTextNode( child, keywords ) ) return false; mTitleInfo->mKeywords = keywords.split( QLatin1Char(' '), QString::SkipEmptyParts ); } else if ( child.tagName() == QLatin1String( "date" ) ) { if ( !convertDate( child, mTitleInfo->mDate ) ) return false; } else if ( child.tagName() == QLatin1String( "coverpage" ) ) { mTitleInfo->mCoverPage = child; } else if ( child.tagName() == QLatin1String( "lang" ) ) { if ( !convertTextNode( child, mTitleInfo->mLanguage ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertDocumentInfo( const QDomElement &element ) { delete mDocumentInfo; mDocumentInfo = new DocumentInfo; QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "author" ) ) { QString firstName, middleName, lastName, email, nickname; if ( !convertAuthor( child, firstName, middleName, lastName, email, nickname ) ) return false; mDocumentInfo->mAuthor = QStringLiteral( "%1 %2 %3 <%4> (%5)" ) .arg( firstName, middleName, lastName, email, nickname ); } else if ( child.tagName() == QLatin1String( "program-used" ) ) { if ( !convertTextNode( child, mDocumentInfo->mProducer ) ) return false; } else if ( child.tagName() == QLatin1String( "date" ) ) { if ( !convertDate( child, mDocumentInfo->mDate ) ) return false; } else if ( child.tagName() == QLatin1String( "id" ) ) { if ( !convertTextNode( child, mDocumentInfo->mId ) ) return false; } else if ( child.tagName() == QLatin1String( "version" ) ) { if ( !convertTextNode( child, mDocumentInfo->mVersion ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertAuthor( const QDomElement &element, QString &firstName, QString &middleName, QString &lastName, QString &email, QString &nickname ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "first-name" ) ) { if ( !convertTextNode( child, firstName ) ) return false; } else if ( child.tagName() == QLatin1String( "middle-name" ) ) { if ( !convertTextNode( child, middleName ) ) return false; } else if ( child.tagName() == QLatin1String( "last-name" ) ) { if ( !convertTextNode( child, lastName ) ) return false; } else if ( child.tagName() == QLatin1String( "email" ) ) { if ( !convertTextNode( child, email ) ) return false; } else if ( child.tagName() == QLatin1String( "nickname" ) ) { if ( !convertTextNode( child, nickname ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertTextNode( const QDomElement &element, QString &data ) { QDomNode child = element.firstChild(); while ( !child.isNull() ) { QDomText text = child.toText(); if ( !text.isNull() ) data = text.data(); child = child.nextSibling(); } return true; } bool Converter::convertDate( const QDomElement &element, QDate &date ) { if ( element.hasAttribute( QStringLiteral("value") ) ) date = QDate::fromString( element.attribute( QStringLiteral("value") ), Qt::ISODate ); return true; } bool Converter::convertSection( const QDomElement &element ) { if ( element.hasAttribute( QStringLiteral("id") ) ) mSectionMap.insert( element.attribute( QStringLiteral("id") ), mCursor->block() ); mSectionCounter++; QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "title" ) ) { if ( !convertTitle( child ) ) return false; } else if ( child.tagName() == QLatin1String( "epigraph" ) ) { if ( !convertEpigraph( child ) ) return false; } else if ( child.tagName() == QLatin1String( "image" ) ) { if ( !convertImage( child ) ) return false; } else if ( child.tagName() == QLatin1String( "section" ) ) { if ( !convertSection( child ) ) return false; } else if ( child.tagName() == QLatin1String( "p" ) ) { QTextBlockFormat format; format.setTextIndent( 10 ); mCursor->insertBlock( format ); if ( !convertParagraph( child ) ) return false; } else if ( child.tagName() == QLatin1String( "poem" ) ) { if ( !convertPoem( child ) ) return false; } else if ( child.tagName() == QLatin1String( "subtitle" ) ) { if ( !convertSubTitle( child ) ) return false; } else if ( child.tagName() == QLatin1String( "cite" ) ) { if ( !convertCite( child ) ) return false; } else if ( child.tagName() == QLatin1String( "empty-line" ) ) { if ( !convertEmptyLine( child ) ) return false; } else if ( child.tagName() == QLatin1String( "code" ) ) { if( !convertCode( child ) ) return false; } child = child.nextSiblingElement(); } mSectionCounter--; return true; } bool Converter::convertTitle( const QDomElement &element ) { QTextFrame *topFrame = mCursor->currentFrame(); QTextFrameFormat frameFormat; frameFormat.setBorder( 1 ); frameFormat.setPadding( 8 ); frameFormat.setBackground( Qt::lightGray ); mCursor->insertFrame( frameFormat ); QDomElement child = element.firstChildElement(); bool firstParagraph = true; while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "p" ) ) { if ( firstParagraph ) firstParagraph = false; else mCursor->insertBlock(); QTextCharFormat origFormat = mCursor->charFormat(); QTextCharFormat titleFormat( origFormat ); titleFormat.setFontPointSize( 22 - ( mSectionCounter*2 ) ); titleFormat.setFontWeight( QFont::Bold ); mCursor->setCharFormat( titleFormat ); if ( !convertParagraph( child ) ) return false; mCursor->setCharFormat( origFormat ); emit addTitle( mSectionCounter, child.text(), mCursor->block() ); } else if ( child.tagName() == QLatin1String( "empty-line" ) ) { if ( !convertEmptyLine( child ) ) return false; } child = child.nextSiblingElement(); } mCursor->setPosition( topFrame->lastPosition() ); return true; } bool Converter::convertParagraph( const QDomElement &element ) { QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "emphasis" ) ) { if ( !convertEmphasis( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "strong" ) ) { if ( !convertStrong( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "style" ) ) { if ( !convertStyle( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "a" ) ) { if ( !convertLink( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "image" ) ) { if ( !convertImage( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "strikethrough" ) ) { if ( !convertStrikethrough( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "code" ) ) { if( !convertCode( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "sup" ) ) { if( !convertSuperScript( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "sub" ) ) { if( !convertSubScript( childElement ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); mCursor->insertText( childText.data() ); } child = child.nextSibling(); } return true; } bool Converter::convertEmphasis( const QDomElement &element ) { QTextCharFormat origFormat = mCursor->charFormat(); QTextCharFormat italicFormat( origFormat ); italicFormat.setFontItalic( true ); mCursor->setCharFormat( italicFormat ); if ( !convertParagraph( element ) ) return false; mCursor->setCharFormat( origFormat ); return true; } bool Converter::convertStrikethrough( const QDomElement &element ) { QTextCharFormat origFormat = mCursor->charFormat(); QTextCharFormat strikeoutFormat( origFormat ); strikeoutFormat.setFontStrikeOut( true ); mCursor->setCharFormat( strikeoutFormat ); if ( !convertParagraph( element ) ) return false; mCursor->setCharFormat( origFormat ); return true; } bool Converter::convertStrong( const QDomElement &element ) { QTextCharFormat origFormat = mCursor->charFormat(); QTextCharFormat boldFormat( origFormat ); boldFormat.setFontWeight( QFont::Bold ); mCursor->setCharFormat( boldFormat ); if ( !convertParagraph( element ) ) return false; mCursor->setCharFormat( origFormat ); return true; } bool Converter::convertStyle( const QDomElement &element ) { if ( !convertParagraph( element ) ) return false; return true; } bool Converter::convertBinary( const QDomElement &element ) { const QString id = element.attribute( QStringLiteral("id") ); const QDomText textNode = element.firstChild().toText(); QByteArray data = textNode.data().toLatin1(); data = QByteArray::fromBase64( data ); mTextDocument->addResource( QTextDocument::ImageResource, QUrl( id ), QImage::fromData( data ) ); return true; } bool Converter::convertCover( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "image" ) ) { if ( !convertImage( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertImage( const QDomElement &element ) { QString href = element.attributeNS( QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href") ); if ( href.startsWith( QLatin1Char('#') ) ) href = href.mid( 1 ); const QImage img = qvariant_cast( mTextDocument->resource( QTextDocument::ImageResource, QUrl( href ) ) ); QTextImageFormat format; format.setName( href ); if ( img.width() > 560 ) format.setWidth( 560 ); format.setHeight( img.height() ); mCursor->insertImage( format ); return true; } bool Converter::convertEpigraph( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "p" ) ) { QTextBlockFormat format; format.setTextIndent( 10 ); mCursor->insertBlock( format ); if ( !convertParagraph( child ) ) return false; } else if ( child.tagName() == QLatin1String( "poem" ) ) { if ( !convertPoem( child ) ) return false; } else if ( child.tagName() == QLatin1String( "cite" ) ) { if ( !convertCite( child ) ) return false; } else if ( child.tagName() == QLatin1String( "empty-line" ) ) { if ( !convertEmptyLine( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertPoem( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "title" ) ) { if ( !convertTitle( child ) ) return false; } else if ( child.tagName() == QLatin1String( "epigraph" ) ) { if ( !convertEpigraph( child ) ) return false; } else if ( child.tagName() == QLatin1String( "empty-line" ) ) { if ( !convertEmptyLine( child ) ) return false; } else if ( child.tagName() == QLatin1String( "stanza" ) ) { if ( !convertStanza( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertSubTitle( const QDomElement &element ) { Q_UNUSED( element ) return true; } bool Converter::convertCite( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "p" ) ) { QTextBlockFormat format; format.setTextIndent( 10 ); mCursor->insertBlock( format ); if ( !convertParagraph( child ) ) return false; } else if ( child.tagName() == QLatin1String( "poem" ) ) { if ( !convertParagraph( child ) ) return false; } else if ( child.tagName() == QLatin1String( "empty-line" ) ) { if ( !convertEmptyLine( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertEmptyLine( const QDomElement& ) { mCursor->insertText( QStringLiteral("\n\n") ); return true; } bool Converter::convertLink( const QDomElement &element ) { QString href = element.attributeNS( QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href") ); QString type = element.attributeNS( QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("type") ); if ( type == QLatin1String("note") ) mCursor->insertText( QStringLiteral("[") ); int startPosition = mCursor->position(); QTextCharFormat origFormat( mCursor->charFormat() ); QTextCharFormat format( mCursor->charFormat() ); format.setForeground( Qt::blue ); format.setFontUnderline( true ); mCursor->setCharFormat( format ); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "emphasis" ) ) { if ( !convertEmphasis( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "strong" ) ) { if ( !convertStrong( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "style" ) ) { if ( !convertStyle( childElement ) ) return false; } } else if ( child.isText() ) { const QDomText text = child.toText(); if ( !text.isNull() ) { mCursor->insertText( text.data() ); } } child = child.nextSibling(); } mCursor->setCharFormat( origFormat ); int endPosition = mCursor->position(); if ( type == QLatin1String("note") ) mCursor->insertText( QStringLiteral("]") ); if ( href.startsWith( QLatin1Char('#') ) ) { // local link mLocalLinks.insert( href.mid( 1 ), QPair( startPosition, endPosition ) ); } else { // external link Okular::BrowseAction *action = new Okular::BrowseAction( QUrl(href) ); emit addAction( action, startPosition, endPosition ); } return true; } bool Converter::convertStanza( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "title" ) ) { if ( !convertTitle( child ) ) return false; } else if ( child.tagName() == QLatin1String( "subtitle" ) ) { if ( !convertSubTitle( child ) ) return false; } else if ( child.tagName() == QLatin1String( "v" ) ) { QTextBlockFormat format; format.setTextIndent( 50 ); mCursor->insertBlock( format ); if ( !convertParagraph( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertCode( const QDomElement &element ) { QTextCharFormat origFormat = mCursor->charFormat(); QTextCharFormat codeFormat( origFormat ); codeFormat.setFontFamily( QStringLiteral( "monospace" ) ); mCursor->setCharFormat( codeFormat ); if ( !convertParagraph( element ) ) return false; mCursor->setCharFormat( origFormat ); return true; } bool Converter::convertSuperScript( const QDomElement &element ) { QTextCharFormat origFormat = mCursor->charFormat(); QTextCharFormat superScriptFormat( origFormat ); superScriptFormat.setVerticalAlignment( QTextCharFormat::AlignSuperScript ); mCursor->setCharFormat( superScriptFormat ); if ( !convertParagraph( element ) ) return false; mCursor->setCharFormat( origFormat ); return true; } bool Converter::convertSubScript( const QDomElement &element ) { QTextCharFormat origFormat = mCursor->charFormat(); QTextCharFormat subScriptFormat( origFormat ); subScriptFormat.setVerticalAlignment( QTextCharFormat::AlignSubScript ); mCursor->setCharFormat( subScriptFormat ); if ( !convertParagraph( element ) ) return false; mCursor->setCharFormat( origFormat ); return true; } diff --git a/generators/fictionbook/document.cpp b/generators/fictionbook/document.cpp index 49c4aaabc..df5bf3970 100644 --- a/generators/fictionbook/document.cpp +++ b/generators/fictionbook/document.cpp @@ -1,91 +1,91 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "document.h" -#include +#include #include #include using namespace FictionBook; Document::Document( const QString &fileName ) : mFileName( fileName ) { } bool Document::open() { QIODevice *device; QFile file( mFileName ); KZip zip( mFileName ); if ( mFileName.endsWith( QLatin1String(".fb") ) || mFileName.endsWith( QLatin1String(".fb2") ) ) { if ( !file.open( QIODevice::ReadOnly ) ) { setError( i18n( "Unable to open document: %1", file.errorString() ) ); return false; } device = &file; } else { if ( !zip.open( QIODevice::ReadOnly ) ) { setError( i18n( "Document is not a valid ZIP archive" ) ); return false; } const KArchiveDirectory *directory = zip.directory(); if ( !directory ) { setError( i18n( "Invalid document structure (main directory is missing)" ) ); return false; } const QStringList entries = directory->entries(); QString documentFile; for ( int i = 0; i < entries.count(); ++i ) { if ( entries[ i ].endsWith( QLatin1String(".fb2") ) ) { documentFile = entries[ i ]; break; } } if ( documentFile.isEmpty() ) { setError( i18n( "No content found in the document" ) ); return false; } const KArchiveFile *entry = static_cast( directory->entry( documentFile ) ); // FIXME delete 'deviceì somewhen device = entry->createDevice(); } QString errorMsg; if ( !mDocument.setContent( device, true, &errorMsg ) ) { setError( i18n( "Invalid XML document: %1", errorMsg ) ); return false; } return true; } QDomDocument Document::content() const { return mDocument; } QString Document::lastErrorString() const { return mErrorString; } void Document::setError( const QString &error ) { mErrorString = error; } diff --git a/generators/fictionbook/document.h b/generators/fictionbook/document.h index 0abf5848e..de0b83260 100644 --- a/generators/fictionbook/document.h +++ b/generators/fictionbook/document.h @@ -1,41 +1,41 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 FICTIONBOOK_DOCUMENT_H #define FICTIONBOOK_DOCUMENT_H -#include -#include -#include -#include +#include +#include +#include +#include namespace FictionBook { class Document { public: Document( const QString &fileName ); bool open(); QDomDocument content() const; QString lastErrorString() const; private: void setError( const QString& ); QString mFileName; QDomDocument mDocument; QString mErrorString; }; } #endif diff --git a/generators/kimgio/generator_kimgio.h b/generators/kimgio/generator_kimgio.h index bd09bbed0..2242b121f 100644 --- a/generators/kimgio/generator_kimgio.h +++ b/generators/kimgio/generator_kimgio.h @@ -1,53 +1,53 @@ /*************************************************************************** * Copyright (C) 2005 by Albert Astals Cid * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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_GENERATOR_KIMGIO_H_ #define _OKULAR_GENERATOR_KIMGIO_H_ #include #include -#include +#include class KIMGIOGenerator : public Okular::Generator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: KIMGIOGenerator( QObject *parent, const QVariantList &args ); virtual ~KIMGIOGenerator(); // [INHERITED] load a document and fill up the pagesVector bool loadDocument( const QString & fileName, QVector & pagesVector ) override; bool loadDocumentFromData( const QByteArray & fileData, QVector & pagesVector ) override; SwapBackingFileResult swapBackingFile( QString const &newFileName, QVector & newPagesVector ) override; // [INHERITED] print document using already configured kprinter bool print( QPrinter& printer ) override; // [INHERITED] document information Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override; protected: bool doCloseDocument() override; QImage image( Okular::PixmapRequest * request ) override; private: bool loadDocumentInternal(const QByteArray & fileData, const QString & fileName, QVector & pagesVector ); private: QImage m_img; Okular::DocumentInfo docInfo; }; #endif diff --git a/generators/markdown/debug_md.h b/generators/markdown/debug_md.h index 9c06d7742..cab7ec487 100644 --- a/generators/markdown/debug_md.h +++ b/generators/markdown/debug_md.h @@ -1,19 +1,19 @@ /*************************************************************************** * Copyright (C) 2006 by Luigi Toscano * * Copyright (C) 2014 by Frederik Gladhorn * * * * 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_MD_DEBUG_P_H -#define OKULAR_MD_DEBUG_P_H +#ifndef OKULAR_DEBUG_MD_H +#define OKULAR_DEBUG_MD_H -#include +#include Q_DECLARE_LOGGING_CATEGORY(OkularMdDebug) #endif diff --git a/generators/mobipocket/converter.cpp b/generators/mobipocket/converter.cpp index 09ac087bd..9888a1ec9 100644 --- a/generators/mobipocket/converter.cpp +++ b/generators/mobipocket/converter.cpp @@ -1,106 +1,106 @@ /*************************************************************************** * Copyright (C) 2008 by Jakub Stachowski * * * * 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 "converter.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include using namespace Mobi; Converter::Converter() { } Converter::~Converter() { } void Converter::handleMetadata(const QMap metadata) { QMapIterator it(metadata); while (it.hasNext()) { it.next(); switch (it.key()) { case Mobipocket::Document::Title: addMetaData(Okular::DocumentInfo::Title, it.value()); break; case Mobipocket::Document::Author: addMetaData(Okular::DocumentInfo::Author, it.value()); break; case Mobipocket::Document::Description: addMetaData(Okular::DocumentInfo::Description, it.value()); break; case Mobipocket::Document::Subject: addMetaData(Okular::DocumentInfo::Subject, it.value()); break; case Mobipocket::Document::Copyright: addMetaData(Okular::DocumentInfo::Copyright, it.value()); break; } } } QTextDocument* Converter::convert( const QString &fileName ) { MobiDocument* newDocument=new MobiDocument(fileName); if (!newDocument->mobi()->isValid()) { emit error(i18n("Error while opening the Mobipocket document."), -1); delete newDocument; return NULL; } if (newDocument->mobi()->hasDRM()) { emit error(i18n("This book is protected by DRM and can be displayed only on designated device"), -1); delete newDocument; return NULL; } handleMetadata(newDocument->mobi()->metadata()); newDocument->setPageSize(QSizeF(600, 800)); QTextFrameFormat frameFormat; frameFormat.setMargin( 20 ); QTextFrame *rootFrame = newDocument->rootFrame(); rootFrame->setFrameFormat( frameFormat ); QMap > links; QMap targets; // go over whole document and add all tags to links or targets map for (QTextBlock it = newDocument->begin(); it != newDocument->end(); it = it.next()) for (QTextBlock::iterator fit=it.begin(); !fit.atEnd(); ++fit) { QTextFragment frag=fit.fragment(); QTextCharFormat format=frag.charFormat(); if (!format.isAnchor()) continue; //link if (!format.anchorHref().isEmpty()) links[format.anchorHref()]= QPair(frag.position(), frag.position()+frag.length()); if (!format.anchorNames().isEmpty()) { // link targets Q_FOREACH(const QString& name, format.anchorNames()) targets[QLatin1Char('#')+name]=it; } } // create link actions QMapIterator > it(links); while (it.hasNext()) { it.next(); QUrl u(it.key()); // external or internal link if (!u.isRelative()) emit addAction(new Okular::BrowseAction(QUrl(it.key())), it.value().first, it.value().second); else { // is there valid target? if (!targets.contains( it.key() ) || !targets[it.key()].isValid()) continue; emit addAction(new Okular::GotoAction(QString(), calculateViewport( newDocument, targets[it.key()] )), it.value().first, it.value().second); } } return newDocument; } diff --git a/generators/mobipocket/mobidocument.cpp b/generators/mobipocket/mobidocument.cpp index c3b1f81d3..d89d5580a 100644 --- a/generators/mobipocket/mobidocument.cpp +++ b/generators/mobipocket/mobidocument.cpp @@ -1,116 +1,116 @@ /*************************************************************************** * Copyright (C) 2008 by Jakub Stachowski * * * * 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 "mobidocument.h" #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include // Because of the HACK #include // Because of the HACK using namespace Mobi; MobiDocument::MobiDocument(const QString &fileName) : QTextDocument() { file = new Mobipocket::QFileStream(fileName); doc = new Mobipocket::Document(file); if (doc->isValid()) { QString text=doc->text(); QString header=text.left(1024); if (header.contains(QStringLiteral("")) || header.contains(QStringLiteral(""))) { // HACK BEGIN Get the links without CSS to be blue // Remove if Qt ever gets fixed and the code in textdocumentgenerator.cpp works const QPalette orig = qApp->palette(); QPalette p = orig; p.setColor(QPalette::Link, Qt::blue); qApp->setPalette(p); // HACK END setHtml(fixMobiMarkup(text)); // HACK BEGIN qApp->setPalette(orig); // HACK END } else { setPlainText(text); } } } MobiDocument::~MobiDocument() { delete doc; delete file; } QVariant MobiDocument::loadResource(int type, const QUrl &name) { if (type!=QTextDocument::ImageResource || name.scheme()!=QString(QStringLiteral("pdbrec"))) return QVariant(); bool ok; quint16 recnum=name.path().mid(1).toUShort(&ok); if (!ok || recnum>=doc->imageCount()) return QVariant(); QVariant resource; resource.setValue(doc->getImage(recnum-1)); addResource(type, name, resource); return resource; } // starting from 'pos', find position in the string that is not inside a tag int outsideTag(const QString& data, int pos) { for (int i=pos-1;i>=0;i--) { if (data[i]==QLatin1Char('>')) return pos; if (data[i]==QLatin1Char('<')) return i; } return pos; } QString MobiDocument::fixMobiMarkup(const QString& data) { QString ret=data; QMap anchorPositions; static QRegExp anchors(QStringLiteral(" it(anchorPositions); while (it.hasNext()) { it.next(); // link pointing outside the document, ignore if ( (it.key()+offset) >= ret.size()) continue; int fixedpos=outsideTag(ret, it.key()+offset); ret.insert(fixedpos,QStringLiteral(" ")); // inserting anchor shifts all offsets after the anchor offset+=21+it.value().size(); } // replace links referencing filepos with normal internal links ret.replace(anchors, QStringLiteral(" where recindex is number of // record containing image static QRegExp imgs(QStringLiteral(""), Qt::CaseInsensitive); imgs.setMinimal(true); ret.replace(imgs, QStringLiteral("")); ret.replace(QStringLiteral(""), QStringLiteral("

")); return ret; } diff --git a/generators/ooo/converter.cpp b/generators/ooo/converter.cpp index 202e14071..159a94f8e 100644 --- a/generators/ooo/converter.cpp +++ b/generators/ooo/converter.cpp @@ -1,594 +1,594 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 "converter.h" -#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 "document.h" #include "styleinformation.h" #include "styleparser.h" using namespace OOO; class Style { public: Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat ); QTextBlockFormat blockFormat() const; QTextCharFormat textFormat() const; private: QTextBlockFormat mBlockFormat; QTextCharFormat mTextFormat; }; Style::Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat ) : mBlockFormat( blockFormat ), mTextFormat( textFormat ) { } QTextBlockFormat Style::blockFormat() const { return mBlockFormat; } QTextCharFormat Style::textFormat() const { return mTextFormat; } Converter::Converter() : mTextDocument( nullptr ), mCursor( nullptr ), mStyleInformation( nullptr ) { } Converter::~Converter() { } Okular::Document::OpenResult Converter::convertWithPassword( const QString &fileName, const QString &password ) { Document oooDocument( fileName ); if ( !oooDocument.open( password ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( oooDocument.lastErrorString(), -1 ); return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } mTextDocument = new QTextDocument; mCursor = new QTextCursor( mTextDocument ); /** * Create the dom of the content */ QXmlSimpleReader reader; QXmlInputSource source; source.setData( oooDocument.content() ); QString errorMsg; QDomDocument document; if ( !document.setContent( &source, &reader, &errorMsg ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Invalid XML document: %1", errorMsg ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } mStyleInformation = new StyleInformation(); /** * Read the style properties, so the are available when * parsing the content. */ StyleParser styleParser( &oooDocument, document, mStyleInformation ); if ( !styleParser.parse() ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Unable to read style information" ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } /** * Add all images of the document to resource framework */ const QMap images = oooDocument.images(); QMapIterator it( images ); while ( it.hasNext() ) { it.next(); mTextDocument->addResource( QTextDocument::ImageResource, QUrl( it.key() ), QImage::fromData( it.value() ) ); } /** * Set the correct page size */ const QString masterLayout = mStyleInformation->masterPageName(); const PageFormatProperty property = mStyleInformation->pageProperty( masterLayout ); const QSizeF dpi = Okular::Utils::realDpi(nullptr); int pageWidth = qRound(property.width() / 72.0 * dpi.width()); int pageHeight = qRound(property.height() / 72.0 * dpi.height()); if ( pageWidth == 0 ) pageWidth = 600; if ( pageHeight == 0 ) pageHeight = 800; mTextDocument->setPageSize( QSize( pageWidth, pageHeight ) ); QTextFrameFormat frameFormat; frameFormat.setMargin( qRound( property.margin() ) ); QTextFrame *rootFrame = mTextDocument->rootFrame(); rootFrame->setFrameFormat( frameFormat ); /** * Parse the content of the document */ const QDomElement documentElement = document.documentElement(); QDomElement element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "body" ) ) { if ( !convertBody( element ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Unable to convert document content" ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } } element = element.nextSiblingElement(); } MetaInformation::List metaInformation = mStyleInformation->metaInformation(); for ( int i = 0; i < metaInformation.count(); ++i ) { emit addMetaData( metaInformation[ i ].key(), metaInformation[ i ].value(), metaInformation[ i ].title() ); } delete mCursor; delete mStyleInformation; mStyleInformation = nullptr; setDocument( mTextDocument ); return Okular::Document::OpenSuccess; } bool Converter::convertBody( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "text" ) ) { if ( !convertText( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertText( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "p" ) ) { mCursor->insertBlock(); if ( !convertParagraph( mCursor, child ) ) return false; } else if ( child.tagName() == QLatin1String( "h" ) ) { mCursor->insertBlock(); if ( !convertHeader( mCursor, child ) ) return false; } else if ( child.tagName() == QLatin1String( "list" ) ) { if ( !convertList( mCursor, child ) ) return false; } else if ( child.tagName() == QLatin1String( "table" ) ) { if ( !convertTable( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertHeader( QTextCursor *cursor, const QDomElement &element ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextBlockFormat blockFormat; QTextCharFormat textFormat; property.applyBlock( &blockFormat ); property.applyText( &textFormat ); cursor->setBlockFormat( blockFormat ); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "span" ) ) { if ( !convertSpan( cursor, childElement, textFormat ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, textFormat ) ) return false; } child = child.nextSibling(); } emit addTitle( element.attribute( QStringLiteral("outline-level"), QStringLiteral("0") ).toInt(), element.text(), cursor->block() ); return true; } bool Converter::convertParagraph( QTextCursor *cursor, const QDomElement &element, const QTextBlockFormat &parentFormat, bool merge ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextBlockFormat blockFormat( parentFormat ); QTextCharFormat textFormat; property.applyBlock( &blockFormat ); property.applyText( &textFormat ); if ( merge ) cursor->mergeBlockFormat( blockFormat ); else cursor->setBlockFormat( blockFormat ); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "span" ) ) { if ( !convertSpan( cursor, childElement, textFormat ) ) return false; } else if ( childElement.tagName() == QLatin1String( "tab" ) ) { mCursor->insertText( QStringLiteral(" ") ); } else if ( childElement.tagName() == QLatin1String( "s" ) ) { QString spaces; spaces.fill( QLatin1Char(' '), childElement.attribute( QStringLiteral("c") ).toInt() ); mCursor->insertText( spaces ); } else if ( childElement.tagName() == QLatin1String( "frame" ) ) { if ( !convertFrame( childElement ) ) return false; } else if ( childElement.tagName() == QLatin1String( "a" ) ) { if ( !convertLink( cursor, childElement, textFormat ) ) return false; } else if ( childElement.tagName() == QLatin1String( "annotation" ) ) { if ( !convertAnnotation( cursor, childElement ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, textFormat ) ) return false; } child = child.nextSibling(); } return true; } bool Converter::convertTextNode( QTextCursor *cursor, const QDomText &element, const QTextCharFormat &format ) { cursor->insertText( element.data(), format ); return true; } bool Converter::convertSpan( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextCharFormat textFormat( format ); property.applyText( &textFormat ); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, textFormat ) ) return false; } child = child.nextSibling(); } return true; } bool Converter::convertList( QTextCursor *cursor, const QDomElement &element ) { const QString styleName = element.attribute( QStringLiteral("style-name") ); const ListFormatProperty property = mStyleInformation->listProperty( styleName ); QTextListFormat format; if ( cursor->currentList() ) { // we are in a nested list format = cursor->currentList()->format(); format.setIndent( format.indent() + 1 ); } property.apply( &format, 0 ); QTextList *list = cursor->insertList( format ); QDomElement itemChild = element.firstChildElement(); int loop = 0; while ( !itemChild.isNull() ) { if ( itemChild.tagName() == QLatin1String( "list-item" ) ) { loop++; QDomElement childElement = itemChild.firstChildElement(); while ( !childElement.isNull() ) { QTextBlock prevBlock; if ( childElement.tagName() == QLatin1String( "p" ) ) { if ( loop > 1 ) cursor->insertBlock(); prevBlock = cursor->block(); if ( !convertParagraph( cursor, childElement, QTextBlockFormat(), true ) ) return false; } else if ( childElement.tagName() == QLatin1String( "list" ) ) { prevBlock = cursor->block(); if ( !convertList( cursor, childElement ) ) return false; } if( prevBlock.isValid() ) list->add( prevBlock ); childElement = childElement.nextSiblingElement(); } } itemChild = itemChild.nextSiblingElement(); } return true; } static void enqueueNodeList( QQueue &queue, const QDomNodeList &list ) { for ( int i = 0; i < list.count(); ++i ) { queue.enqueue( list.at( i ) ); } } bool Converter::convertTable( const QDomElement &element ) { /** * Find out dimension of the table */ int rowCounter = 0; int columnCounter = 0; QQueue nodeQueue; enqueueNodeList( nodeQueue, element.childNodes() ); while ( !nodeQueue.isEmpty() ) { QDomElement el = nodeQueue.dequeue().toElement(); if ( el.isNull() ) continue; if ( el.tagName() == QLatin1String( "table-row" ) ) { rowCounter++; int counter = 0; QDomElement columnElement = el.firstChildElement(); while ( !columnElement.isNull() ) { if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) { counter++; } columnElement = columnElement.nextSiblingElement(); } columnCounter = qMax( columnCounter, counter ); } else if ( el.tagName() == QLatin1String( "table-header-rows" ) ) { enqueueNodeList( nodeQueue, el.childNodes() ); } } /** * Create table */ QTextTable *table = mCursor->insertTable( rowCounter, columnCounter ); mCursor->movePosition( QTextCursor::End ); /** * Fill table */ nodeQueue.clear(); enqueueNodeList( nodeQueue, element.childNodes() ); QTextTableFormat tableFormat; rowCounter = 0; while ( !nodeQueue.isEmpty() ) { QDomElement el = nodeQueue.dequeue().toElement(); if ( el.isNull() ) continue; if ( el.tagName() == QLatin1String( "table-row" ) ) { int columnCounter = 0; QDomElement columnElement = el.firstChildElement(); while ( !columnElement.isNull() ) { if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) { const StyleFormatProperty property = mStyleInformation->styleProperty( columnElement.attribute( QStringLiteral("style-name") ) ); QTextBlockFormat format; property.applyTableCell( &format ); QDomElement paragraphElement = columnElement.firstChildElement(); while ( !paragraphElement.isNull() ) { if ( paragraphElement.tagName() == QLatin1String( "p" ) ) { QTextTableCell cell = table->cellAt( rowCounter, columnCounter ); // Insert a frame into the cell and work on that, so we can handle // different parts of the cell having different block formatting QTextCursor cellCursor = cell.lastCursorPosition(); QTextFrameFormat frameFormat; frameFormat.setMargin( 1 ); // TODO: this shouldn't be hard coded QTextFrame *frame = cellCursor.insertFrame( frameFormat ); QTextCursor frameCursor = frame->firstCursorPosition(); frameCursor.setBlockFormat( format ); if ( !convertParagraph( &frameCursor, paragraphElement, format ) ) return false; } else if ( paragraphElement.tagName() == QLatin1String( "list" ) ) { QTextTableCell cell = table->cellAt( rowCounter, columnCounter ); // insert a list into the cell QTextCursor cellCursor = cell.lastCursorPosition(); if ( !convertList( &cellCursor, paragraphElement ) ) { return false; } } paragraphElement = paragraphElement.nextSiblingElement(); } columnCounter++; } columnElement = columnElement.nextSiblingElement(); } rowCounter++; } else if ( el.tagName() == QLatin1String( "table-column" ) ) { const StyleFormatProperty property = mStyleInformation->styleProperty( el.attribute( QStringLiteral("style-name") ) ); const QString tableColumnNumColumnsRepeated = el.attribute( QStringLiteral("number-columns-repeated"), QStringLiteral("1") ); int numColumnsToApplyTo = tableColumnNumColumnsRepeated.toInt(); for (int i = 0; i < numColumnsToApplyTo; ++i) { property.applyTableColumn( &tableFormat ); } } } table->setFormat( tableFormat ); return true; } bool Converter::convertFrame( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "image" ) ) { const QString href = child.attribute( QStringLiteral("href") ); QTextImageFormat format; format.setWidth( StyleParser::convertUnit( element.attribute( QStringLiteral("width") ) ) ); format.setHeight( StyleParser::convertUnit( element.attribute( QStringLiteral("height") ) ) ); format.setName( href ); mCursor->insertImage( format ); } child = child.nextSiblingElement(); } return true; } bool Converter::convertLink( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format ) { int startPosition = cursor->position(); QDomNode child = element.firstChild(); while ( !child.isNull() ) { if ( child.isElement() ) { const QDomElement childElement = child.toElement(); if ( childElement.tagName() == QLatin1String( "span" ) ) { if ( !convertSpan( cursor, childElement, format ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, format ) ) return false; } child = child.nextSibling(); } int endPosition = cursor->position(); Okular::Action *action = new Okular::BrowseAction( QUrl(element.attribute( QStringLiteral("href") )) ); emit addAction( action, startPosition, endPosition ); return true; } bool Converter::convertAnnotation( QTextCursor *cursor, const QDomElement &element ) { QStringList contents; QString creator; QDateTime dateTime; int position = cursor->position(); QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "creator" ) ) { creator = child.text(); } else if ( child.tagName() == QLatin1String( "date" ) ) { dateTime = QDateTime::fromString( child.text(), Qt::ISODate ); } else if ( child.tagName() == QLatin1String( "p" ) ) { contents.append( child.text() ); } child = child.nextSiblingElement(); } Okular::TextAnnotation *annotation = new Okular::TextAnnotation; annotation->setAuthor( creator ); annotation->setContents( contents.join( QStringLiteral("\n") ) ); annotation->setCreationDate( dateTime ); annotation->style().setColor( QColor( "#ffff00" ) ); annotation->style().setOpacity( 0.5 ); emit addAnnotation( annotation, position, position + 3 ); return true; } diff --git a/generators/ooo/converter.h b/generators/ooo/converter.h index 05cb12a70..056028803 100644 --- a/generators/ooo/converter.h +++ b/generators/ooo/converter.h @@ -1,58 +1,58 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 OOO_CONVERTER_H #define OOO_CONVERTER_H -#include -#include +#include +#include #include #include "styleinformation.h" class QDomElement; class QDomText; namespace OOO { class Document; class Converter : public Okular::TextDocumentConverter { Q_OBJECT public: Converter(); ~Converter(); Okular::Document::OpenResult convertWithPassword( const QString &fileName, const QString &password ) override; private: bool convertBody( const QDomElement &element ); bool convertText( const QDomElement &element ); bool convertHeader( QTextCursor *cursor, const QDomElement &element ); bool convertParagraph( QTextCursor *cursor, const QDomElement &element, const QTextBlockFormat &format = QTextBlockFormat(), bool merge = false ); bool convertTextNode( QTextCursor *cursor, const QDomText &element, const QTextCharFormat &format ); bool convertSpan( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format ); bool convertLink( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format ); bool convertList( QTextCursor *cursor, const QDomElement &element ); bool convertTable( const QDomElement &element ); bool convertFrame( const QDomElement &element ); bool convertAnnotation( QTextCursor *cursor, const QDomElement &element ); QTextDocument *mTextDocument; QTextCursor *mCursor; StyleInformation *mStyleInformation; }; } #endif diff --git a/generators/ooo/document.h b/generators/ooo/document.h index f1dc033fc..9c6bc97f6 100644 --- a/generators/ooo/document.h +++ b/generators/ooo/document.h @@ -1,52 +1,52 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 OOO_DOCUMENT_H #define OOO_DOCUMENT_H -#include -#include -#include +#include +#include +#include #include "manifest.h" namespace OOO { class Document { public: Document( const QString &fileName ); ~Document(); bool open( const QString &password ); QString lastErrorString() const; QByteArray content() const; QByteArray meta() const; QByteArray styles() const; QMap images() const; bool anyFileEncrypted() const; private: void setError( const QString& ); QString mFileName; QByteArray mContent; QByteArray mMeta; QByteArray mStyles; QMap mImages; Manifest *mManifest; QString mErrorString; bool mAnyEncrypted; }; } #endif diff --git a/generators/ooo/formatproperty.cpp b/generators/ooo/formatproperty.cpp index f3dfbb477..4d1eed557 100644 --- a/generators/ooo/formatproperty.cpp +++ b/generators/ooo/formatproperty.cpp @@ -1,441 +1,441 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 "formatproperty.h" -#include +#include #include "styleinformation.h" using namespace OOO; FontFormatProperty::FontFormatProperty() : mFamily( QStringLiteral("Nimbus Sans L") ) { } void FontFormatProperty::apply( QTextFormat *format ) const { format->setProperty( QTextFormat::FontFamily, mFamily ); } void FontFormatProperty::setFamily( const QString &name ) { mFamily = name; } ParagraphFormatProperty::ParagraphFormatProperty() : mPageNumber( 0 ), mWritingMode( LRTB ), mAlignment( Qt::AlignLeft ), mHasAlignment( false ) { } void ParagraphFormatProperty::apply( QTextFormat *format ) const { if ( mWritingMode == LRTB || mWritingMode == TBLR || mWritingMode == LR || mWritingMode == TB ) format->setLayoutDirection( Qt::LeftToRight ); else format->setLayoutDirection( Qt::RightToLeft ); if ( mHasAlignment ) { static_cast( format )->setAlignment( mAlignment ); } format->setProperty( QTextFormat::FrameWidth, 595 ); static_cast( format )->setLeftMargin( mLeftMargin ); if ( mBackgroundColor.isValid() ) format->setBackground( mBackgroundColor ); } void ParagraphFormatProperty::setPageNumber( int number ) { mPageNumber = number; } void ParagraphFormatProperty::setWritingMode( WritingMode mode ) { mWritingMode = mode; } bool ParagraphFormatProperty::writingModeIsRightToLeft() const { return ( ( mWritingMode == RLTB ) || ( mWritingMode == TBRL ) || ( mWritingMode == RL ) ); } void ParagraphFormatProperty::setTextAlignment( Qt::Alignment alignment ) { mHasAlignment = true; mAlignment = alignment; } void ParagraphFormatProperty::setBackgroundColor( const QColor &color ) { mBackgroundColor = color; } void ParagraphFormatProperty::setLeftMargin( const qreal margin ) { mLeftMargin = margin; } TextFormatProperty::TextFormatProperty() : mStyleInformation( nullptr ), mHasFontSize( false ), mFontWeight( -1 ), mFontStyle( -1 ), mTextPosition( 0 ) { } TextFormatProperty::TextFormatProperty( const StyleInformation *information ) : mStyleInformation( information ), mHasFontSize( false ), mFontWeight( -1 ), mFontStyle( -1 ), mTextPosition( 0 ) { } void TextFormatProperty::apply( QTextCharFormat *format ) const { if ( !mFontName.isEmpty() ) { if ( mStyleInformation ) { const FontFormatProperty property = mStyleInformation->fontProperty( mFontName ); property.apply( format ); } } if ( mFontWeight != -1 ) { QFont font = format->font(); font.setWeight( mFontWeight ); format->setFont( font ); } if ( mHasFontSize ) { QFont font = format->font(); font.setPointSize( mFontSize ); format->setFont( font ); } if ( mFontStyle != -1 ) { QFont font = format->font(); font.setStyle( (QFont::Style)mFontStyle ); format->setFont( font ); } if ( mColor.isValid() ) format->setForeground( mColor ); if ( mBackgroundColor.isValid() ) format->setBackground( mBackgroundColor ); // TODO: get FontFormatProperty and apply it // TODO: how to set the base line?!? } void TextFormatProperty::setFontSize( int size ) { mHasFontSize = true; mFontSize = size; } void TextFormatProperty::setFontName( const QString &name ) { mFontName = name; } void TextFormatProperty::setFontWeight( int weight ) { mFontWeight = weight; } void TextFormatProperty::setFontStyle( int style ) { mFontStyle = style; } void TextFormatProperty::setTextPosition( int position ) { mTextPosition = position; } void TextFormatProperty::setColor( const QColor &color ) { mColor = color; } void TextFormatProperty::setBackgroundColor( const QColor &color ) { mBackgroundColor = color; } StyleFormatProperty::StyleFormatProperty() : mStyleInformation( nullptr ), mDefaultStyle( false ) { } StyleFormatProperty::StyleFormatProperty( const StyleInformation *information ) : mStyleInformation( information ), mDefaultStyle( false ) { } void StyleFormatProperty::applyBlock( QTextBlockFormat *format ) const { if ( !mDefaultStyle && !mFamily.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mFamily ); property.applyBlock( format ); } if ( !mParentStyleName.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mParentStyleName ); property.applyBlock( format ); } mParagraphFormat.apply( format ); } void StyleFormatProperty::applyText( QTextCharFormat *format ) const { if ( !mDefaultStyle && !mFamily.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mFamily ); property.applyText( format ); } if ( !mParentStyleName.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mParentStyleName ); property.applyText( format ); } mTextFormat.apply( format ); } void StyleFormatProperty::applyTableColumn( QTextTableFormat *format ) const { if ( !mDefaultStyle && !mFamily.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mFamily ); property.applyTableColumn( format ); } if ( !mParentStyleName.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mParentStyleName ); property.applyTableColumn( format ); } mTableColumnFormat.apply( format ); } void StyleFormatProperty::applyTableCell( QTextBlockFormat *format ) const { if ( !mDefaultStyle && !mFamily.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mFamily ); property.applyTableCell( format ); } if ( !mParentStyleName.isEmpty() && mStyleInformation ) { const StyleFormatProperty property = mStyleInformation->styleProperty( mParentStyleName ); property.applyTableCell( format ); } mTableCellFormat.apply( format ); } void StyleFormatProperty::setParentStyleName( const QString &parentStyleName ) { mParentStyleName = parentStyleName; } QString StyleFormatProperty::parentStyleName() const { return mParentStyleName; } void StyleFormatProperty::setFamily( const QString &family ) { mFamily = family; } void StyleFormatProperty::setDefaultStyle( bool defaultStyle ) { mDefaultStyle = defaultStyle; } void StyleFormatProperty::setMasterPageName( const QString &masterPageName ) { mMasterPageName = masterPageName; } void StyleFormatProperty::setParagraphFormat( const ParagraphFormatProperty &format ) { mParagraphFormat = format; } void StyleFormatProperty::setTextFormat( const TextFormatProperty &format ) { mTextFormat = format; } void StyleFormatProperty::setTableColumnFormat( const TableColumnFormatProperty &format ) { mTableColumnFormat = format; } void StyleFormatProperty::setTableCellFormat( const TableCellFormatProperty &format ) { mTableCellFormat = format; } PageFormatProperty::PageFormatProperty() : mHeight( 0.0 ), mWidth( 0.0 ) { } void PageFormatProperty::apply( QTextFormat *format ) const { format->setProperty( QTextFormat::BlockBottomMargin, mBottomMargin ); format->setProperty( QTextFormat::BlockLeftMargin, mLeftMargin ); format->setProperty( QTextFormat::BlockTopMargin, mTopMargin ); format->setProperty( QTextFormat::BlockRightMargin, mRightMargin ); format->setProperty( QTextFormat::FrameWidth, mWidth ); format->setProperty( QTextFormat::FrameHeight, mHeight ); } void PageFormatProperty::setPageUsage( PageUsage usage ) { mPageUsage = usage; } void PageFormatProperty::setBottomMargin( double margin ) { mBottomMargin = margin; } void PageFormatProperty::setLeftMargin( double margin ) { mLeftMargin = margin; } void PageFormatProperty::setTopMargin( double margin ) { mTopMargin = margin; } void PageFormatProperty::setRightMargin( double margin ) { mRightMargin = margin; } void PageFormatProperty::setHeight( double height ) { mHeight = height; } void PageFormatProperty::setWidth( double width ) { mWidth = width; } void PageFormatProperty::setPrintOrientation( PrintOrientation orientation ) { mPrintOrientation = orientation; } double PageFormatProperty::width() const { return mWidth; } double PageFormatProperty::height() const { return mHeight; } double PageFormatProperty::margin() const { return mLeftMargin; } ListFormatProperty::ListFormatProperty() : mType( Number ) { mIndents.resize( 10 ); } ListFormatProperty::ListFormatProperty( Type type ) : mType( type ) { mIndents.resize( 10 ); } void ListFormatProperty::apply( QTextListFormat *format, int level ) const { if ( mType == Number ) format->setStyle( QTextListFormat::ListDecimal ); else { format->setStyle( QTextListFormat::ListDisc ); if ( level > 0 && level < 10 ) format->setIndent( qRound( mIndents[ level ] ) ); } } void ListFormatProperty::addItem( int level, double indent ) { if ( level < 0 || level >= 10 ) return; mIndents[ level ] = indent; } TableColumnFormatProperty::TableColumnFormatProperty() : mWidth( 0 ), isValid( false ) { } void TableColumnFormatProperty::apply( QTextTableFormat *format ) const { if ( ! isValid ) { return; } QVector lengths = format->columnWidthConstraints(); lengths.append( QTextLength( QTextLength::FixedLength, mWidth ) ); format->setColumnWidthConstraints( lengths ); } void TableColumnFormatProperty::setWidth( double width ) { mWidth = width; isValid = true; } TableCellFormatProperty::TableCellFormatProperty() : mPadding( 0 ), mHasAlignment( false ) { } void TableCellFormatProperty::apply( QTextBlockFormat *format ) const { if ( mBackgroundColor.isValid() ) format->setBackground( mBackgroundColor ); if ( mHasAlignment ) format->setAlignment( mAlignment ); } void TableCellFormatProperty::setBackgroundColor( const QColor &color ) { mBackgroundColor = color; } void TableCellFormatProperty::setPadding( double padding ) { mPadding = padding; } void TableCellFormatProperty::setAlignment( Qt::Alignment alignment ) { mAlignment = alignment; mHasAlignment = true; } diff --git a/generators/ooo/formatproperty.h b/generators/ooo/formatproperty.h index 279e3ee3b..268066906 100644 --- a/generators/ooo/formatproperty.h +++ b/generators/ooo/formatproperty.h @@ -1,239 +1,239 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 OOO_FORMATPROPERTY_H #define OOO_FORMATPROPERTY_H -#include -#include +#include +#include class QTextBlockFormat; class QTextCharFormat; class QTextFormat; class QTextListFormat; class QTextTableFormat; namespace OOO { class StyleInformation; class FontFormatProperty { public: FontFormatProperty(); void apply( QTextFormat *format ) const; void setFamily( const QString &name ); private: QString mFamily; }; class ParagraphFormatProperty { public: enum WritingMode { LRTB, RLTB, TBRL, TBLR, LR, RL, TB, PAGE }; ParagraphFormatProperty(); void apply( QTextFormat *format ) const; void setPageNumber( int number ); void setWritingMode( WritingMode mode ); void setTextAlignment( Qt::Alignment alignment ); void setBackgroundColor( const QColor &color ); void setLeftMargin( const qreal margin ); bool writingModeIsRightToLeft() const; private: int mPageNumber; WritingMode mWritingMode; Qt::Alignment mAlignment; bool mHasAlignment; QColor mBackgroundColor; qreal mLeftMargin; }; class TextFormatProperty { public: TextFormatProperty(); TextFormatProperty( const StyleInformation *information ); void apply( QTextCharFormat *format ) const; void setFontSize( int size ); void setFontName( const QString &name ); void setFontWeight( int weight ); void setFontStyle( int style ); void setTextPosition( int position ); void setColor( const QColor &color ); void setBackgroundColor( const QColor &color ); private: const StyleInformation *mStyleInformation; int mFontSize; bool mHasFontSize; int mFontWeight; QString mFontName; int mFontStyle; int mTextPosition; QColor mColor; QColor mBackgroundColor; }; class PageFormatProperty { public: enum PageUsage { All, Left, Right, Mirrored }; enum PrintOrientation { Portrait, Landscape }; PageFormatProperty(); void apply( QTextFormat *format ) const; void setPageUsage( PageUsage usage ); void setBottomMargin( double margin ); void setLeftMargin( double margin ); void setTopMargin( double margin ); void setRightMargin( double margin ); void setHeight( double height ); void setWidth( double width ); void setPrintOrientation( PrintOrientation orientation ); double width() const; double height() const; double margin() const; private: PageUsage mPageUsage; double mBottomMargin; double mLeftMargin; double mTopMargin; double mRightMargin; double mHeight; double mWidth; PrintOrientation mPrintOrientation; }; class ListFormatProperty { public: enum Type { Number, Bullet }; ListFormatProperty(); ListFormatProperty( Type type ); void apply( QTextListFormat *format, int level ) const; void addItem( int level, double indent = 0 ); private: Type mType; QVector mIndents; }; class TableColumnFormatProperty { public: TableColumnFormatProperty(); void apply( QTextTableFormat *format ) const; void setWidth( double width ); private: double mWidth; bool isValid; }; class TableCellFormatProperty { public: TableCellFormatProperty(); void apply( QTextBlockFormat *format ) const; void setBackgroundColor( const QColor &color ); void setPadding( double padding ); void setAlignment( Qt::Alignment alignment ); private: QColor mBackgroundColor; double mPadding; Qt::Alignment mAlignment; bool mHasAlignment; }; class StyleFormatProperty { public: StyleFormatProperty(); StyleFormatProperty( const StyleInformation *information ); void applyBlock( QTextBlockFormat *format ) const; void applyText( QTextCharFormat *format ) const; void applyTableColumn( QTextTableFormat *format ) const; void applyTableCell( QTextBlockFormat *format ) const; void setParentStyleName( const QString &parentStyleName ); QString parentStyleName() const; void setFamily( const QString &family ); void setDefaultStyle( bool defaultStyle ); void setMasterPageName( const QString &masterPageName ); void setParagraphFormat( const ParagraphFormatProperty &format ); void setTextFormat( const TextFormatProperty &format ); void setTableColumnFormat( const TableColumnFormatProperty &format ); void setTableCellFormat( const TableCellFormatProperty &format ); private: QString mParentStyleName; QString mFamily; QString mMasterPageName; ParagraphFormatProperty mParagraphFormat; TextFormatProperty mTextFormat; TableColumnFormatProperty mTableColumnFormat; TableCellFormatProperty mTableCellFormat; const StyleInformation *mStyleInformation; bool mDefaultStyle; }; } #endif diff --git a/generators/ooo/manifest.h b/generators/ooo/manifest.h index f1b0d5e13..fb5565664 100644 --- a/generators/ooo/manifest.h +++ b/generators/ooo/manifest.h @@ -1,252 +1,252 @@ /*************************************************************************** * Copyright (C) 2007 by Brad Hards * * * * 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 OOO_MANIFEST_H #define OOO_MANIFEST_H -#include -#include -#include +#include +#include +#include #ifdef QCA2 #include #endif -#include +#include Q_DECLARE_LOGGING_CATEGORY(OkularOooDebug) namespace OOO { /** OOO document manifest entry This class holds the manifest information for a single file-entry element. An example of the XML that could be passed in is shown below, although not all manifest entries will have encryption-data. \code \endcode */ class ManifestEntry { public: /** Create a new manifest entry */ ManifestEntry( const QString &fileName ); /** Set the mimetype of the file. */ void setMimeType( const QString &mimeType ); /** Get the file name */ QString fileName() const; /** Set the file size. This is only required for files with encryption */ void setSize( const QString &size ); /** Get the mime type */ QString mimeType() const; /** Get the file size This is only meaningful for files with encryption */ QString size() const; /** Set the checksum type. This is only required for files with encryption */ void setChecksumType( const QString &checksumType ); /** Set the checksum. \param checksum the checksum in base64 encoding. This is only required for files with encryption */ void setChecksum( const QString &checksum ); /** Get the checksum type This is only meaningful for files with encryption */ QString checksumType() const; /** Get the checksum This is only meaningful for files with encryption */ QByteArray checksum() const; /** Set the algorithm name This is only required for files with encryption */ void setAlgorithm( const QString &algorithm ); /** Get the algorithm name This is only meaningful for files with encryption */ QString algorithm() const; /** Set the initialisation vector for the cipher algorithm \param initialisationVector the IV in base64 encoding. This is only required for files with encryption */ void setInitialisationVector( const QString &initialisationVector ); /** Get the initialisation vector for the cipher algorithm This is only meaningful for files with encryption */ QByteArray initialisationVector() const; /** Set the name of the key derivation function This is only required for files with encryption */ void setKeyDerivationName( const QString &keyDerivationName ); /** Get the name of the key derivation function This is only meaningful for files with encryption */ QString keyDerivationName() const; /** Set the iteration count for the key derivation function This is only required for files with encryption */ void setIterationCount( const QString &iterationCount ); /** Get the iteration count for the key derivation function This is only meaningful for files with encryption */ int iterationCount() const; /** Set the salt for the key derivation function \param salt the salt in base64 encoding. This is only required for files with encryption */ void setSalt( const QString &salt ); /** Get the salt for the key derivation function This is only meaningful for files with encryption */ QByteArray salt() const; private: const QString m_fileName; QString m_mimeType; QString m_size; QString m_checksumType; QByteArray m_checksum; QString m_algorithm; QByteArray m_initialisationVector; QString m_keyDerivationName; int m_iterationCount; QByteArray m_salt; }; /** OOO document manifest The document manifest is described in the OASIS Open Office Specification (Version 1.1) section 17.7. This class represents the same data. */ class Manifest { public: /** Create a new manifest. \param odfFileName the name of the parent ODF file \param manifestData the data from the manifest file, as a byte array of the XML format data. \param password to decrypt the file */ Manifest( const QString &odfFileName, const QByteArray &manifestData, const QString &password ); ~Manifest(); /** Check if the manifest indicates that a file is encrypted */ bool testIfEncrypted( const QString &filename ); /** Decrypt data associated with a specific file */ QByteArray decryptFile( const QString &filename, const QByteArray &fileData ); private: /** Retrieve the manifest data for a particular file */ ManifestEntry* entryByName( const QString &filename ); /** Verify whether the password we have is the right one. This verifies the provided checksum */ void checkPassword( ManifestEntry *entry, const QByteArray &fileData, QByteArray *decryptedData ); #ifdef QCA2 QCA::Initializer m_init; #endif const QString m_odfFileName; QMap mEntries; bool m_haveGoodPassword; QString m_password; }; } #endif diff --git a/generators/ooo/styleinformation.h b/generators/ooo/styleinformation.h index b21826513..7f5b5ce46 100644 --- a/generators/ooo/styleinformation.h +++ b/generators/ooo/styleinformation.h @@ -1,76 +1,76 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 OOO_STYLEINFORMATION_H #define OOO_STYLEINFORMATION_H -#include +#include #include "formatproperty.h" namespace OOO { class MetaInformation { public: typedef QList List; MetaInformation( const QString &key, const QString &value, const QString &title ); QString key() const; QString value() const; QString title() const; private: QString mKey; QString mValue; QString mTitle; }; class StyleInformation { public: StyleInformation(); ~StyleInformation(); void addFontProperty( const QString &name, const FontFormatProperty &property ); FontFormatProperty fontProperty( const QString &name ) const; void addStyleProperty( const QString &name, const StyleFormatProperty &property ); StyleFormatProperty styleProperty( const QString &name ) const; void addPageProperty( const QString &name, const PageFormatProperty &property ); PageFormatProperty pageProperty( const QString &name ) const; void addListProperty( const QString &name, const ListFormatProperty &property ); ListFormatProperty listProperty( const QString &name ) const; void addMasterLayout( const QString &name, const QString &layoutName ); QString masterLayout( const QString &name ); void setMasterPageName( const QString &name ); QString masterPageName() const; void addMetaInformation( const QString &key, const QString &value, const QString &title ); MetaInformation::List metaInformation() const; void dump() const; private: QMap mFontProperties; QMap mStyleProperties; QMap mPageProperties; QMap mListProperties; QMap mMasterLayouts; MetaInformation::List mMetaInformation; QString mMasterPageName; }; } #endif diff --git a/generators/ooo/styleparser.cpp b/generators/ooo/styleparser.cpp index 4277a5359..70fd4712a 100644 --- a/generators/ooo/styleparser.cpp +++ b/generators/ooo/styleparser.cpp @@ -1,499 +1,499 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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 "styleparser.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include "document.h" #include "styleinformation.h" using namespace OOO; StyleParser::StyleParser( const Document *document, const QDomDocument &domDocument, StyleInformation *styleInformation ) : mDocument( document ), mDomDocument( domDocument ), mStyleInformation( styleInformation ), mMasterPageNameSet( false ) { } bool StyleParser::parse() { if ( !parseContentFile() ) return false; if ( !parseStyleFile() ) return false; if ( !parseMetaFile() ) return false; return true; } bool StyleParser::parseContentFile() { const QDomElement documentElement = mDomDocument.documentElement(); QDomElement element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "document-common-attrs" ) ) { if ( !parseDocumentCommonAttrs( element ) ) return false; } else if ( element.tagName() == QLatin1String( "font-face-decls" ) ) { if ( !parseFontFaceDecls( element ) ) return false; } else if ( element.tagName() == QLatin1String( "styles" ) ) { if ( !parseStyles( element ) ) return false; } else if ( element.tagName() == QLatin1String( "automatic-styles" ) ) { if ( !parseAutomaticStyles( element ) ) return false; } element = element.nextSiblingElement(); } return true; } bool StyleParser::parseStyleFile() { if ( mDocument->styles().isEmpty() ) return true; QXmlSimpleReader reader; QXmlInputSource source; source.setData( mDocument->styles() ); QString errorMsg; int errorLine, errorCol; QDomDocument document; if ( !document.setContent( &source, &reader, &errorMsg, &errorLine, &errorCol ) ) { qDebug( "%s at (%d,%d)", qPrintable( errorMsg ), errorLine, errorCol ); return false; } const QDomElement documentElement = document.documentElement(); QDomElement element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "styles" ) ) { if ( !parseAutomaticStyles( element ) ) return false; } else if ( element.tagName() == QLatin1String( "automatic-styles" ) ) { if ( !parseAutomaticStyles( element ) ) return false; } else if ( element.tagName() == QLatin1String( "master-styles" ) ) { if ( !parseMasterStyles( element ) ) return false; } element = element.nextSiblingElement(); } return true; } bool StyleParser::parseMetaFile() { if ( mDocument->meta().isEmpty() ) return true; QXmlSimpleReader reader; QXmlInputSource source; source.setData( mDocument->meta() ); QString errorMsg; int errorLine, errorCol; QDomDocument document; if ( !document.setContent( &source, &reader, &errorMsg, &errorLine, &errorCol ) ) { qDebug( "%s at (%d,%d)", qPrintable( errorMsg ), errorLine, errorCol ); return false; } const QDomElement documentElement = document.documentElement(); QDomElement element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "meta" ) ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "generator" ) ) { mStyleInformation->addMetaInformation( QStringLiteral("producer"), child.text(), i18n( "Producer" ) ); } else if ( child.tagName() == QLatin1String( "creation-date" ) ) { const QDateTime dateTime = QDateTime::fromString( child.text(), Qt::ISODate ); mStyleInformation->addMetaInformation( QStringLiteral("creationDate"), QLocale().toString( dateTime, QLocale::LongFormat ), i18n( "Created" ) ); } else if ( child.tagName() == QLatin1String( "initial-creator" ) ) { mStyleInformation->addMetaInformation( QStringLiteral("creator"), child.text(), i18n( "Creator" ) ); } else if ( child.tagName() == QLatin1String( "creator" ) ) { mStyleInformation->addMetaInformation( QStringLiteral("author"), child.text(), i18n( "Author" ) ); } else if ( child.tagName() == QLatin1String( "date" ) ) { const QDateTime dateTime = QDateTime::fromString( child.text(), Qt::ISODate ); mStyleInformation->addMetaInformation( QStringLiteral("modificationDate"), QLocale().toString( dateTime, QLocale::LongFormat ), i18n( "Modified" ) ); } child = child.nextSiblingElement(); } } element = element.nextSiblingElement(); } return true; } bool StyleParser::parseDocumentCommonAttrs( QDomElement& ) { return true; } bool StyleParser::parseFontFaceDecls( QDomElement &parent ) { QDomElement element = parent.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "font-face" ) ) { FontFormatProperty property; property.setFamily( element.attribute( QStringLiteral("font-family") ) ); mStyleInformation->addFontProperty( element.attribute( QStringLiteral("name") ), property ); } else { qDebug( "unknown tag %s", qPrintable( element.tagName() ) ); } element = element.nextSiblingElement(); } return true; } bool StyleParser::parseStyles( QDomElement& ) { return true; } bool StyleParser::parseMasterStyles( QDomElement &parent ) { QDomElement element = parent.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "master-page" ) ) { mStyleInformation->addMasterLayout( element.attribute( QStringLiteral("name") ), element.attribute( QStringLiteral("page-layout-name") ) ); if ( !mMasterPageNameSet ) { mStyleInformation->setMasterPageName( element.attribute( QStringLiteral("name") ) ); mMasterPageNameSet = true; } } else { qDebug( "unknown tag %s", qPrintable( element.tagName() ) ); } element = element.nextSiblingElement(); } return true; } bool StyleParser::parseAutomaticStyles( QDomElement &parent ) { QDomElement element = parent.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "style" ) ) { const StyleFormatProperty property = parseStyleProperty( element ); mStyleInformation->addStyleProperty( element.attribute( QStringLiteral("name") ), property ); } else if ( element.tagName() == QLatin1String( "page-layout" ) ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "page-layout-properties" ) ) { const PageFormatProperty property = parsePageProperty( child ); mStyleInformation->addPageProperty( element.attribute( QStringLiteral("name") ), property ); } child = child.nextSiblingElement(); } } else if ( element.tagName() == QLatin1String( "list-style" ) ) { const ListFormatProperty property = parseListProperty( element ); mStyleInformation->addListProperty( element.attribute( QStringLiteral("name") ), property ); } else if ( element.tagName() == QLatin1String( "default-style" ) ) { StyleFormatProperty property = parseStyleProperty( element ); property.setDefaultStyle( true ); mStyleInformation->addStyleProperty( element.attribute( QStringLiteral("family") ), property ); } else { qDebug( "unknown tag %s", qPrintable( element.tagName() ) ); } element = element.nextSiblingElement(); } return true; } StyleFormatProperty StyleParser::parseStyleProperty( QDomElement &parent ) { StyleFormatProperty property( mStyleInformation ); property.setParentStyleName( parent.attribute( QStringLiteral("parent-style-name") ) ); property.setFamily( parent.attribute( QStringLiteral("family") ) ); if ( parent.hasAttribute( QStringLiteral("master-page-name") ) ) { property.setMasterPageName( parent.attribute( QStringLiteral("master-page-name") ) ); if ( !mMasterPageNameSet ) { mStyleInformation->setMasterPageName( parent.attribute( QStringLiteral("master-page-name") ) ); mMasterPageNameSet = true; } } QDomElement element = parent.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "paragraph-properties" ) ) { const ParagraphFormatProperty paragraphProperty = parseParagraphProperty( element ); property.setParagraphFormat( paragraphProperty ); } else if ( element.tagName() == QLatin1String( "text-properties" ) ) { const TextFormatProperty textProperty = parseTextProperty( element ); property.setTextFormat( textProperty ); } else if ( element.tagName() == QLatin1String( "table-column-properties" ) ) { const TableColumnFormatProperty tableColumnProperty = parseTableColumnProperty( element ); property.setTableColumnFormat( tableColumnProperty ); } else if ( element.tagName() == QLatin1String( "table-cell-properties" ) ) { const TableCellFormatProperty tableCellProperty = parseTableCellProperty( element ); property.setTableCellFormat( tableCellProperty ); } else { qDebug( "unknown tag %s", qPrintable( element.tagName() ) ); } element = element.nextSiblingElement(); } return property; } ParagraphFormatProperty StyleParser::parseParagraphProperty( QDomElement &parent ) { ParagraphFormatProperty property; property.setPageNumber( parent.attribute( QStringLiteral("page-number") ).toInt() ); static QMap map; if ( map.isEmpty() ) { map.insert( QStringLiteral("lr-tb"), ParagraphFormatProperty::LRTB ); map.insert( QStringLiteral("rl-tb"), ParagraphFormatProperty::RLTB ); map.insert( QStringLiteral("tb-rl"), ParagraphFormatProperty::TBRL ); map.insert( QStringLiteral("tb-lr"), ParagraphFormatProperty::TBLR ); map.insert( QStringLiteral("lr"), ParagraphFormatProperty::LR ); map.insert( QStringLiteral("rl"), ParagraphFormatProperty::RL ); map.insert( QStringLiteral("tb"), ParagraphFormatProperty::TB ); map.insert( QStringLiteral("page"), ParagraphFormatProperty::PAGE ); } property.setWritingMode( map[ parent.attribute( QStringLiteral("writing-mode") ) ] ); static QMap alignMap; if ( alignMap.isEmpty() ) { alignMap.insert( QStringLiteral("center"), Qt::AlignCenter ); alignMap.insert( QStringLiteral("left"), Qt::AlignLeft ); alignMap.insert( QStringLiteral("right"), Qt::AlignRight ); alignMap.insert( QStringLiteral("justify"), Qt::AlignJustify ); if ( property.writingModeIsRightToLeft() ) { alignMap.insert( QStringLiteral("start"), Qt::AlignRight ); alignMap.insert( QStringLiteral("end"), Qt::AlignLeft ); } else { // not right to left alignMap.insert( QStringLiteral("start"), Qt::AlignLeft ); alignMap.insert( QStringLiteral("end"), Qt::AlignRight ); } } if ( parent.hasAttribute( QStringLiteral("text-align") ) ) { property.setTextAlignment( alignMap[ parent.attribute( QStringLiteral("text-align"), QStringLiteral("left") ) ] ); } const QString marginLeft = parent.attribute( QStringLiteral("margin-left") ); if ( !marginLeft.isEmpty() ) { qreal leftMargin = qRound( convertUnit( marginLeft ) ); property.setLeftMargin( leftMargin ); } const QString colorText = parent.attribute( QStringLiteral("background-color") ); if ( !colorText.isEmpty() && colorText != QLatin1String( "transparent" ) ) { property.setBackgroundColor( QColor( colorText ) ); } return property; } TextFormatProperty StyleParser::parseTextProperty( QDomElement &parent ) { TextFormatProperty property; const QString fontSize = parent.attribute( QStringLiteral("font-size") ); if ( !fontSize.isEmpty() ) property.setFontSize( qRound( convertUnit( fontSize ) ) ); static QMap weightMap; if ( weightMap.isEmpty() ) { weightMap.insert( QStringLiteral("normal"), QFont::Normal ); weightMap.insert( QStringLiteral("bold"), QFont::Bold ); } const QString fontWeight = parent.attribute( QStringLiteral("font-weight") ); if ( !fontWeight.isEmpty() ) property.setFontWeight( weightMap[ fontWeight ] ); static QMap fontStyleMap; if ( fontStyleMap.isEmpty() ) { fontStyleMap.insert( QStringLiteral("normal"), QFont::StyleNormal ); fontStyleMap.insert( QStringLiteral("italic"), QFont::StyleItalic ); fontStyleMap.insert( QStringLiteral("oblique"), QFont::StyleOblique ); } const QString fontStyle = parent.attribute( QStringLiteral("font-style") ); if ( !fontStyle.isEmpty() ) property.setFontStyle( fontStyleMap.value( fontStyle, QFont::StyleNormal ) ); const QColor color( parent.attribute( QStringLiteral("color") ) ); if ( color.isValid() ) { property.setColor( color ); } const QString colorText = parent.attribute( QStringLiteral("background-color") ); if ( !colorText.isEmpty() && colorText != QLatin1String( "transparent" ) ) { property.setBackgroundColor( QColor( colorText ) ); } return property; } PageFormatProperty StyleParser::parsePageProperty( QDomElement &parent ) { PageFormatProperty property; property.setBottomMargin( convertUnit( parent.attribute( QStringLiteral("margin-bottom") ) ) ); property.setLeftMargin( convertUnit( parent.attribute( QStringLiteral("margin-left") ) ) ); property.setTopMargin( convertUnit( parent.attribute( QStringLiteral("margin-top") ) ) ); property.setRightMargin( convertUnit( parent.attribute( QStringLiteral("margin-right") ) ) ); property.setWidth( convertUnit( parent.attribute( QStringLiteral("page-width") ) ) ); property.setHeight( convertUnit( parent.attribute( QStringLiteral("page-height") ) ) ); return property; } ListFormatProperty StyleParser::parseListProperty( QDomElement &parent ) { ListFormatProperty property; QDomElement element = parent.firstChildElement(); if ( element.tagName() == QLatin1String( "list-level-style-number" ) ) property = ListFormatProperty( ListFormatProperty::Number ); else property = ListFormatProperty( ListFormatProperty::Bullet ); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "list-level-style-number" ) ) { int level = element.attribute( QStringLiteral("level") ).toInt(); property.addItem( level, 0.0 ); } else if ( element.tagName() == QLatin1String( "list-level-style-bullet" ) ) { int level = element.attribute( QStringLiteral("level") ).toInt(); property.addItem( level, convertUnit( element.attribute( QStringLiteral("space-before") ) ) ); } element = element.nextSiblingElement(); } return property; } TableColumnFormatProperty StyleParser::parseTableColumnProperty( QDomElement &parent ) { TableColumnFormatProperty property; const double width = convertUnit( parent.attribute( QStringLiteral("column-width") ) ); property.setWidth( width ); return property; } TableCellFormatProperty StyleParser::parseTableCellProperty( QDomElement &parent ) { TableCellFormatProperty property; if ( parent.hasAttribute( QStringLiteral("background-color") ) ) property.setBackgroundColor( QColor( parent.attribute( QStringLiteral("background-color") ) ) ); property.setPadding( convertUnit( parent.attribute( QStringLiteral("padding") ) ) ); static QMap map; if ( map.isEmpty() ) { map.insert( QStringLiteral("top"), Qt::AlignTop ); map.insert( QStringLiteral("middle"), Qt::AlignVCenter ); map.insert( QStringLiteral("bottom"), Qt::AlignBottom ); map.insert( QStringLiteral("left"), Qt::AlignLeft ); map.insert( QStringLiteral("right"), Qt::AlignRight ); map.insert( QStringLiteral("center"), Qt::AlignHCenter ); } if ( parent.hasAttribute( QStringLiteral("align") ) && parent.hasAttribute( QStringLiteral("vertical-align") ) ) { property.setAlignment( map[ parent.attribute( QStringLiteral("align") ) ] | map[ parent.attribute( QStringLiteral("vertical-align") ) ] ); } else if ( parent.hasAttribute( QStringLiteral("align") ) ) { property.setAlignment( map[ parent.attribute( QStringLiteral("align") ) ] ); } else if ( parent.hasAttribute( QStringLiteral("vertical-align") ) ) { property.setAlignment( map[ parent.attribute( QStringLiteral("vertical-align") ) ] ); } return property; } double StyleParser::convertUnit( const QString &data ) { #define MM_TO_POINT(mm) ((mm)*2.83465058) #define CM_TO_POINT(cm) ((cm)*28.3465058) #define DM_TO_POINT(dm) ((dm)*283.465058) #define INCH_TO_POINT(inch) ((inch)*72.0) #define PI_TO_POINT(pi) ((pi)*12) #define DD_TO_POINT(dd) ((dd)*154.08124) #define CC_TO_POINT(cc) ((cc)*12.840103) double points = 0; if ( data.endsWith( QLatin1String("pt") ) ) { points = data.leftRef( data.length() - 2 ).toDouble(); } else if ( data.endsWith( QLatin1String("cm") ) ) { double value = data.leftRef( data.length() - 2 ).toDouble(); points = CM_TO_POINT( value ); } else if ( data.endsWith( QLatin1String("mm") ) ) { double value = data.leftRef( data.length() - 2 ).toDouble(); points = MM_TO_POINT( value ); } else if ( data.endsWith( QLatin1String("dm") ) ) { double value = data.leftRef( data.length() - 2 ).toDouble(); points = DM_TO_POINT( value ); } else if ( data.endsWith( QLatin1String("in") ) ) { double value = data.leftRef( data.length() - 2 ).toDouble(); points = INCH_TO_POINT( value ); } else if ( data.endsWith( QLatin1String("inch") ) ) { double value = data.leftRef( data.length() - 4 ).toDouble(); points = INCH_TO_POINT( value ); } else if ( data.endsWith( QLatin1String("pi") ) ) { double value = data.leftRef( data.length() - 4 ).toDouble(); points = PI_TO_POINT( value ); } else if ( data.endsWith( QLatin1String("dd") ) ) { double value = data.leftRef( data.length() - 4 ).toDouble(); points = DD_TO_POINT( value ); } else if ( data.endsWith( QLatin1String("cc") ) ) { double value = data.leftRef( data.length() - 4 ).toDouble(); points = CC_TO_POINT( value ); } else { if ( !data.isEmpty() ) { qDebug( "unknown unit for '%s'", qPrintable( data ) ); } points = 12; } return points; } diff --git a/generators/plucker/generator_plucker.cpp b/generators/plucker/generator_plucker.cpp index 25f8e4978..85c205cea 100644 --- a/generators/plucker/generator_plucker.cpp +++ b/generators/plucker/generator_plucker.cpp @@ -1,193 +1,193 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "generator_plucker.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include OKULAR_EXPORT_PLUGIN(PluckerGenerator, "libokularGenerator_plucker.json") static void calculateBoundingRect( QTextDocument *document, int startPosition, int endPosition, QRectF &rect ) { const QTextBlock startBlock = document->findBlock( startPosition ); const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect( startBlock ); const QTextBlock endBlock = document->findBlock( endPosition ); const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect( endBlock ); QTextLayout *startLayout = startBlock.layout(); QTextLayout *endLayout = endBlock.layout(); int startPos = startPosition - startBlock.position(); int endPos = endPosition - endBlock.position(); const QTextLine startLine = startLayout->lineForTextPosition( startPos ); const QTextLine endLine = endLayout->lineForTextPosition( endPos ); double x = startBoundingRect.x() + startLine.cursorToX( startPos ); double y = startBoundingRect.y() + startLine.y(); double r = endBoundingRect.x() + endLine.cursorToX( endPos ); double b = endBoundingRect.y() + endLine.y() + endLine.height(); const QSizeF size = document->size(); rect = QRectF( x / size.width(), y / size.height(), (r - x) / size.width(), (b - y) / size.height() ); } PluckerGenerator::PluckerGenerator( QObject *parent, const QVariantList &args ) : Generator( parent, args ) { } PluckerGenerator::~PluckerGenerator() { } bool PluckerGenerator::loadDocument( const QString & fileName, QVector & pagesVector ) { QUnpluck unpluck; if ( !unpluck.open( fileName ) ) return false; mPages = unpluck.pages(); mLinks = unpluck.links(); const QMap infos = unpluck.infos(); QMapIterator it( infos ); while ( it.hasNext() ) { it.next(); if ( !it.value().isEmpty() ) { if ( it.key() == QLatin1String( "name" ) ) mDocumentInfo.set( QStringLiteral("name"), it.value(), i18n( "Name" ) ); else if ( it.key() == QLatin1String( "title" ) ) mDocumentInfo.set( Okular::DocumentInfo::Title, it.value() ); else if ( it.key() == QLatin1String( "author" ) ) mDocumentInfo.set( Okular::DocumentInfo::Author, it.value() ); else if ( it.key() == QLatin1String( "time" ) ) mDocumentInfo.set( Okular::DocumentInfo::CreationDate, it.value() ); } } pagesVector.resize( mPages.count() ); for ( int i = 0; i < mPages.count(); ++i ) { QSizeF size = mPages[ i ]->size(); Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); pagesVector[i] = page; } return true; } bool PluckerGenerator::doCloseDocument() { mLinkAdded.clear(); mLinks.clear(); qDeleteAll( mPages ); mPages.clear(); // do not use clear() for the following, otherwise its type is changed mDocumentInfo = Okular::DocumentInfo(); return true; } Okular::DocumentInfo PluckerGenerator::generateDocumentInfo( const QSet & /*keys*/ ) const { return mDocumentInfo; } QImage PluckerGenerator::image( Okular::PixmapRequest *request ) { const QSizeF size = mPages[ request->pageNumber() ]->size(); QImage image( request->width(), request->height(), QImage::Format_ARGB32_Premultiplied ); image.fill( Qt::white ); QPainter p; p.begin( &image ); qreal width = request->width(); qreal height = request->height(); p.scale( width / (qreal)size.width(), height / (qreal)size.height() ); mPages[ request->pageNumber() ]->drawContents( &p ); p.end(); if ( !mLinkAdded.contains( request->pageNumber() ) ) { QLinkedList objects; for ( int i = 0; i < mLinks.count(); ++i ) { if ( mLinks[ i ].page == request->pageNumber() ) { QTextDocument *document = mPages[ request->pageNumber() ]; QRectF rect; calculateBoundingRect( document, mLinks[ i ].start, mLinks[ i ].end, rect ); objects.append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, mLinks[ i ].link ) ); } } if ( !objects.isEmpty() ) request->page()->setObjectRects( objects ); mLinkAdded.insert( request->pageNumber() ); } return image; } Okular::ExportFormat::List PluckerGenerator::exportFormats() const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); return formats; } bool PluckerGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { if ( format.mimeType().name() == QLatin1String( "text/plain" ) ) { QFile file( fileName ); if ( !file.open( QIODevice::WriteOnly ) ) return false; QTextStream out( &file ); for ( int i = 0; i < mPages.count(); ++i ) { out << mPages[ i ]->toPlainText(); } return true; } return false; } bool PluckerGenerator::print( QPrinter& ) { /* for ( int i = 0; i < mPages.count(); ++i ) mPages[ i ]->print( &printer ); */ return true; } #include "generator_plucker.moc" diff --git a/generators/plucker/generator_plucker.h b/generators/plucker/generator_plucker.h index 11d714d87..2de7e1835 100644 --- a/generators/plucker/generator_plucker.h +++ b/generators/plucker/generator_plucker.h @@ -1,57 +1,57 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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_GENERATOR_PLUCKER_H_ #define _OKULAR_GENERATOR_PLUCKER_H_ #include #include -#include +#include #include "qunpluck.h" class QTextDocument; class PluckerGenerator : public Okular::Generator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: PluckerGenerator( QObject *parent, const QVariantList &args ); virtual ~PluckerGenerator(); // [INHERITED] load a document and fill up the pagesVector bool loadDocument( const QString & fileName, QVector & pagesVector ) override; // [INHERITED] document information Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override; // [INHERITED] perform actions on document / pages QImage image( Okular::PixmapRequest *request ) override; // [INHERITED] text exporting Okular::ExportFormat::List exportFormats() const override; bool exportTo( const QString &fileName, const Okular::ExportFormat &format ) override; // [INHERITED] print document using already configured kprinter bool print( QPrinter& printer ) override; protected: bool doCloseDocument() override; private: QList mPages; QSet mLinkAdded; Link::List mLinks; Okular::DocumentInfo mDocumentInfo; }; #endif diff --git a/generators/plucker/unpluck/image.cpp b/generators/plucker/unpluck/image.cpp index 9179c0e44..5ab1c6e34 100644 --- a/generators/plucker/unpluck/image.cpp +++ b/generators/plucker/unpluck/image.cpp @@ -1,549 +1,549 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * Based on code written by Bill Janssen 2002 * * * * 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 -#include +#include +#include #include #include /* This code requires the Independent JPEG Group libjpeg library, version 6b or later */ extern "C" { #include "jpeglib.h" } #include "unpluck.h" #include "image.h" #define GET_FUNCTION_CODE_TYPE(x) (((x)>>3) & 0x1F) #define GET_FUNCTION_CODE_DATALEN(x) ((x) & 0x7) #define CELLS(row,col) cells[row*cols+col] /***********************************************************************/ /***********************************************************************/ /***** *****/ /***** Code to decode the Palm image format to JPEG *****/ /***** *****/ /***********************************************************************/ /***********************************************************************/ #define READ_BIGENDIAN_SHORT(p) (((p)[0] << 8)|((p)[1])) #define READ_BIGENDIAN_LONG(p) (((p)[0] << 24)|((p)[1] << 16)|((p)[2] << 8)|((p)[3])) #define PALM_IS_COMPRESSED_FLAG 0x8000 #define PALM_HAS_COLORMAP_FLAG 0x4000 #define PALM_HAS_TRANSPARENCY_FLAG 0x2000 #define PALM_DIRECT_COLOR_FLAG 0x0400 #define PALM_4_BYTE_FIELD_FLAG 0x0200 #define PALM_COMPRESSION_SCANLINE 0x00 #define PALM_COMPRESSION_RLE 0x01 #define PALM_COMPRESSION_PACKBITS 0x02 #define PALM_COMPRESSION_NONE 0xFF #define PALM_COLORMAP_SIZE 232 typedef struct { unsigned char red; unsigned char green; unsigned char blue; } ColorMapEntry; static ColorMapEntry Palm8BitColormap[] = { {255, 255, 255}, {255, 204, 255}, {255, 153, 255}, {255, 102, 255}, {255, 51, 255}, {255, 0, 255}, {255, 255, 204}, {255, 204, 204}, {255, 153, 204}, {255, 102, 204}, {255, 51, 204}, {255, 0, 204}, {255, 255, 153}, {255, 204, 153}, {255, 153, 153}, {255, 102, 153}, {255, 51, 153}, {255, 0, 153}, {204, 255, 255}, {204, 204, 255}, {204, 153, 255}, {204, 102, 255}, {204, 51, 255}, {204, 0, 255}, {204, 255, 204}, {204, 204, 204}, {204, 153, 204}, {204, 102, 204}, {204, 51, 204}, {204, 0, 204}, {204, 255, 153}, {204, 204, 153}, {204, 153, 153}, {204, 102, 153}, {204, 51, 153}, {204, 0, 153}, {153, 255, 255}, {153, 204, 255}, {153, 153, 255}, {153, 102, 255}, {153, 51, 255}, {153, 0, 255}, {153, 255, 204}, {153, 204, 204}, {153, 153, 204}, {153, 102, 204}, {153, 51, 204}, {153, 0, 204}, {153, 255, 153}, {153, 204, 153}, {153, 153, 153}, {153, 102, 153}, {153, 51, 153}, {153, 0, 153}, {102, 255, 255}, {102, 204, 255}, {102, 153, 255}, {102, 102, 255}, {102, 51, 255}, {102, 0, 255}, {102, 255, 204}, {102, 204, 204}, {102, 153, 204}, {102, 102, 204}, {102, 51, 204}, {102, 0, 204}, {102, 255, 153}, {102, 204, 153}, {102, 153, 153}, {102, 102, 153}, {102, 51, 153}, {102, 0, 153}, { 51, 255, 255}, { 51, 204, 255}, { 51, 153, 255}, { 51, 102, 255}, { 51, 51, 255}, { 51, 0, 255}, { 51, 255, 204}, { 51, 204, 204}, { 51, 153, 204}, { 51, 102, 204}, { 51, 51, 204}, { 51, 0, 204}, { 51, 255, 153}, { 51, 204, 153}, { 51, 153, 153}, { 51, 102, 153}, { 51, 51, 153}, { 51, 0, 153}, { 0, 255, 255}, { 0, 204, 255}, { 0, 153, 255}, { 0, 102, 255}, { 0, 51, 255}, { 0, 0, 255}, { 0, 255, 204}, { 0, 204, 204}, { 0, 153, 204}, { 0, 102, 204}, { 0, 51, 204}, { 0, 0, 204}, { 0, 255, 153}, { 0, 204, 153}, { 0, 153, 153}, { 0, 102, 153}, { 0, 51, 153}, { 0, 0, 153}, {255, 255, 102}, {255, 204, 102}, {255, 153, 102}, {255, 102, 102}, {255, 51, 102}, {255, 0, 102}, {255, 255, 51}, {255, 204, 51}, {255, 153, 51}, {255, 102, 51}, {255, 51, 51}, {255, 0, 51}, {255, 255, 0}, {255, 204, 0}, {255, 153, 0}, {255, 102, 0}, {255, 51, 0}, {255, 0, 0}, {204, 255, 102}, {204, 204, 102}, {204, 153, 102}, {204, 102, 102}, {204, 51, 102}, {204, 0, 102}, {204, 255, 51}, {204, 204, 51}, {204, 153, 51}, {204, 102, 51}, {204, 51, 51}, {204, 0, 51}, {204, 255, 0}, {204, 204, 0}, {204, 153, 0}, {204, 102, 0}, {204, 51, 0}, {204, 0, 0}, {153, 255, 102}, {153, 204, 102}, {153, 153, 102}, {153, 102, 102}, {153, 51, 102}, {153, 0, 102}, {153, 255, 51}, {153, 204, 51}, {153, 153, 51}, {153, 102, 51}, {153, 51, 51}, {153, 0, 51}, {153, 255, 0}, {153, 204, 0}, {153, 153, 0}, {153, 102, 0}, {153, 51, 0}, {153, 0, 0}, {102, 255, 102}, {102, 204, 102}, {102, 153, 102}, {102, 102, 102}, {102, 51, 102}, {102, 0, 102}, {102, 255, 51}, {102, 204, 51}, {102, 153, 51}, {102, 102, 51}, {102, 51, 51}, {102, 0, 51}, {102, 255, 0}, {102, 204, 0}, {102, 153, 0}, {102, 102, 0}, {102, 51, 0}, {102, 0, 0}, { 51, 255, 102}, { 51, 204, 102}, { 51, 153, 102}, { 51, 102, 102}, { 51, 51, 102}, { 51, 0, 102}, { 51, 255, 51}, { 51, 204, 51}, { 51, 153, 51}, { 51, 102, 51}, { 51, 51, 51}, { 51, 0, 51}, { 51, 255, 0}, { 51, 204, 0}, { 51, 153, 0}, { 51, 102, 0}, { 51, 51, 0}, { 51, 0, 0}, { 0, 255, 102}, { 0, 204, 102}, { 0, 153, 102}, { 0, 102, 102}, { 0, 51, 102}, { 0, 0, 102}, { 0, 255, 51}, { 0, 204, 51}, { 0, 153, 51}, { 0, 102, 51}, { 0, 51, 51}, { 0, 0, 51}, { 0, 255, 0}, { 0, 204, 0}, { 0, 153, 0}, { 0, 102, 0}, { 0, 51, 0}, { 17, 17, 17}, { 34, 34, 34}, { 68, 68, 68}, { 85, 85, 85}, {119, 119, 119}, {136, 136, 136}, {170, 170, 170}, {187, 187, 187}, {221, 221, 221}, {238, 238, 238}, {192, 192, 192}, {128, 0, 0}, {128, 0, 128}, { 0, 128, 0}, { 0, 128, 128}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0} }; static ColorMapEntry Palm1BitColormap[] = { {255, 255, 255}, { 0, 0, 0} }; static ColorMapEntry Palm2BitColormap[] = { {255, 255, 255}, {192, 192, 192}, {128, 128, 128}, { 0, 0, 0} }; static ColorMapEntry Palm4BitColormap[] = { {255, 255, 255}, {238, 238, 238}, {221, 221, 221}, {204, 204, 204}, {187, 187, 187}, {170, 170, 170}, {153, 153, 153}, {136, 136, 136}, {119, 119, 119}, {102, 102, 102}, { 85, 85, 85}, { 68, 68, 68}, { 51, 51, 51}, { 34, 34, 34}, { 17, 17, 17}, { 0, 0, 0} }; bool TranscribePalmImageToJPEG ( unsigned char *image_bytes_in, QImage &image ) { unsigned int width; unsigned int height; unsigned int bytes_per_row; unsigned int flags; // unsigned int next_depth_offset; unsigned int bits_per_pixel; // unsigned int version; // unsigned int transparent_index; unsigned int compression_type; unsigned int i; unsigned int j; unsigned int inval; unsigned int inbit; unsigned int mask; unsigned int incount; unsigned int palm_red_bits = 0; unsigned int palm_green_bits = 0; unsigned int palm_blue_bits = 0; unsigned char* palm_ptr; // unsigned char* x_ptr; // unsigned char* imagedata = 0; unsigned char* inbyte; unsigned char* rowbuf; unsigned char* lastrow; unsigned char* imagedatastart; unsigned char* palmimage; ColorMapEntry *colormap; JSAMPLE* jpeg_row; struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW row_pointer[1];/* pointer to JSAMPLE row[s] */ palmimage = image_bytes_in; width = READ_BIGENDIAN_SHORT (palmimage + 0); height = READ_BIGENDIAN_SHORT (palmimage + 2); bytes_per_row = READ_BIGENDIAN_SHORT (palmimage + 4); flags = READ_BIGENDIAN_SHORT (palmimage + 6); bits_per_pixel = palmimage[8]; // version = palmimage[9]; // next_depth_offset = READ_BIGENDIAN_SHORT (palmimage + 10); // transparent_index = palmimage[12]; compression_type = palmimage[13]; /* bytes 14 and 15 are reserved by Palm and always 0 */ if (compression_type == PALM_COMPRESSION_PACKBITS) { return false; } else if ((compression_type != PALM_COMPRESSION_NONE) && (compression_type != PALM_COMPRESSION_RLE) && (compression_type != PALM_COMPRESSION_SCANLINE)) { return false; } /* as of PalmOS 4.0, there are 6 different kinds of Palm pixmaps: 1, 2, or 4 bit grayscale 8-bit StaticColor using the Palm standard colormap 8-bit PseudoColor using a user-specified colormap 16-bit DirectColor using 5 bits for red, 6 for green, and 5 for blue Each of these can be compressed with one of four compression schemes, "RLE", "Scanline", "PackBits", or none. We begin by constructing the colormap. */ if (flags & PALM_HAS_COLORMAP_FLAG) { return false; } else if (bits_per_pixel == 1) { colormap = Palm1BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 2) { colormap = Palm2BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 4) { colormap = Palm4BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 8) { colormap = Palm8BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 16 && (flags & PALM_DIRECT_COLOR_FLAG)) { colormap = NULL; palm_red_bits = palmimage[16]; palm_green_bits = palmimage[17]; palm_blue_bits = palmimage[18]; if (palm_blue_bits > 8 || palm_green_bits > 8 || palm_red_bits > 8) { return false; } if (bits_per_pixel > (8 * sizeof (unsigned long))) { return false; } imagedatastart = palmimage + 24; } else { return false; } QTemporaryFile tempFile; tempFile.open(); FILE *outfile = fopen( QFile::encodeName( tempFile.fileName() ).constData(), "w" ); if ( !outfile ) return false; /* now create the JPEG image row buffer */ jpeg_row = (JSAMPLE *) malloc (sizeof (JSAMPLE) * (width * 3)); /* Use standard JPEG error processing */ cinfo.err = jpeg_std_error (&jerr); /* Initialize the JPEG compression object. */ jpeg_create_compress (&cinfo); jpeg_stdio_dest (&cinfo, outfile); cinfo.image_width = width; /* image width and height, in pixels */ cinfo.image_height = height; cinfo.input_components = 3; /* # of color components per pixel */ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ jpeg_set_defaults (&cinfo); jpeg_set_quality (&cinfo, 100, true /* limit to baseline-JPEG values */ ); row_pointer[0] = &jpeg_row[0]; jpeg_start_compress (&cinfo, true); /* row by row, uncompress the Palm image and copy it to the JPEG buffer */ rowbuf = (unsigned char *) malloc (bytes_per_row * width); lastrow = (unsigned char *) malloc (bytes_per_row * width); for (i = 0, palm_ptr = imagedatastart/*, x_ptr = imagedata*/; i < height; ++i) { /* first, uncompress the Palm image */ if ((flags & PALM_IS_COMPRESSED_FLAG) && (compression_type == PALM_COMPRESSION_RLE)) { for (j = 0; j < bytes_per_row;) { incount = *palm_ptr++; inval = *palm_ptr++; if (incount + j <= bytes_per_row * width) { memset (rowbuf + j, inval, incount); j += incount; } else { free (rowbuf); free (lastrow); free (jpeg_row); jpeg_destroy_compress (&cinfo); fclose( outfile ); return false; } } } else if ((flags & PALM_IS_COMPRESSED_FLAG) && (compression_type == PALM_COMPRESSION_SCANLINE)) { for (j = 0; j < bytes_per_row; j += 8) { incount = *palm_ptr++; inval = ((bytes_per_row - j) < 8) ? (bytes_per_row - j) : 8; for (inbit = 0; inbit < inval; inbit += 1) { if (incount & (1 << (7 - inbit))) rowbuf[j + inbit] = *palm_ptr++; else rowbuf[j + inbit] = lastrow[j + inbit]; } } memcpy (lastrow, rowbuf, bytes_per_row); } else if (((flags & PALM_IS_COMPRESSED_FLAG) && (compression_type == PALM_COMPRESSION_NONE)) || (flags & PALM_IS_COMPRESSED_FLAG) == 0) { memcpy (rowbuf, palm_ptr, bytes_per_row); palm_ptr += bytes_per_row; } /* next, write it to the GDK bitmap */ if (colormap) { mask = (1 << bits_per_pixel) - 1; for (inbit = 8 - bits_per_pixel, inbyte = rowbuf, j = 0; j < width; ++j) { inval = ((*inbyte) & (mask << inbit)) >> inbit; /* correct for oddity of the 8-bit color Palm pixmap... */ if ((bits_per_pixel == 8) && (inval == 0xFF)) inval = 231; /* now lookup the correct color and set the pixel in the GTK bitmap */ jpeg_row[(j * 3) + 0] = colormap[inval].red; jpeg_row[(j * 3) + 1] = colormap[inval].green; jpeg_row[(j * 3) + 2] = colormap[inval].blue; if (!inbit) { ++inbyte; inbit = 8 - bits_per_pixel; } else { inbit -= bits_per_pixel; } } } else if (!colormap && bits_per_pixel == 16) { for (inbyte = rowbuf, j = 0; j < width; ++j) { inval = (inbyte[0] << 8) | inbyte[1]; jpeg_row[(j * 3) + 0] = (inval >> (bits_per_pixel - palm_red_bits)) & ((1 << palm_red_bits) - 1); jpeg_row[(j * 3) + 1] = (inval >> palm_blue_bits) & ((1 << palm_green_bits) - 1); jpeg_row[(j * 3) + 2] = (inval >> 0) & ((1 << palm_blue_bits) - 1); inbyte += 2; } } (void) jpeg_write_scanlines (&cinfo, row_pointer, 1); } free (rowbuf); free (lastrow); free (jpeg_row); jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); fclose( outfile ); return image.load( tempFile.fileName() ); } typedef struct { unsigned int width; unsigned int height; unsigned int bytes_per_row; unsigned int flags; unsigned int next_depth_offset; unsigned int bits_per_pixel; unsigned int version; unsigned int transparent_index; unsigned int compression_type; unsigned int palm_red_bits; unsigned int palm_green_bits; unsigned int palm_blue_bits; unsigned char* bytes; } PALMPIX; bool TranscribeMultiImageRecord ( plkr_Document* doc, QImage &image, unsigned char* bytes ) { unsigned char* pbytes = 0; unsigned char* outbytes = 0; unsigned char* outptr = 0; unsigned char* ptr = &bytes[12]; plkr_DataRecordType ptype; PALMPIX* cells = 0; PALMPIX* acell = 0; unsigned int record_id = 0; int plen = 0; unsigned int x = 0; unsigned int y = 0; unsigned int cols = 0; unsigned int rows = 0; unsigned int width = 0; unsigned int height = 0; unsigned int bytes_per_row = 0; unsigned int flags = 0; unsigned int bits_per_pixel = 0; unsigned int version = 0; unsigned int transparent_index = 0; unsigned int compression_type = 0; unsigned int palm_red_bits = 0; unsigned int palm_green_bits = 0; unsigned int palm_blue_bits = 0; unsigned int outlen = 0; unsigned int offset = 0; bool status = true; cols = (bytes[8] << 8) + bytes[9]; rows = (bytes[10] << 8) + bytes[11]; cells = (PALMPIX *) calloc (cols * rows, sizeof (PALMPIX)); height = 0; for (y = 0; y < rows; y++) { width = 0; bytes_per_row = 0; for (x = 0; x < cols; x++) { acell = &CELLS (y, x); record_id = (ptr[0] << 8) + ptr[1]; ptr += 2; pbytes = plkr_GetRecordBytes (doc, record_id, &plen, &ptype); if (pbytes == NULL) { free (cells); return false; } pbytes += 8; acell->width = READ_BIGENDIAN_SHORT (&pbytes[0]); width += acell->width; acell->height = READ_BIGENDIAN_SHORT (&pbytes[2]); acell->bytes_per_row = READ_BIGENDIAN_SHORT (&pbytes[4]); bytes_per_row += acell->bytes_per_row; acell->flags = READ_BIGENDIAN_SHORT (&pbytes[6]); flags = acell->flags; acell->bits_per_pixel = pbytes[8]; bits_per_pixel = acell->bits_per_pixel; acell->version = pbytes[9]; version = acell->version; acell->next_depth_offset = READ_BIGENDIAN_SHORT (&pbytes[10]); acell->transparent_index = pbytes[12]; transparent_index = acell->transparent_index; acell->compression_type = pbytes[13]; compression_type = acell->compression_type; if (acell->flags & PALM_HAS_COLORMAP_FLAG) { free (cells); return false; } acell->bytes = pbytes + 16; offset = 16; if (acell->bits_per_pixel == 16 && (acell->flags & PALM_DIRECT_COLOR_FLAG)) { acell->palm_red_bits = pbytes[16]; palm_red_bits = acell->palm_red_bits; acell->palm_green_bits = pbytes[17]; palm_green_bits = acell->palm_green_bits; acell->palm_blue_bits = pbytes[18]; palm_blue_bits = acell->palm_blue_bits; acell->bytes = pbytes + 24; offset = 24; } } height += acell->height; } outlen = bytes_per_row * height + offset; outbytes = (unsigned char *) malloc (outlen); outptr = outbytes; *outptr++ = width >> 8; *outptr++ = width; *outptr++ = height >> 8; *outptr++ = height; *outptr++ = bytes_per_row >> 8; *outptr++ = bytes_per_row; *outptr++ = flags >> 8; *outptr++ = flags; *outptr++ = bits_per_pixel; *outptr++ = version; *outptr++ = 0; /* next_depth_offset */ *outptr++ = 0; *outptr++ = transparent_index; *outptr++ = compression_type; *outptr++ = 0; *outptr++ = 0; if (acell->bits_per_pixel == 16 && (acell->flags & PALM_DIRECT_COLOR_FLAG)) { *outptr++ = palm_red_bits; *outptr++ = palm_green_bits; *outptr++ = palm_blue_bits; *outptr++ = 0; *outptr++ = 0; *outptr++ = 0; *outptr++ = 0; *outptr++ = 0; } for (y = 0; y < rows; y++) { int i, h; acell = &CELLS (y, 0); h = acell->height; for (i = 0; i < h; i++) { for (x = 0; x < cols; x++) { acell = &CELLS (y, x); memcpy (outptr, acell->bytes, acell->bytes_per_row); acell->bytes += acell->bytes_per_row; outptr += acell->bytes_per_row; } } } status = TranscribePalmImageToJPEG (outbytes, image); free (outbytes); free (cells); return status; } diff --git a/generators/plucker/unpluck/qunpluck.cpp b/generators/plucker/unpluck/qunpluck.cpp index 59ad74d73..9176c9d30 100644 --- a/generators/plucker/unpluck/qunpluck.cpp +++ b/generators/plucker/unpluck/qunpluck.cpp @@ -1,1208 +1,1207 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * Based on code written by Bill Janssen 2002 * * * * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include "qunpluck.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include "qunpluck.h" #include "image.h" #define GET_FUNCTION_CODE_TYPE(x) (((x)>>3) & 0x1F) #define GET_FUNCTION_CODE_DATALEN(x) ((x) & 0x7) #define CELLS(row,col) cells[row*cols+col] #define READ_BIGENDIAN_SHORT(p) (((p)[0] << 8)|((p)[1])) #define READ_BIGENDIAN_LONG(p) (((p)[0] << 24)|((p)[1] << 16)|((p)[2] << 8)|((p)[3])) /* static void LinkRecords ( char* dir ) { RecordNode* ptr; char* realfilename; char* linkname; realfilename = (char*)malloc (strlen (dir) + 20); linkname = (char*)malloc (strlen (dir) + 20); for (ptr = records; ptr != NULL; ptr = ptr->next) { if (ptr->page_id != ptr->index) { sprintf (realfilename, "%s/r%d.html", dir, ptr->page_id); sprintf (linkname, "%s/r%d.html", dir, ptr->index); link (realfilename, linkname); } } free (realfilename); free (linkname); } */ class Context { public: int recordId; QTextDocument *document; QTextCursor *cursor; QStack stack; QList images; QString linkUrl; int linkStart; int linkPage; }; class RecordNode { public: int index; int page_id; bool done; }; static Okular::DocumentViewport calculateViewport( QTextDocument *document, const QTextBlock &block ) { if ( !block.isValid() ) return Okular::DocumentViewport(); const QRectF rect = document->documentLayout()->blockBoundingRect( block ); const QSizeF size = document->size(); int page = qRound( rect.y() ) / qRound( size.height() ); Okular::DocumentViewport viewport( page ); viewport.rePos.normalizedX = (double)rect.x() / (double)size.width(); viewport.rePos.normalizedY = (double)rect.y() / (double)size.height(); viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::Center; return viewport; } QUnpluck::QUnpluck() : mDocument( 0 ) { } QUnpluck::~QUnpluck() { mLinks.clear(); mNamedTargets.clear(); mPages.clear(); } bool QUnpluck::open( const QString &fileName ) { mLinks.clear(); mNamedTargets.clear(); mPages.clear(); mDocument = plkr_OpenDBFile( QFile::encodeName( fileName ).data() ); if ( !mDocument ) { mErrorString = QObject::tr( "Unable to open document" ); return false; } // bool status = true; mInfo.insert( QStringLiteral("name"), QString::fromLocal8Bit(plkr_GetName( mDocument ) )); mInfo.insert( QStringLiteral("title"), QString::fromLocal8Bit(plkr_GetTitle( mDocument ) )); mInfo.insert( QStringLiteral("author"), QString::fromLocal8Bit(plkr_GetAuthor( mDocument ) )); mInfo.insert( QStringLiteral("time"), QDateTime::fromTime_t( plkr_GetPublicationTime( mDocument ) ).toString() ); AddRecord( plkr_GetHomeRecordID( mDocument ) ); int number = GetNextRecordNumber(); while ( number > 0 ) { /*status = */TranscribeRecord( number ); number = GetNextRecordNumber (); } // Iterate over all records again to add those which aren't linked directly for ( int i = 1; i < plkr_GetRecordCount( mDocument ); ++i ) AddRecord( plkr_GetUidForIndex( mDocument, i ) ); number = GetNextRecordNumber(); while ( number > 0 ) { /*status = */TranscribeRecord( number ); number = GetNextRecordNumber (); } for ( int i = 0; i < mRecords.count(); ++i ) delete mRecords[ i ]; mRecords.clear(); plkr_CloseDoc( mDocument ); /** * Calculate hash map */ QHash pageHash; for ( int i = 0; i < mContext.count(); ++i ) pageHash.insert( mContext[ i ]->recordId, i ); /** * Convert ids */ for ( int i = 0; i < mContext.count(); ++i ) { Context *context = mContext[ i ]; for ( int j = 0; j < context->images.count(); ++j ) { int imgNumber = context->images[ j ]; context->document->addResource( QTextDocument::ImageResource, QUrl( QStringLiteral( "%1.jpg" ).arg( imgNumber ) ), mImages[ imgNumber ] ); } mPages.append( context->document ); } qDeleteAll( mContext ); mContext.clear(); // convert record_id into page for ( int i = 0; i < mLinks.count(); ++i ) { mLinks[ i ].page = pageHash[ mLinks[ i ].page ]; if ( mLinks[ i ].url.startsWith( QLatin1String("page:") ) ) { int page = mLinks[ i ].url.midRef( 5 ).toInt(); Okular::DocumentViewport viewport( pageHash[ page ] ); viewport.rePos.normalizedX = 0; viewport.rePos.normalizedY = 0; viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::TopLeft; mLinks[ i ].link = new Okular::GotoAction( QString(), viewport ); } else if ( mLinks[ i ].url.startsWith( QLatin1String("para:") ) ) { QPair data = mNamedTargets[ mLinks[ i ].url ]; QTextDocument *document = mPages[ mLinks[ i ].page ]; Okular::DocumentViewport viewport = calculateViewport( document, data.second ); mLinks[ i ].link = new Okular::GotoAction( QString(), viewport ); } else { mLinks[ i ].link = new Okular::BrowseAction( QUrl(mLinks[ i ].url) ); } } return true; } int QUnpluck::GetNextRecordNumber() { int index = 0; for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( !mRecords[ pos ]->done ) { index = mRecords[ pos ]->index; break; } } return index; } int QUnpluck::GetPageID( int index ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { return mRecords[ pos ]->page_id; } } return 0; } void QUnpluck::AddRecord( int index ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { return; } } RecordNode *node = new RecordNode; node->done = false; node->index = index; node->page_id = index; mRecords.append( node ); } void QUnpluck::MarkRecordDone( int index ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { mRecords[ pos ]->done = true; return; } } AddRecord( index ); MarkRecordDone( index ); } void QUnpluck::SetPageID( int index, int page_id ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { mRecords[ pos ]->page_id = page_id; return; } } AddRecord( index ); SetPageID( index, page_id ); } QString QUnpluck::MailtoURLFromBytes( unsigned char* record_data ) { unsigned char* bytes = record_data + 8; int to_offset = (bytes[0] << 8) + bytes[1]; int cc_offset = (bytes[2] << 8) + bytes[3]; int subject_offset = (bytes[4] << 8) + bytes[5]; int body_offset = (bytes[6] << 8) + bytes[7]; QString url( QStringLiteral("mailto:") ); if ( to_offset != 0 ) url += QString::fromLatin1( (char *)(bytes + to_offset) ); if ( (cc_offset != 0) || (subject_offset != 0) || (body_offset != 0) ) url += QLatin1String( "?" ); if ( cc_offset != 0 ) url += QLatin1String( "cc=" ) + QString::fromLatin1( (char *)(bytes + cc_offset) ); if ( subject_offset != 0 ) url += QLatin1String( "subject=" ) + QString::fromLatin1( (char *)(bytes + subject_offset) ); if ( body_offset != 0 ) url += QLatin1String( "body=" ) + QString::fromLatin1( (char *)(bytes + body_offset) ); return url; } QImage QUnpluck::TranscribeImageRecord( unsigned char* bytes ) { QImage image; TranscribePalmImageToJPEG( bytes + 8, image ); return image; } void QUnpluck::DoStyle( Context* context, int style, bool start ) { if ( start ) { QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); int pointSize = qRound( format.fontPointSize() ); switch (style) { case 1: format.setFontWeight( QFont::Bold ); pointSize += 3; break; case 2: format.setFontWeight( QFont::Bold ); pointSize += 2; break; case 3: format.setFontWeight( QFont::Bold ); pointSize += 1; break; case 4: format.setFontWeight( QFont::Bold ); break; case 5: format.setFontWeight( QFont::Bold ); pointSize += -1; break; case 6: format.setFontWeight( QFont::Bold ); pointSize += -2; break; case 7: format.setFontWeight( QFont::Bold ); break; case 8: format.setFontFamily( QStringLiteral( "Courier New,courier" ) ); break; } format.setFontPointSize( qMax( pointSize, 1 ) ); context->cursor->setCharFormat( format ); } else { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); } } void QUnpluck::ParseText ( plkr_Document* doc, unsigned char* ptr, int text_len, int* font, int* style, Context* context ) { unsigned char* end; int fctype; int fclen; end = ptr + text_len; while (ptr < end) { if (ptr[0]) { context->cursor->insertText( QString::fromLocal8Bit( (char*)ptr ) ); ptr += strlen ((char*)ptr); } else { fctype = GET_FUNCTION_CODE_TYPE (ptr[1]); fclen = 2 + GET_FUNCTION_CODE_DATALEN (ptr[1]); switch (fctype) { case PLKR_TFC_LINK: switch (fclen) { case 4: /* ANCHOR_BEGIN */ { int record_id = (ptr[2] << 8) + ptr[3]; /** TODO: plkr_DataRecordType type = (plkr_DataRecordType)plkr_GetRecordType (doc, record_id); if (type == PLKR_DRTYPE_IMAGE || type == PLKR_DRTYPE_IMAGE_COMPRESSED) output += QString( "
" ).arg(record_id); else output += QString( "" ).arg(record_id); */ AddRecord (record_id); } break; case 2: /* ANCHOR_END */ //TODO: output += QString( "" ); break; } ptr += fclen; break; case PLKR_TFC_FONT: DoStyle (context, *style, false); *style = ptr[2]; DoStyle (context, *style, true); ptr += fclen; break; case PLKR_TFC_NEWLINE: { // TODO: remove the setCharFormat when Qt is fixed QTextCharFormat format( context->cursor->charFormat() ); context->cursor->insertText( QStringLiteral("\n") ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_BITALIC: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( true ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_EITALIC: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( false ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_COLOR: if (*font) { (*font)--; if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); } { QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( QColor((ptr[2] << 16), (ptr[3] << 8), ptr[4]) ); context->cursor->setCharFormat( format ); } (*font)++; ptr += fclen; break; case PLKR_TFC_BULINE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( true ); context->cursor->setCharFormat( format ); ptr += fclen; } break; case PLKR_TFC_EULINE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( false ); context->cursor->setCharFormat( format ); ptr += fclen; } break; case PLKR_TFC_BSTRIKE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( true ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_ESTRIKE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( false ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_TABLE: if (fclen == 4) { int record_id, datalen; plkr_DataRecordType type = (plkr_DataRecordType)0; unsigned char *bytes = NULL; record_id = (ptr[2] << 8) + ptr[3]; bytes = plkr_GetRecordBytes (doc, record_id, &datalen, &type); TranscribeTableRecord (doc, context, bytes); } ptr += fclen; break; default: ptr += fclen; } } } } bool QUnpluck::TranscribeTableRecord ( plkr_Document* doc, Context* context, unsigned char* bytes ) { unsigned char* ptr = &bytes[24]; unsigned char* end; // char* align_names[] = { "left", "right", "center" }; // bool in_row = false; // int cols; int size; // int rows; // int border; int record_id; // int align; int text_len; // int colspan; // int rowspan; int font = 0; int style = 0; int fctype; int fclen; // long border_color; // long link_color; size = (bytes[8] << 8) + bytes[9]; // cols = (bytes[10] << 8) + bytes[11]; // rows = (bytes[12] << 8) + bytes[13]; // border = bytes[15]; // border_color = (bytes[17] << 16) + (bytes[18] << 8) + (bytes[19] << 8); // link_color = (bytes[21] << 16) + (bytes[22] << 8) + (bytes[23] << 8); end = ptr + size - 1; /** output += QString( "\n" ).arg(border, border_color, link_color); */ while (ptr < end) { if (ptr[0] == '\0') { fctype = GET_FUNCTION_CODE_TYPE (ptr[1]); fclen = 2 + GET_FUNCTION_CODE_DATALEN (ptr[1]); switch (fctype) { case PLKR_TFC_TABLE: switch (fclen) { case 2: /* NEW_ROW */ /* if (in_row) output += QString( "\n" ); output += QString( "\n" ); in_row = true; */ ptr += fclen; break; case 9: /* NEW_CELL */ // align = ptr[2]; // colspan = ptr[5]; // rowspan = ptr[6]; /** output += QString( "\n" ); break; default: ptr += fclen; } break; default: ptr += fclen; } } else { //output += QString( "
" ).arg( align_names[align], colspan, rowspan ); // border_color); */ if ( (record_id = READ_BIGENDIAN_SHORT (&ptr[3])) ) { QTextCharFormat format = context->cursor->charFormat(); context->cursor->insertImage( QStringLiteral( "%1.jpg" ).arg(record_id) ); context->cursor->setCharFormat( format ); context->images.append( record_id ); AddRecord (record_id); } DoStyle (context, style, true); text_len = READ_BIGENDIAN_SHORT (&ptr[7]); ptr += fclen; ParseText (doc, ptr, text_len, &font, &style, context); ptr += text_len; DoStyle (context, style, false); //output += QString( "
\n" ); return false; } } // output += QString( "\n" ); return true; } typedef struct { int size; int attributes; } ParagraphInfo; static ParagraphInfo *ParseParagraphInfo ( unsigned char* bytes, int* nparas ) { ParagraphInfo* paragraph_info; int j; int n; n = (bytes[2] << 8) + bytes[3]; paragraph_info = (ParagraphInfo *) malloc (sizeof (ParagraphInfo) * n); for (j = 0; j < n; j++) { paragraph_info[j].size = (bytes[8 + (j * 4) + 0] << 8) + bytes[8 + (j * 4) + 1]; paragraph_info[j].attributes = (bytes[8 + (j * 4) + 2] << 8) + bytes[8 + (j * 4) + 3]; } *nparas = n; return paragraph_info; } bool QUnpluck::TranscribeTextRecord ( plkr_Document* doc, int id, Context *context, unsigned char* bytes, plkr_DataRecordType type ) { unsigned char* ptr; unsigned char* run; unsigned char* para_start; unsigned char* data; unsigned char* start; ParagraphInfo* paragraphs; bool first_record_of_page = true; bool current_link; bool current_italic; bool current_struckthrough; bool current_underline; int fctype; int fclen; int para_index; int para_len; int textlen; int data_len; int current_font; int record_index; // int current_alignment; // int current_left_margin; // int current_right_margin; int nparagraphs; // long current_color; record_index = id; paragraphs = ParseParagraphInfo (bytes, &nparagraphs); start = bytes + 8 + ((bytes[2] << 8) + bytes[3]) * 4; for (para_index = 0, ptr = start, run = start; para_index < nparagraphs; para_index++) { para_len = paragraphs[para_index].size; /* If the paragraph is the last in the record, and it consists of a link to the next record in the logical page, we trim off the paragraph and instead insert the whole page */ if (((para_index + 1) == nparagraphs) && (para_len == (sizeof ("Click here for the next part") + 5)) && (*ptr == 0) && (ptr[1] == ((PLKR_TFC_LINK << 3) + 2)) && (strcmp ((char*)(ptr + 4), "Click here for the next part") == 0)) { record_index = (ptr[2] << 8) + ptr[3]; if ((data = plkr_GetRecordBytes (doc, record_index, &data_len, &type)) == NULL) { // ShowWarning ("Can't open record %d!", record_index); free (paragraphs); return false; } else if (!(type == PLKR_DRTYPE_TEXT_COMPRESSED || type == PLKR_DRTYPE_TEXT)) { // ShowWarning ("Bad record type %d in record linked from end of record %d", type, id); free (paragraphs); return false; } first_record_of_page = false; para_index = 0; ptr = data + 8 + ((data[2] << 8) + data[3]) * 4; run = ptr; free (paragraphs); paragraphs = ParseParagraphInfo (data, &nparagraphs); para_len = paragraphs[para_index].size; MarkRecordDone (record_index); SetPageID (record_index, id); } if ((para_index == 0) && !first_record_of_page && (*ptr == 0) && (ptr[1] == ((PLKR_TFC_LINK << 3) + 2)) && (strcmp ((char*)(ptr + 4), "Click here for the previous part") == 0)) { /* throw away this inserted paragraph */ ptr += para_len; run = ptr; continue; } QTextCharFormat format( context->cursor->charFormat() ); QTextBlockFormat blockFormat( context->cursor->blockFormat() ); blockFormat.setAlignment( Qt::AlignLeft ); context->cursor->insertBlock( blockFormat ); context->cursor->setCharFormat( format ); mNamedTargets.insert( QStringLiteral( "para:%1-%2" ).arg( record_index ).arg( para_index ), QPair( GetPageID( record_index ), context->cursor->block() ) ); current_link = false; /* at the beginning of a paragraph, we start with a clean graphics context */ current_font = 0; // current_alignment = 0; // current_color = 0; current_italic = false; current_underline = false; current_struckthrough = false; // current_left_margin = 0; // current_right_margin = 0; for (para_start = ptr, textlen = 0; (ptr - para_start) < para_len;) { if (*ptr == 0) { /* function code */ if ((ptr - run) > 0) { /* write out any pending text */ context->cursor->insertText( QString::fromLatin1( (char*)run, ptr - run ) ); textlen += (ptr - run); } ptr++; fctype = GET_FUNCTION_CODE_TYPE (*ptr); fclen = GET_FUNCTION_CODE_DATALEN (*ptr); ptr++; if (fctype == PLKR_TFC_NEWLINE) { // TODO: remove the setCharFormat when Qt is fixed QTextCharFormat format( context->cursor->charFormat() ); context->cursor->insertText( QStringLiteral("\n") ); context->cursor->setCharFormat( format ); } else if (fctype == PLKR_TFC_LINK) { int record_id, real_record_id, datalen; plkr_DataRecordType type = (plkr_DataRecordType)0; unsigned char *bytes = NULL; char *url = NULL; if (fclen == 0) { if (current_link) { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); if ( !context->linkUrl.isEmpty() ) { Link link; link.url = context->linkUrl; link.start = context->linkStart; link.end = context->cursor->position(); link.page = GetPageID( id ); mLinks.append( link ); } } current_link = false; } else { record_id = (ptr[0] << 8) + ptr[1]; bytes = plkr_GetRecordBytes (doc, record_id, &datalen, &type); if (!bytes) { url = plkr_GetRecordURL (doc, record_id); } if (bytes && (type == PLKR_DRTYPE_MAILTO)) { context->linkUrl = MailtoURLFromBytes( bytes ); context->linkStart = context->cursor->position(); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else if (!bytes && url) { context->linkUrl = QString::fromLatin1( url ); context->linkStart = context->cursor->position(); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else if (bytes && (fclen == 2)) { AddRecord (record_id); real_record_id = GetPageID (record_id); if (type == PLKR_DRTYPE_IMAGE || type == PLKR_DRTYPE_IMAGE_COMPRESSED) { context->linkUrl = QStringLiteral( "%1.jpg" ).arg( record_id ); context->linkStart = context->cursor->position(); } else { context->linkUrl = QStringLiteral( "page:%1" ).arg( real_record_id ); context->linkStart = context->cursor->position(); } QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else if (bytes && (fclen == 4)) { AddRecord (record_id); context->linkUrl = QStringLiteral( "para:%1-%2" ).arg( record_id ).arg( (ptr[2] << 8) + ptr[3] ); context->linkStart = context->cursor->position(); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else { // ShowWarning("odd link found: record_id=%d, bytes=0x%p, type=%d, url=%s", record_id, bytes, type, (url ? url : "0x0")); } } } else if (fctype == PLKR_TFC_FONT) { if (current_font != *ptr) { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); int pointSize = qRound( format.fontPointSize() ); if (*ptr == 1) { format.setFontWeight( QFont::Bold ); pointSize += 3; } else if (*ptr == 2) { format.setFontWeight( QFont::Bold ); pointSize += 2; } else if (*ptr == 3) { format.setFontWeight( QFont::Bold ); pointSize += 1; } else if (*ptr == 4) { format.setFontWeight( QFont::Bold ); } else if (*ptr == 5) { format.setFontWeight( QFont::Bold ); pointSize += -1; } else if (*ptr == 6) { format.setFontWeight( QFont::Bold ); pointSize += -2; } else if (*ptr == 7) { format.setFontWeight( QFont::Bold ); } else if (*ptr == 8) { format.setFontFamily( QStringLiteral( "Courier New,courier" ) ); } else if (*ptr == 11) { format.setVerticalAlignment( QTextCharFormat::AlignSuperScript ); } format.setFontPointSize( qMax( pointSize, 1 ) ); context->cursor->setCharFormat( format ); current_font = *ptr; } } else if (fctype == PLKR_TFC_BITALIC) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( true ); context->cursor->setCharFormat( format ); current_italic = true; } else if (fctype == PLKR_TFC_EITALIC) { if (current_italic) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( false ); context->cursor->setCharFormat( format ); current_italic = false; } } else if (fctype == PLKR_TFC_BULINE) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( true ); context->cursor->setCharFormat( format ); current_underline = true; } else if (fctype == PLKR_TFC_EULINE) { if (current_underline) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( false ); context->cursor->setCharFormat( format ); current_underline = false; } } else if (fctype == PLKR_TFC_BSTRIKE) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( true ); context->cursor->setCharFormat( format ); current_struckthrough = true; } else if (fctype == PLKR_TFC_ESTRIKE) { if (current_struckthrough) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( false ); context->cursor->setCharFormat( format ); current_struckthrough = false; } } else if (fctype == PLKR_TFC_HRULE) { QTextCharFormat charFormat = context->cursor->charFormat(); QTextBlockFormat oldBlockFormat = context->cursor->blockFormat(); QTextBlockFormat blockFormat; blockFormat.setProperty( QTextFormat::BlockTrailingHorizontalRulerWidth, QStringLiteral("100%")); context->cursor->insertBlock( blockFormat ); context->cursor->insertBlock( oldBlockFormat ); context->cursor->setCharFormat( charFormat ); } else if (fctype == PLKR_TFC_ALIGN) { // current_alignment = 0; if (*ptr < 4) { QTextBlockFormat format( context->cursor->blockFormat() ); if (*ptr == 0) format.setAlignment( Qt::AlignLeft ); else if (*ptr == 1) format.setAlignment( Qt::AlignRight ); else if (*ptr == 2) format.setAlignment( Qt::AlignCenter ); else if (*ptr == 3) format.setAlignment( Qt::AlignJustify ); QTextCharFormat charFormat( context->cursor->charFormat() ); context->cursor->insertBlock( format ); context->cursor->setCharFormat( charFormat ); // current_alignment = (*ptr) + 1; } } else if (fctype == PLKR_TFC_MARGINS) { /* Not easy to set, in HTML */ #if 0 output += QString( "" ).arg(ptr[0], ptr[1]); if (current_left_margin != ptr[0] || current_right_margin != ptr[1]) { if (current_right_margin != 0) fprintf (fp, " ", current_right_margin); fprintf (fp, "\n"); } current_left_margin = ptr[0]; current_right_margin = ptr[1]; if (current_right_margin > 0 || current_left_margin > 0) { fprintf (fp, ""); if (current_left_margin != 0) { fprintf (fp, ""); } fprintf (fp, "
", current_left_margin); if ((ptr - run) > 2) { fwrite (run, 1, ((ptr - 2) - run), fp); textlen += ((ptr - 2) - run); } else { fprintf (fp, " "); } fprintf (fp, ""); if (current_left_margin == 0 && (ptr - run) > 2) { fwrite (run, 1, ((ptr - 2) - run), fp); textlen += ((ptr - 2) - run); } } else { if ((ptr - run) > 2) { fwrite (run, 1, ((ptr - 2) - run), fp); textlen += ((ptr - 2) - run); } } #endif // current_left_margin = ptr[0]; // current_right_margin = ptr[1]; } else if (fctype == PLKR_TFC_COLOR) { /* not sure what to do here yet */ /* fprintf (fp, "", ptr[0], ptr[1], ptr[2]);*/ // current_color = // (ptr[0] << 16) + (ptr[1] << 8) + ptr[2]; } else if (fctype == PLKR_TFC_IMAGE || fctype == PLKR_TFC_IMAGE2) { QTextCharFormat format = context->cursor->charFormat(); context->cursor->insertImage( QStringLiteral( "%1.jpg" ).arg( (ptr[0] << 8) + ptr[1] ) ); context->images.append( (ptr[0] << 8) + ptr[1] ); context->cursor->setCharFormat( format ); AddRecord ((ptr[0] << 8) + ptr[1]); } else if (fctype == PLKR_TFC_TABLE) { int record_id, datalen; plkr_DataRecordType type = (plkr_DataRecordType)0; unsigned char *bytes = NULL; record_id = (ptr[0] << 8) + ptr[1]; bytes = plkr_GetRecordBytes (doc, record_id, &datalen, &type); TranscribeTableRecord (doc, context, bytes); } else if (fctype == PLKR_TFC_UCHAR) { if (fclen == 3) context->cursor->insertText( QChar( (ptr[1] << 8) + ptr[2] ) ); else if (fclen == 5) context->cursor->insertText( QChar( (ptr[3] << 8) + ptr[4] ) ); /* skip over alternate text */ ptr += ptr[0]; } else { /* ignore function */ //output += QString( "" ).arg(fctype); } ptr += fclen; run = ptr; } else { ptr++; } } if ((ptr - run) > 0) { /* output any pending text at the end of the paragraph */ context->cursor->insertText( QString::fromLatin1( (char *)run, ptr - run ) ); textlen += (ptr - run); run = ptr; } /* clear the graphics state again */ if (current_font > 0 && current_font < 9 ) { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); } if (current_italic) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( false ); context->cursor->setCharFormat( format ); } if (current_underline) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( false ); context->cursor->setCharFormat( format ); } if (current_struckthrough) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( false ); context->cursor->setCharFormat( format ); } #if 0 if (current_alignment > 0) { context->cursor->insertBlock(); } if (current_right_margin > 0) fprintf (fp, " 
", current_right_margin); else if (current_left_margin > 0) fprintf (fp, ""); /* end the paragraph */ context->cursor->insertBlock(); #endif } free (paragraphs); return true; } bool QUnpluck::TranscribeRecord( int index ) { plkr_DataRecordType type; int data_len; bool status = true; unsigned char *data = plkr_GetRecordBytes( mDocument, index, &data_len, &type); if ( !data ) { MarkRecordDone( index ); return false; } if (type == PLKR_DRTYPE_TEXT_COMPRESSED || type == PLKR_DRTYPE_TEXT) { QTextDocument *document = new QTextDocument; QTextFrameFormat format( document->rootFrame()->frameFormat() ); format.setMargin( 20 ); document->rootFrame()->setFrameFormat( format ); Context *context = new Context; context->recordId = index; context->document = document; context->cursor = new QTextCursor( document ); QTextCharFormat charFormat; charFormat.setFontPointSize( 10 ); charFormat.setFontFamily( QStringLiteral("Helvetica") ); context->cursor->setCharFormat( charFormat ); status = TranscribeTextRecord( mDocument, index, context, data, type ); document->setTextWidth( 600 ); delete context->cursor; mContext.append( context ); } else if (type == PLKR_DRTYPE_IMAGE_COMPRESSED || type == PLKR_DRTYPE_IMAGE) { QImage image = TranscribeImageRecord( data ); mImages.insert( index, image ); } else if (type == PLKR_DRTYPE_MULTIIMAGE) { QImage image; if ( TranscribeMultiImageRecord( mDocument, image, data ) ) mImages.insert( index, image ); } else { status = false; } // plkr_GetHomeRecordID (doc))) MarkRecordDone( index ); return status; } diff --git a/generators/plucker/unpluck/qunpluck.h b/generators/plucker/unpluck/qunpluck.h index fdbc5d769..e1caf5f4b 100644 --- a/generators/plucker/unpluck/qunpluck.h +++ b/generators/plucker/unpluck/qunpluck.h @@ -1,85 +1,86 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * Based on code written by Bill Janssen 2002 * * * * 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 QUNPLUCK_H #define QUNPLUCK_H -#include -#include -#include +#include +#include +#include +#include #include "unpluck.h" class Context; class RecordNode; class QTextDocument; namespace Okular { class Action; } class Link { public: Link() : link( 0 ) { } typedef QVector List; Okular::Action *link; QString url; int page; int start; int end; }; class QUnpluck { public: QUnpluck(); ~QUnpluck(); bool open( const QString &fileName ); QList pages() const { return mPages; } Link::List links() const { return mLinks; } QMap infos() const { return mInfo; } private: int GetNextRecordNumber(); int GetPageID( int index ); void AddRecord( int index ); void MarkRecordDone( int index ); void SetPageID( int index, int page_id ); QString MailtoURLFromBytes( unsigned char* record_data ); void DoStyle( Context* context, int style, bool start ); bool TranscribeRecord( int index ); QImage TranscribeImageRecord( unsigned char* bytes ); bool TranscribeTableRecord( plkr_Document* doc, Context* context, unsigned char* bytes ); bool TranscribeTextRecord( plkr_Document* doc, int id, Context* context, unsigned char* bytes, plkr_DataRecordType type ); void ParseText( plkr_Document* doc, unsigned char* ptr, int text_len, int* font, int* style, Context* context ); plkr_Document* mDocument; QList mRecords; QList mContext; QList mPages; QMap > mNamedTargets; QMap mImages; QMap mInfo; QString mErrorString; Link::List mLinks; }; #endif diff --git a/generators/poppler/annots.cpp b/generators/poppler/annots.cpp index a24539e75..a195a0714 100644 --- a/generators/poppler/annots.cpp +++ b/generators/poppler/annots.cpp @@ -1,427 +1,428 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2012 by Guillermo A. Amaral B. * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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 "annots.h" + #include // qt/kde includes -#include -#include +#include +#include #include #include -#include "annots.h" #include "debug_pdf.h" #include "generator_pdf.h" #include "popplerembeddedfile.h" #include "config-okular-poppler.h" Q_DECLARE_METATYPE( Poppler::Annotation* ) extern Okular::Sound* createSoundFromPopplerSound( const Poppler::SoundObject *popplerSound ); extern Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerMovie ); extern Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *popplerScreen ); #ifdef HAVE_POPPLER_0_36 extern QPair createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia ); #endif static void disposeAnnotation( const Okular::Annotation *ann ) { Poppler::Annotation *popplerAnn = qvariant_cast< Poppler::Annotation * >( ann->nativeId() ); delete popplerAnn; } static QPointF normPointToPointF( const Okular::NormalizedPoint& pt ) { return QPointF(pt.x, pt.y); } static QRectF normRectToRectF( const Okular::NormalizedRect& rect ) { return QRectF( QPointF(rect.left, rect.top), QPointF(rect.right, rect.bottom) ); } // Poppler and Okular share the same flag values, but we don't want to export internal flags static int maskExportedFlags(int flags) { return flags & ( Okular::Annotation::Hidden | Okular::Annotation::FixedSize | Okular::Annotation::FixedRotation | Okular::Annotation::DenyPrint | Okular::Annotation::DenyWrite | Okular::Annotation::DenyDelete | Okular::Annotation::ToggleHidingOnMouse ); } //BEGIN PopplerAnnotationProxy implementation PopplerAnnotationProxy::PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex, QHash *annotsOnOpenHash ) : ppl_doc ( doc ), mutex ( userMutex ), annotationsOnOpenHash( annotsOnOpenHash ) { } PopplerAnnotationProxy::~PopplerAnnotationProxy() { } bool PopplerAnnotationProxy::supports( Capability cap ) const { switch ( cap ) { case Addition: case Modification: case Removal: return true; default: return false; } } void PopplerAnnotationProxy::notifyAddition( Okular::Annotation *okl_ann, int page ) { // Export annotation to DOM QDomDocument doc; QDomElement dom_ann = doc.createElement( QStringLiteral("root") ); Okular::AnnotationUtils::storeAnnotation( okl_ann, dom_ann, doc ); QMutexLocker ml(mutex); // Create poppler annotation Poppler::Annotation *ppl_ann = Poppler::AnnotationUtils::createAnnotation( dom_ann ); // Poppler doesn't render StampAnnotations yet if ( ppl_ann->subType() != Poppler::Annotation::AStamp ) okl_ann->setFlags( okl_ann->flags() | Okular::Annotation::ExternallyDrawn ); // Poppler stores highlight points in swapped order if ( ppl_ann->subType() == Poppler::Annotation::AHighlight ) { Poppler::HighlightAnnotation * hlann = static_cast( ppl_ann ); QList quads = hlann->highlightQuads(); QMutableListIterator it( quads ); while ( it.hasNext() ) { Poppler::HighlightAnnotation::Quad &q = it.next(); QPointF t; t = q.points[3]; q.points[3] = q.points[0]; q.points[0] = t; t = q.points[2]; q.points[2] = q.points[1]; q.points[1] = t; } hlann->setHighlightQuads( quads ); } // Bind poppler object to page Poppler::Page *ppl_page = ppl_doc->page( page ); ppl_page->addAnnotation( ppl_ann ); delete ppl_page; // Set pointer to poppler annotation as native Id okl_ann->setNativeId( qVariantFromValue( ppl_ann ) ); okl_ann->setDisposeDataFunction( disposeAnnotation ); qCDebug(OkularPdfDebug) << okl_ann->uniqueName(); } void PopplerAnnotationProxy::notifyModification( const Okular::Annotation *okl_ann, int page, bool appearanceChanged ) { Q_UNUSED( page ); Q_UNUSED( appearanceChanged ); Poppler::Annotation *ppl_ann = qvariant_cast( okl_ann->nativeId() ); if ( !ppl_ann ) // Ignore non-native annotations return; QMutexLocker ml(mutex); if ( okl_ann->flags() & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) ) { // Okular ui already renders the annotation on its own ppl_ann->setFlags( Poppler::Annotation::Hidden ); return; } // Set basic properties // Note: flags and boundary must be set first in order to correctly handle // FixedRotation annotations. ppl_ann->setFlags(maskExportedFlags( okl_ann->flags() )); ppl_ann->setBoundary(normRectToRectF( okl_ann->boundingRectangle() )); ppl_ann->setAuthor( okl_ann->author() ); ppl_ann->setContents( okl_ann->contents() ); // Set style Poppler::Annotation::Style s; s.setColor( okl_ann->style().color() ); s.setWidth( okl_ann->style().width() ); s.setOpacity( okl_ann->style().opacity() ); ppl_ann->setStyle( s ); // Set type-specific properties (if any) switch ( ppl_ann->subType() ) { case Poppler::Annotation::AText: { const Okular::TextAnnotation * okl_txtann = static_cast(okl_ann); Poppler::TextAnnotation * ppl_txtann = static_cast(ppl_ann); ppl_txtann->setTextIcon( okl_txtann->textIcon() ); ppl_txtann->setTextFont( okl_txtann->textFont() ); ppl_txtann->setInplaceAlign( okl_txtann->inplaceAlignment() ); ppl_txtann->setCalloutPoints( QVector() ); ppl_txtann->setInplaceIntent( (Poppler::TextAnnotation::InplaceIntent)okl_txtann->inplaceIntent() ); break; } case Poppler::Annotation::ALine: { const Okular::LineAnnotation * okl_lineann = static_cast(okl_ann); Poppler::LineAnnotation * ppl_lineann = static_cast(ppl_ann); QLinkedList points; foreach ( const Okular::NormalizedPoint &p, okl_lineann->linePoints() ) points.append(normPointToPointF( p )); ppl_lineann->setLinePoints( points ); ppl_lineann->setLineStartStyle( (Poppler::LineAnnotation::TermStyle)okl_lineann->lineStartStyle() ); ppl_lineann->setLineEndStyle( (Poppler::LineAnnotation::TermStyle)okl_lineann->lineEndStyle() ); ppl_lineann->setLineClosed( okl_lineann->lineClosed() ); ppl_lineann->setLineInnerColor( okl_lineann->lineInnerColor() ); ppl_lineann->setLineLeadingForwardPoint( okl_lineann->lineLeadingForwardPoint() ); ppl_lineann->setLineLeadingBackPoint( okl_lineann->lineLeadingBackwardPoint() ); ppl_lineann->setLineShowCaption( okl_lineann->showCaption() ); ppl_lineann->setLineIntent( (Poppler::LineAnnotation::LineIntent)okl_lineann->lineIntent() ); break; } case Poppler::Annotation::AGeom: { const Okular::GeomAnnotation * okl_geomann = static_cast(okl_ann); Poppler::GeomAnnotation * ppl_geomann = static_cast(ppl_ann); ppl_geomann->setGeomType( (Poppler::GeomAnnotation::GeomType)okl_geomann->geometricalType() ); ppl_geomann->setGeomInnerColor( okl_geomann->geometricalInnerColor() ); break; } case Poppler::Annotation::AHighlight: { const Okular::HighlightAnnotation * okl_hlann = static_cast(okl_ann); Poppler::HighlightAnnotation * ppl_hlann = static_cast(ppl_ann); ppl_hlann->setHighlightType( (Poppler::HighlightAnnotation::HighlightType)okl_hlann->highlightType() ); break; } case Poppler::Annotation::AStamp: { const Okular::StampAnnotation * okl_stampann = static_cast(okl_ann); Poppler::StampAnnotation * ppl_stampann = static_cast(ppl_ann); ppl_stampann->setStampIconName( okl_stampann->stampIconName() ); break; } case Poppler::Annotation::AInk: { const Okular::InkAnnotation * okl_inkann = static_cast(okl_ann); Poppler::InkAnnotation * ppl_inkann = static_cast(ppl_ann); QList< QLinkedList > paths; foreach ( const QLinkedList &path, okl_inkann->inkPaths() ) { QLinkedList points; foreach ( const Okular::NormalizedPoint &p, path ) points.append(normPointToPointF( p )); paths.append( points ); } ppl_inkann->setInkPaths( paths ); break; } default: qCDebug(OkularPdfDebug) << "Type-specific property modification is not implemented for this annotation type"; break; } qCDebug(OkularPdfDebug) << okl_ann->uniqueName(); } void PopplerAnnotationProxy::notifyRemoval( Okular::Annotation *okl_ann, int page ) { Poppler::Annotation *ppl_ann = qvariant_cast( okl_ann->nativeId() ); if ( !ppl_ann ) // Ignore non-native annotations return; QMutexLocker ml(mutex); Poppler::Page *ppl_page = ppl_doc->page( page ); annotationsOnOpenHash->remove( okl_ann ); ppl_page->removeAnnotation( ppl_ann ); // Also destroys ppl_ann delete ppl_page; okl_ann->setNativeId( qVariantFromValue(0) ); // So that we don't double-free in disposeAnnotation qCDebug(OkularPdfDebug) << okl_ann->uniqueName(); } //END PopplerAnnotationProxy implementation Okular::Annotation* createAnnotationFromPopplerAnnotation( Poppler::Annotation *ann, bool *doDelete ) { Okular::Annotation *annotation = 0; *doDelete = true; bool tieToOkularAnn = false; bool externallyDrawn = false; switch ( ann->subType() ) { case Poppler::Annotation::AFileAttachment: { Poppler::FileAttachmentAnnotation * attachann = static_cast< Poppler::FileAttachmentAnnotation * >( ann ); Okular::FileAttachmentAnnotation * f = new Okular::FileAttachmentAnnotation(); annotation = f; tieToOkularAnn = true; *doDelete = false; f->setFileIconName( attachann->fileIconName() ); f->setEmbeddedFile( new PDFEmbeddedFile( attachann->embeddedFile() ) ); break; } case Poppler::Annotation::ASound: { Poppler::SoundAnnotation * soundann = static_cast< Poppler::SoundAnnotation * >( ann ); Okular::SoundAnnotation * s = new Okular::SoundAnnotation(); annotation = s; s->setSoundIconName( soundann->soundIconName() ); s->setSound( createSoundFromPopplerSound( soundann->sound() ) ); break; } case Poppler::Annotation::AMovie: { Poppler::MovieAnnotation * movieann = static_cast< Poppler::MovieAnnotation * >( ann ); Okular::MovieAnnotation * m = new Okular::MovieAnnotation(); annotation = m; tieToOkularAnn = true; *doDelete = false; m->setMovie( createMovieFromPopplerMovie( movieann->movie() ) ); break; } case Poppler::Annotation::AWidget: { annotation = new Okular::WidgetAnnotation(); break; } case Poppler::Annotation::AScreen: { Okular::ScreenAnnotation * m = new Okular::ScreenAnnotation(); annotation = m; tieToOkularAnn = true; *doDelete = false; break; } #ifdef HAVE_POPPLER_0_36 case Poppler::Annotation::ARichMedia: { Poppler::RichMediaAnnotation * richmediaann = static_cast< Poppler::RichMediaAnnotation * >( ann ); const QPair result = createMovieFromPopplerRichMedia( richmediaann ); if ( result.first ) { Okular::RichMediaAnnotation * r = new Okular::RichMediaAnnotation(); tieToOkularAnn = true; *doDelete = false; annotation = r; r->setMovie( result.first ); r->setEmbeddedFile( result.second ); } break; } #endif case Poppler::Annotation::AText: case Poppler::Annotation::ALine: case Poppler::Annotation::AGeom: case Poppler::Annotation::AHighlight: case Poppler::Annotation::AInk: case Poppler::Annotation::ACaret: externallyDrawn = true; /* fallthrough */ case Poppler::Annotation::AStamp: tieToOkularAnn = true; *doDelete = false; /* fallthrough */ default: { // this is uber ugly but i don't know a better way to do it without introducing a poppler::annotation dependency on core QDomDocument doc; QDomElement root = doc.createElement( QStringLiteral("root") ); doc.appendChild( root ); Poppler::AnnotationUtils::storeAnnotation( ann, root, doc ); annotation = Okular::AnnotationUtils::createAnnotation( root ); break; } } if ( annotation ) { // the Contents field might have lines separated by \r QString contents = ann->contents(); contents.replace( QLatin1Char( '\r' ), QLatin1Char( '\n' ) ); annotation->setAuthor( ann->author() ); annotation->setContents( contents ); annotation->setUniqueName( ann->uniqueName() ); annotation->setModificationDate( ann->modificationDate() ); annotation->setCreationDate( ann->creationDate() ); annotation->setFlags( ann->flags() | Okular::Annotation::External ); annotation->setBoundingRectangle( Okular::NormalizedRect::fromQRectF( ann->boundary() ) ); if (externallyDrawn) annotation->setFlags( annotation->flags() | Okular::Annotation::ExternallyDrawn ); // Poppler stores highlight points in swapped order if ( annotation->subType() == Okular::Annotation::AHighlight ) { Okular::HighlightAnnotation * hlann = static_cast( annotation ); QList &quads = hlann->highlightQuads(); for (QList::iterator it = quads.begin(); it != quads.end(); ++it) { Okular::NormalizedPoint t; t = it->point( 3 ); it->setPoint( it->point(0), 3 ); it->setPoint( t, 0 ); t = it->point( 2 ); it->setPoint( it->point(1), 2 ); it->setPoint( t, 1 ); } } if ( annotation->subType() == Okular::Annotation::AText ) { Okular::TextAnnotation * txtann = static_cast( annotation ); if ( txtann->textType() == Okular::TextAnnotation::Linked ) { Poppler::TextAnnotation * ppl_txtann = static_cast( ann ); // Poppler and Okular assume a different default icon name in XML // We re-read it via getter, which always tells the right one txtann->setTextIcon( ppl_txtann->textIcon() ); } } // TODO clone style // TODO clone window // TODO clone revisions if ( tieToOkularAnn ) { annotation->setNativeId( qVariantFromValue( ann ) ); annotation->setDisposeDataFunction( disposeAnnotation ); } } return annotation; } diff --git a/generators/poppler/debug_pdf.h b/generators/poppler/debug_pdf.h index 7e3d61d57..039579035 100644 --- a/generators/poppler/debug_pdf.h +++ b/generators/poppler/debug_pdf.h @@ -1,17 +1,17 @@ /*************************************************************************** * Copyright (C) 2014 by Frederik Gladhorn * * * * 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_DEBUG_P_H -#define OKULAR_DEBUG_P_H +#ifndef OKULAR_DEBUG_PDF_H +#define OKULAR_DEBUG_PDF_H -#include +#include Q_DECLARE_LOGGING_CATEGORY(OkularPdfDebug) #endif diff --git a/generators/poppler/generator_pdf.cpp b/generators/poppler/generator_pdf.cpp index 2c5e6c6e0..d2066b1e0 100644 --- a/generators/poppler/generator_pdf.cpp +++ b/generators/poppler/generator_pdf.cpp @@ -1,1973 +1,1973 @@ /*************************************************************************** * Copyright (C) 2004-2008 by Albert Astals Cid * * Copyright (C) 2004 by Enrico Ros * * Copyright (C) 2012 by Guillermo A. Amaral B. * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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 "generator_pdf.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 #include "ui_pdfsettingswidget.h" #include "pdfsettings.h" #include #include #include "debug_pdf.h" #include "annots.h" #include "formfields.h" #include "popplerembeddedfile.h" Q_DECLARE_METATYPE(Poppler::Annotation*) Q_DECLARE_METATYPE(Poppler::FontInfo) Q_DECLARE_METATYPE(const Poppler::LinkMovie*) Q_DECLARE_METATYPE(const Poppler::LinkRendition*) #ifdef HAVE_POPPLER_0_50 Q_DECLARE_METATYPE(const Poppler::LinkOCGState*) #endif static const int defaultPageWidth = 595; static const int defaultPageHeight = 842; class PDFOptionsPage : public QWidget { Q_OBJECT public: PDFOptionsPage() { setWindowTitle( i18n( "PDF Options" ) ); QVBoxLayout *layout = new QVBoxLayout(this); m_printAnnots = new QCheckBox(i18n("Print annotations"), this); m_printAnnots->setToolTip(i18n("Include annotations in the printed document")); m_printAnnots->setWhatsThis(i18n("Includes annotations in the printed document. You can disable this if you want to print the original unannotated document.")); layout->addWidget(m_printAnnots); m_forceRaster = new QCheckBox(i18n("Force rasterization"), this); m_forceRaster->setToolTip(i18n("Rasterize into an image before printing")); m_forceRaster->setWhatsThis(i18n("Forces the rasterization of each page into an image before printing it. This usually gives somewhat worse results, but is useful when printing documents that appear to print incorrectly.")); layout->addWidget(m_forceRaster); layout->addStretch(1); #if defined(Q_OS_WIN) && !defined HAVE_POPPLER_0_60 m_printAnnots->setVisible( false ); #endif setPrintAnnots( true ); // Default value } bool printAnnots() { return m_printAnnots->isChecked(); } void setPrintAnnots( bool printAnnots ) { m_printAnnots->setChecked( printAnnots ); } bool printForceRaster() { return m_forceRaster->isChecked(); } void setPrintForceRaster( bool forceRaster ) { m_forceRaster->setChecked( forceRaster ); } private: QCheckBox *m_printAnnots; QCheckBox *m_forceRaster; }; static void fillViewportFromLinkDestination( Okular::DocumentViewport &viewport, const Poppler::LinkDestination &destination ) { viewport.pageNumber = destination.pageNumber() - 1; if (!viewport.isValid()) return; // get destination position // TODO add other attributes to the viewport (taken from link) // switch ( destination->getKind() ) // { // case destXYZ: if (destination.isChangeLeft() || destination.isChangeTop()) { // TODO remember to change this if we implement DPI and/or rotation double left, top; left = destination.left(); top = destination.top(); viewport.rePos.normalizedX = left; viewport.rePos.normalizedY = top; viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::TopLeft; } /* TODO if ( dest->getChangeZoom() ) make zoom change*/ /* break; default: // implement the others cases break;*/ // } } Okular::Sound* createSoundFromPopplerSound( const Poppler::SoundObject *popplerSound ) { Okular::Sound *sound = popplerSound->soundType() == Poppler::SoundObject::Embedded ? new Okular::Sound( popplerSound->data() ) : new Okular::Sound( popplerSound->url() ); sound->setSamplingRate( popplerSound->samplingRate() ); sound->setChannels( popplerSound->channels() ); sound->setBitsPerSample( popplerSound->bitsPerSample() ); switch ( popplerSound->soundEncoding() ) { case Poppler::SoundObject::Raw: sound->setSoundEncoding( Okular::Sound::Raw ); break; case Poppler::SoundObject::Signed: sound->setSoundEncoding( Okular::Sound::Signed ); break; case Poppler::SoundObject::muLaw: sound->setSoundEncoding( Okular::Sound::muLaw ); break; case Poppler::SoundObject::ALaw: sound->setSoundEncoding( Okular::Sound::ALaw ); break; } return sound; } Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerMovie ) { Okular::Movie *movie = new Okular::Movie( popplerMovie->url() ); movie->setSize( popplerMovie->size() ); movie->setRotation( (Okular::Rotation)( popplerMovie->rotation() / 90 ) ); movie->setShowControls( popplerMovie->showControls() ); movie->setPlayMode( (Okular::Movie::PlayMode)popplerMovie->playMode() ); movie->setAutoPlay( false ); // will be triggered by external MovieAnnotation movie->setShowPosterImage( popplerMovie->showPosterImage() ); movie->setPosterImage( popplerMovie->posterImage() ); return movie; } Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *popplerScreen ) { Poppler::MediaRendition *rendition = popplerScreen->rendition(); Okular::Movie *movie = 0; if ( rendition->isEmbedded() ) movie = new Okular::Movie( rendition->fileName(), rendition->data() ); else movie = new Okular::Movie( rendition->fileName() ); movie->setSize( rendition->size() ); movie->setShowControls( rendition->showControls() ); if ( rendition->repeatCount() == 0 ) { movie->setPlayMode( Okular::Movie::PlayRepeat ); } else { movie->setPlayMode( Okular::Movie::PlayLimited ); movie->setPlayRepetitions( rendition->repeatCount() ); } movie->setAutoPlay( rendition->autoPlay() ); return movie; } #ifdef HAVE_POPPLER_0_36 QPair createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia ) { const QPair emptyResult(0, 0); /** * To convert a Flash/Video based RichMedia annotation to a movie, we search for the first * Flash/Video richmedia instance and parse the flashVars parameter for the 'source' identifier. * That identifier is then used to find the associated embedded file through the assets * mapping. */ const Poppler::RichMediaAnnotation::Content *content = popplerRichMedia->content(); if ( !content ) return emptyResult; const QList configurations = content->configurations(); if ( configurations.isEmpty() ) return emptyResult; const Poppler::RichMediaAnnotation::Configuration *configuration = configurations[0]; const QList instances = configuration->instances(); if ( instances.isEmpty() ) return emptyResult; const Poppler::RichMediaAnnotation::Instance *instance = instances[0]; if ( ( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeFlash ) && ( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeVideo ) ) return emptyResult; const Poppler::RichMediaAnnotation::Params *params = instance->params(); if ( !params ) return emptyResult; QString sourceId; bool playbackLoops = false; const QStringList flashVars = params->flashVars().split( QLatin1Char( '&' ) ); foreach ( const QString & flashVar, flashVars ) { const int pos = flashVar.indexOf( QLatin1Char( '=' ) ); if ( pos == -1 ) continue; const QString key = flashVar.left( pos ); const QString value = flashVar.mid( pos + 1 ); if ( key == QLatin1String( "source" ) ) sourceId = value; else if ( key == QLatin1String( "loop" ) ) playbackLoops = ( value == QLatin1String( "true" ) ? true : false ); } if ( sourceId.isEmpty() ) return emptyResult; const QList assets = content->assets(); if ( assets.isEmpty() ) return emptyResult; Poppler::RichMediaAnnotation::Asset *matchingAsset = 0; foreach ( Poppler::RichMediaAnnotation::Asset *asset, assets ) { if ( asset->name() == sourceId ) { matchingAsset = asset; break; } } if ( !matchingAsset ) return emptyResult; Poppler::EmbeddedFile *embeddedFile = matchingAsset->embeddedFile(); if ( !embeddedFile ) return emptyResult; Okular::EmbeddedFile *pdfEmbeddedFile = new PDFEmbeddedFile( embeddedFile ); Okular::Movie *movie = new Okular::Movie( embeddedFile->name(), embeddedFile->data() ); movie->setPlayMode( playbackLoops ? Okular::Movie::PlayRepeat : Okular::Movie::PlayLimited ); if ( popplerRichMedia && popplerRichMedia->settings() && popplerRichMedia->settings()->activation() ) { if ( popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageOpened || popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageVisible ) { movie->setAutoPlay( true ); } else { movie->setAutoPlay( false ); } } else { movie->setAutoPlay( false ); } return qMakePair(movie, pdfEmbeddedFile); } #endif /** * Note: the function will take ownership of the popplerLink object. */ Okular::Action* createLinkFromPopplerLink(const Poppler::Link *popplerLink, bool deletePopplerLink = true) { if (!popplerLink) return nullptr; Okular::Action *link = 0; const Poppler::LinkGoto *popplerLinkGoto; const Poppler::LinkExecute *popplerLinkExecute; const Poppler::LinkBrowse *popplerLinkBrowse; const Poppler::LinkAction *popplerLinkAction; const Poppler::LinkSound *popplerLinkSound; const Poppler::LinkJavaScript *popplerLinkJS; const Poppler::LinkMovie *popplerLinkMovie; const Poppler::LinkRendition *popplerLinkRendition; Okular::DocumentViewport viewport; switch(popplerLink->linkType()) { case Poppler::Link::None: break; case Poppler::Link::Goto: { popplerLinkGoto = static_cast(popplerLink); const Poppler::LinkDestination dest = popplerLinkGoto->destination(); const QString destName = dest.destinationName(); if (destName.isEmpty()) { fillViewportFromLinkDestination( viewport, dest ); link = new Okular::GotoAction(popplerLinkGoto->fileName(), viewport); } else { link = new Okular::GotoAction(popplerLinkGoto->fileName(), destName); } } break; case Poppler::Link::Execute: popplerLinkExecute = static_cast(popplerLink); link = new Okular::ExecuteAction( popplerLinkExecute->fileName(), popplerLinkExecute->parameters() ); break; case Poppler::Link::Browse: popplerLinkBrowse = static_cast(popplerLink); link = new Okular::BrowseAction( QUrl(popplerLinkBrowse->url()) ); break; case Poppler::Link::Action: popplerLinkAction = static_cast(popplerLink); link = new Okular::DocumentAction( (Okular::DocumentAction::DocumentActionType)popplerLinkAction->actionType() ); break; case Poppler::Link::Sound: { popplerLinkSound = static_cast(popplerLink); Poppler::SoundObject *popplerSound = popplerLinkSound->sound(); Okular::Sound *sound = createSoundFromPopplerSound( popplerSound ); link = new Okular::SoundAction( popplerLinkSound->volume(), popplerLinkSound->synchronous(), popplerLinkSound->repeat(), popplerLinkSound->mix(), sound ); } break; case Poppler::Link::JavaScript: { popplerLinkJS = static_cast(popplerLink); link = new Okular::ScriptAction( Okular::JavaScript, popplerLinkJS->script() ); } break; case Poppler::Link::Rendition: { if (!deletePopplerLink) { // If links should not be deleted it probably means that they // are part of a nextActions chain. There is no support // to resolveMediaLinkReferences on nextActions. It would also // be necessary to ensure that resolveMediaLinkReferences does // not delete the Links which are part of a nextActions list // to avoid a double deletion. qCDebug(OkularPdfDebug) << "parsing rendition link without deletion is not supported. Action chain might be broken."; break; } deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references popplerLinkRendition = static_cast( popplerLink ); Okular::RenditionAction::OperationType operation = Okular::RenditionAction::None; switch ( popplerLinkRendition->action() ) { case Poppler::LinkRendition::NoRendition: operation = Okular::RenditionAction::None; break; case Poppler::LinkRendition::PlayRendition: operation = Okular::RenditionAction::Play; break; case Poppler::LinkRendition::StopRendition: operation = Okular::RenditionAction::Stop; break; case Poppler::LinkRendition::PauseRendition: operation = Okular::RenditionAction::Pause; break; case Poppler::LinkRendition::ResumeRendition: operation = Okular::RenditionAction::Resume; break; }; Okular::Movie *movie = 0; if ( popplerLinkRendition->rendition() ) movie = createMovieFromPopplerScreen( popplerLinkRendition ); Okular::RenditionAction *renditionAction = new Okular::RenditionAction( operation, movie, Okular::JavaScript, popplerLinkRendition->script() ); renditionAction->setNativeId( QVariant::fromValue( popplerLinkRendition ) ); link = renditionAction; } break; case Poppler::Link::Movie: { if (!deletePopplerLink) { // See comment above in Link::Rendition qCDebug(OkularPdfDebug) << "parsing movie link without deletion is not supported. Action chain might be broken."; break; } deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references popplerLinkMovie = static_cast( popplerLink ); Okular::MovieAction::OperationType operation = Okular::MovieAction::Play; switch ( popplerLinkMovie->operation() ) { case Poppler::LinkMovie::Play: operation = Okular::MovieAction::Play; break; case Poppler::LinkMovie::Stop: operation = Okular::MovieAction::Stop; break; case Poppler::LinkMovie::Pause: operation = Okular::MovieAction::Pause; break; case Poppler::LinkMovie::Resume: operation = Okular::MovieAction::Resume; break; }; Okular::MovieAction *movieAction = new Okular::MovieAction( operation ); movieAction->setNativeId( QVariant::fromValue( popplerLinkMovie ) ); link = movieAction; } break; #ifdef HAVE_POPPLER_0_64 case Poppler::Link::Hide: { const Poppler::LinkHide * l = static_cast( popplerLink ); QStringList scripts; for ( const QString &target: l->targets() ) { scripts << QStringLiteral( "getField(\"%1\").hidden = %2;" ).arg( target ).arg( l->isShowAction() ? QLatin1String( "false" ) : QLatin1String( "true" ) ); } link = new Okular::ScriptAction( Okular::JavaScript, scripts.join( QLatin1Char( '\n' ) ) ); } break; #endif } #ifdef HAVE_POPPLER_0_64 if (link) { QVector< Okular::Action * > nextActions; for ( const Poppler::Link *nl : popplerLink->nextLinks() ) { nextActions << createLinkFromPopplerLink( nl, false ); } link->setNextActions( nextActions ); } #endif if ( deletePopplerLink ) delete popplerLink; return link; } /** * Note: the function will take ownership of the popplerLink objects. */ static QLinkedList generateLinks( const QList &popplerLinks ) { QLinkedList links; foreach(const Poppler::Link *popplerLink, popplerLinks) { QRectF linkArea = popplerLink->linkArea(); double nl = linkArea.left(), nt = linkArea.top(), nr = linkArea.right(), nb = linkArea.bottom(); // create the rect using normalized coords and attach the Okular::Link to it Okular::ObjectRect * rect = new Okular::ObjectRect( nl, nt, nr, nb, false, Okular::ObjectRect::Action, createLinkFromPopplerLink(popplerLink) ); // add the ObjectRect to the container links.push_front( rect ); } return links; } /** NOTES on threading: * internal: thread race prevention is done via the 'docLock' mutex. the * mutex is needed only because we have the asynchronous thread; else * the operations are all within the 'gui' thread, scheduled by the * Qt scheduler and no mutex is needed. * external: dangerous operations are all locked via mutex internally, and the * only needed external thing is the 'canGeneratePixmap' method * that tells if the generator is free (since we don't want an * internal queue to store PixmapRequests). A generatedPixmap call * without the 'ready' flag set, results in undefined behavior. * So, as example, printing while generating a pixmap asynchronously is safe, * it might only block the gui thread by 1) waiting for the mutex to unlock * in async thread and 2) doing the 'heavy' print operation. */ OKULAR_EXPORT_PLUGIN(PDFGenerator, "libokularGenerator_poppler.json") static void PDFGeneratorPopplerDebugFunction(const QString &message, const QVariant &closure) { Q_UNUSED(closure); qCDebug(OkularPdfDebug) << "[Poppler]" << message; } PDFGenerator::PDFGenerator( QObject *parent, const QVariantList &args ) : Generator( parent, args ), pdfdoc( 0 ), docSynopsisDirty( true ), docEmbeddedFilesDirty( true ), nextFontPage( 0 ), annotProxy( 0 ) { setFeature( Threaded ); setFeature( TextExtraction ); setFeature( FontInfo ); #ifdef Q_OS_WIN32 setFeature( PrintNative ); #else setFeature( PrintPostscript ); #endif if ( Okular::FilePrinter::ps2pdfAvailable() ) setFeature( PrintToFile ); setFeature( ReadRawData ); setFeature( TiledRendering ); setFeature( SwapBackingFile ); #ifdef HAVE_POPPLER_0_63 setFeature( SupportsCancelling ); #endif // You only need to do it once not for each of the documents but it is cheap enough // so doing it all the time won't hurt either Poppler::setDebugErrorFunction(PDFGeneratorPopplerDebugFunction, QVariant()); } PDFGenerator::~PDFGenerator() { delete pdfOptionsPage; } //BEGIN Generator inherited functions Okular::Document::OpenResult PDFGenerator::loadDocumentWithPassword( const QString & filePath, QVector & pagesVector, const QString &password ) { #ifndef NDEBUG if ( pdfdoc ) { qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it."; return Okular::Document::OpenError; } #endif // create PDFDoc for the given file pdfdoc = Poppler::Document::load( filePath, 0, 0 ); return init(pagesVector, password); } Okular::Document::OpenResult PDFGenerator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector & pagesVector, const QString &password ) { #ifndef NDEBUG if ( pdfdoc ) { qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it."; return Okular::Document::OpenError; } #endif // create PDFDoc for the given file pdfdoc = Poppler::Document::loadFromData( fileData, 0, 0 ); return init(pagesVector, password); } Okular::Document::OpenResult PDFGenerator::init(QVector & pagesVector, const QString &password) { if ( !pdfdoc ) return Okular::Document::OpenError; if ( pdfdoc->isLocked() ) { pdfdoc->unlock( password.toLatin1(), password.toLatin1() ); if ( pdfdoc->isLocked() ) { delete pdfdoc; pdfdoc = 0; return Okular::Document::OpenNeedsPassword; } } // build Pages (currentPage was set -1 by deletePages) int pageCount = pdfdoc->numPages(); if (pageCount < 0) { delete pdfdoc; pdfdoc = 0; return Okular::Document::OpenError; } pagesVector.resize(pageCount); rectsGenerated.fill(false, pageCount); annotationsOnOpenHash.clear(); loadPages(pagesVector, 0, false); // update the configuration reparseConfig(); // create annotation proxy annotProxy = new PopplerAnnotationProxy( pdfdoc, userMutex(), &annotationsOnOpenHash ); // the file has been loaded correctly return Okular::Document::OpenSuccess; } PDFGenerator::SwapBackingFileResult PDFGenerator::swapBackingFile( QString const &newFileName, QVector & newPagesVector ) { const QBitArray oldRectsGenerated = rectsGenerated; doCloseDocument(); auto openResult = loadDocumentWithPassword(newFileName, newPagesVector, QString()); if (openResult != Okular::Document::OpenSuccess) return SwapBackingFileError; // Recreate links if needed since they are done on image() and image() is not called when swapping the file // since the page is already rendered if (oldRectsGenerated.count() == rectsGenerated.count()) { for (int i = 0; i < oldRectsGenerated.count(); ++i) { if (oldRectsGenerated[i]) { Okular::Page *page = newPagesVector[i]; Poppler::Page *pp = pdfdoc->page( i ); if (pp) { page->setObjectRects(generateLinks(pp->links())); rectsGenerated[i] = true; resolveMediaLinkReferences(page); delete pp; } } } } return SwapBackingFileReloadInternalData; } bool PDFGenerator::doCloseDocument() { // remove internal objects userMutex()->lock(); delete annotProxy; annotProxy = 0; delete pdfdoc; pdfdoc = 0; userMutex()->unlock(); docSynopsisDirty = true; docSyn.clear(); docEmbeddedFilesDirty = true; qDeleteAll(docEmbeddedFiles); docEmbeddedFiles.clear(); nextFontPage = 0; rectsGenerated.clear(); return true; } void PDFGenerator::loadPages(QVector &pagesVector, int rotation, bool clear) { // TODO XPDF 3.01 check const int count = pagesVector.count(); double w = 0, h = 0; for ( int i = 0; i < count ; i++ ) { // get xpdf page Poppler::Page * p = pdfdoc->page( i ); Okular::Page * page; if (p) { const QSizeF pSize = p->pageSizeF(); w = pSize.width() / 72.0 * dpi().width(); h = pSize.height() / 72.0 * dpi().height(); Okular::Rotation orientation = Okular::Rotation0; switch (p->orientation()) { case Poppler::Page::Landscape: orientation = Okular::Rotation90; break; case Poppler::Page::UpsideDown: orientation = Okular::Rotation180; break; case Poppler::Page::Seascape: orientation = Okular::Rotation270; break; case Poppler::Page::Portrait: orientation = Okular::Rotation0; break; } if (rotation % 2 == 1) qSwap(w,h); // init a Okular::page, add transition and annotation information page = new Okular::Page( i, w, h, orientation ); addTransition( p, page ); if ( true ) //TODO real check addAnnotations( p, page ); Poppler::Link * tmplink = p->action( Poppler::Page::Opening ); if ( tmplink ) { page->setPageAction( Okular::Page::Opening, createLinkFromPopplerLink( tmplink ) ); } tmplink = p->action( Poppler::Page::Closing ); if ( tmplink ) { page->setPageAction( Okular::Page::Closing, createLinkFromPopplerLink( tmplink ) ); } page->setDuration( p->duration() ); page->setLabel( p->label() ); addFormFields( p, page ); // kWarning(PDFDebug).nospace() << page->width() << "x" << page->height(); #ifdef PDFGENERATOR_DEBUG qCDebug(OkularPdfDebug) << "load page" << i << "with rotation" << rotation << "and orientation" << orientation; #endif delete p; if (clear && pagesVector[i]) delete pagesVector[i]; } else { page = new Okular::Page( i, defaultPageWidth, defaultPageHeight, Okular::Rotation0 ); } // set the Okular::page at the right position in document's pages vector pagesVector[i] = page; } } Okular::DocumentInfo PDFGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/pdf") ); userMutex()->lock(); if ( pdfdoc ) { // compile internal structure reading properties from PDFDoc if ( keys.contains( Okular::DocumentInfo::Title ) ) docInfo.set( Okular::DocumentInfo::Title, pdfdoc->info(QStringLiteral("Title")) ); if ( keys.contains( Okular::DocumentInfo::Subject ) ) docInfo.set( Okular::DocumentInfo::Subject, pdfdoc->info(QStringLiteral("Subject")) ); if ( keys.contains( Okular::DocumentInfo::Author ) ) docInfo.set( Okular::DocumentInfo::Author, pdfdoc->info(QStringLiteral("Author")) ); if ( keys.contains( Okular::DocumentInfo::Keywords ) ) docInfo.set( Okular::DocumentInfo::Keywords, pdfdoc->info(QStringLiteral("Keywords")) ); if ( keys.contains( Okular::DocumentInfo::Creator ) ) docInfo.set( Okular::DocumentInfo::Creator, pdfdoc->info(QStringLiteral("Creator")) ); if ( keys.contains( Okular::DocumentInfo::Producer ) ) docInfo.set( Okular::DocumentInfo::Producer, pdfdoc->info(QStringLiteral("Producer")) ); if ( keys.contains( Okular::DocumentInfo::CreationDate ) ) docInfo.set( Okular::DocumentInfo::CreationDate, QLocale().toString( pdfdoc->date(QStringLiteral("CreationDate")), QLocale::LongFormat ) ); if ( keys.contains( Okular::DocumentInfo::ModificationDate ) ) docInfo.set( Okular::DocumentInfo::ModificationDate, QLocale().toString( pdfdoc->date(QStringLiteral("ModDate")), QLocale::LongFormat ) ); if ( keys.contains( Okular::DocumentInfo::CustomKeys ) ) { int major, minor; pdfdoc->getPdfVersion(&major, &minor); docInfo.set( QStringLiteral("format"), i18nc( "PDF v. ", "PDF v. %1.%2", major, minor ), i18n( "Format" ) ); docInfo.set( QStringLiteral("encryption"), pdfdoc->isEncrypted() ? i18n( "Encrypted" ) : i18n( "Unencrypted" ), i18n("Security") ); docInfo.set( QStringLiteral("optimization"), pdfdoc->isLinearized() ? i18n( "Yes" ) : i18n( "No" ), i18n("Optimized") ); } docInfo.set( Okular::DocumentInfo::Pages, QString::number( pdfdoc->numPages() ) ); } userMutex()->unlock(); return docInfo; } const Okular::DocumentSynopsis * PDFGenerator::generateDocumentSynopsis() { if ( !docSynopsisDirty ) return &docSyn; if ( !pdfdoc ) return NULL; userMutex()->lock(); QDomDocument *toc = pdfdoc->toc(); userMutex()->unlock(); if ( !toc ) return NULL; addSynopsisChildren(toc, &docSyn); delete toc; docSynopsisDirty = false; return &docSyn; } static Okular::FontInfo::FontType convertPopplerFontInfoTypeToOkularFontInfoType( Poppler::FontInfo::Type type ) { switch ( type ) { case Poppler::FontInfo::Type1: return Okular::FontInfo::Type1; break; case Poppler::FontInfo::Type1C: return Okular::FontInfo::Type1C; break; case Poppler::FontInfo::Type3: return Okular::FontInfo::Type3; break; case Poppler::FontInfo::TrueType: return Okular::FontInfo::TrueType; break; case Poppler::FontInfo::CIDType0: return Okular::FontInfo::CIDType0; break; case Poppler::FontInfo::CIDType0C: return Okular::FontInfo::CIDType0C; break; case Poppler::FontInfo::CIDTrueType: return Okular::FontInfo::CIDTrueType; break; case Poppler::FontInfo::Type1COT: return Okular::FontInfo::Type1COT; break; case Poppler::FontInfo::TrueTypeOT: return Okular::FontInfo::TrueTypeOT; break; case Poppler::FontInfo::CIDType0COT: return Okular::FontInfo::CIDType0COT; break; case Poppler::FontInfo::CIDTrueTypeOT: return Okular::FontInfo::CIDTrueTypeOT; break; case Poppler::FontInfo::unknown: default: ; } return Okular::FontInfo::Unknown; } static Okular::FontInfo::EmbedType embedTypeForPopplerFontInfo( const Poppler::FontInfo &fi ) { Okular::FontInfo::EmbedType ret = Okular::FontInfo::NotEmbedded; if ( fi.isEmbedded() ) { if ( fi.isSubset() ) { ret = Okular::FontInfo::EmbeddedSubset; } else { ret = Okular::FontInfo::FullyEmbedded; } } return ret; } Okular::FontInfo::List PDFGenerator::fontsForPage( int page ) { Okular::FontInfo::List list; if ( page != nextFontPage ) return list; QList fonts; userMutex()->lock(); Poppler::FontIterator* it = pdfdoc->newFontIterator(page); if (it->hasNext()) { fonts = it->next(); } userMutex()->unlock(); foreach (const Poppler::FontInfo &font, fonts) { Okular::FontInfo of; of.setName( font.name() ); of.setType( convertPopplerFontInfoTypeToOkularFontInfoType( font.type() ) ); of.setEmbedType( embedTypeForPopplerFontInfo( font) ); of.setFile( font.file() ); of.setCanBeExtracted( of.embedType() != Okular::FontInfo::NotEmbedded ); QVariant nativeId; nativeId.setValue( font ); of.setNativeId( nativeId ); list.append( of ); } ++nextFontPage; return list; } const QList *PDFGenerator::embeddedFiles() const { if (docEmbeddedFilesDirty) { userMutex()->lock(); const QList &popplerFiles = pdfdoc->embeddedFiles(); foreach(Poppler::EmbeddedFile* pef, popplerFiles) { docEmbeddedFiles.append(new PDFEmbeddedFile(pef)); } userMutex()->unlock(); docEmbeddedFilesDirty = false; } return &docEmbeddedFiles; } QAbstractItemModel* PDFGenerator::layersModel() const { return pdfdoc->hasOptionalContent() ? pdfdoc->optionalContentModel() : NULL; } void PDFGenerator::opaqueAction( const Okular::BackendOpaqueAction *action ) { #ifdef HAVE_POPPLER_0_50 const Poppler::LinkOCGState *popplerLink = action->nativeId().value(); pdfdoc->optionalContentModel()->applyLink( const_cast< Poppler::LinkOCGState* >( popplerLink ) ); #else (void)action; #endif } bool PDFGenerator::isAllowed( Okular::Permission permission ) const { bool b = true; switch ( permission ) { case Okular::AllowModify: b = pdfdoc->okToChange(); break; case Okular::AllowCopy: b = pdfdoc->okToCopy(); break; case Okular::AllowPrint: b = pdfdoc->okToPrint(); break; case Okular::AllowNotes: b = pdfdoc->okToAddNotes(); break; case Okular::AllowFillForms: b = pdfdoc->okToFillForm(); break; default: ; } return b; } #ifdef HAVE_POPPLER_0_62 struct RenderImagePayload { RenderImagePayload(PDFGenerator *g, Okular::PixmapRequest *r) : generator(g), request(r) { // Don't report partial updates for the first 500 ms timer.setInterval(500); timer.setSingleShot(true); timer.start(); } PDFGenerator *generator; Okular::PixmapRequest *request; QTimer timer; }; Q_DECLARE_METATYPE(RenderImagePayload*) static bool shouldDoPartialUpdateCallback(const QVariant &vPayload) { auto payload = vPayload.value(); // Since the timer lives in a thread without an event loop we need to stop it ourselves // when the remaining time has reached 0 if (payload->timer.isActive() && payload->timer.remainingTime() == 0) { payload->timer.stop(); } return !payload->timer.isActive(); } static void partialUpdateCallback(const QImage &image, const QVariant &vPayload) { auto payload = vPayload.value(); QMetaObject::invokeMethod(payload->generator, "signalPartialPixmapRequest", Qt::QueuedConnection, Q_ARG(Okular::PixmapRequest*, payload->request), Q_ARG(QImage, image)); } #endif #ifdef HAVE_POPPLER_0_63 static bool shouldAbortRenderCallback(const QVariant &vPayload) { auto payload = vPayload.value(); return payload->request->shouldAbortRender(); } #endif QImage PDFGenerator::image( Okular::PixmapRequest * request ) { // debug requests to this (xpdf) generator //qCDebug(OkularPdfDebug) << "id: " << request->id << " is requesting " << (request->async ? "ASYNC" : "sync") << " pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "]."; // compute dpi used to get an image with desired width and height Okular::Page * page = request->page(); double pageWidth = page->width(), pageHeight = page->height(); if ( page->rotation() % 2 ) qSwap( pageWidth, pageHeight ); qreal fakeDpiX = request->width() / pageWidth * dpi().width(); qreal fakeDpiY = request->height() / pageHeight * dpi().height(); // generate links rects only the first time bool genObjectRects = !rectsGenerated.at( page->number() ); // 0. LOCK [waits for the thread end] userMutex()->lock(); if ( request->shouldAbortRender() ) { userMutex()->unlock(); return QImage(); } // 1. Set OutputDev parameters and Generate contents // note: thread safety is set on 'false' for the GUI (this) thread Poppler::Page *p = pdfdoc->page(page->number()); // 2. Take data from outputdev and attach it to the Page QImage img; if (p) { #ifdef HAVE_POPPLER_0_63 if ( request->isTile() ) { const QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); if ( request->partialUpdatesWanted() ) { RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); } else { RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); } } else { if ( request->partialUpdatesWanted() ) { RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); } else { RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); } } #elif defined(HAVE_POPPLER_0_62) if ( request->isTile() ) { const QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); if ( request->partialUpdatesWanted() ) { RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, QVariant::fromValue( &payload ) ); } else { img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0 ); } } else { if ( request->partialUpdatesWanted() ) { RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, QVariant::fromValue( &payload ) ); } else { img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ); } } #else if ( request->isTile() ) { const QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0 ); } else { img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ); } #endif } else { img = QImage( request->width(), request->height(), QImage::Format_Mono ); img.fill( Qt::white ); } if ( p && genObjectRects ) { // TODO previously we extracted Image type rects too, but that needed porting to poppler // and as we are not doing anything with Image type rects i did not port it, have a look at // dead gp_outputdev.cpp on image extraction page->setObjectRects( generateLinks(p->links()) ); rectsGenerated[ request->page()->number() ] = true; resolveMediaLinkReferences( page ); } // 3. UNLOCK [re-enables shared access] userMutex()->unlock(); delete p; return img; } template void resolveMediaLinks( Okular::Action *action, enum Okular::Annotation::SubType subType, QHash &annotationsHash ) { OkularLinkType *okularAction = static_cast( action ); const PopplerLinkType *popplerLink = action->nativeId().value(); QHashIterator it( annotationsHash ); while ( it.hasNext() ) { it.next(); if ( it.key()->subType() == subType ) { const PopplerAnnotationType *popplerAnnotation = static_cast( it.value() ); if ( popplerLink->isReferencedAnnotation( popplerAnnotation ) ) { okularAction->setAnnotation( static_cast( it.key() ) ); okularAction->setNativeId( QVariant() ); delete popplerLink; // delete the associated Poppler::LinkMovie object, it's not needed anymore break; } } } } void PDFGenerator::resolveMediaLinkReference( Okular::Action *action ) { if ( !action ) return; if ( (action->actionType() != Okular::Action::Movie) && (action->actionType() != Okular::Action::Rendition) ) return; resolveMediaLinks( action, Okular::Annotation::AMovie, annotationsOnOpenHash ); resolveMediaLinks( action, Okular::Annotation::AScreen, annotationsOnOpenHash ); } void PDFGenerator::resolveMediaLinkReferences( Okular::Page *page ) { resolveMediaLinkReference( const_cast( page->pageAction( Okular::Page::Opening ) ) ); resolveMediaLinkReference( const_cast( page->pageAction( Okular::Page::Closing ) ) ); foreach ( Okular::Annotation *annotation, page->annotations() ) { if ( annotation->subType() == Okular::Annotation::AScreen ) { Okular::ScreenAnnotation *screenAnnotation = static_cast( annotation ); resolveMediaLinkReference( screenAnnotation->additionalAction( Okular::Annotation::PageOpening ) ); resolveMediaLinkReference( screenAnnotation->additionalAction( Okular::Annotation::PageClosing ) ); } if ( annotation->subType() == Okular::Annotation::AWidget ) { Okular::WidgetAnnotation *widgetAnnotation = static_cast( annotation ); resolveMediaLinkReference( widgetAnnotation->additionalAction( Okular::Annotation::PageOpening ) ); resolveMediaLinkReference( widgetAnnotation->additionalAction( Okular::Annotation::PageClosing ) ); } } foreach ( Okular::FormField *field, page->formFields() ) resolveMediaLinkReference( field->activationAction() ); } #ifdef HAVE_POPPLER_0_63 struct TextExtractionPayload { TextExtractionPayload(Okular::TextRequest *r) : request(r) { } Okular::TextRequest *request; }; Q_DECLARE_METATYPE(TextExtractionPayload*) static bool shouldAbortTextExtractionCallback(const QVariant &vPayload) { auto payload = vPayload.value(); return payload->request->shouldAbortExtraction(); } #endif Okular::TextPage* PDFGenerator::textPage( Okular::TextRequest *request ) { const Okular::Page *page = request->page(); #ifdef PDFGENERATOR_DEBUG qCDebug(OkularPdfDebug) << "page" << page->number(); #endif // build a TextList... QList textList; double pageWidth, pageHeight; userMutex()->lock(); Poppler::Page *pp = pdfdoc->page( page->number() ); if (pp) { #ifdef HAVE_POPPLER_0_63 TextExtractionPayload payload(request); textList = pp->textList( Poppler::Page::Rotate0, shouldAbortTextExtractionCallback, QVariant::fromValue( &payload ) ); #else textList = pp->textList(); #endif const QSizeF s = pp->pageSizeF(); pageWidth = s.width(); pageHeight = s.height(); } else { pageWidth = defaultPageWidth; pageHeight = defaultPageHeight; } delete pp; userMutex()->unlock(); if ( textList.isEmpty() && request->shouldAbortExtraction() ) return nullptr; Okular::TextPage *tp = abstractTextPage(textList, pageHeight, pageWidth, (Poppler::Page::Rotation)page->orientation()); qDeleteAll(textList); return tp; } void PDFGenerator::requestFontData(const Okular::FontInfo &font, QByteArray *data) { Poppler::FontInfo fi = font.nativeId().value(); *data = pdfdoc->fontData(fi); } #define DUMMY_QPRINTER_COPY bool PDFGenerator::print( QPrinter& printer ) { bool printAnnots = true; bool forceRasterize = false; if ( pdfOptionsPage ) { printAnnots = pdfOptionsPage->printAnnots(); forceRasterize = pdfOptionsPage->printForceRaster(); } #ifdef Q_OS_WIN // Windows can only print by rasterization, because that is // currently the only way Okular implements printing without using UNIX-specific // tools like 'lpr'. forceRasterize = true; #ifndef HAVE_POPPLER_0_60 // The Document::HideAnnotations flags was introduced in poppler 0.60 printAnnots = true; #endif #endif #ifdef HAVE_POPPLER_0_60 if ( forceRasterize ) { pdfdoc->setRenderHint(Poppler::Document::HideAnnotations, !printAnnots); #else if ( forceRasterize && printAnnots) { #endif QPainter painter; painter.begin(&printer); QList pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); for ( int i = 0; i < pageList.count(); ++i ) { if ( i != 0 ) printer.newPage(); const int page = pageList.at( i ) - 1; userMutex()->lock(); Poppler::Page *pp = pdfdoc->page( page ); if (pp) { #ifdef Q_OS_WIN QImage img = pp->renderToImage( printer.physicalDpiX(), printer.physicalDpiY() ); #else // UNIX: Same resolution as the postscript rasterizer; see discussion at https://git.reviewboard.kde.org/r/130218/ QImage img = pp->renderToImage( 300, 300 ); #endif painter.drawImage( painter.window(), img, QRectF(0, 0, img.width(), img.height()) ); delete pp; } userMutex()->unlock(); } painter.end(); return true; } #ifdef DUMMY_QPRINTER_COPY // Get the real page size to pass to the ps generator QPrinter dummy( QPrinter::PrinterResolution ); dummy.setFullPage( true ); dummy.setOrientation( printer.orientation() ); dummy.setPageSize( printer.pageSize() ); dummy.setPaperSize( printer.paperSize( QPrinter::Millimeter ), QPrinter::Millimeter ); int width = dummy.width(); int height = dummy.height(); #else int width = printer.width(); int height = printer.height(); #endif if (width <= 0 || height <= 0) { lastPrintError = InvalidPageSizePrintError; return false; } // Create the tempfile to send to FilePrinter, which will manage the deletion QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); if ( !tf.open() ) { lastPrintError = TemporaryFileOpenPrintError; return false; } QString tempfilename = tf.fileName(); // Generate the list of pages to be printed as selected in the print dialog QList pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); // TODO rotation tf.setAutoRemove(false); QString pstitle = metaData(QStringLiteral("Title"), QVariant()).toString(); if ( pstitle.trimmed().isEmpty() ) { pstitle = document()->currentDocument().fileName(); } Poppler::PSConverter *psConverter = pdfdoc->psConverter(); psConverter->setOutputDevice(&tf); psConverter->setPageList(pageList); psConverter->setPaperWidth(width); psConverter->setPaperHeight(height); psConverter->setRightMargin(0); psConverter->setBottomMargin(0); psConverter->setLeftMargin(0); psConverter->setTopMargin(0); psConverter->setStrictMargins(false); psConverter->setForceRasterize(forceRasterize); psConverter->setTitle(pstitle); if (!printAnnots) psConverter->setPSOptions(psConverter->psOptions() | Poppler::PSConverter::HideAnnotations ); userMutex()->lock(); if (psConverter->convert()) { userMutex()->unlock(); delete psConverter; tf.close(); int ret = Okular::FilePrinter::printFile( printer, tempfilename, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange() ); lastPrintError = Okular::FilePrinter::printError( ret ); return (lastPrintError == NoPrintError); } else { lastPrintError = FileConversionPrintError; delete psConverter; userMutex()->unlock(); } tf.close(); return false; } QVariant PDFGenerator::metaData( const QString & key, const QVariant & option ) const { if ( key == QLatin1String("StartFullScreen") ) { QMutexLocker ml(userMutex()); // asking for the 'start in fullscreen mode' (pdf property) if ( pdfdoc->pageMode() == Poppler::Document::FullScreen ) return true; } else if ( key == QLatin1String("NamedViewport") && !option.toString().isEmpty() ) { Okular::DocumentViewport viewport; QString optionString = option.toString(); // asking for the page related to a 'named link destination'. the // option is the link name. @see addSynopsisChildren. userMutex()->lock(); Poppler::LinkDestination *ld = pdfdoc->linkDestination( optionString ); userMutex()->unlock(); if ( ld ) { fillViewportFromLinkDestination( viewport, *ld ); } delete ld; if ( viewport.pageNumber >= 0 ) return viewport.toString(); } else if ( key == QLatin1String("DocumentTitle") ) { userMutex()->lock(); QString title = pdfdoc->info( QStringLiteral("Title") ); userMutex()->unlock(); return title; } else if ( key == QLatin1String("OpenTOC") ) { QMutexLocker ml(userMutex()); if ( pdfdoc->pageMode() == Poppler::Document::UseOutlines ) return true; } else if ( key == QLatin1String("DocumentScripts") && option.toString() == QLatin1String("JavaScript") ) { QMutexLocker ml(userMutex()); return pdfdoc->scripts(); } else if ( key == QLatin1String("HasUnsupportedXfaForm") ) { QMutexLocker ml(userMutex()); return pdfdoc->formType() == Poppler::Document::XfaForm; } else if ( key == QLatin1String("FormCalculateOrder") ) { #ifdef HAVE_POPPLER_0_53 QMutexLocker ml(userMutex()); return QVariant::fromValue>(pdfdoc->formCalculateOrder()); #endif } return QVariant(); } bool PDFGenerator::reparseConfig() { if ( !pdfdoc ) return false; bool somethingchanged = false; // load paper color QColor color = documentMetaData( PaperColorMetaData, true ).value< QColor >(); // if paper color is changed we have to rebuild every visible pixmap in addition // to the outputDevice. it's the 'heaviest' case, other effect are just recoloring // over the page rendered on 'standard' white background. if ( color != pdfdoc->paperColor() ) { userMutex()->lock(); pdfdoc->setPaperColor(color); userMutex()->unlock(); somethingchanged = true; } bool aaChanged = setDocumentRenderHints(); somethingchanged = somethingchanged || aaChanged; return somethingchanged; } void PDFGenerator::addPages( KConfigDialog *dlg ) { #ifdef HAVE_POPPLER_0_24 Ui_PDFSettingsWidget pdfsw; QWidget* w = new QWidget(dlg); pdfsw.setupUi(w); dlg->addPage(w, PDFSettings::self(), i18n("PDF"), QStringLiteral("application-pdf"), i18n("PDF Backend Configuration") ); #endif } bool PDFGenerator::setDocumentRenderHints() { bool changed = false; const Poppler::Document::RenderHints oldhints = pdfdoc->renderHints(); #define SET_HINT(hintname, hintdefvalue, hintflag) \ { \ bool newhint = documentMetaData(hintname, hintdefvalue).toBool(); \ if (newhint != oldhints.testFlag(hintflag)) \ { \ pdfdoc->setRenderHint(hintflag, newhint); \ changed = true; \ } \ } SET_HINT(GraphicsAntialiasMetaData, true, Poppler::Document::Antialiasing) SET_HINT(TextAntialiasMetaData, true, Poppler::Document::TextAntialiasing) SET_HINT(TextHintingMetaData, false, Poppler::Document::TextHinting) #undef SET_HINT #ifdef HAVE_POPPLER_0_24 // load thin line mode const int thinLineMode = PDFSettings::enhanceThinLines(); const bool enableThinLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Solid; const bool enableShapeLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Shape; const bool thinLineSolidWasEnabled = (oldhints & Poppler::Document::ThinLineSolid) == Poppler::Document::ThinLineSolid; const bool thinLineShapeWasEnabled = (oldhints & Poppler::Document::ThinLineShape) == Poppler::Document::ThinLineShape; if (enableThinLineSolid != thinLineSolidWasEnabled) { pdfdoc->setRenderHint(Poppler::Document::ThinLineSolid, enableThinLineSolid); changed = true; } if (enableShapeLineSolid != thinLineShapeWasEnabled) { pdfdoc->setRenderHint(Poppler::Document::ThinLineShape, enableShapeLineSolid); changed = true; } #endif return changed; } Okular::ExportFormat::List PDFGenerator::exportFormats() const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); } return formats; } bool PDFGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { if ( format.mimeType().inherits( QStringLiteral( "text/plain" ) ) ) { QFile f( fileName ); if ( !f.open( QIODevice::WriteOnly ) ) return false; QTextStream ts( &f ); int num = document()->pages(); for ( int i = 0; i < num; ++i ) { QString text; userMutex()->lock(); Poppler::Page *pp = pdfdoc->page(i); if (pp) { text = pp->text(QRect()).normalized(QString::NormalizationForm_KC); } userMutex()->unlock(); ts << text; delete pp; } f.close(); return true; } return false; } //END Generator inherited functions inline void append (Okular::TextPage* ktp, const QString &s, double l, double b, double r, double t) { // kWarning(PDFDebug).nospace() << "text: " << s << " at (" << l << "," << t << ")x(" << r <<","<append(s, new Okular::NormalizedRect(l, t, r, b)); } Okular::TextPage * PDFGenerator::abstractTextPage(const QList &text, double height, double width,int rot) { Q_UNUSED(rot); Okular::TextPage* ktp=new Okular::TextPage; Poppler::TextBox *next; #ifdef PDFGENERATOR_DEBUG qCDebug(OkularPdfDebug) << "getting text page in generator pdf - rotation:" << rot; #endif QString s; bool addChar; foreach (Poppler::TextBox *word, text) { const int qstringCharCount = word->text().length(); next=word->nextWord(); int textBoxChar = 0; for (int j = 0; j < qstringCharCount; j++) { const QChar c = word->text().at(j); if (c.isHighSurrogate()) { s = c; addChar = false; } else if (c.isLowSurrogate()) { s += c; addChar = true; } else { s = c; addChar = true; } if (addChar) { QRectF charBBox = word->charBoundingBox(textBoxChar); append(ktp, (j==qstringCharCount-1 && !next) ? (s + QLatin1Char('\n')) : s, charBBox.left()/width, charBBox.bottom()/height, charBBox.right()/width, charBBox.top()/height); textBoxChar++; } } if ( word->hasSpaceAfter() && next ) { // TODO Check with a document with vertical text // probably won't work and we will need to do comparisons // between wordBBox and nextWordBBox to see if they are // vertically or horizontally aligned QRectF wordBBox = word->boundingBox(); QRectF nextWordBBox = next->boundingBox(); append(ktp, QStringLiteral(" "), wordBBox.right()/width, wordBBox.bottom()/height, nextWordBBox.left()/width, wordBBox.top()/height); } } return ktp; } void PDFGenerator::addSynopsisChildren( QDomNode * parent, QDomNode * parentDestination ) { // keep track of the current listViewItem QDomNode n = parent->firstChild(); while( !n.isNull() ) { // convert the node to an element (sure it is) QDomElement e = n.toElement(); // The name is the same QDomElement item = docSyn.createElement( e.tagName() ); parentDestination->appendChild(item); if (!e.attribute(QStringLiteral("ExternalFileName")).isNull()) item.setAttribute(QStringLiteral("ExternalFileName"), e.attribute(QStringLiteral("ExternalFileName"))); if (!e.attribute(QStringLiteral("DestinationName")).isNull()) item.setAttribute(QStringLiteral("ViewportName"), e.attribute(QStringLiteral("DestinationName"))); if (!e.attribute(QStringLiteral("Destination")).isNull()) { Okular::DocumentViewport vp; fillViewportFromLinkDestination( vp, Poppler::LinkDestination(e.attribute(QStringLiteral("Destination"))) ); item.setAttribute( QStringLiteral("Viewport"), vp.toString() ); } if (!e.attribute(QStringLiteral("Open")).isNull()) item.setAttribute(QStringLiteral("Open"), e.attribute(QStringLiteral("Open"))); if (!e.attribute(QStringLiteral("DestinationURI")).isNull()) item.setAttribute(QStringLiteral("URL"), e.attribute(QStringLiteral("DestinationURI"))); // descend recursively and advance to the next node if ( e.hasChildNodes() ) addSynopsisChildren( &n, & item ); n = n.nextSibling(); } } void PDFGenerator::addAnnotations( Poppler::Page * popplerPage, Okular::Page * page ) { #ifdef HAVE_POPPLER_0_28 QSet subtypes; subtypes << Poppler::Annotation::AFileAttachment << Poppler::Annotation::ASound << Poppler::Annotation::AMovie << Poppler::Annotation::AWidget << Poppler::Annotation::AScreen << Poppler::Annotation::AText << Poppler::Annotation::ALine << Poppler::Annotation::AGeom << Poppler::Annotation::AHighlight << Poppler::Annotation::AInk << Poppler::Annotation::AStamp << Poppler::Annotation::ACaret; QList popplerAnnotations = popplerPage->annotations( subtypes ); #else QList popplerAnnotations = popplerPage->annotations(); #endif foreach(Poppler::Annotation *a, popplerAnnotations) { bool doDelete = true; Okular::Annotation * newann = createAnnotationFromPopplerAnnotation( a, &doDelete ); if (newann) { page->addAnnotation(newann); if ( a->subType() == Poppler::Annotation::AScreen ) { Poppler::ScreenAnnotation *annotScreen = static_cast( a ); Okular::ScreenAnnotation *screenAnnotation = static_cast( newann ); // The activation action const Poppler::Link *actionLink = annotScreen->action(); if ( actionLink ) screenAnnotation->setAction( createLinkFromPopplerLink( actionLink ) ); // The additional actions const Poppler::Link *pageOpeningLink = annotScreen->additionalAction( Poppler::Annotation::PageOpeningAction ); if ( pageOpeningLink ) screenAnnotation->setAdditionalAction( Okular::Annotation::PageOpening, createLinkFromPopplerLink( pageOpeningLink ) ); const Poppler::Link *pageClosingLink = annotScreen->additionalAction( Poppler::Annotation::PageClosingAction ); if ( pageClosingLink ) screenAnnotation->setAdditionalAction( Okular::Annotation::PageClosing, createLinkFromPopplerLink( pageClosingLink ) ); } if ( a->subType() == Poppler::Annotation::AWidget ) { Poppler::WidgetAnnotation *annotWidget = static_cast( a ); Okular::WidgetAnnotation *widgetAnnotation = static_cast( newann ); // The additional actions const Poppler::Link *pageOpeningLink = annotWidget->additionalAction( Poppler::Annotation::PageOpeningAction ); if ( pageOpeningLink ) widgetAnnotation->setAdditionalAction( Okular::Annotation::PageOpening, createLinkFromPopplerLink( pageOpeningLink ) ); const Poppler::Link *pageClosingLink = annotWidget->additionalAction( Poppler::Annotation::PageClosingAction ); if ( pageClosingLink ) widgetAnnotation->setAdditionalAction( Okular::Annotation::PageClosing, createLinkFromPopplerLink( pageClosingLink ) ); } if ( !doDelete ) annotationsOnOpenHash.insert( newann, a ); } if ( doDelete ) delete a; } } void PDFGenerator::addTransition( Poppler::Page * pdfPage, Okular::Page * page ) // called on opening when MUTEX is not used { Poppler::PageTransition *pdfTransition = pdfPage->transition(); if ( !pdfTransition || pdfTransition->type() == Poppler::PageTransition::Replace ) return; Okular::PageTransition *transition = new Okular::PageTransition(); switch ( pdfTransition->type() ) { case Poppler::PageTransition::Replace: // won't get here, added to avoid warning break; case Poppler::PageTransition::Split: transition->setType( Okular::PageTransition::Split ); break; case Poppler::PageTransition::Blinds: transition->setType( Okular::PageTransition::Blinds ); break; case Poppler::PageTransition::Box: transition->setType( Okular::PageTransition::Box ); break; case Poppler::PageTransition::Wipe: transition->setType( Okular::PageTransition::Wipe ); break; case Poppler::PageTransition::Dissolve: transition->setType( Okular::PageTransition::Dissolve ); break; case Poppler::PageTransition::Glitter: transition->setType( Okular::PageTransition::Glitter ); break; case Poppler::PageTransition::Fly: transition->setType( Okular::PageTransition::Fly ); break; case Poppler::PageTransition::Push: transition->setType( Okular::PageTransition::Push ); break; case Poppler::PageTransition::Cover: transition->setType( Okular::PageTransition::Cover ); break; case Poppler::PageTransition::Uncover: transition->setType( Okular::PageTransition::Uncover ); break; case Poppler::PageTransition::Fade: transition->setType( Okular::PageTransition::Fade ); break; } #ifdef HAVE_POPPLER_0_37 transition->setDuration( pdfTransition->durationReal() ); #else transition->setDuration( pdfTransition->duration() ); #endif switch ( pdfTransition->alignment() ) { case Poppler::PageTransition::Horizontal: transition->setAlignment( Okular::PageTransition::Horizontal ); break; case Poppler::PageTransition::Vertical: transition->setAlignment( Okular::PageTransition::Vertical ); break; } switch ( pdfTransition->direction() ) { case Poppler::PageTransition::Inward: transition->setDirection( Okular::PageTransition::Inward ); break; case Poppler::PageTransition::Outward: transition->setDirection( Okular::PageTransition::Outward ); break; } transition->setAngle( pdfTransition->angle() ); transition->setScale( pdfTransition->scale() ); transition->setIsRectangular( pdfTransition->isRectangular() ); page->setTransition( transition ); } void PDFGenerator::addFormFields( Poppler::Page * popplerPage, Okular::Page * page ) { QList popplerFormFields = popplerPage->formFields(); QLinkedList okularFormFields; foreach( Poppler::FormField *f, popplerFormFields ) { Okular::FormField * of = 0; switch ( f->type() ) { case Poppler::FormField::FormButton: of = new PopplerFormFieldButton( static_cast( f ) ); break; case Poppler::FormField::FormText: of = new PopplerFormFieldText( static_cast( f ) ); break; case Poppler::FormField::FormChoice: of = new PopplerFormFieldChoice( static_cast( f ) ); break; default: ; } if ( of ) // form field created, good - it will take care of the Poppler::FormField okularFormFields.append( of ); else // no form field available - delete the Poppler::FormField delete f; } if ( !okularFormFields.isEmpty() ) page->setFormFields( okularFormFields ); } PDFGenerator::PrintError PDFGenerator::printError() const { return lastPrintError; } QWidget* PDFGenerator::printConfigurationWidget() const { if ( !pdfOptionsPage ) { const_cast(this)->pdfOptionsPage = new PDFOptionsPage(); } return pdfOptionsPage; } bool PDFGenerator::supportsOption( SaveOption option ) const { switch ( option ) { case SaveChanges: { return true; } default: ; } return false; } bool PDFGenerator::save( const QString &fileName, SaveOptions options, QString *errorText ) { Q_UNUSED(errorText); Poppler::PDFConverter *pdfConv = pdfdoc->pdfConverter(); pdfConv->setOutputFileName( fileName ); if ( options & SaveChanges ) pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges ); QMutexLocker locker( userMutex() ); QHashIterator it( annotationsOnOpenHash ); while ( it.hasNext() ) { it.next(); if ( it.value()->uniqueName().isEmpty() ) { it.value()->setUniqueName( it.key()->uniqueName() ); } } bool success = pdfConv->convert(); if (!success) { switch (pdfConv->lastError()) { case Poppler::BaseConverter::NotSupportedInputFileError: // This can only happen with Poppler before 0.22 which did not have qt5 version break; case Poppler::BaseConverter::NoError: case Poppler::BaseConverter::FileLockedError: // we can't get here break; case Poppler::BaseConverter::OpenOutputError: // the default text message is good for this case break; } } delete pdfConv; return success; } Okular::AnnotationProxy* PDFGenerator::annotationProxy() const { return annotProxy; } #include "generator_pdf.moc" Q_LOGGING_CATEGORY(OkularPdfDebug, "org.kde.okular.generators.pdf", QtWarningMsg) /* kate: replace-tabs on; indent-width 4; */ diff --git a/generators/spectre/generator_ghostview.cpp b/generators/spectre/generator_ghostview.cpp index b990c699e..866c738d9 100644 --- a/generators/spectre/generator_ghostview.cpp +++ b/generators/spectre/generator_ghostview.cpp @@ -1,308 +1,308 @@ /*************************************************************************** * Copyright (C) 2007-2008 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. * ***************************************************************************/ #include "generator_ghostview.h" #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 "ui_gssettingswidget.h" #include "gssettings.h" #include "spectre_debug.h" #include "rendererthread.h" OKULAR_EXPORT_PLUGIN(GSGenerator, "libokularGenerator_ghostview.json") GSGenerator::GSGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ), m_internalDocument(0), m_request(0) { setFeature( PrintPostscript ); setFeature( PrintToFile ); GSRendererThread *renderer = GSRendererThread::getCreateRenderer(); if (!renderer->isRunning()) renderer->start(); connect(renderer, &GSRendererThread::imageDone, this, &GSGenerator::slotImageGenerated, Qt::QueuedConnection); } GSGenerator::~GSGenerator() { } bool GSGenerator::reparseConfig() { bool changed = false; if (m_internalDocument) { #define SET_HINT(hintname, hintdefvalue, hintvar) \ { \ bool newhint = documentMetaData(hintname, hintdefvalue).toBool(); \ if (newhint != cache_##hintvar) \ { \ cache_##hintvar = newhint; \ changed = true; \ } \ } SET_HINT(GraphicsAntialiasMetaData, true, AAgfx) SET_HINT(TextAntialiasMetaData, true, AAtext) #undef SET_HINT } return changed; } void GSGenerator::addPages( KConfigDialog *dlg ) { Ui_GSSettingsWidget gsw; QWidget* w = new QWidget(dlg); gsw.setupUi(w); dlg->addPage(w, GSSettings::self(), i18n("Ghostscript"), QStringLiteral("okular-gv"), i18n("Ghostscript Backend Configuration") ); } bool GSGenerator::print( QPrinter& printer ) { bool result = false; // Create tempfile to write to QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); // Get list of pages to print QList pageList = Okular::FilePrinter::pageList( printer, spectre_document_get_n_pages( m_internalDocument ), document()->currentPage() + 1, document()->bookmarkedPageList() ); // Default to Postscript export, but if printing to PDF use that instead SpectreExporterFormat exportFormat = SPECTRE_EXPORTER_FORMAT_PS; if ( printer.outputFileName().right(3) == QLatin1String("pdf") ) { exportFormat = SPECTRE_EXPORTER_FORMAT_PDF; tf.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX.pdf")); } if ( !tf.open() ) return false; SpectreExporter *exporter = spectre_exporter_new( m_internalDocument, exportFormat ); SpectreStatus exportStatus = spectre_exporter_begin( exporter, tf.fileName().toLatin1().constData() ); int i = 0; while ( i < pageList.count() && exportStatus == SPECTRE_STATUS_SUCCESS ) { exportStatus = spectre_exporter_do_page( exporter, pageList.at( i ) - 1 ); i++; } SpectreStatus endStatus = SPECTRE_STATUS_EXPORTER_ERROR; if (exportStatus == SPECTRE_STATUS_SUCCESS) endStatus = spectre_exporter_end( exporter ); spectre_exporter_free( exporter ); const QString fileName = tf.fileName(); tf.close(); if ( exportStatus == SPECTRE_STATUS_SUCCESS && endStatus == SPECTRE_STATUS_SUCCESS ) { tf.setAutoRemove( false ); int ret = Okular::FilePrinter::printFile( printer, fileName, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange() ); if ( ret >= 0 ) result = true; } return result; } bool GSGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector ) { cache_AAtext = documentMetaData(TextAntialiasMetaData, true).toBool(); cache_AAgfx = documentMetaData(GraphicsAntialiasMetaData, true).toBool(); m_internalDocument = spectre_document_new(); spectre_document_load(m_internalDocument, QFile::encodeName(fileName).constData()); const SpectreStatus loadStatus = spectre_document_status(m_internalDocument); if (loadStatus != SPECTRE_STATUS_SUCCESS) { qCDebug(OkularSpectreDebug) << "ERR:" << spectre_status_to_string(loadStatus); spectre_document_free(m_internalDocument); m_internalDocument = 0; return false; } pagesVector.resize( spectre_document_get_n_pages(m_internalDocument) ); qCDebug(OkularSpectreDebug) << "Page count:" << pagesVector.count(); return loadPages(pagesVector); } bool GSGenerator::doCloseDocument() { spectre_document_free(m_internalDocument); m_internalDocument = 0; return true; } void GSGenerator::slotImageGenerated(QImage *img, Okular::PixmapRequest *request) { // This can happen as GSInterpreterCMD is a singleton and on creation signals all the slots // of all the generators attached to it if (request != m_request) return; if ( !request->page()->isBoundingBoxKnown() ) updatePageBoundingBox( request->page()->number(), Okular::Utils::imageBoundingBox( img ) ); m_request = 0; QPixmap *pix = new QPixmap(QPixmap::fromImage(*img)); delete img; request->page()->setPixmap( request->observer(), pix ); signalPixmapRequestDone( request ); } bool GSGenerator::loadPages( QVector< Okular::Page * > & pagesVector ) { for (uint i = 0; i < spectre_document_get_n_pages(m_internalDocument); i++) { SpectrePage *page; int width = 0, height = 0; SpectreOrientation pageOrientation = SPECTRE_ORIENTATION_PORTRAIT; page = spectre_document_get_page (m_internalDocument, i); if (spectre_document_status (m_internalDocument)) { qCDebug(OkularSpectreDebug) << "Error getting page" << i << spectre_status_to_string(spectre_document_status(m_internalDocument)); } else { spectre_page_get_size(page, &width, &height); pageOrientation = spectre_page_get_orientation(page); } spectre_page_free(page); if (pageOrientation % 2 == 1) qSwap(width, height); pagesVector[i] = new Okular::Page(i, width, height, orientation(pageOrientation)); } return pagesVector.count() > 0; } void GSGenerator::generatePixmap( Okular::PixmapRequest * req ) { qCDebug(OkularSpectreDebug) << "receiving" << *req; SpectrePage *page = spectre_document_get_page(m_internalDocument, req->pageNumber()); GSRendererThread *renderer = GSRendererThread::getCreateRenderer(); GSRendererThreadRequest gsreq(this); gsreq.spectrePage = page; gsreq.platformFonts = GSSettings::platformFonts(); int graphicsAA = 1; int textAA = 1; if (cache_AAgfx) graphicsAA = 4; if (cache_AAtext) textAA = 4; gsreq.textAAbits = textAA; gsreq.graphicsAAbits = graphicsAA; gsreq.orientation = req->page()->orientation(); if (req->page()->rotation() == Okular::Rotation90 || req->page()->rotation() == Okular::Rotation270) { gsreq.magnify = qMax( (double)req->height() / req->page()->width(), (double)req->width() / req->page()->height() ); } else { gsreq.magnify = qMax( (double)req->width() / req->page()->width(), (double)req->height() / req->page()->height() ); } gsreq.request = req; m_request = req; renderer->addRequest(gsreq); } bool GSGenerator::canGeneratePixmap() const { return !m_request; } Okular::DocumentInfo GSGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; if ( keys.contains( Okular::DocumentInfo::Title ) ) docInfo.set( Okular::DocumentInfo::Title, spectre_document_get_title(m_internalDocument) ); if ( keys.contains( Okular::DocumentInfo::Author ) ) docInfo.set( Okular::DocumentInfo::Author, spectre_document_get_for(m_internalDocument) ); if ( keys.contains( Okular::DocumentInfo::Creator ) ) docInfo.set( Okular::DocumentInfo::Creator, spectre_document_get_creator(m_internalDocument) ); if ( keys.contains( Okular::DocumentInfo::CreationDate ) ) docInfo.set( Okular::DocumentInfo::CreationDate, spectre_document_get_creation_date(m_internalDocument) ); if ( keys.contains( Okular::DocumentInfo::CustomKeys ) ) docInfo.set( QStringLiteral("dscversion"), spectre_document_get_format(m_internalDocument), i18n("Document version") ); if ( keys.contains( Okular::DocumentInfo::MimeType ) ) { int languageLevel = spectre_document_get_language_level(m_internalDocument); if (languageLevel > 0) docInfo.set( QStringLiteral("langlevel"), QString::number(languageLevel), i18n("Language Level") ); if (spectre_document_is_eps(m_internalDocument)) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("image/x-eps") ); else docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/postscript") ); } if ( keys.contains( Okular::DocumentInfo::Pages ) ) docInfo.set( Okular::DocumentInfo::Pages, QString::number(spectre_document_get_n_pages(m_internalDocument)) ); return docInfo; } Okular::Rotation GSGenerator::orientation(SpectreOrientation pageOrientation) const { switch (pageOrientation) { case SPECTRE_ORIENTATION_PORTRAIT: return Okular::Rotation0; case SPECTRE_ORIENTATION_LANDSCAPE: return Okular::Rotation90; case SPECTRE_ORIENTATION_REVERSE_PORTRAIT: return Okular::Rotation180; case SPECTRE_ORIENTATION_REVERSE_LANDSCAPE: return Okular::Rotation270; } // get rid of warnings, should never happen return Okular::Rotation0; } QVariant GSGenerator::metaData(const QString &key, const QVariant &option) const { Q_UNUSED(option) if (key == QLatin1String("DocumentTitle")) { const char *title = spectre_document_get_title(m_internalDocument); if (title) return QString::fromLatin1(title); } return QVariant(); } #include "generator_ghostview.moc" diff --git a/generators/tiff/generator_tiff.cpp b/generators/tiff/generator_tiff.cpp index 27a921f20..9f122dbc7 100644 --- a/generators/tiff/generator_tiff.cpp +++ b/generators/tiff/generator_tiff.cpp @@ -1,439 +1,439 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 "generator_tiff.h" #include #include #include #include #include #include #include -#include +#include #include -#include +#include #include #include #include #include #include #include #include #define TiffDebug 4714 tsize_t okular_tiffReadProc( thandle_t handle, tdata_t buf, tsize_t size ) { QIODevice * device = static_cast< QIODevice * >( handle ); return device->isReadable() ? device->read( static_cast< char * >( buf ), size ) : -1; } tsize_t okular_tiffWriteProc( thandle_t handle, tdata_t buf, tsize_t size ) { QIODevice * device = static_cast< QIODevice * >( handle ); return device->write( static_cast< char * >( buf ), size ); } toff_t okular_tiffSeekProc( thandle_t handle, toff_t offset, int whence ) { QIODevice * device = static_cast< QIODevice * >( handle ); switch ( whence ) { case SEEK_SET: device->seek( offset ); break; case SEEK_CUR: device->seek( device->pos() + offset ); break; case SEEK_END: device->seek( device->size() + offset ); break; } return device->pos(); } int okular_tiffCloseProc( thandle_t handle ) { Q_UNUSED( handle ) return 0; } toff_t okular_tiffSizeProc( thandle_t handle ) { QIODevice * device = static_cast< QIODevice * >( handle ); return device->size(); } int okular_tiffMapProc( thandle_t, tdata_t *, toff_t * ) { return 0; } void okular_tiffUnmapProc( thandle_t, tdata_t, toff_t ) { } class TIFFGenerator::Private { public: Private() : tiff( nullptr ), dev( nullptr ) {} TIFF* tiff; QByteArray data; QIODevice* dev; }; static QDateTime convertTIFFDateTime( const char* tiffdate ) { if ( !tiffdate ) return QDateTime(); return QDateTime::fromString( QString::fromLatin1( tiffdate ), QStringLiteral("yyyy:MM:dd HH:mm:ss") ); } static void adaptSizeToResolution( TIFF *tiff, ttag_t whichres, double dpi, uint32 *size ) { float resvalue = 1.0; uint16 resunit = 0; if ( !TIFFGetField( tiff, whichres, &resvalue ) || !TIFFGetFieldDefaulted( tiff, TIFFTAG_RESOLUTIONUNIT, &resunit ) ) return; float newsize = *size / resvalue; switch ( resunit ) { case RESUNIT_INCH: *size = (uint32)( newsize * dpi ); break; case RESUNIT_CENTIMETER: *size = (uint32)( newsize * 10.0 / 25.4 * dpi ); break; case RESUNIT_NONE: break; } } static Okular::Rotation readTiffRotation( TIFF *tiff ) { uint32 tiffOrientation = 0; if ( !TIFFGetField( tiff, TIFFTAG_ORIENTATION, &tiffOrientation ) ) return Okular::Rotation0; Okular::Rotation ret = Okular::Rotation0; switch ( tiffOrientation ) { case ORIENTATION_TOPLEFT: case ORIENTATION_TOPRIGHT: ret = Okular::Rotation0; break; case ORIENTATION_BOTRIGHT: case ORIENTATION_BOTLEFT: ret = Okular::Rotation180; break; case ORIENTATION_LEFTTOP: case ORIENTATION_LEFTBOT: ret = Okular::Rotation270; break; case ORIENTATION_RIGHTTOP: case ORIENTATION_RIGHTBOT: ret = Okular::Rotation90; break; } return ret; } OKULAR_EXPORT_PLUGIN(TIFFGenerator, "libokularGenerator_tiff.json") TIFFGenerator::TIFFGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ), d( new Private ) { setFeature( Threaded ); setFeature( PrintNative ); setFeature( PrintToFile ); setFeature( ReadRawData ); } TIFFGenerator::~TIFFGenerator() { if ( d->tiff ) { TIFFClose( d->tiff ); d->tiff = nullptr; } delete d; } bool TIFFGenerator::loadDocument( const QString & fileName, QVector & pagesVector ) { QFile* qfile = new QFile( fileName ); qfile->open( QIODevice::ReadOnly ); d->dev = qfile; d->data = QFile::encodeName( QFileInfo( *qfile ).fileName() ); return loadTiff( pagesVector, d->data.constData() ); } bool TIFFGenerator::loadDocumentFromData( const QByteArray & fileData, QVector< Okular::Page * > & pagesVector ) { d->data = fileData; QBuffer* qbuffer = new QBuffer( &d->data ); qbuffer->open( QIODevice::ReadOnly ); d->dev = qbuffer; return loadTiff( pagesVector, "" ); } bool TIFFGenerator::loadTiff( QVector< Okular::Page * > & pagesVector, const char *name ) { d->tiff = TIFFClientOpen( name, "r", d->dev, okular_tiffReadProc, okular_tiffWriteProc, okular_tiffSeekProc, okular_tiffCloseProc, okular_tiffSizeProc, okular_tiffMapProc, okular_tiffUnmapProc ); if ( !d->tiff ) { delete d->dev; d->dev = nullptr; d->data.clear(); return false; } loadPages( pagesVector ); return true; } bool TIFFGenerator::doCloseDocument() { // closing the old document if ( d->tiff ) { TIFFClose( d->tiff ); d->tiff = nullptr; delete d->dev; d->dev = nullptr; d->data.clear(); m_pageMapping.clear(); } return true; } QImage TIFFGenerator::image( Okular::PixmapRequest * request ) { bool generated = false; QImage img; if ( TIFFSetDirectory( d->tiff, mapPage( request->page()->number() ) ) ) { int rotation = request->page()->rotation(); uint32 width = 1; uint32 height = 1; uint32 orientation = 0; TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ); TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ); if ( !TIFFGetField( d->tiff, TIFFTAG_ORIENTATION, &orientation ) ) orientation = ORIENTATION_TOPLEFT; QImage image( width, height, QImage::Format_RGB32 ); uint32 * data = (uint32 *)image.bits(); // read data if ( TIFFReadRGBAImageOriented( d->tiff, width, height, data, orientation ) != 0 ) { // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue uint32 size = width * height; for ( uint32 i = 0; i < size; ++i ) { uint32 red = ( data[i] & 0x00FF0000 ) >> 16; uint32 blue = ( data[i] & 0x000000FF ) << 16; data[i] = ( data[i] & 0xFF00FF00 ) + red + blue; } int reqwidth = request->width(); int reqheight = request->height(); if ( rotation % 2 == 1 ) qSwap( reqwidth, reqheight ); img = image.scaled( reqwidth, reqheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); generated = true; } } if ( !generated ) { img = QImage( request->width(), request->height(), QImage::Format_RGB32 ); img.fill( qRgb( 255, 255, 255 ) ); } return img; } Okular::DocumentInfo TIFFGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; if ( d->tiff ) { if ( keys.contains( Okular::DocumentInfo::MimeType ) ) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("image/tiff") ); if ( keys.contains( Okular::DocumentInfo::Description ) ) { char* buffer = nullptr; TIFFGetField( d->tiff, TIFFTAG_IMAGEDESCRIPTION, &buffer ); docInfo.set( Okular::DocumentInfo::Description, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::Producer ) ) { char* buffer = nullptr; TIFFGetField( d->tiff, TIFFTAG_SOFTWARE, &buffer ); docInfo.set( Okular::DocumentInfo::Producer, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::Copyright ) ) { char* buffer = nullptr; TIFFGetField( d->tiff, TIFFTAG_COPYRIGHT, &buffer ); docInfo.set( Okular::DocumentInfo::Copyright, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::Author ) ) { char* buffer = nullptr; TIFFGetField( d->tiff, TIFFTAG_ARTIST, &buffer ); docInfo.set( Okular::DocumentInfo::Author, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::CreationDate ) ) { char* buffer = nullptr; TIFFGetField( d->tiff, TIFFTAG_DATETIME, &buffer ); QDateTime date = convertTIFFDateTime( buffer ); docInfo.set( Okular::DocumentInfo::CreationDate, date.isValid() ? QLocale().toString( date, QLocale::LongFormat ) : QString() ); } } return docInfo; } void TIFFGenerator::loadPages( QVector & pagesVector ) { if ( !d->tiff ) return; tdir_t dirs = TIFFNumberOfDirectories( d->tiff ); pagesVector.resize( dirs ); tdir_t realdirs = 0; uint32 width = 0; uint32 height = 0; const QSizeF dpi = Okular::Utils::realDpi(nullptr); for ( tdir_t i = 0; i < dirs; ++i ) { if ( !TIFFSetDirectory( d->tiff, i ) ) continue; if ( TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ) != 1 || TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ) != 1 ) continue; adaptSizeToResolution( d->tiff, TIFFTAG_XRESOLUTION, dpi.width(), &width ); adaptSizeToResolution( d->tiff, TIFFTAG_YRESOLUTION, dpi.height(), &height ); Okular::Page * page = new Okular::Page( realdirs, width, height, readTiffRotation( d->tiff ) ); pagesVector[ realdirs ] = page; m_pageMapping[ realdirs ] = i; ++realdirs; } pagesVector.resize( realdirs ); } bool TIFFGenerator::print( QPrinter& printer ) { uint32 width = 0; uint32 height = 0; QPainter p( &printer ); QList pageList = Okular::FilePrinter::pageList( printer, document()->pages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); for ( tdir_t i = 0; i < pageList.count(); ++i ) { if ( !TIFFSetDirectory( d->tiff, mapPage( pageList[i] - 1 ) ) ) continue; if ( TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ) != 1 || TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ) != 1 ) continue; QImage image( width, height, QImage::Format_RGB32 ); uint32 * data = (uint32 *)image.bits(); // read data if ( TIFFReadRGBAImageOriented( d->tiff, width, height, data, ORIENTATION_TOPLEFT ) != 0 ) { // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue uint32 size = width * height; for ( uint32 i = 0; i < size; ++i ) { uint32 red = ( data[i] & 0x00FF0000 ) >> 16; uint32 blue = ( data[i] & 0x000000FF ) << 16; data[i] = ( data[i] & 0xFF00FF00 ) + red + blue; } } if ( i != 0 ) printer.newPage(); QSize targetSize = printer.pageRect().size(); if ( (image.width() < targetSize.width()) && (image.height() < targetSize.height()) ) { // draw small images at 100% (don't scale up) p.drawImage( 0, 0, image ); } else { // fit to page p.drawImage( 0, 0, image.scaled( targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); } } return true; } int TIFFGenerator::mapPage( int page ) const { QHash< int, int >::const_iterator it = m_pageMapping.find( page ); if ( it == m_pageMapping.end() ) { qCWarning(OkularTiffDebug) << "Requesting unmapped page" << page << ":" << m_pageMapping; return -1; } return it.value(); } Q_LOGGING_CATEGORY(OkularTiffDebug, "org.kde.okular.generators.tiff", QtWarningMsg) #include "generator_tiff.moc" diff --git a/generators/tiff/generator_tiff.h b/generators/tiff/generator_tiff.h index d867b2b6f..6302c5d70 100644 --- a/generators/tiff/generator_tiff.h +++ b/generators/tiff/generator_tiff.h @@ -1,50 +1,50 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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_GENERATOR_TIFF_H_ #define _OKULAR_GENERATOR_TIFF_H_ #include -#include -#include +#include +#include class TIFFGenerator : public Okular::Generator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: TIFFGenerator( QObject *parent, const QVariantList &args ); virtual ~TIFFGenerator(); bool loadDocument( const QString & fileName, QVector & pagesVector ) override; bool loadDocumentFromData( const QByteArray & fileData, QVector< Okular::Page * > & pagesVector ) override; Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override; bool print( QPrinter& printer ) override; protected: bool doCloseDocument() override; QImage image( Okular::PixmapRequest * request ) override; private: class Private; Private * const d; bool loadTiff( QVector< Okular::Page * > & pagesVector, const char *name ); void loadPages( QVector & pagesVector ); int mapPage( int page ) const; QHash< int, int > m_pageMapping; }; Q_DECLARE_LOGGING_CATEGORY(OkularTiffDebug) #endif diff --git a/generators/txt/converter.cpp b/generators/txt/converter.cpp index 1dad8daf0..77075c46f 100644 --- a/generators/txt/converter.cpp +++ b/generators/txt/converter.cpp @@ -1,41 +1,41 @@ /*************************************************************************** * Copyright (C) 2013 by Azat Khuzhin * * * * 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 "converter.h" -#include +#include -#include "converter.h" #include "document.h" using namespace Txt; Converter::Converter() { } Converter::~Converter() { } QTextDocument* Converter::convert( const QString &fileName ) { Document *textDocument = new Document( fileName ); textDocument->setPageSize(QSizeF( 600, 800 )); QTextFrameFormat frameFormat; frameFormat.setMargin( 20 ); QTextFrame *rootFrame = textDocument->rootFrame(); rootFrame->setFrameFormat( frameFormat ); emit addMetaData( Okular::DocumentInfo::MimeType, QStringLiteral("text/plain") ); return textDocument; } diff --git a/generators/txt/debug_txt.h b/generators/txt/debug_txt.h index 81b36f1b3..f0f21ca80 100644 --- a/generators/txt/debug_txt.h +++ b/generators/txt/debug_txt.h @@ -1,18 +1,18 @@ /*************************************************************************** * Copyright (C) 2006 by Luigi Toscano * * Copyright (C) 2014 by Frederik Gladhorn * * * * 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_TXT_DEBUG_P_H -#define OKULAR_TXT_DEBUG_P_H +#ifndef OKULAR_DEBUG_TXT_H +#define OKULAR_DEBUG_TXT_H -#include +#include Q_DECLARE_LOGGING_CATEGORY(OkularTxtDebug) #endif diff --git a/generators/txt/document.cpp b/generators/txt/document.cpp index 0bf5a256e..ee3f8240d 100644 --- a/generators/txt/document.cpp +++ b/generators/txt/document.cpp @@ -1,74 +1,74 @@ /*************************************************************************** * Copyright (C) 2013 by Azat Khuzhin * * * * 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 "document.h" #include #include #include #include -#include +#include -#include "document.h" #include "debug_txt.h" using namespace Txt; Document::Document( const QString &fileName ) { #ifdef TXT_DEBUG qCDebug(OkularTxtDebug) << "Opening file" << fileName; #endif QFile plainFile( fileName ); if ( !plainFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) { qCDebug(OkularTxtDebug) << "Can't open file" << plainFile.fileName(); return; } const QByteArray buffer = plainFile.readAll(); setPlainText( toUnicode(buffer) ); } Document::~Document() { } QString Document::toUnicode( const QByteArray &array ) { QByteArray encoding; KEncodingProber prober(KEncodingProber::Universal); int charsFeeded = 0; int chunkSize = 3000; // ~= number of symbols in page. // Try to detect encoding. while ( encoding.isEmpty() && charsFeeded < array.size() ) { prober.feed( array.mid( charsFeeded, chunkSize ) ); charsFeeded += chunkSize; if (prober.confidence() >= 0.5) { encoding = prober.encoding(); break; } } if ( encoding.isEmpty() ) { return QString(); } qCDebug(OkularTxtDebug) << "Detected" << prober.encoding() << "encoding" << "based on" << charsFeeded << "chars"; return QTextCodec::codecForName( encoding )->toUnicode( array ); } Q_LOGGING_CATEGORY(OkularTxtDebug, "org.kde.okular.generators.txt", QtWarningMsg) diff --git a/generators/txt/document.h b/generators/txt/document.h index 1c3d7a892..adfcbab52 100644 --- a/generators/txt/document.h +++ b/generators/txt/document.h @@ -1,26 +1,30 @@ /*************************************************************************** * Copyright (C) 2013 by Azat Khuzhin * * * * 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 _TXT_DOCUMENT_H_ +#define _TXT_DOCUMENT_H_ -#include +#include namespace Txt { class Document : public QTextDocument { Q_OBJECT public: Document( const QString &fileName ); ~Document(); private: QString toUnicode( const QByteArray &array ); }; } + +#endif diff --git a/generators/txt/generator_txt.h b/generators/txt/generator_txt.h index 9bfa7aac2..558ed1253 100644 --- a/generators/txt/generator_txt.h +++ b/generators/txt/generator_txt.h @@ -1,29 +1,29 @@ /*************************************************************************** * Copyright (C) 2013 by Azat Khuzhin * * * * 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 _TXT_GENERATOR_H_ -#define _TXT_GENERATOR_H_ +#ifndef _GENERATOR_TXT_H_ +#define _GENERATOR_TXT_H_ #include class TxtGenerator : public Okular::TextDocumentGenerator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: TxtGenerator(QObject *parent, const QVariantList &args); ~TxtGenerator() {} void addPages( KConfigDialog* dlg ) override; }; #endif diff --git a/generators/xps/generator_xps.h b/generators/xps/generator_xps.h index 6097dd1fe..4b99437aa 100644 --- a/generators/xps/generator_xps.h +++ b/generators/xps/generator_xps.h @@ -1,331 +1,331 @@ /* Copyright (C) 2006 Brad Hards This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _OKULAR_GENERATOR_XPS_H_ #define _OKULAR_GENERATOR_XPS_H_ #include #include #include #include #include #include #include #include #include #include -#include +#include #include typedef enum {abtCommand, abtNumber, abtComma, abtEOF} AbbPathTokenType; class AbbPathToken { public: QString data; int curPos; AbbPathTokenType type; char command; double number; }; /** Holds information about xml element during SAX parsing of page */ class XpsRenderNode { public: QString name; QVector children; QXmlAttributes attributes; QVariant data; XpsRenderNode * findChild( const QString &name ); QVariant getRequiredChildData( const QString &name ); QVariant getChildData( const QString &name ); }; struct XpsGradient { XpsGradient( double o, const QColor &c ) : offset( o ), color( c ) {} double offset; QColor color; }; /** Types of data in XpsRenderNode::data. Name of each type consist of Xps and name of xml element which data it holds */ typedef QTransform XpsMatrixTransform; typedef QTransform XpsRenderTransform; typedef QBrush XpsFill; struct XpsPathFigure { XpsPathFigure( const QPainterPath &_path, bool filled ) : path( _path ), isFilled( filled ) {} QPainterPath path; bool isFilled; }; struct XpsPathGeometry { XpsPathGeometry() : fillRule( Qt::OddEvenFill ) {} ~XpsPathGeometry() { qDeleteAll( paths ); } QList< XpsPathFigure* > paths; Qt::FillRule fillRule; XpsMatrixTransform transform; }; class XpsPage; class XpsFile; class XpsHandler: public QXmlDefaultHandler { public: XpsHandler( XpsPage *page ); ~XpsHandler(); bool startElement( const QString & nameSpace, const QString & localName, const QString & qname, const QXmlAttributes & atts ) override; bool endElement( const QString & nameSpace, const QString & localName, const QString & qname ) override; bool startDocument() override; protected: XpsPage *m_page; void processStartElement( XpsRenderNode &node ); void processEndElement( XpsRenderNode &node ); // Methods for processing of diferent xml elements void processGlyph( XpsRenderNode &node ); void processPath( XpsRenderNode &node ); void processPathData( XpsRenderNode &node ); void processFill( XpsRenderNode &node ); void processStroke( XpsRenderNode &node ); void processImageBrush (XpsRenderNode &node ); void processPathGeometry( XpsRenderNode &node ); void processPathFigure( XpsRenderNode &node ); QPainter *m_painter; QImage m_image; QStack m_nodes; friend class XpsPage; }; class XpsPage { public: XpsPage(XpsFile *file, const QString &fileName); ~XpsPage(); QSizeF size() const; bool renderToImage( QImage *p ); bool renderToPainter( QPainter *painter ); Okular::TextPage* textPage(); QImage loadImageFromFile( const QString &filename ); QString fileName() const { return m_fileName; } private: XpsFile *m_file; const QString m_fileName; QSizeF m_pageSize; QString m_thumbnailFileName; bool m_thumbnailMightBeAvailable; QImage m_thumbnail; bool m_thumbnailIsLoaded; QImage *m_pageImage; bool m_pageIsRendered; friend class XpsHandler; friend class XpsTextExtractionHandler; }; /** Represents one of the (perhaps the only) documents in an XpsFile */ class XpsDocument { public: XpsDocument(XpsFile *file, const QString &fileName); ~XpsDocument(); /** the total number of pages in this document */ int numPages() const; /** obtain a certain page from this document \param pageNum the number of the page to return \note page numbers are zero based - they run from 0 to numPages() - 1 */ XpsPage* page(int pageNum) const; /** whether this document has a Document Structure */ bool hasDocumentStructure(); /** the document structure for this document, if available */ const Okular::DocumentSynopsis * documentStructure(); private: void parseDocumentStructure( const QString &documentStructureFileName ); QList m_pages; XpsFile * m_file; bool m_haveDocumentStructure; Okular::DocumentSynopsis *m_docStructure; QMap m_docStructurePageMap; }; /** Represents the contents of a Microsoft XML Paper Specification format document. */ class XpsFile { public: XpsFile(); ~XpsFile(); bool loadDocument( const QString & fileName ); bool closeDocument(); Okular::DocumentInfo generateDocumentInfo() const; QImage thumbnail(); /** the total number of XpsDocuments with this file */ int numDocuments() const; /** the total number of pages in all the XpsDocuments within this file */ int numPages() const; /** a page from the file \param pageNum the page number of the page to return \note page numbers are zero based - they run from 0 to numPages() - 1 */ XpsPage* page(int pageNum) const; /** obtain a certain document from this file \param documentNum the number of the document to return \note document numbers are zero based - they run from 0 to numDocuments() - 1 */ XpsDocument* document(int documentNum) const; QFont getFontByName( const QString &fontName, float size ); KZip* xpsArchive(); private: int loadFontByName( const QString &fontName ); QList m_documents; QList m_pages; QString m_thumbnailFileName; bool m_thumbnailMightBeAvailable; QImage m_thumbnail; bool m_thumbnailIsLoaded; QString m_corePropertiesFileName; QString m_signatureOrigin; KZip * m_xpsArchive; QMap m_fontCache; QFontDatabase m_fontDatabase; }; class XpsGenerator : public Okular::Generator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: XpsGenerator( QObject *parent, const QVariantList &args ); virtual ~XpsGenerator(); bool loadDocument( const QString & fileName, QVector & pagesVector ) override; Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override; const Okular::DocumentSynopsis * generateDocumentSynopsis() override; Okular::ExportFormat::List exportFormats() const override; bool exportTo( const QString &fileName, const Okular::ExportFormat &format ) override; bool print( QPrinter &printer ) override; protected: bool doCloseDocument() override; QImage image( Okular::PixmapRequest *page ) override; Okular::TextPage* textPage( Okular::TextRequest * request ) override; private: XpsFile *m_xpsFile; }; Q_DECLARE_LOGGING_CATEGORY(OkularXpsDebug) #endif diff --git a/interfaces/configinterface.h b/interfaces/configinterface.h index 1f4cdc923..bb6181cf7 100644 --- a/interfaces/configinterface.h +++ b/interfaces/configinterface.h @@ -1,69 +1,69 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_CONFIGINTERFACE_H_ #define _OKULAR_CONFIGINTERFACE_H_ #include "../core/okularcore_export.h" -#include +#include class KConfigDialog; namespace Okular { /** * @short Abstract interface for configuration control * * This interface defines a way to configure the Generator itself. * * How to use it in a custom Generator: * @code class MyGenerator : public Okular::Generator, public Okular::ConfigInterface { Q_OBJECT Q_INTERFACES( Okular::ConfigInterface ) ... }; * @endcode * and - of course - implementing its methods. */ class OKULARCORE_EXPORT ConfigInterface { public: /** * Destroys the config interface. */ virtual ~ConfigInterface() {} /** * This method is called to tell the generator to re-parse its configuration. * * Returns true if something has changed. * * @note this method can be called also when the generator is not the * active generator, or when there was not changed in the config added * by the generator itself. So the suggestion is to @b check whether * something changed, and only in that case return @p true */ virtual bool reparseConfig() = 0; /** * This method allows the generator to add custom configuration pages to the * config @p dialog of okular. */ virtual void addPages( KConfigDialog *dialog ) = 0; }; } Q_DECLARE_INTERFACE( Okular::ConfigInterface, "org.kde.okular.ConfigInterface/0.1" ) #endif diff --git a/interfaces/guiinterface.h b/interfaces/guiinterface.h index deb076944..c860d44d9 100644 --- a/interfaces/guiinterface.h +++ b/interfaces/guiinterface.h @@ -1,57 +1,57 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_GUIINTERFACE_H_ #define _OKULAR_GUIINTERFACE_H_ #include "../core/okularcore_export.h" -#include +#include #include namespace Okular { /** * @short Abstract interface for user interface control * * This interface defines an way to interact with the Okular user interface, * e.g. adding actions in the menus. * * How to use it in a custom Generator: * @code class MyGenerator : public Okular::Generator, public Okular::GuiInterface { Q_OBJECT Q_INTERFACES( Okular::GuiInterface ) ... }; * @endcode * and - of course - implementing its methods. */ class OKULARCORE_EXPORT GuiInterface : protected KXMLGUIClient { public: /** * Destroys the gui interface. */ virtual ~GuiInterface() {} /** * This method requests the XML GUI Client provided by the interface. */ KXMLGUIClient* guiClient() { return this; } }; } Q_DECLARE_INTERFACE( Okular::GuiInterface, "org.kde.okular.GuiInterface/0.1" ) #endif diff --git a/interfaces/printinterface.h b/interfaces/printinterface.h index 919a0d745..ac51272f8 100644 --- a/interfaces/printinterface.h +++ b/interfaces/printinterface.h @@ -1,60 +1,60 @@ /*************************************************************************** * Copyright (C) 2007 by Pino Toscano * * * * 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_PRINTINTERFACE_H_ #define _OKULAR_PRINTINTERFACE_H_ #include "../core/okularcore_export.h" -#include +#include class QWidget; namespace Okular { /** * @short Abstract interface for advanced printing control * * This interface defines an advanced way of interfacing with the print * process. * * How to use it in a custom Generator: * @code class MyGenerator : public Okular::Generator, public Okular::PrintInterface { Q_OBJECT Q_INTERFACES( Okular::PrintInterface ) ... }; * @endcode * and - of course - implementing its methods. */ class OKULARCORE_EXPORT PrintInterface { public: /** * Destroys the printer interface. */ virtual ~PrintInterface() {} /** * Builds and returns a new printing configuration widget. * * @note don't keep a pointer to the new constructed widget, as it * will be handled elsewhere (in the Okular KPart) */ virtual QWidget* printConfigurationWidget() const = 0; }; } Q_DECLARE_INTERFACE( Okular::PrintInterface, "org.kde.okular.PrintInterface/0.1" ) #endif diff --git a/interfaces/saveinterface.h b/interfaces/saveinterface.h index 95e7ccb49..f052209e9 100644 --- a/interfaces/saveinterface.h +++ b/interfaces/saveinterface.h @@ -1,85 +1,85 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * * * 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_SAVEINTERFACE_H_ #define _OKULAR_SAVEINTERFACE_H_ #include "../core/okularcore_export.h" -#include +#include namespace Okular { class AnnotationProxy; /** * @short Abstract interface for saving * * This interface defines a way to save (or help saving) the document opened * by the Generator. * * How to use it in a custom Generator: * @code class MyGenerator : public Okular::Generator, public Okular::SaveInterface { Q_OBJECT Q_INTERFACES( Okular::SaveInterface ) ... }; * @endcode * and - of course - implementing its methods. */ class OKULARCORE_EXPORT SaveInterface { public: /** * The possible options for the saving. */ enum SaveOption { NoOption = 0, SaveChanges = 1 ///< The possibility to save with the current changes to the document. }; Q_DECLARE_FLAGS( SaveOptions, SaveOption ) /** * Destroys the save interface. */ virtual ~SaveInterface() {} /** * Query for the supported saving options. * * @note NoOption is never queried */ virtual bool supportsOption( SaveOption option ) const = 0; /** * Save to the specified @p fileName with the specified @p options. */ virtual bool save( const QString &fileName, SaveOptions options, QString *errorText ) = 0; /** * Returns the annotation proxy. Generators can return NULL if native * annotations are not supported. * * @note Returning NULL is equivalent to returning an AnnotationProxy * that doesn't support any capability. * @since 0.15 (KDE 4.9) */ virtual AnnotationProxy* annotationProxy() const = 0; }; } Q_DECLARE_INTERFACE( Okular::SaveInterface, "org.kde.okular.SaveInterface/0.3" ) Q_DECLARE_OPERATORS_FOR_FLAGS( Okular::SaveInterface::SaveOptions ) #endif diff --git a/interfaces/viewerinterface.h b/interfaces/viewerinterface.h index 9a16ea421..929bc1c19 100644 --- a/interfaces/viewerinterface.h +++ b/interfaces/viewerinterface.h @@ -1,95 +1,95 @@ /*************************************************************************** * Copyright (C) 2011 by Michel Ludwig * * * * 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_VIEWERINTERFACE_H_ #define _OKULAR_VIEWERINTERFACE_H_ #include "../core/okularcore_export.h" #ifdef Q_OS_WIN #define VIEWERINTERFACE_EXPORT __declspec(dllexport) #else #define VIEWERINTERFACE_EXPORT OKULARCORE_EXPORT #endif -#include -#include +#include +#include namespace Okular { /** * @short Abstract interface for controlling advanced features of a document viewer * * This interface can be used to control some more or less advanced features of a document * viewer. */ class VIEWERINTERFACE_EXPORT ViewerInterface { public: virtual ~ViewerInterface() {} /** * Show the specified source location centrally in the viewer. * * @param fileName source file name * @param line in the source file, starts from 0 * @param column in the source file, starts from 0 * @param showGraphically controls whether the given source location will be * shown graphically in the viewer (if that feature is globally activated) */ virtual void showSourceLocation(const QString& fileName, int line, int column, bool showGraphically = true) = 0; /** * Clear the source location that was set last in the viewer. */ virtual void clearLastShownSourceLocation() = 0; /** * Returns true iff source locations are shown graphically. */ virtual bool areSourceLocationsShownGraphically() const = 0; /** * Allows to control whether source locations are shown graphically, or not. */ virtual void setShowSourceLocationsGraphically(bool b) = 0; /** * Returns true iff the watch file mode is enabled. */ virtual bool isWatchFileModeEnabled() const = 0; /** * Allows to enable or disable the watch file mode */ virtual void setWatchFileModeEnabled(bool b) = 0; /** * Should the shell that supports tabs pen new files in tabs? */ virtual bool openNewFilesInTabs() const = 0; // SIGNALS /** * The signal 'openSourceReference' is emitted whenever the user has triggered a source * reference in the currently displayed document. */ void openSourceReference(const QString& absFileName, int line, int column); /** * The signal 'viewerMenuStateChange' is emitted whenever the state of the menu * 'menu_okular_part_viewer' defined in 'part-viewermode.rc' has changed. */ void viewerMenuStateChange(bool enabled); }; } Q_DECLARE_INTERFACE( Okular::ViewerInterface, "org.kde.okular.ViewerInterface/0.1" ) #endif diff --git a/mobile/app/package/contents/ui/Bookmarks.qml b/mobile/app/package/contents/ui/Bookmarks.qml index 4b4023365..a6063b3b4 100644 --- a/mobile/app/package/contents/ui/Bookmarks.qml +++ b/mobile/app/package/contents/ui/Bookmarks.qml @@ -1,28 +1,28 @@ /* * Copyright 2012 Marco Martin * * 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, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 ThumbnailsBase { model: documentItem.bookmarkedPages onPageClicked: { pageArea.delegate.pageItem.goToBookmark(pageArea.delegate.pageItem.bookmarks[0]) } -} \ No newline at end of file +} diff --git a/mobile/components/test.qml b/mobile/components/test.qml index fc996f8e0..4c029cafc 100644 --- a/mobile/components/test.qml +++ b/mobile/components/test.qml @@ -1,50 +1,50 @@ /* * Copyright 2012 by Marco Martin * * 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, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Controls 1.0 import org.kde.okular 2.0 as Okular Item { width: 500 height: 600 Okular.DocumentItem { id: docItem path: "pageitem.cpp" } Okular.PageItem { id: page anchors.fill: parent document: docItem } Row { anchors { bottom: parent.bottom right: parent.right } Button { text: "prev" onClicked: page.pageNumber-- } Button { text: "next" onClicked: page.pageNumber++ } } -} \ No newline at end of file +} diff --git a/mobile/components/testDocumentView.qml b/mobile/components/testDocumentView.qml index 79abaf165..d3205b9f4 100644 --- a/mobile/components/testDocumentView.qml +++ b/mobile/components/testDocumentView.qml @@ -1,35 +1,35 @@ /* * Copyright 2012 by Marco Martin * * 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, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.2 import QtQuick.Controls 1.0 import org.kde.okular 2.0 as Okular Item { width: 500 height: 600 Okular.DocumentItem { id: docItem path: "pageitem.cpp" } Okular.DocumentView { anchors.fill: parent document: docItem } -} \ No newline at end of file +} diff --git a/part.cpp b/part.cpp index 2974159c3..704feddcc 100644 --- a/part.cpp +++ b/part.cpp @@ -1,3590 +1,3591 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2002 by Chris Cheney * * Copyright (C) 2002 by Malcolm Hunter * * Copyright (C) 2003-2004 by Christophe Devriese * * * * Copyright (C) 2003 by Daniel Molkentin * * Copyright (C) 2003 by Andy Goossens * * Copyright (C) 2003 by Dirk Mueller * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2004 by Dominique Devriese * * Copyright (C) 2004 by Christoph Cullmann * * Copyright (C) 2004 by Henrique Pinto * * Copyright (C) 2004 by Waldo Bastian * * Copyright (C) 2004-2008 by Albert Astals Cid * * Copyright (C) 2004 by Antti Markus * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * 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 "part.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 #include #include #include #include #include #include #include #include #include #include #ifdef WITH_KWALLET #include #endif #include #include #if PURPOSE_FOUND #include #include #endif #if 0 #include #endif // local includes #include "aboutdata.h" #include "extensions.h" #include "ui/debug_ui.h" #include "ui/drawingtoolactions.h" #include "ui/pageview.h" #include "ui/toc.h" #include "ui/searchwidget.h" #include "ui/thumbnaillist.h" #include "ui/side_reviews.h" #include "ui/minibar.h" #include "ui/embeddedfilesdialog.h" #include "ui/propertiesdialog.h" #include "ui/presentationwidget.h" #include "ui/pagesizelabel.h" #include "ui/bookmarklist.h" #include "ui/findbar.h" #include "ui/sidebar.h" #include "ui/fileprinterpreview.h" #include "ui/guiutils.h" #include "ui/layers.h" #include "ui/okmenutitle.h" #include "conf/preferencesdialog.h" #include "settings.h" #include "core/action.h" #include "core/annotations.h" #include "core/bookmarkmanager.h" #include "core/document.h" #include "core/generator.h" #include "core/page.h" #include "core/fileprinter.h" #include #ifdef OKULAR_KEEP_FILE_OPEN class FileKeeper { public: FileKeeper() : m_handle( nullptr ) { } ~FileKeeper() { } void open( const QString & path ) { if ( !m_handle ) m_handle = std::fopen( QFile::encodeName( path ).constData(), "r" ); } void close() { if ( m_handle ) { int ret = std::fclose( m_handle ); Q_UNUSED( ret ) m_handle = nullptr; } } QTemporaryFile* copyToTemporary() const { if ( !m_handle ) return nullptr; QTemporaryFile * retFile = new QTemporaryFile; retFile->open(); std::rewind( m_handle ); int c = -1; do { c = std::fgetc( m_handle ); if ( c == EOF ) break; if ( !retFile->putChar( (char)c ) ) break; } while ( !feof( m_handle ) ); retFile->flush(); return retFile; } private: std::FILE * m_handle; }; #endif K_PLUGIN_FACTORY(OkularPartFactory, registerPlugin();) static QAction* actionForExportFormat( const Okular::ExportFormat& format, QObject *parent = Q_NULLPTR ) { QAction *act = new QAction( format.description(), parent ); if ( !format.icon().isNull() ) { act->setIcon( format.icon() ); } return act; } static KFilterDev::CompressionType compressionTypeFor( const QString& mime_to_check ) { // The compressedMimeMap is here in case you have a very old shared mime database // that doesn't have inheritance info for things like gzeps, etc // Otherwise the "is()" calls below are just good enough static QHash< QString, KFilterDev::CompressionType > compressedMimeMap; static bool supportBzip = false; static bool supportXz = false; const QString app_gzip( QStringLiteral( "application/x-gzip" ) ); const QString app_bzip( QStringLiteral( "application/x-bzip" ) ); const QString app_xz( QStringLiteral( "application/x-xz" ) ); if ( compressedMimeMap.isEmpty() ) { std::unique_ptr< KFilterBase > f; compressedMimeMap[ QLatin1String( "image/x-gzeps" ) ] = KFilterDev::GZip; // check we can read bzip2-compressed files f.reset( KCompressionDevice::filterForCompressionType( KCompressionDevice::BZip2 ) ); if ( f.get() ) { supportBzip = true; compressedMimeMap[ QLatin1String( "application/x-bzpdf" ) ] = KFilterDev::BZip2; compressedMimeMap[ QLatin1String( "application/x-bzpostscript" ) ] = KFilterDev::BZip2; compressedMimeMap[ QLatin1String( "application/x-bzdvi" ) ] = KFilterDev::BZip2; compressedMimeMap[ QLatin1String( "image/x-bzeps" ) ] = KFilterDev::BZip2; } // check if we can read XZ-compressed files f.reset( KCompressionDevice::filterForCompressionType( KCompressionDevice::Xz ) ); if ( f.get() ) { supportXz = true; } } QHash< QString, KFilterDev::CompressionType >::const_iterator it = compressedMimeMap.constFind( mime_to_check ); if ( it != compressedMimeMap.constEnd() ) return it.value(); QMimeDatabase db; QMimeType mime = db.mimeTypeForName( mime_to_check ); if ( mime.isValid() ) { if ( mime.inherits( app_gzip ) ) return KFilterDev::GZip; else if ( supportBzip && mime.inherits( app_bzip ) ) return KFilterDev::BZip2; else if ( supportXz && mime.inherits( app_xz ) ) return KFilterDev::Xz; } return KFilterDev::None; } static Okular::EmbedMode detectEmbedMode( QWidget *parentWidget, QObject *parent, const QVariantList &args ) { Q_UNUSED( parentWidget ); if ( parent && ( parent->objectName().startsWith( QLatin1String( "okular::Shell" ) ) || parent->objectName().startsWith( QLatin1String( "okular/okular__Shell" ) ) ) ) return Okular::NativeShellMode; if ( parent && ( QByteArray( "KHTMLPart" ) == parent->metaObject()->className() ) ) return Okular::KHTMLPartMode; Q_FOREACH ( const QVariant &arg, args ) { if ( arg.type() == QVariant::String ) { if ( arg.toString() == QLatin1String( "Print/Preview" ) ) { return Okular::PrintPreviewMode; } else if ( arg.toString() == QLatin1String( "ViewerWidget" ) ) { return Okular::ViewerWidgetMode; } } } return Okular::UnknownEmbedMode; } static QString detectConfigFileName( const QVariantList &args ) { Q_FOREACH ( const QVariant &arg, args ) { if ( arg.type() == QVariant::String ) { QString argString = arg.toString(); int separatorIndex = argString.indexOf( QStringLiteral("=") ); if ( separatorIndex >= 0 && argString.left( separatorIndex ) == QLatin1String( "ConfigFileName" ) ) { return argString.mid( separatorIndex + 1 ); } } } return QString(); } #undef OKULAR_KEEP_FILE_OPEN #ifdef OKULAR_KEEP_FILE_OPEN static bool keepFileOpen() { static bool keep_file_open = !qgetenv("OKULAR_NO_KEEP_FILE_OPEN").toInt(); return keep_file_open; } #endif int Okular::Part::numberOfParts = 0; namespace Okular { Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList &args) : KParts::ReadWritePart(parent), m_tempfile( nullptr ), m_documentOpenWithPassword( false ), m_swapInsteadOfOpening( false ), m_isReloading( false ), m_fileWasRemoved( false ), m_showMenuBarAction( nullptr ), m_showFullScreenAction( nullptr ), m_actionsSearched( false ), m_cliPresentation(false), m_cliPrint(false), m_cliPrintAndExit(false), m_embedMode(detectEmbedMode(parentWidget, parent, args)), m_generatorGuiClient(nullptr), m_keeper( nullptr ) { // make sure that the component name is okular otherwise the XMLGUI .rc files are not found // when this part is used in an application other than okular (e.g. unit tests) setComponentName(QStringLiteral("okular"), QString()); const QLatin1String configFileName("okularpartrc"); // first, we check if a config file name has been specified QString configFilePath = detectConfigFileName( args ); if ( configFilePath.isEmpty() ) { configFilePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1Char('/') + configFileName; } // Migrate old config if ( !QFile::exists( configFilePath ) ) { qCDebug(OkularUiDebug) << "Did not find a config file, attempting to look for old config"; // Migrate old config + UI Kdelibs4ConfigMigrator configMigrator( componentName() ); // UI file is handled automatically, we only need to specify config name because we're a part configMigrator.setConfigFiles( QStringList( configFileName ) ); // If there's no old okular config to migrate, look for kpdf if ( !configMigrator.migrate() ) { qCDebug(OkularUiDebug) << "Did not find an old okular config file, attempting to look for kpdf config"; // First try the automatic detection, using $KDEHOME etc. Kdelibs4Migration migration; QString kpdfConfig = migration.locateLocal( "config", QStringLiteral("kpdfpartrc") ); // Fallback just in case it tried e. g. ~/.kde4 if ( kpdfConfig.isEmpty() ) { kpdfConfig = QDir::homePath() + QStringLiteral("/.kde/share/config/kpdfpartrc"); } if ( QFile::exists( kpdfConfig ) ) { qCDebug(OkularUiDebug) << "Found old kpdf config" << kpdfConfig << "copying to" << configFilePath; QFile::copy( kpdfConfig, configFilePath ); } else { qCDebug(OkularUiDebug) << "Did not find an old kpdf config file"; } } else { qCDebug(OkularUiDebug) << "Migrated old okular config"; } } Okular::Settings::instance( configFilePath ); numberOfParts++; if (numberOfParts == 1) { m_registerDbusName = QStringLiteral("/okular"); } else { m_registerDbusName = QStringLiteral("/okular%1").arg(numberOfParts); } QDBusConnection::sessionBus().registerObject(m_registerDbusName, this, QDBusConnection::ExportScriptableSlots); // connect the started signal to tell the job the mimetypes we like, // and get some more information from it connect(this, &KParts::ReadOnlyPart::started, this, &Part::slotJobStarted); // connect the completed signal so we can put the window caption when loading remote files connect(this, SIGNAL(completed()), this, SLOT(setWindowTitleFromDocument())); connect(this, &KParts::ReadOnlyPart::canceled, this, &Part::loadCancelled); // create browser extension (for printing when embedded into browser) m_bExtension = new BrowserExtension(this); // create live connect extension (for integrating with browser scripting) new OkularLiveConnectExtension( this ); GuiUtils::addIconLoader( iconLoader() ); m_sidebar = new Sidebar( parentWidget ); setWidget( m_sidebar ); connect( m_sidebar, &Sidebar::urlsDropped, this, &Part::handleDroppedUrls ); // build the document m_document = new Okular::Document(widget()); connect( m_document, &Document::linkFind, this, &Part::slotFind ); connect( m_document, &Document::linkGoToPage, this, &Part::slotGoToPage ); connect( m_document, &Document::linkPresentation, this, &Part::slotShowPresentation ); connect( m_document, &Document::linkEndPresentation, this, &Part::slotHidePresentation ); connect( m_document, &Document::openUrl, this, &Part::openUrlFromDocument ); connect( m_document->bookmarkManager(), &BookmarkManager::openUrl, this, &Part::openUrlFromBookmarks ); connect( m_document, &Document::close, this, &Part::close ); connect( m_document, &Document::undoHistoryCleanChanged, this, [this](bool clean) { setModified( !clean ); setWindowTitleFromDocument(); } ); if ( parent && parent->metaObject()->indexOfSlot( QMetaObject::normalizedSignature( "slotQuit()" ).constData() ) != -1 ) connect( m_document, SIGNAL(quit()), parent, SLOT(slotQuit()) ); else connect( m_document, &Document::quit, this, &Part::cannotQuit ); // widgets: ^searchbar (toolbar containing label and SearchWidget) // m_searchToolBar = new KToolBar( parentWidget, "searchBar" ); // m_searchToolBar->boxLayout()->setSpacing( KDialog::spacingHint() ); // QLabel * sLabel = new QLabel( i18n( "&Search:" ), m_searchToolBar, "kde toolbar widget" ); // m_searchWidget = new SearchWidget( m_searchToolBar, m_document ); // sLabel->setBuddy( m_searchWidget ); // m_searchToolBar->setStretchableWidget( m_searchWidget ); // [left toolbox: Table of Contents] | [] m_toc = new TOC( nullptr, m_document ); connect( m_toc.data(), &TOC::hasTOC, this, &Part::enableTOC ); connect( m_toc.data(), &TOC::rightClick, this, &Part::slotShowTOCMenu ); m_sidebar->addItem( m_toc, QIcon::fromTheme(QApplication::isLeftToRight() ? QStringLiteral("format-justify-left") : QStringLiteral("format-justify-right")), i18n("Contents") ); enableTOC( false ); // [left toolbox: Layers] | [] m_layers = new Layers( nullptr, m_document ); connect( m_layers.data(), &Layers::hasLayers, this, &Part::enableLayers ); m_sidebar->addItem( m_layers, QIcon::fromTheme( QStringLiteral("format-list-unordered") ), i18n( "Layers" ) ); enableLayers( false ); // [left toolbox: Thumbnails and Bookmarks] | [] QWidget * thumbsBox = new ThumbnailsBox( nullptr ); thumbsBox->layout()->setSpacing( 6 ); m_searchWidget = new SearchWidget( thumbsBox, m_document ); thumbsBox->layout()->addWidget(m_searchWidget); m_thumbnailList = new ThumbnailList( thumbsBox, m_document ); thumbsBox->layout()->addWidget(m_thumbnailList); // ThumbnailController * m_tc = new ThumbnailController( thumbsBox, m_thumbnailList ); connect( m_thumbnailList.data(), &ThumbnailList::rightClick, this, &Part::slotShowMenu ); m_sidebar->addItem( thumbsBox, QIcon::fromTheme( QStringLiteral("view-preview") ), i18n("Thumbnails") ); m_sidebar->setCurrentItem( thumbsBox ); // [left toolbox: Reviews] | [] m_reviewsWidget = new Reviews( nullptr, m_document ); m_sidebar->addItem( m_reviewsWidget, QIcon::fromTheme(QStringLiteral("draw-freehand")), i18n("Reviews") ); m_sidebar->setItemEnabled( m_reviewsWidget, false ); // [left toolbox: Bookmarks] | [] m_bookmarkList = new BookmarkList( m_document, nullptr ); m_sidebar->addItem( m_bookmarkList, QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks") ); m_sidebar->setItemEnabled( m_bookmarkList, false ); // widgets: [../miniBarContainer] | [] #ifdef OKULAR_ENABLE_MINIBAR QWidget * miniBarContainer = new QWidget( 0 ); m_sidebar->setBottomWidget( miniBarContainer ); QVBoxLayout * miniBarLayout = new QVBoxLayout( miniBarContainer ); miniBarLayout->setMargin( 0 ); // widgets: [../[spacer/..]] | [] miniBarLayout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ) ); // widgets: [../[../MiniBar]] | [] QFrame * bevelContainer = new QFrame( miniBarContainer ); bevelContainer->setFrameStyle( QFrame::StyledPanel | QFrame::Sunken ); QVBoxLayout * bevelContainerLayout = new QVBoxLayout( bevelContainer ); bevelContainerLayout->setMargin( 4 ); m_progressWidget = new ProgressWidget( bevelContainer, m_document ); bevelContainerLayout->addWidget( m_progressWidget ); miniBarLayout->addWidget( bevelContainer ); miniBarLayout->addItem( new QSpacerItem( 6, 6, QSizePolicy::Fixed, QSizePolicy::Fixed ) ); #endif // widgets: [] | [right 'pageView'] QWidget * rightContainer = new QWidget( nullptr ); m_sidebar->setMainWidget( rightContainer ); QVBoxLayout * rightLayout = new QVBoxLayout( rightContainer ); rightLayout->setMargin( 0 ); rightLayout->setSpacing( 0 ); // KToolBar * rtb = new KToolBar( rightContainer, "mainToolBarSS" ); // rightLayout->addWidget( rtb ); m_migrationMessage = new KMessageWidget( rightContainer ); m_migrationMessage->setVisible( false ); m_migrationMessage->setWordWrap( true ); m_migrationMessage->setMessageType( KMessageWidget::Warning ); m_migrationMessage->setText( i18n( "This document contains annotations or form data that were saved internally by a previous Okular version. Internal storage is no longer supported.
Please save to a file in order to move them if you want to continue to edit the document." ) ); rightLayout->addWidget( m_migrationMessage ); m_topMessage = new KMessageWidget( rightContainer ); m_topMessage->setVisible( false ); m_topMessage->setWordWrap( true ); m_topMessage->setMessageType( KMessageWidget::Information ); m_topMessage->setText( i18n( "This document has embedded files. Click here to see them or go to File -> Embedded Files." ) ); m_topMessage->setIcon( QIcon::fromTheme( QStringLiteral("mail-attachment") ) ); connect( m_topMessage, &KMessageWidget::linkActivated, this, &Part::slotShowEmbeddedFiles ); rightLayout->addWidget( m_topMessage ); m_formsMessage = new KMessageWidget( rightContainer ); m_formsMessage->setVisible( false ); m_formsMessage->setWordWrap( true ); m_formsMessage->setMessageType( KMessageWidget::Information ); rightLayout->addWidget( m_formsMessage ); m_infoMessage = new KMessageWidget( rightContainer ); m_infoMessage->setVisible( false ); m_infoMessage->setWordWrap( true ); m_infoMessage->setMessageType( KMessageWidget::Information ); rightLayout->addWidget( m_infoMessage ); m_infoTimer = new QTimer(); m_infoTimer->setSingleShot( true ); connect( m_infoTimer, &QTimer::timeout, m_infoMessage, &KMessageWidget::animatedHide ); m_pageView = new PageView( rightContainer, m_document ); QMetaObject::invokeMethod( m_pageView, "setFocus", Qt::QueuedConnection ); //usability setting // m_splitter->setFocusProxy(m_pageView); connect( m_pageView.data(), &PageView::rightClick, this, &Part::slotShowMenu ); connect( m_document, &Document::error, this, &Part::errorMessage ); connect( m_document, &Document::warning, this, &Part::warningMessage ); connect( m_document, &Document::notice, this, &Part::noticeMessage ); connect( m_document, &Document::sourceReferenceActivated, this, &Part::slotHandleActivatedSourceReference ); connect( m_pageView.data(), &PageView::fitWindowToPage, this, &Part::fitWindowToPage ); rightLayout->addWidget( m_pageView ); m_layers->setPageView( m_pageView ); m_findBar = new FindBar( m_document, rightContainer ); rightLayout->addWidget( m_findBar ); m_bottomBar = new QWidget( rightContainer ); QHBoxLayout * bottomBarLayout = new QHBoxLayout( m_bottomBar ); m_pageSizeLabel = new PageSizeLabel( m_bottomBar, m_document ); bottomBarLayout->setMargin( 0 ); bottomBarLayout->setSpacing( 0 ); bottomBarLayout->addItem( new QSpacerItem( 5, 5, QSizePolicy::Expanding, QSizePolicy::Minimum ) ); m_miniBarLogic = new MiniBarLogic( this, m_document ); m_miniBar = new MiniBar( m_bottomBar, m_miniBarLogic ); bottomBarLayout->addWidget( m_miniBar ); bottomBarLayout->addWidget( m_pageSizeLabel ); rightLayout->addWidget( m_bottomBar ); m_pageNumberTool = new MiniBar( nullptr, m_miniBarLogic ); connect( m_findBar, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*))); connect( m_findBar, SIGNAL(onCloseButtonPressed()), m_pageView, SLOT(setFocus())); connect( m_miniBar, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*))); connect( m_pageView.data(), &PageView::escPressed, m_findBar, &FindBar::resetSearch ); connect( m_pageNumberTool, SIGNAL(forwardKeyPressEvent(QKeyEvent*)), m_pageView, SLOT(externalKeyPressEvent(QKeyEvent*))); connect( m_reviewsWidget.data(), &Reviews::openAnnotationWindow, m_pageView.data(), &PageView::openAnnotationWindow ); // add document observers m_document->addObserver( this ); m_document->addObserver( m_thumbnailList ); m_document->addObserver( m_pageView ); m_document->registerView( m_pageView ); m_document->addObserver( m_toc ); m_document->addObserver( m_miniBarLogic ); #ifdef OKULAR_ENABLE_MINIBAR m_document->addObserver( m_progressWidget ); #endif m_document->addObserver( m_reviewsWidget ); m_document->addObserver( m_pageSizeLabel ); m_document->addObserver( m_bookmarkList ); connect( m_document->bookmarkManager(), &BookmarkManager::saved, this, &Part::slotRebuildBookmarkMenu ); setupViewerActions(); if ( m_embedMode != ViewerWidgetMode ) { setupActions(); } else { setViewerShortcuts(); } // document watcher and reloader m_watcher = new KDirWatch( this ); connect( m_watcher, &KDirWatch::dirty, this, &Part::slotFileDirty ); connect( m_watcher, &KDirWatch::created, this, &Part::slotFileDirty ); connect( m_watcher, &KDirWatch::deleted, this, &Part::slotFileDirty ); m_dirtyHandler = new QTimer( this ); m_dirtyHandler->setSingleShot( true ); connect( m_dirtyHandler, &QTimer::timeout, this, [this] { slotAttemptReload(); } ); slotNewConfig(); // keep us informed when the user changes settings connect( Okular::Settings::self(), &KCoreConfigSkeleton::configChanged, this, &Part::slotNewConfig ); #ifdef HAVE_SPEECH // [SPEECH] check for TTS presence and usability Okular::Settings::setUseTTS( true ); Okular::Settings::self()->save(); #endif rebuildBookmarkMenu( false ); if ( m_embedMode == ViewerWidgetMode ) { // set the XML-UI resource file for the viewer mode setXMLFile(QStringLiteral("part-viewermode.rc")); } else { // set our main XML-UI resource file setXMLFile(QStringLiteral("part.rc")); } m_pageView->setupBaseActions( actionCollection() ); m_sidebar->setSidebarVisibility( false ); if ( m_embedMode != PrintPreviewMode ) { // now set up actions that are required for all remaining modes m_pageView->setupViewerActions( actionCollection() ); // and if we are not in viewer mode, we want the full GUI if ( m_embedMode != ViewerWidgetMode ) { unsetDummyMode(); } } // ensure history actions are in the correct state updateViewActions(); // also update the state of the actions in the page view m_pageView->updateActionState( false, false, false ); if ( m_embedMode == NativeShellMode ) m_sidebar->setAutoFillBackground( false ); #ifdef OKULAR_KEEP_FILE_OPEN m_keeper = new FileKeeper(); #endif } void Part::setupViewerActions() { // ACTIONS KActionCollection * ac = actionCollection(); // Page Traversal actions m_gotoPage = KStandardAction::gotoPage( this, SLOT(slotGoToPage()), ac ); ac->setDefaultShortcuts(m_gotoPage, KStandardShortcut::gotoLine()); // dirty way to activate gotopage when pressing miniBar's button connect( m_miniBar.data(), &MiniBar::gotoPage, m_gotoPage, &QAction::trigger ); connect( m_pageNumberTool.data(), &MiniBar::gotoPage, m_gotoPage, &QAction::trigger ); m_prevPage = KStandardAction::prior(this, SLOT(slotPreviousPage()), ac); m_prevPage->setIconText( i18nc( "Previous page", "Previous" ) ); m_prevPage->setToolTip( i18n( "Go back to the Previous Page" ) ); m_prevPage->setWhatsThis( i18n( "Moves to the previous page of the document" ) ); ac->setDefaultShortcut(m_prevPage, QKeySequence()); // dirty way to activate prev page when pressing miniBar's button connect( m_miniBar.data(), &MiniBar::prevPage, m_prevPage, &QAction::trigger ); connect( m_pageNumberTool.data(), &MiniBar::prevPage, m_prevPage, &QAction::trigger ); #ifdef OKULAR_ENABLE_MINIBAR connect( m_progressWidget, SIGNAL(prevPage()), m_prevPage, SLOT(trigger()) ); #endif m_nextPage = KStandardAction::next(this, SLOT(slotNextPage()), ac ); m_nextPage->setIconText( i18nc( "Next page", "Next" ) ); m_nextPage->setToolTip( i18n( "Advance to the Next Page" ) ); m_nextPage->setWhatsThis( i18n( "Moves to the next page of the document" ) ); ac->setDefaultShortcut(m_nextPage, QKeySequence()); // dirty way to activate next page when pressing miniBar's button connect( m_miniBar.data(), &MiniBar::nextPage, m_nextPage, &QAction::trigger ); connect( m_pageNumberTool.data(), &MiniBar::nextPage, m_nextPage, &QAction::trigger ); #ifdef OKULAR_ENABLE_MINIBAR connect( m_progressWidget, SIGNAL(nextPage()), m_nextPage, SLOT(trigger()) ); #endif m_beginningOfDocument = KStandardAction::firstPage( this, SLOT(slotGotoFirst()), ac ); ac->addAction(QStringLiteral("first_page"), m_beginningOfDocument); m_beginningOfDocument->setText(i18n( "Beginning of the document")); m_beginningOfDocument->setWhatsThis( i18n( "Moves to the beginning of the document" ) ); m_endOfDocument = KStandardAction::lastPage( this, SLOT(slotGotoLast()), ac ); ac->addAction(QStringLiteral("last_page"),m_endOfDocument); m_endOfDocument->setText(i18n( "End of the document")); m_endOfDocument->setWhatsThis( i18n( "Moves to the end of the document" ) ); // we do not want back and next in history in the dummy mode m_historyBack = nullptr; m_historyNext = nullptr; m_addBookmark = KStandardAction::addBookmark( this, SLOT(slotAddBookmark()), ac ); m_addBookmarkText = m_addBookmark->text(); m_addBookmarkIcon = m_addBookmark->icon(); m_renameBookmark = ac->addAction(QStringLiteral("rename_bookmark")); m_renameBookmark->setText(i18n( "Rename Bookmark" )); m_renameBookmark->setIcon(QIcon::fromTheme( QStringLiteral("edit-rename") )); m_renameBookmark->setWhatsThis( i18n( "Rename the current bookmark" ) ); connect( m_renameBookmark, &QAction::triggered, this, &Part::slotRenameCurrentViewportBookmark ); m_prevBookmark = ac->addAction(QStringLiteral("previous_bookmark")); m_prevBookmark->setText(i18n( "Previous Bookmark" )); m_prevBookmark->setIcon(QIcon::fromTheme( QStringLiteral("go-up-search") )); m_prevBookmark->setWhatsThis( i18n( "Go to the previous bookmark" ) ); connect( m_prevBookmark, &QAction::triggered, this, &Part::slotPreviousBookmark ); m_nextBookmark = ac->addAction(QStringLiteral("next_bookmark")); m_nextBookmark->setText(i18n( "Next Bookmark" )); m_nextBookmark->setIcon(QIcon::fromTheme( QStringLiteral("go-down-search") )); m_nextBookmark->setWhatsThis( i18n( "Go to the next bookmark" ) ); connect( m_nextBookmark, &QAction::triggered, this, &Part::slotNextBookmark ); m_copy = nullptr; m_selectAll = nullptr; // Find and other actions m_find = KStandardAction::find( this, SLOT(slotShowFindBar()), ac ); QList s = m_find->shortcuts(); s.append( QKeySequence( Qt::Key_Slash ) ); ac->setDefaultShortcuts(m_find, s); m_find->setEnabled( false ); m_findNext = KStandardAction::findNext( this, SLOT(slotFindNext()), ac); m_findNext->setEnabled( false ); m_findPrev = KStandardAction::findPrev( this, SLOT(slotFindPrev()), ac ); m_findPrev->setEnabled( false ); m_save = nullptr; m_saveAs = nullptr; QAction * prefs = KStandardAction::preferences( this, SLOT(slotPreferences()), ac); if ( m_embedMode == NativeShellMode ) { prefs->setText( i18n( "Configure Okular..." ) ); } else { // TODO: improve this message prefs->setText( i18n( "Configure Viewer..." ) ); } QAction * genPrefs = new QAction( ac ); ac->addAction(QStringLiteral("options_configure_generators"), genPrefs); if ( m_embedMode == ViewerWidgetMode ) { genPrefs->setText( i18n( "Configure Viewer Backends..." ) ); } else { genPrefs->setText( i18n( "Configure Backends..." ) ); } genPrefs->setIcon( QIcon::fromTheme( QStringLiteral("configure") ) ); genPrefs->setEnabled( m_document->configurableGenerators() > 0 ); connect( genPrefs, &QAction::triggered, this, &Part::slotGeneratorPreferences ); m_printPreview = KStandardAction::printPreview( this, SLOT(slotPrintPreview()), ac ); m_printPreview->setEnabled( false ); m_showLeftPanel = nullptr; m_showBottomBar = nullptr; m_showProperties = ac->addAction(QStringLiteral("properties")); m_showProperties->setText(i18n("&Properties")); m_showProperties->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); connect(m_showProperties, &QAction::triggered, this, &Part::slotShowProperties); m_showProperties->setEnabled( false ); m_showEmbeddedFiles = nullptr; m_showPresentation = nullptr; m_exportAs = nullptr; m_exportAsMenu = nullptr; m_exportAsText = nullptr; m_exportAsDocArchive = nullptr; #if PURPOSE_FOUND m_share = nullptr; m_shareMenu = nullptr; #endif m_presentationDrawingActions = nullptr; m_aboutBackend = ac->addAction(QStringLiteral("help_about_backend")); m_aboutBackend->setText(i18n("About Backend")); m_aboutBackend->setEnabled( false ); connect(m_aboutBackend, &QAction::triggered, this, &Part::slotAboutBackend); QAction *reload = ac->add( QStringLiteral("file_reload") ); reload->setText( i18n( "Reloa&d" ) ); reload->setIcon( QIcon::fromTheme( QStringLiteral("view-refresh") ) ); reload->setWhatsThis( i18n( "Reload the current document from disk." ) ); connect( reload, &QAction::triggered, this, &Part::slotReload ); ac->setDefaultShortcuts(reload, KStandardShortcut::reload()); m_reload = reload; m_closeFindBar = ac->addAction( QStringLiteral("close_find_bar"), this, SLOT(slotHideFindBar()) ); m_closeFindBar->setText( i18n("Close &Find Bar") ); ac->setDefaultShortcut(m_closeFindBar, QKeySequence(Qt::Key_Escape)); m_closeFindBar->setEnabled( false ); QWidgetAction *pageno = new QWidgetAction( ac ); pageno->setText( i18n( "Page Number" ) ); pageno->setDefaultWidget( m_pageNumberTool ); ac->addAction( QStringLiteral("page_number"), pageno ); } void Part::setViewerShortcuts() { KActionCollection * ac = actionCollection(); ac->setDefaultShortcut(m_gotoPage, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_G)); ac->setDefaultShortcut(m_find, QKeySequence()); ac->setDefaultShortcut(m_findNext, QKeySequence()); ac->setDefaultShortcut(m_findPrev, QKeySequence()); ac->setDefaultShortcut(m_addBookmark, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_B)); ac->setDefaultShortcut(m_beginningOfDocument, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Home)); ac->setDefaultShortcut(m_endOfDocument, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_End)); QAction *action = static_cast( ac->action( QStringLiteral("file_reload") ) ); if (action) { ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_F5)); } } void Part::setupActions() { KActionCollection * ac = actionCollection(); QMimeDatabase db; m_copy = KStandardAction::create( KStandardAction::Copy, m_pageView, SLOT(copyTextSelection()), ac ); m_selectAll = KStandardAction::selectAll( m_pageView, SLOT(selectAll()), ac ); m_save = KStandardAction::save( this, [this] { saveFile(); }, ac ); m_save->setEnabled( false ); m_saveAs = KStandardAction::saveAs( this, SLOT(slotSaveFileAs()), ac ); m_saveAs->setEnabled( false ); m_migrationMessage->addAction( m_saveAs ); m_showLeftPanel = ac->add(QStringLiteral("show_leftpanel")); m_showLeftPanel->setText(i18n( "Show &Navigation Panel")); m_showLeftPanel->setIcon(QIcon::fromTheme( QStringLiteral("view-sidetree") )); connect( m_showLeftPanel, &QAction::toggled, this, &Part::slotShowLeftPanel ); ac->setDefaultShortcut(m_showLeftPanel, QKeySequence(Qt::Key_F7)); m_showLeftPanel->setChecked( Okular::Settings::showLeftPanel() ); slotShowLeftPanel(); m_showBottomBar = ac->add(QStringLiteral("show_bottombar")); m_showBottomBar->setText(i18n( "Show &Page Bar")); connect( m_showBottomBar, &QAction::toggled, this, &Part::slotShowBottomBar ); m_showBottomBar->setChecked( Okular::Settings::showBottomBar() ); slotShowBottomBar(); m_showEmbeddedFiles = ac->addAction(QStringLiteral("embedded_files")); m_showEmbeddedFiles->setText(i18n("&Embedded Files")); m_showEmbeddedFiles->setIcon( QIcon::fromTheme( QStringLiteral("mail-attachment") ) ); connect(m_showEmbeddedFiles, &QAction::triggered, this, &Part::slotShowEmbeddedFiles); m_showEmbeddedFiles->setEnabled( false ); m_exportAs = ac->addAction(QStringLiteral("file_export_as")); m_exportAs->setText(i18n("E&xport As")); m_exportAs->setIcon( QIcon::fromTheme( QStringLiteral("document-export") ) ); m_exportAsMenu = new QMenu(); connect(m_exportAsMenu, &QMenu::triggered, this, &Part::slotExportAs); m_exportAs->setMenu( m_exportAsMenu ); m_exportAsText = actionForExportFormat( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ), m_exportAsMenu ); m_exportAsMenu->addAction( m_exportAsText ); m_exportAs->setEnabled( false ); m_exportAsText->setEnabled( false ); #if PURPOSE_FOUND m_share = ac->addAction( QStringLiteral("file_share") ); m_share->setText( i18n("S&hare") ); m_share->setIcon( QIcon::fromTheme( QStringLiteral("document-share") ) ); m_share->setEnabled( false ); m_shareMenu = new Purpose::Menu(); connect(m_shareMenu, &Purpose::Menu::finished, this, &Part::slotShareActionFinished); m_share->setMenu( m_shareMenu ); #endif m_showPresentation = ac->addAction(QStringLiteral("presentation")); m_showPresentation->setText(i18n("P&resentation")); m_showPresentation->setIcon( QIcon::fromTheme( QStringLiteral("view-presentation") ) ); connect(m_showPresentation, &QAction::triggered, this, &Part::slotShowPresentation); ac->setDefaultShortcut(m_showPresentation, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_P)); m_showPresentation->setEnabled( false ); QAction * importPS = ac->addAction(QStringLiteral("import_ps")); importPS->setText(i18n("&Import PostScript as PDF...")); importPS->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); connect(importPS, &QAction::triggered, this, &Part::slotImportPSFile); #if 0 QAction * ghns = ac->addAction("get_new_stuff"); ghns->setText(i18n("&Get Books From Internet...")); ghns->setIcon(QIcon::fromTheme("get-hot-new-stuff")); connect(ghns, SIGNAL(triggered()), this, SLOT(slotGetNewStuff())); #endif KToggleAction *blackscreenAction = new KToggleAction( i18n( "Switch Blackscreen Mode" ), ac ); ac->addAction( QStringLiteral("switch_blackscreen_mode"), blackscreenAction ); ac->setDefaultShortcut(blackscreenAction, QKeySequence(Qt::Key_B)); blackscreenAction->setIcon( QIcon::fromTheme( QStringLiteral("view-presentation") ) ); blackscreenAction->setEnabled( false ); m_presentationDrawingActions = new DrawingToolActions( ac ); QAction *eraseDrawingAction = new QAction( i18n( "Erase Drawings" ), ac ); ac->addAction( QStringLiteral("presentation_erase_drawings"), eraseDrawingAction ); eraseDrawingAction->setIcon( QIcon::fromTheme( QStringLiteral("draw-eraser") ) ); eraseDrawingAction->setEnabled( false ); QAction *configureAnnotations = new QAction( i18n( "Configure Annotations..." ), ac ); ac->addAction( QStringLiteral("options_configure_annotations"), configureAnnotations ); configureAnnotations->setIcon( QIcon::fromTheme( QStringLiteral("configure") ) ); connect(configureAnnotations, &QAction::triggered, this, &Part::slotAnnotationPreferences); QAction *playPauseAction = new QAction( i18n( "Play/Pause Presentation" ), ac ); ac->addAction( QStringLiteral("presentation_play_pause"), playPauseAction ); playPauseAction->setEnabled( false ); } Part::~Part() { QDBusConnection::sessionBus().unregisterObject(m_registerDbusName); GuiUtils::removeIconLoader( iconLoader() ); m_document->removeObserver( this ); if ( m_document->isOpened() ) Part::closeUrl( false ); delete m_toc; delete m_layers; delete m_pageView; delete m_thumbnailList; delete m_miniBar; delete m_pageNumberTool; delete m_miniBarLogic; delete m_bottomBar; #ifdef OKULAR_ENABLE_MINIBAR delete m_progressWidget; #endif delete m_pageSizeLabel; delete m_reviewsWidget; delete m_bookmarkList; delete m_infoTimer; delete m_document; delete m_tempfile; qDeleteAll( m_bookmarkActions ); delete m_exportAsMenu; #if PURPOSE_FOUND delete m_shareMenu; #endif #ifdef OKULAR_KEEP_FILE_OPEN delete m_keeper; #endif } bool Part::openDocument(const QUrl& url, uint page) { Okular::DocumentViewport vp( page - 1 ); vp.rePos.enabled = true; vp.rePos.normalizedX = 0; vp.rePos.normalizedY = 0; vp.rePos.pos = Okular::DocumentViewport::TopLeft; if ( vp.isValid() ) m_document->setNextDocumentViewport( vp ); return openUrl( url ); } void Part::startPresentation() { m_cliPresentation = true; } QStringList Part::supportedMimeTypes() const { return m_document->supportedMimeTypes(); } QUrl Part::realUrl() const { if ( !m_realUrl.isEmpty() ) return m_realUrl; return url(); } // ViewerInterface void Part::showSourceLocation(const QString& fileName, int line, int column, bool showGraphically) { Q_UNUSED(column); const QString u = QStringLiteral( "src:%1 %2" ).arg( line + 1 ).arg( fileName ); GotoAction action( QString(), u ); m_document->processAction( &action ); if( showGraphically ) { m_pageView->setLastSourceLocationViewport( m_document->viewport() ); } } void Part::clearLastShownSourceLocation() { m_pageView->clearLastSourceLocationViewport(); } bool Part::isWatchFileModeEnabled() const { return !m_watcher->isStopped(); } void Part::setWatchFileModeEnabled(bool enabled) { if ( enabled && m_watcher->isStopped() ) { m_watcher->startScan(); } else if( !enabled && !m_watcher->isStopped() ) { m_dirtyHandler->stop(); m_watcher->stopScan(); } } bool Part::areSourceLocationsShownGraphically() const { return m_pageView->areSourceLocationsShownGraphically(); } void Part::setShowSourceLocationsGraphically(bool show) { m_pageView->setShowSourceLocationsGraphically(show); } bool Part::openNewFilesInTabs() const { return Okular::Settings::self()->shellOpenFileInTabs(); } void Part::slotHandleActivatedSourceReference(const QString& absFileName, int line, int col, bool *handled) { emit openSourceReference( absFileName, line, col ); if ( m_embedMode == Okular::ViewerWidgetMode ) { *handled = true; } } void Part::openUrlFromDocument(const QUrl &url) { if ( m_embedMode == PrintPreviewMode ) return; if (url.isLocalFile()) { if (!QFile::exists(url.toLocalFile())) { KMessageBox::error( widget(), i18n("Could not open '%1'. File does not exist", url.toDisplayString() ) ); return; } } else { KIO::StatJob *statJob = KIO::stat(url, KIO::StatJob::SourceSide, 0); KJobWidgets::setWindow(statJob, widget()); if (!statJob->exec() || statJob->error()) { KMessageBox::error( widget(), i18n("Could not open '%1' (%2) ", url.toDisplayString(), statJob->errorString() ) ); return; } } m_bExtension->openUrlNotify(); m_bExtension->setLocationBarUrl(url.toDisplayString()); openUrl(url); } void Part::openUrlFromBookmarks(const QUrl &_url) { QUrl url = _url; Okular::DocumentViewport vp( _url.fragment(QUrl::FullyDecoded) ); if ( vp.isValid() ) m_document->setNextDocumentViewport( vp ); url.setFragment( QString() ); if ( m_document->currentDocument() == url ) { if ( vp.isValid() ) m_document->setViewport( vp ); } else openUrl( url ); } void Part::handleDroppedUrls( const QList& urls ) { if ( urls.isEmpty() ) return; if ( m_embedMode != NativeShellMode || !openNewFilesInTabs() ) { openUrlFromDocument( urls.first() ); return; } emit urlsDropped( urls ); } void Part::slotJobStarted(KIO::Job *job) { if (job) { QStringList supportedMimeTypes = m_document->supportedMimeTypes(); job->addMetaData(QStringLiteral("accept"), supportedMimeTypes.join(QStringLiteral(", ")) + QStringLiteral(", */*;q=0.5")); connect(job, &KJob::result, this, &Part::slotJobFinished); } } void Part::slotJobFinished(KJob *job) { if ( job->error() == KIO::ERR_USER_CANCELED ) { m_pageView->displayMessage( i18n( "The loading of %1 has been canceled.", realUrl().toDisplayString(QUrl::PreferLocalFile) ) ); } } void Part::loadCancelled(const QString &reason) { emit setWindowCaption( QString() ); resetStartArguments(); // when m_viewportDirty.pageNumber != -1 we come from slotAttemptReload // so we don't want to show an ugly messagebox just because the document is // taking more than usual to be recreated if (m_viewportDirty.pageNumber == -1) { if (!reason.isEmpty()) { KMessageBox::error( widget(), i18n("Could not open %1. Reason: %2", url().toDisplayString(), reason ) ); } } } void Part::setWindowTitleFromDocument() { // If 'DocumentTitle' should be used, check if the document has one. If // either case is false, use the file name. QString title = Okular::Settings::displayDocumentNameOrPath() == Okular::Settings::EnumDisplayDocumentNameOrPath::Path ? realUrl().toDisplayString(QUrl::PreferLocalFile) : realUrl().fileName(); if ( Okular::Settings::displayDocumentTitle() ) { const QString docTitle = m_document->metaData( QStringLiteral("DocumentTitle") ).toString(); if ( !docTitle.isEmpty() && !docTitle.trimmed().isEmpty() ) { title = docTitle; } } emit setWindowCaption( title ); } KConfigDialog * Part::slotGeneratorPreferences( ) { // Create dialog KConfigDialog * dialog = new KConfigDialog( m_pageView, QStringLiteral("generator_prefs"), Okular::Settings::self() ); dialog->setAttribute( Qt::WA_DeleteOnClose ); if( m_embedMode == ViewerWidgetMode ) { dialog->setWindowTitle( i18n( "Configure Viewer Backends" ) ); } else { dialog->setWindowTitle( i18n( "Configure Backends" ) ); } m_document->fillConfigDialog( dialog ); // Show it dialog->setWindowModality( Qt::ApplicationModal ); dialog->show(); return dialog; } void Part::notifySetup( const QVector< Okular::Page * > & /*pages*/, int setupFlags ) { // Hide the migration message if the user has just migrated. Otherwise, // if m_migrationMessage is already hidden, this does nothing. if ( !m_document->isDocdataMigrationNeeded() ) m_migrationMessage->animatedHide(); if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) ) return; rebuildBookmarkMenu(); updateAboutBackendAction(); m_findBar->resetSearch(); m_searchWidget->setEnabled( m_document->supportsSearching() ); } void Part::notifyViewportChanged( bool /*smoothMove*/ ) { updateViewActions(); } void Part::notifyPageChanged( int page, int flags ) { if ( !(flags & Okular::DocumentObserver::Bookmark ) ) return; rebuildBookmarkMenu(); if ( page == m_document->viewport().pageNumber ) updateBookmarksActions(); } void Part::goToPage(uint i) { if ( i <= m_document->pages() ) m_document->setViewportPage( i - 1 ); } void Part::openDocument( const QString &doc ) { openUrl( QUrl::fromUserInput( doc ) ); } uint Part::pages() { return m_document->pages(); } uint Part::currentPage() { return m_document->pages() ? m_document->currentPage() + 1 : 0; } QString Part::currentDocument() { return m_document->currentDocument().toDisplayString(QUrl::PreferLocalFile); } QString Part::documentMetaData( const QString &metaData ) const { const Okular::DocumentInfo info = m_document->documentInfo(); return info.get( metaData ); } bool Part::slotImportPSFile() { QString app = QStandardPaths::findExecutable(QStringLiteral("ps2pdf") ); if ( app.isEmpty() ) { // TODO point the user to their distro packages? KMessageBox::error( widget(), i18n( "The program \"ps2pdf\" was not found, so Okular can not import PS files using it." ), i18n("ps2pdf not found") ); return false; } QMimeDatabase mimeDatabase; QString filter = i18n("PostScript files (%1)", mimeDatabase.mimeTypeForName(QStringLiteral("application/postscript")).globPatterns().join(QLatin1Char(' '))); QUrl url = QFileDialog::getOpenFileUrl( widget(), QString(), QUrl(), filter ); if ( url.isLocalFile() ) { QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.pdf")); tf.setAutoRemove( false ); if ( !tf.open() ) return false; m_temporaryLocalFile = tf.fileName(); tf.close(); setLocalFilePath( url.toLocalFile() ); QStringList args; QProcess *p = new QProcess(); args << url.toLocalFile() << m_temporaryLocalFile; m_pageView->displayMessage(i18n("Importing PS file as PDF (this may take a while)...")); connect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(psTransformEnded(int,QProcess::ExitStatus))); p->start(app, args); return true; } m_temporaryLocalFile.clear(); return false; } void Part::setFileToWatch( const QString &filePath ) { if ( !m_watchedFilePath.isEmpty() ) unsetFileToWatch(); const QFileInfo fi(filePath); m_watchedFilePath = filePath; m_watcher->addFile( m_watchedFilePath ); if ( fi.isSymLink() ) { m_watchedFileSymlinkTarget = fi.readLink(); m_watcher->addFile( m_watchedFileSymlinkTarget ); } else { m_watchedFileSymlinkTarget.clear(); } } void Part::unsetFileToWatch() { if ( m_watchedFilePath.isEmpty() ) return; m_watcher->removeFile( m_watchedFilePath ); if ( !m_watchedFileSymlinkTarget.isEmpty() ) m_watcher->removeFile( m_watchedFileSymlinkTarget ); m_watchedFilePath.clear(); m_watchedFileSymlinkTarget.clear(); } Document::OpenResult Part::doOpenFile( const QMimeType &mimeA, const QString &fileNameToOpenA, bool *isCompressedFile ) { QMimeDatabase db; Document::OpenResult openResult = Document::OpenError; bool uncompressOk = true; QMimeType mime = mimeA; QString fileNameToOpen = fileNameToOpenA; KFilterDev::CompressionType compressionType = compressionTypeFor( mime.name() ); if ( compressionType != KFilterDev::None ) { *isCompressedFile = true; uncompressOk = handleCompressed( fileNameToOpen, localFilePath(), compressionType ); mime = db.mimeTypeForFile( fileNameToOpen ); } else { *isCompressedFile = false; } if ( m_swapInsteadOfOpening ) { m_swapInsteadOfOpening = false; if ( !uncompressOk ) return Document::OpenError; if ( mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) ) { isDocumentArchive = true; if (!m_document->swapBackingFileArchive( fileNameToOpen, url() )) return Document::OpenError; } else { isDocumentArchive = false; if (!m_document->swapBackingFile( fileNameToOpen, url() )) return Document::OpenError; } m_fileLastModified = QFileInfo( localFilePath() ).lastModified(); return Document::OpenSuccess; } isDocumentArchive = false; if ( uncompressOk ) { if ( mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) ) { openResult = m_document->openDocumentArchive( fileNameToOpen, url() ); isDocumentArchive = true; } else { openResult = m_document->openDocument( fileNameToOpen, url(), mime ); } m_documentOpenWithPassword = false; #ifdef WITH_KWALLET // if the file didn't open correctly it might be encrypted, so ask for a pass QString walletName, walletFolder, walletKey; m_document->walletDataForFile(fileNameToOpen, &walletName, &walletFolder, &walletKey); bool firstInput = true; bool triedWallet = false; KWallet::Wallet * wallet = nullptr; bool keep = true; while ( openResult == Document::OpenNeedsPassword ) { QString password; // 1.A. try to retrieve the first password from the kde wallet system if ( !triedWallet && !walletKey.isNull() ) { const WId parentwid = widget()->effectiveWinId(); wallet = KWallet::Wallet::openWallet( walletName, parentwid ); if ( wallet ) { // use the KPdf folder (and create if missing) if ( !wallet->hasFolder( walletFolder ) ) wallet->createFolder( walletFolder ); wallet->setFolder( walletFolder ); // look for the pass in that folder QString retrievedPass; if ( !wallet->readPassword( walletKey, retrievedPass ) ) password = retrievedPass; } triedWallet = true; } // 1.B. if not retrieved, ask the password using the kde password dialog if ( password.isNull() ) { QString prompt; if ( firstInput ) prompt = i18n( "Please enter the password to read the document:" ); else prompt = i18n( "Incorrect password. Try again:" ); firstInput = false; // if the user presses cancel, abort opening KPasswordDialog dlg( widget(), wallet ? KPasswordDialog::ShowKeepPassword : KPasswordDialog::KPasswordDialogFlags() ); dlg.setWindowTitle( i18n( "Document Password" ) ); dlg.setPrompt( prompt ); if( !dlg.exec() ) break; password = dlg.password(); if ( wallet ) keep = dlg.keepPassword(); } // 2. reopen the document using the password if ( mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) ) { openResult = m_document->openDocumentArchive( fileNameToOpen, url(), password ); isDocumentArchive = true; } else { openResult = m_document->openDocument( fileNameToOpen, url(), mime, password ); } if ( openResult == Document::OpenSuccess ) { m_documentOpenWithPassword = true; // 3. if the password is correct and the user chose to remember it, store it to the wallet if (wallet && /*safety check*/ wallet->isOpen() && keep ) { wallet->writePassword( walletKey, password ); } } } #endif } if ( openResult == Document::OpenSuccess ) { m_fileLastModified = QFileInfo( localFilePath() ).lastModified(); } return openResult; } bool Part::openFile() { QList mimes; QString fileNameToOpen = localFilePath(); const bool isstdin = url().isLocalFile() && url().fileName() == QLatin1String( "-" ); const QFileInfo fileInfo( fileNameToOpen ); if ( (!isstdin) && (!fileInfo.exists()) ) return false; QMimeDatabase db; QMimeType pathMime = db.mimeTypeForFile( fileNameToOpen ); if ( !arguments().mimeType().isEmpty() ) { QMimeType argMime = db.mimeTypeForName( arguments().mimeType() ); // Select the "childmost" mimetype, if none of them // inherits the other trust more what pathMime says // but still do a second try if that one fails if ( argMime.inherits( pathMime.name() ) ) { mimes << argMime; } else if ( pathMime.inherits( argMime.name() ) ) { mimes << pathMime; } else { mimes << pathMime << argMime; } if (mimes[0].name() == QLatin1String("text/plain")) { QMimeType contentMime = db.mimeTypeForFile(fileNameToOpen, QMimeDatabase::MatchContent); mimes.prepend( contentMime ); } } else { mimes << pathMime; } QMimeType mime; Document::OpenResult openResult = Document::OpenError; bool isCompressedFile = false; while ( !mimes.isEmpty() && openResult == Document::OpenError ) { mime = mimes.takeFirst(); openResult = doOpenFile( mime, fileNameToOpen, &isCompressedFile ); } bool canSearch = m_document->supportsSearching(); emit mimeTypeChanged( mime ); // update one-time actions const bool ok = openResult == Document::OpenSuccess; emit enableCloseAction( ok ); m_find->setEnabled( ok && canSearch ); m_findNext->setEnabled( ok && canSearch ); m_findPrev->setEnabled( ok && canSearch ); if( m_save ) m_save->setEnabled( ok && !( isstdin || mime.inherits( "inode/directory" ) ) ); if( m_saveAs ) m_saveAs->setEnabled( ok && !( isstdin || mime.inherits( "inode/directory" ) ) ); emit enablePrintAction( ok && m_document->printingSupport() != Okular::Document::NoPrinting ); m_printPreview->setEnabled( ok && m_document->printingSupport() != Okular::Document::NoPrinting ); m_showProperties->setEnabled( ok ); bool hasEmbeddedFiles = ok && m_document->embeddedFiles() && m_document->embeddedFiles()->count() > 0; if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( hasEmbeddedFiles ); m_topMessage->setVisible( hasEmbeddedFiles && Okular::Settings::showOSD() ); m_migrationMessage->setVisible( m_document->isDocdataMigrationNeeded() ); // Warn the user that XFA forms are not supported yet (NOTE: poppler generator only) if ( ok && m_document->metaData( QStringLiteral("HasUnsupportedXfaForm") ).toBool() == true ) { m_formsMessage->setText( i18n( "This document has XFA forms, which are currently unsupported." ) ); m_formsMessage->setIcon( QIcon::fromTheme( QStringLiteral("dialog-warning") ) ); m_formsMessage->setMessageType( KMessageWidget::Warning ); m_formsMessage->setVisible( true ); } // m_pageView->toggleFormsAction() may be null on dummy mode else if ( ok && m_pageView->toggleFormsAction() && m_pageView->toggleFormsAction()->isEnabled() ) { m_formsMessage->setText( i18n( "This document has forms. Click on the button to interact with them, or use View -> Show Forms." ) ); m_formsMessage->setMessageType( KMessageWidget::Information ); m_formsMessage->setVisible( true ); } else { m_formsMessage->setVisible( false ); } if ( m_showPresentation ) m_showPresentation->setEnabled( ok ); if ( ok ) { if ( m_exportAs ) { m_exportFormats = m_document->exportFormats(); QList::ConstIterator it = m_exportFormats.constBegin(); QList::ConstIterator itEnd = m_exportFormats.constEnd(); QMenu *menu = m_exportAs->menu(); for ( ; it != itEnd; ++it ) { menu->addAction( actionForExportFormat( *it ) ); } } #if PURPOSE_FOUND if ( m_share ) { m_shareMenu->model()->setInputData(QJsonObject{ { QStringLiteral("mimeType"), mime.name() }, { QStringLiteral("urls"), QJsonArray{ url().toString() } } }); m_shareMenu->model()->setPluginType( QStringLiteral("Export") ); m_shareMenu->reload(); } #endif if ( isCompressedFile ) { m_realUrl = url(); } #ifdef OKULAR_KEEP_FILE_OPEN if ( keepFileOpen() ) m_keeper->open( fileNameToOpen ); #endif } if ( m_exportAsText ) m_exportAsText->setEnabled( ok && m_document->canExportToText() ); if ( m_exportAs ) m_exportAs->setEnabled( ok ); #if PURPOSE_FOUND if ( m_share ) m_share->setEnabled( ok ); #endif // update viewing actions updateViewActions(); m_fileWasRemoved = false; if ( !ok ) { // if can't open document, update windows so they display blank contents m_pageView->viewport()->update(); m_thumbnailList->update(); setUrl( QUrl() ); return false; } // set the file to the fileWatcher if ( url().isLocalFile() ) setFileToWatch( localFilePath() ); // if the 'OpenTOC' flag is set, open the TOC if ( m_document->metaData( QStringLiteral("OpenTOC") ).toBool() && m_sidebar->isItemEnabled( m_toc ) && !m_sidebar->isCollapsed() && m_sidebar->currentItem() != m_toc ) { m_sidebar->setCurrentItem( m_toc, Sidebar::DoNotUncollapseIfCollapsed ); } // if the 'StartFullScreen' flag is set and we're not in viewer widget mode, or the command line flag was // specified, start presentation const bool presentationBecauseOfDocumentMetadata = ( m_embedMode != ViewerWidgetMode ) && m_document->metaData( QStringLiteral("StartFullScreen") ).toBool(); if ( presentationBecauseOfDocumentMetadata || m_cliPresentation ) { bool goAheadWithPresentationMode = true; if ( !m_cliPresentation ) { const QString text = i18n( "This document wants to be shown full screen.\n" "Leave normal mode and enter presentation mode?" ); const QString caption = i18n( "Request to Change Viewing Mode" ); const KGuiItem yesItem = KGuiItem( i18n( "Enter Presentation Mode" ), QStringLiteral("dialog-ok") ); const KGuiItem noItem = KGuiItem( i18n( "Deny Request" ), QStringLiteral("dialog-cancel") ); const int result = KMessageBox::questionYesNo( widget(), text, caption, yesItem, noItem ); if ( result == KMessageBox::No ) goAheadWithPresentationMode = false; } m_cliPresentation = false; if ( goAheadWithPresentationMode ) QMetaObject::invokeMethod( this, "slotShowPresentation", Qt::QueuedConnection ); } m_generatorGuiClient = factory() ? m_document->guiClient() : nullptr; if ( m_generatorGuiClient ) factory()->addClient( m_generatorGuiClient ); if ( m_cliPrint ) { m_cliPrint = false; slotPrint(); } else if ( m_cliPrintAndExit ) { slotPrint(); } return true; } bool Part::openUrl( const QUrl &url ) { return openUrl( url, false /* swapInsteadOfOpening */ ); } bool Part::openUrl( const QUrl &_url, bool swapInsteadOfOpening ) { /* Store swapInsteadOfOpening, so that closeUrl and openFile will be able * to read it */ m_swapInsteadOfOpening = swapInsteadOfOpening; // The subsequent call to closeUrl clears the arguments. // We want to save them and restore them later. const KParts::OpenUrlArguments args = arguments(); // Close current document if any if ( !closeUrl() ) return false; setArguments(args); QUrl url( _url ); if ( url.hasFragment() ) { const QString dest = url.fragment(QUrl::FullyDecoded); bool ok = true; const int page = dest.toInt( &ok ); if ( ok ) { Okular::DocumentViewport vp( page - 1 ); vp.rePos.enabled = true; vp.rePos.normalizedX = 0; vp.rePos.normalizedY = 0; vp.rePos.pos = Okular::DocumentViewport::TopLeft; m_document->setNextDocumentViewport( vp ); } else { m_document->setNextDocumentDestination( dest ); } url.setFragment( QString() ); } // this calls in sequence the 'closeUrl' and 'openFile' methods bool openOk = KParts::ReadWritePart::openUrl( url ); if ( openOk ) { m_viewportDirty.pageNumber = -1; setWindowTitleFromDocument(); } else { resetStartArguments(); KMessageBox::error( widget(), i18n("Could not open %1", url.toDisplayString() ) ); } return openOk; } bool Part::queryClose() { if ( !isReadWrite() || !isModified() ) return true; // TODO When we get different saving backends we need to query the backend // as to if it can save changes even if the open file has been modified, // since we only have poppler as saving backend for now we're skipping that check if ( m_fileLastModified != QFileInfo( localFilePath() ).lastModified() ) { int res; if ( m_isReloading ) { res = KMessageBox::warningYesNo( widget(), i18n( "There are unsaved changes, and the file '%1' has been modified by another program. Your changes will be lost, because the file can no longer be saved.
Do you want to continue reloading the file?", url().fileName() ), i18n( "File Changed" ), KGuiItem( i18n( "Continue Reloading" ) ), // <- KMessageBox::Yes KGuiItem( i18n( "Abort Reloading" ) )); } else { res = KMessageBox::warningYesNo( widget(), i18n( "There are unsaved changes, and the file '%1' has been modified by another program. Your changes will be lost, because the file can no longer be saved.
Do you want to continue closing the file?", url().fileName() ), i18n( "File Changed" ), KGuiItem( i18n( "Continue Closing" ) ), // <- KMessageBox::Yes KGuiItem( i18n( "Abort Closing" ) )); } return res == KMessageBox::Yes; } const int res = KMessageBox::warningYesNoCancel( widget(), i18n( "Do you want to save your changes to \"%1\" or discard them?", url().fileName() ), i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() ); switch ( res ) { case KMessageBox::Yes: // Save saveFile(); return !isModified(); // Only allow closing if file was really saved case KMessageBox::No: // Discard return true; default: // Cancel return false; } } bool Part::closeUrl(bool promptToSave) { if ( promptToSave && !queryClose() ) return false; if ( m_swapInsteadOfOpening ) { // If we're swapping the backing file, we don't want to close the // current one when openUrl() calls us internally return true; // pretend it worked } m_document->setHistoryClean( true ); if (!m_temporaryLocalFile.isNull() && m_temporaryLocalFile != localFilePath()) { QFile::remove( m_temporaryLocalFile ); m_temporaryLocalFile.clear(); } slotHidePresentation(); emit enableCloseAction( false ); m_find->setEnabled( false ); m_findNext->setEnabled( false ); m_findPrev->setEnabled( false ); if( m_save ) m_save->setEnabled( false ); if( m_saveAs ) m_saveAs->setEnabled( false ); m_printPreview->setEnabled( false ); m_showProperties->setEnabled( false ); if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( false ); if ( m_exportAs ) m_exportAs->setEnabled( false ); if ( m_exportAsText ) m_exportAsText->setEnabled( false ); m_exportFormats.clear(); if ( m_exportAs ) { QMenu *menu = m_exportAs->menu(); QList acts = menu->actions(); int num = acts.count(); for ( int i = 1; i < num; ++i ) { menu->removeAction( acts.at(i) ); delete acts.at(i); } } #if PURPOSE_FOUND if ( m_share ) { m_share->setEnabled(false); m_shareMenu->clear(); } #endif if ( m_showPresentation ) m_showPresentation->setEnabled( false ); emit setWindowCaption(QLatin1String("")); emit enablePrintAction(false); m_realUrl = QUrl(); if ( url().isLocalFile() ) unsetFileToWatch(); m_fileWasRemoved = false; if ( m_generatorGuiClient ) factory()->removeClient( m_generatorGuiClient ); m_generatorGuiClient = nullptr; m_document->closeDocument(); m_fileLastModified = QDateTime(); updateViewActions(); delete m_tempfile; m_tempfile = nullptr; if ( widget() ) { m_searchWidget->clearText(); m_migrationMessage->setVisible( false ); m_topMessage->setVisible( false ); m_formsMessage->setVisible( false ); } #ifdef OKULAR_KEEP_FILE_OPEN m_keeper->close(); #endif bool r = KParts::ReadWritePart::closeUrl(); setUrl(QUrl()); return r; } bool Part::closeUrl() { return closeUrl( true ); } void Part::guiActivateEvent(KParts::GUIActivateEvent *event) { updateViewActions(); KParts::ReadWritePart::guiActivateEvent(event); setWindowTitleFromDocument(); } void Part::close() { if ( m_embedMode == NativeShellMode ) { closeUrl(); } else KMessageBox::information( widget(), i18n( "This link points to a close document action that does not work when using the embedded viewer." ), QString(), QStringLiteral("warnNoCloseIfNotInOkular") ); } void Part::cannotQuit() { KMessageBox::information( widget(), i18n( "This link points to a quit application action that does not work when using the embedded viewer." ), QString(), QStringLiteral("warnNoQuitIfNotInOkular") ); } void Part::slotShowLeftPanel() { bool showLeft = m_showLeftPanel->isChecked(); Okular::Settings::setShowLeftPanel( showLeft ); Okular::Settings::self()->save(); // show/hide left panel m_sidebar->setSidebarVisibility( showLeft ); } void Part::slotShowBottomBar() { const bool showBottom = m_showBottomBar->isChecked(); Okular::Settings::setShowBottomBar( showBottom ); Okular::Settings::self()->save(); // show/hide bottom bar m_bottomBar->setVisible( showBottom ); } void Part::slotFileDirty( const QString& path ) { // The beauty of this is that each start cancels the previous one. // This means that timeout() is only fired when there have // no changes to the file for the last 750 milisecs. // This ensures that we don't update on every other byte that gets // written to the file. if ( path == localFilePath() ) { // Only start watching the file in case if it wasn't removed if (QFile::exists(localFilePath())) m_dirtyHandler->start( 750 ); else m_fileWasRemoved = true; } else { const QFileInfo fi(localFilePath()); if ( fi.absolutePath() == path ) { // Our parent has been dirtified if (!QFile::exists(localFilePath())) { m_fileWasRemoved = true; } else if (m_fileWasRemoved && QFile::exists(localFilePath())) { // we need to watch the new file unsetFileToWatch(); setFileToWatch( localFilePath() ); m_dirtyHandler->start( 750 ); } } else if ( fi.isSymLink() && fi.readLink() == path ) { if ( QFile::exists( fi.readLink() )) m_dirtyHandler->start( 750 ); else m_fileWasRemoved = true; } } } // Attempt to reload the document, one or more times, optionally from a different URL bool Part::slotAttemptReload( bool oneShot, const QUrl &newUrl ) { // Skip reload when another reload is already in progress if ( m_isReloading ) { return false; } QScopedValueRollback rollback(m_isReloading, true); bool tocReloadPrepared = false; // do the following the first time the file is reloaded if ( m_viewportDirty.pageNumber == -1 ) { // store the url of the current document m_oldUrl = newUrl.isEmpty() ? url() : newUrl; // store the current viewport m_viewportDirty = m_document->viewport(); // store the current toolbox pane m_dirtyToolboxItem = m_sidebar->currentItem(); m_wasSidebarVisible = m_sidebar->isSidebarVisible(); m_wasSidebarCollapsed = m_sidebar->isCollapsed(); // store if presentation view was open m_wasPresentationOpen = ((PresentationWidget*)m_presentationWidget != nullptr); // preserves the toc state after reload m_toc->prepareForReload(); tocReloadPrepared = true; // store the page rotation m_dirtyPageRotation = m_document->rotation(); // inform the user about the operation in progress // TODO: Remove this line and integrate reload info in queryClose m_pageView->displayMessage( i18n("Reloading the document...") ); } // close and (try to) reopen the document if ( !closeUrl() ) { m_viewportDirty.pageNumber = -1; if ( tocReloadPrepared ) { m_toc->rollbackReload(); } return false; } if ( tocReloadPrepared ) m_toc->finishReload(); // inform the user about the operation in progress m_pageView->displayMessage( i18n("Reloading the document...") ); bool reloadSucceeded = false; if ( KParts::ReadWritePart::openUrl( m_oldUrl ) ) { // on successful opening, restore the previous viewport if ( m_viewportDirty.pageNumber >= (int) m_document->pages() ) m_viewportDirty.pageNumber = (int) m_document->pages() - 1; m_document->setViewport( m_viewportDirty ); m_oldUrl = QUrl(); m_viewportDirty.pageNumber = -1; m_document->setRotation( m_dirtyPageRotation ); if ( m_sidebar->currentItem() != m_dirtyToolboxItem && m_sidebar->isItemEnabled( m_dirtyToolboxItem ) && !m_sidebar->isCollapsed() ) { m_sidebar->setCurrentItem( m_dirtyToolboxItem ); } if ( m_sidebar->isSidebarVisible() != m_wasSidebarVisible ) { m_sidebar->setSidebarVisibility( m_wasSidebarVisible ); } if ( m_sidebar->isCollapsed() != m_wasSidebarCollapsed ) { m_sidebar->setCollapsed( m_wasSidebarCollapsed ); } if (m_wasPresentationOpen) slotShowPresentation(); emit enablePrintAction(true && m_document->printingSupport() != Okular::Document::NoPrinting); reloadSucceeded = true; } else if ( !oneShot ) { // start watching the file again (since we dropped it on close) setFileToWatch( localFilePath() ); m_dirtyHandler->start( 750 ); } return reloadSucceeded; } void Part::updateViewActions() { bool opened = m_document->pages() > 0; if ( opened ) { m_gotoPage->setEnabled( m_document->pages() > 1 ); // Check if you are at the beginning or not if (m_document->currentPage() != 0) { m_beginningOfDocument->setEnabled( true ); m_prevPage->setEnabled( true ); } else { if (m_pageView->verticalScrollBar()->value() != 0) { // The page isn't at the very beginning m_beginningOfDocument->setEnabled( true ); } else { // The page is at the very beginning of the document m_beginningOfDocument->setEnabled( false ); } // The document is at the first page, you can go to a page before m_prevPage->setEnabled( false ); } if (m_document->pages() == m_document->currentPage() + 1 ) { // If you are at the end, disable go to next page m_nextPage->setEnabled( false ); if (m_pageView->verticalScrollBar()->value() == m_pageView->verticalScrollBar()->maximum()) { // If you are the end of the page of the last document, you can't go to the last page m_endOfDocument->setEnabled( false ); } else { // Otherwise you can move to the endif m_endOfDocument->setEnabled( true ); } } else { // If you are not at the end, enable go to next page m_nextPage->setEnabled( true ); m_endOfDocument->setEnabled( true ); } if (m_historyBack) m_historyBack->setEnabled( !m_document->historyAtBegin() ); if (m_historyNext) m_historyNext->setEnabled( !m_document->historyAtEnd() ); m_reload->setEnabled( true ); if (m_copy) m_copy->setEnabled( true ); if (m_selectAll) m_selectAll->setEnabled( true ); } else { m_gotoPage->setEnabled( false ); m_beginningOfDocument->setEnabled( false ); m_endOfDocument->setEnabled( false ); m_prevPage->setEnabled( false ); m_nextPage->setEnabled( false ); if (m_historyBack) m_historyBack->setEnabled( false ); if (m_historyNext) m_historyNext->setEnabled( false ); m_reload->setEnabled( false ); if (m_copy) m_copy->setEnabled( false ); if (m_selectAll) m_selectAll->setEnabled( false ); } if ( factory() ) { QWidget *menu = factory()->container(QStringLiteral("menu_okular_part_viewer"), this); if (menu) menu->setEnabled( opened ); menu = factory()->container(QStringLiteral("view_orientation"), this); if (menu) menu->setEnabled( opened ); } emit viewerMenuStateChange( opened ); updateBookmarksActions(); } void Part::updateBookmarksActions() { bool opened = m_document->pages() > 0; if ( opened ) { m_addBookmark->setEnabled( true ); if ( m_document->bookmarkManager()->isBookmarked( m_document->viewport() ) ) { m_addBookmark->setText( i18n( "Remove Bookmark" ) ); m_addBookmark->setIcon( QIcon::fromTheme( QStringLiteral("edit-delete-bookmark") ) ); m_renameBookmark->setEnabled( true ); } else { m_addBookmark->setText( m_addBookmarkText ); m_addBookmark->setIcon( m_addBookmarkIcon ); m_renameBookmark->setEnabled( false ); } } else { m_addBookmark->setEnabled( false ); m_addBookmark->setText( m_addBookmarkText ); m_addBookmark->setIcon( m_addBookmarkIcon ); m_renameBookmark->setEnabled( false ); } } void Part::enableTOC(bool enable) { m_sidebar->setItemEnabled(m_toc, enable); // If present, show the TOC when a document is opened if ( enable && m_sidebar->currentItem() != m_toc ) { m_sidebar->setCurrentItem( m_toc, Sidebar::DoNotUncollapseIfCollapsed ); } } void Part::slotRebuildBookmarkMenu() { rebuildBookmarkMenu(); } void Part::enableLayers(bool enable) { m_sidebar->setItemVisible( m_layers, enable ); } void Part::slotShowFindBar() { m_findBar->show(); m_findBar->focusAndSetCursor(); m_closeFindBar->setEnabled( true ); } void Part::slotHideFindBar() { if ( m_findBar->maybeHide() ) { m_pageView->setFocus(); m_closeFindBar->setEnabled( false ); } } //BEGIN go to page dialog class GotoPageDialog : public QDialog { Q_OBJECT public: GotoPageDialog(QWidget *p, int current, int max) : QDialog(p) { setWindowTitle(i18n("Go to Page")); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->setMargin(6); QHBoxLayout *midLayout = new QHBoxLayout(); spinbox = new QSpinBox(this); spinbox->setRange(1, max); spinbox->setValue(current); spinbox->setFocus(); slider = new QSlider(Qt::Horizontal, this); slider->setRange(1, max); slider->setValue(current); slider->setSingleStep(1); slider->setTickPosition(QSlider::TicksBelow); slider->setTickInterval(max/10); connect(slider, &QSlider::valueChanged, spinbox, &QSpinBox::setValue); connect(spinbox, static_cast(&QSpinBox::valueChanged), slider, &QSlider::setValue); QLabel *label = new QLabel(i18n("&Page:"), this); label->setBuddy(spinbox); topLayout->addWidget(label); topLayout->addLayout(midLayout); midLayout->addWidget(slider); midLayout->addWidget(spinbox); // A little bit extra space topLayout->addStretch(10); topLayout->addWidget(buttonBox); spinbox->setFocus(); } int getPage() const { return spinbox->value(); } protected: QSpinBox *spinbox; QSlider *slider; QDialogButtonBox *buttonBox; }; //END go to page dialog void Part::slotGoToPage() { GotoPageDialog pageDialog( m_pageView, m_document->currentPage() + 1, m_document->pages() ); if ( pageDialog.exec() == QDialog::Accepted ) m_document->setViewportPage( pageDialog.getPage() - 1 ); } void Part::slotPreviousPage() { if ( m_document->isOpened() && !(m_document->currentPage() < 1) ) m_document->setViewportPage( m_document->currentPage() - 1 ); } void Part::slotNextPage() { if ( m_document->isOpened() && m_document->currentPage() < (m_document->pages() - 1) ) m_document->setViewportPage( m_document->currentPage() + 1 ); } void Part::slotGotoFirst() { if ( m_document->isOpened() ) { m_document->setViewportPage( 0 ); m_beginningOfDocument->setEnabled( false ); } } void Part::slotGotoLast() { if ( m_document->isOpened() ) { DocumentViewport endPage(m_document->pages() -1 ); endPage.rePos.enabled = true; endPage.rePos.normalizedX = 0; endPage.rePos.normalizedY = 1; endPage.rePos.pos = Okular::DocumentViewport::TopLeft; m_document->setViewport(endPage); m_endOfDocument->setEnabled(false); } } void Part::slotHistoryBack() { m_document->setPrevViewport(); } void Part::slotHistoryNext() { m_document->setNextViewport(); } void Part::slotAddBookmark() { DocumentViewport vp = m_document->viewport(); if ( m_document->bookmarkManager()->isBookmarked( vp ) ) { m_document->bookmarkManager()->removeBookmark( vp ); } else { m_document->bookmarkManager()->addBookmark( vp ); } } void Part::slotRenameBookmark( const DocumentViewport &viewport ) { Q_ASSERT(m_document->bookmarkManager()->isBookmarked( viewport )); if ( m_document->bookmarkManager()->isBookmarked( viewport ) ) { KBookmark bookmark = m_document->bookmarkManager()->bookmark( viewport ); const QString newName = QInputDialog::getText(widget(), i18n( "Rename Bookmark" ), i18n( "Enter the new name of the bookmark:" ), QLineEdit::Normal, bookmark.fullText()); if (!newName.isEmpty()) { m_document->bookmarkManager()->renameBookmark(&bookmark, newName); } } } void Part::slotRenameBookmarkFromMenu() { QAction *action = dynamic_cast(sender()); Q_ASSERT( action ); if ( action ) { DocumentViewport vp( action->data().toString() ); slotRenameBookmark( vp ); } } void Part::slotRemoveBookmarkFromMenu() { QAction *action = dynamic_cast(sender()); Q_ASSERT( action ); if ( action ) { DocumentViewport vp ( action->data().toString() ); slotRemoveBookmark( vp ); } } void Part::slotRemoveBookmark(const DocumentViewport &viewport) { Q_ASSERT(m_document->bookmarkManager()->isBookmarked( viewport )); if ( m_document->bookmarkManager()->isBookmarked( viewport ) ) { m_document->bookmarkManager()->removeBookmark( viewport ); } } void Part::slotRenameCurrentViewportBookmark() { slotRenameBookmark( m_document->viewport() ); } bool Part::aboutToShowContextMenu(QMenu * /*menu*/, QAction *action, QMenu *contextMenu) { KBookmarkAction *ba = dynamic_cast(action); if (ba != nullptr) { QAction *separatorAction = contextMenu->addSeparator(); separatorAction->setObjectName(QStringLiteral("OkularPrivateRenameBookmarkActions")); QAction *renameAction = contextMenu->addAction( QIcon::fromTheme( QStringLiteral("edit-rename") ), i18n( "Rename this Bookmark" ), this, SLOT(slotRenameBookmarkFromMenu()) ); renameAction->setData(ba->property("htmlRef").toString()); renameAction->setObjectName(QStringLiteral("OkularPrivateRenameBookmarkActions")); QAction *deleteAction = contextMenu->addAction( QIcon::fromTheme( QStringLiteral("list-remove") ), i18n("Remove this Bookmark"), this, SLOT(slotRemoveBookmarkFromMenu())); deleteAction->setData(ba->property("htmlRef").toString()); deleteAction->setObjectName(QStringLiteral("OkularPrivateRenameBookmarkActions")); } return ba; } void Part::slotPreviousBookmark() { const KBookmark bookmark = m_document->bookmarkManager()->previousBookmark( m_document->viewport() ); if ( !bookmark.isNull() ) { DocumentViewport vp( bookmark.url().fragment(QUrl::FullyDecoded) ); m_document->setViewport( vp ); } } void Part::slotNextBookmark() { const KBookmark bookmark = m_document->bookmarkManager()->nextBookmark( m_document->viewport() ); if ( !bookmark.isNull() ) { DocumentViewport vp( bookmark.url().fragment(QUrl::FullyDecoded) ); m_document->setViewport( vp ); } } void Part::slotFind() { // when in presentation mode, there's already a search bar, taking care of // the 'find' requests if ( (PresentationWidget*)m_presentationWidget != nullptr ) { m_presentationWidget->slotFind(); } else { slotShowFindBar(); } } void Part::slotFindNext() { if (m_findBar->isHidden()) slotShowFindBar(); else m_findBar->findNext(); } void Part::slotFindPrev() { if (m_findBar->isHidden()) slotShowFindBar(); else m_findBar->findPrev(); } bool Part::saveFile() { if ( !isModified() ) return true; else return saveAs( url() ); } bool Part::slotSaveFileAs( bool showOkularArchiveAsDefaultFormat ) { if ( m_embedMode == PrintPreviewMode ) return false; // Determine the document's mimetype QMimeDatabase db; QMimeType originalMimeType; const QString typeName = m_document->documentInfo().get( DocumentInfo::MimeType ); if ( !typeName.isEmpty() ) originalMimeType = db.mimeTypeForName( typeName ); // What data would we lose if we saved natively? bool wontSaveForms, wontSaveAnnotations; checkNativeSaveDataLoss(&wontSaveForms, &wontSaveAnnotations); const QMimeType okularArchiveMimeType = db.mimeTypeForName( QStringLiteral("application/vnd.kde.okular-archive") ); // Prepare "Save As" dialog const QString originalMimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", originalMimeType.comment(), originalMimeType.globPatterns().join(QLatin1Char(' '))); const QString okularArchiveMimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", okularArchiveMimeType.comment(), okularArchiveMimeType.globPatterns().join(QLatin1Char(' '))); // What format choice should we show as default? QString selectedFilter = (isDocumentArchive || showOkularArchiveAsDefaultFormat || wontSaveForms || wontSaveAnnotations) ? okularArchiveMimeTypeFilter : originalMimeTypeFilter; QString filter = originalMimeTypeFilter + QStringLiteral(";;") + okularArchiveMimeTypeFilter; const QUrl saveUrl = QFileDialog::getSaveFileUrl(widget(), i18n("Save As"), url(), filter, &selectedFilter); if ( !saveUrl.isValid() || saveUrl.isEmpty() ) return false; // Has the user chosen to save in .okular archive format? const bool saveAsOkularArchive = ( selectedFilter == okularArchiveMimeTypeFilter ); return saveAs( saveUrl, saveAsOkularArchive ? SaveAsOkularArchive : NoSaveAsFlags ); } bool Part::saveAs(const QUrl & saveUrl) { // Save in the same format (.okular vs native) as the current file return saveAs( saveUrl, isDocumentArchive ? SaveAsOkularArchive : NoSaveAsFlags ); } bool Part::saveAs( const QUrl & saveUrl, SaveAsFlags flags ) { // TODO When we get different saving backends we need to query the backend // as to if it can save changes even if the open file has been modified, // since we only have poppler as saving backend for now we're skipping that check if ( m_fileLastModified != QFileInfo( localFilePath() ).lastModified() ) { - QMessageBox::warning( widget(), - i18n( "File Changed" ), - i18n( "The file '%1' has been modified by another program, which means it can no longer be saved.", url().fileName() ) ); + KMessageBox::sorry( widget(), + i18n( "The file '%1' has been modified by another program, which means it can no longer be saved.", url().fileName() ), + i18n( "File Changed" ) + ); return false; } bool hasUserAcceptedReload = false; if ( m_documentOpenWithPassword ) { const int res = KMessageBox::warningYesNo( widget(), i18n( "The current document is protected with a password.
In order to save, the file needs to be reloaded. You will be asked for the password again and your undo/redo history will be lost.
Do you want to continue?" ), i18n( "Save - Warning" ) ); switch ( res ) { case KMessageBox::Yes: hasUserAcceptedReload = true; // do nothing break; case KMessageBox::No: // User said no to continue, so return true even if save didn't happen otherwise we will get an error return true; } } bool setModifiedAfterSave = false; QTemporaryFile tf; QString fileName; if ( !tf.open() ) { KMessageBox::information( widget(), i18n("Could not open the temporary file for saving." ) ); return false; } fileName = tf.fileName(); tf.close(); QScopedPointer tempFile; KIO::Job *copyJob = nullptr; // this will be filled with the job that writes to saveUrl // Does the user want a .okular archive? if ( flags & SaveAsOkularArchive ) { if ( !hasUserAcceptedReload && !m_document->canSwapBackingFile() ) { const int res = KMessageBox::warningYesNo( widget(), i18n( "After saving, the current document format requires the file to be reloaded. Your undo/redo history will be lost.
Do you want to continue?" ), i18n( "Save - Warning" ) ); switch ( res ) { case KMessageBox::Yes: // do nothing break; case KMessageBox::No: // User said no to continue, so return true even if save didn't happen otherwise we will get an error return true; } } if ( !m_document->saveDocumentArchive( fileName ) ) { KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); return false; } copyJob = KIO::file_copy( QUrl::fromLocalFile( fileName ), saveUrl, -1, KIO::Overwrite ); } else { bool wontSaveForms, wontSaveAnnotations; checkNativeSaveDataLoss(&wontSaveForms, &wontSaveAnnotations); // If something can't be saved in this format, ask for confirmation QStringList listOfwontSaves; if ( wontSaveForms ) listOfwontSaves << i18n( "Filled form contents" ); if ( wontSaveAnnotations ) listOfwontSaves << i18n( "User annotations" ); if ( !listOfwontSaves.isEmpty() ) { if ( saveUrl == url() ) { // Save const QString warningMessage = i18n( "You are about to save changes, but the current file format does not support saving the following elements. Please use the Okular document archive format to preserve them." ); const int result = KMessageBox::warningYesNoList( widget(), warningMessage, listOfwontSaves, i18n( "Warning" ), KGuiItem( i18n( "Save as Okular document archive..." ), "document-save-as" ), // <- KMessageBox::Yes KStandardGuiItem::cancel() ); switch (result) { case KMessageBox::Yes: // -> Save as Okular document archive return slotSaveFileAs( true /* showOkularArchiveAsDefaultFormat */ ); default: return false; } } else { // Save as const QString warningMessage = m_document->canSwapBackingFile() ? i18n( "You are about to save changes, but the current file format does not support saving the following elements. Please use the Okular document archive format to preserve them. Click Continue to save the document and discard these elements." ) : i18n( "You are about to save changes, but the current file format does not support saving the following elements. Please use the Okular document archive format to preserve them. Click Continue to save, but you will lose these elements as well as the undo/redo history." ); const QString continueMessage = m_document->canSwapBackingFile() ? i18n( "Continue" ) : i18n( "Continue losing changes" ); const int result = KMessageBox::warningYesNoCancelList( widget(), warningMessage, listOfwontSaves, i18n( "Warning" ), KGuiItem( i18n( "Save as Okular document archive..." ), "document-save-as" ), // <- KMessageBox::Yes KGuiItem( continueMessage, "arrow-right" ) ); // <- KMessageBox::NO switch (result) { case KMessageBox::Yes: // -> Save as Okular document archive return slotSaveFileAs( true /* showOkularArchiveAsDefaultFormat */ ); case KMessageBox::No: // -> Continue setModifiedAfterSave = m_document->canSwapBackingFile(); break; case KMessageBox::Cancel: return false; } } } if ( m_document->canSaveChanges() ) { // If the generator supports saving changes, save them QString errorText; if ( !m_document->saveChanges( fileName, &errorText ) ) { if (errorText.isEmpty()) KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); else KMessageBox::information( widget(), i18n("File could not be saved in '%1'. %2", fileName, errorText ) ); return false; } copyJob = KIO::file_copy( QUrl::fromLocalFile( fileName ), saveUrl, -1, KIO::Overwrite ); } else { // If the generators doesn't support saving changes, we will // just copy the original file. if ( isDocumentArchive ) { // Special case: if the user is extracting the contents of a // .okular archive back to the native format, we can't just copy // the open file (which is a .okular). So let's ask to core to // extract and give us the real file if ( !m_document->extractArchivedFile( fileName ) ) { KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); return false; } copyJob = KIO::file_copy( QUrl::fromLocalFile( fileName ), saveUrl, -1, KIO::Overwrite ); } else { // Otherwise just copy the open file. // make use of the already downloaded (in case of remote URLs) file, // no point in downloading that again QUrl srcUrl = QUrl::fromLocalFile( localFilePath() ); // duh, our local file disappeared... if ( !QFile::exists( localFilePath() ) ) { if ( url().isLocalFile() ) { #ifdef OKULAR_KEEP_FILE_OPEN // local file: try to get it back from the open handle on it tempFile.reset( m_keeper->copyToTemporary() ); if ( tempFile ) srcUrl = KUrl::fromPath( tempFile->fileName() ); #else const QString msg = i18n( "Okular cannot copy %1 to the specified location.\n\nThe document does not exist anymore.", localFilePath() ); KMessageBox::sorry( widget(), msg ); return false; #endif } else { // we still have the original remote URL of the document, // so copy the document from there srcUrl = url(); } } if ( srcUrl != saveUrl ) { copyJob = KIO::file_copy( srcUrl, saveUrl, -1, KIO::Overwrite ); } else { // Don't do a real copy in this case, just update the timestamps copyJob = KIO::setModificationTime( saveUrl, QDateTime::currentDateTime() ); } } } } // Stop watching for changes while we write the new file (useful when // overwriting) if ( url().isLocalFile() ) unsetFileToWatch(); KJobWidgets::setWindow(copyJob, widget()); if ( !copyJob->exec() ) { KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Error: '%2'. Try to save it to another location.", saveUrl.toDisplayString(), copyJob->errorString() ) ); // Restore watcher if ( url().isLocalFile() ) setFileToWatch( localFilePath() ); return false; } m_document->setHistoryClean( true ); if ( m_document->isDocdataMigrationNeeded() ) m_document->docdataMigrationDone(); bool reloadedCorrectly = true; // Make the generator use the new new file instead of the old one if ( m_document->canSwapBackingFile() && !m_documentOpenWithPassword ) { QWidget *currentSidebarItem = m_sidebar->currentItem(); // this calls openFile internally, which in turn actually calls // m_document->swapBackingFile() instead of the regular loadDocument if ( openUrl( saveUrl, true /* swapInsteadOfOpening */ ) ) { if ( setModifiedAfterSave ) { m_document->setHistoryClean( false ); } } else { reloadedCorrectly = false; } if ( m_sidebar->currentItem() != currentSidebarItem ) m_sidebar->setCurrentItem( currentSidebarItem ); } else { // If the generator doesn't support swapping file, then just reload // the document from the new location if ( !slotAttemptReload( true, saveUrl ) ) reloadedCorrectly = false; } // In case of file swapping errors, close the document to avoid inconsistencies if ( !reloadedCorrectly ) { qWarning() << "The document hasn't been reloaded/swapped correctly"; closeUrl(); } // Restore watcher if ( url().isLocalFile() ) setFileToWatch( localFilePath() ); //Set correct permission taking into account the umask value #ifndef Q_OS_WIN const QString saveFilePath = saveUrl.toLocalFile(); if ( QFile::exists( saveFilePath ) ) { const mode_t mask = umask( 0 ); umask( mask ); const mode_t fileMode = 0666 & ~mask; chmod( QFile::encodeName( saveFilePath ).constData(), fileMode ); } #endif return true; } // If the user wants to save in the original file's format, some features might // not be available. Find out what cannot be saved in this format void Part::checkNativeSaveDataLoss(bool *out_wontSaveForms, bool *out_wontSaveAnnotations) const { bool wontSaveForms = false; bool wontSaveAnnotations = false; if ( !m_document->canSaveChanges( Document::SaveFormsCapability ) ) { /* Set wontSaveForms only if there are forms */ const int pagecount = m_document->pages(); for ( int pageno = 0; pageno < pagecount; ++pageno ) { const Okular::Page *page = m_document->page( pageno ); if ( !page->formFields().empty() ) { wontSaveForms = true; break; } } } if ( !m_document->canSaveChanges( Document::SaveAnnotationsCapability ) ) { /* Set wontSaveAnnotations only if there are local annotations */ const int pagecount = m_document->pages(); for ( int pageno = 0; pageno < pagecount; ++pageno ) { const Okular::Page *page = m_document->page( pageno ); foreach ( const Okular::Annotation *ann, page->annotations() ) { if ( !(ann->flags() & Okular::Annotation::External) ) { wontSaveAnnotations = true; break; } } if ( wontSaveAnnotations ) break; } } *out_wontSaveForms = wontSaveForms; *out_wontSaveAnnotations = wontSaveAnnotations; } void Part::slotGetNewStuff() { #if 0 KNS::Engine engine(widget()); engine.init( "okular.knsrc" ); // show the modal dialog over pageview and execute it KNS::Entry::List entries = engine.downloadDialogModal( m_pageView ); Q_UNUSED( entries ) #endif } void Part::slotPreferences() { // Create dialog PreferencesDialog * dialog = new PreferencesDialog( m_pageView, Okular::Settings::self(), m_embedMode ); dialog->setAttribute( Qt::WA_DeleteOnClose ); // Show it dialog->show(); } void Part::slotToggleChangeColors() { m_pageView->slotToggleChangeColors(); } void Part::slotSetChangeColors(bool active) { m_pageView->slotSetChangeColors(active); } void Part::slotAnnotationPreferences() { // Create dialog PreferencesDialog * dialog = new PreferencesDialog( m_pageView, Okular::Settings::self(), m_embedMode ); dialog->setAttribute( Qt::WA_DeleteOnClose ); // Show it dialog->switchToAnnotationsPage(); dialog->show(); } void Part::slotNewConfig() { // Apply settings here. A good policy is to check whether the setting has // changed before applying changes. // Watch File setWatchFileModeEnabled(Okular::Settings::watchFile()); // Main View (pageView) m_pageView->reparseConfig(); // update document settings m_document->reparseConfig(); // update TOC settings if ( m_sidebar->isItemEnabled(m_toc) ) m_toc->reparseConfig(); // update ThumbnailList contents if ( Okular::Settings::showLeftPanel() && !m_thumbnailList->isHidden() ) m_thumbnailList->updateWidgets(); // update Reviews settings if ( m_sidebar->isItemEnabled(m_reviewsWidget) ) m_reviewsWidget->reparseConfig(); setWindowTitleFromDocument (); if ( m_presentationDrawingActions ) { m_presentationDrawingActions->reparseConfig(); if (factory()) { factory()->refreshActionProperties(); } } } void Part::slotPrintPreview() { if (m_document->pages() == 0) return; QPrinter printer; QString tempFilePattern; if ( m_document->printingSupport() == Okular::Document::PostscriptPrinting ) { tempFilePattern = (QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); } else if ( m_document->printingSupport() == Okular::Document::NativePrinting ) { tempFilePattern = (QDir::tempPath() + QLatin1String("/okular_XXXXXX.pdf")); } else { return; } // Generate a temp filename for Print to File, then release the file so generator can write to it QTemporaryFile tf(tempFilePattern); tf.setAutoRemove( true ); tf.open(); printer.setOutputFileName( tf.fileName() ); tf.close(); setupPrint( printer ); doPrint( printer ); if ( QFile::exists( printer.outputFileName() ) ) { Okular::FilePrinterPreview previewdlg( printer.outputFileName(), widget() ); previewdlg.exec(); } } void Part::slotShowTOCMenu(const Okular::DocumentViewport &vp, const QPoint &point, const QString &title) { showMenu(m_document->page(vp.pageNumber), point, title, vp, true); } void Part::slotShowMenu(const Okular::Page *page, const QPoint &point) { showMenu(page, point); } void Part::showMenu(const Okular::Page *page, const QPoint &point, const QString &bookmarkTitle, const Okular::DocumentViewport &vp, bool showTOCActions) { if ( m_embedMode == PrintPreviewMode ) return; bool reallyShow = false; const bool currentPage = page && page->number() == m_document->viewport().pageNumber; if (!m_actionsSearched) { // the quest for options_show_menubar KActionCollection *ac; QAction *act; if (factory()) { const QList clients(factory()->clients()); for(int i = 0 ; (!m_showMenuBarAction || !m_showFullScreenAction) && i < clients.size(); ++i) { ac = clients.at(i)->actionCollection(); // show_menubar act = ac->action(QStringLiteral("options_show_menubar")); if (act && qobject_cast(act)) m_showMenuBarAction = qobject_cast(act); // fullscreen act = ac->action(QStringLiteral("fullscreen")); if (act && qobject_cast(act)) m_showFullScreenAction = qobject_cast(act); } } m_actionsSearched = true; } QMenu *popup = new QMenu( widget() ); if (showTOCActions) { popup->addAction( i18n("Expand whole section"), m_toc.data(), &TOC::expandRecursively ); popup->addAction( i18n("Collapse whole section"), m_toc.data(), &TOC::collapseRecursively ); popup->addAction( i18n("Expand all"), m_toc.data(), &TOC::expandAll ); popup->addAction( i18n("Collapse all"), m_toc.data(), &TOC::collapseAll ); reallyShow = true; } QAction *addBookmark = nullptr; QAction *removeBookmark = nullptr; QAction *fitPageWidth = nullptr; if (page) { popup->addAction( new OKMenuTitle( popup, i18n( "Page %1", page->number() + 1 ) ) ); if ( ( !currentPage && m_document->bookmarkManager()->isBookmarked( page->number() ) ) || ( currentPage && m_document->bookmarkManager()->isBookmarked( m_document->viewport() ) ) ) removeBookmark = popup->addAction( QIcon::fromTheme(QStringLiteral("edit-delete-bookmark")), i18n("Remove Bookmark") ); else addBookmark = popup->addAction( QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add Bookmark") ); if ( m_pageView->canFitPageWidth() ) fitPageWidth = popup->addAction( QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Fit Width") ); popup->addAction( m_prevBookmark ); popup->addAction( m_nextBookmark ); reallyShow = true; } if ((m_showMenuBarAction && !m_showMenuBarAction->isChecked()) || (m_showFullScreenAction && m_showFullScreenAction->isChecked())) { popup->addAction( new OKMenuTitle( popup, i18n( "Tools" ) ) ); if (m_showMenuBarAction && !m_showMenuBarAction->isChecked()) popup->addAction(m_showMenuBarAction); if (m_showFullScreenAction && m_showFullScreenAction->isChecked()) popup->addAction(m_showFullScreenAction); reallyShow = true; } if (reallyShow) { QAction *res = popup->exec(point); if (res) { if (res == addBookmark) { if ( currentPage && bookmarkTitle.isEmpty() ) m_document->bookmarkManager()->addBookmark( m_document->viewport() ); else if ( !bookmarkTitle.isEmpty() ) m_document->bookmarkManager()->addBookmark( m_document->currentDocument(), vp, bookmarkTitle ); else m_document->bookmarkManager()->addBookmark( page->number() ); } else if (res == removeBookmark) { if (currentPage) m_document->bookmarkManager()->removeBookmark( m_document->viewport() ); else m_document->bookmarkManager()->removeBookmark( page->number() ); } else if (res == fitPageWidth) { m_pageView->fitPageWidth( page->number() ); } } } delete popup; } void Part::slotShowProperties() { PropertiesDialog *d = new PropertiesDialog(widget(), m_document); connect(d, &QDialog::finished, d, &QObject::deleteLater); d->open(); } void Part::slotShowEmbeddedFiles() { EmbeddedFilesDialog *d = new EmbeddedFilesDialog(widget(), m_document); connect(d, &QDialog::finished, d, &QObject::deleteLater); d->open(); } void Part::slotShowPresentation() { if ( !m_presentationWidget ) { m_presentationWidget = new PresentationWidget( widget(), m_document, m_presentationDrawingActions, actionCollection() ); } } void Part::slotHidePresentation() { if ( m_presentationWidget ) delete (PresentationWidget*) m_presentationWidget; } void Part::slotTogglePresentation() { if ( m_document->isOpened() ) { if ( !m_presentationWidget ) m_presentationWidget = new PresentationWidget( widget(), m_document, m_presentationDrawingActions, actionCollection() ); else delete (PresentationWidget*) m_presentationWidget; } } void Part::reload() { if ( m_document->isOpened() ) { slotReload(); } } void Part::enableStartWithPrint() { m_cliPrint = true; } void Part::enableExitAfterPrint() { m_cliPrintAndExit = true; } void Part::slotAboutBackend() { const KPluginMetaData data = m_document->generatorInfo(); if (!data.isValid()) return; KAboutData aboutData = KAboutData::fromPluginMetaData(data); QIcon icon = QIcon::fromTheme(data.iconName()); // fall back to mime type icon if (icon.isNull()) { const Okular::DocumentInfo documentInfo = m_document->documentInfo(QSet() << DocumentInfo::MimeType); const QString mimeTypeName = documentInfo.get(DocumentInfo::MimeType); if (!mimeTypeName.isEmpty()) { QMimeDatabase db; QMimeType type = db.mimeTypeForName(mimeTypeName); if (type.isValid()) { icon = QIcon::fromTheme(type.iconName()); } } } if (!icon.isNull()) { // 48x48 is what KAboutApplicationDialog wants, which doesn't match any default so we hardcode it aboutData.setProgramLogo(icon.pixmap(48, 48)); } KAboutApplicationDialog dlg(aboutData, widget()); dlg.exec(); } void Part::slotExportAs(QAction * act) { QList acts = m_exportAs->menu() ? m_exportAs->menu()->actions() : QList(); int id = acts.indexOf( act ); if ( ( id < 0 ) || ( id >= acts.count() ) ) return; QMimeDatabase mimeDatabase; QMimeType mimeType; switch ( id ) { case 0: mimeType = mimeDatabase.mimeTypeForName(QStringLiteral("text/plain")); break; default: mimeType = m_exportFormats.at( id - 1 ).mimeType(); break; } QString filter = i18nc("File type name and pattern", "%1 (%2)", mimeType.comment(), mimeType.globPatterns().join(QLatin1Char(' '))); QString fileName = QFileDialog::getSaveFileName( widget(), QString(), QString(), filter); if ( !fileName.isEmpty() ) { bool saved = false; switch ( id ) { case 0: saved = m_document->exportToText( fileName ); break; default: saved = m_document->exportTo( fileName, m_exportFormats.at( id - 1 ) ); break; } if ( !saved ) KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) ); } } void Part::slotReload() { // stop the dirty handler timer, otherwise we may conflict with the // auto-refresh system m_dirtyHandler->stop(); slotAttemptReload(); } void Part::slotPrint() { if (m_document->pages() == 0) return; #ifdef Q_OS_WIN QPrinter printer(QPrinter::HighResolution); #else QPrinter printer; #endif QPrintDialog *printDialog = nullptr; QWidget *printConfigWidget = nullptr; // Must do certain QPrinter setup before creating QPrintDialog setupPrint( printer ); // Create the Print Dialog with extra config widgets if required if ( m_document->canConfigurePrinter() ) { printConfigWidget = m_document->printConfigurationWidget(); } printDialog = new QPrintDialog(&printer, widget()); printDialog->setWindowTitle(i18nc("@title:window", "Print")); QList options; if (printConfigWidget) { options << printConfigWidget; } printDialog->setOptionTabs(options); if ( printDialog ) { // Set the available Print Range printDialog->setMinMax( 1, m_document->pages() ); printDialog->setFromTo( 1, m_document->pages() ); // If the user has bookmarked pages for printing, then enable Selection if ( !m_document->bookmarkedPageRange().isEmpty() ) { printDialog->addEnabledOption( QAbstractPrintDialog::PrintSelection ); } // If the Document type doesn't support print to both PS & PDF then disable the Print Dialog option if ( printDialog->isOptionEnabled( QAbstractPrintDialog::PrintToFile ) && !m_document->supportsPrintToFile() ) { printDialog->setEnabledOptions( printDialog->enabledOptions() ^ QAbstractPrintDialog::PrintToFile ); } // Enable the Current Page option in the dialog. if ( m_document->pages() > 1 && currentPage() > 0 ) { printDialog->setOption( QAbstractPrintDialog::PrintCurrentPage ); } bool success = true; if ( printDialog->exec() ) success = doPrint( printer ); delete printDialog; if ( m_cliPrintAndExit ) exit ( success ? EXIT_SUCCESS : EXIT_FAILURE ); } } void Part::setupPrint( QPrinter &printer ) { printer.setOrientation(m_document->orientation()); // title QString title = m_document->metaData( QStringLiteral("DocumentTitle") ).toString(); if ( title.isEmpty() ) { title = m_document->currentDocument().fileName(); } if ( !title.isEmpty() ) { printer.setDocName( title ); } } bool Part::doPrint(QPrinter &printer) { if (!m_document->isAllowed(Okular::AllowPrint)) { KMessageBox::error(widget(), i18n("Printing this document is not allowed.")); return false; } if (!m_document->print(printer)) { const QString error = m_document->printError(); if (error.isEmpty()) { KMessageBox::error(widget(), i18n("Could not print the document. Unknown error. Please report to bugs.kde.org")); } else { KMessageBox::error(widget(), i18n("Could not print the document. Detailed error is \"%1\". Please report to bugs.kde.org", error)); } return false; } return true; } void Part::psTransformEnded(int exit, QProcess::ExitStatus status) { Q_UNUSED( exit ) if ( status != QProcess::NormalExit ) return; QProcess *senderobj = sender() ? qobject_cast< QProcess * >( sender() ) : 0; if ( senderobj ) { senderobj->close(); senderobj->deleteLater(); } setLocalFilePath( m_temporaryLocalFile ); openUrl( QUrl::fromLocalFile(m_temporaryLocalFile) ); m_temporaryLocalFile.clear(); } void Part::displayInfoMessage( const QString &message, KMessageWidget::MessageType messageType, int duration ) { if ( !Okular::Settings::showOSD() ) { if (messageType == KMessageWidget::Error) { KMessageBox::error( widget(), message ); } return; } // hide messageWindow if string is empty if ( message.isEmpty() ) m_infoMessage->animatedHide(); // display message (duration is length dependant) if ( duration < 0 ) { duration = 500 + 100 * message.length(); } m_infoTimer->start( duration ); m_infoMessage->setText( message ); m_infoMessage->setMessageType( messageType ); m_infoMessage->setVisible( true ); } void Part::errorMessage( const QString &message, int duration ) { displayInfoMessage( message, KMessageWidget::Error, duration ); } void Part::warningMessage( const QString &message, int duration ) { displayInfoMessage( message, KMessageWidget::Warning, duration ); } void Part::noticeMessage( const QString &message, int duration ) { // less important message -> simpleer display widget in the PageView m_pageView->displayMessage( message, QString(), PageViewMessage::Info, duration ); } void Part::moveSplitter(int sideWidgetSize) { m_sidebar->moveSplitter( sideWidgetSize ); } void Part::unsetDummyMode() { if ( m_embedMode == PrintPreviewMode ) return; m_sidebar->setItemEnabled( m_reviewsWidget, true ); m_sidebar->setItemEnabled( m_bookmarkList, true ); m_sidebar->setSidebarVisibility( Okular::Settings::showLeftPanel() ); // add back and next in history m_historyBack = KStandardAction::documentBack( this, SLOT(slotHistoryBack()), actionCollection() ); m_historyBack->setWhatsThis( i18n( "Go to the place you were before" ) ); connect(m_pageView.data(), &PageView::mouseBackButtonClick, m_historyBack, &QAction::trigger); m_historyNext = KStandardAction::documentForward( this, SLOT(slotHistoryNext()), actionCollection()); m_historyNext->setWhatsThis( i18n( "Go to the place you were after" ) ); connect(m_pageView.data(), &PageView::mouseForwardButtonClick, m_historyNext, &QAction::trigger); m_pageView->setupActions( actionCollection() ); // attach the actions of the children widgets too m_formsMessage->addAction( m_pageView->toggleFormsAction() ); // ensure history actions are in the correct state updateViewActions(); } bool Part::handleCompressed( QString &destpath, const QString &path, KFilterDev::CompressionType compressionType) { m_tempfile = nullptr; // we are working with a compressed file, decompressing // temporary file for decompressing QTemporaryFile *newtempfile = new QTemporaryFile(); newtempfile->setAutoRemove(true); if ( !newtempfile->open() ) { KMessageBox::error( widget(), i18n("File Error! Could not create temporary file " "%1.", newtempfile->errorString())); delete newtempfile; return false; } // decompression filer KCompressionDevice dev( path, compressionType ); if ( !dev.open(QIODevice::ReadOnly) ) { KMessageBox::detailedError( widget(), i18n("File Error! Could not open the file " "%1 for uncompression. " "The file will not be loaded.", path), i18n("This error typically occurs if you do " "not have enough permissions to read the file. " "You can check ownership and permissions if you " "right-click on the file in the Dolphin " "file manager and then choose the 'Properties' tab.")); delete newtempfile; return false; } char buf[65536]; int read = 0, wrtn = 0; while ((read = dev.read(buf, sizeof(buf))) > 0) { wrtn = newtempfile->write(buf, read); if ( read != wrtn ) break; } if ((read != 0) || (newtempfile->size() == 0)) { KMessageBox::detailedError(widget(), i18n("File Error! Could not uncompress " "the file %1. " "The file will not be loaded.", path ), i18n("This error typically occurs if the file is corrupt. " "If you want to be sure, try to decompress the file manually " "using command-line tools.")); delete newtempfile; return false; } m_tempfile = newtempfile; destpath = m_tempfile->fileName(); return true; } void Part::rebuildBookmarkMenu( bool unplugActions ) { if ( unplugActions ) { unplugActionList( QStringLiteral("bookmarks_currentdocument") ); qDeleteAll( m_bookmarkActions ); m_bookmarkActions.clear(); } QUrl u = m_document->currentDocument(); if ( u.isValid() ) { m_bookmarkActions = m_document->bookmarkManager()->actionsForUrl( u ); } bool havebookmarks = true; if ( m_bookmarkActions.isEmpty() ) { havebookmarks = false; QAction * a = new QAction( nullptr ); a->setText( i18n( "No Bookmarks" ) ); a->setEnabled( false ); m_bookmarkActions.append( a ); } plugActionList( QStringLiteral("bookmarks_currentdocument"), m_bookmarkActions ); if (factory()) { const QList clients(factory()->clients()); bool containerFound = false; for (int i = 0; !containerFound && i < clients.size(); ++i) { QMenu *container = dynamic_cast(factory()->container(QStringLiteral("bookmarks"), clients[i])); if (container && container->actions().contains(m_bookmarkActions.first())) { container->installEventFilter(this); containerFound = true; } } } m_prevBookmark->setEnabled( havebookmarks ); m_nextBookmark->setEnabled( havebookmarks ); } bool Part::eventFilter(QObject * watched, QEvent * event) { switch (event->type()) { case QEvent::ContextMenu: { QContextMenuEvent *e = static_cast(event); QMenu *menu = static_cast(watched); QScopedPointer ctxMenu(new QMenu); QPoint pos; bool ret = false; if (e->reason() == QContextMenuEvent::Mouse) { pos = e->pos(); ret = aboutToShowContextMenu(menu, menu->actionAt(e->pos()), ctxMenu.data()); } else if (menu->activeAction()) { pos = menu->actionGeometry(menu->activeAction()).center(); ret = aboutToShowContextMenu(menu, menu->activeAction(), ctxMenu.data()); } ctxMenu->exec(menu->mapToGlobal(pos)); if (ret) { event->accept(); } return ret; } default: break; } return false; } void Part::updateAboutBackendAction() { const KPluginMetaData data = m_document->generatorInfo(); m_aboutBackend->setEnabled(data.isValid()); } void Part::resetStartArguments() { m_cliPrint = false; m_cliPrintAndExit = false; } #if PURPOSE_FOUND void Part::slotShareActionFinished(const QJsonObject &output, int error, const QString &message) { if (error) { KMessageBox::error(widget(), i18n("There was a problem sharing the document: %1", message), i18n("Share")); } else { const QString url = output["url"].toString(); if (url.isEmpty()) { m_pageView->displayMessage(i18n("Document shared successfully")); } else { KMessageBox::information(widget(), i18n("You can find the shared document at: %1", url), i18n("Share"), QString(), KMessageBox::Notify | KMessageBox::AllowLink); } } } #endif void Part::setReadWrite(bool readwrite) { m_document->setAnnotationEditingEnabled( readwrite ); ReadWritePart::setReadWrite( readwrite ); } } // namespace Okular #include "part.moc" /* kate: replace-tabs on; indent-width 4; */ diff --git a/shell/main.cpp b/shell/main.cpp index c8d731b61..983690d08 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -1,88 +1,88 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003-2007 by Albert Astals Cid * * Copyright (C) 2004 by Andy Goossens * * * * 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 "shell.h" #include -#include +#include #include #include #include #include #include #include #include #include #include "aboutdata.h" #include "okular_main.h" #include "shellutils.h" int main(int argc, char** argv) { QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv); KLocalizedString::setApplicationDomain("okular"); KAboutData aboutData = okularAboutData(); KAboutData::setApplicationData(aboutData); // set icon for shells which do not use desktop file metadata QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("okular"))); KCrash::initialize(); QCommandLineParser parser; // The KDE4 version accepted flags such as -unique with a single dash -> preserve compatibility parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); aboutData.setupCommandLine(&parser); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("p") << QStringLiteral("page"), i18n("Page of the document to be shown"), QStringLiteral("number"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("presentation"), i18n("Start the document in presentation mode"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("print"), i18n("Start with print dialog"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("print-and-exit"), i18n("Start with print dialog and exit after printing"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("unique"), i18n("\"Unique instance\" control"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("noraise"), i18n("Not raise window"))); parser.addPositionalArgument(QStringLiteral("urls"), i18n("Documents to open. Specify '-' to read from stdin.")); parser.process(app); aboutData.processCommandLine(&parser); // see if we are starting with session management if (app.isSessionRestored()) { kRestoreMainWindows(); } else { // no session.. just start up normally QStringList paths; for ( int i = 0; i < parser.positionalArguments().count(); ++i ) paths << parser.positionalArguments().at(i); Okular::Status status = Okular::main(paths, ShellUtils::serializeOptions(parser)); switch (status) { case Okular::Error: return -1; case Okular::AttachedOtherProcess: return 0; case Okular::Success: // Do nothing break; } } return app.exec(); } /* kate: replace-tabs on; indent-width 4; */ diff --git a/shell/okular_main.cpp b/shell/okular_main.cpp index 2285f64a9..1d33f5ab7 100644 --- a/shell/okular_main.cpp +++ b/shell/okular_main.cpp @@ -1,190 +1,190 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003-2007 by Albert Astals Cid * * Copyright (C) 2004 by Andy Goossens * * * * 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 "okular_main.h" #include "shell.h" -#include +#include #include -#include +#include #include #include #include "aboutdata.h" #include "shellutils.h" static bool attachUniqueInstance(const QStringList &paths, const QString &serializedOptions) { if (!ShellUtils::unique(serializedOptions) || paths.count() != 1) return false; QDBusInterface iface(QStringLiteral("org.kde.okular"), QStringLiteral("/okularshell"), QStringLiteral("org.kde.okular")); if (!iface.isValid()) return false; const QString page = ShellUtils::page(serializedOptions); iface.call(QStringLiteral("openDocument"), ShellUtils::urlFromArg(paths[0], ShellUtils::qfileExistFunc(), page).url(), serializedOptions); if (!ShellUtils::noRaise(serializedOptions)) { iface.call(QStringLiteral("tryRaise")); } return true; } // Ask an existing non-unique instance to open new tabs static bool attachExistingInstance(const QStringList &paths, const QString &serializedOptions) { if ( paths.count() < 1 ) return false; const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); // Don't match the service without trailing "-" (unique instance) const QString pattern = QStringLiteral("org.kde.okular-"); const QString myPid = QString::number( qApp->applicationPid() ); QScopedPointer bestService; const int desktop = KWindowSystem::currentDesktop(); // Select the first instance that isn't us (metric may change in future) foreach ( const QString& service, services ) { if ( service.startsWith(pattern) && !service.endsWith( myPid ) ) { bestService.reset( new QDBusInterface(service, QStringLiteral("/okularshell"), QStringLiteral("org.kde.okular")) ); // Find a window that can handle our documents const QDBusReply reply = bestService->call( QStringLiteral("canOpenDocs"), paths.count(), desktop ); if( reply.isValid() && reply.value() ) break; bestService.reset(); } } if ( !bestService ) return false; foreach( const QString &arg, paths ) { // Copy stdin to temporary file which can be opened by the existing // window. The temp file is automatically deleted after it has been // opened. Not sure if this behavior is safe on all platforms. QScopedPointer tempFile; QString path; if( arg == QLatin1String("-") ) { tempFile.reset( new QTemporaryFile ); QFile stdinFile; if( !tempFile->open() || !stdinFile.open(stdin,QIODevice::ReadOnly) ) return false; const size_t bufSize = 1024*1024; QScopedPointer > buf( new char[bufSize] ); size_t bytes; do { bytes = stdinFile.read( buf.data(), bufSize ); tempFile->write( buf.data(), bytes ); } while( bytes != 0 ); path = tempFile->fileName(); } else { // Page only makes sense if we are opening one file const QString page = ShellUtils::page(serializedOptions); path = ShellUtils::urlFromArg(arg, ShellUtils::qfileExistFunc(), page).url(); } // Returns false if it can't fit another document const QDBusReply reply = bestService->call( QStringLiteral("openDocument"), path, serializedOptions ); if( !reply.isValid() || !reply.value() ) return false; } bestService->call( QStringLiteral("tryRaise") ); return true; } namespace Okular { Status main(const QStringList &paths, const QString &serializedOptions) { if (ShellUtils::unique(serializedOptions) && paths.count() > 1) { QTextStream stream(stderr); stream << i18n( "Error: Can't open more than one document with the --unique switch" ) << endl; return Error; } if (ShellUtils::startInPresentation(serializedOptions) && paths.count() > 1) { QTextStream stream(stderr); stream << i18n( "Error: Can't open more than one document with the --presentation switch" ) << endl; return Error; } if (ShellUtils::showPrintDialog(serializedOptions) && paths.count() > 1) { QTextStream stream(stderr); stream << i18n( "Error: Can't open more than one document with the --print switch" ) << endl; return Error; } if (!ShellUtils::page(serializedOptions).isEmpty() && paths.count() > 1) { QTextStream stream(stderr); stream << i18n( "Error: Can't open more than one document with the --page switch" ) << endl; return Error; } // try to attach to existing session, unique or not if (attachUniqueInstance(paths, serializedOptions) || attachExistingInstance(paths, serializedOptions)) { return AttachedOtherProcess; } Shell* shell = new Shell( serializedOptions ); if ( !shell->isValid() ) { return Error; } shell->show(); for ( int i = 0; i < paths.count(); ) { // Page only makes sense if we are opening one file const QString page = ShellUtils::page(serializedOptions); const QUrl url = ShellUtils::urlFromArg(paths[i], ShellUtils::qfileExistFunc(), page); if ( shell->openDocument( url, serializedOptions) ) { ++i; } else { shell = new Shell( serializedOptions ); if ( !shell->isValid() ) { return Error; } shell->show(); } } return Success; } } /* kate: replace-tabs on; indent-width 4; */ diff --git a/shell/okular_main.h b/shell/okular_main.h index 950b18ef0..c4dd42e31 100644 --- a/shell/okular_main.h +++ b/shell/okular_main.h @@ -1,27 +1,32 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003-2007 by Albert Astals Cid * * Copyright (C) 2004 by Andy Goossens * * * * 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_MAIN_H_ +#define _OKULAR_MAIN_H_ + class QString; class QStringList; namespace Okular { enum Status { Error, AttachedOtherProcess, Success }; Status main(const QStringList &paths, const QString &serializedOptions); } +#endif + /* kate: replace-tabs on; indent-width 4; */ diff --git a/shell/shell.h b/shell/shell.h index 4c5604be7..a66c60675 100644 --- a/shell/shell.h +++ b/shell/shell.h @@ -1,185 +1,185 @@ /*************************************************************************** * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Benjamin Meyer * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003 by Luboš Luňák * * Copyright (C) 2004 by Christophe Devriese * * * * 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_SHELL_H_ #define _OKULAR_SHELL_H_ #include #include #include #include #include -#include +#include class KRecentFilesAction; class KToggleAction; class QTabWidget; class KPluginFactory; class KDocumentViewer; class Part; #ifndef Q_OS_WIN namespace KActivities { class ResourceInstance; } #endif /** * This is the application "Shell". It has a menubar and a toolbar * but relies on the "Part" to do all the real work. * * @short Application Shell * @author Wilco Greven * @version 0.1 */ class Shell : public KParts::MainWindow { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.okular") friend class MainShellTest; public: /** * Constructor */ explicit Shell( const QString &serializedOptions = QString() ); /** * Default Destructor */ virtual ~Shell(); QSize sizeHint() const override; /** * Returns false if Okular component wasn't found **/ bool isValid() const; bool openDocument(const QUrl &url, const QString &serializedOptions); public Q_SLOTS: Q_SCRIPTABLE Q_NOREPLY void tryRaise(); Q_SCRIPTABLE bool openDocument(const QString &urlString, const QString &serializedOptions = QString() ); Q_SCRIPTABLE bool canOpenDocs( int numDocs, int desktop ); protected: /** * This method is called when it is time for the app to save its * properties for session management purposes. */ void saveProperties(KConfigGroup&) override; /** * This method is called when this app is restored. The KConfig * object points to the session management config file that was saved * with @ref saveProperties */ void readProperties(const KConfigGroup&) override; /** * Expose internal functions for session restore testing */ void savePropertiesInternal(KConfig* config, int num) {KMainWindow::savePropertiesInternal(config,num);} void readPropertiesInternal(KConfig* config, int num) {KMainWindow::readPropertiesInternal(config,num);} void readSettings(); void writeSettings(); void setFullScreen( bool ); using KParts::MainWindow::setCaption; void setCaption( const QString &caption ) override; bool queryClose() override; void showEvent(QShowEvent *event) override; private Q_SLOTS: void fileOpen(); void slotUpdateFullScreen(); void slotShowMenubar(); void openUrl( const QUrl & url, const QString &serializedOptions = QString() ); void showOpenRecentMenu(); void closeUrl(); void print(); void setPrintEnabled( bool enabled ); void setCloseEnabled( bool enabled ); void setTabIcon(const QMimeType& mimeType ); void handleDroppedUrls( const QList& urls ); // Tab event handlers void setActiveTab( int tab ); void closeTab( int tab ); void activateNextTab(); void activatePrevTab(); void moveTabData( int from, int to ); void slotFitWindowToPage( const QSize& pageViewSize, const QSize& pageSize ); Q_SIGNALS: void moveSplitter(int sideWidgetSize); private: void setupAccel(); void setupActions(); QStringList fileFormats() const; void openNewTab( const QUrl& url, const QString &serializedOptions ); void applyOptionsToPart( QObject* part, const QString &serializedOptions ); void connectPart( QObject* part ); int findTabIndex( QObject* sender ); private: bool eventFilter(QObject *obj, QEvent *event) override; KPluginFactory* m_partFactory; KRecentFilesAction* m_recent; QStringList m_fileformats; bool m_fileformatsscanned; QAction* m_printAction; QAction* m_closeAction; KToggleAction* m_fullScreenAction; KToggleAction* m_showMenuBarAction; bool m_menuBarWasShown, m_toolBarWasShown; bool m_unique; QTabWidget* m_tabWidget; KToggleAction* m_openInTab; struct TabState { TabState( KParts::ReadWritePart* p ) : part(p), printEnabled(false), closeEnabled(false) {} KParts::ReadWritePart* part; bool printEnabled; bool closeEnabled; }; QList m_tabs; QAction* m_nextTabAction; QAction* m_prevTabAction; #ifndef Q_OS_WIN KActivities::ResourceInstance* m_activityResource; #endif bool m_isValid; }; #endif // vim:ts=2:sw=2:tw=78:et diff --git a/shell/shellutils.cpp b/shell/shellutils.cpp index c9cc221ad..a465d6579 100644 --- a/shell/shellutils.cpp +++ b/shell/shellutils.cpp @@ -1,141 +1,141 @@ /*************************************************************************** * Copyright (C) 2009 by Pino Toscano * * * * 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 "shellutils.h" // qt/kde includes #include #include #include #include #include -#include +#include namespace ShellUtils { namespace detail { bool qfileExistFunc( const QString& fileName ) { return QFile::exists( fileName ); } } FileExistFunc qfileExistFunc() { return detail::qfileExistFunc; } QUrl urlFromArg( const QString& _arg, FileExistFunc exist_func, const QString& pageArg ) { QUrl url = QUrl::fromUserInput(_arg, QDir::currentPath(), QUrl::AssumeLocalFile); if ( url.isLocalFile() ) { // make sure something like /tmp/foo#bar.pdf is treated as a path name (default) // but something like /tmp/foo.pdf#bar is foo.pdf plus an anchor "bar" const QString path = url.path(); int hashIndex = path.lastIndexOf( QLatin1Char ( '#' ) ); if ( hashIndex != -1 && !exist_func(path) ) { url.setPath( path.left( hashIndex ) ); url.setFragment( path.mid( hashIndex + 1 ) ); } } else if ( !url.fragment().isEmpty() ) { // make sure something like http://example.org/foo#bar.pdf is treated as a path name // but something like http://example.org/foo.pdf#bar is foo.pdf plus an anchor "bar" if ( url.fragment().contains( QLatin1Char( '.' ) ) ) { url.setPath( url.path() + QLatin1Char ( '#' ) + url.fragment() ); url.setFragment( QString() ); } } if ( !pageArg.isEmpty() ) { url.setFragment( pageArg ); } return url; } QString serializeOptions(const QCommandLineParser &args) { const bool startInPresentation = args.isSet( QStringLiteral("presentation") ); const bool showPrintDialog = args.isSet( QStringLiteral("print") ); const bool showPrintDialogAndExit = args.isSet( QStringLiteral("print-and-exit") ); const bool unique = args.isSet(QStringLiteral("unique")) && args.positionalArguments().count() <= 1; const bool noRaise = args.isSet(QStringLiteral("noraise")); const QString page = args.value(QStringLiteral("page")); return serializeOptions(startInPresentation, showPrintDialog, showPrintDialogAndExit, unique, noRaise, page); } QString serializeOptions(bool startInPresentation, bool showPrintDialog, bool showPrintDialogAndExit, bool unique, bool noRaise, const QString &page) { return QStringLiteral("%1:%2:%3:%4:%5:%6").arg(startInPresentation).arg(showPrintDialog).arg(showPrintDialogAndExit).arg(unique).arg(noRaise).arg(page); } bool unserializeOptions(const QString &serializedOptions, bool *presentation, bool *print, bool *print_and_exit, bool *unique, bool *noraise, QString *page) { const QStringList args = serializedOptions.split(QStringLiteral(":")); if (args.count() == 6) { *presentation = args[0] == QLatin1String("1"); *print = args[1] == QLatin1String("1"); *print_and_exit = args[2] == QLatin1String("1"); *unique = args[3] == QLatin1String("1"); *noraise = args[4] == QLatin1String("1"); *page = args[5]; return true; } return false; } bool startInPresentation(const QString &serializedOptions) { bool result, dummy; QString dummyString; return unserializeOptions(serializedOptions, &result, &dummy, &dummy, &dummy, &dummy, &dummyString) && result; } bool showPrintDialog(const QString &serializedOptions) { bool result, dummy; QString dummyString; return unserializeOptions(serializedOptions, &dummy, &result, &dummy, &dummy, &dummy, &dummyString) && result; } bool showPrintDialogAndExit(const QString &serializedOptions) { bool result, dummy; QString dummyString; return unserializeOptions(serializedOptions, &dummy, &dummy, &result, &dummy, &dummy, &dummyString) && result; } bool unique(const QString &serializedOptions) { bool result, dummy; QString dummyString; return unserializeOptions(serializedOptions, &dummy, &dummy, &dummy, &result, &dummy, &dummyString) && result; } bool noRaise(const QString &serializedOptions) { bool result, dummy; QString dummyString; return unserializeOptions(serializedOptions, &dummy, &dummy, &dummy, &dummy, &result, &dummyString) && result; } QString page(const QString &serializedOptions) { QString result; bool dummy; unserializeOptions(serializedOptions, &dummy, &dummy, &dummy, &dummy, &dummy, &result); return result; } } diff --git a/ui/ktreeviewsearchline.cpp b/ui/ktreeviewsearchline.cpp index 8a280b9fc..070234f59 100644 --- a/ui/ktreeviewsearchline.cpp +++ b/ui/ktreeviewsearchline.cpp @@ -1,399 +1,399 @@ /* Copyright (c) 2003 Scott Wheeler Copyright (c) 2005 Rafal Rzepecki Copyright (c) 2006 Hamish Rodda Copyright 2007 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktreeviewsearchline.h" -#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 class KTreeViewSearchLine::Private { public: Private( KTreeViewSearchLine *_parent ) : parent( _parent ), treeView( nullptr ), caseSensitive( Qt::CaseInsensitive ), regularExpression( false ), activeSearch( false ), queuedSearches( 0 ) { } KTreeViewSearchLine *parent; QTreeView * treeView; Qt::CaseSensitivity caseSensitive; bool regularExpression; bool activeSearch; QString search; int queuedSearches; void rowsInserted(const QModelIndex & parent, int start, int end) const; void treeViewDeleted( QObject *treeView ); void slotCaseSensitive(); void slotRegularExpression(); void checkItemParentsNotVisible(QTreeView *treeView); bool checkItemParentsVisible(QTreeView *treeView, const QModelIndex &index); }; //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::Private::rowsInserted( const QModelIndex & parentIndex, int start, int end ) const { QAbstractItemModel* model = qobject_cast( parent->sender() ); if ( !model ) return; QTreeView* widget = nullptr; if ( treeView->model() == model ) { widget = treeView; } if ( !widget ) return; for ( int i = start; i <= end; ++i ) { widget->setRowHidden( i, parentIndex, !parent->itemMatches( parentIndex, i, parent->text() ) ); } } void KTreeViewSearchLine::Private::treeViewDeleted( QObject *object ) { if ( object == treeView ) { treeView = nullptr; parent->setEnabled( false ); } } void KTreeViewSearchLine::Private::slotCaseSensitive() { if ( caseSensitive == Qt::CaseSensitive) parent->setCaseSensitivity( Qt::CaseInsensitive ); else parent->setCaseSensitivity( Qt::CaseSensitive ); parent->updateSearch(); } void KTreeViewSearchLine::Private::slotRegularExpression() { if ( regularExpression ) parent->setRegularExpression( false ); else parent->setRegularExpression( true ); parent->updateSearch(); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// /** Check whether \p item, its siblings and their descendents should be shown. Show or hide the items as necessary. * * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the * the first child of the list view. * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown. */ bool KTreeViewSearchLine::Private::checkItemParentsVisible( QTreeView *treeView, const QModelIndex &index ) { bool childMatch = false; const int rowcount = treeView->model()->rowCount( index ); for ( int i = 0; i < rowcount; ++i ) childMatch |= checkItemParentsVisible( treeView, treeView->model()->index( i, 0, index ) ); // Should this item be shown? It should if any children should be, or if it matches. const QModelIndex parentindex = index.parent(); if ( childMatch || parent->itemMatches( parentindex, index.row(), search ) ) { treeView->setRowHidden( index.row(), parentindex, false ); return true; } treeView->setRowHidden( index.row(), parentindex, true ); return false; } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// KTreeViewSearchLine::KTreeViewSearchLine( QWidget *parent, QTreeView *treeView ) : KLineEdit( parent ), d( new Private( this ) ) { connect(this, &KTreeViewSearchLine::textChanged, this, &KTreeViewSearchLine::queueSearch); setClearButtonShown( true ); setTreeView( treeView ); if ( !treeView ) { setEnabled( false ); } } KTreeViewSearchLine::~KTreeViewSearchLine() { delete d; } Qt::CaseSensitivity KTreeViewSearchLine::caseSensitivity() const { return d->caseSensitive; } bool KTreeViewSearchLine::regularExpression() const { return d->regularExpression; } QTreeView *KTreeViewSearchLine::treeView() const { return d->treeView; } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::updateSearch( const QString &pattern ) { d->search = pattern.isNull() ? text() : pattern; updateSearch( d->treeView ); } void KTreeViewSearchLine::updateSearch( QTreeView *treeView ) { if ( !treeView || !treeView->model()->rowCount() ) return; // If there's a selected item that is visible, make sure that it's visible // when the search changes too (assuming that it still matches). QModelIndex currentIndex = treeView->currentIndex(); bool wasUpdateEnabled = treeView->updatesEnabled(); treeView->setUpdatesEnabled( false ); for ( int i = 0; i < treeView->model()->rowCount(); ++i ) d->checkItemParentsVisible( treeView, treeView->rootIndex() ); treeView->setUpdatesEnabled( wasUpdateEnabled ); if ( currentIndex.isValid() ) treeView->scrollTo( currentIndex ); } void KTreeViewSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitive ) { if ( d->caseSensitive != caseSensitive ) { d->caseSensitive = caseSensitive; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setRegularExpression( bool value ) { if ( d->regularExpression != value ) { d->regularExpression = value; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setTreeView( QTreeView *treeView ) { disconnectTreeView( d->treeView ); d->treeView = treeView; connectTreeView( treeView ); setEnabled( treeView != nullptr ); } //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// bool KTreeViewSearchLine::itemMatches( const QModelIndex &parentIndex, int row, const QString &pattern ) const { if ( pattern.isEmpty() ) return true; if ( !parentIndex.isValid() && parentIndex != d->treeView->rootIndex() ) return false; // Contruct a regular expression object with the right options. QRegExp expression = QRegExp( pattern, d->caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, d->regularExpression ? QRegExp::RegExp : QRegExp::FixedString ); // If the search column list is populated, search just the columns // specifified. If it is empty default to searching all of the columns. QAbstractItemModel *model = d->treeView->model(); const int columncount = model->columnCount( parentIndex ); for ( int i = 0; i < columncount; ++i) { if ( expression.indexIn( model->data( model->index( row, i, parentIndex ), Qt::DisplayRole ).toString() ) >= 0 ) return true; } return false; } void KTreeViewSearchLine::contextMenuEvent( QContextMenuEvent *event ) { QMenu *popup = KLineEdit::createStandardContextMenu(); popup->addSeparator(); QMenu *optionsSubMenu = popup->addMenu( i18n("Search Options") ); QAction* caseSensitiveAction = optionsSubMenu->addAction( i18nc("Enable case sensitive search in the side navigation panels", "Case Sensitive"), this, SLOT(slotCaseSensitive()) ); caseSensitiveAction->setCheckable( true ); caseSensitiveAction->setChecked( d->caseSensitive ); QAction* regularExpressionAction = optionsSubMenu->addAction( i18nc("Enable regular expression search in the side navigation panels", "Regular Expression"), this, SLOT(slotRegularExpression()) ); regularExpressionAction->setCheckable( true ); regularExpressionAction->setChecked( d->regularExpression ); popup->exec( event->globalPos() ); delete popup; } void KTreeViewSearchLine::connectTreeView( QTreeView *treeView ) { if ( treeView ) { connect( treeView, SIGNAL(destroyed(QObject*)), this, SLOT(treeViewDeleted(QObject*)) ); connect( treeView->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)) ); } } void KTreeViewSearchLine::disconnectTreeView( QTreeView *treeView ) { if ( treeView ) { disconnect( treeView, SIGNAL(destroyed(QObject*)), this, SLOT(treeViewDeleted(QObject*)) ); disconnect( treeView->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)) ); } } //////////////////////////////////////////////////////////////////////////////// // protected slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::queueSearch( const QString &search ) { d->queuedSearches++; d->search = search; QTimer::singleShot( 200, this, &KTreeViewSearchLine::activateSearch ); } void KTreeViewSearchLine::activateSearch() { --(d->queuedSearches); if ( d->queuedSearches == 0 ) updateSearch( d->search ); } //////////////////////////////////////////////////////////////////////////////// // KTreeViewSearchLineWidget //////////////////////////////////////////////////////////////////////////////// class KTreeViewSearchLineWidget::Private { public: Private() : treeView( nullptr ), searchLine( nullptr ) { } QTreeView *treeView; KTreeViewSearchLine *searchLine; }; KTreeViewSearchLineWidget::KTreeViewSearchLineWidget( QWidget *parent, QTreeView *treeView ) : QWidget( parent ), d( new Private ) { d->treeView = treeView; QTimer::singleShot( 0, this, &KTreeViewSearchLineWidget::createWidgets ); } KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget() { delete d; } KTreeViewSearchLine *KTreeViewSearchLineWidget::createSearchLine( QTreeView *treeView ) const { return new KTreeViewSearchLine( const_cast(this), treeView ); } void KTreeViewSearchLineWidget::createWidgets() { QLabel *label = new QLabel( i18n("S&earch:"), this ); label->setObjectName( QStringLiteral("kde toolbar widget") ); searchLine()->show(); label->setBuddy( d->searchLine ); label->show(); QHBoxLayout* layout = new QHBoxLayout( this ); layout->setSpacing( 5 ); layout->setMargin( 0 ); layout->addWidget( label ); layout->addWidget( d->searchLine ); } KTreeViewSearchLine *KTreeViewSearchLineWidget::searchLine() const { if ( !d->searchLine ) d->searchLine = createSearchLine( d->treeView ); return d->searchLine; } #include "moc_ktreeviewsearchline.cpp" diff --git a/ui/latexrenderer.cpp b/ui/latexrenderer.cpp index 47f976109..bed55e2ab 100644 --- a/ui/latexrenderer.cpp +++ b/ui/latexrenderer.cpp @@ -1,200 +1,200 @@ /*************************************************************************** * Copyright (C) 2004 by Duncan Mac-Vicar Prett * * Copyright (C) 2004-2005 by Olivier Goffart * * Copyright (C) 2011 by Niels Ole Salscheider * * * * * * 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 "latexrenderer.h" -#include +#include #include #include #include #include #include #include #include #include #include "debug_ui.h" namespace GuiUtils { LatexRenderer::LatexRenderer() { } LatexRenderer::~LatexRenderer() { foreach(const QString &file, m_fileList) { QFile::remove(file); } } LatexRenderer::Error LatexRenderer::renderLatexInHtml( QString& html, const QColor& textColor, int fontSize, int resolution, QString& latexOutput ) { if( !html.contains(QStringLiteral("$$"))) return NoError; // this searches for $$formula$$ QRegExp rg(QStringLiteral("\\$\\$.+\\$\\$")); rg.setMinimal(true); int pos = 0; QMap replaceMap; while (pos >= 0 && pos < html.length()) { pos = rg.indexIn(html, pos); if (pos >= 0 ) { const QString match = rg.cap(0); pos += rg.matchedLength(); QString formul=match; // first remove the $$ delimiters on start and end formul.remove(QStringLiteral("$$")); // then trim the result, so we can skip totally empty/whitespace-only formulas formul = formul.trimmed(); if (formul.isEmpty() || !securityCheck(formul)) continue; //unescape formula formul.replace(QLatin1String(">"),QLatin1String(">")).replace(QLatin1String("<"),QLatin1String("<")).replace(QLatin1String("&"),QLatin1String("&")).replace(QLatin1String("""),QLatin1String("\"")).replace(QLatin1String("'"),QLatin1String("\'")).replace(QLatin1String("
"),QLatin1String(" ")); QString fileName; Error returnCode = handleLatex(fileName, formul, textColor, fontSize, resolution, latexOutput); if (returnCode != NoError) return returnCode; replaceMap[match] = fileName; } } if(replaceMap.isEmpty()) //we haven't found any LaTeX strings return NoError; int imagePxWidth,imagePxHeight; for (QMap::ConstIterator it = replaceMap.constBegin(); it != replaceMap.constEnd(); ++it) { QImage theImage(*it); if(theImage.isNull()) continue; imagePxWidth = theImage.width(); imagePxHeight = theImage.height(); QString escapedLATEX=it.key().toHtmlEscaped().replace(QLatin1Char('"'),QLatin1String(""")); //we need the escape quotes because that string will be in a title="" argument, but not the \n html.replace(it.key(), QStringLiteral(" \"") ")); } return NoError; } bool LatexRenderer::mightContainLatex (const QString& text) { if( !text.contains(QStringLiteral("$$"))) return false; // this searches for $$formula$$ QRegExp rg(QStringLiteral("\\$\\$.+\\$\\$")); rg.setMinimal(true); if( rg.lastIndexIn(text) == -1 ) return false; return true; } LatexRenderer::Error LatexRenderer::handleLatex( QString& fileName, const QString& latexFormula, const QColor& textColor, int fontSize, int resolution, QString& latexOutput ) { KProcess latexProc; KProcess dvipngProc; QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/okular_kdelatex-XXXXXX.tex")); tempFile->open(); QString tempFileName = tempFile->fileName(); QFileInfo *tempFileInfo = new QFileInfo(tempFileName); QString tempFileNameNS = tempFileInfo->absolutePath() + QDir::separator() + tempFileInfo->baseName(); QString tempFilePath = tempFileInfo->absolutePath(); delete tempFileInfo; QTextStream tempStream(tempFile); tempStream << "\ \\documentclass[" << fontSize << "pt]{article} \ \\usepackage{color} \ \\usepackage{amsmath,latexsym,amsfonts,amssymb,ulem} \ \\pagestyle{empty} \ \\begin{document} \ {\\color[rgb]{" << textColor.redF() << "," << textColor.greenF() << "," << textColor.blueF() << "} \ \\begin{eqnarray*} \ " << latexFormula << " \ \\end{eqnarray*}} \ \\end{document}"; tempFile->close(); QString latexExecutable = QStandardPaths::findExecutable(QStringLiteral("latex")); if (latexExecutable.isEmpty()) { qCDebug(OkularUiDebug) << "Could not find latex!"; delete tempFile; fileName = QString(); return LatexNotFound; } latexProc << latexExecutable << QStringLiteral("-interaction=nonstopmode") << QStringLiteral("-halt-on-error") << QStringLiteral("-output-directory=%1").arg(tempFilePath) << tempFile->fileName(); latexProc.setOutputChannelMode( KProcess::MergedChannels ); latexProc.execute(); latexOutput = QString::fromLocal8Bit(latexProc.readAll()); tempFile->remove(); QFile::remove(tempFileNameNS + QStringLiteral(".log")); QFile::remove(tempFileNameNS + QStringLiteral(".aux")); delete tempFile; if (!QFile::exists(tempFileNameNS + QStringLiteral(".dvi"))) { fileName = QString(); return LatexFailed; } QString dvipngExecutable = QStandardPaths::findExecutable(QStringLiteral("dvipng")); if (dvipngExecutable.isEmpty()) { qCDebug(OkularUiDebug) << "Could not find dvipng!"; fileName = QString(); return DvipngNotFound; } dvipngProc << dvipngExecutable << QStringLiteral("-o%1").arg(tempFileNameNS + QStringLiteral(".png")) << QStringLiteral("-Ttight") << QStringLiteral("-bgTransparent") << QStringLiteral("-D %1").arg(resolution) << QStringLiteral("%1").arg(tempFileNameNS + QStringLiteral(".dvi")); dvipngProc.setOutputChannelMode( KProcess::MergedChannels ); dvipngProc.execute(); QFile::remove(tempFileNameNS + QStringLiteral(".dvi")); if (!QFile::exists(tempFileNameNS + QStringLiteral(".png"))) { fileName = QString(); return DvipngFailed; } fileName = tempFileNameNS + QStringLiteral(".png"); m_fileList << fileName; return NoError; } bool LatexRenderer::securityCheck( const QString &latexFormula ) { return !latexFormula.contains(QRegExp(QString::fromLatin1("\\\\(def|let|futurelet|newcommand|renewcommand|else|fi|write|input|include" "|chardef|catcode|makeatletter|noexpand|toksdef|every|errhelp|errorstopmode|scrollmode|nonstopmode|batchmode" "|read|csname|newhelp|relax|afterground|afterassignment|expandafter|noexpand|special|command|loop|repeat|toks" "|output|line|mathcode|name|item|section|mbox|DeclareRobustCommand)[^a-zA-Z]"))); } } diff --git a/ui/pageview.cpp b/ui/pageview.cpp index 7c804f072..985aaab4c 100644 --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -1,5543 +1,5543 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2006 by Albert Astals Cid * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * With portions of code from kpdf/kpdf_pagewidget.cc by: * * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003 by Dirk Mueller * * Copyright (C) 2004 by James Ots * * Copyright (C) 2011 by Jiri Baum - NICTA * * * * 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 "pageview.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 #include #include #include #include // system includes #include #include // local includes #include "debug_ui.h" #include "formwidgets.h" #include "pageviewutils.h" #include "pagepainter.h" #include "core/annotations.h" #include "annotwindow.h" #include "guiutils.h" #include "annotationpopup.h" #include "pageviewannotator.h" #include "pageviewmouseannotation.h" #include "priorities.h" #include "toolaction.h" #include "okmenutitle.h" #ifdef HAVE_SPEECH #include "tts.h" #endif #include "videowidget.h" #include "core/action.h" #include "core/area.h" #include "core/document_p.h" #include "core/form.h" #include "core/page.h" #include "core/page_p.h" #include "core/misc.h" #include "core/generator.h" #include "core/movie.h" #include "core/audioplayer.h" #include "core/sourcereference.h" #include "core/tile.h" #include "settings.h" #include "settings_core.h" #include "url_utils.h" #include "magnifierview.h" static const int pageflags = PagePainter::Accessibility | PagePainter::EnhanceLinks | PagePainter::EnhanceImages | PagePainter::Highlights | PagePainter::TextSelection | PagePainter::Annotations; static const float kZoomValues[] = { 0.12, 0.25, 0.33, 0.50, 0.66, 0.75, 1.00, 1.25, 1.50, 2.00, 4.00, 8.00, 16.00 }; static inline double normClamp( double value, double def ) { return ( value < 0.0 || value > 1.0 ) ? def : value; } struct TableSelectionPart { PageViewItem * item; Okular::NormalizedRect rectInItem; Okular::NormalizedRect rectInSelection; TableSelectionPart(PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p); }; TableSelectionPart::TableSelectionPart( PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p) : item ( item_p ), rectInItem (rectInItem_p), rectInSelection (rectInSelection_p) { } // structure used internally by PageView for data storage class PageViewPrivate { public: PageViewPrivate( PageView *qq ); FormWidgetsController* formWidgetsController(); #ifdef HAVE_SPEECH OkularTTS* tts(); #endif QString selectedText() const; // the document, pageviewItems and the 'visible cache' PageView *q; Okular::Document * document; QVector< PageViewItem * > items; QLinkedList< PageViewItem * > visibleItems; MagnifierView *magnifierView; // view layout (columns and continuous in Settings), zoom and mouse PageView::ZoomMode zoomMode; float zoomFactor; QPoint mouseGrabPos; QPoint mousePressPos; QPoint mouseSelectPos; int mouseMidLastY; bool mouseSelecting; QRect mouseSelectionRect; QColor mouseSelectionColor; bool mouseTextSelecting; QSet< int > pagesWithTextSelection; bool mouseOnRect; int mouseMode; MouseAnnotation * mouseAnnotation; // table selection QList tableSelectionCols; QList tableSelectionRows; QList tableSelectionParts; bool tableDividersGuessed; // viewport move bool viewportMoveActive; QTime viewportMoveTime; QPoint viewportMoveDest; int lastSourceLocationViewportPageNumber; double lastSourceLocationViewportNormalizedX; double lastSourceLocationViewportNormalizedY; QTimer * viewportMoveTimer; int controlWheelAccumulatedDelta; // auto scroll int scrollIncrement; QTimer * autoScrollTimer; // annotations PageViewAnnotator * annotator; //text annotation dialogs list QSet< AnnotWindow * > m_annowindows; // other stuff QTimer * delayResizeEventTimer; bool dirtyLayout; bool blockViewport; // prevents changes to viewport bool blockPixmapsRequest; // prevent pixmap requests PageViewMessage * messageWindow; // in pageviewutils.h bool m_formsVisible; FormWidgetsController *formsWidgetController; #ifdef HAVE_SPEECH OkularTTS * m_tts; #endif QTimer * refreshTimer; QSet refreshPages; // bbox state for Trim to Selection mode Okular::NormalizedRect trimBoundingBox; // infinite resizing loop prevention bool verticalScrollBarVisible; bool horizontalScrollBarVisible; // drag scroll QPoint dragScrollVector; QTimer dragScrollTimer; // left click depress QTimer leftClickTimer; // actions QAction * aRotateClockwise; QAction * aRotateCounterClockwise; QAction * aRotateOriginal; KSelectAction * aPageSizes; KActionMenu * aTrimMode; KToggleAction * aTrimMargins; QAction * aMouseNormal; QAction * aMouseSelect; QAction * aMouseTextSelect; QAction * aMouseTableSelect; QAction * aMouseMagnifier; KToggleAction * aTrimToSelection; KToggleAction * aToggleAnnotator; KSelectAction * aZoom; QAction * aZoomIn; QAction * aZoomOut; KToggleAction * aZoomFitWidth; KToggleAction * aZoomFitPage; KToggleAction * aZoomAutoFit; KActionMenu * aViewMode; KToggleAction * aViewContinuous; QAction * aPrevAction; QAction * aToggleForms; QAction * aSpeakDoc; QAction * aSpeakPage; QAction * aSpeakStop; KActionCollection * actionCollection; QActionGroup * mouseModeActionGroup; QAction * aFitWindowToPage; int setting_viewCols; bool rtl_Mode; // Keep track of whether tablet pen is currently pressed down bool penDown; // Keep track of mouse over link object const Okular::ObjectRect * mouseOverLinkObject; }; PageViewPrivate::PageViewPrivate( PageView *qq ) : q( qq ) #ifdef HAVE_SPEECH , m_tts( nullptr ) #endif { } FormWidgetsController* PageViewPrivate::formWidgetsController() { if ( !formsWidgetController ) { formsWidgetController = new FormWidgetsController( document ); QObject::connect( formsWidgetController, SIGNAL( changed( int ) ), q, SLOT( slotFormChanged( int ) ) ); QObject::connect( formsWidgetController, SIGNAL( action( Okular::Action* ) ), q, SLOT( slotAction( Okular::Action* ) ) ); } return formsWidgetController; } #ifdef HAVE_SPEECH OkularTTS* PageViewPrivate::tts() { if ( !m_tts ) { m_tts = new OkularTTS( q ); if ( aSpeakStop ) { QObject::connect( m_tts, &OkularTTS::isSpeaking, aSpeakStop, &QAction::setEnabled ); } } return m_tts; } #endif /* PageView. What's in this file? -> quick overview. * Code weight (in rows) and meaning: * 160 - constructor and creating actions plus their connected slots (empty stuff) * 70 - DocumentObserver inherited methodes (important) * 550 - events: mouse, keyboard, drag * 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes * 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc.. * other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable, * and many insignificant stuff like this comment :-) */ PageView::PageView( QWidget *parent, Okular::Document *document ) : QAbstractScrollArea( parent ) , Okular::View( QLatin1String( "PageView" ) ) { // create and initialize private storage structure d = new PageViewPrivate( this ); d->document = document; d->aRotateClockwise = nullptr; d->aRotateCounterClockwise = nullptr; d->aRotateOriginal = nullptr; d->aViewMode = nullptr; d->zoomMode = PageView::ZoomFitWidth; d->zoomFactor = 1.0; d->mouseSelecting = false; d->mouseTextSelecting = false; d->mouseOnRect = false; d->mouseMode = Okular::Settings::mouseMode(); d->mouseAnnotation = new MouseAnnotation( this, document ); d->tableDividersGuessed = false; d->viewportMoveActive = false; d->lastSourceLocationViewportPageNumber = -1; d->lastSourceLocationViewportNormalizedX = 0.0; d->lastSourceLocationViewportNormalizedY = 0.0; d->viewportMoveTimer = nullptr; d->controlWheelAccumulatedDelta = 0; d->scrollIncrement = 0; d->autoScrollTimer = nullptr; d->annotator = nullptr; d->dirtyLayout = false; d->blockViewport = false; d->blockPixmapsRequest = false; d->messageWindow = new PageViewMessage(this); d->m_formsVisible = false; d->formsWidgetController = nullptr; #ifdef HAVE_SPEECH d->m_tts = nullptr; #endif d->refreshTimer = nullptr; d->aRotateClockwise = nullptr; d->aRotateCounterClockwise = nullptr; d->aRotateOriginal = nullptr; d->aPageSizes = nullptr; d->aTrimMode = nullptr; d->aTrimMargins = nullptr; d->aTrimToSelection = nullptr; d->aMouseNormal = nullptr; d->aMouseSelect = nullptr; d->aMouseTextSelect = nullptr; d->aToggleAnnotator = nullptr; d->aZoomFitWidth = nullptr; d->aZoomFitPage = nullptr; d->aZoomAutoFit = nullptr; d->aViewMode = nullptr; d->aViewContinuous = nullptr; d->aPrevAction = nullptr; d->aToggleForms = nullptr; d->aSpeakDoc = nullptr; d->aSpeakPage = nullptr; d->aSpeakStop = nullptr; d->actionCollection = nullptr; d->aPageSizes=nullptr; d->setting_viewCols = Okular::Settings::viewColumns(); d->rtl_Mode = Okular::Settings::rtlReadingDirection(); d->mouseModeActionGroup = nullptr; d->penDown = false; d->aMouseMagnifier = nullptr; d->aFitWindowToPage = nullptr; d->trimBoundingBox = Okular::NormalizedRect(); // Null box switch( Okular::Settings::zoomMode() ) { case 0: { d->zoomFactor = 1; d->zoomMode = PageView::ZoomFixed; break; } case 1: { d->zoomMode = PageView::ZoomFitWidth; break; } case 2: { d->zoomMode = PageView::ZoomFitPage; break; } case 3: { d->zoomMode = PageView::ZoomFitAuto; break; } } d->delayResizeEventTimer = new QTimer( this ); d->delayResizeEventTimer->setSingleShot( true ); connect( d->delayResizeEventTimer, &QTimer::timeout, this, &PageView::delayedResizeEvent ); setFrameStyle(QFrame::NoFrame); setAttribute( Qt::WA_StaticContents ); setObjectName( QStringLiteral( "okular::pageView" ) ); // viewport setup: setup focus, and track mouse viewport()->setFocusProxy( this ); viewport()->setFocusPolicy( Qt::StrongFocus ); viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); viewport()->setAttribute( Qt::WA_NoSystemBackground ); viewport()->setMouseTracking( true ); viewport()->setAutoFillBackground( false ); // the apparently "magic" value of 20 is the same used internally in QScrollArea verticalScrollBar()->setCursor( Qt::ArrowCursor ); verticalScrollBar()->setSingleStep( 20 ); horizontalScrollBar()->setCursor( Qt::ArrowCursor ); horizontalScrollBar()->setSingleStep( 20 ); // conntect the padding of the viewport to pixmaps requests connect(horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &PageView::slotRequestVisiblePixmaps); connect(verticalScrollBar(), &QAbstractSlider::valueChanged, this, &PageView::slotRequestVisiblePixmaps); connect( &d->dragScrollTimer, &QTimer::timeout, this, &PageView::slotDragScroll ); d->leftClickTimer.setSingleShot( true ); connect( &d->leftClickTimer, &QTimer::timeout, this, &PageView::slotShowSizeAllCursor ); // set a corner button to resize the view to the page size // QPushButton * resizeButton = new QPushButton( viewport() ); // resizeButton->setPixmap( SmallIcon("crop") ); // setCornerWidget( resizeButton ); // resizeButton->setEnabled( false ); // connect(...); setAttribute( Qt::WA_InputMethodEnabled, true ); // Grab pinch gestures to zoom and rotate the view grabGesture(Qt::PinchGesture); d->magnifierView = new MagnifierView(document, this); d->magnifierView->hide(); d->magnifierView->setGeometry(0, 0, 351, 201); // TODO: more dynamic? connect(document, &Okular::Document::processMovieAction, this, &PageView::slotProcessMovieAction); connect(document, &Okular::Document::processRenditionAction, this, &PageView::slotProcessRenditionAction); // schedule the welcome message QMetaObject::invokeMethod(this, "slotShowWelcome", Qt::QueuedConnection); } PageView::~PageView() { #ifdef HAVE_SPEECH if ( d->m_tts ) d->m_tts->stopAllSpeechs(); #endif delete d->mouseAnnotation; // delete the local storage structure // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed // will bite us and clear d->m_annowindows QSet< AnnotWindow * > annowindows = d->m_annowindows; d->m_annowindows.clear(); qDeleteAll( annowindows ); // delete all widgets QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; delete d->formsWidgetController; d->document->removeObserver( this ); delete d; } void PageView::setupBaseActions( KActionCollection * ac ) { d->actionCollection = ac; // Zoom actions ( higher scales takes lots of memory! ) d->aZoom = new KSelectAction(QIcon::fromTheme( QStringLiteral("page-zoom") ), i18n("Zoom"), this); ac->addAction(QStringLiteral("zoom_to"), d->aZoom ); d->aZoom->setEditable( true ); d->aZoom->setMaxComboViewCount( 14 ); connect( d->aZoom, SIGNAL(triggered(QAction*)), this, SLOT(slotZoom()) ); updateZoomText(); d->aZoomIn = KStandardAction::zoomIn( this, SLOT(slotZoomIn()), ac ); d->aZoomOut = KStandardAction::zoomOut( this, SLOT(slotZoomOut()), ac ); } void PageView::setupViewerActions( KActionCollection * ac ) { d->actionCollection = ac; ac->setDefaultShortcut(d->aZoomIn, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus)); ac->setDefaultShortcut(d->aZoomOut, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Minus)); // orientation menu actions d->aRotateClockwise = new QAction( QIcon::fromTheme( QStringLiteral("object-rotate-right") ), i18n( "Rotate &Right" ), this ); d->aRotateClockwise->setIconText( i18nc( "Rotate right", "Right" ) ); ac->addAction( QStringLiteral("view_orientation_rotate_cw"), d->aRotateClockwise ); d->aRotateClockwise->setEnabled( false ); connect( d->aRotateClockwise, &QAction::triggered, this, &PageView::slotRotateClockwise ); d->aRotateCounterClockwise = new QAction( QIcon::fromTheme( QStringLiteral("object-rotate-left") ), i18n( "Rotate &Left" ), this ); d->aRotateCounterClockwise->setIconText( i18nc( "Rotate left", "Left" ) ); ac->addAction( QStringLiteral("view_orientation_rotate_ccw"), d->aRotateCounterClockwise ); d->aRotateCounterClockwise->setEnabled( false ); connect( d->aRotateCounterClockwise, &QAction::triggered, this, &PageView::slotRotateCounterClockwise ); d->aRotateOriginal = new QAction( i18n( "Original Orientation" ), this ); ac->addAction( QStringLiteral("view_orientation_original"), d->aRotateOriginal ); d->aRotateOriginal->setEnabled( false ); connect( d->aRotateOriginal, &QAction::triggered, this, &PageView::slotRotateOriginal ); d->aPageSizes = new KSelectAction(i18n("&Page Size"), this); ac->addAction(QStringLiteral("view_pagesizes"), d->aPageSizes); d->aPageSizes->setEnabled( false ); connect( d->aPageSizes , SIGNAL(triggered(int)), this, SLOT(slotPageSizes(int)) ); // Trim View actions d->aTrimMode = new KActionMenu(i18n( "&Trim View" ), this ); d->aTrimMode->setDelayed( false ); ac->addAction(QStringLiteral("view_trim_mode"), d->aTrimMode ); d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), d->aTrimMode->menu() ); d->aTrimMode->addAction( d->aTrimMargins ); ac->addAction( QStringLiteral("view_trim_margins"), d->aTrimMargins ); d->aTrimMargins->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Margins ) ); connect( d->aTrimMargins, &QAction::toggled, this, &PageView::slotTrimMarginsToggled ); d->aTrimMargins->setChecked( Okular::Settings::trimMargins() ); d->aTrimToSelection = new KToggleAction( i18n( "Trim To &Selection" ), d->aTrimMode->menu() ); d->aTrimMode->addAction( d->aTrimToSelection); ac->addAction( QStringLiteral("view_trim_selection"), d->aTrimToSelection); d->aTrimToSelection->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Selection ) ); connect( d->aTrimToSelection, &QAction::toggled, this, &PageView::slotTrimToSelectionToggled ); d->aZoomFitWidth = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-width") ), i18n("Fit &Width"), this); ac->addAction(QStringLiteral("view_fit_to_width"), d->aZoomFitWidth ); connect( d->aZoomFitWidth, &QAction::toggled, this, &PageView::slotFitToWidthToggled ); d->aZoomFitPage = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-best") ), i18n("Fit &Page"), this); ac->addAction(QStringLiteral("view_fit_to_page"), d->aZoomFitPage ); connect( d->aZoomFitPage, &QAction::toggled, this, &PageView::slotFitToPageToggled ); d->aZoomAutoFit = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-best") ), i18n("&Auto Fit"), this); ac->addAction(QStringLiteral("view_auto_fit"), d->aZoomAutoFit ); connect( d->aZoomAutoFit, &QAction::toggled, this, &PageView::slotAutoFitToggled ); d->aFitWindowToPage = new QAction(QIcon::fromTheme( QStringLiteral("zoom-fit-width") ), i18n("Fit Wi&ndow to Page"), this); d->aFitWindowToPage->setEnabled( Okular::Settings::viewMode() == (int)Okular::Settings::EnumViewMode::Single ); ac->setDefaultShortcut(d->aFitWindowToPage, QKeySequence(Qt::CTRL + Qt::Key_J) ); ac->addAction( QStringLiteral("fit_window_to_page"), d->aFitWindowToPage ); connect( d->aFitWindowToPage, &QAction::triggered, this, &PageView::slotFitWindowToPage ); // View-Layout actions d->aViewMode = new KActionMenu( QIcon::fromTheme( QStringLiteral("view-split-left-right") ), i18n( "&View Mode" ), this ); d->aViewMode->setDelayed( false ); #define ADD_VIEWMODE_ACTION( text, name, id ) \ do { \ QAction *vm = new QAction( text, this ); \ vm->setCheckable( true ); \ vm->setData( qVariantFromValue( id ) ); \ d->aViewMode->addAction( vm ); \ ac->addAction( QStringLiteral(name), vm ); \ vmGroup->addAction( vm ); \ } while( 0 ) ac->addAction(QStringLiteral("view_render_mode"), d->aViewMode ); QActionGroup *vmGroup = new QActionGroup( this ); //d->aViewMode->menu() ); ADD_VIEWMODE_ACTION( i18n( "Single Page" ), "view_render_mode_single", (int)Okular::Settings::EnumViewMode::Single ); ADD_VIEWMODE_ACTION( i18n( "Facing Pages" ), "view_render_mode_facing", (int)Okular::Settings::EnumViewMode::Facing ); ADD_VIEWMODE_ACTION( i18n( "Facing Pages (Center First Page)" ), "view_render_mode_facing_center_first", (int)Okular::Settings::EnumViewMode::FacingFirstCentered ); ADD_VIEWMODE_ACTION( i18n( "Overview" ), "view_render_mode_overview", (int)Okular::Settings::EnumViewMode::Summary ); const QList viewModeActions = d->aViewMode->menu()->actions(); foreach(QAction *viewModeAction, viewModeActions) { if (viewModeAction->data().toInt() == Okular::Settings::viewMode()) { viewModeAction->setChecked( true ); } } connect( vmGroup, &QActionGroup::triggered, this, &PageView::slotViewMode ); #undef ADD_VIEWMODE_ACTION d->aViewContinuous = new KToggleAction(QIcon::fromTheme( QStringLiteral("view-list-text") ), i18n("&Continuous"), this); ac->addAction(QStringLiteral("view_continuous"), d->aViewContinuous ); connect( d->aViewContinuous, &QAction::toggled, this, &PageView::slotContinuousToggled ); d->aViewContinuous->setChecked( Okular::Settings::viewContinuous() ); // Mouse mode actions for viewer mode d->mouseModeActionGroup = new QActionGroup( this ); d->mouseModeActionGroup->setExclusive( true ); d->aMouseNormal = new QAction( QIcon::fromTheme( QStringLiteral("input-mouse") ), i18n( "&Browse Tool" ), this ); ac->addAction(QStringLiteral("mouse_drag"), d->aMouseNormal ); connect( d->aMouseNormal, &QAction::triggered, this, &PageView::slotSetMouseNormal ); d->aMouseNormal->setIconText( i18nc( "Browse Tool", "Browse" ) ); d->aMouseNormal->setCheckable( true ); ac->setDefaultShortcut(d->aMouseNormal, QKeySequence(Qt::CTRL + Qt::Key_1)); d->aMouseNormal->setActionGroup( d->mouseModeActionGroup ); d->aMouseNormal->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse ); QAction * mz = new QAction(QIcon::fromTheme( QStringLiteral("page-zoom") ), i18n("&Zoom Tool"), this); ac->addAction(QStringLiteral("mouse_zoom"), mz ); connect( mz, &QAction::triggered, this, &PageView::slotSetMouseZoom ); mz->setIconText( i18nc( "Zoom Tool", "Zoom" ) ); mz->setCheckable( true ); ac->setDefaultShortcut(mz, QKeySequence(Qt::CTRL + Qt::Key_2)); mz->setActionGroup( d->mouseModeActionGroup ); mz->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Zoom ); QAction * aToggleChangeColors = new QAction(i18n("&Toggle Change Colors"), this); ac->addAction(QStringLiteral("toggle_change_colors"), aToggleChangeColors ); connect( aToggleChangeColors, &QAction::triggered, this, &PageView::slotToggleChangeColors ); } // WARNING: 'setupViewerActions' must have been called before this method void PageView::setupActions( KActionCollection * ac ) { d->actionCollection = ac; ac->setDefaultShortcuts(d->aZoomIn, KStandardShortcut::zoomIn()); ac->setDefaultShortcuts(d->aZoomOut, KStandardShortcut::zoomOut()); // Mouse-Mode actions d->aMouseSelect = new QAction(QIcon::fromTheme( QStringLiteral("select-rectangular") ), i18n("&Selection Tool"), this); ac->addAction(QStringLiteral("mouse_select"), d->aMouseSelect ); connect( d->aMouseSelect, &QAction::triggered, this, &PageView::slotSetMouseSelect ); d->aMouseSelect->setIconText( i18nc( "Select Tool", "Selection" ) ); d->aMouseSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseSelect, Qt::CTRL + Qt::Key_3); d->aMouseSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::RectSelect ); d->aMouseTextSelect = new QAction(QIcon::fromTheme( QStringLiteral("draw-text") ), i18n("&Text Selection Tool"), this); ac->addAction(QStringLiteral("mouse_textselect"), d->aMouseTextSelect ); connect( d->aMouseTextSelect, &QAction::triggered, this, &PageView::slotSetMouseTextSelect ); d->aMouseTextSelect->setIconText( i18nc( "Text Selection Tool", "Text Selection" ) ); d->aMouseTextSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseTextSelect, Qt::CTRL + Qt::Key_4); d->aMouseTextSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseTextSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect ); d->aMouseTableSelect = new QAction(QIcon::fromTheme( QStringLiteral("table") ), i18n("T&able Selection Tool"), this); ac->addAction(QStringLiteral("mouse_tableselect"), d->aMouseTableSelect ); connect( d->aMouseTableSelect, &QAction::triggered, this, &PageView::slotSetMouseTableSelect ); d->aMouseTableSelect->setIconText( i18nc( "Table Selection Tool", "Table Selection" ) ); d->aMouseTableSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseTableSelect, Qt::CTRL + Qt::Key_5); d->aMouseTableSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseTableSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TableSelect ); d->aMouseMagnifier = new QAction(QIcon::fromTheme( QStringLiteral("document-preview") ), i18n("&Magnifier"), this); ac->addAction(QStringLiteral("mouse_magnifier"), d->aMouseMagnifier ); connect( d->aMouseMagnifier, &QAction::triggered, this, &PageView::slotSetMouseMagnifier ); d->aMouseMagnifier->setIconText( i18nc( "Magnifier Tool", "Magnifier" ) ); d->aMouseMagnifier->setCheckable( true ); ac->setDefaultShortcut(d->aMouseMagnifier, Qt::CTRL + Qt::Key_6); d->aMouseMagnifier->setActionGroup( d->mouseModeActionGroup ); d->aMouseMagnifier->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier ); d->aToggleAnnotator = new KToggleAction(QIcon::fromTheme( QStringLiteral("draw-freehand") ), i18n("&Review"), this); ac->addAction(QStringLiteral("mouse_toggle_annotate"), d->aToggleAnnotator ); d->aToggleAnnotator->setCheckable( true ); connect( d->aToggleAnnotator, &QAction::toggled, this, &PageView::slotToggleAnnotator ); ac->setDefaultShortcut(d->aToggleAnnotator, Qt::Key_F6); ToolAction *ta = new ToolAction( this ); ac->addAction( QStringLiteral("mouse_selecttools"), ta ); ta->addAction( d->aMouseSelect ); ta->addAction( d->aMouseTextSelect ); ta->addAction( d->aMouseTableSelect ); // speak actions #ifdef HAVE_SPEECH d->aSpeakDoc = new QAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Whole Document" ), this ); ac->addAction( QStringLiteral("speak_document"), d->aSpeakDoc ); d->aSpeakDoc->setEnabled( false ); connect( d->aSpeakDoc, &QAction::triggered, this, &PageView::slotSpeakDocument ); d->aSpeakPage = new QAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Current Page" ), this ); ac->addAction( QStringLiteral("speak_current_page"), d->aSpeakPage ); d->aSpeakPage->setEnabled( false ); connect( d->aSpeakPage, &QAction::triggered, this, &PageView::slotSpeakCurrentPage ); d->aSpeakStop = new QAction( QIcon::fromTheme( QStringLiteral("media-playback-stop") ), i18n( "Stop Speaking" ), this ); ac->addAction( QStringLiteral("speak_stop_all"), d->aSpeakStop ); d->aSpeakStop->setEnabled( false ); connect( d->aSpeakStop, &QAction::triggered, this, &PageView::slotStopSpeaks ); #else d->aSpeakDoc = 0; d->aSpeakPage = 0; d->aSpeakStop = 0; #endif // Other actions QAction * su = new QAction(i18n("Scroll Up"), this); ac->addAction(QStringLiteral("view_scroll_up"), su ); connect( su, &QAction::triggered, this, &PageView::slotAutoScrollUp ); ac->setDefaultShortcut(su, QKeySequence(Qt::SHIFT + Qt::Key_Up)); addAction(su); QAction * sd = new QAction(i18n("Scroll Down"), this); ac->addAction(QStringLiteral("view_scroll_down"), sd ); connect( sd, &QAction::triggered, this, &PageView::slotAutoScrollDown ); ac->setDefaultShortcut(sd, QKeySequence(Qt::SHIFT + Qt::Key_Down)); addAction(sd); QAction * spu = new QAction(i18n("Scroll Page Up"), this); ac->addAction( QStringLiteral("view_scroll_page_up"), spu ); connect( spu, &QAction::triggered, this, &PageView::slotScrollUp ); ac->setDefaultShortcut(spu, QKeySequence(Qt::SHIFT + Qt::Key_Space)); addAction( spu ); QAction * spd = new QAction(i18n("Scroll Page Down"), this); ac->addAction( QStringLiteral("view_scroll_page_down"), spd ); connect( spd, &QAction::triggered, this, &PageView::slotScrollDown ); ac->setDefaultShortcut(spd, QKeySequence(Qt::Key_Space)); addAction( spd ); d->aToggleForms = new QAction( this ); ac->addAction( QStringLiteral("view_toggle_forms"), d->aToggleForms ); connect( d->aToggleForms, &QAction::triggered, this, &PageView::slotToggleForms ); d->aToggleForms->setEnabled( false ); toggleFormWidgets( false ); // Setup undo and redo actions QAction *kundo = KStandardAction::create( KStandardAction::Undo, d->document, SLOT(undo()), ac ); QAction *kredo = KStandardAction::create( KStandardAction::Redo, d->document, SLOT(redo()), ac ); connect(d->document, &Okular::Document::canUndoChanged, kundo, &QAction::setEnabled); connect(d->document, &Okular::Document::canRedoChanged, kredo, &QAction::setEnabled); kundo->setEnabled(false); kredo->setEnabled(false); } bool PageView::canFitPageWidth() const { return Okular::Settings::viewMode() != Okular::Settings::EnumViewMode::Single || d->zoomMode != ZoomFitWidth; } void PageView::fitPageWidth( int page ) { // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update d->zoomMode = ZoomFitWidth; Okular::Settings::setViewMode( 0 ); d->aZoomFitWidth->setChecked( true ); d->aZoomFitPage->setChecked( false ); d->aZoomAutoFit->setChecked( false ); d->aViewMode->menu()->actions().at( 0 )->setChecked( true ); viewport()->setUpdatesEnabled( false ); slotRelayoutPages(); viewport()->setUpdatesEnabled( true ); d->document->setViewportPage( page ); updateZoomText(); setFocus(); } void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNumber ) { if ( !annotation ) return; // find the annot window AnnotWindow* existWindow = nullptr; foreach(AnnotWindow *aw, d->m_annowindows) { if ( aw->annotation() == annotation ) { existWindow = aw; break; } } if ( existWindow == nullptr ) { existWindow = new AnnotWindow( this, annotation, d->document, pageNumber ); connect(existWindow, &QObject::destroyed, this, &PageView::slotAnnotationWindowDestroyed); d->m_annowindows << existWindow; } else { existWindow->raise(); existWindow->findChild()->setFocus(); } existWindow->show(); } void PageView::slotAnnotationWindowDestroyed( QObject * window ) { d->m_annowindows.remove( static_cast( window ) ); } void PageView::displayMessage( const QString & message, const QString & details, PageViewMessage::Icon icon, int duration ) { if ( !Okular::Settings::showOSD() ) { if (icon == PageViewMessage::Error) { if ( !details.isEmpty() ) KMessageBox::detailedError( this, message, details ); else KMessageBox::error( this, message ); } return; } // hide messageWindow if string is empty if ( message.isEmpty() ) return d->messageWindow->hide(); // display message (duration is length dependant) if (duration==-1) { duration = 500 + 100 * message.length(); if ( !details.isEmpty() ) duration += 500 + 100 * details.length(); } d->messageWindow->display( message, details, icon, duration ); } void PageView::reparseConfig() { // set the scroll bars policies Qt::ScrollBarPolicy scrollBarMode = Okular::Settings::showScrollBars() ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff; if ( horizontalScrollBarPolicy() != scrollBarMode ) { setHorizontalScrollBarPolicy( scrollBarMode ); setVerticalScrollBarPolicy( scrollBarMode ); } if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Summary && ( (int)Okular::Settings::viewColumns() != d->setting_viewCols ) ) { d->setting_viewCols = Okular::Settings::viewColumns(); slotRelayoutPages(); } if (Okular::Settings::rtlReadingDirection() != d->rtl_Mode ) { d->rtl_Mode = Okular::Settings::rtlReadingDirection(); slotRelayoutPages(); } updatePageStep(); if ( d->annotator ) { d->annotator->setEnabled( false ); d->annotator->reparseConfig(); if ( d->aToggleAnnotator->isChecked() ) slotToggleAnnotator( true ); } // Something like invert colors may have changed // As we don't have a way to find out the old value // We just update the viewport, this shouldn't be that bad // since it's just a repaint of pixmaps we already have viewport()->update(); } KActionCollection *PageView::actionCollection() const { return d->actionCollection; } QAction *PageView::toggleFormsAction() const { return d->aToggleForms; } int PageView::contentAreaWidth() const { return horizontalScrollBar()->maximum() + viewport()->width(); } int PageView::contentAreaHeight() const { return verticalScrollBar()->maximum() + viewport()->height(); } QPoint PageView::contentAreaPosition() const { return QPoint( horizontalScrollBar()->value(), verticalScrollBar()->value() ); } QPoint PageView::contentAreaPoint( const QPoint & pos ) const { return pos + contentAreaPosition(); } QPointF PageView::contentAreaPoint( const QPointF & pos ) const { return pos + contentAreaPosition(); } QString PageViewPrivate::selectedText() const { if ( pagesWithTextSelection.isEmpty() ) return QString(); QString text; QList< int > selpages = pagesWithTextSelection.toList(); qSort( selpages ); const Okular::Page * pg = nullptr; if ( selpages.count() == 1 ) { pg = document->page( selpages.first() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } else { pg = document->page( selpages.first() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); int end = selpages.count() - 1; for( int i = 1; i < end; ++i ) { pg = document->page( selpages.at( i ) ); text.append( pg->text( nullptr, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } pg = document->page( selpages.last() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } return text; } void PageView::copyTextSelection() const { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); cb->setText( text, QClipboard::Clipboard ); } } void PageView::selectAll() { QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); for ( ; it < itEnd; ++it ) { Okular::RegularAreaRect * area = textSelectionForItem( *it ); d->pagesWithTextSelection.insert( (*it)->pageNumber() ); d->document->setPageTextSelection( (*it)->pageNumber(), area, palette().color( QPalette::Active, QPalette::Highlight ) ); } } void PageView::createAnnotationsVideoWidgets(PageViewItem *item, const QLinkedList< Okular::Annotation * > &annotations) { qDeleteAll( item->videoWidgets() ); item->videoWidgets().clear(); QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.constBegin(), aEnd = annotations.constEnd(); 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(), d->document, viewport() ); item->videoWidgets().insert( movieAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::ARichMedia ) { Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), d->document, viewport() ); item->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, d->document, viewport() ); item->videoWidgets().insert( movie, vw ); vw->pageInitialized(); } } } } //BEGIN DocumentObserver inherited methods void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) { bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged; const bool allownotes = d->document->isAllowed( Okular::AllowNotes ); const bool allowfillforms = d->document->isAllowed( Okular::AllowFillForms ); // allownotes may have changed if ( d->aToggleAnnotator ) d->aToggleAnnotator->setEnabled( allownotes ); // reuse current pages if nothing new if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) ) { int count = pageSet.count(); for ( int i = 0; (i < count) && !documentChanged; i++ ) { if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() ) { documentChanged = true; } else { // even if the document has not changed, allowfillforms may have // changed, so update all fields' "canBeFilled" flag foreach ( FormWidgetIface * w, d->items[i]->formWidgets() ) w->setCanBeFilled( allowfillforms ); } } if ( !documentChanged ) { if ( setupFlags & Okular::DocumentObserver::UrlChanged ) { // Here with UrlChanged and no document changed it means we // need to update all the Annotation* and Form* otherwise // they still point to the old document ones, luckily the old ones are still // around so we can look for the new ones using unique ids, etc d->mouseAnnotation->updateAnnotationPointers(); foreach(AnnotWindow *aw, d->m_annowindows) { Okular::Annotation *newA = d->document->page( aw->pageNumber() )->annotation( aw->annotation()->uniqueName() ); aw->updateAnnotation( newA ); } const QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewport()->width(), viewport()->height() ); for ( int i = 0; i < count; i++ ) { PageViewItem *item = d->items[i]; const QSet fws = item->formWidgets(); foreach ( FormWidgetIface * w, fws ) { Okular::FormField *f = Okular::PagePrivate::findEquivalentForm( d->document->page( i ), w->formField() ); if (f) { w->setFormField( f ); } else { qWarning() << "Lost form field on document save, something is wrong"; item->formWidgets().remove(w); delete w; } } // For the video widgets we don't really care about reusing them since they don't contain much info so just // create them again createAnnotationsVideoWidgets( item, pageSet[i]->annotations() ); Q_FOREACH ( VideoWidget *vw, item->videoWidgets() ) { const Okular::NormalizedRect r = vw->normGeometry(); vw->setGeometry( qRound( item->uncroppedGeometry().left() + item->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( item->uncroppedGeometry().top() + item->uncroppedHeight() * r.top ) + 1 - viewportRect.top(), qRound( fabs( r.right - r.left ) * item->uncroppedGeometry().width() ), qRound( fabs( r.bottom - r.top ) * item->uncroppedGeometry().height() ) ); // Workaround, otherwise the size somehow gets lost vw->show(); vw->hide(); } } } return; } } // mouseAnnotation must not access our PageViewItem widgets any longer d->mouseAnnotation->reset(); // delete all widgets (one for each page in pageSet) QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; d->items.clear(); d->visibleItems.clear(); d->pagesWithTextSelection.clear(); toggleFormWidgets( false ); if ( d->formsWidgetController ) d->formsWidgetController->dropRadioButtons(); bool haspages = !pageSet.isEmpty(); bool hasformwidgets = false; // create children widgets QVector< Okular::Page * >::const_iterator setIt = pageSet.constBegin(), setEnd = pageSet.constEnd(); for ( ; setIt != setEnd; ++setIt ) { PageViewItem * item = new PageViewItem( *setIt ); d->items.push_back( item ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug).nospace() << "cropped geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->croppedGeometry(); #endif const QLinkedList< Okular::FormField * > pageFields = (*setIt)->formFields(); QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd(); for ( ; ffIt != ffEnd; ++ffIt ) { Okular::FormField * ff = *ffIt; FormWidgetIface * w = FormWidgetFactory::createWidget( ff, viewport() ); if ( w ) { w->setPageItem( item ); w->setFormWidgetsController( d->formWidgetsController() ); w->setVisibility( false ); w->setCanBeFilled( allowfillforms ); item->formWidgets().insert( w ); hasformwidgets = true; } } createAnnotationsVideoWidgets( item, (*setIt)->annotations() ); } // invalidate layout so relayout/repaint will happen on next viewport change if ( haspages ) { // We do a delayed call to slotRelayoutPages but also set the dirtyLayout // because we might end up in notifyViewportChanged while slotRelayoutPages // has not been done and we don't want that to happen d->dirtyLayout = true; QMetaObject::invokeMethod(this, "slotRelayoutPages", Qt::QueuedConnection); } else { // update the mouse cursor when closing because we may have close through a link and // want the cursor to come back to the normal cursor updateCursor(); // then, make the message window and scrollbars disappear, and trigger a repaint d->messageWindow->hide(); resizeContentArea( QSize( 0,0 ) ); viewport()->update(); // when there is no change to the scrollbars, no repaint would // be done and the old document would still be shown } // OSD to display pages if ( documentChanged && pageSet.count() > 0 && Okular::Settings::showOSD() ) d->messageWindow->display( i18np(" Loaded a one-page document.", " Loaded a %1-page document.", pageSet.count() ), QString(), PageViewMessage::Info, 4000 ); updateActionState( haspages, documentChanged, hasformwidgets ); // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed // will bite us and clear d->m_annowindows QSet< AnnotWindow * > annowindows = d->m_annowindows; d->m_annowindows.clear(); qDeleteAll( annowindows ); selectionClear(); } void PageView::updateActionState( bool haspages, bool documentChanged, bool hasformwidgets ) { if ( d->aPageSizes ) { // may be null if dummy mode is on bool pageSizes = d->document->supportsPageSizes(); d->aPageSizes->setEnabled( pageSizes ); // set the new page sizes: // - if the generator supports them // - if the document changed if ( pageSizes && documentChanged ) { QStringList items; foreach ( const Okular::PageSize &p, d->document->pageSizes() ) items.append( p.name() ); d->aPageSizes->setItems( items ); } } if ( d->aTrimMargins ) d->aTrimMargins->setEnabled( haspages ); if ( d->aTrimToSelection ) d->aTrimToSelection->setEnabled( haspages ); if ( d->aViewMode ) d->aViewMode->setEnabled( haspages ); if ( d->aViewContinuous ) d->aViewContinuous->setEnabled( haspages ); if ( d->aZoomFitWidth ) d->aZoomFitWidth->setEnabled( haspages ); if ( d->aZoomFitPage ) d->aZoomFitPage->setEnabled( haspages ); if ( d->aZoomAutoFit ) d->aZoomAutoFit->setEnabled( haspages ); if ( d->aZoom ) { d->aZoom->selectableActionGroup()->setEnabled( haspages ); d->aZoom->setEnabled( haspages ); } if ( d->aZoomIn ) d->aZoomIn->setEnabled( haspages ); if ( d->aZoomOut ) d->aZoomOut->setEnabled( haspages ); if ( d->mouseModeActionGroup ) d->mouseModeActionGroup->setEnabled( haspages ); if ( d->aRotateClockwise ) d->aRotateClockwise->setEnabled( haspages ); if ( d->aRotateCounterClockwise ) d->aRotateCounterClockwise->setEnabled( haspages ); if ( d->aRotateOriginal ) d->aRotateOriginal->setEnabled( haspages ); if ( d->aToggleForms ) { // may be null if dummy mode is on d->aToggleForms->setEnabled( haspages && hasformwidgets ); } bool allowAnnotations = d->document->isAllowed( Okular::AllowNotes ); if ( d->annotator ) { bool allowTools = haspages && allowAnnotations; d->annotator->setToolsEnabled( allowTools ); d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); } if ( d->aToggleAnnotator ) { if ( !allowAnnotations && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); } d->aToggleAnnotator->setEnabled( allowAnnotations ); } #ifdef HAVE_SPEECH if ( d->aSpeakDoc ) { const bool enablettsactions = haspages ? Okular::Settings::useTTS() : false; d->aSpeakDoc->setEnabled( enablettsactions ); d->aSpeakPage->setEnabled( enablettsactions ); } #endif if (d->aMouseMagnifier) d->aMouseMagnifier->setEnabled(d->document->supportsTiles()); if ( d->aFitWindowToPage ) d->aFitWindowToPage->setEnabled( haspages && !Okular::Settings::viewContinuous() ); } bool PageView::areSourceLocationsShownGraphically() const { return Okular::Settings::showSourceLocationsGraphically(); } void PageView::setShowSourceLocationsGraphically(bool show) { if( show == Okular::Settings::showSourceLocationsGraphically() ) { return; } Okular::Settings::setShowSourceLocationsGraphically( show ); viewport()->update(); } void PageView::setLastSourceLocationViewport( const Okular::DocumentViewport& vp ) { if( vp.rePos.enabled ) { d->lastSourceLocationViewportNormalizedX = normClamp( vp.rePos.normalizedX, 0.5 ); d->lastSourceLocationViewportNormalizedY = normClamp( vp.rePos.normalizedY, 0.0 ); } else { d->lastSourceLocationViewportNormalizedX = 0.5; d->lastSourceLocationViewportNormalizedY = 0.0; } d->lastSourceLocationViewportPageNumber = vp.pageNumber; viewport()->update(); } void PageView::clearLastSourceLocationViewport() { d->lastSourceLocationViewportPageNumber = -1; d->lastSourceLocationViewportNormalizedX = 0.0; d->lastSourceLocationViewportNormalizedY = 0.0; viewport()->update(); } void PageView::notifyViewportChanged( bool smoothMove ) { QMetaObject::invokeMethod(this, "slotRealNotifyViewportChanged", Qt::QueuedConnection, Q_ARG( bool, smoothMove )); } void PageView::slotRealNotifyViewportChanged( bool smoothMove ) { // if we are the one changing viewport, skip this nofity if ( d->blockViewport ) return; // block setViewport outgoing calls d->blockViewport = true; // find PageViewItem matching the viewport description const Okular::DocumentViewport & vp = d->document->viewport(); PageViewItem * item = nullptr; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == vp.pageNumber ) { item = *iIt; break; } if ( !item ) { qCWarning(OkularUiDebug) << "viewport for page" << vp.pageNumber << "has no matching item!"; d->blockViewport = false; return; } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "document viewport changed"; #endif // relayout in "Single Pages" mode or if a relayout is pending d->blockPixmapsRequest = true; if ( !Okular::Settings::viewContinuous() || d->dirtyLayout ) slotRelayoutPages(); // restore viewport center or use default {x-center,v-top} alignment const QRect & r = item->croppedGeometry(); int newCenterX = r.left(), newCenterY = r.top(); if ( vp.rePos.enabled ) { if ( vp.rePos.pos == Okular::DocumentViewport::Center ) { newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.5 ) * (double)r.width() ); newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() ); } else { // TopLeft newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.0 ) * (double)r.width() + viewport()->width() / 2 ); newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() + viewport()->height() / 2 ); } } else { newCenterX += r.width() / 2; newCenterY += viewport()->height() / 2 - 10; } // if smooth movement requested, setup parameters and start it if ( smoothMove ) { d->viewportMoveActive = true; d->viewportMoveTime.start(); d->viewportMoveDest.setX( newCenterX ); d->viewportMoveDest.setY( newCenterY ); if ( !d->viewportMoveTimer ) { d->viewportMoveTimer = new QTimer( this ); connect( d->viewportMoveTimer, &QTimer::timeout, this, &PageView::slotMoveViewport ); } d->viewportMoveTimer->start( 25 ); verticalScrollBar()->setEnabled( false ); horizontalScrollBar()->setEnabled( false ); } else center( newCenterX, newCenterY ); d->blockPixmapsRequest = false; // request visible pixmaps in the current viewport and recompute it slotRequestVisiblePixmaps(); // enable setViewport calls d->blockViewport = false; if( viewport() ) { viewport()->update(); } // since the page has moved below cursor, update it updateCursor(); } void PageView::notifyPageChanged( int pageNumber, int changedFlags ) { // only handle pixmap / highlight changes notifies if ( changedFlags & DocumentObserver::Bookmark ) return; if ( changedFlags & DocumentObserver::Annotations ) { const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations(); const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end(); QSet< AnnotWindow * >::Iterator it = d->m_annowindows.begin(); for ( ; it != d->m_annowindows.end(); ) { QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, (*it)->annotation() ); if ( annIt != annItEnd ) { (*it)->reloadInfo(); ++it; } else { AnnotWindow *w = *it; it = d->m_annowindows.erase( it ); // Need to delete after removing from the list // otherwise deleting will call slotAnnotationWindowDestroyed which will mess // the list and the iterators delete w; } } d->mouseAnnotation->notifyAnnotationChanged( pageNumber ); } if ( changedFlags & DocumentObserver::BoundingBox ) { #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "BoundingBox change on page" << pageNumber; #endif slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! // Repaint the whole widget since layout may have changed viewport()->update(); return; } // iterate over visible items: if page(pageNumber) is one of them, repaint it QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == pageNumber && (*iIt)->isVisible() ) { // update item's rectangle plus the little outline QRect expandedRect = (*iIt)->croppedGeometry(); // a PageViewItem is placed in the global page layout, // while we need to map its position in the viewport coordinates // (to get the correct area to repaint) expandedRect.translate( -contentAreaPosition() ); expandedRect.adjust( -1, -1, 3, 3 ); viewport()->update( expandedRect ); // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor if ( cursor().shape() != Qt::SizeVerCursor ) { // since the page has been regenerated below cursor, update it updateCursor(); } break; } } void PageView::notifyContentsCleared( int changedFlags ) { // if pixmaps were cleared, re-ask them if ( changedFlags & DocumentObserver::Pixmap ) QMetaObject::invokeMethod(this, "slotRequestVisiblePixmaps", Qt::QueuedConnection); } void PageView::notifyZoom( int factor ) { if ( factor > 0 ) updateZoom( ZoomIn ); else updateZoom( ZoomOut ); } bool PageView::canUnloadPixmap( int pageNumber ) const { if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal ) { // if the item is visible, forbid unloading QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); for ( ; vIt != vEnd; ++vIt ) if ( (*vIt)->pageNumber() == pageNumber ) return false; } else { // forbid unloading of the visible items, and of the previous and next QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); for ( ; vIt != vEnd; ++vIt ) if ( abs( (*vIt)->pageNumber() - pageNumber ) <= 1 ) return false; } // if hidden premit unloading return true; } void PageView::notifyCurrentPageChanged( int previous, int current ) { if ( previous != -1 ) { PageViewItem * item = d->items.at( previous ); if ( item ) { Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) videoWidget->pageLeft(); } } if ( current != -1 ) { PageViewItem * item = d->items.at( current ); if ( item ) { Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) videoWidget->pageEntered(); } // update zoom text and factor if in a ZoomFit/* zoom mode if ( d->zoomMode != ZoomFixed ) updateZoomText(); } } //END DocumentObserver inherited methods //BEGIN View inherited methods bool PageView::supportsCapability( ViewCapability capability ) const { switch ( capability ) { case Zoom: case ZoomModality: return true; } return false; } Okular::View::CapabilityFlags PageView::capabilityFlags( ViewCapability capability ) const { switch ( capability ) { case Zoom: case ZoomModality: return CapabilityRead | CapabilityWrite | CapabilitySerializable; } return nullptr; } QVariant PageView::capability( ViewCapability capability ) const { switch ( capability ) { case Zoom: return d->zoomFactor; case ZoomModality: return d->zoomMode; } return QVariant(); } void PageView::setCapability( ViewCapability capability, const QVariant &option ) { switch ( capability ) { case Zoom: { bool ok = true; double factor = option.toDouble( &ok ); if ( ok && factor > 0.0 ) { d->zoomFactor = static_cast< float >( factor ); updateZoom( ZoomRefreshCurrent ); } break; } case ZoomModality: { bool ok = true; int mode = option.toInt( &ok ); if ( ok ) { if ( mode >= 0 && mode < 3 ) updateZoom( (ZoomMode)mode ); } break; } } } //END View inherited methods //BEGIN widget events bool PageView::event( QEvent * event ) { if ( event->type() == QEvent::Gesture ) { return gestureEvent(static_cast( event )); } // do not stop the event return QAbstractScrollArea::event( event ); } bool PageView::gestureEvent( QGestureEvent * event ) { QPinchGesture *pinch = static_cast(event->gesture(Qt::PinchGesture)); if (pinch) { // Viewport zoom level at the moment where the pinch gesture starts. // The viewport zoom level _during_ the gesture will be this value // times the relative zoom reported by QGestureEvent. static qreal vanillaZoom = d->zoomFactor; if (pinch->state() == Qt::GestureStarted) { vanillaZoom = d->zoomFactor; } const QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); // Zoom if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged) { d->zoomFactor = vanillaZoom * pinch->totalScaleFactor(); d->blockPixmapsRequest = true; updateZoom( ZoomRefreshCurrent ); d->blockPixmapsRequest = false; viewport()->repaint(); } // Count the number of 90-degree rotations we did since the start of the pinch gesture. // Otherwise a pinch turned to 90 degrees and held there will rotate the page again and again. static int rotations = 0; if (changeFlags & QPinchGesture::RotationAngleChanged) { // Rotation angle relative to the accumulated page rotations triggered by the current pinch // We actually turn at 80 degrees rather than at 90 degrees. That's less strain on the hands. const qreal relativeAngle = pinch->rotationAngle() - rotations*90; if (relativeAngle > 80) { slotRotateClockwise(); rotations++; } if (relativeAngle < -80) { slotRotateCounterClockwise(); rotations--; } } if (pinch->state() == Qt::GestureFinished) { rotations = 0; } return true; } return false; } void PageView::paintEvent(QPaintEvent *pe) { const QPoint areaPos = contentAreaPosition(); // create the rect into contents from the clipped screen rect QRect viewportRect = viewport()->rect(); viewportRect.translate( areaPos ); QRect contentsRect = pe->rect().translated( areaPos ).intersected( viewportRect ); if ( !contentsRect.isValid() ) return; #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "paintevent" << contentsRect; #endif // create the screen painter. a pixel painted at contentsX,contentsY // appears to the top-left corner of the scrollview. QPainter screenPainter( viewport() ); // translate to simulate the scrolled content widget screenPainter.translate( -areaPos ); // selectionRect is the normalized mouse selection rect QRect selectionRect = d->mouseSelectionRect; if ( !selectionRect.isNull() ) selectionRect = selectionRect.normalized(); // selectionRectInternal without the border QRect selectionRectInternal = selectionRect; selectionRectInternal.adjust( 1, 1, -1, -1 ); // color for blending QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ? d->mouseSelectionColor : Qt::red; // subdivide region into rects const QVector &allRects = pe->region().rects(); uint numRects = allRects.count(); // preprocess rects area to see if it worths or not using subdivision uint summedArea = 0; for ( uint i = 0; i < numRects; i++ ) { const QRect & r = allRects[i]; summedArea += r.width() * r.height(); } // very elementary check: SUMj(Region[j].area) is less than boundingRect.area bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height()); if ( !useSubdivision ) numRects = 1; // iterate over the rects (only one loop if not using subdivision) for ( uint i = 0; i < numRects; i++ ) { if ( useSubdivision ) { // set 'contentsRect' to a part of the sub-divided region contentsRect = allRects[i].translated( areaPos ).intersected( viewportRect ); if ( !contentsRect.isValid() ) continue; } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << contentsRect; #endif // note: this check will take care of all things requiring alpha blending (not only selection) bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect ); // also alpha-blend when there is a table selection... wantCompositing |= !d->tableSelectionParts.isEmpty(); if ( wantCompositing && Okular::Settings::enableCompositing() ) { // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0}) QPixmap doubleBuffer( contentsRect.size() * devicePixelRatioF() ); doubleBuffer.setDevicePixelRatio(devicePixelRatioF()); QPainter pixmapPainter( &doubleBuffer ); pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() ); // 1) Layer 0: paint items and clear bg on unpainted rects drawDocumentOnPainter( contentsRect, &pixmapPainter ); // 2a) Layer 1a: paint (blend) transparent selection (rectangle) if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { QRect blendRect = selectionRectInternal.intersected( contentsRect ); // skip rectangles covered by the selection's border if ( blendRect.isValid() ) { // grab current pixmap into a new one to colorize contents QPixmap blendedPixmap( blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); blendedPixmap.setDevicePixelRatio(devicePixelRatioF()); QPainter p( &blendedPixmap ); p.drawPixmap( 0, 0, doubleBuffer, (blendRect.left() - contentsRect.left()) * devicePixelRatioF(), (blendRect.top() - contentsRect.top()) * devicePixelRatioF(), blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); QColor blCol = selBlendColor.dark( 140 ); blCol.setAlphaF( 0.2 ); p.fillRect( blendedPixmap.rect(), blCol ); p.end(); // copy the blended pixmap back to its place pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); } // draw border (red if the selection is too small) pixmapPainter.setPen( selBlendColor ); pixmapPainter.drawRect( selectionRect.adjusted( 0, 0, -1, -1 ) ); } // 2b) Layer 1b: paint (blend) transparent selection (table) foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && !selectionPartRectInternal.contains( contentsRect ) ) { QRect blendRect = selectionPartRectInternal.intersected( contentsRect ); // skip rectangles covered by the selection's border if ( blendRect.isValid() ) { // grab current pixmap into a new one to colorize contents QPixmap blendedPixmap( blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); blendedPixmap.setDevicePixelRatio(devicePixelRatioF()); QPainter p( &blendedPixmap ); p.drawPixmap( 0, 0, doubleBuffer, (blendRect.left() - contentsRect.left()) * devicePixelRatioF(), (blendRect.top() - contentsRect.top()) * devicePixelRatioF(), blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); QColor blCol = d->mouseSelectionColor.dark( 140 ); blCol.setAlphaF( 0.2 ); p.fillRect( blendedPixmap.rect(), blCol ); p.end(); // copy the blended pixmap back to its place pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); } // draw border (red if the selection is too small) pixmapPainter.setPen( d->mouseSelectionColor ); pixmapPainter.drawRect( selectionPartRect.adjusted( 0, 0, -1, -1 ) ); } } drawTableDividers( &pixmapPainter ); // 3a) Layer 1: give annotator painting control if ( d->annotator && d->annotator->routePaints( contentsRect ) ) d->annotator->routePaint( &pixmapPainter, contentsRect ); // 3b) Layer 1: give mouseAnnotation painting control d->mouseAnnotation->routePaint( &pixmapPainter, contentsRect ); // 4) Layer 2: overlays if ( Okular::Settings::debugDrawBoundaries() ) { pixmapPainter.setPen( Qt::blue ); pixmapPainter.drawRect( contentsRect ); } // finish painting and draw contents pixmapPainter.end(); screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer ); } else { // 1) Layer 0: paint items and clear bg on unpainted rects drawDocumentOnPainter( contentsRect, &screenPainter ); // 2a) Layer 1a: paint opaque selection (rectangle) if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); screenPainter.drawRect( selectionRect ); } // 2b) Layer 1b: paint opaque selection (table) foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && !selectionPartRectInternal.contains( contentsRect ) ) { screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); screenPainter.drawRect( selectionPartRect ); } } drawTableDividers( &screenPainter ); // 3a) Layer 1: give annotator painting control if ( d->annotator && d->annotator->routePaints( contentsRect ) ) d->annotator->routePaint( &screenPainter, contentsRect ); // 3b) Layer 1: give mouseAnnotation painting control d->mouseAnnotation->routePaint( &screenPainter, contentsRect ); // 4) Layer 2: overlays if ( Okular::Settings::debugDrawBoundaries() ) { screenPainter.setPen( Qt::red ); screenPainter.drawRect( contentsRect ); } } } } void PageView::drawTableDividers(QPainter * screenPainter) { if (!d->tableSelectionParts.isEmpty()) { screenPainter->setPen( d->mouseSelectionColor.dark() ); if (d->tableDividersGuessed) { QPen p = screenPainter->pen(); p.setStyle( Qt::DashLine ); screenPainter->setPen( p ); } foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); foreach(double col, d->tableSelectionCols) { if (col >= tsp.rectInSelection.left && col <= tsp.rectInSelection.right) { col = (col - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); const int x = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; screenPainter->drawLine( x, selectionPartRectInternal.top(), x, selectionPartRectInternal.top() + selectionPartRectInternal.height() ); } } foreach(double row, d->tableSelectionRows) { if (row >= tsp.rectInSelection.top && row <= tsp.rectInSelection.bottom) { row = (row - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); const int y = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; screenPainter->drawLine( selectionPartRectInternal.left(), y, selectionPartRectInternal.left() + selectionPartRectInternal.width(), y ); } } } } } void PageView::resizeEvent( QResizeEvent *e ) { if ( d->items.isEmpty() ) { resizeContentArea( e->size() ); return; } if ( ( d->zoomMode == ZoomFitWidth || d->zoomMode == ZoomFitAuto ) && !verticalScrollBar()->isVisible() && qAbs(e->oldSize().height() - e->size().height()) < verticalScrollBar()->width() && d->verticalScrollBarVisible ) { // this saves us from infinite resizing loop because of scrollbars appearing and disappearing // see bug 160628 for more info // TODO looks are still a bit ugly because things are left uncentered // but better a bit ugly than unusable d->verticalScrollBarVisible = false; resizeContentArea( e->size() ); return; } else if ( d->zoomMode == ZoomFitAuto && !horizontalScrollBar()->isVisible() && qAbs(e->oldSize().width() - e->size().width()) < horizontalScrollBar()->height() && d->horizontalScrollBarVisible ) { // this saves us from infinite resizing loop because of scrollbars appearing and disappearing // TODO looks are still a bit ugly because things are left uncentered // but better a bit ugly than unusable d->horizontalScrollBarVisible = false; resizeContentArea( e->size() ); return; } // start a timer that will refresh the pixmap after 0.2s d->delayResizeEventTimer->start( 200 ); d->verticalScrollBarVisible = verticalScrollBar()->isVisible(); d->horizontalScrollBarVisible = horizontalScrollBar()->isVisible(); } void PageView::keyPressEvent( QKeyEvent * e ) { e->accept(); // if performing a selection or dyn zooming, disable keys handling if ( ( d->mouseSelecting && e->key() != Qt::Key_Escape ) || ( QApplication::mouseButtons () & Qt::MidButton ) ) return; // if viewport is moving, disable keys handling if ( d->viewportMoveActive ) return; // move/scroll page by using keys switch ( e->key() ) { case Qt::Key_J: case Qt::Key_K: case Qt::Key_Down: case Qt::Key_PageDown: case Qt::Key_Up: case Qt::Key_PageUp: case Qt::Key_Backspace: if ( e->key() == Qt::Key_Down || e->key() == Qt::Key_PageDown || e->key() == Qt::Key_J ) { bool singleStep = e->key() == Qt::Key_Down || e->key() == Qt::Key_J; slotScrollDown( singleStep ); } else { bool singleStep = e->key() == Qt::Key_Up || e->key() == Qt::Key_K; slotScrollUp( singleStep ); } break; case Qt::Key_Left: case Qt::Key_H: if ( horizontalScrollBar()->maximum() == 0 ) { //if we cannot scroll we go to the previous page vertically int next_page = d->document->currentPage() - viewColumns(); d->document->setViewportPage(next_page); } else horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); break; case Qt::Key_Right: case Qt::Key_L: if ( horizontalScrollBar()->maximum() == 0 ) { //if we cannot scroll we advance the page vertically int next_page = d->document->currentPage() + viewColumns(); d->document->setViewportPage(next_page); } else horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); break; case Qt::Key_Escape: emit escPressed(); selectionClear( d->tableDividersGuessed ? ClearOnlyDividers : ClearAllSelection ); d->mousePressPos = QPoint(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } d->mouseAnnotation->routeKeyPressEvent( e ); break; case Qt::Key_Delete: d->mouseAnnotation->routeKeyPressEvent( e ); break; case Qt::Key_Shift: case Qt::Key_Control: if ( d->autoScrollTimer ) { if ( d->autoScrollTimer->isActive() ) d->autoScrollTimer->stop(); else slotAutoScroll(); return; } // fallthrough default: e->ignore(); return; } // if a known key has been pressed, stop scrolling the page if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::keyReleaseEvent( QKeyEvent * e ) { e->accept(); if ( d->annotator && d->annotator->active() ) { if ( d->annotator->routeKeyEvent( e ) ) return; } if ( e->key() == Qt::Key_Escape && d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::inputMethodEvent( QInputMethodEvent * e ) { Q_UNUSED(e) } void PageView::tabletEvent( QTabletEvent * e ) { // Ignore tablet events that we don't care about if ( !( e->type() == QEvent::TabletPress || e->type() == QEvent::TabletRelease || e->type() == QEvent::TabletMove ) ) { e->ignore(); return; } // Determine pen state bool penReleased = false; if ( e->type() == QEvent::TabletPress ) { d->penDown = true; } if ( e->type() == QEvent::TabletRelease ) { d->penDown = false; penReleased = true; } // If we're editing an annotation and the tablet pen is either down or just released // then dispatch event to annotator if ( d->annotator && d->annotator->active() && ( d->penDown || penReleased ) ) { const QPoint eventPos = contentAreaPoint( e->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const QPoint localOriginInGlobal = mapToGlobal( QPoint(0,0) ); // routeTabletEvent will accept or ignore event as appropriate d->annotator->routeTabletEvent( e, pageItem, localOriginInGlobal ); } else { e->ignore(); } } void PageView::mouseMoveEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; // if holding mouse mid button, perform zoom if ( e->buttons() & Qt::MidButton ) { int mouseY = e->globalPos().y(); int deltaY = d->mouseMidLastY - mouseY; // wrap mouse from top to bottom const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); const int absDeltaY = abs(deltaY); if ( absDeltaY > mouseContainer.height() / 2 ) { deltaY = mouseContainer.height() - absDeltaY; } const float upperZoomLimit = d->document->supportsTiles() ? 15.99 : 3.99; if ( mouseY <= mouseContainer.top() + 4 && d->zoomFactor < upperZoomLimit ) { mouseY = mouseContainer.bottom() - 5; QCursor::setPos( e->globalPos().x(), mouseY ); } // wrap mouse from bottom to top else if ( mouseY >= mouseContainer.bottom() - 4 && d->zoomFactor > 0.101 ) { mouseY = mouseContainer.top() + 5; QCursor::setPos( e->globalPos().x(), mouseY ); } // remember last position d->mouseMidLastY = mouseY; // update zoom level, perform zoom and redraw if ( deltaY ) { d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) ); d->blockPixmapsRequest = true; updateZoom( ZoomRefreshCurrent ); d->blockPixmapsRequest = false; viewport()->repaint(); } return; } const QPoint eventPos = contentAreaPoint( e->pos() ); // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); updateCursor( eventPos ); d->annotator->routeMouseEvent( e, pageItem ); return; } bool leftButton = (e->buttons() == Qt::LeftButton); bool rightButton = (e->buttons() == Qt::RightButton); switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse: { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( leftButton ) { d->leftClickTimer.stop(); if ( pageItem && d->mouseAnnotation->isActive() ) { // if left button pressed and annotation is focused, forward move event d->mouseAnnotation->routeMouseMoveEvent( pageItem, eventPos, leftButton ); } // drag page else if ( !d->mouseGrabPos.isNull() ) { setCursor( Qt::ClosedHandCursor ); QPoint mousePos = e->globalPos(); QPoint delta = d->mouseGrabPos - mousePos; // wrap mouse from top to bottom const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); // If the delta is huge it probably means we just wrapped in that direction const QPoint absDelta(abs(delta.x()), abs(delta.y())); if ( absDelta.y() > mouseContainer.height() / 2 ) { delta.setY(mouseContainer.height() - absDelta.y()); } if ( absDelta.x() > mouseContainer.width() / 2 ) { delta.setX(mouseContainer.width() - absDelta.x()); } if ( mousePos.y() <= mouseContainer.top() + 4 && verticalScrollBar()->value() < verticalScrollBar()->maximum() - 10 ) { mousePos.setY( mouseContainer.bottom() - 5 ); QCursor::setPos( mousePos ); } // wrap mouse from bottom to top else if ( mousePos.y() >= mouseContainer.bottom() - 4 && verticalScrollBar()->value() > 10 ) { mousePos.setY( mouseContainer.top() + 5 ); QCursor::setPos( mousePos ); } // remember last position d->mouseGrabPos = mousePos; // scroll page by position increment scrollTo( horizontalScrollBar()->value() + delta.x(), verticalScrollBar()->value() + delta.y() ); } } else if ( rightButton && !d->mousePressPos.isNull() && d->aMouseSelect ) { // if mouse moves 5 px away from the press point, switch to 'selection' int deltaX = d->mousePressPos.x() - e->globalPos().x(), deltaY = d->mousePressPos.y() - e->globalPos().y(); if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 ) { d->aPrevAction = d->aMouseNormal; d->aMouseSelect->trigger(); QPoint newPos = eventPos + QPoint( deltaX, deltaY ); selectionStart( newPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); updateSelection( eventPos ); break; } } else { /* Forward move events which are still not yet consumed by "mouse grab" or aMouseSelect */ d->mouseAnnotation->routeMouseMoveEvent( pageItem, eventPos, leftButton ); updateCursor(); } } break; case Okular::Settings::EnumMouseMode::Zoom: case Okular::Settings::EnumMouseMode::RectSelect: case Okular::Settings::EnumMouseMode::TableSelect: case Okular::Settings::EnumMouseMode::TrimSelect: // set second corner of selection if ( d->mouseSelecting ) { updateSelection( eventPos ); d->mouseOverLinkObject = nullptr; } updateCursor(); break; case Okular::Settings::EnumMouseMode::Magnifier: if ( e->buttons() ) // if any button is pressed at all { moveMagnifier( e->pos() ); updateMagnifier( eventPos ); } break; case Okular::Settings::EnumMouseMode::TextSelect: // if mouse moves 5 px away from the press point and the document soupports text extraction, do 'textselection' if ( !d->mouseTextSelecting && !d->mousePressPos.isNull() && d->document->supportsSearching() && ( ( eventPos - d->mouseSelectPos ).manhattanLength() > 5 ) ) { d->mouseTextSelecting = true; } updateSelection( eventPos ); updateCursor(); break; } } void PageView::mousePressEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // if performing a selection or dyn zooming, disable mouse press if ( d->mouseSelecting || ( e->button() != Qt::MidButton && ( e->buttons() & Qt::MidButton) ) || d->viewportMoveActive ) return; // if the page is scrolling, stop it if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } // if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode if ( e->button() == Qt::MidButton ) { d->mouseMidLastY = e->globalPos().y(); setCursor( Qt::SizeVerCursor ); return; } const QPoint eventPos = contentAreaPoint( e->pos() ); // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); d->annotator->routeMouseEvent( e, pageItem ); return; } // trigger history navigation for additional mouse buttons if ( e->button() == Qt::XButton1 ) { emit mouseBackButtonClick(); return; } if ( e->button() == Qt::XButton2 ) { emit mouseForwardButtonClick(); return; } // update press / 'start drag' mouse position d->mousePressPos = e->globalPos(); // handle mode dependant mouse press actions bool leftButton = e->button() == Qt::LeftButton, rightButton = e->button() == Qt::RightButton; // Not sure we should erase the selection when clicking with left. if ( d->mouseMode != Okular::Settings::EnumMouseMode::TextSelect ) textSelectionClear(); switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse: // drag start / click / link following { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( leftButton ) { if ( pageItem ) { d->mouseAnnotation->routeMousePressEvent( pageItem, eventPos ); } d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos; if ( !d->mouseOnRect ) d->leftClickTimer.start( QApplication::doubleClickInterval() + 10 ); } else if ( rightButton ) { if ( pageItem ) { // find out normalized mouse coords inside current item const QRect & itemRect = pageItem->uncroppedGeometry(); double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); const QLinkedList< const Okular::ObjectRect *> orects = pageItem->page()->objectRects( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); if ( !orects.isEmpty() ) { AnnotationPopup popup( d->document, AnnotationPopup::MultiAnnotationMode, this ); foreach ( const Okular::ObjectRect * orect, orects ) { Okular::Annotation * ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); if ( ann && (ann->subType() != Okular::Annotation::AWidget) ) popup.addAnnotation( ann, pageItem->pageNumber() ); } connect( &popup, &AnnotationPopup::openAnnotationWindow, this, &PageView::openAnnotationWindow ); popup.exec( e->globalPos() ); // Since ↑ spins its own event loop we won't get the mouse release event // so reset mousePressPos here d->mousePressPos = QPoint(); } } } } break; case Okular::Settings::EnumMouseMode::Zoom: // set first corner of the zoom rect if ( leftButton ) selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ), false ); else if ( rightButton ) updateZoom( ZoomOut ); break; case Okular::Settings::EnumMouseMode::Magnifier: moveMagnifier( e->pos() ); d->magnifierView->show(); updateMagnifier( eventPos ); break; case Okular::Settings::EnumMouseMode::RectSelect: // set first corner of the selection rect case Okular::Settings::EnumMouseMode::TrimSelect: if ( leftButton ) { selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); } break; case Okular::Settings::EnumMouseMode::TableSelect: if ( leftButton ) { if (d->tableSelectionParts.isEmpty()) { selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); } else { QRect updatedRect; foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); // This will update the whole table rather than just the added/removed divider // (which can span more than one part). updatedRect = updatedRect.united(selectionPartRect); if (!selectionPartRect.contains(eventPos)) continue; // At this point it's clear we're either adding or removing a divider manually, so obviously the user is happy with the guess (if any). d->tableDividersGuessed = false; // There's probably a neat trick to finding which edge it's closest to, // but this way has the advantage of simplicity. const int fromLeft = abs(selectionPartRect.left() - eventPos.x()); const int fromRight = abs(selectionPartRect.left() + selectionPartRect.width() - eventPos.x()); const int fromTop = abs(selectionPartRect.top() - eventPos.y()); const int fromBottom = abs(selectionPartRect.top() + selectionPartRect.height() - eventPos.y()); const int colScore = fromToptableSelectionCols.length(); i++) { const double col = (d->tableSelectionCols[i] - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); const int colX = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; if (abs(colX - eventPos.x())<=3) { d->tableSelectionCols.removeAt(i); deleted=true; break; } } if (!deleted) { double col = eventPos.x() - selectionPartRect.left(); col /= selectionPartRect.width(); // at this point, it's normalised within the part col *= (tsp.rectInSelection.right - tsp.rectInSelection.left); col += tsp.rectInSelection.left; // at this point, it's normalised within the whole table d->tableSelectionCols.append(col); qSort(d->tableSelectionCols); } } else { bool deleted=false; for(int i=0; itableSelectionRows.length(); i++) { const double row = (d->tableSelectionRows[i] - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); const int rowY = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; if (abs(rowY - eventPos.y())<=3) { d->tableSelectionRows.removeAt(i); deleted=true; break; } } if (!deleted) { double row = eventPos.y() - selectionPartRect.top(); row /= selectionPartRect.height(); // at this point, it's normalised within the part row *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); row += tsp.rectInSelection.top; // at this point, it's normalised within the whole table d->tableSelectionRows.append(row); qSort(d->tableSelectionRows); } } } updatedRect.translate( -contentAreaPosition() ); viewport()->update( updatedRect ); } } break; case Okular::Settings::EnumMouseMode::TextSelect: d->mouseSelectPos = eventPos; if ( !rightButton ) { textSelectionClear(); } break; } } void PageView::mouseReleaseEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // stop the drag scrolling d->dragScrollTimer.stop(); d->leftClickTimer.stop(); const bool leftButton = e->button() == Qt::LeftButton; const bool rightButton = e->button() == Qt::RightButton; if ( d->mouseAnnotation->isActive() && leftButton ) { // Just finished to move the annotation d->mouseAnnotation->routeMouseReleaseEvent(); } // don't perform any mouse action when no document is shown.. if ( d->items.isEmpty() ) { // ..except for right Clicks (emitted even it viewport is empty) if ( e->button() == Qt::RightButton ) emit rightClick( nullptr, e->globalPos() ); return; } // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; const QPoint eventPos = contentAreaPoint( e->pos() ); // handle mode indepent mid buttom zoom if ( e->button() == Qt::MidButton ) { // request pixmaps since it was disabled during drag slotRequestVisiblePixmaps(); // the cursor may now be over a link.. update it updateCursor( eventPos ); return; } // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); d->annotator->routeMouseEvent( e, pageItem ); return; } switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse:{ // return the cursor to its normal state after dragging if ( cursor().shape() == Qt::ClosedHandCursor ) updateCursor( eventPos ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const QPoint pressPos = contentAreaPoint( mapFromGlobal( d->mousePressPos ) ); const PageViewItem * pageItemPressPos = pickItemOnPoint( pressPos.x(), pressPos.y() ); // if the mouse has not moved since the press, that's a -click- if ( leftButton && pageItem && pageItem == pageItemPressPos && ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) { if ( !mouseReleaseOverLink( d->mouseOverLinkObject ) && ( e->modifiers() == Qt::ShiftModifier ) ) { const double nX = pageItem->absToPageX(eventPos.x()); const double nY = pageItem->absToPageY(eventPos.y()); const Okular::ObjectRect * rect; // TODO: find a better way to activate the source reference "links" // for the moment they are activated with Shift + left click // Search the nearest source reference. rect = pageItem->page()->objectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( !rect ) { static const double s_minDistance = 0.025; // FIXME?: empirical value? double distance = 0.0; rect = pageItem->page()->nearestObjectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight(), &distance ); // distance is distanceSqr, adapt it to a normalized value distance = distance / (pow( pageItem->uncroppedWidth(), 2 ) + pow( pageItem->uncroppedHeight(), 2 )); if ( rect && ( distance > s_minDistance ) ) rect = nullptr; } if ( rect ) { const Okular::SourceReference * ref = static_cast< const Okular::SourceReference * >( rect->object() ); d->document->processSourceReference( ref ); } else { const Okular::SourceReference * ref = d->document->dynamicSourceReference( pageItem-> pageNumber(), nX * pageItem->page()->width(), nY * pageItem->page()->height() ); if ( ref ) { d->document->processSourceReference( ref ); delete ref; } } } #if 0 else { // a link can move us to another page or even to another document, there's no point in trying to // process the click on the image once we have processes the click on the link rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->width(), pageItem->height() ); if ( rect ) { // handle click over a image } /* Enrico and me have decided this is not worth the trouble it generates else { // if not on a rect, the click selects the page // if ( pageItem->pageNumber() != (int)d->document->currentPage() ) d->document->setViewportPage( pageItem->pageNumber(), this ); }*/ } #endif } else if ( rightButton && !d->mouseAnnotation->isModified() ) { if ( pageItem && pageItem == pageItemPressPos && ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) { QMenu * menu = createProcessLinkMenu(pageItem, eventPos ); if ( menu ) { menu->exec( e->globalPos() ); menu->deleteLater(); } else { const double nX = pageItem->absToPageX(eventPos.x()); const double nY = pageItem->absToPageY(eventPos.y()); // a link can move us to another page or even to another document, there's no point in trying to // process the click on the image once we have processes the click on the link const Okular::ObjectRect * rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) { // handle right click over a image } else { // right click (if not within 5 px of the press point, the mode // had been already changed to 'Selection' instead of 'Normal') emit rightClick( pageItem->page(), e->globalPos() ); } } } else { // right click (if not within 5 px of the press point, the mode // had been already changed to 'Selection' instead of 'Normal') emit rightClick( pageItem ? pageItem->page() : nullptr, e->globalPos() ); } } }break; case Okular::Settings::EnumMouseMode::Zoom: // if a selection rect has been defined, zoom into it if ( leftButton && d->mouseSelecting ) { QRect selRect = d->mouseSelectionRect.normalized(); if ( selRect.width() <= 8 && selRect.height() <= 8 ) { selectionClear(); break; } // find out new zoom ratio and normalized view center (relative to the contentsRect) double zoom = qMin( (double)viewport()->width() / (double)selRect.width(), (double)viewport()->height() / (double)selRect.height() ); double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentAreaWidth()); double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentAreaHeight()); const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; if ( d->zoomFactor <= upperZoomLimit || zoom <= 1.0 ) { d->zoomFactor *= zoom; viewport()->setUpdatesEnabled( false ); updateZoom( ZoomRefreshCurrent ); viewport()->setUpdatesEnabled( true ); } // recenter view and update the viewport center( (int)(nX * contentAreaWidth()), (int)(nY * contentAreaHeight()) ); viewport()->update(); // hide message box and delete overlay window selectionClear(); } break; case Okular::Settings::EnumMouseMode::Magnifier: d->magnifierView->hide(); break; case Okular::Settings::EnumMouseMode::TrimSelect: { // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { break; } PageViewItem * pageItem = pickItemOnPoint(eventPos.x(), eventPos.y()); // ensure end point rests within a page, or ignore if (!pageItem) { break; } QRect selectionRect = d->mouseSelectionRect.normalized(); double nLeft = pageItem->absToPageX(selectionRect.left()); double nRight = pageItem->absToPageX(selectionRect.right()); double nTop = pageItem->absToPageY(selectionRect.top()); double nBottom = pageItem->absToPageY(selectionRect.bottom()); if ( nLeft < 0 ) nLeft = 0; if ( nTop < 0 ) nTop = 0; if ( nRight > 1 ) nRight = 1; if ( nBottom > 1 ) nBottom = 1; d->trimBoundingBox = Okular::NormalizedRect(nLeft, nTop, nRight, nBottom); // Trim Selection successfully done, hide prompt d->messageWindow->hide(); // clear widget selection and invalidate rect selectionClear(); // When Trim selection bbox interaction is over, we should switch to another mousemode. if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } else { d->aMouseNormal->trigger(); } // with d->trimBoundingBox defined, redraw for trim to take visual effect if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } break; } case Okular::Settings::EnumMouseMode::RectSelect: { // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); emit rightClick( pageItem ? pageItem->page() : nullptr, e->globalPos() ); break; } // if a selection is defined, display a popup if ( (!leftButton && !d->aPrevAction) || (!rightButton && d->aPrevAction) || !d->mouseSelecting ) break; QRect selectionRect = d->mouseSelectionRect.normalized(); if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 ) { selectionClear(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } break; } // if we support text generation QString selectedText; if (d->document->supportsSearching()) { // grab text in selection by extracting it from all intersected pages const Okular::Page * okularPage=nullptr; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( !item->isVisible() ) continue; const QRect & itemRect = item->croppedGeometry(); if ( selectionRect.intersects( itemRect ) ) { // request the textpage if there isn't one okularPage= item->page(); qCDebug(OkularUiDebug) << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); // grab text in the rect that intersects itemRect QRect relativeRect = selectionRect.intersected( itemRect ); relativeRect.translate( -item->uncroppedGeometry().topLeft() ); Okular::RegularAreaRect rects; rects.append( Okular::NormalizedRect( relativeRect, item->uncroppedWidth(), item->uncroppedHeight() ) ); selectedText += okularPage->text( &rects ); } } } // popup that ask to copy:text and copy/save:image QMenu menu( this ); menu.setObjectName("PopupMenu"); QAction *textToClipboard = nullptr; #ifdef HAVE_SPEECH QAction *speakText = nullptr; #endif QAction *imageToClipboard = nullptr; QAction *imageToFile = nullptr; if ( d->document->supportsSearching() && !selectedText.isEmpty() ) { menu.addAction( new OKMenuTitle( &menu, i18np( "Text (1 character)", "Text (%1 characters)", selectedText.length() ) ) ); textToClipboard = menu.addAction( QIcon::fromTheme(QStringLiteral("edit-copy")), i18n( "Copy to Clipboard" ) ); textToClipboard->setObjectName("CopyTextToClipboard"); bool copyAllowed = d->document->isAllowed( Okular::AllowCopy ); if ( !copyAllowed ) { textToClipboard->setEnabled( false ); textToClipboard->setText( i18n("Copy forbidden by DRM") ); } #ifdef HAVE_SPEECH if ( Okular::Settings::useTTS() ) speakText = menu.addAction( QIcon::fromTheme(QStringLiteral("text-speak")), i18n( "Speak Text" ) ); #endif if ( copyAllowed ) { addWebShortcutsMenu( &menu, selectedText ); } } menu.addAction( new OKMenuTitle( &menu, i18n( "Image (%1 by %2 pixels)", selectionRect.width(), selectionRect.height() ) ) ); imageToClipboard = menu.addAction( QIcon::fromTheme(QStringLiteral("image-x-generic")), i18n( "Copy to Clipboard" ) ); imageToFile = menu.addAction( QIcon::fromTheme(QStringLiteral("document-save")), i18n( "Save to File..." ) ); QAction *choice = menu.exec( e->globalPos() ); // check if the user really selected an action if ( choice ) { // IMAGE operation chosen if ( choice == imageToClipboard || choice == imageToFile ) { // renders page into a pixmap QPixmap copyPix( selectionRect.width(), selectionRect.height() ); QPainter copyPainter( ©Pix ); copyPainter.translate( -selectionRect.left(), -selectionRect.top() ); drawDocumentOnPainter( selectionRect, ©Painter ); copyPainter.end(); if ( choice == imageToClipboard ) { // [2] copy pixmap to clipboard QClipboard *cb = QApplication::clipboard(); cb->setPixmap( copyPix, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setPixmap( copyPix, QClipboard::Selection ); d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard.", copyPix.width(), copyPix.height() ) ); } else if ( choice == imageToFile ) { // [3] save pixmap to file QString fileName = QFileDialog::getSaveFileName(this, i18n("Save file"), QString(), i18n("Images (*.png *.jpeg)")); if ( fileName.isEmpty() ) d->messageWindow->display( i18n( "File not saved." ), QString(), PageViewMessage::Warning ); else { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl( QUrl::fromLocalFile(fileName) ); QString type; if ( !mime.isDefault() ) type = QStringLiteral("PNG"); else type = mime.name().section( QLatin1Char('/'), -1 ).toUpper(); copyPix.save( fileName, qPrintable( type ) ); d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file.", copyPix.width(), copyPix.height(), type ) ); } } } // TEXT operation chosen else { if ( choice == textToClipboard ) { // [1] copy text to clipboard QClipboard *cb = QApplication::clipboard(); cb->setText( selectedText, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setText( selectedText, QClipboard::Selection ); } #ifdef HAVE_SPEECH else if ( choice == speakText ) { // [2] speech selection using TTS d->tts()->say( selectedText ); } #endif } } // clear widget selection and invalidate rect selectionClear(); // restore previous action if came from it using right button if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } }break; case Okular::Settings::EnumMouseMode::TableSelect: { // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); emit rightClick( pageItem ? pageItem->page() : nullptr, e->globalPos() ); break; } QRect selectionRect = d->mouseSelectionRect.normalized(); if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 && d->tableSelectionParts.isEmpty() ) { selectionClear(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } break; } if (d->mouseSelecting) { // break up the selection into page-relative pieces d->tableSelectionParts.clear(); const Okular::Page * okularPage=nullptr; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( !item->isVisible() ) continue; const QRect & itemRect = item->croppedGeometry(); if ( selectionRect.intersects( itemRect ) ) { // request the textpage if there isn't one okularPage= item->page(); qCDebug(OkularUiDebug) << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); // grab text in the rect that intersects itemRect QRect rectInItem = selectionRect.intersected( itemRect ); rectInItem.translate( -item->uncroppedGeometry().topLeft() ); QRect rectInSelection = selectionRect.intersected( itemRect ); rectInSelection.translate( -selectionRect.topLeft() ); d->tableSelectionParts.append( TableSelectionPart( item, Okular::NormalizedRect( rectInItem, item->uncroppedWidth(), item->uncroppedHeight() ), Okular::NormalizedRect( rectInSelection, selectionRect.width(), selectionRect.height() ) ) ); } } QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 ); updatedRect.translate( -contentAreaPosition() ); d->mouseSelecting = false; d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); d->tableSelectionCols.clear(); d->tableSelectionRows.clear(); guessTableDividers(); viewport()->update( updatedRect ); } if ( !d->document->isAllowed( Okular::AllowCopy ) ) { d->messageWindow->display( i18n("Copy forbidden by DRM"), QString(), PageViewMessage::Info, -1 ); break; } QString selText; QString selHtml; QList xs = d->tableSelectionCols; QList ys = d->tableSelectionRows; xs.prepend(0.0); xs.append(1.0); ys.prepend(0.0); ys.append(1.0); selHtml = QString::fromLatin1("" "" ""); for (int r=0; r+1"); for (int c=0; c+1tableSelectionParts) { // first, crop the cell to this part if (!tsp.rectInSelection.intersects(cell)) continue; Okular::NormalizedRect cellPart = tsp.rectInSelection & cell; // intersection // second, convert it from table coordinates to part coordinates cellPart.left -= tsp.rectInSelection.left; cellPart.left /= (tsp.rectInSelection.right - tsp.rectInSelection.left); cellPart.right -= tsp.rectInSelection.left; cellPart.right /= (tsp.rectInSelection.right - tsp.rectInSelection.left); cellPart.top -= tsp.rectInSelection.top; cellPart.top /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); cellPart.bottom -= tsp.rectInSelection.top; cellPart.bottom /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); // third, convert from part coordinates to item coordinates cellPart.left *= (tsp.rectInItem.right - tsp.rectInItem.left); cellPart.left += tsp.rectInItem.left; cellPart.right *= (tsp.rectInItem.right - tsp.rectInItem.left); cellPart.right += tsp.rectInItem.left; cellPart.top *= (tsp.rectInItem.bottom - tsp.rectInItem.top); cellPart.top += tsp.rectInItem.top; cellPart.bottom *= (tsp.rectInItem.bottom - tsp.rectInItem.top); cellPart.bottom += tsp.rectInItem.top; // now get the text Okular::RegularAreaRect rects; rects.append( cellPart ); txt += tsp.item->page()->text( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); } QString html = txt; selText += txt.replace(QLatin1Char('\n'), QLatin1Char(' ')); html.replace(QLatin1Char('&'), QLatin1String("&")).replace(QLatin1Char('<'), QLatin1String("<")).replace(QLatin1Char('>'), QLatin1String(">")); // Remove newlines, do not turn them into
, because // Excel interprets
within cell as new cell... html.replace(QLatin1Char('\n'), QLatin1String(" ")); selHtml += QStringLiteral("
"); } selText += QLatin1Char('\n'); selHtml += QLatin1String("\n"); } selHtml += QLatin1String("
") + html + QStringLiteral("
\n"); QClipboard *cb = QApplication::clipboard(); QMimeData *md = new QMimeData(); md->setText(selText); md->setHtml(selHtml); cb->setMimeData( md, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setMimeData( md, QClipboard::Selection ); }break; case Okular::Settings::EnumMouseMode::TextSelect: // if it is a left release checks if is over a previous link press if ( leftButton && mouseReleaseOverLink ( d->mouseOverLinkObject ) ) { selectionClear(); break; } if ( d->mouseTextSelecting ) { d->mouseTextSelecting = false; // textSelectionClear(); if ( d->document->isAllowed( Okular::AllowCopy ) ) { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); if ( cb->supportsSelection() ) cb->setText( text, QClipboard::Selection ); } } } else if ( !d->mousePressPos.isNull() && rightButton ) { PageViewItem* item = pickItemOnPoint(eventPos.x(),eventPos.y()); const Okular::Page *page; //if there is text selected in the page if (item) { QAction * httpLink = nullptr; QAction * textToClipboard = nullptr; QString url; QMenu * menu = createProcessLinkMenu( item, eventPos ); const bool mouseClickOverLink = (menu != nullptr); #ifdef HAVE_SPEECH QAction *speakText = nullptr; #endif if ( (page = item->page())->textSelection() ) { if ( !menu ) { menu = new QMenu(this); } textToClipboard = menu->addAction( QIcon::fromTheme( QStringLiteral("edit-copy") ), i18n( "Copy Text" ) ); #ifdef HAVE_SPEECH if ( Okular::Settings::useTTS() ) speakText = menu->addAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Text" ) ); #endif if ( !d->document->isAllowed( Okular::AllowCopy ) ) { textToClipboard->setEnabled( false ); textToClipboard->setText( i18n("Copy forbidden by DRM") ); } else { addWebShortcutsMenu( menu, d->selectedText() ); } // if the right-click was over a link add "Follow This link" instead of "Go to" if (!mouseClickOverLink) { url = UrlUtils::getUrl( d->selectedText() ); if ( !url.isEmpty() ) { const QString squeezedText = KStringHandler::rsqueeze( url, 30 ); httpLink = menu->addAction( i18n( "Go to '%1'", squeezedText ) ); httpLink->setObjectName("GoToAction"); } } } if ( menu ) { menu->setObjectName("PopupMenu"); QAction *choice = menu->exec( e->globalPos() ); // check if the user really selected an action if ( choice ) { if ( choice == textToClipboard ) copyTextSelection(); #ifdef HAVE_SPEECH else if ( choice == speakText ) { const QString text = d->selectedText(); d->tts()->say( text ); } #endif else if ( choice == httpLink ) { new KRun( QUrl( url ), this ); } } menu->deleteLater(); } } } break; } // reset mouse press / 'drag start' position d->mousePressPos = QPoint(); } void PageView::guessTableDividers() { QList< QPair > colTicks, rowTicks, colSelectionTicks, rowSelectionTicks; foreach ( const TableSelectionPart& tsp, d->tableSelectionParts ) { // add ticks for the edges of this area... colSelectionTicks.append( qMakePair( tsp.rectInSelection.left, +1 ) ); colSelectionTicks.append( qMakePair( tsp.rectInSelection.right, -1 ) ); rowSelectionTicks.append( qMakePair( tsp.rectInSelection.top, +1 ) ); rowSelectionTicks.append( qMakePair( tsp.rectInSelection.bottom, -1 ) ); // get the words in this part Okular::RegularAreaRect rects; rects.append( tsp.rectInItem ); const Okular::TextEntity::List words = tsp.item->page()->words( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); foreach (Okular::TextEntity *te, words) { if (te->text().isEmpty()) { delete te; continue; } Okular::NormalizedRect wordArea = *te->area(); // convert it from item coordinates to part coordinates wordArea.left -= tsp.rectInItem.left; wordArea.left /= (tsp.rectInItem.right - tsp.rectInItem.left); wordArea.right -= tsp.rectInItem.left; wordArea.right /= (tsp.rectInItem.right - tsp.rectInItem.left); wordArea.top -= tsp.rectInItem.top; wordArea.top /= (tsp.rectInItem.bottom - tsp.rectInItem.top); wordArea.bottom -= tsp.rectInItem.top; wordArea.bottom /= (tsp.rectInItem.bottom - tsp.rectInItem.top); // convert from part coordinates to table coordinates wordArea.left *= (tsp.rectInSelection.right - tsp.rectInSelection.left); wordArea.left += tsp.rectInSelection.left; wordArea.right *= (tsp.rectInSelection.right - tsp.rectInSelection.left); wordArea.right += tsp.rectInSelection.left; wordArea.top *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); wordArea.top += tsp.rectInSelection.top; wordArea.bottom *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); wordArea.bottom += tsp.rectInSelection.top; // add to the ticks arrays... colTicks.append( qMakePair( wordArea.left, +1) ); colTicks.append( qMakePair( wordArea.right, -1) ); rowTicks.append( qMakePair( wordArea.top, +1) ); rowTicks.append( qMakePair( wordArea.bottom, -1) ); delete te; } } int tally = 0; qSort( colSelectionTicks ); qSort( rowSelectionTicks ); for (int i = 0; i < colSelectionTicks.length(); ++i) { tally += colSelectionTicks[i].second; if ( tally == 0 && i + 1 < colSelectionTicks.length() && colSelectionTicks[i+1].first != colSelectionTicks[i].first) { colTicks.append( qMakePair( colSelectionTicks[i].first, +1 ) ); colTicks.append( qMakePair( colSelectionTicks[i+1].first, -1 ) ); } } Q_ASSERT( tally == 0 ); for (int i = 0; i < rowSelectionTicks.length(); ++i) { tally += rowSelectionTicks[i].second; if ( tally == 0 && i + 1 < rowSelectionTicks.length() && rowSelectionTicks[i+1].first != rowSelectionTicks[i].first) { rowTicks.append( qMakePair( rowSelectionTicks[i].first, +1 ) ); rowTicks.append( qMakePair( rowSelectionTicks[i+1].first, -1 ) ); } } Q_ASSERT( tally == 0 ); qSort( colTicks ); qSort( rowTicks ); for (int i = 0; i < colTicks.length(); ++i) { tally += colTicks[i].second; if ( tally == 0 && i + 1 < colTicks.length() && colTicks[i+1].first != colTicks[i].first) { d->tableSelectionCols.append( (colTicks[i].first+colTicks[i+1].first) / 2 ); d->tableDividersGuessed = true; } } Q_ASSERT( tally == 0 ); for (int i = 0; i < rowTicks.length(); ++i) { tally += rowTicks[i].second; if ( tally == 0 && i + 1 < rowTicks.length() && rowTicks[i+1].first != rowTicks[i].first) { d->tableSelectionRows.append( (rowTicks[i].first+rowTicks[i+1].first) / 2 ); d->tableDividersGuessed = true; } } Q_ASSERT( tally == 0 ); } void PageView::mouseDoubleClickEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; if ( e->button() == Qt::LeftButton ) { const QPoint eventPos = contentAreaPoint( e->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( pageItem ) { // find out normalized mouse coords inside current item double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); if ( d->mouseMode == Okular::Settings::EnumMouseMode::TextSelect ) { textSelectionClear(); Okular::RegularAreaRect *wordRect = pageItem->page()->wordAt( Okular::NormalizedPoint( nX, nY ) ); if ( wordRect ) { // TODO words with hyphens across pages d->document->setPageTextSelection( pageItem->pageNumber(), wordRect, palette().color( QPalette::Active, QPalette::Highlight ) ); d->pagesWithTextSelection << pageItem->pageNumber(); if ( d->document->isAllowed( Okular::AllowCopy ) ) { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); if ( cb->supportsSelection() ) cb->setText( text, QClipboard::Selection ); } } return; } } const QRect & itemRect = pageItem->uncroppedGeometry(); Okular::Annotation * ann = nullptr; const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); if ( orect ) ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); if ( ann && ann->subType() != Okular::Annotation::AWidget ) { openAnnotationWindow( ann, pageItem->pageNumber() ); } } } } void PageView::wheelEvent( QWheelEvent *e ) { // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; if ( !d->document->isOpened() ) { QAbstractScrollArea::wheelEvent( e ); return; } int delta = e->delta(), vScroll = verticalScrollBar()->value(); e->accept(); if ( (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) { d->controlWheelAccumulatedDelta += delta; if ( d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep ) { slotZoomOut(); d->controlWheelAccumulatedDelta = 0; } else if ( d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep ) { slotZoomIn(); d->controlWheelAccumulatedDelta = 0; } } else { d->controlWheelAccumulatedDelta = 0; if ( delta <= -QWheelEvent::DefaultDeltasPerStep && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->maximum() ) { // go to next page if ( (int)d->document->currentPage() < d->items.count() - 1 ) { // more optimized than document->setNextPage and then move view to top Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += viewColumns(); if ( newViewport.pageNumber >= (int)d->items.count() ) newViewport.pageNumber = d->items.count() - 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } } else if ( delta >= QWheelEvent::DefaultDeltasPerStep && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->minimum() ) { // go to prev page if ( d->document->currentPage() > 0 ) { // more optimized than document->setPrevPage and then move view to bottom Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= viewColumns(); if ( newViewport.pageNumber < 0 ) newViewport.pageNumber = 0; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } } else QAbstractScrollArea::wheelEvent( e ); } updateCursor(); } bool PageView::viewportEvent( QEvent * e ) { if ( e->type() == QEvent::ToolTip // Show tool tips only for those modes that change the cursor // to a hand when hovering over the link. && ( d->mouseMode == Okular::Settings::EnumMouseMode::Browse || d->mouseMode == Okular::Settings::EnumMouseMode::RectSelect || d->mouseMode == Okular::Settings::EnumMouseMode::TextSelect || d->mouseMode == Okular::Settings::EnumMouseMode::TrimSelect ) ) { QHelpEvent * he = static_cast< QHelpEvent* >( e ); if ( d->mouseAnnotation->isMouseOver() ) { d->mouseAnnotation->routeTooltipEvent( he ); } else { const QPoint eventPos = contentAreaPoint( he->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const Okular::ObjectRect * rect = nullptr; const Okular::Action * link = nullptr; if ( pageItem ) { double nX = pageItem->absToPageX( eventPos.x() ); double nY = pageItem->absToPageY( eventPos.y() ); rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) link = static_cast< const Okular::Action * >( rect->object() ); } if ( link ) { QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); r.translate( pageItem->uncroppedGeometry().topLeft() ); r.translate( -contentAreaPosition() ); QString tip = link->actionTip(); if ( !tip.isEmpty() ) QToolTip::showText( he->globalPos(), tip, viewport(), r ); } } e->accept(); return true; } else // do not stop the event return QAbstractScrollArea::viewportEvent( e ); } void PageView::scrollContentsBy( int dx, int dy ) { const QRect r = viewport()->rect(); viewport()->scroll( dx, dy, r ); // HACK manually repaint the damaged regions, as it seems some updates are missed // thus leaving artifacts around QRegion rgn( r ); rgn -= rgn & r.translated( dx, dy ); foreach ( const QRect &rect, rgn.rects() ) viewport()->repaint( rect ); } //END widget events QList< Okular::RegularAreaRect * > PageView::textSelections( const QPoint& start, const QPoint& end, int& firstpage ) { firstpage = -1; QList< Okular::RegularAreaRect * > ret; QSet< int > affectedItemsSet; QRect selectionRect = QRect( start, end ).normalized(); foreach( PageViewItem * item, d->items ) { if ( item->isVisible() && selectionRect.intersects( item->croppedGeometry() ) ) affectedItemsSet.insert( item->pageNumber() ); } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << ">>>> item selected by mouse:" << affectedItemsSet.count(); #endif if ( !affectedItemsSet.isEmpty() ) { // is the mouse drag line the ne-sw diagonal of the selection rect? bool direction_ne_sw = start == selectionRect.topRight() || start == selectionRect.bottomLeft(); int tmpmin = d->document->pages(); int tmpmax = 0; foreach( int p, affectedItemsSet ) { if ( p < tmpmin ) tmpmin = p; if ( p > tmpmax ) tmpmax = p; } PageViewItem * a = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.right() : selectionRect.left() ), (int)selectionRect.top() ); int min = a && ( a->pageNumber() != tmpmax ) ? a->pageNumber() : tmpmin; PageViewItem * b = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.left() : selectionRect.right() ), (int)selectionRect.bottom() ); int max = b && ( b->pageNumber() != tmpmin ) ? b->pageNumber() : tmpmax; QList< int > affectedItemsIds; for ( int i = min; i <= max; ++i ) affectedItemsIds.append( i ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << ">>>> pages:" << affectedItemsIds; #endif firstpage = affectedItemsIds.first(); if ( affectedItemsIds.count() == 1 ) { PageViewItem * item = d->items[ affectedItemsIds.first() ]; selectionRect.translate( -item->uncroppedGeometry().topLeft() ); ret.append( textSelectionForItem( item, direction_ne_sw ? selectionRect.topRight() : selectionRect.topLeft(), direction_ne_sw ? selectionRect.bottomLeft() : selectionRect.bottomRight() ) ); } else if ( affectedItemsIds.count() > 1 ) { // first item PageViewItem * first = d->items[ affectedItemsIds.first() ]; QRect geom = first->croppedGeometry().intersected( selectionRect ).translated( -first->uncroppedGeometry().topLeft() ); ret.append( textSelectionForItem( first, selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.topRight() : geom.topLeft() ) : ( direction_ne_sw ? geom.bottomRight() : geom.bottomLeft() ), QPoint() ) ); // last item PageViewItem * last = d->items[ affectedItemsIds.last() ]; geom = last->croppedGeometry().intersected( selectionRect ).translated( -last->uncroppedGeometry().topLeft() ); // the last item needs to appended at last... Okular::RegularAreaRect * lastArea = textSelectionForItem( last, QPoint(), selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.bottomLeft() : geom.bottomRight() ) : ( direction_ne_sw ? geom.topLeft() : geom.topRight() ) ); affectedItemsIds.removeFirst(); affectedItemsIds.removeLast(); // item between the two above foreach( int page, affectedItemsIds ) { ret.append( textSelectionForItem( d->items[ page ] ) ); } ret.append( lastArea ); } } return ret; } void PageView::drawDocumentOnPainter( const QRect & contentsRect, QPainter * p ) { QColor backColor; if ( Okular::Settings::useCustomBackgroundColor() ) backColor = Okular::Settings::backgroundColor(); else backColor = viewport()->palette().color( QPalette::Dark ); // when checking if an Item is contained in contentsRect, instead of // growing PageViewItems rects (for keeping outline into account), we // grow the contentsRect QRect checkRect = contentsRect; checkRect.adjust( -3, -3, 1, 1 ); // create a region from which we'll subtract painted rects QRegion remainingArea( contentsRect ); // iterate over all items painting the ones intersecting contentsRect QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { // check if a piece of the page intersects the contents rect if ( !(*iIt)->isVisible() || !(*iIt)->croppedGeometry().intersects( checkRect ) ) continue; // get item and item's outline geometries PageViewItem * item = *iIt; QRect itemGeometry = item->croppedGeometry(), outlineGeometry = itemGeometry; outlineGeometry.adjust( -1, -1, 3, 3 ); // move the painter to the top-left corner of the real page p->save(); p->translate( itemGeometry.left(), itemGeometry.top() ); // draw the page outline (black border and 2px bottom-right shadow) if ( !itemGeometry.contains( contentsRect ) ) { int itemWidth = itemGeometry.width(), itemHeight = itemGeometry.height(); // draw simple outline p->setPen( Qt::black ); p->drawRect( -1, -1, itemWidth + 1, itemHeight + 1 ); // draw bottom/right gradient static const int levels = 2; int r = backColor.red() / (levels + 2) + 6, g = backColor.green() / (levels + 2) + 6, b = backColor.blue() / (levels + 2) + 6; for ( int i = 0; i < levels; i++ ) { p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) ); p->drawLine( i, i + itemHeight + 1, i + itemWidth + 1, i + itemHeight + 1 ); p->drawLine( i + itemWidth + 1, i, i + itemWidth + 1, i + itemHeight ); p->setPen( backColor ); p->drawLine( -1, i + itemHeight + 1, i - 1, i + itemHeight + 1 ); p->drawLine( i + itemWidth + 1, -1, i + itemWidth + 1, i - 1 ); } } // draw the page using the PagePainter with all flags active if ( contentsRect.intersects( itemGeometry ) ) { Okular::NormalizedPoint *viewPortPoint = nullptr; Okular::NormalizedPoint point( d->lastSourceLocationViewportNormalizedX, d->lastSourceLocationViewportNormalizedY ); if( Okular::Settings::showSourceLocationsGraphically() && item->pageNumber() == d->lastSourceLocationViewportPageNumber ) { viewPortPoint = &point; } QRect pixmapRect = contentsRect.intersected( itemGeometry ); pixmapRect.translate( -item->croppedGeometry().topLeft() ); PagePainter::paintCroppedPageOnPainter( p, item->page(), this, pageflags, item->uncroppedWidth(), item->uncroppedHeight(), pixmapRect, item->crop(), viewPortPoint ); } // remove painted area from 'remainingArea' and restore painter remainingArea -= outlineGeometry.intersected( contentsRect ); p->restore(); } // fill with background color the unpainted area const QVector &backRects = remainingArea.rects(); int backRectsNumber = backRects.count(); for ( int jr = 0; jr < backRectsNumber; jr++ ) p->fillRect( backRects[ jr ], backColor ); } void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight ) { const Okular::Page * okularPage = item->page(); double width = okularPage->width(), height = okularPage->height(), zoom = d->zoomFactor; Okular::NormalizedRect crop( 0., 0., 1., 1. ); // Handle cropping, due to either "Trim Margin" or "Trim to Selection" cases if (( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown() && !okularPage->boundingBox().isNull() ) || ( d->aTrimToSelection && d->aTrimToSelection->isChecked() && !d->trimBoundingBox.isNull())) { crop = Okular::Settings::trimMargins() ? okularPage->boundingBox() : d->trimBoundingBox; // Rotate the bounding box for ( int i = okularPage->rotation(); i > 0; --i ) { Okular::NormalizedRect rot = crop; crop.left = 1 - rot.bottom; crop.top = rot.left; crop.right = 1 - rot.top; crop.bottom = rot.right; } // Expand the crop slightly beyond the bounding box (for Trim Margins only) if (Okular::Settings::trimMargins()) { static const double cropExpandRatio = 0.04; const double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2; crop = Okular::NormalizedRect( crop.left - cropExpand, crop.top - cropExpand, crop.right + cropExpand, crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 ); } // We currently generate a larger image and then crop it, so if the // crop rect is very small the generated image is huge. Hence, we shouldn't // let the crop rect become too small. static double minCropRatio; if (Okular::Settings::trimMargins()) { // Make sure we crop by at most 50% in either dimension: minCropRatio = 0.5; } else { // Looser Constraint for "Trim Selection" minCropRatio = 0.20; } if ( ( crop.right - crop.left ) < minCropRatio ) { const double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2; crop.left = qMax( 0.0, qMin( 1.0 - minCropRatio, newLeft ) ); crop.right = crop.left + minCropRatio; } if ( ( crop.bottom - crop.top ) < minCropRatio ) { const double newTop = ( crop.top + crop.bottom ) / 2 - minCropRatio/2; crop.top = qMax( 0.0, qMin( 1.0 - minCropRatio, newTop ) ); crop.bottom = crop.top + minCropRatio; } width *= ( crop.right - crop.left ); height *= ( crop.bottom - crop.top ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "Cropped page" << okularPage->number() << "to" << crop << "width" << width << "height" << height << "by bbox" << okularPage->boundingBox(); #endif } if ( d->zoomMode == ZoomFixed ) { width *= zoom; height *= zoom; item->setWHZC( (int)width, (int)height, d->zoomFactor, crop ); } else if ( d->zoomMode == ZoomFitWidth ) { height = ( height / width ) * colWidth; zoom = (double)colWidth / width; item->setWHZC( colWidth, (int)height, zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } else if ( d->zoomMode == ZoomFitPage ) { const double scaleW = (double)colWidth / (double)width; const double scaleH = (double)rowHeight / (double)height; zoom = qMin( scaleW, scaleH ); item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } else if ( d->zoomMode == ZoomFitAuto ) { const double aspectRatioRelation = 1.25; // relation between aspect ratios for "auto fit" const double uiAspect = (double)rowHeight / (double)colWidth; const double pageAspect = (double)height / (double)width; const double rel = uiAspect / pageAspect; const bool isContinuous = Okular::Settings::viewContinuous(); if ( !isContinuous && rel > aspectRatioRelation ) { // UI space is relatively much higher than the page zoom = (double)rowHeight / (double)height; } else if ( rel < 1.0 / aspectRatioRelation ) { // UI space is relatively much wider than the page in relation zoom = (double)colWidth / (double)width; } else { // aspect ratios of page and UI space are very similar const double scaleW = (double)colWidth / (double)width; const double scaleH = (double)rowHeight / (double)height; zoom = qMin( scaleW, scaleH ); } item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } #ifndef NDEBUG else qCDebug(OkularUiDebug) << "calling updateItemSize with unrecognized d->zoomMode!"; #endif } PageViewItem * PageView::pickItemOnPoint( int x, int y ) { PageViewItem * item = nullptr; QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; const QRect & r = i->croppedGeometry(); if ( x < r.right() && x > r.left() && y < r.bottom() ) { if ( y > r.top() ) item = i; break; } } return item; } void PageView::textSelectionClear() { // something to clear if ( !d->pagesWithTextSelection.isEmpty() ) { QSet< int >::ConstIterator it = d->pagesWithTextSelection.constBegin(), itEnd = d->pagesWithTextSelection.constEnd(); for ( ; it != itEnd; ++it ) d->document->setPageTextSelection( *it, nullptr, QColor() ); d->pagesWithTextSelection.clear(); } } void PageView::selectionStart( const QPoint & pos, const QColor & color, bool /*aboveAll*/ ) { selectionClear(); d->mouseSelecting = true; d->mouseSelectionRect.setRect( pos.x(), pos.y(), 1, 1 ); d->mouseSelectionColor = color; // ensures page doesn't scroll if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::scrollPosIntoView( const QPoint & pos ) { if (pos.x() < horizontalScrollBar()->value()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value()); else if (horizontalScrollBar()->value() + viewport()->width() < pos.x()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value() - viewport()->width()); else d->dragScrollVector.setX(0); if (pos.y() < verticalScrollBar()->value()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value()); else if (verticalScrollBar()->value() + viewport()->height() < pos.y()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value() - viewport()->height()); else d->dragScrollVector.setY(0); if (d->dragScrollVector != QPoint(0, 0)) { if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100); } else d->dragScrollTimer.stop(); } void PageView::updateSelection( const QPoint & pos ) { if ( d->mouseSelecting ) { scrollPosIntoView( pos ); // update the selection rect QRect updateRect = d->mouseSelectionRect; d->mouseSelectionRect.setBottomLeft( pos ); updateRect |= d->mouseSelectionRect; updateRect.translate( -contentAreaPosition() ); viewport()->update( updateRect.adjusted( -1, -2, 2, 1 ) ); } else if ( d->mouseTextSelecting) { scrollPosIntoView( pos ); int first = -1; const QList< Okular::RegularAreaRect * > selections = textSelections( pos, d->mouseSelectPos, first ); QSet< int > pagesWithSelectionSet; for ( int i = 0; i < selections.count(); ++i ) pagesWithSelectionSet.insert( i + first ); const QSet< int > noMoreSelectedPages = d->pagesWithTextSelection - pagesWithSelectionSet; // clear the selection from pages not selected anymore foreach( int p, noMoreSelectedPages ) { d->document->setPageTextSelection( p, nullptr, QColor() ); } // set the new selection for the selected pages foreach( int p, pagesWithSelectionSet ) { d->document->setPageTextSelection( p, selections[ p - first ], palette().color( QPalette::Active, QPalette::Highlight ) ); } d->pagesWithTextSelection = pagesWithSelectionSet; } } static Okular::NormalizedPoint rotateInNormRect( const QPoint &rotated, const QRect &rect, Okular::Rotation rotation ) { Okular::NormalizedPoint ret; switch ( rotation ) { case Okular::Rotation0: ret = Okular::NormalizedPoint( rotated.x(), rotated.y(), rect.width(), rect.height() ); break; case Okular::Rotation90: ret = Okular::NormalizedPoint( rotated.y(), rect.width() - rotated.x(), rect.height(), rect.width() ); break; case Okular::Rotation180: ret = Okular::NormalizedPoint( rect.width() - rotated.x(), rect.height() - rotated.y(), rect.width(), rect.height() ); break; case Okular::Rotation270: ret = Okular::NormalizedPoint( rect.height() - rotated.y(), rotated.x(), rect.height(), rect.width() ); break; } return ret; } Okular::RegularAreaRect * PageView::textSelectionForItem( PageViewItem * item, const QPoint & startPoint, const QPoint & endPoint ) { const QRect & geometry = item->uncroppedGeometry(); Okular::NormalizedPoint startCursor( 0.0, 0.0 ); if ( !startPoint.isNull() ) { startCursor = rotateInNormRect( startPoint, geometry, item->page()->rotation() ); } Okular::NormalizedPoint endCursor( 1.0, 1.0 ); if ( !endPoint.isNull() ) { endCursor = rotateInNormRect( endPoint, geometry, item->page()->rotation() ); } Okular::TextSelection mouseTextSelectionInfo( startCursor, endCursor ); const Okular::Page * okularPage = item->page(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); Okular::RegularAreaRect * selectionArea = okularPage->textArea( &mouseTextSelectionInfo ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug).nospace() << "text areas (" << okularPage->number() << "): " << ( selectionArea ? QString::number( selectionArea->count() ) : "(none)" ); #endif return selectionArea; } void PageView::selectionClear(const ClearMode mode) { QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( -2, -2, 2, 2 ); d->mouseSelecting = false; d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); d->tableSelectionCols.clear(); d->tableSelectionRows.clear(); d->tableDividersGuessed = false; foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); // should check whether this is on-screen here? updatedRect = updatedRect.united(selectionPartRect); } if ( mode != ClearOnlyDividers ) { d->tableSelectionParts.clear(); } d->tableSelectionParts.clear(); updatedRect.translate( -contentAreaPosition() ); viewport()->update( updatedRect ); } // const to be used for both zoomFactorFitMode function and slotRelayoutPages. static const int kcolWidthMargin = 6; static const int krowHeightMargin = 12; double PageView::zoomFactorFitMode( ZoomMode mode ) { const int pageCount = d->items.count(); if ( pageCount == 0 ) return 0; const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); const bool overrideCentering = facingCentered && pageCount < 3; const int nCols = overrideCentering ? 1 : viewColumns(); const double colWidth = viewport()->width() / nCols - kcolWidthMargin; const double rowHeight = viewport()->height() - krowHeightMargin; const PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage()) ]; // prevent segmentation fault when openning a new document; if ( !currentItem ) return 0; const Okular::Page * okularPage = currentItem->page(); const double width = okularPage->width(), height = okularPage->height(); if ( mode == ZoomFitWidth ) return (double) colWidth / width; if ( mode == ZoomFitPage ) { const double scaleW = (double) colWidth / (double)width; const double scaleH = (double) rowHeight / (double)height; return qMin(scaleW, scaleH); } return 0; } void PageView::updateZoom( ZoomMode newZoomMode ) { if ( newZoomMode == ZoomFixed ) { if ( d->aZoom->currentItem() == 0 ) newZoomMode = ZoomFitWidth; else if ( d->aZoom->currentItem() == 1 ) newZoomMode = ZoomFitPage; else if ( d->aZoom->currentItem() == 2 ) newZoomMode = ZoomFitAuto; } float newFactor = d->zoomFactor; QAction * checkedZoomAction = nullptr; switch ( newZoomMode ) { case ZoomFixed:{ //ZoomFixed case QString z = d->aZoom->currentText(); // kdelibs4 sometimes adds accelerators to actions' text directly :( z.remove (QLatin1Char('&')); z.remove (QLatin1Char('%')); newFactor = QLocale().toDouble( z ) / 100.0; }break; case ZoomIn: case ZoomOut:{ const float zoomFactorFitWidth = zoomFactorFitMode(ZoomFitWidth); const float zoomFactorFitPage = zoomFactorFitMode(ZoomFitPage); QVector zoomValue(15); qCopy(kZoomValues, kZoomValues + 13, zoomValue.begin()); zoomValue[13] = zoomFactorFitWidth; zoomValue[14] = zoomFactorFitPage; qSort(zoomValue.begin(), zoomValue.end()); QVector::iterator i; if ( newZoomMode == ZoomOut ) { if (newFactor <= zoomValue.first()) return; i = qLowerBound(zoomValue.begin(), zoomValue.end(), newFactor) - 1; } else { if (newFactor >= zoomValue.last()) return; i = qUpperBound(zoomValue.begin(), zoomValue.end(), newFactor); } const float tmpFactor = *i; if ( tmpFactor == zoomFactorFitWidth ) { newZoomMode = ZoomFitWidth; checkedZoomAction = d->aZoomFitWidth; } else if ( tmpFactor == zoomFactorFitPage ) { newZoomMode = ZoomFitPage; checkedZoomAction = d->aZoomFitPage; } else { newFactor = tmpFactor; newZoomMode = ZoomFixed; } } break; case ZoomFitWidth: checkedZoomAction = d->aZoomFitWidth; break; case ZoomFitPage: checkedZoomAction = d->aZoomFitPage; break; case ZoomFitAuto: checkedZoomAction = d->aZoomAutoFit; break; case ZoomRefreshCurrent: newZoomMode = ZoomFixed; d->zoomFactor = -1; break; } const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; if ( newFactor > upperZoomLimit ) newFactor = upperZoomLimit; if ( newFactor < 0.1 ) newFactor = 0.1; if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) ) { // rebuild layout and update the whole viewport d->zoomMode = newZoomMode; d->zoomFactor = newFactor; // be sure to block updates to document's viewport bool prevState = d->blockViewport; d->blockViewport = true; slotRelayoutPages(); d->blockViewport = prevState; // request pixmaps slotRequestVisiblePixmaps(); // update zoom text updateZoomText(); // update actions checked state if ( d->aZoomFitWidth ) { d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth ); d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage ); d->aZoomAutoFit->setChecked( checkedZoomAction == d->aZoomAutoFit ); } } else if ( newZoomMode == ZoomFixed && newFactor == d->zoomFactor ) updateZoomText(); d->aZoomIn->setEnabled( d->zoomFactor < upperZoomLimit-0.001 ); d->aZoomOut->setEnabled( d->zoomFactor > 0.101 ); } void PageView::updateZoomText() { // use current page zoom as zoomFactor if in ZoomFit/* mode if ( d->zoomMode != ZoomFixed && d->items.count() > 0 ) d->zoomFactor = d->items[ qMax( 0, (int)d->document->currentPage() ) ]->zoomFactor(); float newFactor = d->zoomFactor; d->aZoom->removeAllActions(); // add items that describe fit actions QStringList translated; translated << i18n("Fit Width") << i18n("Fit Page") << i18n("Auto Fit"); // add percent items int idx = 0, selIdx = 3; bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio int zoomValueCount = 11; if ( d->document->supportsTiles() ) zoomValueCount = 13; while ( idx < zoomValueCount || !inserted ) { float value = idx < zoomValueCount ? kZoomValues[ idx ] : newFactor; if ( !inserted && newFactor < (value - 0.0001) ) value = newFactor; else idx ++; if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) ) inserted = true; if ( !inserted ) selIdx++; // we do not need to display 2-digit precision QString localValue( QLocale().toString( value * 100.0, 'f', 1 ) ); localValue.remove( QLocale().decimalPoint() + QLatin1Char('0') ); // remove a trailing zero in numbers like 66.70 if ( localValue.right( 1 ) == QLatin1String( "0" ) && localValue.indexOf( QLocale().decimalPoint() ) > -1 ) localValue.chop( 1 ); translated << QStringLiteral( "%1%" ).arg( localValue ); } d->aZoom->setItems( translated ); // select current item in list if ( d->zoomMode == ZoomFitWidth ) selIdx = 0; else if ( d->zoomMode == ZoomFitPage ) selIdx = 1; else if ( d->zoomMode == ZoomFitAuto ) selIdx = 2; // we have to temporarily enable the actions as otherwise we can't set a new current item d->aZoom->setEnabled( true ); d->aZoom->selectableActionGroup()->setEnabled( true ); d->aZoom->setCurrentItem( selIdx ); d->aZoom->setEnabled( d->items.size() > 0 ); d->aZoom->selectableActionGroup()->setEnabled( d->items.size() > 0 ); } void PageView::updateCursor() { const QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); updateCursor( p ); } void PageView::updateCursor( const QPoint &p ) { // reset mouse over link it will be re-set if that still valid d->mouseOverLinkObject = nullptr; // detect the underlaying page (if present) PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() ); if ( d->annotator && d->annotator->active() ) { if ( pageItem || d->annotator->annotating() ) setCursor( d->annotator->cursor() ); else setCursor( Qt::ForbiddenCursor ); } else if ( pageItem ) { double nX = pageItem->absToPageX(p.x()); double nY = pageItem->absToPageY(p.y()); Qt::CursorShape cursorShapeFallback; // if over a ObjectRect (of type Link) change cursor to hand switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::TextSelect: if (d->mouseTextSelecting) { setCursor( Qt::IBeamCursor ); return; } cursorShapeFallback = Qt::IBeamCursor; break; case Okular::Settings::EnumMouseMode::Magnifier: setCursor( Qt::CrossCursor ); return; case Okular::Settings::EnumMouseMode::RectSelect: case Okular::Settings::EnumMouseMode::TrimSelect: if (d->mouseSelecting) { setCursor( Qt::CrossCursor ); return; } cursorShapeFallback = Qt::CrossCursor; break; case Okular::Settings::EnumMouseMode::Browse: d->mouseOnRect = false; if ( d->mouseAnnotation->isMouseOver() ) { d->mouseOnRect = true; setCursor( d->mouseAnnotation->cursor() ); return; } else { cursorShapeFallback = Qt::OpenHandCursor; } break; default: setCursor( Qt::ArrowCursor ); return; } const Okular::ObjectRect * linkobj = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( linkobj ) { d->mouseOverLinkObject = linkobj; d->mouseOnRect = true; setCursor( Qt::PointingHandCursor ); } else { setCursor(cursorShapeFallback); } } else { // if there's no page over the cursor and we were showing the pointingHandCursor // go back to the normal one d->mouseOnRect = false; setCursor( Qt::ArrowCursor ); } } void PageView::reloadForms() { QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); if( d->m_formsVisible ) { for ( ; iIt != iEnd; ++iIt ) { (*iIt)->reloadFormWidgetsState(); } } } void PageView::moveMagnifier( const QPoint& p ) // non scaled point { const int w = d->magnifierView->width() * 0.5; const int h = d->magnifierView->height() * 0.5; int x = p.x() - w; int y = p.y() - h; const int max_x = viewport()->width(); const int max_y = viewport()->height(); QPoint scroll(0,0); if (x < 0) { if (horizontalScrollBar()->value() > 0) scroll.setX(x - w); x = 0; } if (y < 0) { if (verticalScrollBar()->value() > 0) scroll.setY(y - h); y = 0; } if (p.x() + w > max_x) { if (horizontalScrollBar()->value() < horizontalScrollBar()->maximum()) scroll.setX(p.x() + 2 * w - max_x); x = max_x - d->magnifierView->width() - 1; } if (p.y() + h > max_y) { if (verticalScrollBar()->value() < verticalScrollBar()->maximum()) scroll.setY(p.y() + 2 * h - max_y); y = max_y - d->magnifierView->height() - 1; } if (!scroll.isNull()) scrollPosIntoView(contentAreaPoint(p + scroll)); d->magnifierView->move(x, y); } void PageView::updateMagnifier( const QPoint& p ) // scaled point { /* translate mouse coordinates to page coordinates and inform the magnifier of the situation */ PageViewItem *item = pickItemOnPoint(p.x(), p.y()); if (item) { Okular::NormalizedPoint np(item->absToPageX(p.x()), item->absToPageY(p.y())); d->magnifierView->updateView( np, item->page() ); } } int PageView::viewColumns() const { int vm = Okular::Settings::viewMode(); if (vm == Okular::Settings::EnumViewMode::Single) return 1; else if (vm == Okular::Settings::EnumViewMode::Facing || vm == Okular::Settings::EnumViewMode::FacingFirstCentered) return 2; else if (vm == Okular::Settings::EnumViewMode::Summary && d->document->pages() < Okular::Settings::viewColumns() ) return d->document->pages(); else return Okular::Settings::viewColumns(); } void PageView::center(int cx, int cy) { scrollTo( cx - viewport()->width() / 2, cy - viewport()->height() / 2 ); } void PageView::scrollTo( int x, int y ) { bool prevState = d->blockPixmapsRequest; int newValue = -1; if ( x != horizontalScrollBar()->value() || y != verticalScrollBar()->value() ) newValue = 1; // Pretend this call is the result of a scrollbar event d->blockPixmapsRequest = true; horizontalScrollBar()->setValue( x ); verticalScrollBar()->setValue( y ); d->blockPixmapsRequest = prevState; slotRequestVisiblePixmaps( newValue ); } void PageView::toggleFormWidgets( bool on ) { bool somehadfocus = false; QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) { bool hadfocus = (*dIt)->setFormWidgetsVisible( on ); somehadfocus = somehadfocus || hadfocus; } if ( somehadfocus ) setFocus(); d->m_formsVisible = on; if ( d->aToggleForms ) // it may not exist if we are on dummy mode { if ( d->m_formsVisible ) { d->aToggleForms->setText( i18n( "Hide Forms" ) ); } else { d->aToggleForms->setText( i18n( "Show Forms" ) ); } } } void PageView::resizeContentArea( const QSize & newSize ) { const QSize vs = viewport()->size(); int hRange = newSize.width() - vs.width(); int vRange = newSize.height() - vs.height(); if ( horizontalScrollBar()->isVisible() && hRange == verticalScrollBar()->width() && verticalScrollBar()->isVisible() && vRange == horizontalScrollBar()->height() && Okular::Settings::showScrollBars() ) { hRange = 0; vRange = 0; } horizontalScrollBar()->setRange( 0, hRange ); verticalScrollBar()->setRange( 0, vRange ); updatePageStep(); } void PageView::updatePageStep() { const QSize vs = viewport()->size(); horizontalScrollBar()->setPageStep( vs.width() ); verticalScrollBar()->setPageStep( vs.height() * (100 - Okular::Settings::scrollOverlap()) / 100 ); } void PageView::addWebShortcutsMenu( QMenu * menu, const QString & text ) { if ( text.isEmpty() ) { return; } QString searchText = text; searchText = searchText.replace( QLatin1Char('\n'), QLatin1Char(' ') ).replace(QLatin1Char( '\r'), QLatin1Char(' ') ).simplified(); if ( searchText.isEmpty() ) { return; } KUriFilterData filterData( searchText ); filterData.setSearchFilteringOptions( KUriFilterData::RetrievePreferredSearchProvidersOnly ); if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::NormalTextFilter ) ) { const QStringList searchProviders = filterData.preferredSearchProviders(); if ( !searchProviders.isEmpty() ) { QMenu *webShortcutsMenu = new QMenu( menu ); webShortcutsMenu->setIcon( QIcon::fromTheme( QStringLiteral("preferences-web-browser-shortcuts") ) ); const QString squeezedText = KStringHandler::rsqueeze( searchText, 21 ); webShortcutsMenu->setTitle( i18n( "Search for '%1' with", squeezedText ) ); QAction *action = nullptr; foreach( const QString &searchProvider, searchProviders ) { action = new QAction( searchProvider, webShortcutsMenu ); action->setIcon( QIcon::fromTheme( filterData.iconNameForPreferredSearchProvider( searchProvider ) ) ); action->setData( filterData.queryForPreferredSearchProvider( searchProvider ) ); connect( action, &QAction::triggered, this, &PageView::slotHandleWebShortcutAction ); webShortcutsMenu->addAction( action ); } webShortcutsMenu->addSeparator(); action = new QAction( i18n( "Configure Web Shortcuts..." ), webShortcutsMenu ); action->setIcon( QIcon::fromTheme( QStringLiteral("configure") ) ); connect( action, &QAction::triggered, this, &PageView::slotConfigureWebShortcuts ); webShortcutsMenu->addAction( action ); menu->addMenu(webShortcutsMenu); } } } QMenu* PageView::createProcessLinkMenu(PageViewItem *item, const QPoint &eventPos) { // check if the right-click was over a link const double nX = item->absToPageX(eventPos.x()); const double nY = item->absToPageY(eventPos.y()); const Okular::ObjectRect * rect = item->page()->objectRect( Okular::ObjectRect::Action, nX, nY, item->uncroppedWidth(), item->uncroppedHeight() ); if ( rect ) { QMenu *menu = new QMenu(this); const Okular::Action * link = static_cast< const Okular::Action * >( rect->object() ); // creating the menu and its actions QAction * processLink = menu->addAction( i18n( "Follow This Link" ) ); processLink->setObjectName("ProcessLinkAction"); if ( link->actionType() == Okular::Action::Sound ) { processLink->setText( i18n( "Play this Sound" ) ); if ( Okular::AudioPlayer::instance()->state() == Okular::AudioPlayer::PlayingState ) { QAction * actStopSound = menu->addAction( i18n( "Stop Sound" ) ); connect( actStopSound, &QAction::triggered, []() { Okular::AudioPlayer::instance()->stopPlaybacks(); }); } } if ( dynamic_cast< const Okular::BrowseAction * >( link ) ) { QAction * actCopyLinkLocation = menu->addAction( QIcon::fromTheme( QStringLiteral("edit-copy") ), i18n( "Copy Link Address" ) ); actCopyLinkLocation->setObjectName("CopyLinkLocationAction"); connect( actCopyLinkLocation, &QAction::triggered, [ link ]() { const Okular::BrowseAction * browseLink = static_cast< const Okular::BrowseAction * >( link ); QClipboard *cb = QApplication::clipboard(); cb->setText( browseLink->url().toDisplayString(), QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setText( browseLink->url().toDisplayString(), QClipboard::Selection ); } ); } connect( processLink, &QAction::triggered, [this, link]() { d->document->processAction( link ); }); return menu; } return nullptr; } //BEGIN private SLOTS void PageView::slotRelayoutPages() // called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom { // set an empty container if we have no pages const int pageCount = d->items.count(); if ( pageCount < 1 ) { return; } // if viewport was auto-moving, stop it if ( d->viewportMoveActive ) { center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); d->viewportMoveActive = false; d->viewportMoveTimer->stop(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); } // common iterator used in this method and viewport parameters QVector< PageViewItem * >::const_iterator iIt, iEnd = d->items.constEnd(); int viewportWidth = viewport()->width(), viewportHeight = viewport()->height(), fullWidth = 0, fullHeight = 0; QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewportWidth, viewportHeight ); // handle the 'center first page in row' stuff const bool facing = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount > 1; const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); const bool overrideCentering = facingCentered && pageCount < 3; const bool centerFirstPage = facingCentered && !overrideCentering; const bool facingPages = facing || centerFirstPage; const bool centerLastPage = centerFirstPage && pageCount % 2 == 0; const bool continuousView = Okular::Settings::viewContinuous(); const int nCols = overrideCentering ? 1 : viewColumns(); const bool singlePageViewMode = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Single; if ( d->aFitWindowToPage ) d->aFitWindowToPage->setEnabled( !continuousView && singlePageViewMode ); // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage() ) ]; // Here we find out column's width and row's height to compute a table // so we can place widgets 'centered in virtual cells'. const int nRows = (int)ceil( (float)(centerFirstPage ? (pageCount + nCols - 1) : pageCount) / (float)nCols ); int * colWidth = new int[ nCols ], * rowHeight = new int[ nRows ], cIdx = 0, rIdx = 0; for ( int i = 0; i < nCols; i++ ) colWidth[ i ] = viewportWidth / nCols; for ( int i = 0; i < nRows; i++ ) rowHeight[ i ] = 0; // handle the 'centering on first row' stuff if ( centerFirstPage ) cIdx += nCols - 1; // 1) find the maximum columns width and rows height for a grid in // which each page must well-fit inside a cell for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; // update internal page size (leaving a little margin in case of Fit* modes) updateItemSize( item, colWidth[ cIdx ] - kcolWidthMargin, viewportHeight - krowHeightMargin ); // find row's maximum height and column's max width if ( item->croppedWidth() + kcolWidthMargin > colWidth[ cIdx ] ) colWidth[ cIdx ] = item->croppedWidth() + kcolWidthMargin; if ( item->croppedHeight() + krowHeightMargin > rowHeight[ rIdx ] ) rowHeight[ rIdx ] = item->croppedHeight() + krowHeightMargin; // handle the 'centering on first row' stuff // update col/row indices if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; } } const int pageRowIdx = ( ( centerFirstPage ? nCols - 1 : 0 ) + currentItem->pageNumber() ) / nCols; // 2) compute full size for ( int i = 0; i < nCols; i++ ) fullWidth += colWidth[ i ]; if ( continuousView ) { for ( int i = 0; i < nRows; i++ ) fullHeight += rowHeight[ i ]; } else fullHeight = rowHeight[ pageRowIdx ]; // 3) arrange widgets inside cells (and refine fullHeight if needed) int insertX = 0, insertY = fullHeight < viewportHeight ? ( viewportHeight - fullHeight ) / 2 : 0; const int origInsertY = insertY; cIdx = 0; rIdx = 0; if ( centerFirstPage ) { cIdx += nCols - 1; for ( int i = 0; i < cIdx; ++i ) insertX += colWidth[ i ]; } for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; int cWidth = colWidth[ cIdx ], rHeight = rowHeight[ rIdx ]; if ( continuousView || rIdx == pageRowIdx ) { const bool reallyDoCenterFirst = item->pageNumber() == 0 && centerFirstPage; const bool reallyDoCenterLast = item->pageNumber() == pageCount - 1 && centerLastPage; int actualX = 0; if ( reallyDoCenterFirst || reallyDoCenterLast ) { // page is centered across entire viewport actualX = (fullWidth - item->croppedWidth()) / 2; } else if ( facingPages ) { if (Okular::Settings::rtlReadingDirection()){ // RTL reading mode actualX = ( (centerFirstPage && item->pageNumber() % 2 == 0) || (!centerFirstPage && item->pageNumber() % 2 == 1) ) ? (fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1; } else { // page edges 'touch' the center of the viewport actualX = ( (centerFirstPage && item->pageNumber() % 2 == 1) || (!centerFirstPage && item->pageNumber() % 2 == 0) ) ? (fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1; } } else { // page is centered within its virtual column //actualX = insertX + (cWidth - item->croppedWidth()) / 2; if (Okular::Settings::rtlReadingDirection()){ actualX = fullWidth - insertX - cWidth +( (cWidth - item->croppedWidth()) / 2); } else { actualX = insertX + (cWidth - item->croppedWidth()) / 2; } } item->moveTo( actualX, (continuousView ? insertY : origInsertY) + (rHeight - item->croppedHeight()) / 2 ); item->setVisible( true ); } else { item->moveTo( 0, 0 ); item->setVisible( false ); } item->setFormWidgetsVisible( d->m_formsVisible ); // advance col/row index insertX += cWidth; if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; insertX = 0; insertY += rHeight; } #ifdef PAGEVIEW_DEBUG kWarning() << "updating size for pageno" << item->pageNumber() << "cropped" << item->croppedGeometry() << "uncropped" << item->uncroppedGeometry(); #endif } delete [] colWidth; delete [] rowHeight; // 3) reset dirty state d->dirtyLayout = false; // 4) update scrollview's contents size and recenter view bool wasUpdatesEnabled = viewport()->updatesEnabled(); if ( fullWidth != contentAreaWidth() || fullHeight != contentAreaHeight() ) { const Okular::DocumentViewport vp = d->document->viewport(); // disable updates and resize the viewportContents if ( wasUpdatesEnabled ) viewport()->setUpdatesEnabled( false ); resizeContentArea( QSize( fullWidth, fullHeight ) ); // restore previous viewport if defined and updates enabled if ( wasUpdatesEnabled ) { if ( vp.pageNumber >= 0 ) { int prevX = horizontalScrollBar()->value(), prevY = verticalScrollBar()->value(); const QRect & geometry = d->items[ vp.pageNumber ]->croppedGeometry(); double nX = vp.rePos.enabled ? normClamp( vp.rePos.normalizedX, 0.5 ) : 0.5, nY = vp.rePos.enabled ? normClamp( vp.rePos.normalizedY, 0.0 ) : 0.0; center( geometry.left() + qRound( nX * (double)geometry.width() ), geometry.top() + qRound( nY * (double)geometry.height() ) ); // center() usually moves the viewport, that requests pixmaps too. // if that doesn't happen we have to request them by hand if ( prevX == horizontalScrollBar()->value() && prevY == verticalScrollBar()->value() ) slotRequestVisiblePixmaps(); } // or else go to center page else center( fullWidth / 2, 0 ); viewport()->setUpdatesEnabled( true ); } } // 5) update the whole viewport if updated enabled if ( wasUpdatesEnabled ) viewport()->update(); } void PageView::delayedResizeEvent() { // If we already got here we don't need to execute the timer slot again d->delayResizeEventTimer->stop(); slotRelayoutPages(); slotRequestVisiblePixmaps(); } static void slotRequestPreloadPixmap( Okular::DocumentObserver * observer, const PageViewItem * i, const QRect &expandedViewportRect, QLinkedList< Okular::PixmapRequest * > *requestedPixmaps ) { Okular::NormalizedRect preRenderRegion; const QRect intersectionRect = expandedViewportRect.intersected( i->croppedGeometry() ); if ( !intersectionRect.isEmpty() ) preRenderRegion = Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ); // request the pixmap if not already present if ( !i->page()->hasPixmap( observer, i->uncroppedWidth(), i->uncroppedHeight(), preRenderRegion ) && i->uncroppedWidth() > 0 ) { Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload; requestFeatures |= Okular::PixmapRequest::Asynchronous; const bool pageHasTilesManager = i->page()->hasTilesManager( observer ); if ( pageHasTilesManager && !preRenderRegion.isNull() ) { Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); requestedPixmaps->push_back( p ); p->setNormalizedRect( preRenderRegion ); p->setTile( true ); } else if ( !pageHasTilesManager ) { Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); requestedPixmaps->push_back( p ); p->setNormalizedRect( preRenderRegion ); } } } void PageView::slotRequestVisiblePixmaps( int newValue ) { // if requests are blocked (because raised by an unwanted event), exit if ( d->blockPixmapsRequest || d->viewportMoveActive ) return; // precalc view limits for intersecting with page coords inside the loop const bool isEvent = newValue != -1 && !d->blockViewport; const QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewport()->width(), viewport()->height() ); const QRect viewportRectAtZeroZero( 0, 0, viewport()->width(), viewport()->height() ); // some variables used to determine the viewport int nearPageNumber = -1; const double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0; const double viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0; double focusedX = 0.5, focusedY = 0.0, minDistance = -1.0; // Margin (in pixels) around the viewport to preload const int pixelsToExpand = 512; // iterate over all items d->visibleItems.clear(); QLinkedList< Okular::PixmapRequest * > requestedPixmaps; QVector< Okular::VisiblePageRect * > visibleRects; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; foreach( FormWidgetIface *fwi, i->formWidgets() ) { Okular::NormalizedRect r = fwi->rect(); fwi->moveTo( qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); } Q_FOREACH ( VideoWidget *vw, i->videoWidgets() ) { const Okular::NormalizedRect r = vw->normGeometry(); vw->move( qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); if ( vw->isPlaying() && viewportRectAtZeroZero.intersected( vw->geometry() ).isEmpty() ) { vw->stop(); vw->pageLeft(); } } if ( !i->isVisible() ) continue; #ifdef PAGEVIEW_DEBUG kWarning() << "checking page" << i->pageNumber(); kWarning().nospace() << "viewportRect is " << viewportRect << ", page item is " << i->croppedGeometry() << " intersect : " << viewportRect.intersects( i->croppedGeometry() ); #endif // if the item doesn't intersect the viewport, skip it QRect intersectionRect = viewportRect.intersected( i->croppedGeometry() ); if ( intersectionRect.isEmpty() ) { continue; } // add the item to the 'visible list' d->visibleItems.push_back( i ); Okular::VisiblePageRect * vItem = new Okular::VisiblePageRect( i->pageNumber(), Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ) ); visibleRects.push_back( vItem ); #ifdef PAGEVIEW_DEBUG kWarning() << "checking for pixmap for page" << i->pageNumber() << "=" << i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight() ); kWarning() << "checking for text for page" << i->pageNumber() << "=" << i->page()->hasTextPage(); #endif Okular::NormalizedRect expandedVisibleRect = vItem->rect; if ( i->page()->hasTilesManager( this ) && Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low ) { double rectMargin = pixelsToExpand/(double)i->uncroppedHeight(); expandedVisibleRect.left = qMax( 0.0, vItem->rect.left - rectMargin ); expandedVisibleRect.top = qMax( 0.0, vItem->rect.top - rectMargin ); expandedVisibleRect.right = qMin( 1.0, vItem->rect.right + rectMargin ); expandedVisibleRect.bottom = qMin( 1.0, vItem->rect.bottom + rectMargin ); } // if the item has not the right pixmap, add a request for it if ( !i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight(), expandedVisibleRect ) ) { #ifdef PAGEVIEW_DEBUG kWarning() << "rerequesting visible pixmaps for page" << i->pageNumber() << "!"; #endif Okular::PixmapRequest * p = new Okular::PixmapRequest( this, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRIO, Okular::PixmapRequest::Asynchronous ); requestedPixmaps.push_back( p ); if ( i->page()->hasTilesManager( this ) ) { p->setNormalizedRect( expandedVisibleRect ); p->setTile( true ); } else p->setNormalizedRect( vItem->rect ); } // look for the item closest to viewport center and the relative // position between the item and the viewport center if ( isEvent ) { const QRect & geometry = i->croppedGeometry(); // compute distance between item center and viewport center (slightly moved left) double distance = hypot( (geometry.left() + geometry.right()) / 2 - (viewportCenterX - 4), (geometry.top() + geometry.bottom()) / 2 - viewportCenterY ); if ( distance >= minDistance && nearPageNumber != -1 ) continue; nearPageNumber = i->pageNumber(); minDistance = distance; if ( geometry.height() > 0 && geometry.width() > 0 ) { focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width(); focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height(); } } } // if preloading is enabled, add the pages before and after in preloading if ( !d->visibleItems.isEmpty() && Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low ) { // as the requests are done in the order as they appear in the list, // request first the next page and then the previous int pagesToPreload = viewColumns(); // if the greedy option is set, preload all pages if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) pagesToPreload = d->items.count(); const QRect expandedViewportRect = viewportRect.adjusted( 0, -pixelsToExpand, 0, pixelsToExpand ); for( int j = 1; j <= pagesToPreload; j++ ) { // add the page after the 'visible series' in preload const int tailRequest = d->visibleItems.last()->pageNumber() + j; if ( tailRequest < (int)d->items.count() ) { slotRequestPreloadPixmap( this, d->items[ tailRequest ], expandedViewportRect, &requestedPixmaps ); } // add the page before the 'visible series' in preload const int headRequest = d->visibleItems.first()->pageNumber() - j; if ( headRequest >= 0 ) { slotRequestPreloadPixmap( this, d->items[ headRequest ], expandedViewportRect, &requestedPixmaps ); } // stop if we've already reached both ends of the document if ( headRequest < 0 && tailRequest >= (int)d->items.count() ) break; } } // send requests to the document if ( !requestedPixmaps.isEmpty() ) { d->document->requestPixmaps( requestedPixmaps ); } // if this functions was invoked by viewport events, send update to document if ( isEvent && nearPageNumber != -1 ) { // determine the document viewport Okular::DocumentViewport newViewport( nearPageNumber ); newViewport.rePos.enabled = true; newViewport.rePos.normalizedX = focusedX; newViewport.rePos.normalizedY = focusedY; // set the viewport to other observers d->document->setViewport( newViewport , this ); } d->document->setVisiblePageRects( visibleRects, this ); } void PageView::slotMoveViewport() { // converge to viewportMoveDest in 1 second int diffTime = d->viewportMoveTime.elapsed(); if ( diffTime >= 667 || !d->viewportMoveActive ) { center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); d->viewportMoveTimer->stop(); d->viewportMoveActive = false; slotRequestVisiblePixmaps(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); return; } // move the viewport smoothly (kmplot: p(x)=1+0.47*(x-1)^3-0.25*(x-1)^4) float convergeSpeed = (float)diffTime / 667.0, x = ((float)viewport()->width() / 2.0) + horizontalScrollBar()->value(), y = ((float)viewport()->height() / 2.0) + verticalScrollBar()->value(), diffX = (float)d->viewportMoveDest.x() - x, diffY = (float)d->viewportMoveDest.y() - y; convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed); center( (int)(x + diffX * convergeSpeed), (int)(y + diffY * convergeSpeed ) ); } void PageView::slotAutoScroll() { // the first time create the timer if ( !d->autoScrollTimer ) { d->autoScrollTimer = new QTimer( this ); d->autoScrollTimer->setSingleShot( true ); connect( d->autoScrollTimer, &QTimer::timeout, this, &PageView::slotAutoScroll ); } // if scrollIncrement is zero, stop the timer if ( !d->scrollIncrement ) { d->autoScrollTimer->stop(); return; } // compute delay between timer ticks and scroll amount per tick int index = abs( d->scrollIncrement ) - 1; // 0..9 const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 }; const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 }; d->autoScrollTimer->start( scrollDelay[ index ] ); int delta = d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ]; verticalScrollBar()->setValue(verticalScrollBar()->value() + delta); } void PageView::slotDragScroll() { scrollTo( horizontalScrollBar()->value() + d->dragScrollVector.x(), verticalScrollBar()->value() + d->dragScrollVector.y() ); QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); updateSelection( p ); } void PageView::slotShowWelcome() { // show initial welcome text d->messageWindow->display( i18n( "Welcome" ), QString(), PageViewMessage::Info, 2000 ); } void PageView::slotShowSizeAllCursor() { setCursor( Qt::SizeAllCursor ); } void PageView::slotHandleWebShortcutAction() { QAction *action = qobject_cast( sender() ); if (action) { KUriFilterData filterData( action->data().toString() ); if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::WebShortcutFilter ) ) { QDesktopServices::openUrl( filterData.uri() ); } } } void PageView::slotConfigureWebShortcuts() { KToolInvocation::kdeinitExec( QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts") ); } void PageView::slotZoom() { if ( !d->aZoom->selectableActionGroup()->isEnabled() ) return; setFocus(); updateZoom( ZoomFixed ); } void PageView::slotZoomIn() { updateZoom( ZoomIn ); } void PageView::slotZoomOut() { updateZoom( ZoomOut ); } void PageView::slotFitToWidthToggled( bool on ) { if ( on ) updateZoom( ZoomFitWidth ); } void PageView::slotFitToPageToggled( bool on ) { if ( on ) updateZoom( ZoomFitPage ); } void PageView::slotAutoFitToggled( bool on ) { if ( on ) updateZoom( ZoomFitAuto ); } void PageView::slotViewMode( QAction *action ) { const int nr = action->data().toInt(); if ( (int)Okular::Settings::viewMode() != nr ) { Okular::Settings::setViewMode( nr ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotContinuousToggled( bool on ) { if ( Okular::Settings::viewContinuous() != on ) { Okular::Settings::setViewContinuous( on ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotSetMouseNormal() { d->mouseMode = Okular::Settings::EnumMouseMode::Browse; Okular::Settings::setMouseMode( d->mouseMode ); // hide the messageWindow d->messageWindow->hide(); // reshow the annotator toolbar if hiding was forced (and if it is not already visible) if ( d->annotator && d->annotator->hidingWasForced() && d->aToggleAnnotator && !d->aToggleAnnotator->isChecked() ) d->aToggleAnnotator->trigger(); // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseZoom() { d->mouseMode = Okular::Settings::EnumMouseMode::Zoom; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseMagnifier() { d->mouseMode = Okular::Settings::EnumMouseMode::Magnifier; Okular::Settings::setMouseMode( d->mouseMode ); d->messageWindow->display( i18n( "Click to see the magnified view." ), QString() ); // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::RectSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseTextSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::TextSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Select text" ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseTableSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::TableSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the table, then click near edges to divide up; press Esc to clear." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotToggleAnnotator( bool on ) { // the 'inHere' trick is needed as the slotSetMouseZoom() calls this static bool inHere = false; if ( inHere ) return; inHere = true; // the annotator can be used in normal mouse mode only, so if asked for it, // switch to normal mode if ( on && d->mouseMode != Okular::Settings::EnumMouseMode::Browse ) d->aMouseNormal->trigger(); // ask for Author's name if not already set if ( Okular::Settings::identityAuthor().isEmpty() ) { // get default username from the kdelibs/kdecore/KUser KUser currentUser; QString userName = currentUser.property( KUser::FullName ).toString(); // ask the user for confirmation/change if ( userName.isEmpty() ) { bool ok = false; userName = QInputDialog::getText(nullptr, i18n( "Annotations author" ), i18n( "Please insert your name or initials:" ), QLineEdit::Normal, QString(), &ok ); if ( !ok ) { d->aToggleAnnotator->trigger(); inHere = false; return; } } // save the name Okular::Settings::setIdentityAuthor( userName ); Okular::Settings::self()->save(); } // create the annotator object if not present if ( !d->annotator ) { d->annotator = new PageViewAnnotator( this, d->document ); bool allowTools = d->document->pages() > 0 && d->document->isAllowed( Okular::AllowNotes ); d->annotator->setToolsEnabled( allowTools ); d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); } // initialize/reset annotator (and show/hide toolbar) d->annotator->setEnabled( on ); d->annotator->setHidingForced( false ); inHere = false; } void PageView::slotAutoScrollUp() { if ( d->scrollIncrement < -9 ) return; d->scrollIncrement--; slotAutoScroll(); setFocus(); } void PageView::slotAutoScrollDown() { if ( d->scrollIncrement > 9 ) return; d->scrollIncrement++; slotAutoScroll(); setFocus(); } void PageView::slotScrollUp( bool singleStep ) { // if in single page mode and at the top of the screen, go to \ page if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minimum() ) { if ( singleStep ) verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); else verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub ); } else if ( d->document->currentPage() > 0 ) { // more optimized than document->setPrevPage and then move view to bottom Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= viewColumns(); if ( newViewport.pageNumber < 0 ) newViewport.pageNumber = 0; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } } void PageView::slotScrollDown( bool singleStep ) { // if in single page mode and at the bottom of the screen, go to next page if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maximum() ) { if ( singleStep ) verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); else verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd ); } else if ( (int)d->document->currentPage() < d->items.count() - 1 ) { // more optimized than document->setNextPage and then move view to top Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += viewColumns(); if ( newViewport.pageNumber >= (int)d->items.count() ) newViewport.pageNumber = d->items.count() - 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } } void PageView::slotRotateClockwise() { int id = ( (int)d->document->rotation() + 1 ) % 4; d->document->setRotation( id ); } void PageView::slotRotateCounterClockwise() { int id = ( (int)d->document->rotation() + 3 ) % 4; d->document->setRotation( id ); } void PageView::slotRotateOriginal() { d->document->setRotation( 0 ); } void PageView::slotPageSizes( int newsize ) { if ( newsize < 0 || newsize >= d->document->pageSizes().count() ) return; d->document->setPageSize( d->document->pageSizes().at( newsize ) ); } // Enforce mutual-exclusion between trim modes // Each mode is uniquely identified by a single value // From Okular::Settings::EnumTrimMode void PageView::updateTrimMode( int except_id ) { const QList trimModeActions = d->aTrimMode->menu()->actions(); foreach(QAction *trimModeAction, trimModeActions) { if (trimModeAction->data().toInt() != except_id) trimModeAction->setChecked( false ); } } bool PageView::mouseReleaseOverLink( const Okular::ObjectRect * rect ) const { if ( rect ) { // handle click over a link const Okular::Action * action = static_cast< const Okular::Action * >( rect->object() ); d->document->processAction( action ); return true; } return false; } void PageView::slotTrimMarginsToggled( bool on ) { if (on) { // Turn off any other Trim modes updateTrimMode(d->aTrimMargins->data().toInt()); } if ( Okular::Settings::trimMargins() != on ) { Okular::Settings::setTrimMargins( on ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } } } void PageView::slotTrimToSelectionToggled( bool on ) { if ( on ) { // Turn off any other Trim modes updateTrimMode(d->aTrimToSelection->data().toInt()); d->mouseMode = Okular::Settings::EnumMouseMode::TrimSelect; // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the page area you wish to keep visible" ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); } else { // toggled off while making selection if ( Okular::Settings::EnumMouseMode::TrimSelect == d->mouseMode ) { // clear widget selection and invalidate rect selectionClear(); // When Trim selection bbox interaction is over, we should switch to another mousemode. if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = nullptr; } else { d->aMouseNormal->trigger(); } } d->trimBoundingBox = Okular::NormalizedRect(); // invalidate box if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } } } void PageView::slotToggleForms() { toggleFormWidgets( !d->m_formsVisible ); } void PageView::slotFormChanged( int pageNumber ) { if ( !d->refreshTimer ) { d->refreshTimer = new QTimer( this ); d->refreshTimer->setSingleShot( true ); connect( d->refreshTimer, &QTimer::timeout, this, &PageView::slotRefreshPage ); } d->refreshPages << pageNumber; int delay = 0; if ( d->m_formsVisible ) { delay = 1000; } d->refreshTimer->start( delay ); } void PageView::slotRefreshPage() { foreach(int req, d->refreshPages) { QMetaObject::invokeMethod( d->document, "refreshPixmaps", Qt::QueuedConnection, Q_ARG( int, req ) ); } d->refreshPages.clear(); } #ifdef HAVE_SPEECH void PageView::slotSpeakDocument() { QString text; QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); for ( ; it < itEnd; ++it ) { Okular::RegularAreaRect * area = textSelectionForItem( *it ); text.append( (*it)->page()->text( area ) ); text.append( '\n' ); delete area; } d->tts()->say( text ); } void PageView::slotSpeakCurrentPage() { const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); Okular::RegularAreaRect * area = textSelectionForItem( item ); const QString text = item->page()->text( area ); delete area; d->tts()->say( text ); } void PageView::slotStopSpeaks() { if ( !d->m_tts ) return; d->m_tts->stopAllSpeechs(); } #endif void PageView::slotAction( Okular::Action *action ) { d->document->processAction( action ); } void PageView::externalKeyPressEvent( QKeyEvent *e ) { keyPressEvent( e ); } void PageView::slotProcessMovieAction( const Okular::MovieAction *action ) { const Okular::MovieAnnotation *movieAnnotation = action->annotation(); if ( !movieAnnotation ) return; Okular::Movie *movie = movieAnnotation->movie(); if ( !movie ) return; const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); if ( !item ) return; VideoWidget *vw = item->videoWidgets().value( 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 PageView::slotProcessRenditionAction( const Okular::RenditionAction *action ) { Okular::Movie *movie = action->movie(); if ( !movie ) return; const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); if ( !item ) return; VideoWidget *vw = item->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 PageView::slotSetChangeColors(bool active) { Okular::SettingsCore::setChangeColors(active); Okular::Settings::self()->save(); viewport()->update(); } void PageView::slotToggleChangeColors() { slotSetChangeColors( !Okular::SettingsCore::changeColors() ); } void PageView::slotFitWindowToPage() { PageViewItem currentPageItem = nullptr; QSize viewportSize = viewport()->size(); foreach ( const PageViewItem * pageItem, d->items ) { if ( pageItem->isVisible() ) { currentPageItem = *pageItem; break; } } const QSize pageSize = QSize( currentPageItem.uncroppedWidth() + kcolWidthMargin, currentPageItem.uncroppedHeight() + krowHeightMargin ); if ( verticalScrollBar()->isVisible() ) viewportSize.setWidth( viewportSize.width() + verticalScrollBar()->width() ); if ( horizontalScrollBar()->isVisible() ) viewportSize.setHeight( viewportSize.height() + horizontalScrollBar()->height() ); emit fitWindowToPage( viewportSize, pageSize ); } //END private SLOTS #include "moc_pageview.cpp" /* kate: replace-tabs on; indent-width 4; */ diff --git a/ui/presentationwidget.cpp b/ui/presentationwidget.cpp index 9d81f4e45..3e75e0bd3 100644 --- a/ui/presentationwidget.cpp +++ b/ui/presentationwidget.cpp @@ -1,2457 +1,2457 @@ /*************************************************************************** * 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 #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() { qDeleteAll( videoWidgets ); } 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_sleepInhibitCookie(0), 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->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(); QAction *eraseDrawingAct = collection->action( QStringLiteral("presentation_erase_drawings") ); eraseDrawingAct->setEnabled( true ); connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings); m_topBar->addAction( eraseDrawingAct ); addAction( eraseDrawingAct ); 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 ); 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 ); 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 consistancy 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_sleepInhibitCookie) { 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_sleepInhibitCookie = reply.value().fileDescriptor(); } else { qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error(); } } #endif } void PresentationWidget::allowPowerManagement() { #ifdef Q_OS_LINUX if (m_sleepInhibitCookie) { ::close(m_sleepInhibitCookie); m_sleepInhibitCookie = 0; } 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++ ) { // compure shrinked 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++ ) { // compure shrinked 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/snapshottaker.h b/ui/snapshottaker.h index 19bee5681..4f624b26c 100644 --- a/ui/snapshottaker.h +++ b/ui/snapshottaker.h @@ -1,37 +1,37 @@ /*************************************************************************** * Copyright (C) 2012 by Tobias Koening * * * * 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 SNAPSHOTTAKER_H #define SNAPSHOTTAKER_H #include -#include +#include class QImage; class SnapshotTaker : public QObject { Q_OBJECT public: SnapshotTaker( const QUrl &url, QObject *parent = nullptr ); ~SnapshotTaker(); Q_SIGNALS: void finished( const QImage &image ); private Q_SLOTS: void stateChanged(Phonon::State, Phonon::State); private: Phonon::VideoPlayer *m_player; }; #endif