1 /***************************************************************************
2 * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
3 * This program is free software; you can redistribute it and/or modify *
4 * it under the terms of the GNU General Public License as published by *
5 * the Free Software Foundation; either version 2 of the License, or *
6 * (at your option) any later version. *
7 ***************************************************************************/
13 #include <QtCore/QHash>
14 #include <QtCore/QSet>
15 #include <QtCore/QString>
16 #include <QtCore/QVariant>
17 #include <QtGui/QPixmap>
18 #include <QtXml/QDomDocument>
19 #include <QtXml/QDomElement>
25 #include "annotations.h"
26 #include "annotations_p.h"
31 #include "pagecontroller_p.h"
33 #include "pagetransition.h"
34 #include "rotationjob_p.h"
36 #include "textpage_p.h"
39 #include <QtCore/QTime>
42 using namespace Okular
;
44 static void deleteObjectRects( QLinkedList
< ObjectRect
* >& rects
, const QSet
<ObjectRect::ObjectType
>& which
)
46 QLinkedList
< ObjectRect
* >::iterator it
= rects
.begin(), end
= rects
.end();
48 if ( which
.contains( (*it
)->objectType() ) )
51 it
= rects
.erase( it
);
57 PagePrivate::PagePrivate( Page
*page
, uint n
, double w
, double h
, Rotation o
)
58 : m_page( page
), m_number( n
), m_orientation( o
),
59 m_width( w
), m_height( h
), m_boundingBox( 0, 0, 1, 1 ),
60 m_rotation( Rotation0
), m_maxuniqueNum( 0 ),
61 m_text( 0 ), m_transition( 0 ), m_textSelections( 0 ),
62 m_openingAction( 0 ), m_closingAction( 0 ), m_duration( -1 ),
63 m_isBoundingBoxKnown( false )
65 // avoid Division-By-Zero problems in the program
73 PagePrivate::~PagePrivate()
75 qDeleteAll( formfields
);
76 delete m_openingAction
;
77 delete m_closingAction
;
83 void PagePrivate::imageRotationDone( RotationJob
* job
)
85 QMap
< int, PixmapObject
>::iterator it
= m_pixmaps
.find( job
->id() );
86 if ( it
!= m_pixmaps
.end() )
88 PixmapObject
&object
= it
.value();
89 (*object
.m_pixmap
) = QPixmap::fromImage( job
->image() );
90 object
.m_rotation
= job
->rotation();
93 object
.m_pixmap
= new QPixmap( QPixmap::fromImage( job
->image() ) );
94 object
.m_rotation
= job
->rotation();
96 m_pixmaps
.insert( job
->id(), object
);
100 QMatrix
PagePrivate::rotationMatrix() const
103 matrix
.rotate( (int)m_rotation
* 90 );
105 switch ( m_rotation
)
108 matrix
.translate( 0, -1 );
111 matrix
.translate( -1, -1 );
114 matrix
.translate( -1, 0 );
124 Page::Page( uint page
, double w
, double h
, Rotation o
)
125 : d( new PagePrivate( this, page
, w
, h
, o
) )
133 d
->deleteHighlights();
135 d
->deleteTextSelections();
136 deleteSourceReferences();
141 int Page::number() const
146 Rotation
Page::orientation() const
148 return d
->m_orientation
;
151 Rotation
Page::rotation() const
153 return d
->m_rotation
;
156 Rotation
Page::totalOrientation() const
158 return (Rotation
)( ( (int)d
->m_orientation
+ (int)d
->m_rotation
) % 4 );
161 double Page::width() const
166 double Page::height() const
171 double Page::ratio() const
173 return d
->m_height
/ d
->m_width
;
176 NormalizedRect
Page::boundingBox() const
178 return d
->m_boundingBox
;
181 bool Page::isBoundingBoxKnown() const
183 return d
->m_isBoundingBoxKnown
;
186 void Page::setBoundingBox( const NormalizedRect
& bbox
)
188 if ( d
->m_isBoundingBoxKnown
&& d
->m_boundingBox
== bbox
)
191 // Allow tiny rounding errors (happens during rotation)
192 static const double epsilon
= 0.00001;
193 Q_ASSERT( bbox
.left
>= -epsilon
&& bbox
.top
>= -epsilon
&& bbox
.right
<= 1 + epsilon
&& bbox
.bottom
<= 1 + epsilon
);
195 d
->m_boundingBox
= bbox
& NormalizedRect( 0., 0., 1., 1. );
196 d
->m_isBoundingBoxKnown
= true;
199 bool Page::hasPixmap( int id
, int width
, int height
) const
201 QMap
< int, PagePrivate::PixmapObject
>::const_iterator it
= d
->m_pixmaps
.constFind( id
);
202 if ( it
== d
->m_pixmaps
.constEnd() )
205 if ( width
== -1 || height
== -1 )
208 const QPixmap
*pixmap
= it
.value().m_pixmap
;
210 return (pixmap
->width() == width
&& pixmap
->height() == height
);
213 bool Page::hasTextPage() const
215 return d
->m_text
!= 0;
218 RegularAreaRect
* Page::textArea ( TextSelection
* selection
) const
221 return d
->m_text
->textArea( selection
);
226 bool Page::hasObjectRect( double x
, double y
, double xScale
, double yScale
) const
228 if ( m_rects
.isEmpty() )
231 QLinkedList
< ObjectRect
* >::const_iterator it
= m_rects
.begin(), end
= m_rects
.end();
232 for ( ; it
!= end
; ++it
)
233 if ( (*it
)->contains( x
, y
, xScale
, yScale
) )
239 bool Page::hasHighlights( int s_id
) const
241 // simple case: have no highlights
242 if ( m_highlights
.isEmpty() )
244 // simple case: we have highlights and no id to match
247 // iterate on the highlights list to find an entry by id
248 QLinkedList
< HighlightAreaRect
* >::const_iterator it
= m_highlights
.begin(), end
= m_highlights
.end();
249 for ( ; it
!= end
; ++it
)
250 if ( (*it
)->s_id
== s_id
)
255 bool Page::hasTransition() const
257 return d
->m_transition
!= 0;
260 bool Page::hasAnnotations() const
262 return !m_annotations
.isEmpty();
265 RegularAreaRect
* Page::findText( int id
, const QString
& text
, SearchDirection direction
,
266 Qt::CaseSensitivity caseSensitivity
, const RegularAreaRect
*lastRect
) const
268 RegularAreaRect
* rect
= 0;
269 if ( text
.isEmpty() || !d
->m_text
)
272 rect
= d
->m_text
->findText( id
, text
, direction
, caseSensitivity
, lastRect
);
276 QString
Page::text( const RegularAreaRect
* area
) const
285 RegularAreaRect rotatedArea
= *area
;
286 rotatedArea
.transform( d
->rotationMatrix().inverted() );
288 ret
= d
->m_text
->text( &rotatedArea
);
291 ret
= d
->m_text
->text();
296 void PagePrivate::rotateAt( Rotation orientation
)
298 if ( orientation
== m_rotation
)
302 deleteTextSelections();
304 if ( ( (int)m_orientation
+ (int)m_rotation
) % 2 != ( (int)m_orientation
+ (int)orientation
) % 2 )
305 qSwap( m_width
, m_height
);
307 Rotation oldRotation
= m_rotation
;
308 m_rotation
= orientation
;
311 * Rotate the images of the page.
313 QMapIterator
< int, PagePrivate::PixmapObject
> it( m_pixmaps
);
314 while ( it
.hasNext() ) {
317 const PagePrivate::PixmapObject
&object
= it
.value();
319 RotationJob
*job
= new RotationJob( object
.m_pixmap
->toImage(), object
.m_rotation
, m_rotation
, it
.key() );
320 job
->setPage( this );
321 PageController::self()->addRotationJob(job
);
325 * Rotate the object rects on the page.
327 const QMatrix matrix
= rotationMatrix();
328 QLinkedList
< ObjectRect
* >::const_iterator objectIt
= m_page
->m_rects
.begin(), end
= m_page
->m_rects
.end();
329 for ( ; objectIt
!= end
; ++objectIt
)
330 (*objectIt
)->transform( matrix
);
332 QLinkedList
< HighlightAreaRect
* >::const_iterator hlIt
= m_page
->m_highlights
.begin(), hlItEnd
= m_page
->m_highlights
.end();
333 for ( ; hlIt
!= hlItEnd
; ++hlIt
)
335 (*hlIt
)->transform( RotationJob::rotationMatrix( oldRotation
, m_rotation
) );
339 void PagePrivate::changeSize( const PageSize
&size
)
341 if ( size
.isNull() || ( size
.width() == m_width
&& size
.height() == m_height
) )
344 m_page
->deletePixmaps();
345 // deleteHighlights();
346 // deleteTextSelections();
348 m_width
= size
.width();
349 m_height
= size
.height();
350 if ( m_rotation
% 2 )
351 qSwap( m_width
, m_height
);
354 const ObjectRect
* Page::objectRect( ObjectRect::ObjectType type
, double x
, double y
, double xScale
, double yScale
) const
356 QLinkedList
< ObjectRect
* >::const_iterator it
= m_rects
.begin(), end
= m_rects
.end();
357 for ( ; it
!= end
; ++it
)
358 if ( ( (*it
)->objectType() == type
) && (*it
)->contains( x
, y
, xScale
, yScale
) )
363 const PageTransition
* Page::transition() const
365 return d
->m_transition
;
368 QLinkedList
< Annotation
* > Page::annotations() const
370 return m_annotations
;
373 const Action
* Page::pageAction( PageAction action
) const
378 return d
->m_openingAction
;
381 return d
->m_closingAction
;
388 QLinkedList
< FormField
* > Page::formFields() const
390 return d
->formfields
;
393 void Page::setPixmap( int id
, QPixmap
*pixmap
)
395 if ( d
->m_rotation
== Rotation0
) {
396 QMap
< int, PagePrivate::PixmapObject
>::iterator it
= d
->m_pixmaps
.find( id
);
397 if ( it
!= d
->m_pixmaps
.end() )
399 delete it
.value().m_pixmap
;
403 it
= d
->m_pixmaps
.insert( id
, PagePrivate::PixmapObject() );
405 it
.value().m_pixmap
= pixmap
;
406 it
.value().m_rotation
= d
->m_rotation
;
408 RotationJob
*job
= new RotationJob( pixmap
->toImage(), Rotation0
, d
->m_rotation
, id
);
410 PageController::self()->addRotationJob(job
);
416 void Page::setTextPage( TextPage
* textPage
)
420 d
->m_text
= textPage
;
423 d
->m_text
->d
->m_page
= d
;
427 void Page::setObjectRects( const QLinkedList
< ObjectRect
* > & rects
)
429 QSet
<ObjectRect::ObjectType
> which
;
430 which
<< ObjectRect::Action
<< ObjectRect::Image
;
431 deleteObjectRects( m_rects
, which
);
434 * Rotate the object rects of the page.
436 const QMatrix matrix
= d
->rotationMatrix();
438 QLinkedList
< ObjectRect
* >::const_iterator objectIt
= rects
.begin(), end
= rects
.end();
439 for ( ; objectIt
!= end
; ++objectIt
)
440 (*objectIt
)->transform( matrix
);
445 void PagePrivate::setHighlight( int s_id
, RegularAreaRect
*rect
, const QColor
& color
)
447 HighlightAreaRect
* hr
= new HighlightAreaRect(rect
);
451 m_page
->m_highlights
.append( hr
);
454 void PagePrivate::setTextSelections( RegularAreaRect
*r
, const QColor
& color
)
456 deleteTextSelections();
459 HighlightAreaRect
* hr
= new HighlightAreaRect( r
);
462 m_textSelections
= hr
;
467 void Page::setSourceReferences( const QLinkedList
< SourceRefObjectRect
* > & refRects
)
469 deleteSourceReferences();
470 foreach( SourceRefObjectRect
* rect
, refRects
)
474 void Page::setDuration( double seconds
)
476 d
->m_duration
= seconds
;
479 double Page::duration() const
481 return d
->m_duration
;
484 void Page::setLabel( const QString
& label
)
489 QString
Page::label() const
494 const RegularAreaRect
* Page::textSelection() const
496 return d
->m_textSelections
;
499 QColor
Page::textSelectionColor() const
501 return d
->m_textSelections
? d
->m_textSelections
->color
: QColor();
504 void Page::addAnnotation( Annotation
* annotation
)
506 //uniqueName: okular-PAGENUM-ID
507 if(annotation
->uniqueName().isEmpty())
509 QString uniqueName
= "okular-";
510 uniqueName
+= ( QString::number(d
->m_number
) + '-' + QString::number(++(d
->m_maxuniqueNum
)) );
512 kDebug(OkularDebug
).nospace() << "inc m_maxuniqueNum=" << d
->m_maxuniqueNum
;
514 annotation
->setUniqueName( uniqueName
);
516 annotation
->d_ptr
->m_page
= d
;
517 m_annotations
.append( annotation
);
519 AnnotationObjectRect
*rect
= new AnnotationObjectRect( annotation
);
521 // Rotate the annotation on the page.
522 const QMatrix matrix
= d
->rotationMatrix();
523 annotation
->d_ptr
->baseTransform( matrix
.inverted() );
524 annotation
->d_ptr
->annotationTransform( matrix
);
526 m_rects
.append( rect
);
529 void PagePrivate::modifyAnnotation(Annotation
* newannotation
)
534 QLinkedList
< Annotation
* >::iterator aIt
= m_page
->m_annotations
.begin(), aEnd
= m_page
->m_annotations
.end();
535 for ( ; aIt
!= aEnd
; ++aIt
)
537 if((*aIt
)==newannotation
)
538 return; //modified already
539 if((*aIt
) && (*aIt
)->uniqueName()==newannotation
->uniqueName())
541 int rectfound
= false;
542 QLinkedList
< ObjectRect
* >::iterator it
= m_page
->m_rects
.begin(), end
= m_page
->m_rects
.end();
543 for ( ; it
!= end
&& !rectfound
; ++it
)
544 if ( ( (*it
)->objectType() == ObjectRect::OAnnotation
) && ( (*it
)->object() == (*aIt
) ) )
547 *it
= new AnnotationObjectRect( newannotation
);
551 *aIt
= newannotation
;
557 bool Page::removeAnnotation( Annotation
* annotation
)
559 if ( !annotation
|| ( annotation
->flags() & Annotation::DenyDelete
) )
562 QLinkedList
< Annotation
* >::iterator aIt
= m_annotations
.begin(), aEnd
= m_annotations
.end();
563 for ( ; aIt
!= aEnd
; ++aIt
)
565 if((*aIt
) && (*aIt
)->uniqueName()==annotation
->uniqueName())
567 int rectfound
= false;
568 QLinkedList
< ObjectRect
* >::iterator it
= m_rects
.begin(), end
= m_rects
.end();
569 for ( ; it
!= end
&& !rectfound
; ++it
)
570 if ( ( (*it
)->objectType() == ObjectRect::OAnnotation
) && ( (*it
)->object() == (*aIt
) ) )
573 it
= m_rects
.erase( it
);
576 kDebug(OkularDebug
) << "removed annotation:" << annotation
->uniqueName();
578 m_annotations
.erase( aIt
);
586 void Page::setTransition( PageTransition
* transition
)
588 delete d
->m_transition
;
589 d
->m_transition
= transition
;
592 void Page::setPageAction( PageAction action
, Action
* link
)
597 delete d
->m_openingAction
;
598 d
->m_openingAction
= link
;
601 delete d
->m_closingAction
;
602 d
->m_closingAction
= link
;
607 void Page::setFormFields( const QLinkedList
< FormField
* >& fields
)
609 qDeleteAll( d
->formfields
);
610 d
->formfields
= fields
;
611 QLinkedList
< FormField
* >::const_iterator it
= d
->formfields
.begin(), itEnd
= d
->formfields
.end();
612 for ( ; it
!= itEnd
; ++it
)
614 (*it
)->d_ptr
->setDefault();
618 void Page::deletePixmap( int id
)
620 PagePrivate::PixmapObject object
= d
->m_pixmaps
.take( id
);
621 delete object
.m_pixmap
;
624 void Page::deletePixmaps()
626 QMapIterator
< int, PagePrivate::PixmapObject
> it( d
->m_pixmaps
);
627 while ( it
.hasNext() ) {
629 delete it
.value().m_pixmap
;
632 d
->m_pixmaps
.clear();
635 void Page::deleteRects()
637 // delete ObjectRects of type Link and Image
638 QSet
<ObjectRect::ObjectType
> which
;
639 which
<< ObjectRect::Action
<< ObjectRect::Image
;
640 deleteObjectRects( m_rects
, which
);
643 void PagePrivate::deleteHighlights( int s_id
)
645 // delete highlights by ID
646 QLinkedList
< HighlightAreaRect
* >::iterator it
= m_page
->m_highlights
.begin(), end
= m_page
->m_highlights
.end();
649 HighlightAreaRect
* highlight
= *it
;
650 if ( s_id
== -1 || highlight
->s_id
== s_id
)
652 it
= m_page
->m_highlights
.erase( it
);
660 void PagePrivate::deleteTextSelections()
662 delete m_textSelections
;
663 m_textSelections
= 0;
666 void Page::deleteSourceReferences()
668 deleteObjectRects( m_rects
, QSet
<ObjectRect::ObjectType
>() << ObjectRect::SourceRef
);
671 void Page::deleteAnnotations()
673 // delete ObjectRects of type Annotation
674 deleteObjectRects( m_rects
, QSet
<ObjectRect::ObjectType
>() << ObjectRect::OAnnotation
);
675 // delete all stored annotations
676 QLinkedList
< Annotation
* >::const_iterator aIt
= m_annotations
.begin(), aEnd
= m_annotations
.end();
677 for ( ; aIt
!= aEnd
; ++aIt
)
679 m_annotations
.clear();
682 void PagePrivate::restoreLocalContents( const QDomNode
& pageNode
)
684 // iterate over all chilren (annotationList, ...)
685 QDomNode childNode
= pageNode
.firstChild();
686 while ( childNode
.isElement() )
688 QDomElement childElement
= childNode
.toElement();
689 childNode
= childNode
.nextSibling();
691 // parse annotationList child element
692 if ( childElement
.tagName() == "annotationList" )
699 // iterate over all annotations
700 QDomNode annotationNode
= childElement
.firstChild();
701 while( annotationNode
.isElement() )
703 // get annotation element and advance to next annot
704 QDomElement annotElement
= annotationNode
.toElement();
705 annotationNode
= annotationNode
.nextSibling();
707 // get annotation from the dom element
708 Annotation
* annotation
= AnnotationUtils::createAnnotation( annotElement
);
710 // append annotation to the list or show warning
713 annotation
->d_ptr
->m_page
= this;
714 m_page
->m_annotations
.append( annotation
);
715 m_page
->m_rects
.append( new AnnotationObjectRect( annotation
) );
716 int pos
= annotation
->uniqueName().lastIndexOf("-");
719 int uniqID
=annotation
->uniqueName().right(annotation
->uniqueName().length()-pos
-1).toInt();
720 if ( m_maxuniqueNum
< uniqID
)
721 m_maxuniqueNum
= uniqID
;
724 kDebug(OkularDebug
) << "restored annot:" << annotation
->uniqueName();
727 kWarning(OkularDebug
).nospace() << "page (" << m_number
<< "): can't restore an annotation from XML.";
730 kDebug(OkularDebug
).nospace() << "annots: XML Load time: " << time
.elapsed() << "ms";
733 // parse formList child element
734 else if ( childElement
.tagName() == "forms" )
736 if ( formfields
.isEmpty() )
739 QHash
<int, FormField
*> hashedforms
;
740 QLinkedList
< FormField
* >::const_iterator fIt
= formfields
.begin(), fItEnd
= formfields
.end();
741 for ( ; fIt
!= fItEnd
; ++fIt
)
743 hashedforms
[(*fIt
)->id()] = (*fIt
);
746 // iterate over all forms
747 QDomNode formsNode
= childElement
.firstChild();
748 while( formsNode
.isElement() )
750 // get annotation element and advance to next annot
751 QDomElement formElement
= formsNode
.toElement();
752 formsNode
= formsNode
.nextSibling();
754 if ( formElement
.tagName() != "form" )
758 int index
= formElement
.attribute( "id" ).toInt( &ok
);
762 QHash
<int, FormField
*>::const_iterator wantedIt
= hashedforms
.find( index
);
763 if ( wantedIt
== hashedforms
.end() )
766 QString value
= formElement
.attribute( "value" );
767 (*wantedIt
)->d_ptr
->setValue( value
);
773 void PagePrivate::saveLocalContents( QDomNode
& parentNode
, QDomDocument
& document
) const
775 // only add a node if there is some stuff to write into
776 if ( m_page
->m_annotations
.isEmpty() && formfields
.isEmpty() )
779 // create the page node and set the 'number' attribute
780 QDomElement pageElement
= document
.createElement( "page" );
781 pageElement
.setAttribute( "number", m_number
);
784 // add bookmark info if is bookmarked
785 if ( d
->m_bookmarked
)
787 // create the pageElement's 'bookmark' child
788 QDomElement bookmarkElement
= document
.createElement( "bookmark" );
789 pageElement
.appendChild( bookmarkElement
);
791 // add attributes to the element
792 //bookmarkElement.setAttribute( "name", bookmark name );
796 // add annotations info if has got any
797 if ( !m_page
->m_annotations
.isEmpty() )
799 // create the annotationList
800 QDomElement annotListElement
= document
.createElement( "annotationList" );
802 // add every annotation to the annotationList
803 QLinkedList
< Annotation
* >::const_iterator aIt
= m_page
->m_annotations
.begin(), aEnd
= m_page
->m_annotations
.end();
804 for ( ; aIt
!= aEnd
; ++aIt
)
807 const Annotation
* a
= *aIt
;
808 // only save okular annotations (not the embedded in file ones)
809 if ( !(a
->flags() & Annotation::External
) )
811 // append an filled-up element called 'annotation' to the list
812 QDomElement annElement
= document
.createElement( "annotation" );
813 AnnotationUtils::storeAnnotation( a
, annElement
, document
);
814 annotListElement
.appendChild( annElement
);
815 kDebug(OkularDebug
) << "save annotation:" << a
->uniqueName();
819 // append the annotationList element if annotations have been set
820 if ( annotListElement
.hasChildNodes() )
821 pageElement
.appendChild( annotListElement
);
824 // add forms info if has got any
825 if ( !formfields
.isEmpty() )
827 // create the formList
828 QDomElement formListElement
= document
.createElement( "forms" );
830 // add every form data to the formList
831 QLinkedList
< FormField
* >::const_iterator fIt
= formfields
.begin(), fItEnd
= formfields
.end();
832 for ( ; fIt
!= fItEnd
; ++fIt
)
834 // get the form field
835 const FormField
* f
= *fIt
;
837 QString newvalue
= f
->d_ptr
->value();
838 if ( f
->d_ptr
->m_default
== newvalue
)
841 // append an filled-up element called 'annotation' to the list
842 QDomElement formElement
= document
.createElement( "form" );
843 formElement
.setAttribute( "id", f
->id() );
844 formElement
.setAttribute( "value", newvalue
);
845 formListElement
.appendChild( formElement
);
848 // append the annotationList element if annotations have been set
849 if ( formListElement
.hasChildNodes() )
850 pageElement
.appendChild( formListElement
);
853 // append the page element only if has children
854 if ( pageElement
.hasChildNodes() )
855 parentNode
.appendChild( pageElement
);
858 const QPixmap
* Page::_o_nearestPixmap( int pixID
, int w
, int h
) const
862 const QPixmap
* pixmap
= 0;
864 // if a pixmap is present for given id, use it
865 QMap
< int, PagePrivate::PixmapObject
>::const_iterator itPixmap
= d
->m_pixmaps
.find( pixID
);
866 if ( itPixmap
!= d
->m_pixmaps
.end() )
867 pixmap
= itPixmap
.value().m_pixmap
;
868 // else find the closest match using pixmaps of other IDs (great optim!)
869 else if ( !d
->m_pixmaps
.isEmpty() )
871 int minDistance
= -1;
872 QMap
< int, PagePrivate::PixmapObject
>::const_iterator it
= d
->m_pixmaps
.begin(), end
= d
->m_pixmaps
.end();
873 for ( ; it
!= end
; ++it
)
875 int pixWidth
= (*it
).m_pixmap
->width(),
876 distance
= pixWidth
> w
? pixWidth
- w
: w
- pixWidth
;
877 if ( minDistance
== -1 || distance
< minDistance
)
879 pixmap
= (*it
).m_pixmap
;
880 minDistance
= distance
;