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) 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 * * 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 * * it under the terms of the GNU General Public License as published by *
@ -11,9 +14,12 @@
#include <threadweaver/queue.h> #include <threadweaver/queue.h>
#include "../core/annotations.h"
#include "../core/document.h" #include "../core/document.h"
#include "../core/document_p.h"
#include "../core/generator.h" #include "../core/generator.h"
#include "../core/observer.h" #include "../core/observer.h"
#include "../core/page.h"
#include "../core/rotationjob_p.h" #include "../core/rotationjob_p.h"
#include "../settings_core.h" #include "../settings_core.h"
@ -24,6 +30,7 @@ class DocumentTest
private slots: private slots:
void testCloseDuringRotationJob(); void testCloseDuringRotationJob();
void testDocdataMigration();
}; };
// Test that we don't crash if the document is closed while a RotationJob // 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()->resume();
ThreadWeaver::Queue::instance()->finish(); ThreadWeaver::Queue::instance()->finish();
qApp->processEvents(); 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 ) QTEST_MAIN( DocumentTest )

View file

@ -1,5 +1,8 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2013 by Albert Astals Cid <aacid@kde.org> * * 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 * * 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 * * it under the terms of the GNU General Public License as published by *
@ -9,6 +12,8 @@
#include <QtTest> #include <QtTest>
#include "../core/annotations.h"
#include "../core/form.h"
#include "../core/page.h" #include "../core/page.h"
#include "../part.h" #include "../part.h"
#include "../ui/toc.h" #include "../ui/toc.h"
@ -17,6 +22,8 @@
#include <KConfigDialog> #include <KConfigDialog>
#include <QClipboard> #include <QClipboard>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollBar> #include <QScrollBar>
#include <QTemporaryDir> #include <QTemporaryDir>
#include <QTreeView> #include <QTreeView>
@ -24,6 +31,40 @@
#include <QDesktopServices> #include <QDesktopServices>
#include <QMenu> #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 namespace Okular
{ {
class PartTest class PartTest
@ -45,6 +86,12 @@ class PartTest
void testGeneratorPreferences(); void testGeneratorPreferences();
void testSelectText(); void testSelectText();
void testClickInternalLink(); void testClickInternalLink();
void testSaveAs();
void testSaveAs_data();
void testSaveAsUndoStackAnnotations();
void testSaveAsUndoStackAnnotations_data();
void testSaveAsUndoStackForms();
void testSaveAsUndoStackForms_data();
void testMouseMoveOverLinkWhileInSelectionMode(); void testMouseMoveOverLinkWhileInSelectionMode();
void testClickUrlLinkWhileInSelectionMode(); void testClickUrlLinkWhileInSelectionMode();
void testeTextSelectionOverAndAcrossLinks_data(); void testeTextSelectionOverAndAcrossLinks_data();
@ -725,6 +772,412 @@ void PartTest::simulateMouseSelection(double startX, double startY, double endX,
events.simulate(target); 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[]) int main(int argc, char *argv[])

View file

@ -114,6 +114,7 @@ struct ArchiveData
{ {
} }
QString originalFileName;
QTemporaryFile document; QTemporaryFile document;
QTemporaryFile metadataFile; QTemporaryFile metadataFile;
}; };
@ -526,22 +527,22 @@ qulonglong DocumentPrivate::getFreeMemory( qulonglong *freeSwap )
#endif #endif
} }
void DocumentPrivate::loadDocumentInfo() bool DocumentPrivate::loadDocumentInfo( LoadDocumentInfoFlags loadWhat )
// note: load data and stores it internally (document or pages). observers // note: load data and stores it internally (document or pages). observers
// are still uninitialized at this point so don't access them // are still uninitialized at this point so don't access them
{ {
//qCDebug(OkularCoreDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file."; //qCDebug(OkularCoreDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file.";
if ( m_xmlFileName.isEmpty() ) if ( m_xmlFileName.isEmpty() )
return; return false;
QFile infoFile( m_xmlFileName ); 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 ) ) if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) )
return; return false;
// Load DOM from XML file // Load DOM from XML file
QDomDocument doc( QStringLiteral("documentInfo") ); QDomDocument doc( QStringLiteral("documentInfo") );
@ -549,13 +550,17 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
{ {
qCDebug(OkularCoreDebug) << "Can't load XML pair! Check for broken xml."; qCDebug(OkularCoreDebug) << "Can't load XML pair! Check for broken xml.";
infoFile.close(); infoFile.close();
return; return false;
} }
infoFile.close(); infoFile.close();
QDomElement root = doc.documentElement(); QDomElement root = doc.documentElement();
if ( root.tagName() != QLatin1String("documentInfo") ) 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 // Parse the DOM tree
QDomNode topLevelNode = root.firstChild(); QDomNode topLevelNode = root.firstChild();
@ -564,7 +569,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
QString catName = topLevelNode.toElement().tagName(); QString catName = topLevelNode.toElement().tagName();
// Restore page attributes (bookmark, annotations, ...) from the DOM // Restore page attributes (bookmark, annotations, ...) from the DOM
if ( catName == QLatin1String("pageList") ) if ( catName == QLatin1String("pageList") && ( loadWhat & LoadPageInfo ) )
{ {
QDomNode pageNode = topLevelNode.firstChild(); QDomNode pageNode = topLevelNode.firstChild();
while ( pageNode.isElement() ) while ( pageNode.isElement() )
@ -578,14 +583,17 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
// pass the domElement to the right page, to read config data from // pass the domElement to the right page, to read config data from
if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() ) 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(); pageNode = pageNode.nextSibling();
} }
} }
// Restore 'general info' from the DOM // Restore 'general info' from the DOM
else if ( catName == QLatin1String("generalInfo") ) else if ( catName == QLatin1String("generalInfo") && ( loadWhat & LoadGeneralInfo ) )
{ {
QDomNode infoNode = topLevelNode.firstChild(); QDomNode infoNode = topLevelNode.firstChild();
while ( infoNode.isElement() ) while ( infoNode.isElement() )
@ -607,6 +615,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
QString vpString = historyElement.attribute( QStringLiteral("viewport") ); QString vpString = historyElement.attribute( QStringLiteral("viewport") );
m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(),
DocumentViewport( vpString ) ); DocumentViewport( vpString ) );
loadedAnything = true;
} }
historyNode = historyNode.nextSibling(); historyNode = historyNode.nextSibling();
} }
@ -622,6 +631,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
if ( ok && newrotation != 0 ) if ( ok && newrotation != 0 )
{ {
setRotationInternal( newrotation, false ); setRotationInternal( newrotation, false );
loadedAnything = true;
} }
} }
else if ( infoElement.tagName() == QLatin1String("views") ) else if ( infoElement.tagName() == QLatin1String("views") )
@ -638,6 +648,7 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
if ( view->name() == viewName ) if ( view->name() == viewName )
{ {
loadViewsInfo( view, viewElement ); loadViewsInfo( view, viewElement );
loadedAnything = true;
break; break;
} }
} }
@ -651,6 +662,8 @@ void DocumentPrivate::loadDocumentInfo( QFile &infoFile )
topLevelNode = topLevelNode.nextSibling(); topLevelNode = topLevelNode.nextSibling();
} // </documentInfo> } // </documentInfo>
return loadedAnything;
} }
void DocumentPrivate::loadViewsInfo( View *view, const QDomElement &e ) void DocumentPrivate::loadViewsInfo( View *view, const QDomElement &e )
@ -952,25 +965,6 @@ DocumentViewport DocumentPrivate::nextDocumentViewport() const
return ret; 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 ) void DocumentPrivate::performAddPageAnnotation( int page, Annotation * annotation )
{ {
Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); 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 // Redraw everything, including ExternallyDrawn annotations
refreshPixmaps( page ); refreshPixmaps( page );
} }
warnLimitedAnnotSupport();
} }
void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annotation ) void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annotation )
@ -1038,8 +1030,6 @@ void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annota
refreshPixmaps( page ); refreshPixmaps( page );
} }
} }
warnLimitedAnnotSupport();
} }
void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ) void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged )
@ -1080,10 +1070,6 @@ void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annota
qCDebug(OkularCoreDebug) << "Refreshing Pixmaps"; qCDebug(OkularCoreDebug) << "Refreshing Pixmaps";
refreshPixmaps( page ); 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 ) 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; qCWarning(OkularCoreDebug) << "Failed to open docdata file" << m_xmlFileName;
return; return;
} }
// 1. Create DOM // 1. Create DOM
QDomDocument doc( QStringLiteral("documentInfo") ); QDomDocument doc( QStringLiteral("documentInfo") );
@ -1174,21 +1159,23 @@ void DocumentPrivate::saveDocumentInfo() const
doc.appendChild( root ); doc.appendChild( root );
// 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM
QDomElement pageList = doc.createElement( QStringLiteral("pageList") ); // -> do this if there are not-yet-migrated annots or forms in docdata/
root.appendChild( pageList ); if ( m_docdataMigrationNeeded )
PageItems saveWhat = AllPageItems;
if ( m_annotationsNeedSaveAs )
{ {
/* In this case, if the user makes a modification, he's requested to QDomElement pageList = doc.createElement( "pageList" );
* save to a new document. Therefore, if there are existing local root.appendChild( pageList );
* annotations, we save them back unmodified in the original // OriginalAnnotationPageItems and OriginalFormFieldPageItems tell to
* document's metadata, so that it appears that it was not changed */ // store the same unmodified annotation list and form contents that we
saveWhat |= OriginalAnnotationPageItems; // 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 // 2.2. Save document info (current viewport, history, ... ) to DOM
QDomElement generalInfo = doc.createElement( QStringLiteral("generalInfo") ); QDomElement generalInfo = doc.createElement( QStringLiteral("generalInfo") );
@ -2142,6 +2129,7 @@ Document::Document( QWidget *widget )
connect( SettingsCore::self(), SIGNAL(configChanged()), this, SLOT(_o_configChanged()) ); connect( SettingsCore::self(), SIGNAL(configChanged()), this, SLOT(_o_configChanged()) );
connect(d->m_undoStack, &QUndoStack::canUndoChanged, this, &Document::canUndoChanged); connect(d->m_undoStack, &QUndoStack::canUndoChanged, this, &Document::canUndoChanged);
connect(d->m_undoStack, &QUndoStack::canRedoChanged, this, &Document::canRedoChanged); connect(d->m_undoStack, &QUndoStack::canRedoChanged, this, &Document::canRedoChanged);
connect(d->m_undoStack, &QUndoStack::cleanChanged, this, &Document::undoHistoryCleanChanged);
qRegisterMetaType<Okular::FontInfo>(); qRegisterMetaType<Okular::FontInfo>();
} }
@ -2290,7 +2278,6 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
QMimeDatabase db; QMimeDatabase db;
QMimeType mime = _mime; QMimeType mime = _mime;
QByteArray filedata; QByteArray filedata;
qint64 document_size = -1;
bool isstdin = url.fileName() == QLatin1String( "-" ); bool isstdin = url.fileName() == QLatin1String( "-" );
bool triedMimeFromFileContent = false; bool triedMimeFromFileContent = false;
if ( !isstdin ) if ( !isstdin )
@ -2298,21 +2285,11 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
if ( !mime.isValid() ) if ( !mime.isValid() )
return OpenError; 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_url = url;
d->m_docFileName = docFile; d->m_docFileName = docFile;
if ( url.isLocalFile() && !d->m_archiveData )
{ if ( !d->updateMetadataXmlNameAndDocSize() )
document_size = fileReadTest.size(); return OpenError;
d->m_xmlFileName = DocumentPrivate::docDataFileName(url, document_size);
}
} }
else else
{ {
@ -2322,7 +2299,7 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
mime = db.mimeTypeForData( filedata ); mime = db.mimeTypeForData( filedata );
if ( !mime.isValid() || mime.isDefault() ) if ( !mime.isValid() || mime.isDefault() )
return OpenError; return OpenError;
document_size = filedata.size(); d->m_docSize = filedata.size();
triedMimeFromFileContent = true; triedMimeFromFileContent = true;
} }
@ -2428,35 +2405,30 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
connect( d->m_pageController, SIGNAL(rotationFinished(int,Okular::Page*)), connect( d->m_pageController, SIGNAL(rotationFinished(int,Okular::Page*)),
this, SLOT(rotationFinished(int,Okular::Page*)) ); this, SLOT(rotationFinished(int,Okular::Page*)) );
bool containsExternalAnnotations = false;
foreach ( Page * p, d->m_pagesVector ) foreach ( Page * p, d->m_pagesVector )
{
p->d->m_doc = d; p->d->m_doc = d;
if ( !p->annotations().empty() )
containsExternalAnnotations = true;
}
// Be quiet while restoring local annotations d->m_metadataLoadingCompleted = false;
d->m_showWarningLimitedAnnotSupport = false; d->m_docdataMigrationNeeded = false;
d->m_annotationsNeedSaveAs = false;
// 2. load Additional Data (bookmarks, local annotations and metadata) about the document // 2. load Additional Data (bookmarks, local annotations and metadata) about the document
if ( d->m_archiveData ) if ( d->m_archiveData )
{ {
d->loadDocumentInfo( d->m_archiveData->metadataFile ); d->loadDocumentInfo( d->m_archiveData->metadataFile, LoadPageInfo );
d->m_annotationsNeedSaveAs = true; d->loadDocumentInfo( LoadGeneralInfo );
} }
else else
{ {
d->loadDocumentInfo(); if ( d->loadDocumentInfo( LoadPageInfo ) )
d->m_annotationsNeedSaveAs = ( d->canAddAnnotationsNatively() && containsExternalAnnotations ); d->m_docdataMigrationNeeded = true;
d->loadDocumentInfo( LoadGeneralInfo );
} }
d->m_showWarningLimitedAnnotSupport = true; d->m_metadataLoadingCompleted = true;
d->m_bookmarkManager->setUrl( d->m_url ); d->m_bookmarkManager->setUrl( d->m_url );
// 3. setup observers inernal lists and data // 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) // 4. set initial page (restoring the page saved in xml if loaded)
DocumentViewport loadedViewport = (*d->m_viewportIterator); 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; 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(); const QStringList docScripts = d->m_generator->metaData( QStringLiteral("DocumentScripts"), QStringLiteral ( "JavaScript" ) ).toStringList();
if ( !docScripts.isEmpty() ) if ( !docScripts.isEmpty() )
@ -2510,6 +2481,31 @@ Document::OpenResult Document::openDocument(const QString & docFile, const QUrl
return OpenSuccess; 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() KXMLGUIClient* Document::guiClient()
{ {
@ -2618,7 +2614,7 @@ void Document::closeDocument()
d->m_rotation = Rotation0; d->m_rotation = Rotation0;
// send an empty list to observers (to free their data) // 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 // delete pages and clear 'd->m_pagesVector' container
QVector< Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); QVector< Page * >::const_iterator pIt = d->m_pagesVector.constBegin();
@ -2662,6 +2658,7 @@ void Document::closeDocument()
AudioPlayer::instance()->d->m_currentDocument = QUrl(); AudioPlayer::instance()->d->m_currentDocument = QUrl();
d->m_undoStack->clear(); d->m_undoStack->clear();
d->m_docdataMigrationNeeded = false;
} }
void Document::addObserver( DocumentObserver * pObserver ) 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 the observer is added while a document is already opened, tell it
if ( !d->m_pagesVector.isEmpty() ) 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*/ ); pObserver->notifyViewportChanged( false /*disables smoothMove*/ );
} }
} }
@ -2909,7 +2906,9 @@ QUrl Document::currentDocument() const
bool Document::isAllowed( Permission action ) 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; return false;
#if !OKULAR_FORCE_DRM #if !OKULAR_FORCE_DRM
@ -3219,12 +3218,11 @@ void Document::requestTextPage( uint page )
void DocumentPrivate::notifyAnnotationChanges( int page ) void DocumentPrivate::notifyAnnotationChanges( int page )
{ {
int flags = DocumentObserver::Annotations; foreachObserverD( notifyPageChanged( page, DocumentObserver::Annotations ) );
}
if ( m_annotationsNeedSaveAs ) void DocumentPrivate::notifyFormChanges( int /*page*/ )
flags |= DocumentObserver::NeedSaveAs; {
foreachObserverD( notifyPageChanged( page, flags ) );
} }
void Document::addPageAnnotation( int page, Annotation * annotation ) void Document::addPageAnnotation( int page, Annotation * annotation )
@ -4317,6 +4315,9 @@ QStringList Document::supportedMimeTypes() const
result.append(mimeType.name()); 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, // Sorting by mimetype name doesn't make a ton of sense,
// but ensures that the list is ordered the same way every time // but ensures that the list is ordered the same way every time
qSort(result); qSort(result);
@ -4326,6 +4327,154 @@ QStringList Document::supportedMimeTypes() const
return result; 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 bool Document::canSaveChanges() const
{ {
if ( !d->m_generator ) if ( !d->m_generator )
@ -4423,31 +4572,31 @@ QByteArray Document::fontData(const FontInfo &font) const
return result; return result;
} }
Document::OpenResult Document::openDocumentArchive( const QString & docFile, const QUrl & url, const QString & password ) ArchiveData *DocumentPrivate::unpackDocumentArchive( const QString &archivePath )
{ {
QMimeDatabase db; 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") ) ) if ( !mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) )
return OpenError; return nullptr;
KZip okularArchive( docFile ); KZip okularArchive( archivePath );
if ( !okularArchive.open( QIODevice::ReadOnly ) ) if ( !okularArchive.open( QIODevice::ReadOnly ) )
return OpenError; return nullptr;
const KArchiveDirectory * mainDir = okularArchive.directory(); const KArchiveDirectory * mainDir = okularArchive.directory();
const KArchiveEntry * mainEntry = mainDir->entry( QStringLiteral("content.xml") ); const KArchiveEntry * mainEntry = mainDir->entry( QStringLiteral("content.xml") );
if ( !mainEntry || !mainEntry->isFile() ) if ( !mainEntry || !mainEntry->isFile() )
return OpenError; return nullptr;
std::unique_ptr< QIODevice > mainEntryDevice( static_cast< const KZipFileEntry * >( mainEntry )->createDevice() ); std::unique_ptr< QIODevice > mainEntryDevice( static_cast< const KZipFileEntry * >( mainEntry )->createDevice() );
QDomDocument doc; QDomDocument doc;
if ( !doc.setContent( mainEntryDevice.get() ) ) if ( !doc.setContent( mainEntryDevice.get() ) )
return OpenError; return nullptr;
mainEntryDevice.reset(); mainEntryDevice.reset();
QDomElement root = doc.documentElement(); QDomElement root = doc.documentElement();
if ( root.tagName() != QLatin1String("OkularArchive") ) if ( root.tagName() != QLatin1String("OkularArchive") )
return OpenError; return nullptr;
QString documentFileName; QString documentFileName;
QString metadataFileName; QString metadataFileName;
@ -4467,20 +4616,21 @@ Document::OpenResult Document::openDocumentArchive( const QString & docFile, con
} }
} }
if ( documentFileName.isEmpty() ) if ( documentFileName.isEmpty() )
return OpenError; return nullptr;
const KArchiveEntry * docEntry = mainDir->entry( documentFileName ); const KArchiveEntry * docEntry = mainDir->entry( documentFileName );
if ( !docEntry || !docEntry->isFile() ) if ( !docEntry || !docEntry->isFile() )
return OpenError; return nullptr;
std::unique_ptr< ArchiveData > archiveData( new ArchiveData() ); std::unique_ptr< ArchiveData > archiveData( new ArchiveData() );
const int dotPos = documentFileName.indexOf( QLatin1Char('.') ); const int dotPos = documentFileName.indexOf( QLatin1Char('.') );
if ( dotPos != -1 ) if ( dotPos != -1 )
archiveData->document.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX") + documentFileName.mid(dotPos)); archiveData->document.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX") + documentFileName.mid(dotPos));
if ( !archiveData->document.open() ) 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() ); std::unique_ptr< QIODevice > docEntryDevice( static_cast< const KZipFileEntry * >( docEntry )->createDevice() );
copyQIODevice( docEntryDevice.get(), &archiveData->document ); 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 ); 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 ); const OpenResult ret = openDocument( tempFileName, url, docMime, password );
if ( ret == OpenSuccess ) if ( ret != OpenSuccess )
{
archiveData.release();
}
else
{ {
delete d->m_archiveData;
d->m_archiveData = nullptr; 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) /* If we opened an archive, use the name of original file (eg foo.pdf)
* instead of the archive's one (eg foo.okular) */ * 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( "-" ) ) if ( docFileName == QLatin1String( "-" ) )
return false; return false;
@ -4564,7 +4720,8 @@ bool Document::saveDocumentArchive( const QString &fileName )
// If the generator can save annotations natively, do it // If the generator can save annotations natively, do it
QTemporaryFile modifiedFile; QTemporaryFile modifiedFile;
bool annotationsSavedNatively = false; bool annotationsSavedNatively = false;
if ( d->canAddAnnotationsNatively() ) bool formsSavedNatively = false;
if ( d->canAddAnnotationsNatively() || canSaveChanges( SaveFormsCapability ) )
{ {
if ( !modifiedFile.open() ) if ( !modifiedFile.open() )
return false; return false;
@ -4575,7 +4732,8 @@ bool Document::saveDocumentArchive( const QString &fileName )
if ( saveChanges( modifiedFile.fileName(), &errorText ) ) if ( saveChanges( modifiedFile.fileName(), &errorText ) )
{ {
docPath = modifiedFile.fileName(); // Save this instead of the original file docPath = modifiedFile.fileName(); // Save this instead of the original file
annotationsSavedNatively = true; annotationsSavedNatively = d->canAddAnnotationsNatively();
formsSavedNatively = canSaveChanges( SaveFormsCapability );
} }
else else
{ {
@ -4584,8 +4742,13 @@ bool Document::saveDocumentArchive( const QString &fileName )
} }
} }
PageItems saveWhat = None;
if ( !annotationsSavedNatively )
saveWhat |= AnnotationPageItems;
if ( !formsSavedNatively )
saveWhat |= FormFieldPageItems;
QTemporaryFile metadataFile; QTemporaryFile metadataFile;
PageItems saveWhat = annotationsSavedNatively ? None : AnnotationPageItems;
if ( !d->savePageDocumentInfo( &metadataFile, saveWhat ) ) if ( !d->savePageDocumentInfo( &metadataFile, saveWhat ) )
return false; return false;
@ -4604,6 +4767,17 @@ bool Document::saveDocumentArchive( const QString &fileName )
return true; 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 QPrinter::Orientation Document::orientation() const
{ {
double width, height; 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 QAbstractItemModel * Document::layersModel() const
{ {
return d->m_generator ? d->m_generator->layersModel() : nullptr; 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-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org> * * 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 * * 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 * * 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; 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 * Saving capabilities. Their availability varies according to the
* underlying generator and/or the document type. * underlying generator and/or the document type.
@ -822,6 +867,15 @@ class OKULARCORE_EXPORT Document : public QObject
*/ */
bool saveDocumentArchive( const QString &fileName ); 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 * Asks the generator to dynamically generate a SourceReference for a given
* page number and absolute X and Y position on this page. * 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; 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) * 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 ); 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. * 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; 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 class DocumentPrivate
{ {
public: public:
@ -99,12 +108,14 @@ class DocumentPrivate
m_fontsCached( false ), m_fontsCached( false ),
m_annotationEditingEnabled ( true ), m_annotationEditingEnabled ( true ),
m_annotationBeingModified( false ), m_annotationBeingModified( false ),
m_docdataMigrationNeeded( false ),
m_synctex_scanner( nullptr ) m_synctex_scanner( nullptr )
{ {
calculateMaxTextPages(); calculateMaxTextPages();
} }
// private methods // private methods
bool updateMetadataXmlNameAndDocSize();
QString pagesSizeString() const; QString pagesSizeString() const;
QString namePaperSize(double inchesWidth, double inchesHeight) const; QString namePaperSize(double inchesWidth, double inchesHeight) const;
QString localizedSize(const QSizeF &size) const; QString localizedSize(const QSizeF &size) const;
@ -115,8 +126,8 @@ class DocumentPrivate
void calculateMaxTextPages(); void calculateMaxTextPages();
qulonglong getTotalMemory(); qulonglong getTotalMemory();
qulonglong getFreeMemory( qulonglong *freeSwap = nullptr ); qulonglong getFreeMemory( qulonglong *freeSwap = nullptr );
void loadDocumentInfo(); bool loadDocumentInfo( LoadDocumentInfoFlags loadWhat );
void loadDocumentInfo( QFile &infoFile ); bool loadDocumentInfo( QFile &infoFile, LoadDocumentInfoFlags loadWhat );
void loadViewsInfo( View *view, const QDomElement &e ); void loadViewsInfo( View *view, const QDomElement &e );
void saveViewsInfo( View *view, QDomElement &e ) const; void saveViewsInfo( View *view, QDomElement &e ) const;
QUrl giveAbsoluteUrl( const QString & fileName ) const; QUrl giveAbsoluteUrl( const QString & fileName ) const;
@ -130,13 +141,14 @@ class DocumentPrivate
ConfigInterface* generatorConfig( GeneratorInfo& info ); ConfigInterface* generatorConfig( GeneratorInfo& info );
SaveInterface* generatorSave( GeneratorInfo& info ); SaveInterface* generatorSave( GeneratorInfo& info );
Document::OpenResult openDocumentInternal( const KPluginMetaData& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ); 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; bool savePageDocumentInfo( QTemporaryFile *infoFile, int what ) const;
DocumentViewport nextDocumentViewport() const; DocumentViewport nextDocumentViewport() const;
void notifyAnnotationChanges( int page ); void notifyAnnotationChanges( int page );
void notifyFormChanges( int page );
bool canAddAnnotationsNatively() const; bool canAddAnnotationsNatively() const;
bool canModifyExternalAnnotations() const; bool canModifyExternalAnnotations() const;
bool canRemoveExternalAnnotations() const; bool canRemoveExternalAnnotations() const;
void warnLimitedAnnotSupport();
OKULARCORE_EXPORT static QString docDataFileName(const QUrl &url, qint64 document_size); OKULARCORE_EXPORT static QString docDataFileName(const QUrl &url, qint64 document_size);
// Methods that implement functionality needed by undo commands // Methods that implement functionality needed by undo commands
@ -273,13 +285,19 @@ class DocumentPrivate
QSet< View * > m_views; QSet< View * > m_views;
bool m_annotationEditingEnabled; bool m_annotationEditingEnabled;
bool m_annotationsNeedSaveAs;
bool m_annotationBeingModified; // is an annotation currently being moved or resized? bool m_annotationBeingModified; // is an annotation currently being moved or resized?
bool m_showWarningLimitedAnnotSupport; bool m_metadataLoadingCompleted;
QUndoStack *m_undoStack; QUndoStack *m_undoStack;
QDomNode m_prevPropsOfAnnotBeingModified; 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; synctex_scanner_p m_synctex_scanner;
// generator selection // generator selection

View file

@ -1,5 +1,8 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2013 Jon Mease <jon.mease@gmail.com> * * 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 * * 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 * * it under the terms of the GNU General Public License as published by *
@ -15,6 +18,7 @@
#include "form.h" #include "form.h"
#include "utils_p.h" #include "utils_p.h"
#include "page.h" #include "page.h"
#include "page_p.h"
#include <KLocalizedString> #include <KLocalizedString>
@ -87,6 +91,21 @@ void AddAnnotationCommand::redo()
m_done = true; 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) RemoveAnnotationCommand::RemoveAnnotationCommand(Okular::DocumentPrivate * doc, Okular::Annotation* annotation, int pageNumber)
: m_docPriv( doc ), : m_docPriv( doc ),
@ -112,12 +131,27 @@ void RemoveAnnotationCommand::undo()
m_done = false; m_done = false;
} }
void RemoveAnnotationCommand::redo(){ void RemoveAnnotationCommand::redo()
{
moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( m_annotation->boundingRectangle(), m_docPriv, m_pageNumber );
m_docPriv->performRemovePageAnnotation( m_pageNumber, m_annotation ); m_docPriv->performRemovePageAnnotation( m_pageNumber, m_annotation );
m_done = true; 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, ModifyAnnotationPropertiesCommand::ModifyAnnotationPropertiesCommand( DocumentPrivate* docPriv,
Annotation* annotation, Annotation* annotation,
@ -147,6 +181,16 @@ void ModifyAnnotationPropertiesCommand::redo()
m_docPriv->performModifyPageAnnotation( m_pageNumber, m_annotation, true ); 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, TranslateAnnotationCommand::TranslateAnnotationCommand( DocumentPrivate* docPriv,
Annotation* annotation, Annotation* annotation,
int pageNumber, int pageNumber,
@ -212,6 +256,16 @@ Okular::NormalizedRect TranslateAnnotationCommand::translateBoundingRectangle( c
return boundingRect; 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, AdjustAnnotationCommand::AdjustAnnotationCommand(Okular::DocumentPrivate * docPriv,
Okular::Annotation * annotation, Okular::Annotation * annotation,
int pageNumber, int pageNumber,
@ -277,6 +331,16 @@ Okular::NormalizedRect AdjustAnnotationCommand::adjustBoundingRectangle(
return Okular::NormalizedRect( left, top, right, bottom ); 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, EditTextCommand::EditTextCommand( const QString & newContents,
int newCursorPos, int newCursorPos,
const QString & prevContents, const QString & prevContents,
@ -363,6 +427,7 @@ QString EditTextCommand::newContentsRightOfCursor()
return m_newContents.right(m_newContents.length() - m_newCursorPos); return m_newContents.right(m_newContents.length() - m_newCursorPos);
} }
EditAnnotationContentsCommand::EditAnnotationContentsCommand( DocumentPrivate* docPriv, EditAnnotationContentsCommand::EditAnnotationContentsCommand( DocumentPrivate* docPriv,
Annotation* annotation, Annotation* annotation,
int pageNumber, 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, EditFormTextCommand::EditFormTextCommand( Okular::DocumentPrivate* docPriv,
Okular::FormFieldText* form, Okular::FormFieldText* form,
int pageNumber, int pageNumber,
@ -433,6 +507,7 @@ void EditFormTextCommand::undo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setText( m_prevContents ); m_form->setText( m_prevContents );
emit m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos ); emit m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
m_docPriv->notifyFormChanges( m_pageNumber );
} }
void EditFormTextCommand::redo() void EditFormTextCommand::redo()
@ -440,6 +515,7 @@ void EditFormTextCommand::redo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setText( m_newContents ); m_form->setText( m_newContents );
emit m_docPriv->m_parent->formTextChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos ); 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 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, EditFormListCommand::EditFormListCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form, FormFieldChoice* form,
int pageNumber, int pageNumber,
@ -480,6 +564,7 @@ void EditFormListCommand::undo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setCurrentChoices( m_prevChoices ); m_form->setCurrentChoices( m_prevChoices );
emit m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_prevChoices ); emit m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, m_prevChoices );
m_docPriv->notifyFormChanges( m_pageNumber );
} }
void EditFormListCommand::redo() void EditFormListCommand::redo()
@ -487,8 +572,17 @@ void EditFormListCommand::redo()
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
m_form->setCurrentChoices( m_newChoices ); m_form->setCurrentChoices( m_newChoices );
emit m_docPriv->m_parent->formListChangedByUndoRedo( m_pageNumber, m_form, 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, EditFormComboCommand::EditFormComboCommand( Okular::DocumentPrivate* docPriv,
FormFieldChoice* form, FormFieldChoice* form,
int pageNumber, int pageNumber,
@ -533,6 +627,7 @@ void EditFormComboCommand::undo()
} }
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos ); emit m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos );
m_docPriv->notifyFormChanges( m_pageNumber );
} }
void EditFormComboCommand::redo() void EditFormComboCommand::redo()
@ -547,6 +642,7 @@ void EditFormComboCommand::redo()
} }
moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( m_form->rect(), m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formComboChangedByUndoRedo( m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos ); 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 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, EditFormButtonsCommand::EditFormButtonsCommand( Okular::DocumentPrivate* docPriv,
int pageNumber, int pageNumber,
const QList< FormFieldButton* > & formButtons, const QList< FormFieldButton* > & formButtons,
@ -603,6 +707,7 @@ void EditFormButtonsCommand::undo()
Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons ); Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons );
moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons ); emit m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons );
m_docPriv->notifyFormChanges( m_pageNumber );
} }
void EditFormButtonsCommand::redo() void EditFormButtonsCommand::redo()
@ -618,6 +723,22 @@ void EditFormButtonsCommand::redo()
Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons ); Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons( m_formButtons );
moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber ); moveViewportIfBoundingRectNotFullyVisible( boundingRect, m_docPriv, m_pageNumber );
emit m_docPriv->m_parent->formButtonsChangedByUndoRedo( m_pageNumber, m_formButtons ); 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() void EditFormButtonsCommand::clearFormButtonStates()

View file

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

View file

@ -203,6 +203,11 @@ Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArr
return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError; return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError;
} }
Generator::SwapBackingFileResult Generator::swapBackingFile( QString const &/*newFileName */, QVector<Okular::Page*> & /*newPagesVector*/ )
{
return SwapBackingFileError;
}
bool Generator::closeDocument() bool Generator::closeDocument()
{ {
Q_D( Generator ); 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). PrintNative, ///< Whether the Generator supports native cross-platform printing (QPainter-based).
PrintPostscript, ///< Whether the Generator supports postscript-based file printing. PrintPostscript, ///< Whether the Generator supports postscript-based file printing.
PrintToFile, ///< Whether the Generator supports export to PDF & PS through the Print Dialog 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 ); 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 * 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 Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2005 by Albert Astals Cid <aacid@kde.org> * * 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 * * 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 * * 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 TextSelection = 8, ///< Text selection has been changed
Annotations = 16, ///< Annotations have been changed Annotations = 16, ///< Annotations have been changed
BoundingBox = 32, ///< Bounding boxes 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 { enum SetupFlags {
DocumentChanged = 1, ///< The document is a new document. 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() Page::~Page()
{ {
deletePixmaps(); if (d)
deleteRects(); {
d->deleteHighlights(); deletePixmaps();
deleteAnnotations(); deleteRects();
d->deleteTextSelections(); d->deleteHighlights();
deleteSourceReferences(); deleteAnnotations();
d->deleteTextSelections();
deleteSourceReferences();
delete d; delete d;
}
} }
int Page::number() const int Page::number() const
@ -496,6 +499,16 @@ QLinkedList< Annotation* > Page::annotations() const
return m_annotations; 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 const Action * Page::pageAction( PageAction action ) const
{ {
switch ( action ) switch ( action )
@ -801,8 +814,10 @@ void Page::deleteAnnotations()
m_annotations.clear(); 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, ...) // iterate over all chilren (annotationList, ...)
QDomNode childNode = pageNode.firstChild(); QDomNode childNode = pageNode.firstChild();
while ( childNode.isElement() ) while ( childNode.isElement() )
@ -837,6 +852,7 @@ void PagePrivate::restoreLocalContents( const QDomNode & pageNode )
{ {
m_doc->performAddPageAnnotation(m_number, annotation); m_doc->performAddPageAnnotation(m_number, annotation);
qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName(); qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName();
loadedAnything = true;
} }
else else
qCWarning(OkularCoreDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML."; 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 // parse formList child element
else if ( childElement.tagName() == QLatin1String("forms") ) 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() ) if ( formfields.isEmpty() )
continue; continue;
@ -880,9 +900,12 @@ void PagePrivate::restoreLocalContents( const QDomNode & pageNode )
QString value = formElement.attribute( QStringLiteral("value") ); QString value = formElement.attribute( QStringLiteral("value") );
(*wantedIt)->d_ptr->setValue( value ); (*wantedIt)->d_ptr->setValue( value );
loadedAnything = true;
} }
} }
} }
return loadedAnything;
} }
void PagePrivate::saveLocalContents( QDomNode & parentNode, QDomDocument & document, PageItems what ) const 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 // 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 // create the formList
QDomElement formListElement = document.createElement( QStringLiteral("forms") ); QDomElement formListElement = document.createElement( QStringLiteral("forms") );
@ -1032,3 +1065,59 @@ void PagePrivate::setTilesManager( const DocumentObserver *observer, TilesManage
m_tilesManagers.insert(observer, tm); 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) 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 * * 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 * * 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; 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 * Returns the @ref Action object which is associated with the given page @p action
* or 0 if no page action is set. * 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; QList<Tile> tilesAt( const DocumentObserver *observer, const NormalizedRect &rect ) const;
private: private:
PagePrivate* const d; PagePrivate* d;
/// @cond PRIVATE /// @cond PRIVATE
friend class PagePrivate; friend class PagePrivate;
friend class Document; friend class Document;

View file

@ -1,6 +1,9 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2007 by Pino Toscano <pino@kde.org> * * 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 * * 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 * * 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 /* If set along with AnnotationPageItems, tells saveLocalContents to save
* the original annotations (if any) instead of the modified ones */ * 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) Q_DECLARE_FLAGS(PageItems, PageItem)
@ -66,7 +73,7 @@ class PagePrivate
/** /**
* Loads the local contents (e.g. annotations) of the page. * 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. * Saves the local contents (e.g. annotations) of the page.
@ -116,6 +123,17 @@ class PagePrivate
*/ */
void setTilesManager( const DocumentObserver *observer, TilesManager *tm ); 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 class PixmapObject
{ {
public: public:
@ -144,6 +162,7 @@ class PagePrivate
bool m_isBoundingBoxKnown : 1; bool m_isBoundingBoxKnown : 1;
QDomDocument restoredLocalAnnotationList; // <annotationList>...</annotationList> QDomDocument restoredLocalAnnotationList; // <annotationList>...</annotationList>
QDomDocument restoredFormFieldList; // <forms>...</forms>
}; };
} }

View file

@ -438,9 +438,6 @@ Context menu actions like Rename Bookmarks etc.)
<title>Annotations</title> <title>Annotations</title>
<para> <para>
&okular; allows you to review and annotate your documents. &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> </para>
<screenshot> <screenshot>
<screeninfo>&okular;'s Annotations</screeninfo> <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>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>Annotations are not only limited to &PDF; files, they can be used for any format &okular; supports.</para>
<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>
<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> </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> <note>
<para> <para>
Due to DRM limitations (typically with &PDF; documents), adding, editing some properties Due to DRM limitations (typically with &PDF; documents), adding, editing some properties
@ -488,7 +474,7 @@ Context menu actions like Rename Bookmarks etc.)
</para> </para>
</note> </note>
<para> <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> </para>
<sect2 id="annotations-add"> <sect2 id="annotations-add">
<title>Adding annotations</title> <title>Adding annotations</title>
@ -954,7 +940,7 @@ Context menu actions like Rename Bookmarks etc.)
</term> </term>
<listitem> <listitem>
<para> <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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -1010,31 +996,31 @@ Context menu actions like Rename Bookmarks etc.)
<keycombo action="simul">&Ctrl;<keycap>S</keycap></keycombo> <keycombo action="simul">&Ctrl;<keycap>S</keycap></keycombo>
</shortcut> </shortcut>
<guimenu>File</guimenu> <guimenu>File</guimenu>
<guimenuitem>Save As...</guimenuitem> <guimenuitem>Save</guimenuitem>
</menuchoice> </menuchoice>
</term> </term>
<listitem> <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> <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>
<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> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term> <term>
<menuchoice> <menuchoice>
<shortcut> <shortcut>
<keycombo action="simul">&Ctrl;&Shift;<keycap>S</keycap></keycombo> <keycombo action="simul">&Ctrl;&Shift;<keycap>S</keycap></keycombo>
</shortcut> </shortcut>
<guimenu>File</guimenu> <guimenu>File</guimenu>
<guimenuitem>Save Copy As...</guimenuitem> <guimenuitem>Save As...</guimenuitem>
</menuchoice> </menuchoice>
</term> </term>
<listitem> <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> </listitem>
</varlistentry> </varlistentry>

View file

@ -2,6 +2,9 @@
* Copyright (C) 2005 by Albert Astals Cid <aacid@kde.org> * * 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 Pino Toscano <pino@kde.org> *
* Copyright (C) 2006-2007 by Tobias Koenig <tokoe@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 * * 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 * * 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( TiledRendering );
setFeature( PrintNative ); setFeature( PrintNative );
setFeature( PrintToFile ); setFeature( PrintToFile );
setFeature( SwapBackingFile );
} }
KIMGIOGenerator::~KIMGIOGenerator() KIMGIOGenerator::~KIMGIOGenerator()
@ -94,6 +98,13 @@ bool KIMGIOGenerator::loadDocumentInternal(const QByteArray & fileData, const QS
return true; 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() bool KIMGIOGenerator::doCloseDocument()
{ {
m_img = QImage(); m_img = QImage();

View file

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

View file

@ -1,6 +1,9 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2008 by Pino Toscano <pino@kde.org> * * Copyright (C) 2008 by Pino Toscano <pino@kde.org> *
* Copyright (C) 2012 by Guillermo A. Amaral B. <gamaral@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 * * 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 * * 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 //BEGIN PopplerAnnotationProxy implementation
PopplerAnnotationProxy::PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex ) PopplerAnnotationProxy::PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex, QHash<Okular::Annotation*, Poppler::Annotation*> *annotsOnOpenHash )
: ppl_doc ( doc ), mutex ( userMutex ) : ppl_doc ( doc ), mutex ( userMutex ), annotationsOnOpenHash( annotsOnOpenHash )
{ {
} }
@ -254,6 +257,7 @@ void PopplerAnnotationProxy::notifyRemoval( Okular::Annotation *okl_ann, int pag
QMutexLocker ml(mutex); QMutexLocker ml(mutex);
Poppler::Page *ppl_page = ppl_doc->page( page ); Poppler::Page *ppl_page = ppl_doc->page( page );
annotationsOnOpenHash->remove( okl_ann );
ppl_page->removeAnnotation( ppl_ann ); // Also destroys ppl_ann ppl_page->removeAnnotation( ppl_ann ); // Also destroys ppl_ann
delete ppl_page; delete ppl_page;

View file

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

View file

@ -516,6 +516,7 @@ PDFGenerator::PDFGenerator( QObject *parent, const QVariantList &args )
setFeature( PrintToFile ); setFeature( PrintToFile );
setFeature( ReadRawData ); setFeature( ReadRawData );
setFeature( TiledRendering ); setFeature( TiledRendering );
setFeature( SwapBackingFile );
// You only need to do it once not for each of the documents but it is cheap enough // 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 // 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); pagesVector.resize(pageCount);
rectsGenerated.fill(false, pageCount); rectsGenerated.fill(false, pageCount);
annotationsHash.clear(); annotationsOnOpenHash.clear();
loadPages(pagesVector, 0, false); loadPages(pagesVector, 0, false);
@ -590,12 +591,22 @@ Okular::Document::OpenResult PDFGenerator::init(QVector<Okular::Page*> & pagesVe
reparseConfig(); reparseConfig();
// create annotation proxy // create annotation proxy
annotProxy = new PopplerAnnotationProxy( pdfdoc, userMutex() ); annotProxy = new PopplerAnnotationProxy( pdfdoc, userMutex(), &annotationsOnOpenHash );
// the file has been loaded correctly // the file has been loaded correctly
return Okular::Document::OpenSuccess; 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() bool PDFGenerator::doCloseDocument()
{ {
// remove internal objects // remove internal objects
@ -1059,8 +1070,8 @@ void PDFGenerator::resolveMediaLinkReference( Okular::Action *action )
if ( (action->actionType() != Okular::Action::Movie) && (action->actionType() != Okular::Action::Rendition) ) if ( (action->actionType() != Okular::Action::Movie) && (action->actionType() != Okular::Action::Rendition) )
return; return;
resolveMediaLinks<Poppler::LinkMovie, Okular::MovieAction, Poppler::MovieAnnotation, Okular::MovieAnnotation>( action, Okular::Annotation::AMovie, 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, annotationsHash ); resolveMediaLinks<Poppler::LinkRendition, Okular::RenditionAction, Poppler::ScreenAnnotation, Okular::ScreenAnnotation>( action, Okular::Annotation::AScreen, annotationsOnOpenHash );
} }
void PDFGenerator::resolveMediaLinkReferences( Okular::Page *page ) void PDFGenerator::resolveMediaLinkReferences( Okular::Page *page )
@ -1617,7 +1628,7 @@ void PDFGenerator::addAnnotations( Poppler::Page * popplerPage, Okular::Page * p
} }
if ( !doDelete ) if ( !doDelete )
annotationsHash.insert( newann, a ); annotationsOnOpenHash.insert( newann, a );
} }
if ( doDelete ) if ( doDelete )
delete a; delete a;
@ -1770,6 +1781,18 @@ bool PDFGenerator::save( const QString &fileName, SaveOptions options, QString *
pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges ); pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges );
QMutexLocker locker( userMutex() ); 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(); bool success = pdfConv->convert();
if (!success) if (!success)
{ {

View file

@ -1,6 +1,9 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org> * * Copyright (C) 2004-2008 by Albert Astals Cid <aacid@kde.org> *
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> * * 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 * * 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 * * 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; Okular::AnnotationProxy* annotationProxy() const override;
protected: protected:
SwapBackingFileResult swapBackingFile( QString const &newFileName, QVector<Okular::Page*> & newPagesVector ) override;
bool doCloseDocument() override; bool doCloseDocument() override;
Okular::TextPage* textPage( Okular::Page *page ) 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; mutable QList<Okular::EmbeddedFile*> docEmbeddedFiles;
int nextFontPage; int nextFontPage;
PopplerAnnotationProxy *annotProxy; 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; QBitArray rectsGenerated;

618
part.cpp
View file

@ -14,6 +14,9 @@
* Copyright (C) 2004 by Waldo Bastian <bastian@kde.org> * * Copyright (C) 2004 by Waldo Bastian <bastian@kde.org> *
* Copyright (C) 2004-2008 by Albert Astals Cid <aacid@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) 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 * * 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 * * it under the terms of the GNU General Public License as published by *
@ -300,7 +303,7 @@ Part::Part(QWidget *parentWidget,
QObject *parent, QObject *parent,
const QVariantList &args) const QVariantList &args)
: KParts::ReadWritePart(parent), : 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 ) 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 // 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, &Document::openUrl, this, &Part::openUrlFromDocument );
connect( m_document->bookmarkManager(), &BookmarkManager::openUrl, this, &Part::openUrlFromBookmarks ); connect( m_document->bookmarkManager(), &BookmarkManager::openUrl, this, &Part::openUrlFromBookmarks );
connect( m_document, &Document::close, this, &Part::close ); 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 ) if ( parent && parent->metaObject()->indexOfSlot( QMetaObject::normalizedSignature( "slotQuit()" ).constData() ) != -1 )
connect( m_document, SIGNAL(quit()), parent, SLOT(slotQuit()) ); 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 ); rightLayout->setSpacing( 0 );
// KToolBar * rtb = new KToolBar( rightContainer, "mainToolBarSS" ); // KToolBar * rtb = new KToolBar( rightContainer, "mainToolBarSS" );
// rightLayout->addWidget( rtb ); // 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 = new KMessageWidget( rightContainer );
m_topMessage->setVisible( false ); m_topMessage->setVisible( false );
m_topMessage->setWordWrap( true ); m_topMessage->setWordWrap( true );
@ -554,9 +570,10 @@ m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentW
m_watcher = new KDirWatch( this ); m_watcher = new KDirWatch( this );
connect( m_watcher, &KDirWatch::dirty, this, &Part::slotFileDirty ); connect( m_watcher, &KDirWatch::dirty, this, &Part::slotFileDirty );
connect( m_watcher, &KDirWatch::created, 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 = new QTimer( this );
m_dirtyHandler->setSingleShot( true ); m_dirtyHandler->setSingleShot( true );
connect( m_dirtyHandler, &QTimer::timeout,this, &Part::slotDoFileDirty ); connect( m_dirtyHandler, &QTimer::timeout, this, [this] { slotAttemptReload(); } );
slotNewConfig(); slotNewConfig();
@ -698,7 +715,7 @@ void Part::setupViewerActions()
m_findPrev = KStandardAction::findPrev( this, SLOT(slotFindPrev()), ac ); m_findPrev = KStandardAction::findPrev( this, SLOT(slotFindPrev()), ac );
m_findPrev->setEnabled( false ); m_findPrev->setEnabled( false );
m_saveCopyAs = nullptr; m_save = nullptr;
m_saveAs = nullptr; m_saveAs = nullptr;
QAction * prefs = KStandardAction::preferences( this, SLOT(slotPreferences()), ac); 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_selectAll = KStandardAction::selectAll( m_pageView, SLOT(selectAll()), ac );
m_saveCopyAs = KStandardAction::saveAs( this, SLOT(slotSaveCopyAs()), ac ); m_save = KStandardAction::save( this, [this] { saveFile(); }, ac );
m_saveCopyAs->setText( i18n( "Save &Copy As..." ) ); m_save->setEnabled( false );
ac->addAction( QStringLiteral("file_save_copy"), m_saveCopyAs );
ac->setDefaultShortcuts(m_saveCopyAs, KStandardShortcut::shortcut(KStandardShortcut::SaveAs));
m_saveCopyAs->setEnabled( false );
m_saveAs = KStandardAction::saveAs( this, SLOT(slotSaveFileAs()), ac ); m_saveAs = KStandardAction::saveAs( this, SLOT(slotSaveFileAs()), ac );
ac->setDefaultShortcuts(m_saveAs, KStandardShortcut::shortcut(KStandardShortcut::Save));
m_saveAs->setEnabled( false ); m_saveAs->setEnabled( false );
m_migrationMessage->addAction( m_saveAs );
m_showLeftPanel = ac->add<KToggleAction>(QStringLiteral("show_leftpanel")); m_showLeftPanel = ac->add<KToggleAction>(QStringLiteral("show_leftpanel"));
m_showLeftPanel->setText(i18n( "Show &Navigation Panel")); m_showLeftPanel->setText(i18n( "Show &Navigation Panel"));
@ -847,11 +861,6 @@ void Part::setupActions()
m_exportAsMenu->addAction( m_exportAsText ); m_exportAsMenu->addAction( m_exportAsText );
m_exportAs->setEnabled( false ); m_exportAs->setEnabled( false );
m_exportAsText->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 #if PURPOSE_FOUND
m_share = ac->addAction( QStringLiteral("file_share") ); m_share = ac->addAction( QStringLiteral("file_share") );
@ -1120,7 +1129,7 @@ void Part::loadCancelled(const QString &reason)
emit setWindowCaption( QString() ); emit setWindowCaption( QString() );
resetStartArguments(); 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 // so we don't want to show an ugly messagebox just because the document is
// taking more than usual to be recreated // taking more than usual to be recreated
if (m_viewportDirty.pageNumber == -1) if (m_viewportDirty.pageNumber == -1)
@ -1178,6 +1187,11 @@ KConfigDialog * Part::slotGeneratorPreferences( )
void Part::notifySetup( const QVector< Okular::Page * > & /*pages*/, int setupFlags ) 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 ) ) if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) )
return; return;
@ -1194,9 +1208,6 @@ void Part::notifyViewportChanged( bool /*smoothMove*/ )
void Part::notifyPageChanged( int page, int flags ) void Part::notifyPageChanged( int page, int flags )
{ {
if ( flags & Okular::DocumentObserver::NeedSaveAs )
setModified();
if ( !(flags & Okular::DocumentObserver::Bookmark ) ) if ( !(flags & Okular::DocumentObserver::Bookmark ) )
return; return;
@ -1281,12 +1292,39 @@ bool Part::slotImportPSFile()
return false; 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); 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 ) 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; *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; isDocumentArchive = false;
if ( uncompressOk ) if ( uncompressOk )
{ {
@ -1320,6 +1381,7 @@ Document::OpenResult Part::doOpenFile( const QMimeType &mimeA, const QString &fi
{ {
openResult = m_document->openDocument( fileNameToOpen, url(), mime ); 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 // if the file didn't open correctly it might be encrypted, so ask for a pass
QString walletName, walletFolder, walletKey; 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 ); 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 )
if ( openResult == Document::OpenSuccess && wallet && /*safety check*/ wallet->isOpen() && keep )
{ {
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_find->setEnabled( ok && canSearch );
m_findNext->setEnabled( ok && canSearch ); m_findNext->setEnabled( ok && canSearch );
m_findPrev->setEnabled( ok && canSearch ); m_findPrev->setEnabled( ok && canSearch );
if( m_saveAs ) m_saveAs->setEnabled( ok && (m_document->canSaveChanges() || isDocumentArchive) ); if( m_save ) m_save->setEnabled( ok && !( isstdin || mime.inherits( "inode/directory" ) ) );
if( m_saveCopyAs ) m_saveCopyAs->setEnabled( ok ); if( m_saveAs ) m_saveAs->setEnabled( ok && !( isstdin || mime.inherits( "inode/directory" ) ) );
emit enablePrintAction( ok && m_document->printingSupport() != Okular::Document::NoPrinting ); emit enablePrintAction( ok && m_document->printingSupport() != Okular::Document::NoPrinting );
m_printPreview->setEnabled( ok && m_document->printingSupport() != Okular::Document::NoPrinting ); m_printPreview->setEnabled( ok && m_document->printingSupport() != Okular::Document::NoPrinting );
m_showProperties->setEnabled( ok ); m_showProperties->setEnabled( ok );
bool hasEmbeddedFiles = ok && m_document->embeddedFiles() && m_document->embeddedFiles()->count() > 0; bool hasEmbeddedFiles = ok && m_document->embeddedFiles() && m_document->embeddedFiles()->count() > 0;
if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( hasEmbeddedFiles ); if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( hasEmbeddedFiles );
m_topMessage->setVisible( hasEmbeddedFiles && Okular::Settings::showOSD() ); 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) // Warn the user that XFA forms are not supported yet (NOTE: poppler generator only)
if ( ok && m_document->metaData( QStringLiteral("HasUnsupportedXfaForm") ).toBool() == true ) if ( ok && m_document->metaData( QStringLiteral("HasUnsupportedXfaForm") ).toBool() == true )
@ -1516,7 +1584,6 @@ bool Part::openFile()
#endif #endif
} }
if ( m_exportAsText ) m_exportAsText->setEnabled( ok && m_document->canExportToText() ); 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 ( m_exportAs ) m_exportAs->setEnabled( ok );
#if PURPOSE_FOUND #if PURPOSE_FOUND
if ( m_share ) m_share->setEnabled( ok ); if ( m_share ) m_share->setEnabled( ok );
@ -1538,9 +1605,7 @@ bool Part::openFile()
// set the file to the fileWatcher // set the file to the fileWatcher
if ( url().isLocalFile() ) if ( url().isLocalFile() )
{ setFileToWatch( localFilePath() );
addFileToWatcher( m_watcher, localFilePath() );
}
// if the 'OpenTOC' flag is set, open the TOC // 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 ) 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; 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 // Close current document if any
if ( !closeUrl() ) if ( !closeUrl() )
return false; return false;
@ -1630,15 +1704,15 @@ bool Part::queryClose()
return true; return true;
const int res = KMessageBox::warningYesNoCancel( widget(), 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" ), i18n( "Close Document" ),
KStandardGuiItem::saveAs(), KStandardGuiItem::save(),
KStandardGuiItem::discard() ); KStandardGuiItem::discard() );
switch ( res ) switch ( res )
{ {
case KMessageBox::Yes: // Save as case KMessageBox::Yes: // Save
slotSaveFileAs(); saveFile();
return !isModified(); // Only allow closing if file was really saved return !isModified(); // Only allow closing if file was really saved
case KMessageBox::No: // Discard case KMessageBox::No: // Discard
return true; return true;
@ -1652,7 +1726,14 @@ bool Part::closeUrl(bool promptToSave)
if ( promptToSave && !queryClose() ) if ( promptToSave && !queryClose() )
return false; 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()) if (!m_temporaryLocalFile.isNull() && m_temporaryLocalFile != localFilePath())
{ {
@ -1665,21 +1746,20 @@ bool Part::closeUrl(bool promptToSave)
m_find->setEnabled( false ); m_find->setEnabled( false );
m_findNext->setEnabled( false ); m_findNext->setEnabled( false );
m_findPrev->setEnabled( false ); m_findPrev->setEnabled( false );
if( m_save ) m_save->setEnabled( false );
if( m_saveAs ) m_saveAs->setEnabled( false ); if( m_saveAs ) m_saveAs->setEnabled( false );
if( m_saveCopyAs ) m_saveCopyAs->setEnabled( false );
m_printPreview->setEnabled( false ); m_printPreview->setEnabled( false );
m_showProperties->setEnabled( false ); m_showProperties->setEnabled( false );
if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( false ); if ( m_showEmbeddedFiles ) m_showEmbeddedFiles->setEnabled( false );
if ( m_exportAs ) m_exportAs->setEnabled( false ); if ( m_exportAs ) m_exportAs->setEnabled( false );
if ( m_exportAsText ) m_exportAsText->setEnabled( false ); if ( m_exportAsText ) m_exportAsText->setEnabled( false );
if ( m_exportAsDocArchive ) m_exportAsDocArchive->setEnabled( false );
m_exportFormats.clear(); m_exportFormats.clear();
if ( m_exportAs ) if ( m_exportAs )
{ {
QMenu *menu = m_exportAs->menu(); QMenu *menu = m_exportAs->menu();
QList<QAction*> acts = menu->actions(); QList<QAction*> acts = menu->actions();
int num = acts.count(); int num = acts.count();
for ( int i = 2; i < num; ++i ) for ( int i = 1; i < num; ++i )
{ {
menu->removeAction( acts.at(i) ); menu->removeAction( acts.at(i) );
delete acts.at(i); delete acts.at(i);
@ -1697,12 +1777,7 @@ bool Part::closeUrl(bool promptToSave)
emit enablePrintAction(false); emit enablePrintAction(false);
m_realUrl = QUrl(); m_realUrl = QUrl();
if ( url().isLocalFile() ) if ( url().isLocalFile() )
{ unsetFileToWatch();
m_watcher->removeFile( localFilePath() );
QFileInfo fi(localFilePath());
m_watcher->removeDir( fi.absolutePath() );
if ( fi.isSymLink() ) m_watcher->removeFile( fi.readLink() );
}
m_fileWasRemoved = false; m_fileWasRemoved = false;
if ( m_generatorGuiClient ) if ( m_generatorGuiClient )
factory()->removeClient( m_generatorGuiClient ); factory()->removeClient( m_generatorGuiClient );
@ -1714,6 +1789,7 @@ bool Part::closeUrl(bool promptToSave)
if ( widget() ) if ( widget() )
{ {
m_searchWidget->clearText(); m_searchWidget->clearText();
m_migrationMessage->setVisible( false );
m_topMessage->setVisible( false ); m_topMessage->setVisible( false );
m_formsMessage->setVisible( false ); m_formsMessage->setVisible( false );
} }
@ -1802,8 +1878,8 @@ void Part::slotFileDirty( const QString& path )
else if (m_fileWasRemoved && QFile::exists(localFilePath())) else if (m_fileWasRemoved && QFile::exists(localFilePath()))
{ {
// we need to watch the new file // we need to watch the new file
m_watcher->removeFile(localFilePath()); unsetFileToWatch();
m_watcher->addFile(localFilePath()); setFileToWatch( localFilePath() );
m_dirtyHandler->start( 750 ); m_dirtyHandler->start( 750 );
} }
} }
@ -1817,12 +1893,12 @@ void Part::slotFileDirty( const QString& path )
} }
} }
// Attempt to reload the document, one or more times, optionally from a different URL
void Part::slotDoFileDirty() bool Part::slotAttemptReload( bool oneShot, const QUrl &newUrl )
{ {
// Skip reload when another reload is already in progress // Skip reload when another reload is already in progress
if ( m_isReloading ) { if ( m_isReloading ) {
return; return false;
} }
QScopedValueRollback<bool> rollback(m_isReloading, true); QScopedValueRollback<bool> rollback(m_isReloading, true);
@ -1832,7 +1908,7 @@ void Part::slotDoFileDirty()
if ( m_viewportDirty.pageNumber == -1 ) if ( m_viewportDirty.pageNumber == -1 )
{ {
// store the url of the current document // store the url of the current document
m_oldUrl = url(); m_oldUrl = newUrl.isEmpty() ? url() : newUrl;
// store the current viewport // store the current viewport
m_viewportDirty = m_document->viewport(); m_viewportDirty = m_document->viewport();
@ -1866,7 +1942,7 @@ void Part::slotDoFileDirty()
{ {
m_toc->rollbackReload(); m_toc->rollbackReload();
} }
return; return false;
} }
if ( tocReloadPrepared ) if ( tocReloadPrepared )
@ -1875,6 +1951,8 @@ void Part::slotDoFileDirty()
// inform the user about the operation in progress // inform the user about the operation in progress
m_pageView->displayMessage( i18n("Reloading the document...") ); m_pageView->displayMessage( i18n("Reloading the document...") );
bool reloadSucceeded = false;
if ( KParts::ReadWritePart::openUrl( m_oldUrl ) ) if ( KParts::ReadWritePart::openUrl( m_oldUrl ) )
{ {
// on successful opening, restore the previous viewport // on successful opening, restore the previous viewport
@ -1899,13 +1977,17 @@ void Part::slotDoFileDirty()
} }
if (m_wasPresentationOpen) slotShowPresentation(); if (m_wasPresentationOpen) slotShowPresentation();
emit enablePrintAction(true && m_document->printingSupport() != Okular::Document::NoPrinting); 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) // start watching the file again (since we dropped it on close)
addFileToWatcher( m_watcher, localFilePath() ); setFileToWatch( localFilePath() );
m_dirtyHandler->start( 750 ); m_dirtyHandler->start( 750 );
} }
return reloadSucceeded;
} }
@ -2312,56 +2394,80 @@ void Part::slotFindPrev()
bool Part::saveFile() bool Part::saveFile()
{ {
qCDebug(OkularUiDebug) << "Okular part doesn't support saving the file in the location from which it was opened"; if ( !isModified() )
return false; return true;
else
return saveAs( url() );
} }
void Part::slotSaveFileAs() bool Part::slotSaveFileAs( bool showOkularArchiveAsDefaultFormat )
{ {
if ( m_embedMode == PrintPreviewMode ) if ( m_embedMode == PrintPreviewMode )
return; return false;
/* Show a warning before saving if the generator can't save annotations, // Determine the document's mimetype
* unless we are going to save a .okular archive. */ QMimeDatabase db;
if ( !isDocumentArchive && !m_document->canSaveChanges( Document::SaveAnnotationsCapability ) ) 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 */ const int res = KMessageBox::warningYesNo( widget(),
bool containsLocalAnnotations = false; 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?" ),
const int pagecount = m_document->pages(); i18n( "Save - Warning" ) );
for ( int pageno = 0; pageno < pagecount; ++pageno ) switch ( res )
{ {
const Okular::Page *page = m_document->page( pageno ); case KMessageBox::Yes:
foreach ( const Okular::Annotation *ann, page->annotations() ) hasUserAcceptedReload = true;
{ // do nothing
if ( !(ann->flags() & Okular::Annotation::External) )
{
containsLocalAnnotations = true;
break;
}
}
if ( containsLocalAnnotations )
break; 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;
/* 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
} }
} }
QUrl saveUrl = QFileDialog::getSaveFileUrl( widget(), QString(), url() ); bool setModifiedAfterSave = false;
if ( !saveUrl.isValid() || saveUrl.isEmpty() )
return;
saveAs( saveUrl );
}
bool Part::saveAs( const QUrl & saveUrl )
{
QTemporaryFile tf; QTemporaryFile tf;
QString fileName; QString fileName;
if ( !tf.open() ) if ( !tf.open() )
@ -2372,85 +2478,283 @@ bool Part::saveAs( const QUrl & saveUrl )
fileName = tf.fileName(); fileName = tf.fileName();
tf.close(); tf.close();
QString errorText; QScopedPointer<QTemporaryFile> tempFile;
bool saved; KIO::Job *copyJob = nullptr; // this will be filled with the job that writes to saveUrl
if ( isDocumentArchive ) // Does the user want a .okular archive?
saved = m_document->saveDocumentArchive( fileName ); if ( flags & SaveAsOkularArchive )
else
saved = m_document->saveChanges( fileName, &errorText );
if ( !saved )
{ {
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 ) ); KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", fileName ) );
return false;
} }
else
{ copyJob = KIO::file_copy( QUrl::fromLocalFile( fileName ), saveUrl, -1, KIO::Overwrite );
KMessageBox::information( widget(), i18n("File could not be saved in '%1'. %2", fileName, errorText ) );
}
return false;
} }
else
KIO::Job *copyJob = KIO::file_copy( QUrl::fromLocalFile(fileName), 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() ) ); bool wontSaveForms, wontSaveAnnotations;
return false; checkNativeSaveDataLoss(&wontSaveForms, &wontSaveAnnotations);
}
setModified( false ); // If something can't be saved in this format, ask for confirmation
return true; QStringList listOfwontSaves;
} if ( wontSaveForms ) listOfwontSaves << i18n( "Filled form contents" );
if ( wontSaveAnnotations ) listOfwontSaves << i18n( "User annotations" );
if ( !listOfwontSaves.isEmpty() )
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 ( url().isLocalFile() ) if ( saveUrl == url() )
{ {
#ifdef OKULAR_KEEP_FILE_OPEN // Save
// local file: try to get it back from the open handle on it 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." );
if ( ( tempFile = m_keeper->copyToTemporary() ) ) const int result = KMessageBox::warningYesNoList( widget(),
srcUrl = QUrl::fromLocalFile( tempFile->fileName() ); warningMessage,
#else listOfwontSaves, i18n( "Warning" ),
const QString msg = i18n( "Okular cannot copy %1 to the specified location.\n\nThe document does not exist anymore.", localFilePath() ); KGuiItem( i18n( "Save as Okular document archive..." ), "document-save-as" ), // <- KMessageBox::Yes
KMessageBox::sorry( widget(), msg ); KStandardGuiItem::cancel() );
return;
#endif switch (result)
{
case KMessageBox::Yes: // -> Save as Okular document archive
return slotSaveFileAs( true /* showOkularArchiveAsDefaultFormat */ );
default:
return false;
}
} }
else else
{ {
// we still have the original remote URL of the document, // Save as
// so copy the document from there const QString warningMessage = m_document->canSwapBackingFile() ?
srcUrl = url(); 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 ); if ( m_document->canSaveChanges() )
KJobWidgets::setWindow(copyJob, widget()); {
if ( !copyJob->exec() ) // If the generator supports saving changes, save them
KMessageBox::information( widget(), i18n("File could not be saved in '%1'. Try to save it to another location.", saveUrl.toDisplayString() ) );
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() void Part::slotGetNewStuff()
{ {
@ -2763,9 +3067,6 @@ void Part::slotExportAs(QAction * act)
case 0: case 0:
mimeType = mimeDatabase.mimeTypeForName(QStringLiteral("text/plain")); mimeType = mimeDatabase.mimeTypeForName(QStringLiteral("text/plain"));
break; break;
case 1:
mimeType = mimeDatabase.mimeTypeForName(QStringLiteral("application/vnd.kde.okular-archive"));
break;
default: default:
mimeType = m_exportFormats.at( id - 2 ).mimeType(); mimeType = m_exportFormats.at( id - 2 ).mimeType();
break; break;
@ -2782,11 +3083,8 @@ void Part::slotExportAs(QAction * act)
case 0: case 0:
saved = m_document->exportToText( fileName ); saved = m_document->exportToText( fileName );
break; break;
case 1:
saved = m_document->saveDocumentArchive( fileName );
break;
default: default:
saved = m_document->exportTo( fileName, m_exportFormats.at( id - 2 ) ); saved = m_document->exportTo( fileName, m_exportFormats.at( id - 1 ) );
break; break;
} }
if ( !saved ) if ( !saved )
@ -2801,7 +3099,7 @@ void Part::slotReload()
// auto-refresh system // auto-refresh system
m_dirtyHandler->stop(); 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) 2003 by Laurent Montel <montel@kde.org> *
* Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> * * Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> *
* Copyright (C) 2004-2007 by Albert Astals Cid <aacid@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 * * 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 * * 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 guiActivateEvent(KParts::GUIActivateEvent *event) override;
void displayInfoMessage( const QString &message, KMessageWidget::MessageType messageType = KMessageWidget::Information, int duration = -1 ); void displayInfoMessage( const QString &message, KMessageWidget::MessageType messageType = KMessageWidget::Information, int duration = -1 );
public: public:
bool saveFile() override;
bool queryClose() override; bool queryClose() override;
bool closeUrl() override; bool closeUrl() override;
bool closeUrl(bool promptToSave) override; bool closeUrl(bool promptToSave) override;
@ -202,8 +204,7 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
void slotNextBookmark(); void slotNextBookmark();
void slotFindNext(); void slotFindNext();
void slotFindPrev(); void slotFindPrev();
void slotSaveFileAs(); bool slotSaveFileAs(bool showOkularArchiveAsDefaultFormat = false);
void slotSaveCopyAs();
void slotGetNewStuff(); void slotGetNewStuff();
void slotNewConfig(); void slotNewConfig();
void slotShowMenu(const Okular::Page *page, const QPoint &point); 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 ); void enableLayers( bool enable );
public Q_SLOTS: public Q_SLOTS:
bool saveFile() override;
// connected to Shell action (and browserExtension), not local one // connected to Shell action (and browserExtension), not local one
void slotPrint(); void slotPrint();
void slotFileDirty( const QString& ); void slotFileDirty( const QString& );
void slotDoFileDirty(); bool slotAttemptReload( bool oneShot = false, const QUrl &newUrl = QUrl() );
void psTransformEnded(int, QProcess::ExitStatus); void psTransformEnded(int, QProcess::ExitStatus);
KConfigDialog * slotGeneratorPreferences(); 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()); 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; bool eventFilter(QObject * watched, QEvent * event) override;
Document::OpenResult doOpenFile(const QMimeType &mime, const QString &fileNameToOpen, bool *isCompressedFile); Document::OpenResult doOpenFile(const QMimeType &mime, const QString &fileNameToOpen, bool *isCompressedFile);
bool openUrl( const QUrl &url, bool swapInsteadOfOpening );
void setupViewerActions(); void setupViewerActions();
void setViewerShortcuts(); void setViewerShortcuts();
@ -266,6 +269,19 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
void slotRenameBookmark( const DocumentViewport &viewport ); void slotRenameBookmark( const DocumentViewport &viewport );
void slotRemoveBookmark( const DocumentViewport &viewport ); void slotRemoveBookmark( const DocumentViewport &viewport );
void resetStartArguments(); 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 #if PURPOSE_FOUND
void slotShareActionFinished(const QJsonObject &output, int error, const QString &message); 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; Okular::Document * m_document;
QString m_temporaryLocalFile; QString m_temporaryLocalFile;
bool isDocumentArchive; 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 // main widgets
Sidebar *m_sidebar; Sidebar *m_sidebar;
SearchWidget *m_searchWidget; SearchWidget *m_searchWidget;
FindBar * m_findBar; FindBar * m_findBar;
KMessageWidget * m_migrationMessage;
KMessageWidget * m_topMessage; KMessageWidget * m_topMessage;
KMessageWidget * m_formsMessage; KMessageWidget * m_formsMessage;
KMessageWidget * m_infoMessage; KMessageWidget * m_infoMessage;
@ -303,6 +322,7 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
// document watcher (and reloader) variables // document watcher (and reloader) variables
KDirWatch *m_watcher; KDirWatch *m_watcher;
QString m_watchedFilePath, m_watchedFileSymlinkTarget;
QTimer *m_dirtyHandler; QTimer *m_dirtyHandler;
QUrl m_oldUrl; QUrl m_oldUrl;
Okular::DocumentViewport m_viewportDirty; Okular::DocumentViewport m_viewportDirty;
@ -334,6 +354,7 @@ class OKULARPART_EXPORT Part : public KParts::ReadWritePart, public Okular::Docu
QAction *m_find; QAction *m_find;
QAction *m_findNext; QAction *m_findNext;
QAction *m_findPrev; QAction *m_findPrev;
QAction *m_save;
QAction *m_saveAs; QAction *m_saveAs;
QAction *m_saveCopyAs; QAction *m_saveCopyAs;
QAction *m_printPreview; QAction *m_printPreview;

View file

@ -1,11 +1,11 @@
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> <!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="okular_part" version="37"> <kpartgui name="okular_part" version="38">
<MenuBar> <MenuBar>
<Menu name="file"><text>&amp;File</text> <Menu name="file"><text>&amp;File</text>
<Action name="get_new_stuff" group="file_open"/> <Action name="get_new_stuff" group="file_open"/>
<Action name="import_ps" 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_as" group="file_save"/>
<Action name="file_save_copy" group="file_save"/>
<Action name="file_reload" group="file_save"/> <Action name="file_reload" group="file_save"/>
<Action name="file_print" group="file_print"/> <Action name="file_print" group="file_print"/>
<Action name="file_print_preview" 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) 2003 by Malcolm Hunter <malcolm.hunter@gmx.co.uk> *
* Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> * * Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> *
* Copyright (C) 2004 by Dirk Mueller <mueller@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 * * 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 * * 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 else
{ {
Shell* newShell = new Shell( serializedOptions ); Shell* newShell = new Shell( serializedOptions );
newShell->openUrl( url, serializedOptions );
newShell->show(); newShell->show();
newShell->openUrl( url, serializedOptions );
} }
} }
} }
@ -495,6 +498,28 @@ void Shell::setFullScreen( bool useFullScreen )
setWindowState( windowState() & ~Qt::WindowFullScreen ); // reset 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) void Shell::showEvent(QShowEvent *e)
{ {
if (!menuBar()->isNativeMenuBar() && m_showMenuBarAction) if (!menuBar()->isNativeMenuBar() && m_showMenuBarAction)

View file

@ -99,6 +99,10 @@ protected:
void readSettings(); void readSettings();
void writeSettings(); void writeSettings();
void setFullScreen( bool ); void setFullScreen( bool );
using KParts::MainWindow::setCaption;
void setCaption( const QString &caption ) override;
bool queryClose() override; bool queryClose() override;
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;

View file

@ -1,5 +1,8 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2006 by Pino Toscano <pino@kde.org> * * 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 * * 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 * * it under the terms of the GNU General Public License as published by *
@ -105,10 +108,32 @@ AnnotationModelPrivate::~AnnotationModelPrivate()
delete root; 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 ) void AnnotationModelPrivate::notifySetup( const QVector< Okular::Page * > &pages, int setupFlags )
{ {
if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) ) 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; return;
}
q->beginResetModel(); q->beginResetModel();
qDeleteAll( root->children ); qDeleteAll( root->children );

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,9 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> * * Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> * * 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: * * With portions of code from kpdf/kpdf_pagewidget.cc by: *
* Copyright (C) 2002 by Wilco Greven <greven@kde.org> * * Copyright (C) 2002 by Wilco Greven <greven@kde.org> *
@ -81,6 +84,7 @@
#include "core/document_p.h" #include "core/document_p.h"
#include "core/form.h" #include "core/form.h"
#include "core/page.h" #include "core/page.h"
#include "core/page_p.h"
#include "core/misc.h" #include "core/misc.h"
#include "core/generator.h" #include "core/generator.h"
#include "core/movie.h" #include "core/movie.h"
@ -172,7 +176,7 @@ public:
// annotations // annotations
PageViewAnnotator * annotator; PageViewAnnotator * annotator;
//text annotation dialogs list //text annotation dialogs list
QHash< Okular::Annotation *, AnnotWindow * > m_annowindows; QSet< AnnotWindow * > m_annowindows;
// other stuff // other stuff
QTimer * delayResizeEventTimer; QTimer * delayResizeEventTimer;
bool dirtyLayout; bool dirtyLayout;
@ -451,7 +455,7 @@ PageView::~PageView()
// We need to assign it to a different list otherwise slotAnnotationWindowDestroyed // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed
// will bite us and clear d->m_annowindows // 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(); d->m_annowindows.clear();
qDeleteAll( annowindows ); qDeleteAll( annowindows );
@ -756,10 +760,13 @@ void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNu
// find the annot window // find the annot window
AnnotWindow* existWindow = nullptr; AnnotWindow* existWindow = nullptr;
QHash< Okular::Annotation *, AnnotWindow * >::ConstIterator it = d->m_annowindows.constFind( annotation ); foreach(AnnotWindow *aw, d->m_annowindows)
if ( it != d->m_annowindows.constEnd() )
{ {
existWindow = *it; if ( aw->annotation() == annotation )
{
existWindow = aw;
break;
}
} }
if ( existWindow == nullptr ) if ( existWindow == nullptr )
@ -767,7 +774,7 @@ void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNu
existWindow = new AnnotWindow( this, annotation, d->document, pageNumber ); existWindow = new AnnotWindow( this, annotation, d->document, pageNumber );
connect(existWindow, &QObject::destroyed, this, &PageView::slotAnnotationWindowDestroyed); connect(existWindow, &QObject::destroyed, this, &PageView::slotAnnotationWindowDestroyed);
d->m_annowindows.insert( annotation, existWindow ); d->m_annowindows << existWindow;
} }
existWindow->show(); existWindow->show();
@ -775,19 +782,7 @@ void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNu
void PageView::slotAnnotationWindowDestroyed( QObject * window ) void PageView::slotAnnotationWindowDestroyed( QObject * window )
{ {
QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin(); d->m_annowindows.remove( static_cast<AnnotWindow*>( window ) );
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;
}
}
} }
void PageView::displayMessage( const QString & message, const QString & details, PageViewMessage::Icon icon, int duration ) 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 //BEGIN DocumentObserver inherited methods
void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags )
{ {
bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged; 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 // reuse current pages if nothing new
if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) ) if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) )
{ {
int count = pageSet.count(); int count = pageSet.count();
for ( int i = 0; (i < count) && !documentChanged; i++ ) for ( int i = 0; (i < count) && !documentChanged; i++ )
{
if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() ) if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
{
documentChanged = true; 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 ( !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; return;
}
} }
// mouseAnnotation must not access our PageViewItem widgets any longer // 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->setPageItem( item );
w->setFormWidgetsController( d->formWidgetsController() ); w->setFormWidgetsController( d->formWidgetsController() );
w->setVisibility( false ); w->setVisibility( false );
w->setCanBeFilled( d->document->isAllowed( Okular::AllowFillForms ) ); w->setCanBeFilled( allowfillforms );
item->formWidgets().insert( ff->id(), w ); item->formWidgets().insert( w );
hasformwidgets = true; hasformwidgets = true;
} }
} }
const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations();
QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.constBegin(), aEnd = annotations.constEnd(); createAnnotationsVideoWidgets( item, (*setIt)->annotations() );
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();
}
}
}
} }
// invalidate layout so relayout/repaint will happen on next viewport change // 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 // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed
// will bite us and clear d->m_annowindows // 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(); d->m_annowindows.clear();
qDeleteAll( annowindows ); 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 * > annots = d->document->page( pageNumber )->annotations();
const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end(); 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(); ) 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 ) if ( annIt != annItEnd )
{ {
(*it)->reloadInfo(); (*it)->reloadInfo();

View file

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

View file

@ -2,6 +2,9 @@
* Copyright (C) 2017 by Tobias Deiminger <haxtibal@t-online.de> * * Copyright (C) 2017 by Tobias Deiminger <haxtibal@t-online.de> *
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> * * Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> * * 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: * * With portions of code from kpdf/kpdf_pagewidget.cc by: *
* Copyright (C) 2002 by Wilco Greven <greven@kde.org> * * Copyright (C) 2002 by Wilco Greven <greven@kde.org> *
@ -400,6 +403,19 @@ Qt::CursorShape MouseAnnotation::cursor() const
return Qt::ArrowCursor; 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() void MouseAnnotation::cancel()
{ {
if ( isActive() ) if ( isActive() )

View file

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

View file

@ -1,5 +1,8 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> * * 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 * * 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 * * 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() PageViewItem::~PageViewItem()
{ {
QHash<int, FormWidgetIface*>::iterator it = m_formWidgets.begin(), itEnd = m_formWidgets.end(); qDeleteAll( m_formWidgets );
for ( ; it != itEnd; ++it )
delete *it;
qDeleteAll( m_videoWidgets ); qDeleteAll( m_videoWidgets );
} }
@ -122,7 +123,7 @@ bool PageViewItem::isVisible() const
return m_visible; return m_visible;
} }
QHash<int, FormWidgetIface*>& PageViewItem::formWidgets() QSet<FormWidgetIface*>& PageViewItem::formWidgets()
{ {
return m_formWidgets; return m_formWidgets;
} }
@ -163,7 +164,7 @@ void PageViewItem::moveTo( int x, int y )
m_croppedGeometry.moveTop( y ); m_croppedGeometry.moveTop( y );
m_uncroppedGeometry.moveLeft( qRound( x - m_crop.left * m_uncroppedGeometry.width() ) ); m_uncroppedGeometry.moveLeft( qRound( x - m_crop.left * m_uncroppedGeometry.width() ) );
m_uncroppedGeometry.moveTop( qRound( y - m_crop.top * m_uncroppedGeometry.height() ) ); 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 ) for ( ; it != itEnd; ++it )
{ {
Okular::NormalizedRect r = (*it)->rect(); Okular::NormalizedRect r = (*it)->rect();
@ -200,7 +201,7 @@ bool PageViewItem::setFormWidgetsVisible( bool visible )
return false; return false;
bool somehadfocus = 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 ) for ( ; it != itEnd; ++it )
{ {
bool hadfocus = (*it)->setVisibility( visible && (*it)->formField()->isVisible() ); 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) 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 * * 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 * * it under the terms of the GNU General Public License as published by *
@ -47,7 +50,7 @@ class PageViewItem
int pageNumber() const; int pageNumber() const;
double zoomFactor() const; double zoomFactor() const;
bool isVisible() const; bool isVisible() const;
QHash<int, FormWidgetIface*>& formWidgets(); QSet<FormWidgetIface*>& formWidgets();
QHash< Okular::Movie *, VideoWidget * >& videoWidgets(); QHash< Okular::Movie *, VideoWidget * >& videoWidgets();
/* The page is cropped as follows: */ /* The page is cropped as follows: */
@ -87,7 +90,7 @@ class PageViewItem
QRect m_croppedGeometry; QRect m_croppedGeometry;
QRect m_uncroppedGeometry; QRect m_uncroppedGeometry;
Okular::NormalizedRect m_crop; Okular::NormalizedRect m_crop;
QHash<int, FormWidgetIface*> m_formWidgets; QSet<FormWidgetIface*> m_formWidgets;
QHash< Okular::Movie *, VideoWidget * > m_videoWidgets; QHash< Okular::Movie *, VideoWidget * > m_videoWidgets;
}; };