Merge branch 'dont-use-docdata-for-annots-and-forms' into Applications/17.12

This commit is contained in:
Albert Astals Cid 2017-11-16 15:00:23 +01:00
commit 3957683d76
39 changed files with 2602 additions and 527 deletions

View file

@ -0,0 +1,427 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE documentInfo>
<documentInfo url="/home/kdeunstable/okular/autotests/data/file1.pdf">
<pageList>
<page number="0">
<annotationList>
<annotation type="6">
<base color="#00ff00" modifyDate="2017-09-11T19:40:57" flags="0" creationDate="2017-09-11T19:40:57" uniqueName="testannot" author="someone">
<boundary l="0.195707" t="0.129927" b="0.232603" r="0.818182"/>
<penStyle marks="3" width="2" style="1" ycr="0" xcr="0" spaces="0"/>
</base>
<ink>
<path>
<point x="0.445076" y="0.149878"/>
<point x="0.445076" y="0.149392"/>
<point x="0.444444" y="0.148905"/>
<point x="0.442551" y="0.148418"/>
<point x="0.440025" y="0.147932"/>
<point x="0.435606" y="0.146959"/>
<point x="0.433081" y="0.145985"/>
<point x="0.429924" y="0.145012"/>
<point x="0.424874" y="0.145012"/>
<point x="0.418561" y="0.144039"/>
<point x="0.412247" y="0.142579"/>
<point x="0.407197" y="0.142579"/>
<point x="0.398359" y="0.141606"/>
<point x="0.392045" y="0.140146"/>
<point x="0.38447" y="0.138686"/>
<point x="0.379419" y="0.1382"/>
<point x="0.372475" y="0.1382"/>
<point x="0.368056" y="0.137226"/>
<point x="0.361111" y="0.135766"/>
<point x="0.352904" y="0.134307"/>
<point x="0.34596" y="0.133333"/>
<point x="0.339015" y="0.132847"/>
<point x="0.332702" y="0.132847"/>
<point x="0.327652" y="0.131873"/>
<point x="0.322601" y="0.131873"/>
<point x="0.318813" y="0.131873"/>
<point x="0.314394" y="0.131873"/>
<point x="0.310606" y="0.131873"/>
<point x="0.306818" y="0.131873"/>
<point x="0.304293" y="0.131873"/>
<point x="0.301768" y="0.131873"/>
<point x="0.299242" y="0.131873"/>
<point x="0.295455" y="0.131873"/>
<point x="0.292298" y="0.131873"/>
<point x="0.289141" y="0.131873"/>
<point x="0.285985" y="0.131387"/>
<point x="0.282828" y="0.1309"/>
<point x="0.278409" y="0.1309"/>
<point x="0.273359" y="0.1309"/>
<point x="0.269571" y="0.1309"/>
<point x="0.265152" y="0.1309"/>
<point x="0.262626" y="0.131387"/>
<point x="0.258207" y="0.131387"/>
<point x="0.253157" y="0.131873"/>
<point x="0.25" y="0.13236"/>
<point x="0.247475" y="0.133333"/>
<point x="0.244318" y="0.13382"/>
<point x="0.24053" y="0.13528"/>
<point x="0.238636" y="0.13528"/>
<point x="0.234217" y="0.136253"/>
<point x="0.232955" y="0.137226"/>
<point x="0.229167" y="0.1382"/>
<point x="0.22601" y="0.140146"/>
<point x="0.225379" y="0.142092"/>
<point x="0.223485" y="0.143066"/>
<point x="0.220328" y="0.144526"/>
<point x="0.219066" y="0.146472"/>
<point x="0.21654" y="0.148418"/>
<point x="0.215909" y="0.149878"/>
<point x="0.215278" y="0.151825"/>
<point x="0.214646" y="0.152798"/>
<point x="0.212753" y="0.154745"/>
<point x="0.21149" y="0.157178"/>
<point x="0.210227" y="0.159611"/>
<point x="0.208333" y="0.161557"/>
<point x="0.206439" y="0.16399"/>
<point x="0.204545" y="0.165937"/>
<point x="0.203914" y="0.167397"/>
<point x="0.203283" y="0.169343"/>
<point x="0.202652" y="0.171776"/>
<point x="0.200758" y="0.173723"/>
<point x="0.200126" y="0.176642"/>
<point x="0.200126" y="0.179075"/>
<point x="0.199495" y="0.181995"/>
<point x="0.199495" y="0.185401"/>
<point x="0.198864" y="0.188808"/>
<point x="0.198864" y="0.191241"/>
<point x="0.198864" y="0.193674"/>
<point x="0.19697" y="0.196107"/>
<point x="0.19697" y="0.19854"/>
<point x="0.198864" y="0.2"/>
<point x="0.198864" y="0.201946"/>
<point x="0.200126" y="0.20438"/>
<point x="0.200126" y="0.204866"/>
<point x="0.200758" y="0.205353"/>
<point x="0.20202" y="0.206813"/>
<point x="0.203283" y="0.207786"/>
<point x="0.204545" y="0.208759"/>
<point x="0.205808" y="0.209246"/>
<point x="0.207702" y="0.210706"/>
<point x="0.210859" y="0.211679"/>
<point x="0.213384" y="0.212165"/>
<point x="0.21654" y="0.213139"/>
<point x="0.22096" y="0.214112"/>
<point x="0.22601" y="0.214599"/>
<point x="0.232955" y="0.216058"/>
<point x="0.24053" y="0.216058"/>
<point x="0.248737" y="0.217518"/>
<point x="0.258207" y="0.218491"/>
<point x="0.265783" y="0.218978"/>
<point x="0.27399" y="0.220925"/>
<point x="0.280303" y="0.220925"/>
<point x="0.287879" y="0.221411"/>
<point x="0.29798" y="0.221411"/>
<point x="0.305556" y="0.221411"/>
<point x="0.314394" y="0.222384"/>
<point x="0.322601" y="0.222384"/>
<point x="0.330177" y="0.222384"/>
<point x="0.339646" y="0.223358"/>
<point x="0.34596" y="0.223358"/>
<point x="0.353535" y="0.223358"/>
<point x="0.361111" y="0.223844"/>
<point x="0.368687" y="0.224818"/>
<point x="0.376263" y="0.224818"/>
<point x="0.388258" y="0.224818"/>
<point x="0.397727" y="0.225791"/>
<point x="0.405303" y="0.225791"/>
<point x="0.414141" y="0.225791"/>
<point x="0.42298" y="0.224818"/>
<point x="0.431818" y="0.224818"/>
<point x="0.435606" y="0.225791"/>
<point x="0.443813" y="0.225791"/>
<point x="0.452652" y="0.225791"/>
<point x="0.463384" y="0.226764"/>
<point x="0.474116" y="0.227251"/>
<point x="0.48548" y="0.227251"/>
<point x="0.493056" y="0.227251"/>
<point x="0.501894" y="0.228224"/>
<point x="0.510101" y="0.228224"/>
<point x="0.518939" y="0.23017"/>
<point x="0.530303" y="0.230657"/>
<point x="0.541667" y="0.230657"/>
<point x="0.549874" y="0.23163"/>
<point x="0.558712" y="0.23163"/>
<point x="0.567551" y="0.23163"/>
<point x="0.57702" y="0.23163"/>
<point x="0.584596" y="0.23163"/>
<point x="0.596591" y="0.23163"/>
<point x="0.609217" y="0.23163"/>
<point x="0.617424" y="0.23163"/>
<point x="0.629419" y="0.23163"/>
<point x="0.63952" y="0.23163"/>
<point x="0.646465" y="0.23163"/>
<point x="0.653409" y="0.23163"/>
<point x="0.659091" y="0.23163"/>
<point x="0.666035" y="0.23163"/>
<point x="0.672348" y="0.23163"/>
<point x="0.679924" y="0.231144"/>
<point x="0.684343" y="0.23017"/>
<point x="0.690657" y="0.23017"/>
<point x="0.695076" y="0.229197"/>
<point x="0.700126" y="0.22871"/>
<point x="0.703914" y="0.227737"/>
<point x="0.707702" y="0.226277"/>
<point x="0.710859" y="0.225304"/>
<point x="0.714015" y="0.224331"/>
<point x="0.717803" y="0.223358"/>
<point x="0.720328" y="0.222871"/>
<point x="0.72601" y="0.221411"/>
<point x="0.731061" y="0.220925"/>
<point x="0.73548" y="0.220438"/>
<point x="0.74053" y="0.218978"/>
<point x="0.745581" y="0.218491"/>
<point x="0.747475" y="0.217032"/>
<point x="0.751263" y="0.215572"/>
<point x="0.754419" y="0.213625"/>
<point x="0.756944" y="0.213139"/>
<point x="0.75947" y="0.211192"/>
<point x="0.763889" y="0.211192"/>
<point x="0.768308" y="0.211192"/>
<point x="0.770202" y="0.210219"/>
<point x="0.774621" y="0.210219"/>
<point x="0.777778" y="0.208759"/>
<point x="0.78346" y="0.208273"/>
<point x="0.787247" y="0.206813"/>
<point x="0.78851" y="0.206813"/>
<point x="0.790404" y="0.206326"/>
<point x="0.791667" y="0.206326"/>
<point x="0.794192" y="0.204866"/>
<point x="0.795455" y="0.20438"/>
<point x="0.798611" y="0.203893"/>
<point x="0.801768" y="0.201946"/>
<point x="0.802399" y="0.20146"/>
<point x="0.805556" y="0.2"/>
<point x="0.806818" y="0.19854"/>
<point x="0.808081" y="0.198054"/>
<point x="0.808712" y="0.196594"/>
<point x="0.809975" y="0.196107"/>
<point x="0.810606" y="0.194647"/>
<point x="0.810606" y="0.193674"/>
<point x="0.811237" y="0.192214"/>
<point x="0.811869" y="0.190754"/>
<point x="0.813763" y="0.188808"/>
<point x="0.813763" y="0.187348"/>
<point x="0.814394" y="0.185401"/>
<point x="0.815025" y="0.183942"/>
<point x="0.815657" y="0.181022"/>
<point x="0.816288" y="0.179562"/>
<point x="0.816288" y="0.177616"/>
<point x="0.816288" y="0.176156"/>
<point x="0.816919" y="0.173723"/>
<point x="0.816919" y="0.171776"/>
<point x="0.816919" y="0.170316"/>
<point x="0.816919" y="0.167883"/>
<point x="0.816919" y="0.165937"/>
<point x="0.816288" y="0.16399"/>
<point x="0.816288" y="0.162044"/>
<point x="0.816288" y="0.160584"/>
<point x="0.816288" y="0.159124"/>
<point x="0.815657" y="0.158151"/>
<point x="0.815657" y="0.156691"/>
<point x="0.815025" y="0.155231"/>
<point x="0.814394" y="0.154258"/>
<point x="0.813763" y="0.152798"/>
<point x="0.813763" y="0.151825"/>
<point x="0.8125" y="0.150365"/>
<point x="0.811869" y="0.149392"/>
<point x="0.811869" y="0.148905"/>
<point x="0.810606" y="0.147445"/>
<point x="0.809975" y="0.146959"/>
<point x="0.809343" y="0.146472"/>
<point x="0.808712" y="0.145985"/>
<point x="0.808081" y="0.145499"/>
<point x="0.807449" y="0.145012"/>
<point x="0.806818" y="0.145012"/>
<point x="0.805556" y="0.144526"/>
<point x="0.804293" y="0.144039"/>
<point x="0.80303" y="0.143552"/>
<point x="0.802399" y="0.143552"/>
<point x="0.800505" y="0.143552"/>
<point x="0.799874" y="0.143066"/>
<point x="0.796717" y="0.142579"/>
<point x="0.793561" y="0.142579"/>
<point x="0.791667" y="0.142092"/>
<point x="0.789773" y="0.142092"/>
<point x="0.787247" y="0.141119"/>
<point x="0.78346" y="0.140633"/>
<point x="0.780934" y="0.140633"/>
<point x="0.777778" y="0.140633"/>
<point x="0.773359" y="0.140146"/>
<point x="0.768939" y="0.140146"/>
<point x="0.763889" y="0.139659"/>
<point x="0.761364" y="0.139173"/>
<point x="0.757576" y="0.139173"/>
<point x="0.752525" y="0.139173"/>
<point x="0.747475" y="0.1382"/>
<point x="0.744318" y="0.1382"/>
<point x="0.739899" y="0.137713"/>
<point x="0.73548" y="0.137713"/>
<point x="0.732323" y="0.137713"/>
<point x="0.727904" y="0.137713"/>
<point x="0.723485" y="0.137713"/>
<point x="0.72096" y="0.137713"/>
<point x="0.718434" y="0.137713"/>
<point x="0.715909" y="0.137713"/>
<point x="0.71149" y="0.137226"/>
<point x="0.707702" y="0.137226"/>
<point x="0.704545" y="0.137226"/>
<point x="0.700126" y="0.137226"/>
<point x="0.696338" y="0.137226"/>
<point x="0.693182" y="0.137226"/>
<point x="0.689394" y="0.137226"/>
<point x="0.686869" y="0.137226"/>
<point x="0.683712" y="0.137226"/>
<point x="0.681818" y="0.137226"/>
<point x="0.679293" y="0.137226"/>
<point x="0.676136" y="0.137226"/>
<point x="0.67298" y="0.137226"/>
<point x="0.669823" y="0.137226"/>
<point x="0.667298" y="0.137226"/>
<point x="0.66351" y="0.137226"/>
<point x="0.661616" y="0.137226"/>
<point x="0.660354" y="0.137226"/>
<point x="0.657828" y="0.137226"/>
<point x="0.654672" y="0.13674"/>
<point x="0.652778" y="0.13674"/>
<point x="0.648359" y="0.13674"/>
<point x="0.645202" y="0.13674"/>
<point x="0.642045" y="0.13674"/>
<point x="0.636995" y="0.137713"/>
<point x="0.632576" y="0.137713"/>
<point x="0.627525" y="0.1382"/>
<point x="0.623106" y="0.1382"/>
<point x="0.618687" y="0.1382"/>
<point x="0.614899" y="0.137713"/>
<point x="0.611742" y="0.137713"/>
<point x="0.606692" y="0.137713"/>
<point x="0.602273" y="0.137713"/>
<point x="0.599747" y="0.137713"/>
<point x="0.596591" y="0.137226"/>
<point x="0.592172" y="0.137226"/>
<point x="0.589646" y="0.136253"/>
<point x="0.585227" y="0.135766"/>
<point x="0.582071" y="0.135766"/>
<point x="0.577652" y="0.135766"/>
<point x="0.575758" y="0.13528"/>
<point x="0.571338" y="0.134307"/>
<point x="0.568813" y="0.13382"/>
<point x="0.565025" y="0.13382"/>
<point x="0.5625" y="0.133333"/>
<point x="0.559975" y="0.133333"/>
<point x="0.557449" y="0.13236"/>
<point x="0.554924" y="0.13236"/>
<point x="0.551768" y="0.131873"/>
<point x="0.54798" y="0.131873"/>
<point x="0.544823" y="0.131873"/>
<point x="0.541667" y="0.131873"/>
<point x="0.539773" y="0.131873"/>
<point x="0.537879" y="0.131873"/>
<point x="0.536616" y="0.131873"/>
<point x="0.534722" y="0.131873"/>
<point x="0.532197" y="0.131873"/>
<point x="0.529672" y="0.131873"/>
<point x="0.526515" y="0.131873"/>
<point x="0.52399" y="0.131387"/>
<point x="0.522096" y="0.131387"/>
<point x="0.520202" y="0.131387"/>
<point x="0.518308" y="0.131387"/>
<point x="0.513889" y="0.131387"/>
<point x="0.510732" y="0.131387"/>
<point x="0.507576" y="0.131387"/>
<point x="0.505051" y="0.131387"/>
<point x="0.502525" y="0.131387"/>
<point x="0.499369" y="0.131387"/>
<point x="0.496843" y="0.131873"/>
<point x="0.493687" y="0.131873"/>
<point x="0.489268" y="0.131873"/>
<point x="0.486111" y="0.131873"/>
<point x="0.483586" y="0.131873"/>
<point x="0.481692" y="0.131873"/>
<point x="0.478535" y="0.131873"/>
<point x="0.476641" y="0.131873"/>
<point x="0.474116" y="0.131873"/>
<point x="0.471591" y="0.132847"/>
<point x="0.47096" y="0.132847"/>
<point x="0.470328" y="0.132847"/>
<point x="0.467803" y="0.133333"/>
<point x="0.467172" y="0.133333"/>
<point x="0.465909" y="0.133333"/>
<point x="0.464646" y="0.133333"/>
<point x="0.462121" y="0.13382"/>
<point x="0.460859" y="0.134307"/>
<point x="0.459596" y="0.134793"/>
<point x="0.458965" y="0.134793"/>
<point x="0.456439" y="0.13528"/>
<point x="0.455808" y="0.135766"/>
<point x="0.455177" y="0.135766"/>
<point x="0.453914" y="0.135766"/>
<point x="0.451389" y="0.135766"/>
<point x="0.450126" y="0.136253"/>
<point x="0.449495" y="0.136253"/>
<point x="0.44697" y="0.137226"/>
<point x="0.445707" y="0.137226"/>
<point x="0.443182" y="0.137713"/>
<point x="0.441919" y="0.137713"/>
<point x="0.440657" y="0.138686"/>
<point x="0.438131" y="0.139173"/>
<point x="0.435606" y="0.139659"/>
<point x="0.432449" y="0.140146"/>
<point x="0.429924" y="0.140146"/>
<point x="0.428662" y="0.140633"/>
<point x="0.426136" y="0.140633"/>
<point x="0.424242" y="0.141119"/>
<point x="0.421086" y="0.142092"/>
<point x="0.419192" y="0.142092"/>
<point x="0.416667" y="0.142579"/>
<point x="0.414141" y="0.142579"/>
<point x="0.411616" y="0.142579"/>
<point x="0.409091" y="0.142579"/>
<point x="0.405934" y="0.142579"/>
<point x="0.403409" y="0.142579"/>
<point x="0.399621" y="0.143552"/>
<point x="0.396465" y="0.143552"/>
<point x="0.395202" y="0.144039"/>
<point x="0.392677" y="0.144039"/>
<point x="0.390152" y="0.144039"/>
<point x="0.387626" y="0.144039"/>
<point x="0.386364" y="0.144039"/>
<point x="0.385101" y="0.144039"/>
<point x="0.381944" y="0.144039"/>
<point x="0.380682" y="0.144039"/>
<point x="0.379419" y="0.144039"/>
<point x="0.378788" y="0.144039"/>
<point x="0.378157" y="0.144039"/>
<point x="0.377525" y="0.144039"/>
<point x="0.376263" y="0.144526"/>
<point x="0.375631" y="0.144526"/>
<point x="0.375" y="0.144526"/>
<point x="0.374369" y="0.144526"/>
<point x="0.373737" y="0.144526"/>
<point x="0.373106" y="0.144526"/>
<point x="0.372475" y="0.144526"/>
<point x="0.371843" y="0.145012"/>
<point x="0.370581" y="0.145012"/>
<point x="0.370581" y="0.145499"/>
<point x="0.369949" y="0.145985"/>
<point x="0.369318" y="0.145985"/>
</path>
</ink>
</annotation>
</annotationList>
</page>
</pageList>
<generalInfo>
<history>
<current viewport="0;C2:0.499684:0.23236:1"/>
</history>
<views>
<view name="PageView">
<zoom value="1.3145" mode="1"/>
</view>
</views>
</generalInfo>
</documentInfo>

BIN
autotests/data/potato.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2013 by Fabio D'Urso <fabiodurso@hotmail.it> *
* 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 *
@ -11,9 +14,12 @@
#include <threadweaver/queue.h>
#include "../core/annotations.h"
#include "../core/document.h"
#include "../core/document_p.h"
#include "../core/generator.h"
#include "../core/observer.h"
#include "../core/page.h"
#include "../core/rotationjob_p.h"
#include "../settings_core.h"
@ -24,6 +30,7 @@ class DocumentTest
private slots:
void testCloseDuringRotationJob();
void testDocdataMigration();
};
// Test that we don't crash if the document is closed while a RotationJob
@ -57,6 +64,69 @@ void DocumentTest::testCloseDuringRotationJob()
ThreadWeaver::Queue::instance()->resume();
ThreadWeaver::Queue::instance()->finish();
qApp->processEvents();
delete dummyDocumentObserver;
}
// Test that, if there's a XML file in docdata referring to a document, we
// detect that it must be migrated, that it doesn't get wiped out if you close
// the document without migrating and that it does get wiped out after migrating
void DocumentTest::testDocdataMigration()
{
Okular::SettingsCore::instance( "documenttest" );
const QUrl testFileUrl = QUrl::fromLocalFile(KDESRCDIR "data/file1.pdf");
const QString testFilePath = testFileUrl.toLocalFile();
const qint64 testFileSize = QFileInfo(testFilePath).size();
// Copy XML file to the docdata/ directory
const QString docDataPath = Okular::DocumentPrivate::docDataFileName(testFileUrl, testFileSize);
QFile::remove(docDataPath);
QVERIFY( QFile::copy(KDESRCDIR "data/file1-docdata.xml", docDataPath) );
// Open our document
Okular::Document *m_document = new Okular::Document( 0 );
QMimeDatabase db;
const QMimeType mime = db.mimeTypeForFile( testFilePath );
QCOMPARE( m_document->openDocument( testFilePath, testFileUrl, mime ), Okular::Document::OpenSuccess );
// Check that the annotation from file1-docdata.xml was loaded
QCOMPARE( m_document->page( 0 )->annotations().size(), 1 );
QCOMPARE( m_document->page( 0 )->annotations().first()->uniqueName(), QString("testannot") );
// Check that we detect that it must be migrated
QCOMPARE( m_document->isDocdataMigrationNeeded(), true );
m_document->closeDocument();
// Reopen the document and check that the annotation is still present
// (because we have not migrated)
QCOMPARE( m_document->openDocument( testFilePath, testFileUrl, mime ), Okular::Document::OpenSuccess );
QCOMPARE( m_document->page( 0 )->annotations().size(), 1 );
QCOMPARE( m_document->page( 0 )->annotations().first()->uniqueName(), QString("testannot") );
QCOMPARE( m_document->isDocdataMigrationNeeded(), true );
// Do the migration
QTemporaryFile migratedSaveFile( QString( "%1/okrXXXXXX.pdf" ).arg( QDir::tempPath() ) );
QVERIFY( migratedSaveFile.open() );
migratedSaveFile.close();
QVERIFY( m_document->saveChanges( migratedSaveFile.fileName() ) );
m_document->docdataMigrationDone();
QCOMPARE( m_document->isDocdataMigrationNeeded(), false );
m_document->closeDocument();
// Now the docdata file should have no annotations, let's check
QCOMPARE( m_document->openDocument( testFilePath, testFileUrl, mime ), Okular::Document::OpenSuccess );
QCOMPARE( m_document->page( 0 )->annotations().size(), 0 );
QCOMPARE( m_document->isDocdataMigrationNeeded(), false );
m_document->closeDocument();
// And the new file should have 1 annotation, let's check
QCOMPARE( m_document->openDocument( migratedSaveFile.fileName(), migratedSaveFile.fileName(), mime ), Okular::Document::OpenSuccess );
QCOMPARE( m_document->page( 0 )->annotations().size(), 1 );
QCOMPARE( m_document->isDocdataMigrationNeeded(), false );
m_document->closeDocument();
delete m_document;
}
QTEST_MAIN( DocumentTest )

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2013 by Albert Astals Cid <aacid@kde.org> *
* 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 *
@ -9,6 +12,8 @@
#include <QtTest>
#include "../core/annotations.h"
#include "../core/form.h"
#include "../core/page.h"
#include "../part.h"
#include "../ui/toc.h"
@ -17,6 +22,8 @@
#include <KConfigDialog>
#include <QClipboard>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollBar>
#include <QTemporaryDir>
#include <QTreeView>
@ -24,6 +31,40 @@
#include <QDesktopServices>
#include <QMenu>
class CloseDialogHelper : public QObject
{
Q_OBJECT
public:
CloseDialogHelper(Okular::Part *p, QDialogButtonBox::StandardButton b) : m_part(p), m_button(b), m_clicked(false)
{
QTimer::singleShot(0, this, &CloseDialogHelper::closeDialog);
}
~CloseDialogHelper()
{
QVERIFY(m_clicked);
}
private slots:
void closeDialog()
{
QDialog *dialog = m_part->widget()->findChild<QDialog*>();
if (!dialog) {
QTimer::singleShot(0, this, &CloseDialogHelper::closeDialog);
return;
}
QDialogButtonBox *buttonBox = dialog->findChild<QDialogButtonBox*>();
buttonBox->button(m_button)->click();
m_clicked = true;
}
private:
Okular::Part *m_part;
QDialogButtonBox::StandardButton m_button;
bool m_clicked;
};
namespace Okular
{
class PartTest
@ -45,6 +86,12 @@ class PartTest
void testGeneratorPreferences();
void testSelectText();
void testClickInternalLink();
void testSaveAs();
void testSaveAs_data();
void testSaveAsUndoStackAnnotations();
void testSaveAsUndoStackAnnotations_data();
void testSaveAsUndoStackForms();
void testSaveAsUndoStackForms_data();
void testMouseMoveOverLinkWhileInSelectionMode();
void testClickUrlLinkWhileInSelectionMode();
void testeTextSelectionOverAndAcrossLinks_data();
@ -725,6 +772,412 @@ void PartTest::simulateMouseSelection(double startX, double startY, double endX,
events.simulate(target);
}
void PartTest::testSaveAs()
{
QFETCH(QString, file);
QFETCH(QString, extension);
QFETCH(bool, nativelySupportsAnnotations);
QFETCH(bool, canSwapBackingFile);
QScopedPointer<CloseDialogHelper> closeDialogHelper;
QString annotName;
QTemporaryFile archiveSave( QString( "%1/okrXXXXXX.okular" ).arg( QDir::tempPath() ) );
QTemporaryFile nativeDirectSave( QString( "%1/okrXXXXXX.%2" ).arg( QDir::tempPath() ).arg ( extension ) );
QTemporaryFile nativeFromArchiveFile( QString( "%1/okrXXXXXX.%2" ).arg( QDir::tempPath() ).arg ( extension ) );
QVERIFY( archiveSave.open() );
archiveSave.close();
QVERIFY( nativeDirectSave.open() );
nativeDirectSave.close();
QVERIFY( nativeFromArchiveFile.open() );
nativeFromArchiveFile.close();
qDebug() << "Open file, add annotation and save both natively and to .okular";
{
Okular::Part part(nullptr, nullptr, QVariantList());
part.openDocument( file );
QCOMPARE(part.m_document->canSwapBackingFile(), canSwapBackingFile);
Okular::Annotation *annot = new Okular::TextAnnotation();
annot->setBoundingRectangle( Okular::NormalizedRect( 0.1, 0.1, 0.15, 0.15 ) );
annot->setContents( "annot contents" );
part.m_document->addPageAnnotation( 0, annot );
annotName = annot->uniqueName();
if ( canSwapBackingFile )
{
if ( !nativelySupportsAnnotations )
{
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( nativeDirectSave.fileName() ), Part::NoSaveAsFlags ) );
// For backends that don't support annotations natively we mark the part as still modified
// after a save because we keep the annotation around but it will get lost if the user closes the app
// so we want to give her a last chance to save on close with the "you have changes dialog"
QCOMPARE( part.isModified(), !nativelySupportsAnnotations );
QVERIFY( part.saveAs( QUrl::fromLocalFile( archiveSave.fileName() ), Part::SaveAsOkularArchive ) );
}
else
{
// We need to save to archive first otherwise we lose the annotation
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::Yes )); // this is the "you're going to lose the undo/redo stack" dialog
QVERIFY( part.saveAs( QUrl::fromLocalFile( archiveSave.fileName() ), Part::SaveAsOkularArchive ) );
if ( !nativelySupportsAnnotations )
{
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( nativeDirectSave.fileName() ), Part::NoSaveAsFlags ) );
}
part.closeUrl();
}
qDebug() << "Open the .okular, check that the annotation is present and save to native";
{
Okular::Part part(nullptr, nullptr, QVariantList());
part.openDocument( archiveSave.fileName() );
QCOMPARE( part.m_document->page( 0 )->annotations().size(), 1 );
QCOMPARE( part.m_document->page( 0 )->annotations().first()->uniqueName(), annotName );
if ( !nativelySupportsAnnotations )
{
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( nativeFromArchiveFile.fileName() ), Part::NoSaveAsFlags ) );
if ( canSwapBackingFile && !nativelySupportsAnnotations )
{
// For backends that don't support annotations natively we mark the part as still modified
// after a save because we keep the annotation around but it will get lost if the user closes the app
// so we want to give her a last chance to save on close with the "you have changes dialog"
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "do you want to save or discard" dialog
}
part.closeUrl();
}
qDebug() << "Open the native file saved directly, and check that the annot"
<< "is there iff we expect it";
{
Okular::Part part(nullptr, nullptr, QVariantList());
part.openDocument( nativeDirectSave.fileName() );
QCOMPARE( part.m_document->page( 0 )->annotations().size(), nativelySupportsAnnotations ? 1 : 0 );
if ( nativelySupportsAnnotations )
QCOMPARE( part.m_document->page( 0 )->annotations().first()->uniqueName(), annotName );
part.closeUrl();
}
qDebug() << "Open the native file saved from the .okular, and check that the annot"
<< "is there iff we expect it";
{
Okular::Part part(nullptr, nullptr, QVariantList());
part.openDocument( nativeFromArchiveFile.fileName() );
QCOMPARE( part.m_document->page( 0 )->annotations().size(), nativelySupportsAnnotations ? 1 : 0 );
if ( nativelySupportsAnnotations )
QCOMPARE( part.m_document->page( 0 )->annotations().first()->uniqueName(), annotName );
part.closeUrl();
}
}
void PartTest::testSaveAs_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("extension");
QTest::addColumn<bool>("nativelySupportsAnnotations");
QTest::addColumn<bool>("canSwapBackingFile");
QTest::newRow("pdf") << KDESRCDIR "data/file1.pdf" << "pdf" << true << true;
QTest::newRow("epub") << KDESRCDIR "data/contents.epub" << "epub" << false << false;
QTest::newRow("jpg") << KDESRCDIR "data/potato.jpg" << "jpg" << false << true;
}
void PartTest::testSaveAsUndoStackAnnotations()
{
QFETCH(QString, file);
QFETCH(QString, extension);
QFETCH(bool, nativelySupportsAnnotations);
QFETCH(bool, canSwapBackingFile);
QFETCH(bool, saveToArchive);
const Part::SaveAsFlag saveFlags = saveToArchive ? Part::SaveAsOkularArchive : Part::NoSaveAsFlags;
QScopedPointer<CloseDialogHelper> closeDialogHelper;
QTemporaryFile saveFile( QString( "%1/okrXXXXXX.%2" ).arg( QDir::tempPath() ).arg ( extension ) );
QVERIFY( saveFile.open() );
saveFile.close();
Okular::Part part(nullptr, nullptr, QVariantList());
part.openDocument( file );
QCOMPARE(part.m_document->canSwapBackingFile(), canSwapBackingFile);
Okular::Annotation *annot = new Okular::TextAnnotation();
annot->setBoundingRectangle( Okular::NormalizedRect( 0.1, 0.1, 0.15, 0.15 ) );
annot->setContents( "annot contents" );
part.m_document->addPageAnnotation( 0, annot );
QString annotName = annot->uniqueName();
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
if (!canSwapBackingFile) {
// The undo/redo stack gets lost if you can not swap the backing file
QVERIFY( !part.m_document->canUndo() );
QVERIFY( !part.m_document->canRedo() );
return;
}
// Check we can still undo the annot add after save
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( !part.m_document->canUndo() );
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->page( 0 )->annotations().isEmpty() );
// Check we can redo the annot add after save
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( !part.m_document->canRedo() );
if ( nativelySupportsAnnotations ) {
// If the annots are provived by the backend we need to refetch the pointer after save
annot = part.m_document->page( 0 )->annotation( annotName );
QVERIFY( annot );
}
// Remove the annotation, creates another undo command
QVERIFY( part.m_document->canRemovePageAnnotation( annot ) );
part.m_document->removePageAnnotation( 0, annot );
QVERIFY( part.m_document->page( 0 )->annotations().isEmpty() );
// Check we can still undo the annot remove after save
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.m_document->canUndo() );
QCOMPARE( part.m_document->page( 0 )->annotations().count(), 1 );
// Check we can still undo the annot add after save
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( !part.m_document->canUndo() );
QVERIFY( part.m_document->page( 0 )->annotations().isEmpty() );
// Redo the add annotation
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.m_document->canUndo() );
QVERIFY( part.m_document->canRedo() );
if ( nativelySupportsAnnotations ) {
// If the annots are provived by the backend we need to refetch the pointer after save
annot = part.m_document->page( 0 )->annotation( annotName );
QVERIFY( annot );
}
// Add translate, adjust and modify commands
part.m_document->translatePageAnnotation( 0, annot, Okular::NormalizedPoint( 0.1, 0.1 ) );
part.m_document->adjustPageAnnotation( 0, annot, Okular::NormalizedPoint( 0.1, 0.1 ), Okular::NormalizedPoint( 0.1, 0.1 ) );
part.m_document->prepareToModifyAnnotationProperties( annot );
part.m_document->modifyPageAnnotationProperties( 0, annot );
// Now check we can still undo/redo/save at all the intermediate states and things still work
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.m_document->canUndo() );
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.m_document->canUndo() );
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.m_document->canUndo() );
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( !part.m_document->canUndo() );
QVERIFY( part.m_document->canRedo() );
QVERIFY( part.m_document->page( 0 )->annotations().isEmpty() );
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.m_document->canRedo() );
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.m_document->canRedo() );
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.m_document->canRedo() );
if ( !nativelySupportsAnnotations && !saveToArchive ) {
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "you're going to lose the annotations" dialog
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( !part.m_document->canRedo() );
closeDialogHelper.reset(new CloseDialogHelper( &part, QDialogButtonBox::No )); // this is the "do you want to save or discard" dialog
part.closeUrl();
}
void PartTest::testSaveAsUndoStackAnnotations_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("extension");
QTest::addColumn<bool>("nativelySupportsAnnotations");
QTest::addColumn<bool>("canSwapBackingFile");
QTest::addColumn<bool>("saveToArchive");
QTest::newRow("pdf") << KDESRCDIR "data/file1.pdf" << "pdf" << true << true << false;
QTest::newRow("epub") << KDESRCDIR "data/contents.epub" << "epub" << false << false << false;
QTest::newRow("jpg") << KDESRCDIR "data/potato.jpg" << "jpg" << false << true << false;
QTest::newRow("pdfarchive") << KDESRCDIR "data/file1.pdf" << "okular" << true << true << true;
QTest::newRow("jpgarchive") << KDESRCDIR "data/potato.jpg" << "okular" << false << true << true;
}
void PartTest::testSaveAsUndoStackForms()
{
QFETCH(QString, file);
QFETCH(QString, extension);
QFETCH(bool, saveToArchive);
const Part::SaveAsFlag saveFlags = saveToArchive ? Part::SaveAsOkularArchive : Part::NoSaveAsFlags;
QTemporaryFile saveFile( QString( "%1/okrXXXXXX.%2" ).arg( QDir::tempPath(), extension ) );
QVERIFY( saveFile.open() );
saveFile.close();
Okular::Part part(nullptr, nullptr, QVariantList());
part.openDocument( file );
for ( FormField *ff : part.m_document->page( 0 )->formFields() )
{
if ( ff->id() == 65537 )
{
QCOMPARE( ff->type(), FormField::FormText );
FormFieldText *fft = static_cast<FormFieldText *>( ff );
part.m_document->editFormText( 0, fft, "BlaBla", 6, 0, 0 );
}
else if ( ff->id() == 65538 )
{
QCOMPARE( ff->type(), FormField::FormButton );
FormFieldButton *ffb = static_cast<FormFieldButton *>( ff );
QCOMPARE( ffb->buttonType(), FormFieldButton::Radio );
part.m_document->editFormButtons( 0, QList< FormFieldButton* >() << ffb, QList< bool >() << true );
}
else if ( ff->id() == 65542 )
{
QCOMPARE( ff->type(), FormField::FormChoice );
FormFieldChoice *ffc = static_cast<FormFieldChoice *>( ff );
QCOMPARE( ffc->choiceType(), FormFieldChoice::ListBox );
part.m_document->editFormList( 0, ffc, QList< int >() << 1 );
}
else if ( ff->id() == 65543 )
{
QCOMPARE( ff->type(), FormField::FormChoice );
FormFieldChoice *ffc = static_cast<FormFieldChoice *>( ff );
QCOMPARE( ffc->choiceType(), FormFieldChoice::ComboBox );
part.m_document->editFormCombo( 0, ffc, "combo2", 3, 0, 0);
}
}
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canUndo() );
part.m_document->undo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( !part.m_document->canUndo() );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
QVERIFY( part.m_document->canRedo() );
part.m_document->redo();
QVERIFY( part.saveAs( QUrl::fromLocalFile( saveFile.fileName() ), saveFlags ) );
}
void PartTest::testSaveAsUndoStackForms_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("extension");
QTest::addColumn<bool>("saveToArchive");
QTest::newRow("pdf") << KDESRCDIR "data/formSamples.pdf" << "pdf" << false;
QTest::newRow("pdfarchive") << KDESRCDIR "data/formSamples.pdf" << "okular" << true;
}
}
int main(int argc, char *argv[])

View file

@ -114,6 +114,7 @@ struct ArchiveData
{
}
QString originalFileName;
QTemporaryFile document;
QTemporaryFile metadataFile;
};
@ -526,22 +527,22 @@ qulonglong DocumentPrivate::getFreeMemory( qulonglong *freeSwap )
#endif
}
void DocumentPrivate::loadDocumentInfo()
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;
return false;
QFile infoFile( m_xmlFileName );
loadDocumentInfo( infoFile );
return loadDocumentInfo( infoFile, loadWhat );
}
void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
bool DocumentPrivate::loadDocumentInfo( QFile &infoFile, LoadDocumentInfoFlags loadWhat )
{
if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) )
return;
return false;
// Load DOM from XML file
QDomDocument doc( QStringLiteral("documentInfo") );
@ -549,13 +550,17 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
{
qCDebug(OkularCoreDebug) << "Can't load XML pair! Check for broken xml.";
infoFile.close();
return;
return false;
}
infoFile.close();
QDomElement root = doc.documentElement();
if ( root.tagName() != QLatin1String("documentInfo") )
return;
return false;
QUrl documentUrl( root.attribute( "url" ) );
bool loadedAnything = false; // set if something gets actually loaded
// Parse the DOM tree
QDomNode topLevelNode = root.firstChild();
@ -564,7 +569,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
QString catName = topLevelNode.toElement().tagName();
// Restore page attributes (bookmark, annotations, ...) from the DOM
if ( catName == QLatin1String("pageList") )
if ( catName == QLatin1String("pageList") && ( loadWhat & LoadPageInfo ) )
{
QDomNode pageNode = topLevelNode.firstChild();
while ( pageNode.isElement() )
@ -578,14 +583,17 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
// pass the domElement to the right page, to read config data from
if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() )
m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement );
{
if ( m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement ) )
loadedAnything = true;
}
}
pageNode = pageNode.nextSibling();
}
}
// Restore 'general info' from the DOM
else if ( catName == QLatin1String("generalInfo") )
else if ( catName == QLatin1String("generalInfo") && ( loadWhat & LoadGeneralInfo ) )
{
QDomNode infoNode = topLevelNode.firstChild();
while ( infoNode.isElement() )
@ -607,6 +615,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
QString vpString = historyElement.attribute( QStringLiteral("viewport") );
m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(),
DocumentViewport( vpString ) );
loadedAnything = true;
}
historyNode = historyNode.nextSibling();
}
@ -622,6 +631,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
if ( ok && newrotation != 0 )
{
setRotationInternal( newrotation, false );
loadedAnything = true;
}
}
else if ( infoElement.tagName() == QLatin1String("views") )
@ -638,6 +648,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
if ( view->name() == viewName )
{
loadViewsInfo( view, viewElement );
loadedAnything = true;
break;
}
}
@ -651,6 +662,8 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
topLevelNode = topLevelNode.nextSibling();
} // </documentInfo>
return loadedAnything;
}
void DocumentPrivate::loadViewsInfo( View *view, const QDomElement &e )
@ -952,25 +965,6 @@ DocumentViewport DocumentPrivate::nextDocumentViewport() const
return ret;
}
void DocumentPrivate::warnLimitedAnnotSupport()
{
if ( !m_showWarningLimitedAnnotSupport )
return;
m_showWarningLimitedAnnotSupport = false; // Show the warning once
if ( m_annotationsNeedSaveAs )
{
// Shown if the user is editing annotations in a file whose metadata is
// not stored locally (.okular archives belong to this category)
KMessageBox::information( m_widget, i18n("Your annotation changes will not be saved automatically. Use File -> Save As...\nor your changes will be lost once the document is closed"), QString(), QStringLiteral("annotNeedSaveAs") );
}
else if ( !canAddAnnotationsNatively() )
{
// If the generator doesn't support native annotations
KMessageBox::information( m_widget, i18n("Your annotations are saved internally by Okular.\nYou can export the annotated document using File -> Export As -> Document Archive"), QString(), QStringLiteral("annotExportAsArchive") );
}
}
void DocumentPrivate::performAddPageAnnotation( int page, Annotation * annotation )
{
Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator );
@ -1000,8 +994,6 @@ void DocumentPrivate::performAddPageAnnotation( int page, Annotation * annotatio
// Redraw everything, including ExternallyDrawn annotations
refreshPixmaps( page );
}
warnLimitedAnnotSupport();
}
void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annotation )
@ -1038,8 +1030,6 @@ void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annota
refreshPixmaps( page );
}
}
warnLimitedAnnotSupport();
}
void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged )
@ -1080,10 +1070,6 @@ void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annota
qCDebug(OkularCoreDebug) << "Refreshing Pixmaps";
refreshPixmaps( page );
}
// If the user is moving or resizing the annotation, don't steal the focus
if ( (annotation->flags() & (Annotation::BeingMoved | Annotation::BeingResized) ) == 0 )
warnLimitedAnnotSupport();
}
void DocumentPrivate::performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber )
@ -1162,7 +1148,6 @@ void DocumentPrivate::saveDocumentInfo() const
{
qCWarning(OkularCoreDebug) << "Failed to open docdata file" << m_xmlFileName;
return;
}
// 1. Create DOM
QDomDocument doc( QStringLiteral("documentInfo") );
@ -1174,21 +1159,23 @@ void DocumentPrivate::saveDocumentInfo() const
doc.appendChild( root );
// 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
QDomElement pageList = doc.createElement( QStringLiteral("pageList") );
root.appendChild( pageList );
PageItems saveWhat = AllPageItems;
if ( m_annotationsNeedSaveAs )
// -> do this if there are not-yet-migrated annots or forms in docdata/
if ( m_docdataMigrationNeeded )
{
/* In this case, if the user makes a modification, he's requested to
* save to a new document. Therefore, if there are existing local
* annotations, we save them back unmodified in the original
* document's metadata, so that it appears that it was not changed */
saveWhat |= OriginalAnnotationPageItems;
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;
// <page list><page number='x'>.... </page> 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 );
}
// <page list><page number='x'>.... </page> 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") );
@ -2142,6 +2129,7 @@ Document::Document( QWidget *widget )
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<Okular::FontInfo>();
}
@ -2290,7 +2278,6 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
QMimeDatabase db;
QMimeType mime = _mime;
QByteArray filedata;
qint64 document_size = -1;
bool isstdin = url.fileName() == QLatin1String( "-" );
bool triedMimeFromFileContent = false;
if ( !isstdin )
@ -2298,21 +2285,11 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
if ( !mime.isValid() )
return OpenError;
// docFile is always local so we can use QFileInfo on it
QFileInfo fileReadTest( docFile );
if ( fileReadTest.isFile() && !fileReadTest.isReadable() )
{
d->m_docFileName.clear();
return OpenError;
}
// determine the related "xml document-info" filename
d->m_url = url;
d->m_docFileName = docFile;
if ( url.isLocalFile() && !d->m_archiveData )
{
document_size = fileReadTest.size();
d->m_xmlFileName = DocumentPrivate::docDataFileName(url, document_size);
}
if ( !d->updateMetadataXmlNameAndDocSize() )
return OpenError;
}
else
{
@ -2322,7 +2299,7 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
mime = db.mimeTypeForData( filedata );
if ( !mime.isValid() || mime.isDefault() )
return OpenError;
document_size = filedata.size();
d->m_docSize = filedata.size();
triedMimeFromFileContent = true;
}
@ -2428,35 +2405,30 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
connect( d->m_pageController, SIGNAL(rotationFinished(int,Okular::Page*)),
this, SLOT(rotationFinished(int,Okular::Page*)) );
bool containsExternalAnnotations = false;
foreach ( Page * p, d->m_pagesVector )
{
p->d->m_doc = d;
if ( !p->annotations().empty() )
containsExternalAnnotations = true;
}
// Be quiet while restoring local annotations
d->m_showWarningLimitedAnnotSupport = false;
d->m_annotationsNeedSaveAs = false;
d->m_metadataLoadingCompleted = false;
d->m_docdataMigrationNeeded = false;
// 2. load Additional Data (bookmarks, local annotations and metadata) about the document
if ( d->m_archiveData )
{
d->loadDocumentInfo( d->m_archiveData->metadataFile );
d->m_annotationsNeedSaveAs = true;
d->loadDocumentInfo( d->m_archiveData->metadataFile, LoadPageInfo );
d->loadDocumentInfo( LoadGeneralInfo );
}
else
{
d->loadDocumentInfo();
d->m_annotationsNeedSaveAs = ( d->canAddAnnotationsNatively() && containsExternalAnnotations );
if ( d->loadDocumentInfo( LoadPageInfo ) )
d->m_docdataMigrationNeeded = true;
d->loadDocumentInfo( LoadGeneralInfo );
}
d->m_showWarningLimitedAnnotSupport = true;
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 ) );
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);
@ -2495,7 +2467,6 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
}
AudioPlayer::instance()->d->m_currentDocument = isstdin ? QUrl() : d->m_url;
d->m_docSize = document_size;
const QStringList docScripts = d->m_generator->metaData( QStringLiteral("DocumentScripts"), QStringLiteral ( "JavaScript" ) ).toStringList();
if ( !docScripts.isEmpty() )
@ -2510,6 +2481,31 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
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()
{
@ -2618,7 +2614,7 @@ void Document::closeDocument()
d->m_rotation = Rotation0;
// send an empty list to observers (to free their data)
foreachObserver( notifySetup( QVector< Page * >(), DocumentObserver::DocumentChanged ) );
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();
@ -2662,6 +2658,7 @@ void Document::closeDocument()
AudioPlayer::instance()->d->m_currentDocument = QUrl();
d->m_undoStack->clear();
d->m_docdataMigrationNeeded = false;
}
void Document::addObserver( DocumentObserver * pObserver )
@ -2672,7 +2669,7 @@ void Document::addObserver( DocumentObserver * 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 );
pObserver->notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged | DocumentObserver::UrlChanged );
pObserver->notifyViewportChanged( false /*disables smoothMove*/ );
}
}
@ -2909,7 +2906,9 @@ QUrl Document::currentDocument() const
bool Document::isAllowed( Permission action ) const
{
if ( action == Okular::AllowNotes && !d->m_annotationEditingEnabled )
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
@ -3219,12 +3218,11 @@ void Document::requestTextPage( uint page )
void DocumentPrivate::notifyAnnotationChanges( int page )
{
int flags = DocumentObserver::Annotations;
foreachObserverD( notifyPageChanged( page, DocumentObserver::Annotations ) );
}
if ( m_annotationsNeedSaveAs )
flags |= DocumentObserver::NeedSaveAs;
foreachObserverD( notifyPageChanged( page, flags ) );
void DocumentPrivate::notifyFormChanges( int /*page*/ )
{
}
void Document::addPageAnnotation( int page, Annotation * annotation )
@ -4317,6 +4315,9 @@ QStringList Document::supportedMimeTypes() const
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);
@ -4326,6 +4327,154 @@ QStringList Document::supportedMimeTypes() const
return result;
}
bool Document::canSwapBackingFile() 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() );
return genIt->generator->hasFeature( Generator::SwapBackingFile );
}
bool Document::swapBackingFile( const QString &newFileName, const QUrl &url )
{
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() );
if ( !genIt->generator->hasFeature( Generator::SwapBackingFile ) )
return false;
// Save metadata about the file we're about to close
d->saveDocumentInfo();
qCDebug(OkularCoreDebug) << "Swapping backing file to" << newFileName;
QVector< Page * > newPagesVector;
Generator::SwapBackingFileResult result = genIt->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<QUndoCommand *>( d->m_undoStack->command( i ) );
if (OkularUndoCommand *ouc = dynamic_cast<OkularUndoCommand*>( 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 );
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();
else
d->m_undoStack->resetClean();
}
bool Document::canSaveChanges() const
{
if ( !d->m_generator )
@ -4423,31 +4572,31 @@ QByteArray Document::fontData(const FontInfo &font) const
return result;
}
Document::OpenResult Document::openDocumentArchive( const QString & docFile, const QUrl & url, const QString & password )
ArchiveData *DocumentPrivate::unpackDocumentArchive( const QString &archivePath )
{
QMimeDatabase db;
const QMimeType mime = db.mimeTypeForFile( docFile, QMimeDatabase::MatchExtension );
const QMimeType mime = db.mimeTypeForFile( archivePath, QMimeDatabase::MatchExtension );
if ( !mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) )
return OpenError;
return nullptr;
KZip okularArchive( docFile );
KZip okularArchive( archivePath );
if ( !okularArchive.open( QIODevice::ReadOnly ) )
return OpenError;
return nullptr;
const KArchiveDirectory * mainDir = okularArchive.directory();
const KArchiveEntry * mainEntry = mainDir->entry( QStringLiteral("content.xml") );
if ( !mainEntry || !mainEntry->isFile() )
return OpenError;
return nullptr;
std::unique_ptr< QIODevice > mainEntryDevice( static_cast< const KZipFileEntry * >( mainEntry )->createDevice() );
QDomDocument doc;
if ( !doc.setContent( mainEntryDevice.get() ) )
return OpenError;
return nullptr;
mainEntryDevice.reset();
QDomElement root = doc.documentElement();
if ( root.tagName() != QLatin1String("OkularArchive") )
return OpenError;
if ( root.tagName() != QLatin1String("OkularArchive") )
return nullptr;
QString documentFileName;
QString metadataFileName;
@ -4467,20 +4616,21 @@ Document::OpenResult Document::openDocumentArchive( const QString & docFile, con
}
}
if ( documentFileName.isEmpty() )
return OpenError;
return nullptr;
const KArchiveEntry * docEntry = mainDir->entry( documentFileName );
if ( !docEntry || !docEntry->isFile() )
return OpenError;
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 OpenError;
return nullptr;
archiveData->originalFileName = documentFileName;
QString tempFileName = archiveData->document.fileName();
{
std::unique_ptr< QIODevice > docEntryDevice( static_cast< const KZipFileEntry * >( docEntry )->createDevice() );
copyQIODevice( docEntryDevice.get(), &archiveData->document );
@ -4499,17 +4649,23 @@ Document::OpenResult Document::openDocumentArchive( const QString & docFile, con
}
}
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 );
d->m_archiveData = archiveData.get();
d->m_archivedFileName = documentFileName;
const OpenResult ret = openDocument( tempFileName, url, docMime, password );
if ( ret == OpenSuccess )
{
archiveData.release();
}
else
if ( ret != OpenSuccess )
{
delete d->m_archiveData;
d->m_archiveData = nullptr;
}
@ -4523,7 +4679,7 @@ bool Document::saveDocumentArchive( const QString &fileName )
/* 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_archivedFileName : d->m_url.fileName();
QString docFileName = d->m_archiveData ? d->m_archiveData->originalFileName : d->m_url.fileName();
if ( docFileName == QLatin1String( "-" ) )
return false;
@ -4564,7 +4720,8 @@ bool Document::saveDocumentArchive( const QString &fileName )
// If the generator can save annotations natively, do it
QTemporaryFile modifiedFile;
bool annotationsSavedNatively = false;
if ( d->canAddAnnotationsNatively() )
bool formsSavedNatively = false;
if ( d->canAddAnnotationsNatively() || canSaveChanges( SaveFormsCapability ) )
{
if ( !modifiedFile.open() )
return false;
@ -4575,7 +4732,8 @@ bool Document::saveDocumentArchive( const QString &fileName )
if ( saveChanges( modifiedFile.fileName(), &errorText ) )
{
docPath = modifiedFile.fileName(); // Save this instead of the original file
annotationsSavedNatively = true;
annotationsSavedNatively = d->canAddAnnotationsNatively();
formsSavedNatively = canSaveChanges( SaveFormsCapability );
}
else
{
@ -4584,8 +4742,13 @@ bool Document::saveDocumentArchive( const QString &fileName )
}
}
PageItems saveWhat = None;
if ( !annotationsSavedNatively )
saveWhat |= AnnotationPageItems;
if ( !formsSavedNatively )
saveWhat |= FormFieldPageItems;
QTemporaryFile metadataFile;
PageItems saveWhat = annotationsSavedNatively ? None : AnnotationPageItems;
if ( !d->savePageDocumentInfo( &metadataFile, saveWhat ) )
return false;
@ -4604,6 +4767,17 @@ bool Document::saveDocumentArchive( const QString &fileName )
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;
@ -4641,6 +4815,20 @@ void Document::walletDataForFile( const QString &fileName, QString *walletName,
}
}
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;

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org> *
* 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 *
@ -737,6 +740,48 @@ class OKULARCORE_EXPORT Document : public QObject
*/
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.
@ -822,6 +867,15 @@ class OKULARCORE_EXPORT Document : public QObject
*/
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.
@ -857,6 +911,25 @@ class OKULARCORE_EXPORT Document : public QObject
*/
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)
*
@ -1066,6 +1139,12 @@ class OKULARCORE_EXPORT Document : public QObject
*/
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.
*

View file

@ -74,6 +74,15 @@ struct DoContinueDirectionMatchSearchStruct
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:
@ -99,12 +108,14 @@ class DocumentPrivate
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;
@ -115,8 +126,8 @@ class DocumentPrivate
void calculateMaxTextPages();
qulonglong getTotalMemory();
qulonglong getFreeMemory( qulonglong *freeSwap = nullptr );
void loadDocumentInfo();
void loadDocumentInfo( QFile &infoFile );
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;
@ -130,13 +141,14 @@ class DocumentPrivate
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;
void warnLimitedAnnotSupport();
OKULARCORE_EXPORT static QString docDataFileName(const QUrl &url, qint64 document_size);
// Methods that implement functionality needed by undo commands
@ -273,13 +285,19 @@ class DocumentPrivate
QSet< View * > m_views;
bool m_annotationEditingEnabled;
bool m_annotationsNeedSaveAs;
bool m_annotationBeingModified; // is an annotation currently being moved or resized?
bool m_showWarningLimitedAnnotSupport;
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

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2013 Jon Mease <jon.mease@gmail.com> *
* 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 *
@ -15,6 +18,7 @@
#include "form.h"
#include "utils_p.h"
#include "page.h"
#include "page_p.h"
#include <KLocalizedString>
@ -87,6 +91,21 @@ void AddAnnotationCommand::redo()
m_done = true;
}
bool AddAnnotationCommand::refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector )
{
if ( m_done )
{
// We don't always update m_annotation because even if the annotation has been added to the document
// it can have been removed later so the annotation pointer is stored inside a following RemoveAnnotationCommand
// and thus doesn't need updating because it didn't change
// because of the document reload
auto a = newPagesVector[m_pageNumber]->annotation( m_annotation->uniqueName() );
if (a) m_annotation = a;
}
return true;
}
RemoveAnnotationCommand::RemoveAnnotationCommand(Okular::DocumentPrivate * doc, Okular::Annotation* annotation, int pageNumber)
: m_docPriv( doc ),
@ -112,12 +131,27 @@ void RemoveAnnotationCommand::undo()
m_done = false;
}
void RemoveAnnotationCommand::redo(){
void RemoveAnnotationCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performRemovePageAnnotation( m_pageNumber, m_annotation );
m_done = true;
}
bool RemoveAnnotationCommand::refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector )
{
if ( !m_done )
{
// We don't always update m_annotation because it can happen that the annotation remove has been undo
// and that annotation addition has also been undone so the the annotation pointer is stored inside
// a previous AddAnnotationCommand and thus doesn't need updating because it didn't change
// because of the document reload
auto a = newPagesVector[m_pageNumber]->annotation( m_annotation->uniqueName() );
if (a) m_annotation = a;
}
return true;
}
ModifyAnnotationPropertiesCommand::ModifyAnnotationPropertiesCommand( DocumentPrivate* docPriv,
Annotation* annotation,
@ -147,6 +181,16 @@ void ModifyAnnotationPropertiesCommand::redo()
m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true );
}
bool ModifyAnnotationPropertiesCommand::refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector )
{
// Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command
auto a = newPagesVector[m_pageNumber]->annotation( m_annotation->uniqueName() );
if (a) m_annotation = a;
return true;
}
TranslateAnnotationCommand::TranslateAnnotationCommand( DocumentPrivate* docPriv,
Annotation* annotation,
int pageNumber,
@ -212,6 +256,16 @@ Okular::NormalizedRect TranslateAnnotationCommand::translateBoundingRectangle( c
return boundingRect;
}
bool TranslateAnnotationCommand::refreshInternalPageReferences( const QVector< Page * > &newPagesVector )
{
// Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command
auto a = newPagesVector[m_pageNumber]->annotation( m_annotation->uniqueName() );
if (a) m_annotation = a;
return true;
}
AdjustAnnotationCommand::AdjustAnnotationCommand(Okular::DocumentPrivate * docPriv,
Okular::Annotation * annotation,
int pageNumber,
@ -277,6 +331,16 @@ Okular::NormalizedRect AdjustAnnotationCommand::adjustBoundingRectangle(
return Okular::NormalizedRect( left, top, right, bottom );
}
bool AdjustAnnotationCommand::refreshInternalPageReferences( const QVector< Page * > &newPagesVector )
{
// Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command
auto a = newPagesVector[m_pageNumber]->annotation( m_annotation->uniqueName() );
if (a) m_annotation = a;
return true;
}
EditTextCommand::EditTextCommand( const QString & newContents,
int newCursorPos,
const QString & prevContents,
@ -363,6 +427,7 @@ QString EditTextCommand::newContentsRightOfCursor()
return m_newContents.right(m_newContents.length() - m_newCursorPos);
}
EditAnnotationContentsCommand::EditAnnotationContentsCommand( DocumentPrivate* docPriv,
Annotation* annotation,
int pageNumber,
@ -412,6 +477,15 @@ bool EditAnnotationContentsCommand::mergeWith(const QUndoCommand* uc)
}
}
bool EditAnnotationContentsCommand::refreshInternalPageReferences( const QVector< Page * > &newPagesVector )
{
auto a = newPagesVector[m_pageNumber]->annotation( m_annotation->uniqueName() );
if (a) m_annotation = a;
return true;
}
EditFormTextCommand::EditFormTextCommand( Okular::DocumentPrivate* docPriv,
Okular::FormFieldText* form,
int pageNumber,
@ -433,6 +507,7 @@ void EditFormTextCommand::undo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setText( m_prevContents );
emit m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
m_docPriv->notifyFormChanges( m_pageNumber );
}
void EditFormTextCommand::redo()
@ -440,6 +515,7 @@ void EditFormTextCommand::redo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setText( m_newContents );
emit m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos );
m_docPriv->notifyFormChanges( m_pageNumber );
}
int EditFormTextCommand::id() const
@ -461,6 +537,14 @@ bool EditFormTextCommand::mergeWith(const QUndoCommand* uc)
}
}
bool EditFormTextCommand::refreshInternalPageReferences( const QVector< Page * > &newPagesVector )
{
m_form = dynamic_cast<FormFieldText *>(Okular::PagePrivate::findEquivalentForm( newPagesVector[m_pageNumber], m_form ));
return m_form;
}
EditFormListCommand::EditFormListCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form,
int pageNumber,
@ -480,6 +564,7 @@ void EditFormListCommand::undo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setCurrentChoices( m_prevChoices );
emit m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_prevChoices );
m_docPriv->notifyFormChanges( m_pageNumber );
}
void EditFormListCommand::redo()
@ -487,8 +572,17 @@ void EditFormListCommand::redo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setCurrentChoices( m_newChoices );
emit m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_newChoices );
m_docPriv->notifyFormChanges( m_pageNumber );
}
bool EditFormListCommand::refreshInternalPageReferences( const QVector< Page * > &newPagesVector )
{
m_form = dynamic_cast<FormFieldChoice *>(Okular::PagePrivate::findEquivalentForm( newPagesVector[m_pageNumber], m_form ));
return m_form;
}
EditFormComboCommand::EditFormComboCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form,
int pageNumber,
@ -533,6 +627,7 @@ void EditFormComboCommand::undo()
}
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
m_docPriv->notifyFormChanges( m_pageNumber );
}
void EditFormComboCommand::redo()
@ -547,6 +642,7 @@ void EditFormComboCommand::redo()
}
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos );
m_docPriv->notifyFormChanges( m_pageNumber );
}
int EditFormComboCommand::id() const
@ -573,6 +669,14 @@ bool EditFormComboCommand::mergeWith( const QUndoCommand *uc )
}
}
bool EditFormComboCommand::refreshInternalPageReferences( const QVector< Page * > &newPagesVector )
{
m_form = dynamic_cast<FormFieldChoice *>(Okular::PagePrivate::findEquivalentForm( newPagesVector[m_pageNumber], m_form ));
return m_form;
}
EditFormButtonsCommand::EditFormButtonsCommand( Okular::DocumentPrivate* docPriv,
int pageNumber,
const QList< FormFieldButton* > & formButtons,
@ -603,6 +707,7 @@ void EditFormButtonsCommand::undo()
Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons );
moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons );
m_docPriv->notifyFormChanges( m_pageNumber );
}
void EditFormButtonsCommand::redo()
@ -618,6 +723,22 @@ void EditFormButtonsCommand::redo()
Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons );
moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons );
m_docPriv->notifyFormChanges( m_pageNumber );
}
bool EditFormButtonsCommand::refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector )
{
const QList< FormFieldButton* > oldFormButtons = m_formButtons;
m_formButtons.clear();
foreach( FormFieldButton* oldFormButton, oldFormButtons )
{
FormFieldButton *button = dynamic_cast<FormFieldButton *>(Okular::PagePrivate::findEquivalentForm( newPagesVector[m_pageNumber], oldFormButton ));
if ( !button )
return false;
m_formButtons << button;
}
return true;
}
void EditFormButtonsCommand::clearFormButtonStates()

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2013 Jon Mease <jon.mease@gmail.com> *
* 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 *
@ -23,8 +26,15 @@ class DocumentPrivate;
class FormFieldText;
class FormFieldButton;
class FormFieldChoice;
class Page;
class AddAnnotationCommand : public QUndoCommand
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);
@ -35,6 +45,8 @@ class AddAnnotationCommand : public QUndoCommand
void redo() override;
bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override;
private:
Okular::DocumentPrivate * m_docPriv;
Okular::Annotation* m_annotation;
@ -42,7 +54,7 @@ class AddAnnotationCommand : public QUndoCommand
bool m_done;
};
class RemoveAnnotationCommand : public QUndoCommand
class RemoveAnnotationCommand : public OkularUndoCommand
{
public:
RemoveAnnotationCommand(Okular::DocumentPrivate * doc, Okular::Annotation* annotation, int pageNumber);
@ -50,6 +62,8 @@ class RemoveAnnotationCommand : public QUndoCommand
void undo() override;
void redo() override;
bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override;
private:
Okular::DocumentPrivate * m_docPriv;
Okular::Annotation* m_annotation;
@ -57,7 +71,7 @@ class RemoveAnnotationCommand : public QUndoCommand
bool m_done;
};
class ModifyAnnotationPropertiesCommand : public QUndoCommand
class ModifyAnnotationPropertiesCommand : public OkularUndoCommand
{
public:
ModifyAnnotationPropertiesCommand( Okular::DocumentPrivate* docPriv, Okular::Annotation* annotation,
@ -68,6 +82,8 @@ class ModifyAnnotationPropertiesCommand : public QUndoCommand
void undo() override;
void redo() override;
bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override;
private:
Okular::DocumentPrivate * m_docPriv;
Okular::Annotation* m_annotation;
@ -76,7 +92,7 @@ class ModifyAnnotationPropertiesCommand : public QUndoCommand
QDomNode m_newProperties;
};
class TranslateAnnotationCommand : public QUndoCommand
class TranslateAnnotationCommand : public OkularUndoCommand
{
public:
TranslateAnnotationCommand(Okular::DocumentPrivate* docPriv,
@ -92,6 +108,8 @@ class TranslateAnnotationCommand : public QUndoCommand
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;
@ -100,7 +118,7 @@ class TranslateAnnotationCommand : public QUndoCommand
bool m_completeDrag;
};
class AdjustAnnotationCommand : public QUndoCommand
class AdjustAnnotationCommand : public OkularUndoCommand
{
public:
AdjustAnnotationCommand(Okular::DocumentPrivate * docPriv,
@ -117,6 +135,8 @@ class AdjustAnnotationCommand : public QUndoCommand
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;
@ -126,7 +146,7 @@ class AdjustAnnotationCommand : public QUndoCommand
bool m_completeDrag;
};
class EditTextCommand : public QUndoCommand
class EditTextCommand : public OkularUndoCommand
{
public:
EditTextCommand( const QString & newContents,
@ -182,6 +202,8 @@ class EditAnnotationContentsCommand : public EditTextCommand
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;
@ -203,13 +225,16 @@ class EditFormTextCommand : public EditTextCommand
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 QUndoCommand
class EditFormListCommand : public OkularUndoCommand
{
public:
EditFormListCommand( Okular::DocumentPrivate* docPriv,
@ -222,6 +247,8 @@ class EditFormListCommand : public QUndoCommand
void undo() override;
void redo() override;
bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override;
private:
Okular::DocumentPrivate* m_docPriv;
FormFieldChoice* m_form;
@ -248,6 +275,8 @@ class EditFormComboCommand : public EditTextCommand
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;
@ -256,7 +285,7 @@ class EditFormComboCommand : public EditTextCommand
int m_prevIndex;
};
class EditFormButtonsCommand : public QUndoCommand
class EditFormButtonsCommand : public OkularUndoCommand
{
public:
EditFormButtonsCommand( Okular::DocumentPrivate* docPriv,
@ -268,6 +297,8 @@ class EditFormButtonsCommand : public QUndoCommand
void undo() override;
void redo() override;
bool refreshInternalPageReferences( const QVector< Okular::Page * > &newPagesVector ) override;
private:
void clearFormButtonStates();

View file

@ -203,6 +203,11 @@ Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArr
return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError;
}
Generator::SwapBackingFileResult Generator::swapBackingFile( QString const &/*newFileName */, QVector<Okular::Page*> & /*newPagesVector*/ )
{
return SwapBackingFileError;
}
bool Generator::closeDocument()
{
Q_D( Generator );

View file

@ -211,7 +211,8 @@ class OKULARCORE_EXPORT Generator : public QObject
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)
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
};
/**
@ -272,6 +273,27 @@ class OKULARCORE_EXPORT Generator : public QObject
*/
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<Okular::Page*> & newPagesVector );
/**
* This method is called when the document is closed and not used

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2005 by Albert Astals Cid <aacid@kde.org> *
* 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 *
@ -45,7 +48,7 @@ class OKULARCORE_EXPORT DocumentObserver
TextSelection = 8, ///< Text selection has been changed
Annotations = 16, ///< Annotations have been changed
BoundingBox = 32, ///< Bounding boxes have been changed
NeedSaveAs = 64 ///< Set along with Annotations when Save As is needed or annotation changes will be lost @since 0.15 (KDE 4.9)
NeedSaveAs = 64 ///< Set when "Save" is needed or annotation/form changes will be lost @since 0.15 (KDE 4.9) @deprecated
};
/**
@ -53,7 +56,8 @@ class OKULARCORE_EXPORT DocumentObserver
*/
enum SetupFlags {
DocumentChanged = 1, ///< The document is a new document.
NewLayoutForPages = 2 ///< All the pages have
NewLayoutForPages = 2, ///< All the pages have
UrlChanged = 4 ///< The URL has changed @since 1.3
};
/**

View file

@ -139,14 +139,17 @@ Page::Page( uint page, double w, double h, Rotation o )
Page::~Page()
{
deletePixmaps();
deleteRects();
d->deleteHighlights();
deleteAnnotations();
d->deleteTextSelections();
deleteSourceReferences();
if (d)
{
deletePixmaps();
deleteRects();
d->deleteHighlights();
deleteAnnotations();
d->deleteTextSelections();
deleteSourceReferences();
delete d;
delete d;
}
}
int Page::number() const
@ -496,6 +499,16 @@ 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 )
@ -801,8 +814,10 @@ void Page::deleteAnnotations()
m_annotations.clear();
}
void PagePrivate::restoreLocalContents( const QDomNode & pageNode )
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() )
@ -837,6 +852,7 @@ void PagePrivate::restoreLocalContents( const QDomNode & pageNode )
{
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.";
@ -848,6 +864,10 @@ void PagePrivate::restoreLocalContents( const QDomNode & pageNode )
// 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;
@ -880,9 +900,12 @@ void PagePrivate::restoreLocalContents( const QDomNode & pageNode )
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
@ -943,7 +966,17 @@ void PagePrivate::saveLocalContents( QDomNode & parentNode, QDomDocument & docum
}
// add forms info if has got any
if ( ( what & FormFieldPageItems ) && !formfields.isEmpty() )
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") );
@ -1032,3 +1065,59 @@ void PagePrivate::setTilesManager( const DocumentObserver *observer, TilesManage
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;
}

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
* 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 *
@ -249,6 +252,12 @@ class OKULARCORE_EXPORT 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.
@ -386,7 +395,7 @@ class OKULARCORE_EXPORT Page
QList<Tile> tilesAt( const DocumentObserver *observer, const NormalizedRect &rect ) const;
private:
PagePrivate* const d;
PagePrivate* d;
/// @cond PRIVATE
friend class PagePrivate;
friend class Document;

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2007 by Pino Toscano <pino@kde.org> *
* 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 *
@ -48,7 +51,11 @@ enum PageItem
/* If set along with AnnotationPageItems, tells saveLocalContents to save
* the original annotations (if any) instead of the modified ones */
OriginalAnnotationPageItems = 0x100
OriginalAnnotationPageItems = 0x100,
/* If set along with FormFieldPageItems, tells saveLocalContents to save
* the original form contents (if any) instead of the modified one */
OriginalFormFieldPageItems = 0x200
};
Q_DECLARE_FLAGS(PageItems, PageItem)
@ -66,7 +73,7 @@ class PagePrivate
/**
* Loads the local contents (e.g. annotations) of the page.
*/
void restoreLocalContents( const QDomNode & pageNode );
bool restoreLocalContents( const QDomNode & pageNode );
/**
* Saves the local contents (e.g. annotations) of the page.
@ -116,6 +123,17 @@ class PagePrivate
*/
void setTilesManager( const DocumentObserver *observer, TilesManager *tm );
/**
* Moves contents that are generated from oldPage to this. And clears them from page
* so it can be deleted fine.
*/
void adoptGeneratedContents( PagePrivate *oldPage );
/*
* Tries to find an equivalent form field to oldField by looking into the rect, type and name
*/
OKULARCORE_EXPORT static FormField *findEquivalentForm( const Page *p, FormField *oldField );
class PixmapObject
{
public:
@ -144,6 +162,7 @@ class PagePrivate
bool m_isBoundingBoxKnown : 1;
QDomDocument restoredLocalAnnotationList; // <annotationList>...</annotationList>
QDomDocument restoredFormFieldList; // <forms>...</forms>
};
}

View file

@ -438,9 +438,6 @@ Context menu actions like Rename Bookmarks etc.)
<title>Annotations</title>
<para>
&okular; allows you to review and annotate your documents.
Annotations created in &okular; are automatically saved in the internal local data folder
for each user.
&okular; does not implicitly change any document it opens.
</para>
<screenshot>
<screeninfo>&okular;'s Annotations</screeninfo>
@ -460,22 +457,11 @@ Context menu actions like Rename Bookmarks etc.)
<para>Using the context menu either in the <guilabel>Reviews</guilabel> view of the navigation panel or in the main window you can open a <guilabel>Pop up Note</guilabel> for any kind of annotation and add or edit comments.</para>
<para>Annotations are not only limited to &PDF; files, they can be used for any format &okular; supports.</para>
<para>
Since &kde; 4.2, &okular; has the "document archiving" feature. This is an &okular;-specific format for carrying the document plus various metadata related to it (currently only annotations). You can save a "document archive" from the open document by choosing <menuchoice><guimenu>File</guimenu><guisubmenu>Export As</guisubmenu><guimenuitem>Document Archive</guimenuitem></menuchoice>. To open an &okular; document archive, just open it with &okular; as it would be &eg; a &PDF; document.
&okular; has the "document archiving" feature. This is an &okular;-specific format for carrying the document plus various metadata related to it (currently only annotations). You can save a "document archive" from the open document by choosing <menuchoice><guimenu>File</guimenu><guisubmenu>Save As</guisubmenu></menuchoice> and selecting <guilabel>Okular document archive</guilabel> in the <guilabel>Filter</guilabel> selector. To open an &okular; document archive, just open it with &okular; as it would be &eg; a &PDF; document.
</para>
<para>
Since &okular; 0.15 you can <emphasis>also</emphasis> save annotations directly into &PDF; files. This feature is only available if &okular; has been built with version 0.20 or later of <ulink url="http://poppler.freedesktop.org/">Poppler rendering library</ulink>. You can use <menuchoice><guimenu>File</guimenu> <guimenuitem>Save As...</guimenuitem></menuchoice> to save the copy of &PDF; file with annotations.
You can <emphasis>also</emphasis> save annotations directly into &PDF; files. You can use <menuchoice><guimenu>File</guimenu> <guimenuitem>Save</guimenuitem></menuchoice> to save it over the current file or <menuchoice><guimenu>File</guimenu> <guimenuitem>Save As...</guimenuitem></menuchoice> to save it to a new file.
</para>
<note>
<para>
It is not possible to save annotations into &PDF; file if original file was encrypted and &okular; uses Poppler libraries of version which is lower than 0.22.
</para>
</note>
<note>
<para>
If you open a &PDF; with existing annotations, your annotation changes are not automatically saved in the internal local data folder, and you need to save the modified document (using <menuchoice><guimenu>File</guimenu><guimenuitem>Save As...</guimenuitem></menuchoice>) before closing it. Should you forget to do this &okular; will show confirmation window that allows you to save the document.
</para>
</note>
<note>
<para>
Due to DRM limitations (typically with &PDF; documents), adding, editing some properties
@ -488,7 +474,7 @@ Context menu actions like Rename Bookmarks etc.)
</para>
</note>
<para>
Since &okular; 0.17 you can configure the default properties and appearance of each annotating tool. Please refer to the <link linkend="configannotations">corresponding section in this documentation</link>.
You can configure the default properties and appearance of each annotating tool. Please refer to the <link linkend="configannotations">corresponding section in this documentation</link>.
</para>
<sect2 id="annotations-add">
<title>Adding annotations</title>
@ -954,7 +940,7 @@ Context menu actions like Rename Bookmarks etc.)
</term>
<listitem>
<para>
<action>Open</action> a supported file or &okular; archive. If there is already an opened file it will be closed. For more information, see the section about <link linkend="opening">Opening Files</link>.
<action>Open</action> a supported file or &okular; document archive. If there is already an opened file it will be closed. For more information, see the section about <link linkend="opening">Opening Files</link>.
</para>
</listitem>
</varlistentry>
@ -1010,31 +996,31 @@ Context menu actions like Rename Bookmarks etc.)
<keycombo action="simul">&Ctrl;<keycap>S</keycap></keycombo>
</shortcut>
<guimenu>File</guimenu>
<guimenuitem>Save As...</guimenuitem>
<guimenuitem>Save</guimenuitem>
</menuchoice>
</term>
<listitem>
<para><action>Saves</action> the document under a new name including all the changes (annotations, form contents, &etc;), provided the document backend supports saving changes. With the &PDF; backend it is possible to save the document with the changed values of the form fields.<!--Only useful for pdf? what happens with other formats/backends?--> It can be possible (provided that the data were not secured using DRM) to save annotations with &PDF; files.</para>
<note>
<para>
Note that, due to the way this is implemented, even if there are no changes to the file, the new file need not to be an exact bit-for-bit copy of the original file (&eg; can have a different SHA-1 hash, &etc;).
</para>
</note>
<para><action>Saves</action> the document including all the changes (annotations, form contents, &etc;), provided the document backend supports saving those changes. If the backend does not support saving the changes, the user will be given the option to either discard or to save them together with the document in an &okular; document archive.</para>
</listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term>
<menuchoice>
<shortcut>
<keycombo action="simul">&Ctrl;&Shift;<keycap>S</keycap></keycombo>
</shortcut>
<guimenu>File</guimenu>
<guimenuitem>Save Copy As...</guimenuitem>
<guimenuitem>Save As...</guimenuitem>
</menuchoice>
</term>
<listitem>
<para><action>Saves</action> a copy of the original document under a new name (completely bypassing the document backend). The saved document will be a bit-for-bit copy of the original.</para>
<para><action>Saves</action> the document under a new name including all the changes (annotations, form contents, &etc;), provided the document backend supports saving changes. If the backend does not support saving the changes, the user will be given the option to either discard or to save them together with the document in an &okular; document archive.</para>
<note>
<para>
Note that, due to the way this is implemented, even if there are no changes to the file, the new file need not to be an exact bit-for-bit copy of the original file (&eg; can have a different SHA-1 hash, &etc;).
</para>
</note>
</listitem>
</varlistentry>

View file

@ -2,6 +2,9 @@
* Copyright (C) 2005 by Albert Astals Cid <aacid@kde.org> *
* Copyright (C) 2006-2007 by Pino Toscano <pino@kde.org> *
* Copyright (C) 2006-2007 by Tobias Koenig <tokoe@kde.org> *
* 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 *
@ -39,6 +42,7 @@ KIMGIOGenerator::KIMGIOGenerator( QObject *parent, const QVariantList &args )
setFeature( TiledRendering );
setFeature( PrintNative );
setFeature( PrintToFile );
setFeature( SwapBackingFile );
}
KIMGIOGenerator::~KIMGIOGenerator()
@ -94,6 +98,13 @@ bool KIMGIOGenerator::loadDocumentInternal(const QByteArray & fileData, const QS
return true;
}
KIMGIOGenerator::SwapBackingFileResult KIMGIOGenerator::swapBackingFile( QString const &/*newFileName*/, QVector<Okular::Page*> & /*newPagesVector*/ )
{
// NOP: We don't actually need to do anything because all data has already
// been loaded in RAM
return SwapBackingFileNoOp;
}
bool KIMGIOGenerator::doCloseDocument()
{
m_img = QImage();

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2005 by Albert Astals Cid <aacid@kde.org> *
* 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 *
@ -27,6 +30,7 @@ class KIMGIOGenerator : public Okular::Generator
// [INHERITED] load a document and fill up the pagesVector
bool loadDocument( const QString & fileName, QVector<Okular::Page*> & pagesVector ) override;
bool loadDocumentFromData( const QByteArray & fileData, QVector<Okular::Page*> & pagesVector ) override;
SwapBackingFileResult swapBackingFile( QString const &newFileName, QVector<Okular::Page*> & newPagesVector ) override;
// [INHERITED] print document using already configured kprinter
bool print( QPrinter& printer ) override;

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2008 by Pino Toscano <pino@kde.org> *
* Copyright (C) 2012 by Guillermo A. Amaral B. <gamaral@kde.org> *
* 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 *
@ -62,8 +65,8 @@ static int maskExportedFlags(int flags)
}
//BEGIN PopplerAnnotationProxy implementation
PopplerAnnotationProxy::PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex )
: ppl_doc ( doc ), mutex ( userMutex )
PopplerAnnotationProxy::PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex, QHash<Okular::Annotation*, Poppler::Annotation*> *annotsOnOpenHash )
: ppl_doc ( doc ), mutex ( userMutex ), annotationsOnOpenHash( annotsOnOpenHash )
{
}
@ -254,6 +257,7 @@ void PopplerAnnotationProxy::notifyRemoval( Okular::Annotation *okl_ann, int pag
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;

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2012 by Fabio D'Urso <fabiodurso@hotmail.it> *
* 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 *
@ -23,7 +26,7 @@ extern Okular::Annotation* createAnnotationFromPopplerAnnotation( Poppler::Annot
class PopplerAnnotationProxy : public Okular::AnnotationProxy
{
public:
PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex );
PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex, QHash<Okular::Annotation*, Poppler::Annotation*> *annotsOnOpenHash );
~PopplerAnnotationProxy();
bool supports( Capability capability ) const override;
@ -33,6 +36,7 @@ class PopplerAnnotationProxy : public Okular::AnnotationProxy
private:
Poppler::Document *ppl_doc;
QMutex *mutex;
QHash<Okular::Annotation*, Poppler::Annotation*> *annotationsOnOpenHash;
};
#endif

View file

@ -516,6 +516,7 @@ PDFGenerator::PDFGenerator( QObject *parent, const QVariantList &args )
setFeature( PrintToFile );
setFeature( ReadRawData );
setFeature( TiledRendering );
setFeature( SwapBackingFile );
// 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
@ -582,7 +583,7 @@ Okular::Document::OpenResult PDFGenerator::init(QVector<Okular::Page*> & pagesVe
pagesVector.resize(pageCount);
rectsGenerated.fill(false, pageCount);
annotationsHash.clear();
annotationsOnOpenHash.clear();
loadPages(pagesVector, 0, false);
@ -590,12 +591,22 @@ Okular::Document::OpenResult PDFGenerator::init(QVector<Okular::Page*> & pagesVe
reparseConfig();
// create annotation proxy
annotProxy = new PopplerAnnotationProxy( pdfdoc, userMutex() );
annotProxy = new PopplerAnnotationProxy( pdfdoc, userMutex(), &annotationsOnOpenHash );
// the file has been loaded correctly
return Okular::Document::OpenSuccess;
}
PDFGenerator::SwapBackingFileResult PDFGenerator::swapBackingFile( QString const &newFileName, QVector<Okular::Page*> & newPagesVector )
{
doCloseDocument();
auto openResult = loadDocumentWithPassword(newFileName, newPagesVector, QString());
if (openResult != Okular::Document::OpenSuccess)
return SwapBackingFileError;
return SwapBackingFileReloadInternalData;
}
bool PDFGenerator::doCloseDocument()
{
// remove internal objects
@ -1059,8 +1070,8 @@ void PDFGenerator::resolveMediaLinkReference( Okular::Action *action )
if ( (action->actionType() != Okular::Action::Movie) && (action->actionType() != Okular::Action::Rendition) )
return;
resolveMediaLinks<Poppler::LinkMovie, Okular::MovieAction, Poppler::MovieAnnotation, Okular::MovieAnnotation>( action, Okular::Annotation::AMovie, annotationsHash );
resolveMediaLinks<Poppler::LinkRendition, Okular::RenditionAction, Poppler::ScreenAnnotation, Okular::ScreenAnnotation>( action, Okular::Annotation::AScreen, annotationsHash );
resolveMediaLinks<Poppler::LinkMovie, Okular::MovieAction, Poppler::MovieAnnotation, Okular::MovieAnnotation>( action, Okular::Annotation::AMovie, annotationsOnOpenHash );
resolveMediaLinks<Poppler::LinkRendition, Okular::RenditionAction, Poppler::ScreenAnnotation, Okular::ScreenAnnotation>( action, Okular::Annotation::AScreen, annotationsOnOpenHash );
}
void PDFGenerator::resolveMediaLinkReferences( Okular::Page *page )
@ -1617,7 +1628,7 @@ void PDFGenerator::addAnnotations( Poppler::Page * popplerPage, Okular::Page * p
}
if ( !doDelete )
annotationsHash.insert( newann, a );
annotationsOnOpenHash.insert( newann, a );
}
if ( doDelete )
delete a;
@ -1770,6 +1781,18 @@ bool PDFGenerator::save( const QString &fileName, SaveOptions options, QString *
pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges );
QMutexLocker locker( userMutex() );
QHashIterator<Okular::Annotation*, Poppler::Annotation*> it( annotationsOnOpenHash );
while ( it.hasNext() )
{
it.next();
if ( it.value()->uniqueName().isEmpty() )
{
it.value()->setUniqueName( it.key()->uniqueName() );
}
}
bool success = pdfConv->convert();
if (!success)
{

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org> *
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
* 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 *
@ -99,6 +102,7 @@ class PDFGenerator : public Okular::Generator, public Okular::ConfigInterface, p
Okular::AnnotationProxy* annotationProxy() const override;
protected:
SwapBackingFileResult swapBackingFile( QString const &newFileName, QVector<Okular::Page*> & newPagesVector ) override;
bool doCloseDocument() override;
Okular::TextPage* textPage( Okular::Page *page ) override;
@ -136,7 +140,9 @@ class PDFGenerator : public Okular::Generator, public Okular::ConfigInterface, p
mutable QList<Okular::EmbeddedFile*> docEmbeddedFiles;
int nextFontPage;
PopplerAnnotationProxy *annotProxy;
QHash<Okular::Annotation*, Poppler::Annotation*> annotationsHash;
// the hash below only contains annotations that were present on the file at open time
// this is enough for what we use it for
QHash<Okular::Annotation*, Poppler::Annotation*> annotationsOnOpenHash;
QBitArray rectsGenerated;

618
part.cpp
View file

@ -14,6 +14,9 @@
* Copyright (C) 2004 by Waldo Bastian <bastian@kde.org> *
* Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org> *
* Copyright (C) 2004 by Antti Markus <antti.markus@starman.ee> *
* 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 *
@ -300,7 +303,7 @@ Part::Part(QWidget *parentWidget,
QObject *parent,
const QVariantList &args)
: KParts::ReadWritePart(parent),
m_tempfile( nullptr ), m_isReloading( false ), m_fileWasRemoved( false ), m_showMenuBarAction( nullptr ), m_showFullScreenAction( nullptr ), m_actionsSearched( false ),
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_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
@ -389,6 +392,13 @@ m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentW
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()) );
@ -465,6 +475,12 @@ m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentW
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 <b>no longer supported</b>.<br/>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 );
@ -554,9 +570,10 @@ m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentW
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, &Part::slotDoFileDirty );
connect( m_dirtyHandler, &QTimer::timeout, this, [this] { slotAttemptReload(); } );
slotNewConfig();
@ -698,7 +715,7 @@ void Part::setupViewerActions()
m_findPrev = KStandardAction::findPrev( this, SLOT(slotFindPrev()), ac );
m_findPrev->setEnabled( false );
m_saveCopyAs = nullptr;
m_save = nullptr;
m_saveAs = nullptr;
QAction * prefs = KStandardAction::preferences( this, SLOT(slotPreferences()), ac);
@ -807,15 +824,12 @@ void Part::setupActions()
m_selectAll = KStandardAction::selectAll( m_pageView, SLOT(selectAll()), ac );
m_saveCopyAs = KStandardAction::saveAs( this, SLOT(slotSaveCopyAs()), ac );
m_saveCopyAs->setText( i18n( "Save &Copy As..." ) );
ac->addAction( QStringLiteral("file_save_copy"), m_saveCopyAs );
ac->setDefaultShortcuts(m_saveCopyAs, KStandardShortcut::shortcut(KStandardShortcut::SaveAs));
m_saveCopyAs->setEnabled( false );
m_save = KStandardAction::save( this, [this] { saveFile(); }, ac );
m_save->setEnabled( false );
m_saveAs = KStandardAction::saveAs( this, SLOT(slotSaveFileAs()), ac );
ac->setDefaultShortcuts(m_saveAs, KStandardShortcut::shortcut(KStandardShortcut::Save));
m_saveAs->setEnabled( false );
m_migrationMessage->addAction( m_saveAs );
m_showLeftPanel = ac->add<KToggleAction>(QStringLiteral("show_leftpanel"));
m_showLeftPanel->setText(i18n( "Show &Navigation Panel"));
@ -847,11 +861,6 @@ void Part::setupActions()
m_exportAsMenu->addAction( m_exportAsText );
m_exportAs->setEnabled( false );
m_exportAsText->setEnabled( false );
m_exportAsDocArchive = actionForExportFormat( Okular::ExportFormat(
i18nc( "A document format, Okular-specific", "Document Archive" ),
db.mimeTypeForName( QStringLiteral("application/vnd.kde.okular-archive") ) ), m_exportAsMenu );
m_exportAsMenu->addAction( m_exportAsDocArchive );
m_exportAsDocArchive->setEnabled( false );
#if PURPOSE_FOUND
m_share = ac->addAction( QStringLiteral("file_share") );
@ -1120,7 +1129,7 @@ void Part::loadCancelled(const QString &reason)
emit setWindowCaption( QString() );
resetStartArguments();
// when m_viewportDirty.pageNumber != -1 we come from slotDoFileDirty
// 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)
@ -1178,6 +1187,11 @@ KConfigDialog * Part::slotGeneratorPreferences( )
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;
@ -1194,9 +1208,6 @@ void Part::notifyViewportChanged( bool /*smoothMove*/ )
void Part::notifyPageChanged( int page, int flags )
{
if ( flags & Okular::DocumentObserver::NeedSaveAs )
setModified();
if ( !(flags & Okular::DocumentObserver::Bookmark ) )
return;
@ -1281,12 +1292,39 @@ bool Part::slotImportPSFile()
return false;
}
static void addFileToWatcher( KDirWatch *watcher, const QString &filePath)
void Part::setFileToWatch( const QString &filePath )
{
if ( !watcher->contains( filePath ) ) watcher->addFile(filePath);
if ( !m_watchedFilePath.isEmpty() )
unsetFileToWatch();
const QFileInfo fi(filePath);
if ( !watcher->contains( fi.absolutePath() ) ) watcher->addDir(fi.absolutePath());
if ( fi.isSymLink() ) watcher->addFile( fi.readLink() );
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 )
@ -1308,6 +1346,29 @@ Document::OpenResult Part::doOpenFile( const QMimeType &mimeA, const QString &fi
*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;
}
return Document::OpenSuccess;
}
isDocumentArchive = false;
if ( uncompressOk )
{
@ -1320,6 +1381,7 @@ Document::OpenResult Part::doOpenFile( const QMimeType &mimeA, const QString &fi
{
openResult = m_document->openDocument( fileNameToOpen, url(), mime );
}
m_documentOpenWithPassword = false;
// if the file didn't open correctly it might be encrypted, so ask for a pass
QString walletName, walletFolder, walletKey;
@ -1384,10 +1446,15 @@ Document::OpenResult Part::doOpenFile( const QMimeType &mimeA, const QString &fi
openResult = m_document->openDocument( fileNameToOpen, url(), mime, password );
}
// 3. if the password is correct and the user chose to remember it, store it to the wallet
if ( openResult == Document::OpenSuccess && wallet && /*safety check*/ wallet->isOpen() && keep )
if ( openResult == Document::OpenSuccess )
{
wallet->writePassword( walletKey, password );
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 );
}
}
}
}
@ -1452,14 +1519,15 @@ bool Part::openFile()
m_find->setEnabled( ok && canSearch );
m_findNext->setEnabled( ok && canSearch );
m_findPrev->setEnabled( ok && canSearch );
if( m_saveAs ) m_saveAs->setEnabled( ok && (m_document->canSaveChanges() || isDocumentArchive) );
if( m_saveCopyAs ) m_saveCopyAs->setEnabled( ok );
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 )
@ -1516,7 +1584,6 @@ bool Part::openFile()
#endif
}
if ( m_exportAsText ) m_exportAsText->setEnabled( ok && m_document->canExportToText() );
if ( m_exportAsDocArchive ) m_exportAsDocArchive->setEnabled( ok );
if ( m_exportAs ) m_exportAs->setEnabled( ok );
#if PURPOSE_FOUND
if ( m_share ) m_share->setEnabled( ok );
@ -1538,9 +1605,7 @@ bool Part::openFile()
// set the file to the fileWatcher
if ( url().isLocalFile() )
{
addFileToWatcher( m_watcher, localFilePath() );
}
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 )
@ -1578,8 +1643,17 @@ bool Part::openFile()
return true;
}
bool Part::openUrl(const QUrl &_url)
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;
// Close current document if any
if ( !closeUrl() )
return false;
@ -1630,15 +1704,15 @@ bool Part::queryClose()
return true;
const int res = KMessageBox::warningYesNoCancel( widget(),
i18n( "Do you want to save your annotation changes or discard them?" ),
i18n( "Do you want to save your changes to \"%1\" or discard them?", url().fileName() ),
i18n( "Close Document" ),
KStandardGuiItem::saveAs(),
KStandardGuiItem::save(),
KStandardGuiItem::discard() );
switch ( res )
{
case KMessageBox::Yes: // Save as
slotSaveFileAs();
case KMessageBox::Yes: // Save
saveFile();
return !isModified(); // Only allow closing if file was really saved
case KMessageBox::No: // Discard
return true;
@ -1652,7 +1726,14 @@ bool Part::closeUrl(bool promptToSave)
if ( promptToSave && !queryClose() )
return false;
setModified( 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())
{
@ -1665,21 +1746,20 @@ bool Part::closeUrl(bool promptToSave)
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 );
if( m_saveCopyAs ) m_saveCopyAs->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 );
if ( m_exportAsDocArchive ) m_exportAsDocArchive->setEnabled( false );
m_exportFormats.clear();
if ( m_exportAs )
{
QMenu *menu = m_exportAs->menu();
QList<QAction*> acts = menu->actions();
int num = acts.count();
for ( int i = 2; i < num; ++i )
for ( int i = 1; i < num; ++i )
{
menu->removeAction( acts.at(i) );
delete acts.at(i);
@ -1697,12 +1777,7 @@ bool Part::closeUrl(bool promptToSave)
emit enablePrintAction(false);
m_realUrl = QUrl();
if ( url().isLocalFile() )
{
m_watcher->removeFile( localFilePath() );
QFileInfo fi(localFilePath());
m_watcher->removeDir( fi.absolutePath() );
if ( fi.isSymLink() ) m_watcher->removeFile( fi.readLink() );
}
unsetFileToWatch();
m_fileWasRemoved = false;
if ( m_generatorGuiClient )
factory()->removeClient( m_generatorGuiClient );
@ -1714,6 +1789,7 @@ bool Part::closeUrl(bool promptToSave)
if ( widget() )
{
m_searchWidget->clearText();
m_migrationMessage->setVisible( false );
m_topMessage->setVisible( false );
m_formsMessage->setVisible( false );
}
@ -1802,8 +1878,8 @@ void Part::slotFileDirty( const QString& path )
else if (m_fileWasRemoved && QFile::exists(localFilePath()))
{
// we need to watch the new file
m_watcher->removeFile(localFilePath());
m_watcher->addFile(localFilePath());
unsetFileToWatch();
setFileToWatch( localFilePath() );
m_dirtyHandler->start( 750 );
}
}
@ -1817,12 +1893,12 @@ void Part::slotFileDirty( const QString& path )
}
}
void Part::slotDoFileDirty()
// 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;
return false;
}
QScopedValueRollback<bool> rollback(m_isReloading, true);
@ -1832,7 +1908,7 @@ void Part::slotDoFileDirty()
if ( m_viewportDirty.pageNumber == -1 )
{
// store the url of the current document
m_oldUrl = url();
m_oldUrl = newUrl.isEmpty() ? url() : newUrl;
// store the current viewport
m_viewportDirty = m_document->viewport();
@ -1866,7 +1942,7 @@ void Part::slotDoFileDirty()
{
m_toc->rollbackReload();
}
return;
return false;
}
if ( tocReloadPrepared )
@ -1875,6 +1951,8 @@ void Part::slotDoFileDirty()
// 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
@ -1899,13 +1977,17 @@ void Part::slotDoFileDirty()
}
if (m_wasPresentationOpen) slotShowPresentation();
emit enablePrintAction(true && m_document->printingSupport() != Okular::Document::NoPrinting);
reloadSucceeded = true;
}
else
else if ( !oneShot )
{
// start watching the file again (since we dropped it on close)
addFileToWatcher( m_watcher, localFilePath() );
// start watching the file again (since we dropped it on close)
setFileToWatch( localFilePath() );
m_dirtyHandler->start( 750 );
}
return reloadSucceeded;
}
@ -2312,56 +2394,80 @@ void Part::slotFindPrev()
bool Part::saveFile()
{
qCDebug(OkularUiDebug) << "Okular part doesn't support saving the file in the location from which it was opened";
return false;
if ( !isModified() )
return true;
else
return saveAs( url() );
}
void Part::slotSaveFileAs()
bool Part::slotSaveFileAs( bool showOkularArchiveAsDefaultFormat )
{
if ( m_embedMode == PrintPreviewMode )
return;
return false;
/* Show a warning before saving if the generator can't save annotations,
* unless we are going to save a .okular archive. */
if ( !isDocumentArchive && !m_document->canSaveChanges( Document::SaveAnnotationsCapability ) )
// 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 )
{
bool hasUserAcceptedReload = false;
if ( m_documentOpenWithPassword )
{
/* Search local annotations */
bool containsLocalAnnotations = false;
const int pagecount = m_document->pages();
const int res = KMessageBox::warningYesNo( widget(),
i18n( "The current document is protected with a password.<br />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.<br />Do you want to continue?" ),
i18n( "Save - Warning" ) );
for ( int pageno = 0; pageno < pagecount; ++pageno )
switch ( res )
{
const Okular::Page *page = m_document->page( pageno );
foreach ( const Okular::Annotation *ann, page->annotations() )
{
if ( !(ann->flags() & Okular::Annotation::External) )
{
containsLocalAnnotations = true;
break;
}
}
if ( containsLocalAnnotations )
case KMessageBox::Yes:
hasUserAcceptedReload = true;
// do nothing
break;
}
/* Don't show it if there are no local annotations */
if ( containsLocalAnnotations )
{
int res = KMessageBox::warningContinueCancel( widget(), i18n("Your annotations will not be exported.\nYou can export the annotated document using File -> Export As -> Document Archive") );
if ( res != KMessageBox::Continue )
return; // Canceled
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;
}
}
QUrl saveUrl = QFileDialog::getSaveFileUrl( widget(), QString(), url() );
if ( !saveUrl.isValid() || saveUrl.isEmpty() )
return;
bool setModifiedAfterSave = false;
saveAs( saveUrl );
}
bool Part::saveAs( const QUrl & saveUrl )
{
QTemporaryFile tf;
QString fileName;
if ( !tf.open() )
@ -2372,85 +2478,283 @@ bool Part::saveAs( const QUrl & saveUrl )
fileName = tf.fileName();
tf.close();
QString errorText;
bool saved;
QScopedPointer<QTemporaryFile> tempFile;
KIO::Job *copyJob = nullptr; // this will be filled with the job that writes to saveUrl
if ( isDocumentArchive )
saved = m_document->saveDocumentArchive( fileName );
else
saved = m_document->saveChanges( fileName, &errorText );
if ( !saved )
// Does the user want a .okular archive?
if ( flags & SaveAsOkularArchive )
{
if (errorText.isEmpty())
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.<br />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;
}
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 );
}
KIO::Job *copyJob = KIO::file_copy( QUrl::fromLocalFile(fileName), saveUrl, -1, KIO::Overwrite );
KJobWidgets::setWindow(copyJob, widget());
if ( !copyJob->exec() )
else
{
KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", saveUrl.toDisplayString() ) );
return false;
}
bool wontSaveForms, wontSaveAnnotations;
checkNativeSaveDataLoss(&wontSaveForms, &wontSaveAnnotations);
setModified( false );
return true;
}
void Part::slotSaveCopyAs()
{
if ( m_embedMode == PrintPreviewMode )
return;
QUrl saveUrl = QFileDialog::getSaveFileUrl( widget(), QString(), url());
if ( saveUrl.isValid() && !saveUrl.isEmpty() )
{
// make use of the already downloaded (in case of remote URLs) file,
// no point in downloading that again
QUrl srcUrl = QUrl::fromLocalFile( localFilePath() );
QTemporaryFile * tempFile = nullptr;
// duh, our local file disappeared...
if ( !QFile::exists( localFilePath() ) )
// 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 ( url().isLocalFile() )
if ( saveUrl == url() )
{
#ifdef OKULAR_KEEP_FILE_OPEN
// local file: try to get it back from the open handle on it
if ( ( tempFile = m_keeper->copyToTemporary() ) )
srcUrl = QUrl::fromLocalFile( 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;
#endif
// 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 <i>Okular document archive</i> 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
{
// we still have the original remote URL of the document,
// so copy the document from there
srcUrl = url();
// 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 <i>Okular document archive</i> format to preserve them. Click <i>Continue</i> 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 <i>Okular document archive</i> format to preserve them. Click <i>Continue</i> 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;
}
}
}
KIO::Job *copyJob = KIO::file_copy( srcUrl, saveUrl, -1, KIO::Overwrite );
KJobWidgets::setWindow(copyJob, widget());
if ( !copyJob->exec() )
KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", saveUrl.toDisplayString() ) );
if ( m_document->canSaveChanges() )
{
// If the generator supports saving changes, save them
delete tempFile;
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 )
{
// 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;
}
}
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() );
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()
{
@ -2763,9 +3067,6 @@ void Part::slotExportAs(QAction * act)
case 0:
mimeType = mimeDatabase.mimeTypeForName(QStringLiteral("text/plain"));
break;
case 1:
mimeType = mimeDatabase.mimeTypeForName(QStringLiteral("application/vnd.kde.okular-archive"));
break;
default:
mimeType = m_exportFormats.at( id - 2 ).mimeType();
break;
@ -2782,11 +3083,8 @@ void Part::slotExportAs(QAction * act)
case 0:
saved = m_document->exportToText( fileName );
break;
case 1:
saved = m_document->saveDocumentArchive( fileName );
break;
default:
saved = m_document->exportTo( fileName, m_exportFormats.at( id - 2 ) );
saved = m_document->exportTo( fileName, m_exportFormats.at( id - 1 ) );
break;
}
if ( !saved )
@ -2801,7 +3099,7 @@ void Part::slotReload()
// auto-refresh system
m_dirtyHandler->stop();
slotDoFileDirty();
slotAttemptReload();
}

29
part.h
View file

@ -6,6 +6,9 @@
* Copyright (C) 2003 by Laurent Montel <montel@kde.org> *
* Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> *
* Copyright (C) 2004-2007 by Albert Astals Cid <aacid@kde.org> *
* 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 *
@ -179,7 +182,6 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
void guiActivateEvent(KParts::GUIActivateEvent *event) override;
void displayInfoMessage( const QString &message, KMessageWidget::MessageType messageType = KMessageWidget::Information, int duration = -1 );
public:
bool saveFile() override;
bool queryClose() override;
bool closeUrl() override;
bool closeUrl(bool promptToSave) override;
@ -202,8 +204,7 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
void slotNextBookmark();
void slotFindNext();
void slotFindPrev();
void slotSaveFileAs();
void slotSaveCopyAs();
bool slotSaveFileAs(bool showOkularArchiveAsDefaultFormat = false);
void slotGetNewStuff();
void slotNewConfig();
void slotShowMenu(const Okular::Page *page, const QPoint &point);
@ -234,10 +235,11 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
void enableLayers( bool enable );
public Q_SLOTS:
bool saveFile() override;
// connected to Shell action (and browserExtension), not local one
void slotPrint();
void slotFileDirty( const QString& );
void slotDoFileDirty();
bool slotAttemptReload( bool oneShot = false, const QUrl &newUrl = QUrl() );
void psTransformEnded(int, QProcess::ExitStatus);
KConfigDialog * slotGeneratorPreferences();
@ -252,6 +254,7 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
void showMenu(const Okular::Page *page, const QPoint &point, const QString &bookmarkTitle = QString(), const Okular::DocumentViewport &vp = DocumentViewport());
bool eventFilter(QObject * watched, QEvent * event) override;
Document::OpenResult doOpenFile(const QMimeType &mime, const QString &fileNameToOpen, bool *isCompressedFile);
bool openUrl( const QUrl &url, bool swapInsteadOfOpening );
void setupViewerActions();
void setViewerShortcuts();
@ -266,6 +269,19 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
void slotRenameBookmark( const DocumentViewport &viewport );
void slotRemoveBookmark( const DocumentViewport &viewport );
void resetStartArguments();
void checkNativeSaveDataLoss(bool *out_wontSaveForms, bool *out_wontSaveAnnotations) const;
enum SaveAsFlag
{
NoSaveAsFlags = 0, ///< No options
SaveAsOkularArchive = 1 ///< Save as Okular Archive (.okular) instead of document's native format
};
Q_DECLARE_FLAGS( SaveAsFlags, SaveAsFlag )
bool saveAs( const QUrl & saveUrl, SaveAsFlags flags );
void setFileToWatch( const QString &filePath );
void unsetFileToWatch();
#if PURPOSE_FOUND
void slotShareActionFinished(const QJsonObject &output, int error, const QString &message);
@ -279,11 +295,14 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
Okular::Document * m_document;
QString m_temporaryLocalFile;
bool isDocumentArchive;
bool m_documentOpenWithPassword;
bool m_swapInsteadOfOpening; // if set, the next open operation will replace the backing file (used when reloading just saved files)
// main widgets
Sidebar *m_sidebar;
SearchWidget *m_searchWidget;
FindBar * m_findBar;
KMessageWidget * m_migrationMessage;
KMessageWidget * m_topMessage;
KMessageWidget * m_formsMessage;
KMessageWidget * m_infoMessage;
@ -303,6 +322,7 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
// document watcher (and reloader) variables
KDirWatch *m_watcher;
QString m_watchedFilePath, m_watchedFileSymlinkTarget;
QTimer *m_dirtyHandler;
QUrl m_oldUrl;
Okular::DocumentViewport m_viewportDirty;
@ -334,6 +354,7 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
QAction *m_find;
QAction *m_findNext;
QAction *m_findPrev;
QAction *m_save;
QAction *m_saveAs;
QAction *m_saveCopyAs;
QAction *m_printPreview;

View file

@ -1,11 +1,11 @@
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="okular_part" version="37">
<kpartgui name="okular_part" version="38">
<MenuBar>
<Menu name="file"><text>&amp;File</text>
<Action name="get_new_stuff" group="file_open"/>
<Action name="import_ps" group="file_open"/>
<Action name="file_save" group="file_save"/>
<Action name="file_save_as" group="file_save"/>
<Action name="file_save_copy" group="file_save"/>
<Action name="file_reload" group="file_save"/>
<Action name="file_print" group="file_print"/>
<Action name="file_print_preview" group="file_print"/>

View file

@ -10,6 +10,9 @@
* Copyright (C) 2003 by Malcolm Hunter <malcolm.hunter@gmx.co.uk> *
* Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> *
* Copyright (C) 2004 by Dirk Mueller <mueller@kde.org> *
* 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 *
@ -265,8 +268,8 @@ void Shell::openUrl( const QUrl & url, const QString &serializedOptions )
else
{
Shell* newShell = new Shell( serializedOptions );
newShell->openUrl( url, serializedOptions );
newShell->show();
newShell->openUrl( url, serializedOptions );
}
}
}
@ -495,6 +498,28 @@ void Shell::setFullScreen( bool useFullScreen )
setWindowState( windowState() & ~Qt::WindowFullScreen ); // reset
}
void Shell::setCaption( const QString &caption )
{
bool modified = false;
const int activeTab = m_tabWidget->currentIndex();
if ( activeTab >= 0 && activeTab < m_tabs.size() )
{
KParts::ReadWritePart* const activePart = m_tabs[activeTab].part;
QString tabCaption = activePart->url().fileName();
if ( activePart->isModified() ) {
modified = true;
if ( !tabCaption.isEmpty() ) {
tabCaption.append( QStringLiteral( " *" ) );
}
}
m_tabWidget->setTabText( activeTab, tabCaption );
}
setCaption( caption, modified );
}
void Shell::showEvent(QShowEvent *e)
{
if (!menuBar()->isNativeMenuBar() && m_showMenuBarAction)

View file

@ -99,6 +99,10 @@ protected:
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;

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2006 by Pino Toscano <pino@kde.org> *
* 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 *
@ -105,10 +108,32 @@ AnnotationModelPrivate::~AnnotationModelPrivate()
delete root;
}
static void updateAnnotationPointer( AnnItem *item, const QVector< Okular::Page * > &pages )
{
if ( item->annotation ) {
item->annotation = pages[ item->page ]->annotation( item->annotation->uniqueName() );
if ( !item->annotation )
qWarning() << "Lost annotation on document save, something went wrong";
}
foreach ( AnnItem *child, item->children )
updateAnnotationPointer( child, pages );
}
void AnnotationModelPrivate::notifySetup( const QVector< Okular::Page * > &pages, int setupFlags )
{
if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) )
{
if ( setupFlags & Okular::DocumentObserver::UrlChanged )
{
// Here with UrlChanged and no document changed it means we
// need to update all the Annotation* 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
updateAnnotationPointer( root, pages );
}
return;
}
q->beginResetModel();
qDeleteAll( root->children );

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2006 by Chu Xiaodong <xiaodongchu@gmail.com> *
* Copyright (C) 2006 by Pino Toscano <pino@kde.org> *
* 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 *
@ -241,6 +244,16 @@ AnnotWindow::~AnnotWindow()
delete m_latexRenderer;
}
Okular::Annotation * AnnotWindow::annotation() const
{
return m_annot;
}
void AnnotWindow::updateAnnotation( Okular::Annotation * a )
{
m_annot = a;
}
void AnnotWindow::reloadInfo()
{
const QColor newcolor = m_annot->style().color().isValid() ? m_annot->style().color() : Qt::yellow;
@ -256,6 +269,11 @@ void AnnotWindow::reloadInfo()
m_title->setDate( m_annot->modificationDate() );
}
int AnnotWindow::pageNumber() const
{
return m_page;
}
void AnnotWindow::showEvent( QShowEvent * event )
{
QFrame::showEvent( event );

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2006 by Chu Xiaodong <xiaodongchu@gmail.com> *
* Copyright (C) 2006 by Pino Toscano <pino@kde.org> *
* 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 *
@ -36,6 +39,11 @@ class AnnotWindow : public QFrame
void reloadInfo();
Okular::Annotation * annotation() const;
int pageNumber() const;
void updateAnnotation( Okular::Annotation * a );
private:
MovableTitle * m_title;
KTextEdit *textEdit;

View file

@ -169,7 +169,7 @@ BookmarkList::~BookmarkList()
void BookmarkList::notifySetup( const QVector< Okular::Page * > & pages, int setupFlags )
{
Q_UNUSED( pages );
if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) )
if ( !( setupFlags & Okular::DocumentObserver::UrlChanged ) )
return;
// clear contents

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2007 by Pino Toscano <pino@kde.org> *
* 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 *
@ -78,14 +81,20 @@ void FormWidgetsController::signalAction( Okular::Action *a )
emit action( a );
}
QButtonGroup* FormWidgetsController::registerRadioButton( QAbstractButton *button, Okular::FormFieldButton *formButton )
void FormWidgetsController::registerRadioButton( FormWidgetIface *fwButton, Okular::FormFieldButton *formButton )
{
if ( !fwButton )
return;
QAbstractButton *button = dynamic_cast<QAbstractButton *>(fwButton);
if ( !button )
return nullptr;
{
qWarning() << "fwButton is not a QAbstractButton" << fwButton;
return;
}
QList< RadioData >::iterator it = m_radios.begin(), itEnd = m_radios.end();
const int id = formButton->id();
m_formButtons.insert( id, formButton );
m_buttons.insert( id, button );
for ( ; it != itEnd; ++it )
{
@ -95,7 +104,7 @@ QButtonGroup* FormWidgetsController::registerRadioButton( QAbstractButton *butto
qCDebug(OkularUiDebug) << "Adding id" << id << "To group including" << (*it).ids;
(*it).group->addButton( button );
(*it).group->setId( button, id );
return (*it).group;
return;
}
}
@ -115,7 +124,6 @@ QButtonGroup* FormWidgetsController::registerRadioButton( QAbstractButton *butto
connect( newdata.group, SIGNAL( buttonClicked(QAbstractButton* ) ),
this, SLOT( slotButtonClicked( QAbstractButton* ) ) );
m_radios.append( newdata );
return newdata.group;
}
void FormWidgetsController::dropRadioButtons()
@ -127,7 +135,6 @@ void FormWidgetsController::dropRadioButtons()
}
m_radios.clear();
m_buttons.clear();
m_formButtons.clear();
}
bool FormWidgetsController::canUndo()
@ -147,7 +154,8 @@ void FormWidgetsController::slotButtonClicked( QAbstractButton *button )
{
// Checkboxes need to be uncheckable so if clicking a checked one
// disable the exclusive status temporarily and uncheck it
if (m_formButtons[check->formField()->id()]->state()) {
Okular::FormFieldButton *formButton = static_cast<Okular::FormFieldButton *>( check->formField() );
if ( formButton->state() ) {
const bool wasExclusive = button->group()->exclusive();
button->group()->setExclusive(false);
check->setChecked(false);
@ -168,9 +176,9 @@ void FormWidgetsController::slotButtonClicked( QAbstractButton *button )
foreach ( QAbstractButton* button, buttons )
{
checked.append( button->isChecked() );
int id = button->group()->id( button );
formButtons.append( m_formButtons[id] );
prevChecked.append( m_formButtons[id]->state() );
Okular::FormFieldButton *formButton = static_cast<Okular::FormFieldButton *>( dynamic_cast<FormWidgetIface*>(button)->formField() );
formButtons.append( formButton );
prevChecked.append( formButton->state() );
}
if (checked != prevChecked)
emit formButtonsChangedByWidget( pageNumber, formButtons, checked );
@ -259,9 +267,11 @@ FormWidgetIface * FormWidgetFactory::createWidget( Okular::FormField * ff, QWidg
}
FormWidgetIface::FormWidgetIface( QWidget * w, Okular::FormField * ff )
: m_controller( nullptr ), m_widget( w ), m_ff( ff ), m_pageItem( nullptr )
FormWidgetIface::FormWidgetIface( QWidget * w, Okular::FormField * ff, bool canBeEnabled )
: m_controller( nullptr ), m_ff( ff ), m_widget( w ), m_pageItem( nullptr ),
m_canBeEnabled( canBeEnabled )
{
m_widget->setEnabled( m_canBeEnabled );
}
FormWidgetIface::~FormWidgetIface()
@ -294,10 +304,7 @@ bool FormWidgetIface::setVisibility( bool visible )
void FormWidgetIface::setCanBeFilled( bool fill )
{
if ( m_widget->isEnabled() )
{
m_widget->setEnabled( fill );
}
m_widget->setEnabled( fill && m_canBeEnabled );
}
void FormWidgetIface::setPageItem( PageViewItem *pageItem )
@ -305,6 +312,11 @@ void FormWidgetIface::setPageItem( PageViewItem *pageItem )
m_pageItem = pageItem;
}
void FormWidgetIface::setFormField( Okular::FormField *field )
{
m_ff = field;
}
Okular::FormField* FormWidgetIface::formField() const
{
return m_ff;
@ -320,17 +332,12 @@ void FormWidgetIface::setFormWidgetsController( FormWidgetsController *controlle
m_controller = controller;
}
QAbstractButton* FormWidgetIface::button()
{
return nullptr;
}
PushButtonEdit::PushButtonEdit( Okular::FormFieldButton * button, QWidget * parent )
: QPushButton( parent ), FormWidgetIface( this, button ), m_form( button )
: QPushButton( parent ), FormWidgetIface( this, button, !button->isReadOnly() )
{
setText( m_form->caption() );
setVisible( m_form->isVisible() );
setText( button->caption() );
setVisible( button->isVisible() );
setCursor( Qt::ArrowCursor );
connect( this, &QAbstractButton::clicked, this, &PushButtonEdit::slotClicked );
@ -338,70 +345,64 @@ PushButtonEdit::PushButtonEdit( Okular::FormFieldButton * button, QWidget * pare
void PushButtonEdit::slotClicked()
{
if ( m_form->activationAction() )
m_controller->signalAction( m_form->activationAction() );
if ( m_ff->activationAction() )
m_controller->signalAction( m_ff->activationAction() );
}
CheckBoxEdit::CheckBoxEdit( Okular::FormFieldButton * button, QWidget * parent )
: QCheckBox( parent ), FormWidgetIface( this, button ), m_form( button )
: QCheckBox( parent ), FormWidgetIface( this, button, !button->isReadOnly() )
{
setText( m_form->caption() );
setText( button->caption() );
setVisible( m_form->isVisible() );
setVisible( button->isVisible() );
setCursor( Qt::ArrowCursor );
}
void CheckBoxEdit::setFormWidgetsController( FormWidgetsController *controller )
{
Okular::FormFieldButton *form = static_cast<Okular::FormFieldButton *>(m_ff);
FormWidgetIface::setFormWidgetsController( controller );
m_controller->registerRadioButton( button(), m_form );
setChecked( m_form->state() );
m_controller->registerRadioButton( this, form );
setChecked( form->state() );
connect( this, &QCheckBox::stateChanged, this, &CheckBoxEdit::slotStateChanged );
}
QAbstractButton* CheckBoxEdit::button()
{
return this;
}
void CheckBoxEdit::slotStateChanged( int state )
{
if ( state == Qt::Checked && m_form->activationAction() )
m_controller->signalAction( m_form->activationAction() );
Okular::FormFieldButton *form = static_cast<Okular::FormFieldButton *>(m_ff);
if ( state == Qt::Checked && form->activationAction() )
m_controller->signalAction( form->activationAction() );
}
RadioButtonEdit::RadioButtonEdit( Okular::FormFieldButton * button, QWidget * parent )
: QRadioButton( parent ), FormWidgetIface( this, button ), m_form( button )
: QRadioButton( parent ), FormWidgetIface( this, button, !button->isReadOnly() )
{
setText( m_form->caption() );
setText( button->caption() );
setVisible( m_form->isVisible() );
setVisible( button->isVisible() );
setCursor( Qt::ArrowCursor );
}
void RadioButtonEdit::setFormWidgetsController( FormWidgetsController *controller )
{
Okular::FormFieldButton *form = static_cast<Okular::FormFieldButton *>(m_ff);
FormWidgetIface::setFormWidgetsController( controller );
m_controller->registerRadioButton( button(), m_form );
setChecked( m_form->state() );
m_controller->registerRadioButton( this, form );
setChecked( form->state() );
}
QAbstractButton* RadioButtonEdit::button()
{
return this;
}
FormLineEdit::FormLineEdit( Okular::FormFieldText * text, QWidget * parent )
: QLineEdit( parent ), FormWidgetIface( this, text ), m_form( text )
: QLineEdit( parent ), FormWidgetIface( this, text, true )
{
int maxlen = m_form->maximumLength();
int maxlen = text->maximumLength();
if ( maxlen >= 0 )
setMaxLength( maxlen );
setAlignment( m_form->textAlignment() );
setText( m_form->text() );
if ( m_form->isPassword() )
setAlignment( text->textAlignment() );
setText( text->text() );
if ( text->isPassword() )
setEchoMode( QLineEdit::Password );
m_prevCursorPos = cursorPosition();
@ -410,7 +411,7 @@ FormLineEdit::FormLineEdit( Okular::FormFieldText * text, QWidget * parent )
connect( this, &QLineEdit::textEdited, this, &FormLineEdit::slotChanged );
connect( this, &QLineEdit::cursorPositionChanged, this, &FormLineEdit::slotChanged );
setVisible( m_form->isVisible() );
setVisible( text->isVisible() );
}
void FormLineEdit::setFormWidgetsController(FormWidgetsController* controller)
@ -469,12 +470,13 @@ void FormLineEdit::contextMenuEvent( QContextMenuEvent* event )
void FormLineEdit::slotChanged()
{
Okular::FormFieldText *form = static_cast<Okular::FormFieldText *>(m_ff);
QString contents = text();
int cursorPos = cursorPosition();
if ( contents != m_form->text() )
if ( contents != form->text() )
{
m_controller->formTextChangedByWidget( pageItem()->pageNumber(),
m_form,
form,
contents,
cursorPos,
m_prevCursorPos,
@ -499,7 +501,7 @@ void FormLineEdit::slotHandleTextChangedByUndoRedo( int pageNumber,
int anchorPos )
{
Q_UNUSED(pageNumber);
if ( textForm != m_form || contents == text() )
if ( textForm != m_ff || contents == text() )
{
return;
}
@ -514,12 +516,12 @@ void FormLineEdit::slotHandleTextChangedByUndoRedo( int pageNumber,
}
TextAreaEdit::TextAreaEdit( Okular::FormFieldText * text, QWidget * parent )
: KTextEdit( parent ), FormWidgetIface( this, text ), m_form( text )
: KTextEdit( parent ), FormWidgetIface( this, text, true )
{
setAcceptRichText( m_form->isRichText() );
setCheckSpellingEnabled( m_form->canBeSpellChecked() );
setAlignment( m_form->textAlignment() );
setPlainText( m_form->text() );
setAcceptRichText( text->isRichText() );
setCheckSpellingEnabled( text->canBeSpellChecked() );
setAlignment( text->textAlignment() );
setPlainText( text->text() );
setUndoRedoEnabled( false );
connect( this, &QTextEdit::textChanged, this, &TextAreaEdit::slotChanged );
@ -528,7 +530,7 @@ TextAreaEdit::TextAreaEdit( Okular::FormFieldText * text, QWidget * parent )
this, &TextAreaEdit::slotUpdateUndoAndRedoInContextMenu );
m_prevCursorPos = textCursor().position();
m_prevAnchorPos = textCursor().anchor();
setVisible( m_form->isVisible() );
setVisible( text->isVisible() );
}
bool TextAreaEdit::event( QEvent* e )
@ -589,7 +591,7 @@ void TextAreaEdit::slotHandleTextChangedByUndoRedo( int pageNumber,
int anchorPos )
{
Q_UNUSED(pageNumber);
if ( textForm != m_form )
if ( textForm != m_ff )
{
return;
}
@ -605,12 +607,13 @@ void TextAreaEdit::slotHandleTextChangedByUndoRedo( int pageNumber,
void TextAreaEdit::slotChanged()
{
Okular::FormFieldText *form = static_cast<Okular::FormFieldText *>(m_ff);
QString contents = toPlainText();
int cursorPos = textCursor().position();
if (contents != m_form->text())
if (contents != form->text())
{
m_controller->formTextChangedByWidget( pageItem()->pageNumber(),
m_form,
form,
contents,
cursorPos,
m_prevCursorPos,
@ -622,19 +625,19 @@ void TextAreaEdit::slotChanged()
FileEdit::FileEdit( Okular::FormFieldText * text, QWidget * parent )
: KUrlRequester( parent ), FormWidgetIface( this, text ), m_form( text )
: KUrlRequester( parent ), FormWidgetIface( this, text, !text->isReadOnly() )
{
setMode( KFile::File | KFile::ExistingOnly | KFile::LocalOnly );
setFilter( i18n( "*|All Files" ) );
setUrl( QUrl::fromUserInput( m_form->text() ) );
lineEdit()->setAlignment( m_form->textAlignment() );
setUrl( QUrl::fromUserInput( text->text() ) );
lineEdit()->setAlignment( text->textAlignment() );
m_prevCursorPos = lineEdit()->cursorPosition();
m_prevAnchorPos = lineEdit()->cursorPosition();
connect( this, &KUrlRequester::textChanged, this, &FileEdit::slotChanged );
connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &FileEdit::slotChanged );
setVisible( m_form->isVisible() );
setVisible( text->isVisible() );
}
void FileEdit::setFormWidgetsController( FormWidgetsController* controller )
@ -701,12 +704,14 @@ void FileEdit::slotChanged()
if ( text() != url().toLocalFile() )
this->setText( url().toLocalFile() );
Okular::FormFieldText *form = static_cast<Okular::FormFieldText *>(m_ff);
QString contents = text();
int cursorPos = lineEdit()->cursorPosition();
if (contents != m_form->text())
if (contents != form->text())
{
m_controller->formTextChangedByWidget( pageItem()->pageNumber(),
m_form,
form,
contents,
cursorPos,
m_prevCursorPos,
@ -731,7 +736,7 @@ void FileEdit::slotHandleFileChangedByUndoRedo( int pageNumber,
int anchorPos )
{
Q_UNUSED(pageNumber);
if ( form != m_form || contents == text() )
if ( form != m_ff || contents == text() )
{
return;
}
@ -746,13 +751,13 @@ void FileEdit::slotHandleFileChangedByUndoRedo( int pageNumber,
}
ListEdit::ListEdit( Okular::FormFieldChoice * choice, QWidget * parent )
: QListWidget( parent ), FormWidgetIface( this, choice ), m_form( choice )
: QListWidget( parent ), FormWidgetIface( this, choice, !choice->isReadOnly() )
{
addItems( m_form->choices() );
setSelectionMode( m_form->multiSelect() ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection );
addItems( choice->choices() );
setSelectionMode( choice->multiSelect() ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection );
setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
QList< int > selectedItems = m_form->currentChoices();
if ( m_form->multiSelect() )
QList< int > selectedItems = choice->currentChoices();
if ( choice->multiSelect() )
{
foreach ( int index, selectedItems )
if ( index >= 0 && index < count() )
@ -769,7 +774,7 @@ ListEdit::ListEdit( Okular::FormFieldChoice * choice, QWidget * parent )
connect( this, &QListWidget::itemSelectionChanged, this, &ListEdit::slotSelectionChanged );
setVisible( m_form->isVisible() );
setVisible( choice->isVisible() );
setCursor( Qt::ArrowCursor );
}
@ -787,9 +792,10 @@ void ListEdit::slotSelectionChanged()
foreach( const QListWidgetItem * item, selection )
rows.append( row( item ) );
if ( rows != m_form->currentChoices() ) {
Okular::FormFieldChoice *form = static_cast<Okular::FormFieldChoice *>(m_ff);
if ( rows != form->currentChoices() ) {
m_controller->formListChangedByWidget( pageItem()->pageNumber(),
m_form,
form,
rows );
}
}
@ -800,7 +806,7 @@ void ListEdit::slotHandleFormListChangedByUndoRedo( int pageNumber,
{
Q_UNUSED(pageNumber);
if ( m_form != listForm ) {
if ( m_ff != listForm ) {
return;
}
@ -815,24 +821,24 @@ void ListEdit::slotHandleFormListChangedByUndoRedo( int pageNumber,
}
ComboEdit::ComboEdit( Okular::FormFieldChoice * choice, QWidget * parent )
: QComboBox( parent ), FormWidgetIface( this, choice ), m_form( choice )
: QComboBox( parent ), FormWidgetIface( this, choice, !choice->isReadOnly() )
{
addItems( m_form->choices() );
addItems( choice->choices() );
setEditable( true );
setInsertPolicy( NoInsert );
lineEdit()->setReadOnly( !m_form->isEditable() );
QList< int > selectedItems = m_form->currentChoices();
lineEdit()->setReadOnly( !choice->isEditable() );
QList< int > selectedItems = choice->currentChoices();
if ( selectedItems.count() == 1 && selectedItems.at(0) >= 0 && selectedItems.at(0) < count() )
setCurrentIndex( selectedItems.at(0) );
if ( m_form->isEditable() && !m_form->editChoice().isEmpty() )
lineEdit()->setText( m_form->editChoice() );
if ( choice->isEditable() && !choice->editChoice().isEmpty() )
lineEdit()->setText( choice->editChoice() );
connect( this, SIGNAL(currentIndexChanged(int)), this, SLOT(slotValueChanged()) );
connect( this, &QComboBox::editTextChanged, this, &ComboEdit::slotValueChanged );
connect( lineEdit(), &QLineEdit::cursorPositionChanged, this, &ComboEdit::slotValueChanged );
setVisible( m_form->isVisible() );
setVisible( choice->isVisible() );
setCursor( Qt::ArrowCursor );
m_prevCursorPos = lineEdit()->cursorPosition();
m_prevAnchorPos = lineEdit()->cursorPosition();
@ -850,21 +856,23 @@ void ComboEdit::slotValueChanged()
{
const QString text = lineEdit()->text();
Okular::FormFieldChoice *form = static_cast<Okular::FormFieldChoice *>(m_ff);
QString prevText;
if ( m_form->currentChoices().isEmpty() )
if ( form->currentChoices().isEmpty() )
{
prevText = m_form->editChoice();
prevText = form->editChoice();
}
else
{
prevText = m_form->choices()[m_form->currentChoices()[0]];
prevText = form->choices()[form->currentChoices()[0]];
}
int cursorPos = lineEdit()->cursorPosition();
if ( text != prevText )
{
m_controller->formComboChangedByWidget( pageItem()->pageNumber(),
m_form,
form,
currentText(),
cursorPos,
m_prevCursorPos,
@ -891,7 +899,7 @@ void ComboEdit::slotHandleFormComboChangedByUndoRedo( int pageNumber,
{
Q_UNUSED(pageNumber);
if ( m_form != form ) {
if ( m_ff != form ) {
return;
}

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2007 by Pino Toscano <pino@kde.org> *
* 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 *
@ -56,7 +59,7 @@ class FormWidgetsController : public QObject
void signalAction( Okular::Action *action );
QButtonGroup* registerRadioButton( QAbstractButton *button, Okular::FormFieldButton *formButton );
void registerRadioButton( FormWidgetIface *fwButton, Okular::FormFieldButton *formButton );
void dropRadioButtons();
bool canUndo();
bool canRedo();
@ -123,7 +126,6 @@ class FormWidgetsController : public QObject
friend class ComboEdit;
QList< RadioData > m_radios;
QHash< int, Okular::FormFieldButton* > m_formButtons;
QHash< int, QAbstractButton* > m_buttons;
Okular::Document* m_doc;
};
@ -139,7 +141,7 @@ class FormWidgetFactory
class FormWidgetIface
{
public:
FormWidgetIface( QWidget * w, Okular::FormField * ff );
FormWidgetIface( QWidget * w, Okular::FormField * ff, bool canBeEnabled );
virtual ~FormWidgetIface();
Okular::NormalizedRect rect() const;
@ -149,19 +151,20 @@ class FormWidgetIface
void setCanBeFilled( bool fill );
void setPageItem( PageViewItem *pageItem );
Okular::FormField* formField() const;
PageViewItem* pageItem() const;
void setFormField( Okular::FormField *field );
Okular::FormField* formField() const;
virtual void setFormWidgetsController( FormWidgetsController *controller );
virtual QAbstractButton* button();
protected:
FormWidgetsController * m_controller;
Okular::FormField * m_ff;
private:
QWidget * m_widget;
Okular::FormField * m_ff;
PageViewItem * m_pageItem;
bool m_canBeEnabled;
};
@ -174,9 +177,6 @@ class PushButtonEdit : public QPushButton, public FormWidgetIface
private Q_SLOTS:
void slotClicked();
private:
Okular::FormFieldButton * m_form;
};
class CheckBoxEdit : public QCheckBox, public FormWidgetIface
@ -188,13 +188,9 @@ class CheckBoxEdit : public QCheckBox, public FormWidgetIface
// reimplemented from FormWidgetIface
void setFormWidgetsController( FormWidgetsController *controller ) override;
QAbstractButton* button() override;
private Q_SLOTS:
void slotStateChanged( int state );
private:
Okular::FormFieldButton * m_form;
};
class RadioButtonEdit : public QRadioButton, public FormWidgetIface
@ -206,10 +202,6 @@ class RadioButtonEdit : public QRadioButton, public FormWidgetIface
// reimplemented from FormWidgetIface
void setFormWidgetsController( FormWidgetsController *controller ) override;
QAbstractButton* button() override;
private:
Okular::FormFieldButton * m_form;
};
class FormLineEdit : public QLineEdit, public FormWidgetIface
@ -233,7 +225,6 @@ class FormLineEdit : public QLineEdit, public FormWidgetIface
void slotChanged();
private:
Okular::FormFieldText * m_form;
int m_prevCursorPos;
int m_prevAnchorPos;
};
@ -260,7 +251,6 @@ class TextAreaEdit : public KTextEdit, public FormWidgetIface
void slotChanged();
private:
Okular::FormFieldText * m_form;
int m_prevCursorPos;
int m_prevAnchorPos;
};
@ -286,7 +276,6 @@ class FileEdit : public KUrlRequester, public FormWidgetIface
int cursorPos,
int anchorPos );
private:
Okular::FormFieldText * m_form;
int m_prevCursorPos;
int m_prevAnchorPos;
};
@ -305,9 +294,6 @@ class ListEdit : public QListWidget, public FormWidgetIface
void slotHandleFormListChangedByUndoRedo( int pageNumber,
Okular::FormFieldChoice * listForm,
const QList< int > & choices );
private:
Okular::FormFieldChoice * m_form;
};
@ -331,7 +317,6 @@ class ComboEdit : public QComboBox, public FormWidgetIface
);
private:
Okular::FormFieldChoice * m_form;
int m_prevCursorPos;
int m_prevAnchorPos;
};

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* 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 <greven@kde.org> *
@ -81,6 +84,7 @@
#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"
@ -172,7 +176,7 @@ public:
// annotations
PageViewAnnotator * annotator;
//text annotation dialogs list
QHash< Okular::Annotation *, AnnotWindow * > m_annowindows;
QSet< AnnotWindow * > m_annowindows;
// other stuff
QTimer * delayResizeEventTimer;
bool dirtyLayout;
@ -451,7 +455,7 @@ PageView::~PageView()
// We need to assign it to a different list otherwise slotAnnotationWindowDestroyed
// will bite us and clear d->m_annowindows
QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows;
QSet< AnnotWindow * > annowindows = d->m_annowindows;
d->m_annowindows.clear();
qDeleteAll( annowindows );
@ -756,10 +760,13 @@ void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNu
// find the annot window
AnnotWindow* existWindow = nullptr;
QHash< Okular::Annotation *, AnnotWindow * >::ConstIterator it = d->m_annowindows.constFind( annotation );
if ( it != d->m_annowindows.constEnd() )
foreach(AnnotWindow *aw, d->m_annowindows)
{
existWindow = *it;
if ( aw->annotation() == annotation )
{
existWindow = aw;
break;
}
}
if ( existWindow == nullptr )
@ -767,7 +774,7 @@ void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNu
existWindow = new AnnotWindow( this, annotation, d->document, pageNumber );
connect(existWindow, &QObject::destroyed, this, &PageView::slotAnnotationWindowDestroyed);
d->m_annowindows.insert( annotation, existWindow );
d->m_annowindows << existWindow;
}
existWindow->show();
@ -775,19 +782,7 @@ void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNu
void PageView::slotAnnotationWindowDestroyed( QObject * window )
{
QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin();
QHash< Okular::Annotation*, AnnotWindow * >::Iterator itEnd = d->m_annowindows.end();
while ( it != itEnd )
{
if ( it.value() == window )
{
it = d->m_annowindows.erase( it );
}
else
{
++it;
}
}
d->m_annowindows.remove( static_cast<AnnotWindow*>( window ) );
}
void PageView::displayMessage( const QString & message, const QString & details, PageViewMessage::Icon icon, int duration )
@ -946,19 +941,132 @@ void PageView::selectAll()
}
}
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<FormWidgetIface*> 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
@ -997,42 +1105,13 @@ void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setup
w->setPageItem( item );
w->setFormWidgetsController( d->formWidgetsController() );
w->setVisibility( false );
w->setCanBeFilled( d->document->isAllowed( Okular::AllowFillForms ) );
item->formWidgets().insert( ff->id(), w );
w->setCanBeFilled( allowfillforms );
item->formWidgets().insert( w );
hasformwidgets = true;
}
}
const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations();
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();
}
}
}
createAnnotationsVideoWidgets( item, (*setIt)->annotations() );
}
// invalidate layout so relayout/repaint will happen on next viewport change
@ -1069,7 +1148,7 @@ void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setup
// We need to assign it to a different list otherwise slotAnnotationWindowDestroyed
// will bite us and clear d->m_annowindows
QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows;
QSet< AnnotWindow * > annowindows = d->m_annowindows;
d->m_annowindows.clear();
qDeleteAll( annowindows );
@ -1312,10 +1391,10 @@ void PageView::notifyPageChanged( int pageNumber, int changedFlags )
{
const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations();
const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end();
QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin();
QSet< AnnotWindow * >::Iterator it = d->m_annowindows.begin();
for ( ; it != d->m_annowindows.end(); )
{
QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, it.key() );
QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, (*it)->annotation() );
if ( annIt != annItEnd )
{
(*it)->reloadInfo();

View file

@ -1,6 +1,9 @@
/***************************************************************************
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004 by Albert Astals Cid <aacid@kde.org> *
* 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.h by: *
* Copyright (C) 2002 by Wilco Greven <greven@kde.org> *
@ -199,6 +202,8 @@ Q_OBJECT
// handle link clicked
bool mouseReleaseOverLink( const Okular::ObjectRect * rect ) const;
void createAnnotationsVideoWidgets(PageViewItem *item, const QLinkedList< Okular::Annotation * > &annotations);
// don't want to expose classes in here
class PageViewPrivate * d;

View file

@ -2,6 +2,9 @@
* Copyright (C) 2017 by Tobias Deiminger <haxtibal@t-online.de> *
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* 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 <greven@kde.org> *
@ -400,6 +403,19 @@ Qt::CursorShape MouseAnnotation::cursor() const
return Qt::ArrowCursor;
}
void MouseAnnotation::updateAnnotationPointers()
{
if (m_focusedAnnotation.annotation)
{
m_focusedAnnotation.annotation = m_document->page( m_focusedAnnotation.pageNumber )->annotation( m_focusedAnnotation.annotation->uniqueName() );
}
if (m_mouseOverAnnotation.annotation)
{
m_mouseOverAnnotation.annotation = m_document->page( m_mouseOverAnnotation.pageNumber )->annotation( m_mouseOverAnnotation.annotation->uniqueName() );
}
}
void MouseAnnotation::cancel()
{
if ( isActive() )

View file

@ -2,6 +2,9 @@
* Copyright (C) 2017 by Tobias Deiminger <haxtibal@t-online.de> *
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* 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 <greven@kde.org> *
@ -113,6 +116,9 @@ public:
Qt::CursorShape cursor() const;
// needs to be called after document save
void updateAnnotationPointers();
enum MouseAnnotationState {
StateInactive,
StateFocused,

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* 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 *
@ -51,9 +54,7 @@ PageViewItem::PageViewItem( const Okular::Page * page )
PageViewItem::~PageViewItem()
{
QHash<int, FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
for ( ; it != itEnd; ++it )
delete *it;
qDeleteAll( m_formWidgets );
qDeleteAll( m_videoWidgets );
}
@ -122,7 +123,7 @@ bool PageViewItem::isVisible() const
return m_visible;
}
QHash<int, FormWidgetIface*>& PageViewItem::formWidgets()
QSet<FormWidgetIface*>& PageViewItem::formWidgets()
{
return m_formWidgets;
}
@ -163,7 +164,7 @@ void PageViewItem::moveTo( int x, int y )
m_croppedGeometry.moveTop( y );
m_uncroppedGeometry.moveLeft( qRound( x - m_crop.left * m_uncroppedGeometry.width() ) );
m_uncroppedGeometry.moveTop( qRound( y - m_crop.top * m_uncroppedGeometry.height() ) );
QHash<int, FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
QSet<FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
for ( ; it != itEnd; ++it )
{
Okular::NormalizedRect r = (*it)->rect();
@ -200,7 +201,7 @@ bool PageViewItem::setFormWidgetsVisible( bool visible )
return false;
bool somehadfocus = false;
QHash<int, FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
QSet<FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end();
for ( ; it != itEnd; ++it )
{
bool hadfocus = (*it)->setVisibility( visible && (*it)->formField()->isVisible() );

View file

@ -1,5 +1,8 @@
/***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* 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 *
@ -47,7 +50,7 @@ class PageViewItem
int pageNumber() const;
double zoomFactor() const;
bool isVisible() const;
QHash<int, FormWidgetIface*>& formWidgets();
QSet<FormWidgetIface*>& formWidgets();
QHash< Okular::Movie *, VideoWidget * >& videoWidgets();
/* The page is cropped as follows: */
@ -87,7 +90,7 @@ class PageViewItem
QRect m_croppedGeometry;
QRect m_uncroppedGeometry;
Okular::NormalizedRect m_crop;
QHash<int, FormWidgetIface*> m_formWidgets;
QSet<FormWidgetIface*> m_formWidgets;
QHash< Okular::Movie *, VideoWidget * > m_videoWidgets;
};