source: terepaima/terepaima-0.4.16/sources/documentview.cpp @ f626f58

desarrollostretch
Last change on this file since f626f58 was 1f4adec, checked in by aosorio <aosorio@…>, 8 years ago

Agregado proyecto base, esto luego del dh_make -f

  • Property mode set to 100644
File size: 69.2 KB
Line 
1/*
2
3Copyright 2014 S. Razi Alavizadeh
4Copyright 2013 Thomas Etter
5Copyright 2012-2015 Adam Reichold
6Copyright 2014 Dorian Scholz
7
8This file is part of qpdfview.
9
10qpdfview is free software: you can redistribute it and/or modify
11it under the terms of the GNU General Public License as published by
12the Free Software Foundation, either version 2 of the License, or
13(at your option) any later version.
14
15qpdfview is distributed in the hope that it will be useful,
16but WITHOUT ANY WARRANTY; without even the implied warranty of
17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with qpdfview.  If not, see <http://www.gnu.org/licenses/>.
22
23*/
24
25#include "documentview.h"
26
27#include <QApplication>
28#include <QInputDialog>
29#include <QDebug>
30#include <QDesktopWidget>
31#include <QDesktopServices>
32#include <QDir>
33#include <QFileSystemWatcher>
34#include <QKeyEvent>
35#include <qmath.h>
36#include <QMenu>
37#include <QMessageBox>
38#include <QPrintEngine>
39#include <QProcess>
40#include <QProgressDialog>
41#include <QScrollBar>
42#include <QTemporaryFile>
43#include <QTimer>
44#include <QUrl>
45
46#ifdef WITH_CUPS
47
48#include <cups/cups.h>
49#include <cups/ppd.h>
50
51#endif // WITH_CUPS
52
53#ifdef WITH_SYNCTEX
54
55#include <synctex_parser.h>
56
57#endif // WITH_SYNCTEX
58
59#include "settings.h"
60#include "model.h"
61#include "pluginhandler.h"
62#include "shortcuthandler.h"
63#include "pageitem.h"
64#include "thumbnailitem.h"
65#include "presentationview.h"
66#include "searchmodel.h"
67#include "searchtask.h"
68#include "miscellaneous.h"
69#include "documentlayout.h"
70
71namespace
72{
73
74using namespace qpdfview;
75
76// taken from http://rosettacode.org/wiki/Roman_numerals/Decode#C.2B.2B
77int romanToInt(const QString& text)
78{
79    if(text.size() == 1)
80    {
81        switch(text.at(0).toLower().toLatin1())
82        {
83        case 'i': return 1;
84        case 'v': return 5;
85        case 'x': return 10;
86        case 'l': return 50;
87        case 'c': return 100;
88        case 'd': return 500;
89        case 'm': return 1000;
90        }
91
92        return 0;
93    }
94
95    int result = 0;
96    int previous = 0, current = 0;
97
98    for(int i = text.size() - 1; i >= 0; --i)
99    {
100        current = romanToInt(text.at(i));
101
102        result += current < previous ? -current : current;
103
104        previous = current;
105    }
106
107    return result;
108}
109
110// taken from http://rosettacode.org/wiki/Roman_numerals/Encode#C.2B.2B
111QString intToRoman(int number)
112{
113    struct romandata_t
114    {
115        int value;
116        char const* numeral;
117    };
118
119    static const romandata_t romandata[] =
120    {
121        { 1000, "m" },
122        { 900, "cm" },
123        { 500, "d" },
124        { 400, "cd" },
125        { 100, "c" },
126        { 90, "xc" },
127        { 50, "l" },
128        { 40, "xl" },
129        { 10, "x" },
130        { 9, "ix" },
131        { 5, "v" },
132        { 4, "iv" },
133        { 1, "i" },
134        { 0, NULL }
135    };
136
137    if(number >= 4000)
138    {
139        return QLatin1String("?");
140    }
141
142    QString result;
143
144    for(const romandata_t* current = romandata; current->value > 0; ++current)
145    {
146        while(number >= current->value)
147        {
148            number -= current->value;
149            result += QLatin1String(current->numeral);
150        }
151    }
152
153    return result;
154}
155
156bool copyFile(QFile& source, QFile& destination)
157{
158    const qint64 maxSize = 4096;
159    qint64 size = -1;
160
161    QScopedArrayPointer< char > buffer(new char[maxSize]);
162
163    do
164    {
165        if((size = source.read(buffer.data(), maxSize)) < 0)
166        {
167            return false;
168        }
169
170        if(destination.write(buffer.data(), size) < 0)
171        {
172            return false;
173        }
174    }
175    while(size > 0);
176
177    return true;
178}
179
180#ifdef WITH_CUPS
181
182struct RemovePpdFileDeleter
183{
184    static inline void cleanup(const char* ppdFileName) { if(ppdFileName != 0) { QFile::remove(ppdFileName); } }
185};
186
187struct ClosePpdFileDeleter
188{
189    static inline void cleanup(ppd_file_t* ppdFile) { if(ppdFile != 0) { ppdClose(ppdFile); } }
190};
191
192int addCMYKorRGBColorModel(cups_dest_t* dest, int num_options, cups_option_t** options)
193{
194    QScopedPointer< const char, RemovePpdFileDeleter > ppdFileName(cupsGetPPD(dest->name));
195
196    if(ppdFileName.isNull())
197    {
198        return num_options;
199    }
200
201    QScopedPointer< ppd_file_t, ClosePpdFileDeleter > ppdFile(ppdOpenFile(ppdFileName.data()));
202
203    if(ppdFile.isNull())
204    {
205        return num_options;
206    }
207
208    ppd_option_t* colorModel = ppdFindOption(ppdFile.data(), "ColorModel");
209
210    if(colorModel == 0)
211    {
212        return num_options;
213    }
214
215    for(int index = 0; index < colorModel->num_choices; ++index)
216    {
217        if(qstrcmp(colorModel->choices[index].choice, "CMYK") == 0)
218        {
219            return cupsAddOption("ColorModel", "CMYK", num_options, options);
220        }
221    }
222
223    for(int index = 0; index < colorModel->num_choices; ++index)
224    {
225        if(qstrcmp(colorModel->choices[index].choice, "RGB") == 0)
226        {
227            return cupsAddOption("ColorModel", "RGB", num_options, options);
228        }
229    }
230
231    return num_options;
232}
233
234#endif // WITH_CUPS
235
236void adjustFileTemplateSuffix(QTemporaryFile& temporaryFile, const QString& suffix)
237{
238    temporaryFile.setFileTemplate(temporaryFile.fileTemplate() + QLatin1String(".") + suffix);
239}
240
241bool modifiersUseMouseButton(Settings* settings, Qt::MouseButton mouseButton)
242{
243    return ((settings->documentView().zoomModifiers() | settings->documentView().rotateModifiers() | settings->documentView().scrollModifiers()) & mouseButton) != 0;
244}
245
246inline int pageOfResult(const QModelIndex& index)
247{
248    return index.data(SearchModel::PageRole).toInt();
249}
250
251inline QRectF rectOfResult(const QModelIndex& index)
252{
253    return index.data(SearchModel::RectRole).toRectF();
254}
255
256inline void adjustScaleFactor(RenderParam& renderParam, qreal scaleFactor)
257{
258    if(!qFuzzyCompare(renderParam.scaleFactor(), scaleFactor))
259    {
260        renderParam.setScaleFactor(scaleFactor);
261    }
262}
263
264inline void setValueIfNotVisible(QScrollBar* scrollBar, int value)
265{
266    if(value < scrollBar->value() || value > scrollBar->value() + scrollBar->pageStep())
267    {
268        scrollBar->setValue(value);
269    }
270}
271
272void saveExpandedPaths(const QAbstractItemModel* model, QSet< QString >& paths, const QModelIndex& index, QString path)
273{
274    path += index.data(Qt::DisplayRole).toString();
275
276    if(model->data(index, Model::Document::ExpansionRole).toBool())
277    {
278        paths.insert(path);
279    }
280
281    for(int row = 0, rowCount = model->rowCount(index); row < rowCount; ++row)
282    {
283        saveExpandedPaths(model, paths, model->index(row, 0, index), path);
284    }
285}
286
287void restoreExpandedPaths(QAbstractItemModel* model, const QSet< QString >& paths, const QModelIndex& index, QString path)
288{
289    path += index.data(Qt::DisplayRole).toString();
290
291    if(paths.contains(path))
292    {
293        model->setData(index, true, Model::Document::ExpansionRole);
294    }
295
296    for(int row = 0, rowCount = model->rowCount(index); row < rowCount; ++row)
297    {
298        restoreExpandedPaths(model, paths, model->index(row, 0, index), path);
299    }
300}
301
302} // anonymous
303
304namespace qpdfview
305{
306
307Settings* DocumentView::s_settings = 0;
308ShortcutHandler* DocumentView::s_shortcutHandler = 0;
309SearchModel* DocumentView::s_searchModel = 0;
310
311DocumentView::DocumentView(QWidget* parent) : QGraphicsView(parent),
312    m_autoRefreshWatcher(0),
313    m_autoRefreshTimer(0),
314    m_prefetchTimer(0),
315    m_document(0),
316    m_pages(),
317    m_fileInfo(),
318    m_wasModified(false),
319    m_currentPage(-1),
320    m_firstPage(-1),
321    m_past(),
322    m_future(),
323    m_layout(new SinglePageLayout),
324    m_continuousMode(false),
325    m_scaleMode(ScaleFactorMode),
326    m_scaleFactor(1.0),
327    m_rotation(RotateBy0),
328    m_renderFlags(0),
329    m_highlightAll(false),
330    m_rubberBandMode(ModifiersMode),
331    m_pageItems(),
332    m_thumbnailItems(),
333    m_highlight(0),
334    m_thumbnailsViewportSize(),
335    m_thumbnailsOrientation(Qt::Vertical),
336    m_thumbnailsScene(0),
337    m_outlineModel(0),
338    m_propertiesModel(0),
339    m_currentResult(),
340    m_searchTask(0)
341{
342    if(s_settings == 0)
343    {
344        s_settings = Settings::instance();
345    }
346
347    if(s_shortcutHandler == 0)
348    {
349        s_shortcutHandler = ShortcutHandler::instance();
350    }
351
352    if(s_searchModel == 0)
353    {
354        s_searchModel = SearchModel::instance();
355    }
356
357    setScene(new QGraphicsScene(this));
358
359    setAcceptDrops(false);
360    setDragMode(QGraphicsView::ScrollHandDrag);
361
362    reconnectVerticalScrollBar();
363
364    m_thumbnailsScene = new QGraphicsScene(this);
365
366    m_outlineModel = new QStandardItemModel(this);
367    m_propertiesModel = new QStandardItemModel(this);
368
369    // highlight
370
371    m_highlight = new QGraphicsRectItem();
372    m_highlight->setGraphicsEffect(new GraphicsCompositionModeEffect(QPainter::CompositionMode_Multiply, this));
373
374    m_highlight->setVisible(false);
375    scene()->addItem(m_highlight);
376
377    // search
378
379    m_searchTask = new SearchTask(this);
380
381    connect(m_searchTask, SIGNAL(finished()), SIGNAL(searchFinished()));
382
383    connect(m_searchTask, SIGNAL(progressChanged(int)), SLOT(on_searchTask_progressChanged(int)));
384    connect(m_searchTask, SIGNAL(resultsReady(int,QList<QRectF>)), SLOT(on_searchTask_resultsReady(int,QList<QRectF>)));
385
386    // auto-refresh
387
388    m_autoRefreshWatcher = new QFileSystemWatcher(this);
389
390    m_autoRefreshTimer = new QTimer(this);
391    m_autoRefreshTimer->setInterval(s_settings->documentView().autoRefreshTimeout());
392    m_autoRefreshTimer->setSingleShot(true);
393
394    connect(m_autoRefreshWatcher, SIGNAL(fileChanged(QString)), m_autoRefreshTimer, SLOT(start()));
395
396    connect(m_autoRefreshTimer, SIGNAL(timeout()), this, SLOT(on_autoRefresh_timeout()));
397
398    // prefetch
399
400    m_prefetchTimer = new QTimer(this);
401    m_prefetchTimer->setInterval(s_settings->documentView().prefetchTimeout());
402    m_prefetchTimer->setSingleShot(true);
403
404    connect(this, SIGNAL(currentPageChanged(int)), m_prefetchTimer, SLOT(start()));
405    connect(this, SIGNAL(layoutModeChanged(LayoutMode)), m_prefetchTimer, SLOT(start()));
406    connect(this, SIGNAL(scaleModeChanged(ScaleMode)), m_prefetchTimer, SLOT(start()));
407    connect(this, SIGNAL(scaleFactorChanged(qreal)), m_prefetchTimer, SLOT(start()));
408    connect(this, SIGNAL(rotationChanged(Rotation)), m_prefetchTimer, SLOT(start()));
409    connect(this, SIGNAL(invertColorsChanged(bool)), m_prefetchTimer, SLOT(start()));
410
411    connect(m_prefetchTimer, SIGNAL(timeout()), SLOT(on_prefetch_timeout()));
412
413    // settings
414
415    m_continuousMode = s_settings->documentView().continuousMode();
416    m_layout.reset(DocumentLayout::fromLayoutMode(s_settings->documentView().layoutMode()));
417    m_rightToLeftMode = s_settings->documentView().rightToLeftMode();
418
419    m_scaleMode = s_settings->documentView().scaleMode();
420    m_scaleFactor = s_settings->documentView().scaleFactor();
421    m_rotation = s_settings->documentView().rotation();
422
423    if(s_settings->documentView().invertColors())
424    {
425        m_renderFlags |= InvertColors;
426    }
427
428    if(s_settings->documentView().convertToGrayscale())
429    {
430        m_renderFlags |= ConvertToGrayscale;
431    }
432
433    if(s_settings->documentView().trimMargins())
434    {
435        m_renderFlags |= TrimMargins;
436    }
437
438    switch(s_settings->documentView().compositionMode())
439    {
440    default:
441    case DefaultCompositionMode:
442        break;
443    case DarkenWithPaperColorMode:
444        m_renderFlags |= DarkenWithPaperColor;
445        break;
446    case LightenWithPaperColorMode:
447        m_renderFlags |= LightenWithPaperColor;
448        break;
449    }
450
451    m_highlightAll = s_settings->documentView().highlightAll();
452}
453
454DocumentView::~DocumentView()
455{
456    m_searchTask->cancel();
457    m_searchTask->wait();
458
459    s_searchModel->clearResults(this);
460
461    qDeleteAll(m_pageItems);
462    qDeleteAll(m_thumbnailItems);
463
464    qDeleteAll(m_pages);
465    delete m_document;
466}
467
468void DocumentView::setFirstPage(int firstPage)
469{
470    if(m_firstPage != firstPage)
471    {
472        m_firstPage = firstPage;
473
474        for(int index = 0; index < m_thumbnailItems.count(); ++index)
475        {
476            m_thumbnailItems.at(index)->setText(pageLabelFromNumber(index + 1));
477        }
478
479        prepareThumbnailsScene();
480
481        if(m_outlineModel->invisibleRootItem()->data().toBool())
482        {
483            loadFallbackOutline();
484        }
485
486        emit currentPageChanged(m_currentPage);
487    }
488}
489
490QString DocumentView::defaultPageLabelFromNumber(int number) const
491{
492    QLocale modifiedLocale = locale();
493
494    modifiedLocale.setNumberOptions(modifiedLocale.numberOptions() | QLocale::OmitGroupSeparator);
495
496    return modifiedLocale.toString(number);
497}
498
499QString DocumentView::pageLabelFromNumber(int number) const
500{
501    QString label;
502
503    if(hasFrontMatter())
504    {
505        if(number < m_firstPage)
506        {
507            label = number < 4000 ? intToRoman(number) : defaultPageLabelFromNumber(-number);
508        }
509        else
510        {
511            label = defaultPageLabelFromNumber(number - m_firstPage + 1);
512        }
513    }
514    else if(number >= 1 && number <= m_pages.count())
515    {
516        const QString& pageLabel = m_pages.at(number - 1)->label();
517
518        if(number != pageLabel.toInt())
519        {
520            label = pageLabel;
521        }
522    }
523
524    if(label.isEmpty())
525    {
526        label = defaultPageLabelFromNumber(number);
527    }
528
529    return label;
530}
531
532int DocumentView::pageNumberFromLabel(const QString& label) const
533{
534    if(hasFrontMatter())
535    {
536        bool ok = false;
537        int value = locale().toInt(label, &ok);
538
539        if(ok)
540        {
541            if(value < 0)
542            {
543                value = -value; // front matter
544            }
545            else
546            {
547                value = value + m_firstPage - 1; // body matter
548            }
549        }
550        else
551        {
552            value = romanToInt(label);
553        }
554
555        return value;
556    }
557
558    for(int index = 0; index < m_pages.count(); ++index)
559    {
560        if(m_pages.at(index)->label() == label)
561        {
562            return index + 1;
563        }
564    }
565
566    return locale().toInt(label);
567}
568
569QString DocumentView::title() const
570{
571    QString title;
572
573    if(s_settings->mainWindow().documentTitleAsTabTitle())
574    {
575        for(int row = 0, rowCount = m_propertiesModel->rowCount(); row < rowCount; ++row)
576        {
577            const QStandardItem* keyItem = m_propertiesModel->item(row, 0);
578            const QStandardItem* valueItem = m_propertiesModel->item(row, 1);
579
580            if(keyItem != 0 && valueItem != 0 && QLatin1String("Title") == keyItem->text())
581            {
582                title = valueItem->text();
583                break;
584            }
585        }
586    }
587
588    if(title.isEmpty())
589    {
590        title = m_fileInfo.completeBaseName();
591    }
592
593    return title;
594}
595
596QStringList DocumentView::openFilter()
597{
598    return PluginHandler::openFilter();
599}
600
601QStringList DocumentView::saveFilter() const
602{
603    return m_document->saveFilter();
604}
605
606bool DocumentView::canSave() const
607{
608    return m_document->canSave();
609}
610
611void DocumentView::setContinuousMode(bool continuousMode)
612{
613    if(m_continuousMode != continuousMode)
614    {
615        m_continuousMode = continuousMode;
616
617        qreal left = 0.0, top = 0.0;
618        saveLeftAndTop(left, top);
619
620        adjustScrollBarPolicy();
621
622        prepareView(left, top);
623
624        emit continuousModeChanged(m_continuousMode);
625
626        s_settings->documentView().setContinuousMode(m_continuousMode);
627    }
628}
629
630LayoutMode DocumentView::layoutMode() const
631{
632    return m_layout->layoutMode();
633}
634
635void DocumentView::setLayoutMode(LayoutMode layoutMode)
636{
637    if(m_layout->layoutMode() != layoutMode && layoutMode >= 0 && layoutMode < NumberOfLayoutModes)
638    {
639        m_layout.reset(DocumentLayout::fromLayoutMode(layoutMode));
640
641        if(m_currentPage != m_layout->currentPage(m_currentPage))
642        {
643            m_currentPage = m_layout->currentPage(m_currentPage);
644
645            emit currentPageChanged(m_currentPage);
646        }
647
648        prepareScene();
649        prepareView();
650
651        emit layoutModeChanged(layoutMode);
652
653        s_settings->documentView().setLayoutMode(layoutMode);
654    }
655}
656
657void DocumentView::setRightToLeftMode(bool rightToLeftMode)
658{
659    if(m_rightToLeftMode != rightToLeftMode)
660    {
661        m_rightToLeftMode = rightToLeftMode;
662
663        prepareScene();
664        prepareView();
665
666        emit rightToLeftModeChanged(m_rightToLeftMode);
667
668        s_settings->documentView().setRightToLeftMode(m_rightToLeftMode);
669    }
670}
671
672void DocumentView::setScaleMode(ScaleMode scaleMode)
673{
674    if(m_scaleMode != scaleMode && scaleMode >= 0 && scaleMode < NumberOfScaleModes)
675    {
676        m_scaleMode = scaleMode;
677
678        qreal left = 0.0, top = 0.0;
679        saveLeftAndTop(left, top);
680
681        adjustScrollBarPolicy();
682
683        prepareScene();
684        prepareView(left, top);
685
686        emit scaleModeChanged(m_scaleMode);
687
688        s_settings->documentView().setScaleMode(m_scaleMode);
689    }
690}
691
692void DocumentView::setScaleFactor(qreal scaleFactor)
693{
694    if(!qFuzzyCompare(m_scaleFactor, scaleFactor)
695            && scaleFactor >= s_settings->documentView().minimumScaleFactor()
696            && scaleFactor <= s_settings->documentView().maximumScaleFactor())
697    {
698        m_scaleFactor = scaleFactor;
699
700        if(m_scaleMode == ScaleFactorMode)
701        {
702            qreal left = 0.0, top = 0.0;
703            saveLeftAndTop(left, top);
704
705            prepareScene();
706            prepareView(left, top);
707        }
708
709        emit scaleFactorChanged(m_scaleFactor);
710
711        s_settings->documentView().setScaleFactor(m_scaleFactor);
712    }
713}
714
715void DocumentView::setRotation(Rotation rotation)
716{
717    if(m_rotation != rotation && rotation >= 0 && rotation < NumberOfRotations)
718    {
719        m_rotation = rotation;
720
721        prepareScene();
722        prepareView();
723
724        emit rotationChanged(m_rotation);
725
726        s_settings->documentView().setRotation(m_rotation);
727    }
728}
729
730void DocumentView::setRenderFlags(qpdfview::RenderFlags renderFlags)
731{
732    if(m_renderFlags != renderFlags)
733    {
734        const qpdfview::RenderFlags changedFlags = m_renderFlags ^ renderFlags;
735
736        m_renderFlags = renderFlags;
737
738        qreal left = 0.0, top = 0.0;
739        saveLeftAndTop(left, top);
740
741        prepareScene();
742        prepareView(left, top);
743
744        prepareThumbnailsScene();
745
746        if(changedFlags.testFlag(InvertColors))
747        {
748            prepareBackground();
749
750            emit invertColorsChanged(invertColors());
751
752            s_settings->documentView().setInvertColors(invertColors());
753        }
754
755        if(changedFlags.testFlag(ConvertToGrayscale))
756        {
757            emit convertToGrayscaleChanged(convertToGrayscale());
758
759            s_settings->documentView().setConvertToGrayscale(convertToGrayscale());
760        }
761
762        if(changedFlags.testFlag(TrimMargins))
763        {
764            emit trimMarginsChanged(trimMargins());
765
766            s_settings->documentView().setTrimMargins(trimMargins());
767        }
768
769        if(changedFlags.testFlag(DarkenWithPaperColor) || changedFlags.testFlag(LightenWithPaperColor))
770        {
771            emit compositionModeChanged(compositionMode());
772
773            s_settings->documentView().setCompositionMode(compositionMode());
774        }
775
776        emit renderFlagsChanged(m_renderFlags);
777    }
778}
779
780void DocumentView::setRenderFlag(qpdfview::RenderFlag renderFlag, bool enabled)
781{
782    if(enabled)
783    {
784        setRenderFlags(m_renderFlags | renderFlag);
785    }
786    else
787    {
788        setRenderFlags(m_renderFlags & ~renderFlag);
789    }
790}
791
792CompositionMode DocumentView::compositionMode() const
793{
794    if(m_renderFlags.testFlag(DarkenWithPaperColor))
795    {
796        return DarkenWithPaperColorMode;
797    }
798    else if(m_renderFlags.testFlag(LightenWithPaperColor))
799    {
800        return LightenWithPaperColorMode;
801    }
802    else
803    {
804        return DefaultCompositionMode;
805    }
806}
807
808void DocumentView::setCompositionMode(CompositionMode compositionMode)
809{
810    switch(compositionMode)
811    {
812    default:
813    case DefaultCompositionMode:
814        setRenderFlags((renderFlags() & ~DarkenWithPaperColor) & ~LightenWithPaperColor);
815        break;
816    case DarkenWithPaperColorMode:
817        setRenderFlags((renderFlags() | DarkenWithPaperColor) & ~LightenWithPaperColor);
818        break;
819    case LightenWithPaperColorMode:
820        setRenderFlags((renderFlags() & ~DarkenWithPaperColor) | LightenWithPaperColor);
821        break;
822    }
823}
824
825void DocumentView::setHighlightAll(bool highlightAll)
826{
827    if(m_highlightAll != highlightAll)
828    {
829        m_highlightAll = highlightAll;
830
831        if(m_highlightAll)
832        {
833            for(int index = 0; index < m_pages.count(); ++index)
834            {
835                const QList< QRectF >& results = s_searchModel->resultsOnPage(this, index + 1);
836
837                m_pageItems.at(index)->setHighlights(results);
838                m_thumbnailItems.at(index)->setHighlights(results);
839            }
840        }
841        else
842        {
843            for(int index = 0; index < m_pages.count(); ++index)
844            {
845                m_pageItems.at(index)->setHighlights(QList< QRectF >());
846                m_thumbnailItems.at(index)->setHighlights(QList< QRectF >());
847            }
848        }
849
850        emit highlightAllChanged(m_highlightAll);
851
852        s_settings->documentView().setHighlightAll(highlightAll);
853    }
854}
855
856void DocumentView::setRubberBandMode(RubberBandMode rubberBandMode)
857{
858    if(m_rubberBandMode != rubberBandMode && rubberBandMode >= 0 && rubberBandMode < NumberOfRubberBandModes)
859    {
860        m_rubberBandMode = rubberBandMode;
861
862        foreach(PageItem* page, m_pageItems)
863        {
864            page->setRubberBandMode(m_rubberBandMode);
865        }
866
867        emit rubberBandModeChanged(m_rubberBandMode);
868    }
869}
870
871void DocumentView::setThumbnailsViewportSize(const QSize& thumbnailsViewportSize)
872{
873    if(m_thumbnailsViewportSize != thumbnailsViewportSize)
874    {
875        m_thumbnailsViewportSize = thumbnailsViewportSize;
876
877        prepareThumbnailsScene();
878    }
879}
880
881void DocumentView::setThumbnailsOrientation(Qt::Orientation thumbnailsOrientation)
882{
883    if(m_thumbnailsOrientation != thumbnailsOrientation)
884    {
885        m_thumbnailsOrientation = thumbnailsOrientation;
886
887        prepareThumbnailsScene();
888    }
889}
890
891QStandardItemModel* DocumentView::fontsModel() const
892{
893    QStandardItemModel* fontsModel = new QStandardItemModel();
894
895    m_document->loadFonts(fontsModel);
896
897    return fontsModel;
898}
899
900bool DocumentView::searchWasCanceled() const
901{
902    return m_searchTask->wasCanceled();
903}
904
905int DocumentView::searchProgress() const
906{
907    return m_searchTask->progress();
908}
909
910QString DocumentView::searchText() const
911{
912    return m_searchTask->text();
913}
914
915bool DocumentView::searchMatchCase() const
916{
917    return m_searchTask->matchCase();
918}
919
920bool DocumentView::searchWholeWords() const
921{
922    return m_searchTask->wholeWords();
923}
924
925QPair< QString, QString > DocumentView::searchContext(int page, const QRectF& rect) const
926{
927    if(page < 1 || page > m_pages.size() || rect.isEmpty())
928    {
929        return qMakePair(QString(), QString());
930    }
931
932    // Fetch at most half of a line as centered on the given rectangle as possible.
933    const qreal pageWidth = m_pages.at(page - 1)->size().width();
934    const qreal width = qMax(rect.width(), pageWidth / qreal(2));
935    const qreal x = qBound(qreal(0), rect.x() + rect.width() / qreal(2) - width / qreal(2), pageWidth - width);
936
937    const QRectF surroundingRect(x, rect.top(), width, rect.height());
938
939    const QString& matchedText = m_pages.at(page - 1)->cachedText(rect);
940    const QString& surroundingText = m_pages.at(page - 1)->cachedText(surroundingRect);
941
942    return qMakePair(matchedText, surroundingText);
943}
944
945DocumentView::SourceLink DocumentView::sourceLink(const QPoint& pos)
946{
947    SourceLink sourceLink;
948
949#ifdef WITH_SYNCTEX
950
951    if(const PageItem* page = dynamic_cast< PageItem* >(itemAt(pos)))
952    {
953        if(synctex_scanner_t scanner = synctex_scanner_new_with_output_file(m_fileInfo.absoluteFilePath().toLocal8Bit(), 0, 1))
954        {
955            const int sourcePage = page->index() + 1;
956            const QPointF sourcePos = page->sourcePos(page->mapFromScene(mapToScene(pos)));
957
958            if(synctex_edit_query(scanner, sourcePage, sourcePos.x(), sourcePos.y()) > 0)
959            {
960                for(synctex_node_t node = synctex_next_result(scanner); node != 0; node = synctex_next_result(scanner))
961                {
962                    sourceLink.name = QString::fromLocal8Bit(synctex_scanner_get_name(scanner, synctex_node_tag(node)));
963                    sourceLink.line = qMax(synctex_node_line(node), 0);
964                    sourceLink.column = qMax(synctex_node_column(node), 0);
965                    break;
966                }
967            }
968
969            synctex_scanner_free(scanner);
970        }
971    }
972
973#else
974
975    Q_UNUSED(pos);
976
977#endif // WITH_SYNCTEX
978
979    return sourceLink;
980}
981
982void DocumentView::openInSourceEditor(const DocumentView::SourceLink& sourceLink)
983{
984    if(!s_settings->documentView().sourceEditor().isEmpty())
985    {
986        const QString absoluteFilePath = m_fileInfo.dir().absoluteFilePath(sourceLink.name);
987        const QString sourceEditorCommand = s_settings->documentView().sourceEditor().arg(absoluteFilePath, QString::number(sourceLink.line), QString::number(sourceLink.column));
988
989        QProcess::startDetached(sourceEditorCommand);
990    }
991    else
992    {
993        QMessageBox::information(this, tr("Information"), tr("The source editor has not been set."));
994    }
995}
996
997void DocumentView::show()
998{
999    QGraphicsView::show();
1000
1001    prepareView();
1002}
1003
1004bool DocumentView::open(const QString& filePath)
1005{
1006    Model::Document* document = PluginHandler::instance()->loadDocument(filePath);
1007
1008    if(document != 0)
1009    {
1010        QVector< Model::Page* > pages;
1011
1012        if(!checkDocument(filePath, document, pages))
1013        {
1014            delete document;
1015            qDeleteAll(pages);
1016
1017            return false;
1018        }
1019
1020        m_fileInfo.setFile(filePath);
1021        m_wasModified = false;
1022
1023        m_currentPage = 1;
1024
1025        m_past.clear();
1026        m_future.clear();
1027
1028        prepareDocument(document, pages);
1029
1030        loadDocumentDefaults();
1031
1032        adjustScrollBarPolicy();
1033
1034        prepareScene();
1035        prepareView();
1036
1037        prepareThumbnailsScene();
1038
1039        emit documentChanged();
1040
1041        emit numberOfPagesChanged(m_pages.count());
1042        emit currentPageChanged(m_currentPage);
1043
1044        emit canJumpChanged(false, false);
1045
1046        emit continuousModeChanged(m_continuousMode);
1047        emit layoutModeChanged(m_layout->layoutMode());
1048        emit rightToLeftModeChanged(m_rightToLeftMode);
1049    }
1050
1051    return document != 0;
1052}
1053
1054bool DocumentView::refresh()
1055{
1056    Model::Document* document = PluginHandler::instance()->loadDocument(m_fileInfo.filePath());
1057
1058    if(document != 0)
1059    {
1060        QVector< Model::Page* > pages;
1061
1062        if(!checkDocument(m_fileInfo.filePath(), document, pages))
1063        {
1064            delete document;
1065            qDeleteAll(pages);
1066
1067            return false;
1068        }
1069
1070        qreal left = 0.0, top = 0.0;
1071        saveLeftAndTop(left, top);
1072
1073        m_wasModified = false;
1074
1075        m_currentPage = qMin(m_currentPage, document->numberOfPages());
1076
1077        QSet< QString > expandedPaths;
1078        saveExpandedPaths(m_outlineModel, expandedPaths, QModelIndex(), QString());
1079
1080        prepareDocument(document, pages);
1081
1082        restoreExpandedPaths(m_outlineModel, expandedPaths, QModelIndex(), QString());
1083
1084        prepareScene();
1085        prepareView(left, top);
1086
1087        prepareThumbnailsScene();
1088
1089        emit documentChanged();
1090
1091        emit numberOfPagesChanged(m_pages.count());
1092        emit currentPageChanged(m_currentPage);
1093    }
1094
1095    return document != 0;
1096}
1097
1098bool DocumentView::save(const QString& filePath, bool withChanges)
1099{
1100    // Save document to temporary file...
1101    QTemporaryFile temporaryFile;
1102
1103    adjustFileTemplateSuffix(temporaryFile, QFileInfo(filePath).suffix());
1104
1105    if(!temporaryFile.open())
1106    {
1107        return false;
1108    }
1109
1110    temporaryFile.close();
1111
1112    if(!m_document->save(temporaryFile.fileName(), withChanges))
1113    {
1114        return false;
1115    }
1116
1117    // Copy from temporary file to actual file...
1118    QFile file(filePath);
1119
1120    if(!temporaryFile.open())
1121    {
1122        return false;
1123    }
1124
1125    if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
1126    {
1127        return false;
1128    }
1129
1130    if(!copyFile(temporaryFile, file))
1131    {
1132        return false;
1133    }
1134
1135    if(withChanges)
1136    {
1137        m_wasModified = false;
1138    }
1139
1140    return true;
1141}
1142
1143bool DocumentView::print(QPrinter* printer, const PrintOptions& printOptions)
1144{
1145    const int fromPage = printer->fromPage() != 0 ? printer->fromPage() : 1;
1146    const int toPage = printer->toPage() != 0 ? printer->toPage() : m_pages.count();
1147
1148#ifdef WITH_CUPS
1149
1150    if(m_document->canBePrintedUsingCUPS())
1151    {
1152        return printUsingCUPS(printer, printOptions, fromPage, toPage);
1153    }
1154
1155#endif // WITH_CUPS
1156
1157    return printUsingQt(printer, printOptions, fromPage, toPage);
1158}
1159
1160void DocumentView::previousPage()
1161{
1162    jumpToPage(m_layout->previousPage(m_currentPage));
1163}
1164
1165void DocumentView::nextPage()
1166{
1167    jumpToPage(m_layout->nextPage(m_currentPage, m_pages.count()));
1168}
1169
1170void DocumentView::firstPage()
1171{
1172    jumpToPage(1);
1173}
1174
1175void DocumentView::lastPage()
1176{
1177    jumpToPage(m_pages.count());
1178}
1179
1180void DocumentView::jumpToPage(int page, bool trackChange, qreal newLeft, qreal newTop)
1181{
1182    if(page >= 1 && page <= m_pages.count())
1183    {
1184        qreal left = 0.0, top = 0.0;
1185        saveLeftAndTop(left, top);
1186
1187        if(qIsNaN(newLeft))
1188        {
1189            newLeft = qBound(qreal(0.0), left, qreal(1.0));
1190        }
1191
1192        if(qIsNaN(newTop))
1193        {
1194            newTop = qBound(qreal(0.0), top, qreal(1.0));
1195        }
1196
1197        if(m_currentPage != m_layout->currentPage(page) || qAbs(left - newLeft) > 0.01 || qAbs(top - newTop) > 0.01)
1198        {
1199            if(trackChange)
1200            {
1201                m_past.append(Position(m_currentPage, left, top));
1202                m_future.clear();
1203
1204                emit canJumpChanged(true, false);
1205            }
1206
1207            m_currentPage = m_layout->currentPage(page);
1208
1209            prepareView(newLeft, newTop, false, page);
1210
1211            emit currentPageChanged(m_currentPage, trackChange);
1212        }
1213    }
1214}
1215
1216bool DocumentView::canJumpBackward() const
1217{
1218    return !m_past.isEmpty();
1219}
1220
1221void DocumentView::jumpBackward()
1222{
1223    if(!m_past.isEmpty())
1224    {
1225        qreal left = 0.0, top = 0.0;
1226        saveLeftAndTop(left, top);
1227
1228        m_future.prepend(Position(m_currentPage, left, top));
1229
1230        const Position pos = m_past.takeLast();
1231        jumpToPage(pos.page, false, pos.left, pos.top);
1232
1233        emit canJumpChanged(!m_past.isEmpty(), !m_future.isEmpty());
1234    }
1235}
1236
1237bool DocumentView::canJumpForward() const
1238{
1239    return !m_future.isEmpty();
1240}
1241
1242void DocumentView::jumpForward()
1243{
1244    if(!m_future.isEmpty())
1245    {
1246        qreal left = 0.0, top = 0.0;
1247        saveLeftAndTop(left, top);
1248
1249        m_past.append(Position(m_currentPage, left, top));
1250
1251        const Position pos = m_future.takeFirst();
1252        jumpToPage(pos.page, false, pos.left, pos.top);
1253
1254        emit canJumpChanged(!m_past.isEmpty(), !m_future.isEmpty());
1255    }
1256}
1257
1258void DocumentView::temporaryHighlight(int page, const QRectF& highlight)
1259{
1260    if(page >= 1 && page <= m_pages.count() && !highlight.isNull())
1261    {
1262        prepareHighlight(page - 1, highlight);
1263
1264        QTimer::singleShot(s_settings->documentView().highlightDuration(), this, SLOT(on_temporaryHighlight_timeout()));
1265    }
1266}
1267
1268void DocumentView::startSearch(const QString& text, bool matchCase, bool wholeWords)
1269{
1270    cancelSearch();
1271    clearResults();
1272
1273    m_searchTask->start(m_pages, text, matchCase, wholeWords, m_currentPage);
1274}
1275
1276void DocumentView::cancelSearch()
1277{
1278    m_searchTask->cancel();
1279    m_searchTask->wait();
1280}
1281
1282void DocumentView::clearResults()
1283{
1284    s_searchModel->clearResults(this);
1285
1286    m_currentResult = QModelIndex();
1287
1288    m_highlight->setVisible(false);
1289
1290    foreach(PageItem* page, m_pageItems)
1291    {
1292        page->setHighlights(QList< QRectF >());
1293    }
1294
1295    foreach(ThumbnailItem* page, m_thumbnailItems)
1296    {
1297        page->setHighlights(QList< QRectF >());
1298    }
1299
1300    if(s_settings->documentView().limitThumbnailsToResults())
1301    {
1302        prepareThumbnailsScene();
1303    }
1304}
1305
1306void DocumentView::findPrevious()
1307{
1308    checkResult();
1309
1310    m_currentResult = s_searchModel->findResult(this, m_currentResult, m_currentPage, SearchModel::FindPrevious);
1311
1312    applyResult();
1313}
1314
1315void DocumentView::findNext()
1316{
1317    checkResult();
1318
1319    m_currentResult = s_searchModel->findResult(this, m_currentResult, m_currentPage, SearchModel::FindNext);
1320
1321    applyResult();
1322}
1323
1324void DocumentView::findResult(const QModelIndex& index)
1325{
1326    const int page = pageOfResult(index);
1327    const QRectF rect = rectOfResult(index);
1328
1329    if(page >= 1 && page <= m_pages.count() && !rect.isEmpty())
1330    {
1331        m_currentResult = index;
1332
1333        applyResult();
1334    }
1335}
1336
1337void DocumentView::zoomIn()
1338{
1339    if(scaleMode() != ScaleFactorMode)
1340    {
1341        const qreal currentScaleFactor = m_pageItems.at(m_currentPage - 1)->renderParam().scaleFactor();
1342
1343        setScaleFactor(qMin(currentScaleFactor * s_settings->documentView().zoomFactor(),
1344                            s_settings->documentView().maximumScaleFactor()));
1345
1346        setScaleMode(ScaleFactorMode);
1347    }
1348    else
1349    {
1350        setScaleFactor(qMin(m_scaleFactor * s_settings->documentView().zoomFactor(),
1351                            s_settings->documentView().maximumScaleFactor()));
1352    }
1353}
1354
1355void DocumentView::zoomOut()
1356{
1357    if(scaleMode() != ScaleFactorMode)
1358    {
1359        const qreal currentScaleFactor = m_pageItems.at(m_currentPage - 1)->renderParam().scaleFactor();
1360
1361        setScaleFactor(qMax(currentScaleFactor / s_settings->documentView().zoomFactor(),
1362                            s_settings->documentView().minimumScaleFactor()));
1363
1364        setScaleMode(ScaleFactorMode);
1365    }
1366    else
1367    {
1368        setScaleFactor(qMax(m_scaleFactor / s_settings->documentView().zoomFactor(),
1369                            s_settings->documentView().minimumScaleFactor()));
1370    }
1371}
1372
1373void DocumentView::originalSize()
1374{
1375    setScaleFactor(1.0);
1376    setScaleMode(ScaleFactorMode);
1377}
1378
1379void DocumentView::rotateLeft()
1380{
1381    switch(m_rotation)
1382    {
1383    default:
1384    case RotateBy0:
1385        setRotation(RotateBy270);
1386        break;
1387    case RotateBy90:
1388        setRotation(RotateBy0);
1389        break;
1390    case RotateBy180:
1391        setRotation(RotateBy90);
1392        break;
1393    case RotateBy270:
1394        setRotation(RotateBy180);
1395        break;
1396    }
1397}
1398
1399void DocumentView::rotateRight()
1400{
1401    switch(m_rotation)
1402    {
1403    default:
1404    case RotateBy0:
1405        setRotation(RotateBy90);
1406        break;
1407    case RotateBy90:
1408        setRotation(RotateBy180);
1409        break;
1410    case RotateBy180:
1411        setRotation(RotateBy270);
1412        break;
1413    case RotateBy270:
1414        setRotation(RotateBy0);
1415        break;
1416    }
1417}
1418
1419void DocumentView::startPresentation()
1420{
1421    const int screen = s_settings->presentationView().screen();
1422
1423    PresentationView* presentationView = new PresentationView(m_pages);
1424
1425    presentationView->setGeometry(QApplication::desktop()->screenGeometry(screen));
1426
1427    presentationView->show();
1428    presentationView->setAttribute(Qt::WA_DeleteOnClose);
1429
1430    connect(this, SIGNAL(destroyed()), presentationView, SLOT(close()));
1431    connect(this, SIGNAL(documentChanged()), presentationView, SLOT(close()));
1432
1433    presentationView->setRotation(rotation());
1434    presentationView->setRenderFlags(renderFlags());
1435
1436    presentationView->jumpToPage(currentPage(), false);
1437
1438    if(s_settings->presentationView().synchronize())
1439    {
1440        connect(this, SIGNAL(currentPageChanged(int,bool)), presentationView, SLOT(jumpToPage(int,bool)));
1441        connect(presentationView, SIGNAL(currentPageChanged(int,bool)), this, SLOT(jumpToPage(int,bool)));
1442    }
1443}
1444
1445void DocumentView::on_verticalScrollBar_valueChanged()
1446{
1447    if(!m_continuousMode)
1448    {
1449        return;
1450    }
1451
1452    int currentPage = -1;
1453    const QRectF visibleRect = mapToScene(viewport()->rect()).boundingRect();
1454
1455    for(int index = 0, count = m_pageItems.count(); index < count; ++index)
1456    {
1457        PageItem* page = m_pageItems.at((m_currentPage - 1 + index) % count);
1458
1459        const int pageNumber = page->index() + 1;
1460        const QRectF pageRect = page->boundingRect().translated(page->pos());
1461
1462        if(!pageRect.intersects(visibleRect))
1463        {
1464            page->cancelRender();
1465        }
1466        else if(currentPage == -1 &&
1467                 m_layout->currentPage(pageNumber) == pageNumber &&
1468                 m_layout->isCurrentPage(visibleRect, pageRect))
1469        {
1470            currentPage = pageNumber;
1471        }
1472    }
1473
1474    if(currentPage != -1 && m_currentPage != currentPage)
1475    {
1476        m_currentPage = currentPage;
1477
1478        emit currentPageChanged(m_currentPage);
1479
1480        if(s_settings->documentView().highlightCurrentThumbnail())
1481        {
1482            for(int index = 0; index < m_thumbnailItems.count(); ++index)
1483            {
1484                m_thumbnailItems.at(index)->setHighlighted(index == m_currentPage - 1);
1485            }
1486        }
1487    }
1488}
1489
1490void DocumentView::on_autoRefresh_timeout()
1491{
1492    if(m_fileInfo.exists())
1493    {
1494        refresh();
1495    }
1496    else
1497    {
1498        m_wasModified = true;
1499
1500        emit documentModified();
1501    }
1502}
1503
1504void DocumentView::on_prefetch_timeout()
1505{
1506    const QPair< int, int > prefetchRange = m_layout->prefetchRange(m_currentPage, m_pages.count());
1507
1508    const int maxCost = prefetchRange.second - prefetchRange.first + 1;
1509    int cost = 0;
1510
1511    for(int index = m_currentPage - 1; index <= prefetchRange.second - 1; ++index)
1512    {
1513        cost += m_pageItems.at(index)->startRender(true);
1514
1515        if(cost >= maxCost)
1516        {
1517            return;
1518        }
1519    }
1520
1521    for(int index = m_currentPage - 1; index >= prefetchRange.first - 1; --index)
1522    {
1523        cost += m_pageItems.at(index)->startRender(true);
1524
1525        if(cost >= maxCost)
1526        {
1527            return;
1528        }
1529    }
1530}
1531
1532void DocumentView::on_temporaryHighlight_timeout()
1533{
1534    m_highlight->setVisible(false);
1535}
1536
1537void DocumentView::on_searchTask_progressChanged(int progress)
1538{
1539    s_searchModel->updateProgress(this);
1540
1541    emit searchProgressChanged(progress);
1542}
1543
1544void DocumentView::on_searchTask_resultsReady(int index, const QList< QRectF >& results)
1545{
1546    if(m_searchTask->wasCanceled())
1547    {
1548        return;
1549    }
1550
1551    s_searchModel->insertResults(this, index + 1, results);
1552
1553    if(m_highlightAll)
1554    {
1555        m_pageItems.at(index)->setHighlights(results);
1556        m_thumbnailItems.at(index)->setHighlights(results);
1557    }
1558
1559    if(s_settings->documentView().limitThumbnailsToResults())
1560    {
1561        prepareThumbnailsScene();
1562    }
1563
1564    if(!results.isEmpty() && !m_currentResult.isValid())
1565    {
1566        setFocus();
1567
1568        findNext();
1569    }
1570}
1571
1572void DocumentView::on_pages_cropRectChanged()
1573{
1574    qreal left = 0.0, top = 0.0;
1575    saveLeftAndTop(left, top);
1576
1577    prepareScene();
1578    prepareView(left, top);
1579}
1580
1581void DocumentView::on_thumbnails_cropRectChanged()
1582{
1583    prepareThumbnailsScene();
1584}
1585
1586void DocumentView::on_pages_linkClicked(bool newTab, int page, qreal left, qreal top)
1587{
1588    if(newTab)
1589    {
1590        emit linkClicked(page);
1591    }
1592    else
1593    {
1594        jumpToPage(page, true, left, top);
1595    }
1596}
1597
1598void DocumentView::on_pages_linkClicked(bool newTab, const QString& fileName, int page)
1599{
1600    const QString filePath = QFileInfo(fileName).isAbsolute() ? fileName : m_fileInfo.dir().filePath(fileName);
1601
1602    emit linkClicked(newTab, filePath, page);
1603}
1604
1605void DocumentView::on_pages_linkClicked(const QString& url)
1606{
1607    if(s_settings->documentView().openUrl())
1608    {
1609        QUrl resolvedUrl(url);
1610
1611        if(resolvedUrl.isRelative() && QFileInfo(url).isRelative())
1612        {
1613            resolvedUrl.setPath(m_fileInfo.dir().filePath(url));
1614        }
1615
1616        QDesktopServices::openUrl(resolvedUrl);
1617    }
1618    else
1619    {
1620        QMessageBox::information(this, tr("Information"), tr("Opening URL is disabled in the settings."));
1621    }
1622}
1623
1624void DocumentView::on_pages_rubberBandFinished()
1625{
1626    setRubberBandMode(ModifiersMode);
1627}
1628
1629void DocumentView::on_pages_zoomToSelection(int page, const QRectF& rect)
1630{
1631    if(rect.isEmpty())
1632    {
1633        return;
1634    }
1635
1636    const qreal visibleWidth = m_layout->visibleWidth(viewport()->width());
1637    const qreal visibleHeight = m_layout->visibleHeight(viewport()->height());
1638
1639    const QSizeF displayedSize = m_pageItems.at(page - 1)->displayedSize();
1640
1641    setScaleFactor(qMin(qMin(visibleWidth / displayedSize.width() / rect.width(),
1642                             visibleHeight / displayedSize.height() / rect.height()),
1643                        Defaults::DocumentView::maximumScaleFactor()));
1644
1645    setScaleMode(ScaleFactorMode);
1646
1647    jumpToPage(page, false, rect.left(), rect.top());
1648}
1649
1650void DocumentView::on_pages_wasModified()
1651{
1652    m_wasModified = true;
1653
1654    emit documentModified();
1655}
1656
1657void DocumentView::resizeEvent(QResizeEvent* event)
1658{
1659    qreal left = 0.0, top = 0.0;
1660    saveLeftAndTop(left, top);
1661
1662    QGraphicsView::resizeEvent(event);
1663
1664    if(m_scaleMode != ScaleFactorMode)
1665    {
1666        prepareScene();
1667        prepareView(left, top);
1668    }
1669}
1670
1671void DocumentView::keyPressEvent(QKeyEvent* event)
1672{
1673    foreach(const PageItem* page, m_pageItems)
1674    {
1675        if(page->showsAnnotationOverlay() || page->showsFormFieldOverlay())
1676        {
1677            QGraphicsView::keyPressEvent(event);
1678            return;
1679        }
1680    }
1681
1682    const QKeySequence keySequence(event->modifiers() + event->key());
1683
1684    int maskedKey = -1;
1685
1686    if(s_shortcutHandler->matchesSkipBackward(keySequence))
1687    {
1688        maskedKey = Qt::Key_PageUp;
1689    }
1690    else if(s_shortcutHandler->matchesSkipForward(keySequence))
1691    {
1692        maskedKey = Qt::Key_PageDown;
1693    }
1694    else if(s_shortcutHandler->matchesMoveUp(keySequence))
1695    {
1696        maskedKey = Qt::Key_Up;
1697    }
1698    else if(s_shortcutHandler->matchesMoveDown(keySequence))
1699    {
1700        maskedKey = Qt::Key_Down;
1701    }
1702    else if(s_shortcutHandler->matchesMoveLeft(keySequence))
1703    {
1704        maskedKey = Qt::Key_Left;
1705    }
1706    else if(s_shortcutHandler->matchesMoveRight(keySequence))
1707    {
1708        maskedKey = Qt::Key_Right;
1709    }
1710    else if(event->key() == Qt::Key_PageUp || event->key() == Qt::Key_PageDown ||
1711            event->key() == Qt::Key_Up || event->key() == Qt::Key_Down ||
1712            event->key() == Qt::Key_Left || event->key() == Qt::Key_Right)
1713    {
1714        event->ignore();
1715        return;
1716    }
1717
1718    if(maskedKey == -1)
1719    {
1720        QGraphicsView::keyPressEvent(event);
1721    }
1722    else
1723    {
1724        if(!m_continuousMode)
1725        {
1726            if(maskedKey == Qt::Key_PageUp && verticalScrollBar()->value() == verticalScrollBar()->minimum() && m_currentPage != 1)
1727            {
1728                previousPage();
1729
1730                verticalScrollBar()->setValue(verticalScrollBar()->maximum());
1731
1732                event->accept();
1733                return;
1734            }
1735            else if(maskedKey == Qt::Key_PageDown && verticalScrollBar()->value() == verticalScrollBar()->maximum() && m_currentPage != m_layout->currentPage(m_pages.count()))
1736            {
1737                nextPage();
1738
1739                verticalScrollBar()->setValue(verticalScrollBar()->minimum());
1740
1741                event->accept();
1742                return;
1743            }
1744        }
1745
1746        if((maskedKey == Qt::Key_Up && verticalScrollBar()->minimum() == verticalScrollBar()->maximum()) ||
1747           (maskedKey == Qt::Key_Left && !horizontalScrollBar()->isVisible()))
1748        {
1749            previousPage();
1750
1751            event->accept();
1752            return;
1753        }
1754        else if((maskedKey == Qt::Key_Down && verticalScrollBar()->minimum() == verticalScrollBar()->maximum()) ||
1755                (maskedKey == Qt::Key_Right && !horizontalScrollBar()->isVisible()))
1756        {
1757            nextPage();
1758
1759            event->accept();
1760            return;
1761        }
1762
1763        QKeyEvent keyEvent(event->type(), maskedKey, Qt::NoModifier, event->text(), event->isAutoRepeat(), event->count());
1764        QGraphicsView::keyPressEvent(&keyEvent);
1765    }
1766}
1767
1768void DocumentView::mousePressEvent(QMouseEvent* event)
1769{
1770    if(event->button() == Qt::XButton1)
1771    {
1772        event->accept();
1773
1774        jumpBackward();
1775    }
1776    else if(event->button() == Qt::XButton2)
1777    {
1778        event->accept();
1779
1780        jumpForward();
1781    }
1782
1783    QGraphicsView::mousePressEvent(event);
1784}
1785
1786void DocumentView::wheelEvent(QWheelEvent* event)
1787{
1788    const Qt::KeyboardModifiers zoomModifiers = s_settings->documentView().zoomModifiers();
1789    const Qt::KeyboardModifiers rotateModifiers = s_settings->documentView().rotateModifiers();
1790    const Qt::KeyboardModifiers scrollModifiers = s_settings->documentView().scrollModifiers();
1791
1792    if(event->modifiers() == zoomModifiers || event->buttons() == zoomModifiers)
1793    {
1794        if(event->delta() > 0)
1795        {
1796            zoomIn();
1797        }
1798        else
1799        {
1800            zoomOut();
1801        }
1802
1803        event->accept();
1804        return;
1805    }
1806    else if(event->modifiers() == rotateModifiers || event->buttons() == rotateModifiers)
1807    {
1808        if(event->delta() > 0)
1809        {
1810            rotateLeft();
1811        }
1812        else
1813        {
1814            rotateRight();
1815        }
1816
1817        event->accept();
1818        return;
1819    }
1820    else if(event->modifiers() == scrollModifiers || event->buttons() == scrollModifiers)
1821    {
1822        QWheelEvent wheelEvent(event->pos(), event->delta(), event->buttons(), Qt::AltModifier, Qt::Horizontal);
1823        QGraphicsView::wheelEvent(&wheelEvent);
1824
1825        event->accept();
1826        return;
1827    }
1828    else if(event->modifiers() == Qt::NoModifier && !m_continuousMode)
1829    {
1830        if(event->delta() > 0 && verticalScrollBar()->value() == verticalScrollBar()->minimum() && m_currentPage != 1)
1831        {
1832            previousPage();
1833
1834            verticalScrollBar()->setValue(verticalScrollBar()->maximum());
1835
1836            event->accept();
1837            return;
1838        }
1839        else if(event->delta() < 0 && verticalScrollBar()->value() == verticalScrollBar()->maximum() && m_currentPage != m_layout->currentPage(m_pages.count()))
1840        {
1841            nextPage();
1842
1843            verticalScrollBar()->setValue(verticalScrollBar()->minimum());
1844
1845            event->accept();
1846            return;
1847        }
1848    }
1849
1850    QGraphicsView::wheelEvent(event);
1851}
1852
1853void DocumentView::contextMenuEvent(QContextMenuEvent* event)
1854{
1855    if(event->reason() == QContextMenuEvent::Mouse && modifiersUseMouseButton(s_settings, Qt::RightButton))
1856    {
1857        event->accept();
1858        return;
1859    }
1860
1861    event->setAccepted(false);
1862
1863    QGraphicsView::contextMenuEvent(event);
1864
1865    if(!event->isAccepted())
1866    {
1867        event->setAccepted(true);
1868
1869        emit customContextMenuRequested(event->pos());
1870    }
1871}
1872
1873#ifdef WITH_CUPS
1874
1875bool DocumentView::printUsingCUPS(QPrinter* printer, const PrintOptions& printOptions, int fromPage, int toPage)
1876{
1877    int num_dests = 0;
1878    cups_dest_t* dests = 0;
1879
1880    int num_options = 0;
1881    cups_option_t* options = 0;
1882
1883    cups_dest_t* dest = 0;
1884    int jobId = 0;
1885
1886    num_dests = cupsGetDests(&dests);
1887    dest = cupsGetDest(printer->printerName().toLocal8Bit(), 0, num_dests, dests);
1888
1889    if(dest == 0)
1890    {
1891        qWarning() << cupsLastErrorString();
1892
1893        cupsFreeDests(num_dests, dests);
1894
1895        return false;
1896    }
1897
1898    for(int index = 0; index < dest->num_options; ++index)
1899    {
1900        num_options = cupsAddOption(dest->options[index].name, dest->options[index].value, num_options, &options);
1901    }
1902
1903    const QStringList cupsOptions = printer->printEngine()->property(QPrintEngine::PrintEnginePropertyKey(0xfe00)).toStringList();
1904
1905    for(int index = 0; index < cupsOptions.count() - 1; index += 2)
1906    {
1907        num_options = cupsAddOption(cupsOptions.at(index).toLocal8Bit(), cupsOptions.at(index + 1).toLocal8Bit(), num_options, &options);
1908    }
1909
1910#if QT_VERSION >= QT_VERSION_CHECK(4,7,0)
1911
1912    num_options = cupsAddOption("copies", QString::number(printer->copyCount()).toLocal8Bit(), num_options, &options);
1913
1914#endif // QT_VERSION
1915
1916    num_options = cupsAddOption("Collate", printer->collateCopies() ? "true" : "false", num_options, &options);
1917
1918    switch(printer->pageOrder())
1919    {
1920    case QPrinter::FirstPageFirst:
1921        num_options = cupsAddOption("outputorder", "normal", num_options, &options);
1922        break;
1923    case QPrinter::LastPageFirst:
1924        num_options = cupsAddOption("outputorder", "reverse", num_options, &options);
1925        break;
1926    }
1927
1928    num_options = cupsAddOption("fit-to-page", printOptions.fitToPage ? "true" : "false", num_options, &options);
1929
1930    switch(printer->orientation())
1931    {
1932    case QPrinter::Portrait:
1933        num_options = cupsAddOption("landscape", "false", num_options, &options);
1934        break;
1935    case QPrinter::Landscape:
1936        num_options = cupsAddOption("landscape", "true", num_options, &options);
1937        break;
1938    }
1939
1940    switch(printer->colorMode())
1941    {
1942    case QPrinter::Color:
1943        num_options = addCMYKorRGBColorModel(dest, num_options, &options);
1944        break;
1945    case QPrinter::GrayScale:
1946        num_options = cupsAddOption("ColorModel", "Gray", num_options, &options);
1947        break;
1948    }
1949
1950    switch(printer->duplex())
1951    {
1952    case QPrinter::DuplexNone:
1953        num_options = cupsAddOption("sides", "one-sided", num_options, &options);
1954        break;
1955    case QPrinter::DuplexAuto:
1956        break;
1957    case QPrinter::DuplexLongSide:
1958        num_options = cupsAddOption("sides", "two-sided-long-edge", num_options, &options);
1959        break;
1960    case QPrinter::DuplexShortSide:
1961        num_options = cupsAddOption("sides", "two-sided-short-edge", num_options, &options);
1962        break;
1963    }
1964
1965    int numberUp = 1;
1966
1967#if QT_VERSION < QT_VERSION_CHECK(5,2,0)
1968
1969    switch(printOptions.numberUp)
1970    {
1971    case PrintOptions::SinglePage:
1972        num_options = cupsAddOption("number-up", "1", num_options, &options);
1973        numberUp = 1;
1974        break;
1975    case PrintOptions::TwoPages:
1976        num_options = cupsAddOption("number-up", "2", num_options, &options);
1977        numberUp = 2;
1978        break;
1979    case PrintOptions::FourPages:
1980        num_options = cupsAddOption("number-up", "4", num_options, &options);
1981        numberUp = 4;
1982        break;
1983    case PrintOptions::SixPages:
1984        num_options = cupsAddOption("number-up", "6", num_options, &options);
1985        numberUp = 6;
1986        break;
1987    case PrintOptions::NinePages:
1988        num_options = cupsAddOption("number-up", "9", num_options, &options);
1989        numberUp = 9;
1990        break;
1991    case PrintOptions::SixteenPages:
1992        num_options = cupsAddOption("number-up", "16", num_options, &options);
1993        numberUp = 16;
1994        break;
1995    }
1996
1997    switch(printOptions.numberUpLayout)
1998    {
1999    case PrintOptions::BottomTopLeftRight:
2000        num_options = cupsAddOption("number-up-layout", "btlr", num_options, &options);
2001        break;
2002    case PrintOptions::BottomTopRightLeft:
2003        num_options = cupsAddOption("number-up-layout", "btrl", num_options, &options);
2004        break;
2005    case PrintOptions::LeftRightBottomTop:
2006        num_options = cupsAddOption("number-up-layout", "lrbt", num_options, &options);
2007        break;
2008    case PrintOptions::LeftRightTopBottom:
2009        num_options = cupsAddOption("number-up-layout", "lrtb", num_options, &options);
2010        break;
2011    case PrintOptions::RightLeftBottomTop:
2012        num_options = cupsAddOption("number-up-layout", "rlbt", num_options, &options);
2013        break;
2014    case PrintOptions::RightLeftTopBottom:
2015        num_options = cupsAddOption("number-up-layout", "rltb", num_options, &options);
2016        break;
2017    case PrintOptions::TopBottomLeftRight:
2018        num_options = cupsAddOption("number-up-layout", "tblr", num_options, &options);
2019        break;
2020    case PrintOptions::TopBottomRightLeft:
2021        num_options = cupsAddOption("number-up-layout", "tbrl", num_options, &options);
2022        break;
2023    }
2024
2025    switch(printOptions.pageSet)
2026    {
2027    case PrintOptions::AllPages:
2028        break;
2029    case PrintOptions::EvenPages:
2030        num_options = cupsAddOption("page-set", "even", num_options, &options);
2031        break;
2032    case PrintOptions::OddPages:
2033        num_options = cupsAddOption("page-set", "odd", num_options, &options);
2034        break;
2035    }
2036
2037#else
2038
2039    {
2040        bool ok = false;
2041        int value = QString::fromLocal8Bit(cupsGetOption("number-up", num_options, options)).toInt(&ok);
2042
2043        numberUp = ok ? value : 1;
2044    }
2045
2046#endif // QT_VERSION
2047
2048    fromPage = (fromPage - 1) / numberUp + 1;
2049    toPage = (toPage - 1) / numberUp + 1;
2050
2051    if(printOptions.pageRanges.isEmpty())
2052    {
2053        num_options = cupsAddOption("page-ranges", QString("%1-%2").arg(fromPage).arg(toPage).toLocal8Bit(), num_options, &options);
2054    }
2055    else
2056    {
2057        num_options = cupsAddOption("page-ranges", printOptions.pageRanges.toLocal8Bit(), num_options, &options);
2058    }
2059
2060    QTemporaryFile temporaryFile;
2061
2062    adjustFileTemplateSuffix(temporaryFile, m_fileInfo.suffix());
2063
2064    if(!temporaryFile.open())
2065    {
2066        cupsFreeDests(num_dests, dests);
2067        cupsFreeOptions(num_options, options);
2068
2069        return false;
2070    }
2071
2072    temporaryFile.close();
2073
2074    if(!m_document->save(temporaryFile.fileName(), true))
2075    {
2076        cupsFreeDests(num_dests, dests);
2077        cupsFreeOptions(num_options, options);
2078
2079        return false;
2080    }
2081
2082    jobId = cupsPrintFile(dest->name, temporaryFile.fileName().toLocal8Bit(), m_fileInfo.completeBaseName().toLocal8Bit(), num_options, options);
2083
2084    if(jobId < 1)
2085    {
2086        qWarning() << cupsLastErrorString();
2087    }
2088
2089    cupsFreeDests(num_dests, dests);
2090    cupsFreeOptions(num_options, options);
2091
2092    return jobId >= 1;
2093}
2094
2095#endif // WITH_CUPS
2096
2097bool DocumentView::printUsingQt(QPrinter* printer, const PrintOptions& printOptions, int fromPage, int toPage)
2098{
2099    QScopedPointer< QProgressDialog > progressDialog(new QProgressDialog(this));
2100    progressDialog->setLabelText(tr("Printing '%1'...").arg(m_fileInfo.completeBaseName()));
2101    progressDialog->setRange(fromPage - 1, toPage);
2102
2103    QPainter painter;
2104
2105    if(!painter.begin(printer))
2106    {
2107        return false;
2108    }
2109
2110    for(int index = fromPage - 1; index <= toPage - 1; ++index)
2111    {
2112        progressDialog->setValue(index);
2113
2114        QApplication::processEvents();
2115
2116        painter.save();
2117
2118        const Model::Page* page = m_pages.at(index);
2119
2120        if(printOptions.fitToPage)
2121        {
2122            const qreal pageWidth = printer->physicalDpiX() / 72.0 * page->size().width();
2123            const qreal pageHeight = printer->physicalDpiY() / 72.0 * page->size().width();
2124
2125            const qreal scaleFactor = qMin(printer->width() / pageWidth, printer->height() / pageHeight);
2126
2127            painter.setTransform(QTransform::fromScale(scaleFactor, scaleFactor));
2128        }
2129        else
2130        {
2131            const qreal scaleFactorX = static_cast< qreal >(printer->logicalDpiX()) / static_cast< qreal >(printer->physicalDpiX());
2132            const qreal scaleFactorY = static_cast< qreal >(printer->logicalDpiY()) / static_cast< qreal >(printer->physicalDpiY());
2133
2134            painter.setTransform(QTransform::fromScale(scaleFactorX, scaleFactorY));
2135        }
2136
2137        painter.drawImage(QPointF(), page->render(printer->physicalDpiX(), printer->physicalDpiY()));
2138
2139        painter.restore();
2140
2141        if(index < toPage - 1)
2142        {
2143            printer->newPage();
2144        }
2145
2146        QApplication::processEvents();
2147
2148        if(progressDialog->wasCanceled())
2149        {
2150            printer->abort();
2151
2152            return false;
2153        }
2154    }
2155
2156    return true;
2157}
2158
2159void DocumentView::saveLeftAndTop(qreal& left, qreal& top) const
2160{
2161    const PageItem* page = m_pageItems.at(m_currentPage - 1);
2162    const QRectF boundingRect = page->uncroppedBoundingRect().translated(page->pos());
2163
2164    const QPointF topLeft = mapToScene(viewport()->rect().topLeft());
2165
2166    left = (topLeft.x() - boundingRect.x()) / boundingRect.width();
2167    top = (topLeft.y() - boundingRect.y()) / boundingRect.height();
2168}
2169
2170bool DocumentView::checkDocument(const QString& filePath, Model::Document* document, QVector< Model::Page* >& pages)
2171{
2172    if(document->isLocked())
2173    {
2174        QString password = QInputDialog::getText(this, tr("Unlock %1").arg(QFileInfo(filePath).completeBaseName()), tr("Password:"), QLineEdit::Password);
2175
2176        if(document->unlock(password))
2177        {
2178            return false;
2179        }
2180    }
2181
2182    const int numberOfPages = document->numberOfPages();
2183
2184    if(numberOfPages == 0)
2185    {
2186        qWarning() << "No pages were found in document at" << filePath;
2187
2188        return false;
2189    }
2190
2191    pages.reserve(numberOfPages);
2192
2193    for(int index = 0; index < numberOfPages; ++index)
2194    {
2195        Model::Page* page = document->page(index);
2196
2197        if(page == 0)
2198        {
2199            qWarning() << "No page" << index << "was found in document at" << filePath;
2200
2201            return false;
2202        }
2203
2204        pages.append(page);
2205    }
2206
2207    return true;
2208}
2209
2210void DocumentView::loadFallbackOutline()
2211{
2212    m_outlineModel->clear();
2213
2214    for(int page = 1; page <= m_pages.count(); ++page)
2215    {
2216        QStandardItem* item = new QStandardItem(tr("Page %1").arg(pageLabelFromNumber(page)));
2217        item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
2218
2219        item->setData(page, Model::Document::PageRole);
2220        item->setData(qQNaN(), Model::Document::LeftRole);
2221        item->setData(qQNaN(), Model::Document::TopRole);
2222
2223        QStandardItem* pageItem = item->clone();
2224        pageItem->setText(QString::number(page));
2225        pageItem->setTextAlignment(Qt::AlignRight);
2226
2227        m_outlineModel->appendRow(QList< QStandardItem* >() << item << pageItem);
2228    }
2229
2230    m_outlineModel->invisibleRootItem()->setData(true); // Flags this as a fallback outline.
2231}
2232
2233void DocumentView::loadDocumentDefaults()
2234{
2235    if(m_document->wantsContinuousMode())
2236    {
2237        m_continuousMode = true;
2238    }
2239
2240    if(m_document->wantsSinglePageMode())
2241    {
2242        m_layout.reset(new SinglePageLayout);
2243    }
2244    else if(m_document->wantsTwoPagesMode())
2245    {
2246        m_layout.reset(new TwoPagesLayout);
2247    }
2248    else if(m_document->wantsTwoPagesWithCoverPageMode())
2249    {
2250        m_layout.reset(new TwoPagesWithCoverPageLayout);
2251    }
2252
2253    if(m_document->wantsRightToLeftMode())
2254    {
2255        m_rightToLeftMode = true;
2256    }
2257}
2258
2259void DocumentView::adjustScrollBarPolicy()
2260{
2261    switch(m_scaleMode)
2262    {
2263    default:
2264    case ScaleFactorMode:
2265        setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
2266        setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
2267        break;
2268    case FitToPageWidthMode:
2269        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2270        setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
2271        break;
2272    case FitToPageSizeMode:
2273        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2274        setVerticalScrollBarPolicy(m_continuousMode ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
2275        break;
2276    }
2277}
2278
2279void DocumentView::disconnectVerticalScrollBar()
2280{
2281    disconnect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_verticalScrollBar_valueChanged()));
2282}
2283
2284void DocumentView::reconnectVerticalScrollBar()
2285{
2286    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_verticalScrollBar_valueChanged()));
2287}
2288
2289void DocumentView::prepareDocument(Model::Document* document, const QVector< Model::Page* >& pages)
2290{
2291    m_prefetchTimer->blockSignals(true);
2292    m_prefetchTimer->stop();
2293
2294    cancelSearch();
2295    clearResults();
2296
2297    qDeleteAll(m_pageItems);
2298    qDeleteAll(m_thumbnailItems);
2299
2300    qDeleteAll(m_pages);
2301    m_pages = pages;
2302
2303    delete m_document;
2304    m_document = document;
2305
2306    if(!m_autoRefreshWatcher->files().isEmpty())
2307    {
2308        m_autoRefreshWatcher->removePaths(m_autoRefreshWatcher->files());
2309    }
2310
2311    if(s_settings->documentView().autoRefresh())
2312    {
2313        m_autoRefreshWatcher->addPath(m_fileInfo.filePath());
2314    }
2315
2316    m_document->setPaperColor(s_settings->pageItem().paperColor());
2317
2318    preparePages();
2319    prepareThumbnails();
2320    prepareBackground();
2321
2322    m_document->loadOutline(m_outlineModel);
2323    m_document->loadProperties(m_propertiesModel);
2324
2325    if(m_outlineModel->rowCount() == 0)
2326    {
2327        loadFallbackOutline();
2328    }
2329
2330    if(s_settings->documentView().prefetch())
2331    {
2332        m_prefetchTimer->blockSignals(false);
2333        m_prefetchTimer->start();
2334    }
2335}
2336
2337void DocumentView::preparePages()
2338{
2339    m_pageItems.clear();
2340    m_pageItems.reserve(m_pages.count());
2341
2342    for(int index = 0; index < m_pages.count(); ++index)
2343    {
2344        PageItem* page = new PageItem(m_pages.at(index), index);
2345
2346        page->setRubberBandMode(m_rubberBandMode);
2347
2348        scene()->addItem(page);
2349        m_pageItems.append(page);
2350
2351        connect(page, SIGNAL(cropRectChanged()), SLOT(on_pages_cropRectChanged()));
2352
2353        connect(page, SIGNAL(linkClicked(bool,int,qreal,qreal)), SLOT(on_pages_linkClicked(bool,int,qreal,qreal)));
2354        connect(page, SIGNAL(linkClicked(bool,QString,int)), SLOT(on_pages_linkClicked(bool,QString,int)));
2355        connect(page, SIGNAL(linkClicked(QString)), SLOT(on_pages_linkClicked(QString)));
2356
2357        connect(page, SIGNAL(rubberBandFinished()), SLOT(on_pages_rubberBandFinished()));
2358
2359        connect(page, SIGNAL(zoomToSelection(int,QRectF)), SLOT(on_pages_zoomToSelection(int,QRectF)));
2360
2361        connect(page, SIGNAL(wasModified()), SLOT(on_pages_wasModified()));
2362    }
2363}
2364
2365void DocumentView::prepareThumbnails()
2366{
2367    m_thumbnailItems.clear();
2368    m_thumbnailItems.reserve(m_pages.count());
2369
2370    for(int index = 0; index < m_pages.count(); ++index)
2371    {
2372        ThumbnailItem* page = new ThumbnailItem(m_pages.at(index), pageLabelFromNumber(index + 1), index);
2373
2374        m_thumbnailsScene->addItem(page);
2375        m_thumbnailItems.append(page);
2376
2377        connect(page, SIGNAL(cropRectChanged()), SLOT(on_thumbnails_cropRectChanged()));
2378
2379        connect(page, SIGNAL(linkClicked(bool,int,qreal,qreal)), SLOT(on_pages_linkClicked(bool,int,qreal,qreal)));
2380    }
2381}
2382
2383void DocumentView::prepareBackground()
2384{
2385    QColor backgroundColor;
2386
2387    if(s_settings->pageItem().decoratePages())
2388    {
2389        backgroundColor = s_settings->pageItem().backgroundColor();
2390    }
2391    else
2392    {
2393        backgroundColor = s_settings->pageItem().paperColor();
2394
2395        if(invertColors())
2396        {
2397            backgroundColor.setRgb(~backgroundColor.rgb());
2398        }
2399    }
2400
2401    scene()->setBackgroundBrush(QBrush(backgroundColor));
2402    m_thumbnailsScene->setBackgroundBrush(QBrush(backgroundColor));
2403}
2404
2405void DocumentView::prepareScene()
2406{
2407    // prepare render parameters and adjust scale factor
2408
2409    RenderParam renderParam(logicalDpiX(), logicalDpiY(), 1.0,
2410                            scaleFactor(), rotation(), renderFlags());
2411
2412#if QT_VERSION >= QT_VERSION_CHECK(5,1,0)
2413
2414    if(s_settings->pageItem().useDevicePixelRatio())
2415    {
2416        renderParam.setDevicePixelRatio(devicePixelRatio());
2417    }
2418
2419#endif // QT_VERSION
2420
2421    const qreal visibleWidth = m_layout->visibleWidth(viewport()->width());
2422    const qreal visibleHeight = m_layout->visibleHeight(viewport()->height());
2423
2424    foreach(PageItem* page, m_pageItems)
2425    {
2426        const QSizeF displayedSize = page->displayedSize(renderParam);
2427
2428        if(m_scaleMode == FitToPageWidthMode)
2429        {
2430            adjustScaleFactor(renderParam, visibleWidth / displayedSize.width());
2431        }
2432        else if(m_scaleMode == FitToPageSizeMode)
2433        {
2434            adjustScaleFactor(renderParam, qMin(visibleWidth / displayedSize.width(), visibleHeight / displayedSize.height()));
2435        }
2436
2437        page->setRenderParam(renderParam);
2438    }
2439
2440    // prepare layout
2441
2442    qreal left = 0.0;
2443    qreal right = 0.0;
2444    qreal height = s_settings->documentView().pageSpacing();
2445
2446    m_layout->prepareLayout(m_pageItems, m_rightToLeftMode,
2447                            left, right, height);
2448
2449    scene()->setSceneRect(left, 0.0, right - left, height);
2450}
2451
2452void DocumentView::prepareView(qreal newLeft, qreal newTop, bool forceScroll, int scrollToPage)
2453{
2454    const QRectF sceneRect = scene()->sceneRect();
2455
2456    qreal top = sceneRect.top();
2457    qreal height = sceneRect.height();
2458
2459    int horizontalValue = 0;
2460    int verticalValue = 0;
2461
2462    scrollToPage = scrollToPage != 0 ? scrollToPage : m_currentPage;
2463
2464    const int highlightIsOnPage = m_currentResult.isValid() ? pageOfResult(m_currentResult) : 0;
2465    const bool highlightCurrentThumbnail = s_settings->documentView().highlightCurrentThumbnail();
2466
2467    for(int index = 0; index < m_pageItems.count(); ++index)
2468    {
2469        PageItem* page = m_pageItems.at(index);
2470
2471        if(m_continuousMode)
2472        {
2473            page->setVisible(true);
2474        }
2475        else
2476        {
2477            if(m_layout->leftIndex(index) == m_currentPage - 1)
2478            {
2479                page->setVisible(true);
2480
2481                const QRectF boundingRect = page->boundingRect().translated(page->pos());
2482
2483                top = boundingRect.top() - s_settings->documentView().pageSpacing();
2484                height = boundingRect.height() + 2.0 * s_settings->documentView().pageSpacing();
2485            }
2486            else
2487            {
2488                page->setVisible(false);
2489
2490                page->cancelRender();
2491            }
2492        }
2493
2494        if(index == scrollToPage - 1)
2495        {
2496            const QRectF boundingRect = page->uncroppedBoundingRect().translated(page->pos());
2497
2498            horizontalValue = qFloor(boundingRect.left() + newLeft * boundingRect.width());
2499            verticalValue = qFloor(boundingRect.top() + newTop * boundingRect.height());
2500        }
2501
2502        if(index == highlightIsOnPage - 1)
2503        {
2504            m_highlight->setPos(page->pos());
2505            m_highlight->setTransform(page->transform());
2506
2507            page->stackBefore(m_highlight);
2508        }
2509
2510        m_thumbnailItems.at(index)->setHighlighted(highlightCurrentThumbnail && (index == m_currentPage - 1));
2511    }
2512
2513    setSceneRect(sceneRect.left(), top, sceneRect.width(), height);
2514
2515    if(!forceScroll && s_settings->documentView().minimalScrolling())
2516    {
2517        setValueIfNotVisible(horizontalScrollBar(), horizontalValue);
2518        setValueIfNotVisible(verticalScrollBar(), verticalValue);
2519    }
2520    else
2521    {
2522        horizontalScrollBar()->setValue(horizontalValue);
2523        verticalScrollBar()->setValue(verticalValue);
2524    }
2525
2526    viewport()->update();
2527}
2528
2529void DocumentView::prepareThumbnailsScene()
2530{
2531    // prepare render parameters and adjust scale factor
2532
2533    RenderParam renderParam(logicalDpiX(), logicalDpiY(), 1.0,
2534                            scaleFactor(), rotation(), renderFlags());
2535
2536#if QT_VERSION >= QT_VERSION_CHECK(5,1,0)
2537
2538    if(s_settings->pageItem().useDevicePixelRatio())
2539    {
2540        renderParam.setDevicePixelRatio(devicePixelRatio());
2541    }
2542
2543#endif // QT_VERSION
2544
2545    const qreal thumbnailSize = s_settings->documentView().thumbnailSize();
2546    const qreal thumbnailSpacing = s_settings->documentView().thumbnailSpacing();
2547
2548    qreal visibleWidth = Defaults::DocumentView::thumbnailSize();
2549    qreal visibleHeight = Defaults::DocumentView::thumbnailSize();
2550
2551    if(!m_thumbnailsViewportSize.isNull())
2552    {
2553        visibleWidth = m_thumbnailsViewportSize.width() - 3.0 * thumbnailSpacing;
2554        visibleHeight = m_thumbnailsViewportSize.height() - 3.0 * thumbnailSpacing;
2555    }
2556
2557    foreach(ThumbnailItem* page, m_thumbnailItems)
2558    {
2559        const QSizeF displayedSize = page->displayedSize(renderParam);
2560
2561        if(thumbnailSize != 0.0)
2562        {
2563            adjustScaleFactor(renderParam, qMin(thumbnailSize / displayedSize.width(), thumbnailSize / displayedSize.height()));
2564        }
2565        else
2566        {
2567            if(m_thumbnailsOrientation == Qt::Vertical)
2568            {
2569                adjustScaleFactor(renderParam, visibleWidth / displayedSize.width());
2570            }
2571            else
2572            {
2573                adjustScaleFactor(renderParam, (visibleHeight - page->textHeight()) / displayedSize.height());
2574            }
2575        }
2576
2577        page->setRenderParam(renderParam);
2578    }
2579
2580    // prepare layout
2581
2582    qreal left = 0.0;
2583    qreal right = m_thumbnailsOrientation == Qt::Vertical ? 0.0 : thumbnailSpacing;
2584    qreal top = 0.0;
2585    qreal bottom = m_thumbnailsOrientation == Qt::Vertical ? thumbnailSpacing : 0.0;
2586
2587    const bool limitThumbnailsToResults = s_settings->documentView().limitThumbnailsToResults();
2588
2589    for(int index = 0; index < m_thumbnailItems.count(); ++index)
2590    {
2591        ThumbnailItem* page = m_thumbnailItems.at(index);
2592
2593        // prepare visibility
2594
2595        if(limitThumbnailsToResults && s_searchModel->hasResults(this) && !s_searchModel->hasResultsOnPage(this, index + 1))
2596        {
2597            page->setVisible(false);
2598
2599            page->cancelRender();
2600
2601            continue;
2602        }
2603
2604        page->setVisible(true);
2605
2606        // prepare position
2607
2608        const QRectF boundingRect = page->boundingRect();
2609
2610        if(m_thumbnailsOrientation == Qt::Vertical)
2611        {
2612            page->setPos(-boundingRect.left() - 0.5 * boundingRect.width(), bottom - boundingRect.top());
2613
2614            left = qMin(left, -0.5f * boundingRect.width() - thumbnailSpacing);
2615            right = qMax(right, 0.5f * boundingRect.width() + thumbnailSpacing);
2616            bottom += boundingRect.height() + thumbnailSpacing;
2617        }
2618        else
2619        {
2620            page->setPos(right - boundingRect.left(), -boundingRect.top() - 0.5 * boundingRect.height());
2621
2622            top = qMin(top, -0.5f * boundingRect.height() - thumbnailSpacing);
2623            bottom = qMax(bottom, 0.5f * boundingRect.height() + thumbnailSpacing);
2624            right += boundingRect.width() + thumbnailSpacing;
2625        }
2626    }
2627
2628    m_thumbnailsScene->setSceneRect(left, top, right - left, bottom - top);
2629}
2630
2631void DocumentView::prepareHighlight(int index, const QRectF& rect)
2632{
2633    PageItem* page = m_pageItems.at(index);
2634
2635    m_highlight->setPos(page->pos());
2636    m_highlight->setTransform(page->transform());
2637
2638    m_highlight->setRect(rect.normalized());
2639    m_highlight->setBrush(QBrush(s_settings->pageItem().highlightColor()));
2640
2641    page->stackBefore(m_highlight);
2642
2643    m_highlight->setVisible(true);
2644
2645    disconnectVerticalScrollBar();
2646    centerOn(m_highlight);
2647    reconnectVerticalScrollBar();
2648
2649    viewport()->update();
2650}
2651
2652void DocumentView::checkResult()
2653{
2654    if(m_currentResult.isValid() && m_layout->currentPage(pageOfResult(m_currentResult)) != m_currentPage)
2655    {
2656        m_currentResult = QModelIndex();
2657    }
2658}
2659
2660void DocumentView::applyResult()
2661{
2662    if(m_currentResult.isValid())
2663    {
2664        const int page = pageOfResult(m_currentResult);
2665        const QRectF rect = rectOfResult(m_currentResult);
2666
2667        jumpToPage(page);
2668
2669        prepareHighlight(page - 1, rect);
2670    }
2671    else
2672    {
2673        m_highlight->setVisible(false);
2674    }
2675}
2676
2677} // qpdfview
Note: See TracBrowser for help on using the repository browser.