qpdfviewsb/sources/documentview.cpp

3110 lines
79 KiB
C++

/*
Copyright 2014 S. Razi Alavizadeh
Copyright 2013 Thomas Etter
Copyright 2012-2015, 2018 Adam Reichold
Copyright 2014 Dorian Scholz
Copyright 2018 Egor Zenkov
This file is part of qpdfview.
qpdfview is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
qpdfview is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qpdfview. If not, see <http://www.gnu.org/licenses/>.
*/
#include "documentview.h"
#include <QApplication>
#include <QInputDialog>
#include <QDateTime>
#include <QDebug>
#include <QDesktopWidget>
#include <QDesktopServices>
#include <QDir>
#include <QFileSystemWatcher>
#include <QKeyEvent>
#include <qmath.h>
#include <QMenu>
#include <QMessageBox>
#include <QPrintEngine>
#include <QProcess>
#include <QProgressDialog>
#include <QScrollBar>
#include <QTemporaryFile>
#include <QTimer>
#include <QUrl>
#ifdef WITH_CUPS
#include <cups/cups.h>
#include <cups/ppd.h>
#endif // WITH_CUPS
#ifdef WITH_SYNCTEX
#include <synctex_parser.h>
#ifndef HAS_SYNCTEX_2
typedef synctex_scanner_t synctex_scanner_p;
typedef synctex_node_t synctex_node_p;
#define synctex_scanner_next_result(scanner) synctex_next_result(scanner)
#endif // HAS_SYNCTEX_2
#endif // WITH_SYNCTEX
#include "settings.h"
#include "model.h"
#include "pluginhandler.h"
#include "shortcuthandler.h"
#include "thumbnailitem.h"
#include "presentationview.h"
#include "searchmodel.h"
#include "searchtask.h"
#include "miscellaneous.h"
#include "documentlayout.h"
namespace
{
using namespace qpdfview;
// taken from http://rosettacode.org/wiki/Roman_numerals/Decode#C.2B.2B
int romanToInt(const QString& text)
{
if(text.size() == 1)
{
switch(text.at(0).toLower().toLatin1())
{
case 'i': return 1;
case 'v': return 5;
case 'x': return 10;
case 'l': return 50;
case 'c': return 100;
case 'd': return 500;
case 'm': return 1000;
}
return 0;
}
int result = 0;
int previous = 0, current = 0;
for(int i = text.size() - 1; i >= 0; --i)
{
current = romanToInt(text.at(i));
result += current < previous ? -current : current;
previous = current;
}
return result;
}
// taken from http://rosettacode.org/wiki/Roman_numerals/Encode#C.2B.2B
QString intToRoman(int number)
{
struct romandata_t
{
int value;
char const* numeral;
};
static const romandata_t romandata[] =
{
{ 1000, "m" },
{ 900, "cm" },
{ 500, "d" },
{ 400, "cd" },
{ 100, "c" },
{ 90, "xc" },
{ 50, "l" },
{ 40, "xl" },
{ 10, "x" },
{ 9, "ix" },
{ 5, "v" },
{ 4, "iv" },
{ 1, "i" },
{ 0, NULL }
};
if(number >= 4000)
{
return QLatin1String("?");
}
QString result;
for(const romandata_t* current = romandata; current->value > 0; ++current)
{
while(number >= current->value)
{
number -= current->value;
result += QLatin1String(current->numeral);
}
}
return result;
}
bool copyFile(QFile& source, QFile& destination)
{
const qint64 maxSize = 4096;
qint64 size = -1;
QScopedArrayPointer< char > buffer(new char[maxSize]);
do
{
if((size = source.read(buffer.data(), maxSize)) < 0)
{
return false;
}
if(destination.write(buffer.data(), size) < 0)
{
return false;
}
}
while(size > 0);
return true;
}
inline void adjustFileTemplateSuffix(QTemporaryFile& temporaryFile, const QString& suffix)
{
temporaryFile.setFileTemplate(temporaryFile.fileTemplate() + QLatin1String(".") + suffix);
}
#ifdef WITH_CUPS
struct RemovePpdFileDeleter
{
static inline void cleanup(const char* ppdFileName) { if(ppdFileName != 0) { QFile::remove(ppdFileName); } }
};
struct ClosePpdFileDeleter
{
static inline void cleanup(ppd_file_t* ppdFile) { if(ppdFile != 0) { ppdClose(ppdFile); } }
};
int addCMYKorRGBColorModel(cups_dest_t* dest, int num_options, cups_option_t** options)
{
QScopedPointer< const char, RemovePpdFileDeleter > ppdFileName(cupsGetPPD(dest->name));
if(ppdFileName.isNull())
{
return num_options;
}
QScopedPointer< ppd_file_t, ClosePpdFileDeleter > ppdFile(ppdOpenFile(ppdFileName.data()));
if(ppdFile.isNull())
{
return num_options;
}
ppd_option_t* colorModel = ppdFindOption(ppdFile.data(), "ColorModel");
if(colorModel == 0)
{
return num_options;
}
for(int index = 0; index < colorModel->num_choices; ++index)
{
if(qstrcmp(colorModel->choices[index].choice, "CMYK") == 0)
{
return cupsAddOption("ColorModel", "CMYK", num_options, options);
}
}
for(int index = 0; index < colorModel->num_choices; ++index)
{
if(qstrcmp(colorModel->choices[index].choice, "RGB") == 0)
{
return cupsAddOption("ColorModel", "RGB", num_options, options);
}
}
return num_options;
}
#endif // WITH_CUPS
#ifdef WITH_SYNCTEX
DocumentView::SourceLink scanForSourceLink(const QString& filePath, const int page, QPointF pos)
{
DocumentView::SourceLink sourceLink;
if(synctex_scanner_p scanner = synctex_scanner_new_with_output_file(filePath.toLocal8Bit(), 0, 1))
{
if(synctex_edit_query(scanner, page, pos.x(), pos.y()) > 0)
{
for(synctex_node_p node = synctex_scanner_next_result(scanner); node != 0; node = synctex_scanner_next_result(scanner))
{
sourceLink.name = QString::fromLocal8Bit(synctex_scanner_get_name(scanner, synctex_node_tag(node)));
sourceLink.line = qMax(synctex_node_line(node), 0);
sourceLink.column = qMax(synctex_node_column(node), 0);
break;
}
}
synctex_scanner_free(scanner);
}
return sourceLink;
}
#endif // WITH_SYNCTEX
inline bool modifiersAreActive(const QWheelEvent* event, Qt::KeyboardModifiers modifiers)
{
if(modifiers == Qt::NoModifier)
{
return false;
}
return event->modifiers() == modifiers || (event->buttons() & modifiers) != 0;
}
inline bool modifiersUseMouseButton(Settings* settings, Qt::MouseButton mouseButton)
{
return ((settings->documentView().zoomModifiers() | settings->documentView().rotateModifiers() | settings->documentView().scrollModifiers()) & mouseButton) != 0;
}
inline void adjustScaleFactor(RenderParam& renderParam, qreal scaleFactor)
{
if(!qFuzzyCompare(renderParam.scaleFactor(), scaleFactor))
{
renderParam.setScaleFactor(scaleFactor);
}
}
inline void setValueIfNotVisible(QScrollBar* scrollBar, int value)
{
if(value < scrollBar->value() || value > scrollBar->value() + scrollBar->pageStep())
{
scrollBar->setValue(value);
}
}
inline int pageOfResult(const QModelIndex& index)
{
return index.data(SearchModel::PageRole).toInt();
}
inline QRectF rectOfResult(const QModelIndex& index)
{
return index.data(SearchModel::RectRole).toRectF();
}
class OutlineModel : public QAbstractItemModel
{
public:
OutlineModel(const Model::Outline& outline, DocumentView* parent) : QAbstractItemModel(parent),
m_outline(outline)
{
}
QModelIndex index(int row, int column, const QModelIndex& parent) const
{
if(!hasIndex(row, column, parent))
{
return QModelIndex();
}
if(parent.isValid())
{
const Model::Section* section = resolveIndex(parent);
return createIndex(row, column, &section->children);
}
else
{
return createIndex(row, column, &m_outline);
}
}
QModelIndex parent(const QModelIndex& child) const
{
if(!child.isValid())
{
return QModelIndex();
}
const Model::Outline* children = static_cast< const Model::Outline* >(child.internalPointer());
if(&m_outline != children)
{
return findParent(&m_outline, children);
}
return QModelIndex();
}
int columnCount(const QModelIndex&) const
{
return 2;
}
int rowCount(const QModelIndex& parent) const
{
if(parent.isValid())
{
const Model::Section* section = resolveIndex(parent);
return section->children.size();
}
else
{
return m_outline.size();
}
}
QVariant data(const QModelIndex& index, int role) const
{
if(!index.isValid())
{
return QVariant();
}
const Model::Section* section = resolveIndex(index);
switch(role)
{
case Qt::DisplayRole:
switch(index.column())
{
case 0:
return section->title;
case 1:
return pageLabel(section->link.page);
default:
return QVariant();
}
case Model::Document::PageRole:
return section->link.page;
case Model::Document::LeftRole:
return section->link.left;
case Model::Document::TopRole:
return section->link.top;
case Model::Document::FileNameRole:
return section->link.urlOrFileName;
case Model::Document::ExpansionRole:
return m_expanded.contains(section);
default:
return QVariant();
}
}
bool setData(const QModelIndex& index, const QVariant& value, int role)
{
if(!index.isValid() || role != Model::Document::ExpansionRole)
{
return false;
}
const Model::Section* section = resolveIndex(index);
if(value.toBool())
{
m_expanded.insert(section);
}
else
{
m_expanded.remove(section);
}
return true;
}
private:
const Model::Outline m_outline;
DocumentView* documentView() const
{
return static_cast< DocumentView* >(QObject::parent());
}
QString pageLabel(int pageNumber) const
{
return documentView()->pageLabelFromNumber(pageNumber);
}
QSet< const Model::Section* > m_expanded;
const Model::Section* resolveIndex(const QModelIndex& index) const
{
return &static_cast< const Model::Outline* >(index.internalPointer())->at(index.row());
}
QModelIndex createIndex(int row, int column, const Model::Outline* outline) const
{
return QAbstractItemModel::createIndex(row, column, const_cast< void* >(static_cast< const void* >(outline)));
}
QModelIndex findParent(const Model::Outline* outline, const Model::Outline* children) const
{
for(Model::Outline::const_iterator section = outline->begin(); section != outline->end(); ++section)
{
if(&section->children == children)
{
return createIndex(section - outline->begin(), 0, outline);
}
}
for(Model::Outline::const_iterator section = outline->begin(); section != outline->end(); ++section)
{
const QModelIndex parent = findParent(&section->children, children);
if(parent.isValid())
{
return parent;
}
}
return QModelIndex();
}
};
class FallbackOutlineModel : public QAbstractTableModel
{
public:
FallbackOutlineModel(DocumentView* parent) : QAbstractTableModel(parent)
{
}
int columnCount(const QModelIndex&) const
{
return 2;
}
int rowCount(const QModelIndex& parent) const
{
if(parent.isValid())
{
return 0;
}
return numberOfPages();
}
QVariant data(const QModelIndex& index, int role) const
{
if(!index.isValid())
{
return QVariant();
}
const int pageNumber = index.row() + 1;
switch(role)
{
case Qt::DisplayRole:
switch(index.column())
{
case 0:
return DocumentView::tr("Page %1").arg(pageLabel(pageNumber));
case 1:
return pageLabel(pageNumber);
default:
return QVariant();
}
case Model::Document::PageRole:
return pageNumber;
case Model::Document::LeftRole:
case Model::Document::TopRole:
return qQNaN();
default:
return QVariant();
}
}
private:
DocumentView* documentView() const
{
return static_cast< DocumentView* >(QObject::parent());
}
int numberOfPages() const
{
return documentView()->numberOfPages();
}
QString pageLabel(int pageNumber) const
{
return documentView()->pageLabelFromNumber(pageNumber);
}
};
class PropertiesModel : public QAbstractTableModel
{
public:
PropertiesModel(const Model::Properties& properties, DocumentView* parent = 0) : QAbstractTableModel(parent),
m_properties(properties)
{
}
int columnCount(const QModelIndex&) const
{
return 2;
}
int rowCount(const QModelIndex& parent) const
{
if(parent.isValid())
{
return 0;
}
return m_properties.size();
}
QVariant data(const QModelIndex& index, int role) const
{
if(!index.isValid() || role != Qt::DisplayRole)
{
return QVariant();
}
switch (index.column())
{
case 0:
return m_properties[index.row()].first;
case 1:
return m_properties[index.row()].second;
default:
return QVariant();
}
}
private:
const Model::Properties m_properties;
};
void addProperty(Model::Properties& properties, const char* name, const QString& value)
{
properties.append(qMakePair(DocumentView::tr(name), value));
}
QString formatFileSize(qint64 size)
{
static const char* const units[] = { "B", "kB", "MB", "GB" };
static const char* const* const lastUnit = &units[sizeof(units) / sizeof(units[0]) - 1];
const char* const* unit = &units[0];
while(size > 2048 && unit < lastUnit)
{
size /= 1024;
unit++;
}
return QString("%1 %2").arg(size).arg(*unit);
}
void addFileProperties(Model::Properties& properties, const QFileInfo& fileInfo)
{
addProperty(properties, "File path", fileInfo.absoluteFilePath());
addProperty(properties, "File size", formatFileSize(fileInfo.size()));
addProperty(properties, "File created", fileInfo.created().toString());
addProperty(properties, "File last modified", fileInfo.lastModified().toString());
addProperty(properties, "File owner", fileInfo.owner());
addProperty(properties, "File group", fileInfo.owner());
}
void appendToPath(const QModelIndex& index, QByteArray& path)
{
path.append(index.data(Qt::DisplayRole).toByteArray()).append('\0');
}
void saveExpandedPaths(const QAbstractItemModel* model, QSet< QByteArray >& paths, const QModelIndex& index = QModelIndex(), QByteArray path = QByteArray())
{
appendToPath(index, path);
if(model->data(index, Model::Document::ExpansionRole).toBool())
{
paths.insert(path);
}
for(int row = 0, rowCount = model->rowCount(index); row < rowCount; ++row)
{
saveExpandedPaths(model, paths, model->index(row, 0, index), path);
}
}
void restoreExpandedPaths(QAbstractItemModel* model, const QSet< QByteArray >& paths, const QModelIndex& index = QModelIndex(), QByteArray path = QByteArray())
{
appendToPath(index, path);
if(paths.contains(path))
{
model->setData(index, true, Model::Document::ExpansionRole);
}
for(int row = 0, rowCount = model->rowCount(index); row < rowCount; ++row)
{
restoreExpandedPaths(model, paths, model->index(row, 0, index), path);
}
}
} // anonymous
namespace qpdfview
{
class DocumentView::VerticalScrollBarChangedBlocker
{
Q_DISABLE_COPY(VerticalScrollBarChangedBlocker)
private:
DocumentView* const that;
public:
VerticalScrollBarChangedBlocker(DocumentView* that) : that(that)
{
that->m_verticalScrollBarChangedBlocked = true;
}
~VerticalScrollBarChangedBlocker()
{
that->m_verticalScrollBarChangedBlocked = false;
}
};
Settings* DocumentView::s_settings = 0;
ShortcutHandler* DocumentView::s_shortcutHandler = 0;
SearchModel* DocumentView::s_searchModel = 0;
DocumentView::DocumentView(QWidget* parent) : QGraphicsView(parent),
m_autoRefreshWatcher(0),
m_autoRefreshTimer(0),
m_prefetchTimer(0),
m_document(0),
m_pages(),
m_fileInfo(),
m_wasModified(false),
m_currentPage(-1),
m_firstPage(-1),
m_past(),
m_future(),
m_layout(new SinglePageLayout),
m_continuousMode(false),
m_scaleMode(ScaleFactorMode),
m_scaleFactor(1.0),
m_rotation(RotateBy0),
m_renderFlags(0),
m_highlightAll(false),
m_rubberBandMode(ModifiersMode),
m_pageItems(),
m_thumbnailItems(),
m_highlight(0),
m_thumbnailsViewportSize(),
m_thumbnailsOrientation(Qt::Vertical),
m_thumbnailsScene(0),
m_outlineModel(0),
m_propertiesModel(0),
m_verticalScrollBarChangedBlocked(false),
m_currentResult(),
m_searchTask(0)
{
if(s_settings == 0)
{
s_settings = Settings::instance();
}
if(s_shortcutHandler == 0)
{
s_shortcutHandler = ShortcutHandler::instance();
}
if(s_searchModel == 0)
{
s_searchModel = SearchModel::instance();
}
setScene(new QGraphicsScene(this));
setFocusPolicy(Qt::StrongFocus);
setAcceptDrops(false);
setDragMode(QGraphicsView::ScrollHandDrag);
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_verticalScrollBar_valueChanged()));
m_thumbnailsScene = new QGraphicsScene(this);
// highlight
m_highlight = new QGraphicsRectItem();
m_highlight->setGraphicsEffect(new GraphicsCompositionModeEffect(QPainter::CompositionMode_Multiply, this));
m_highlight->setVisible(false);
scene()->addItem(m_highlight);
// search
m_searchTask = new SearchTask(this);
connect(m_searchTask, SIGNAL(finished()), SIGNAL(searchFinished()));
connect(m_searchTask, SIGNAL(progressChanged(int)), SLOT(on_searchTask_progressChanged(int)));
connect(m_searchTask, SIGNAL(resultsReady(int,QList<QRectF>)), SLOT(on_searchTask_resultsReady(int,QList<QRectF>)));
// auto-refresh
m_autoRefreshWatcher = new QFileSystemWatcher(this);
m_autoRefreshTimer = new QTimer(this);
m_autoRefreshTimer->setInterval(s_settings->documentView().autoRefreshTimeout());
m_autoRefreshTimer->setSingleShot(true);
connect(m_autoRefreshWatcher, SIGNAL(fileChanged(QString)), m_autoRefreshTimer, SLOT(start()));
connect(m_autoRefreshTimer, SIGNAL(timeout()), this, SLOT(on_autoRefresh_timeout()));
// prefetch
m_prefetchTimer = new QTimer(this);
m_prefetchTimer->setInterval(s_settings->documentView().prefetchTimeout());
m_prefetchTimer->setSingleShot(true);
connect(this, SIGNAL(currentPageChanged(int)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(layoutModeChanged(LayoutMode)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(scaleModeChanged(ScaleMode)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(scaleFactorChanged(qreal)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(rotationChanged(Rotation)), m_prefetchTimer, SLOT(start()));
connect(this, SIGNAL(renderFlagsChanged(qpdfview::RenderFlags)), m_prefetchTimer, SLOT(start()));
connect(m_prefetchTimer, SIGNAL(timeout()), SLOT(on_prefetch_timeout()));
// settings
m_continuousMode = s_settings->documentView().continuousMode();
m_layout.reset(DocumentLayout::fromLayoutMode(s_settings->documentView().layoutMode()));
m_rightToLeftMode = s_settings->documentView().rightToLeftMode();
m_scaleMode = s_settings->documentView().scaleMode();
m_scaleFactor = s_settings->documentView().scaleFactor();
m_rotation = s_settings->documentView().rotation();
if(s_settings->documentView().invertColors())
{
m_renderFlags |= InvertColors;
}
if(s_settings->documentView().convertToGrayscale())
{
m_renderFlags |= ConvertToGrayscale;
}
if(s_settings->documentView().trimMargins())
{
m_renderFlags |= TrimMargins;
}
switch(s_settings->documentView().compositionMode())
{
default:
case DefaultCompositionMode:
break;
case DarkenWithPaperColorMode:
m_renderFlags |= DarkenWithPaperColor;
break;
case LightenWithPaperColorMode:
m_renderFlags |= LightenWithPaperColor;
break;
}
m_highlightAll = s_settings->documentView().highlightAll();
}
DocumentView::~DocumentView()
{
m_searchTask->cancel();
m_searchTask->wait();
s_searchModel->clearResults(this);
qDeleteAll(m_pageItems);
qDeleteAll(m_thumbnailItems);
qDeleteAll(m_pages);
delete m_document;
}
void DocumentView::setFirstPage(int firstPage)
{
if(m_firstPage != firstPage)
{
m_firstPage = firstPage;
for(int index = 0; index < m_thumbnailItems.count(); ++index)
{
m_thumbnailItems.at(index)->setText(pageLabelFromNumber(index + 1));
}
prepareThumbnailsScene();
emit numberOfPagesChanged(m_pages.count());
emit currentPageChanged(m_currentPage);
}
}
QString DocumentView::defaultPageLabelFromNumber(int number) const
{
QLocale modifiedLocale = locale();
modifiedLocale.setNumberOptions(modifiedLocale.numberOptions() | QLocale::OmitGroupSeparator);
return modifiedLocale.toString(number);
}
QString DocumentView::pageLabelFromNumber(int number) const
{
QString label;
if(hasFrontMatter())
{
if(number < m_firstPage)
{
label = number < 4000 ? intToRoman(number) : defaultPageLabelFromNumber(-number);
}
else
{
label = defaultPageLabelFromNumber(number - m_firstPage + 1);
}
}
else if(number >= 1 && number <= m_pages.count())
{
const QString& pageLabel = m_pages.at(number - 1)->label();
if(number != pageLabel.toInt())
{
label = pageLabel;
}
}
if(label.isEmpty())
{
label = defaultPageLabelFromNumber(number);
}
return label;
}
int DocumentView::pageNumberFromLabel(const QString& label) const
{
if(hasFrontMatter())
{
bool ok = false;
int value = locale().toInt(label, &ok);
if(ok)
{
if(value < 0)
{
value = -value; // front matter
}
else
{
value = value + m_firstPage - 1; // body matter
}
}
else
{
value = romanToInt(label);
}
return value;
}
for(int index = 0; index < m_pages.count(); ++index)
{
if(m_pages.at(index)->label() == label)
{
return index + 1;
}
}
return locale().toInt(label);
}
QString DocumentView::title() const
{
QString title;
if(s_settings->mainWindow().documentTitleAsTabTitle())
{
for(int row = 0, rowCount = m_propertiesModel->rowCount(); row < rowCount; ++row)
{
const QString key = m_propertiesModel->index(row, 0).data().toString();
const QString value = m_propertiesModel->index(row, 1).data().toString();
if(QLatin1String("Title") == key)
{
title = value;
break;
}
}
}
if(title.isEmpty())
{
title = m_fileInfo.completeBaseName();
}
return title;
}
QStringList DocumentView::openFilter()
{
return PluginHandler::openFilter();
}
QStringList DocumentView::saveFilter() const
{
return m_document->saveFilter();
}
bool DocumentView::canSave() const
{
return m_document->canSave();
}
void DocumentView::setContinuousMode(bool continuousMode)
{
if(m_continuousMode != continuousMode)
{
m_continuousMode = continuousMode;
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
adjustScrollBarPolicy();
prepareView(left, top);
emit continuousModeChanged(m_continuousMode);
s_settings->documentView().setContinuousMode(m_continuousMode);
}
}
LayoutMode DocumentView::layoutMode() const
{
return m_layout->layoutMode();
}
void DocumentView::setLayoutMode(LayoutMode layoutMode)
{
if(m_layout->layoutMode() != layoutMode && layoutMode >= 0 && layoutMode < NumberOfLayoutModes)
{
m_layout.reset(DocumentLayout::fromLayoutMode(layoutMode));
if(m_currentPage != m_layout->currentPage(m_currentPage))
{
m_currentPage = m_layout->currentPage(m_currentPage);
emit currentPageChanged(m_currentPage);
}
prepareScene();
prepareView();
emit layoutModeChanged(layoutMode);
s_settings->documentView().setLayoutMode(layoutMode);
}
}
void DocumentView::setRightToLeftMode(bool rightToLeftMode)
{
if(m_rightToLeftMode != rightToLeftMode)
{
m_rightToLeftMode = rightToLeftMode;
prepareScene();
prepareView();
emit rightToLeftModeChanged(m_rightToLeftMode);
s_settings->documentView().setRightToLeftMode(m_rightToLeftMode);
}
}
void DocumentView::setScaleMode(ScaleMode scaleMode)
{
if(m_scaleMode != scaleMode && scaleMode >= 0 && scaleMode < NumberOfScaleModes)
{
m_scaleMode = scaleMode;
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
adjustScrollBarPolicy();
prepareScene();
prepareView(left, top);
emit scaleModeChanged(m_scaleMode);
s_settings->documentView().setScaleMode(m_scaleMode);
}
}
void DocumentView::setScaleFactor(qreal scaleFactor)
{
if(!qFuzzyCompare(m_scaleFactor, scaleFactor)
&& scaleFactor >= s_settings->documentView().minimumScaleFactor()
&& scaleFactor <= s_settings->documentView().maximumScaleFactor())
{
m_scaleFactor = scaleFactor;
if(m_scaleMode == ScaleFactorMode)
{
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
prepareScene();
prepareView(left, top);
}
emit scaleFactorChanged(m_scaleFactor);
s_settings->documentView().setScaleFactor(m_scaleFactor);
}
}
void DocumentView::setRotation(Rotation rotation)
{
if(m_rotation != rotation && rotation >= 0 && rotation < NumberOfRotations)
{
m_rotation = rotation;
prepareScene();
prepareView();
emit rotationChanged(m_rotation);
s_settings->documentView().setRotation(m_rotation);
}
}
void DocumentView::setRenderFlags(qpdfview::RenderFlags renderFlags)
{
if(m_renderFlags != renderFlags)
{
const qpdfview::RenderFlags changedFlags = m_renderFlags ^ renderFlags;
m_renderFlags = renderFlags;
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
prepareScene();
prepareView(left, top);
prepareThumbnailsScene();
if(changedFlags.testFlag(InvertColors))
{
prepareBackground();
emit invertColorsChanged(invertColors());
s_settings->documentView().setInvertColors(invertColors());
}
if(changedFlags.testFlag(ConvertToGrayscale))
{
emit convertToGrayscaleChanged(convertToGrayscale());
s_settings->documentView().setConvertToGrayscale(convertToGrayscale());
}
if(changedFlags.testFlag(TrimMargins))
{
emit trimMarginsChanged(trimMargins());
s_settings->documentView().setTrimMargins(trimMargins());
}
if(changedFlags.testFlag(DarkenWithPaperColor) || changedFlags.testFlag(LightenWithPaperColor))
{
emit compositionModeChanged(compositionMode());
s_settings->documentView().setCompositionMode(compositionMode());
}
emit renderFlagsChanged(m_renderFlags);
}
}
void DocumentView::setRenderFlag(qpdfview::RenderFlag renderFlag, bool enabled)
{
if(enabled)
{
setRenderFlags(m_renderFlags | renderFlag);
}
else
{
setRenderFlags(m_renderFlags & ~renderFlag);
}
}
CompositionMode DocumentView::compositionMode() const
{
if(m_renderFlags.testFlag(DarkenWithPaperColor))
{
return DarkenWithPaperColorMode;
}
else if(m_renderFlags.testFlag(LightenWithPaperColor))
{
return LightenWithPaperColorMode;
}
else
{
return DefaultCompositionMode;
}
}
void DocumentView::setCompositionMode(CompositionMode compositionMode)
{
switch(compositionMode)
{
default:
case DefaultCompositionMode:
setRenderFlags((renderFlags() & ~DarkenWithPaperColor) & ~LightenWithPaperColor);
break;
case DarkenWithPaperColorMode:
setRenderFlags((renderFlags() | DarkenWithPaperColor) & ~LightenWithPaperColor);
break;
case LightenWithPaperColorMode:
setRenderFlags((renderFlags() & ~DarkenWithPaperColor) | LightenWithPaperColor);
break;
}
}
void DocumentView::setHighlightAll(bool highlightAll)
{
if(m_highlightAll != highlightAll)
{
m_highlightAll = highlightAll;
if(m_highlightAll)
{
for(int index = 0; index < m_pages.count(); ++index)
{
const QList< QRectF >& results = s_searchModel->resultsOnPage(this, index + 1);
m_pageItems.at(index)->setHighlights(results);
m_thumbnailItems.at(index)->setHighlights(results);
}
}
else
{
for(int index = 0; index < m_pages.count(); ++index)
{
m_pageItems.at(index)->setHighlights(QList< QRectF >());
m_thumbnailItems.at(index)->setHighlights(QList< QRectF >());
}
}
emit highlightAllChanged(m_highlightAll);
s_settings->documentView().setHighlightAll(highlightAll);
}
}
void DocumentView::setRubberBandMode(RubberBandMode rubberBandMode)
{
if(m_rubberBandMode != rubberBandMode && rubberBandMode >= 0 && rubberBandMode < NumberOfRubberBandModes)
{
m_rubberBandMode = rubberBandMode;
foreach(PageItem* page, m_pageItems)
{
page->setRubberBandMode(m_rubberBandMode);
}
emit rubberBandModeChanged(m_rubberBandMode);
}
}
void DocumentView::setThumbnailsViewportSize(QSize thumbnailsViewportSize)
{
if(m_thumbnailsViewportSize != thumbnailsViewportSize)
{
m_thumbnailsViewportSize = thumbnailsViewportSize;
prepareThumbnailsScene();
}
}
void DocumentView::setThumbnailsOrientation(Qt::Orientation thumbnailsOrientation)
{
if(m_thumbnailsOrientation != thumbnailsOrientation)
{
m_thumbnailsOrientation = thumbnailsOrientation;
prepareThumbnailsScene();
}
}
QSet< QByteArray > DocumentView::saveExpandedPaths() const
{
QSet< QByteArray > expandedPaths;
::saveExpandedPaths(m_outlineModel.data(), expandedPaths);
return expandedPaths;
}
void DocumentView::restoreExpandedPaths(const QSet< QByteArray >& expandedPaths)
{
::restoreExpandedPaths(m_outlineModel.data(), expandedPaths);
}
QAbstractItemModel* DocumentView::fontsModel() const
{
return m_document->fonts();
}
bool DocumentView::searchWasCanceled() const
{
return m_searchTask->wasCanceled();
}
int DocumentView::searchProgress() const
{
return m_searchTask->progress();
}
QString DocumentView::searchText() const
{
return m_searchTask->text();
}
bool DocumentView::searchMatchCase() const
{
return m_searchTask->matchCase();
}
bool DocumentView::searchWholeWords() const
{
return m_searchTask->wholeWords();
}
QPair< QString, QString > DocumentView::searchContext(int page, const QRectF& rect) const
{
if(page < 1 || page > m_pages.size() || rect.isEmpty())
{
return qMakePair(QString(), QString());
}
// Fetch at most half of a line as centered on the given rectangle as possible.
const qreal pageWidth = m_pages.at(page - 1)->size().width();
const qreal width = qMax(rect.width(), pageWidth / qreal(2));
const qreal x = qBound(qreal(0), rect.x() + rect.width() / qreal(2) - width / qreal(2), pageWidth - width);
const QRectF surroundingRect(x, rect.top(), width, rect.height());
const QString& matchedText = m_pages.at(page - 1)->cachedText(rect);
const QString& surroundingText = m_pages.at(page - 1)->cachedText(surroundingRect);
return qMakePair(matchedText, surroundingText);
}
bool DocumentView::hasSearchResults()
{
return s_searchModel->hasResults(this);
}
QString DocumentView::resolveFileName(QString fileName) const
{
if(QFileInfo(fileName).isRelative())
{
fileName = m_fileInfo.dir().filePath(fileName);
}
return fileName;
}
QUrl DocumentView::resolveUrl(QUrl url) const
{
const QString path = url.path();
if(url.isRelative() && QFileInfo(path).isRelative())
{
url.setPath(m_fileInfo.dir().filePath(path));
}
return url;
}
DocumentView::SourceLink DocumentView::sourceLink(QPoint pos)
{
SourceLink sourceLink;
#ifdef WITH_SYNCTEX
if(const PageItem* page = dynamic_cast< PageItem* >(itemAt(pos)))
{
const int sourcePage = page->index() + 1;
const QPointF sourcePos = page->sourcePos(page->mapFromScene(mapToScene(pos)));
sourceLink = scanForSourceLink(m_fileInfo.absoluteFilePath(), sourcePage, sourcePos);
}
#else
Q_UNUSED(pos);
#endif // WITH_SYNCTEX
return sourceLink;
}
void DocumentView::openInSourceEditor(const DocumentView::SourceLink& sourceLink)
{
if(!s_settings->documentView().sourceEditor().isEmpty())
{
const QString absoluteFilePath = m_fileInfo.dir().absoluteFilePath(sourceLink.name);
const QString sourceEditorCommand = s_settings->documentView().sourceEditor().arg(absoluteFilePath, QString::number(sourceLink.line), QString::number(sourceLink.column));
QProcess::startDetached(sourceEditorCommand);
}
else
{
QMessageBox::information(this, tr("Information"), tr("The source editor has not been set."));
}
}
void DocumentView::show()
{
QGraphicsView::show();
prepareView();
}
bool DocumentView::open(const QString& filePath)
{
Model::Document* document = PluginHandler::instance()->loadDocument(filePath);
if(document != 0)
{
QVector< Model::Page* > pages;
if(!checkDocument(filePath, document, pages))
{
delete document;
qDeleteAll(pages);
return false;
}
m_fileInfo.setFile(filePath);
m_wasModified = false;
m_currentPage = 1;
m_past.clear();
m_future.clear();
prepareDocument(document, pages);
loadDocumentDefaults();
adjustScrollBarPolicy();
prepareScene();
prepareView();
prepareThumbnailsScene();
emit documentChanged();
emit numberOfPagesChanged(m_pages.count());
emit currentPageChanged(m_currentPage);
emit canJumpChanged(false, false);
emit continuousModeChanged(m_continuousMode);
emit layoutModeChanged(m_layout->layoutMode());
emit rightToLeftModeChanged(m_rightToLeftMode);
}
return document != 0;
}
bool DocumentView::refresh()
{
Model::Document* document = PluginHandler::instance()->loadDocument(m_fileInfo.filePath());
if(document != 0)
{
QVector< Model::Page* > pages;
if(!checkDocument(m_fileInfo.filePath(), document, pages))
{
delete document;
qDeleteAll(pages);
return false;
}
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
m_wasModified = false;
m_currentPage = qMin(m_currentPage, document->numberOfPages());
QSet< QByteArray > expandedPaths;
::saveExpandedPaths(m_outlineModel.data(), expandedPaths);
prepareDocument(document, pages);
::restoreExpandedPaths(m_outlineModel.data(), expandedPaths);
prepareScene();
prepareView(left, top);
prepareThumbnailsScene();
emit documentChanged();
emit numberOfPagesChanged(m_pages.count());
emit currentPageChanged(m_currentPage);
}
return document != 0;
}
bool DocumentView::save(const QString& filePath, bool withChanges)
{
// Save document to temporary file...
QTemporaryFile temporaryFile;
adjustFileTemplateSuffix(temporaryFile, QFileInfo(filePath).suffix());
if(!temporaryFile.open())
{
return false;
}
temporaryFile.close();
if(!m_document->save(temporaryFile.fileName(), withChanges))
{
return false;
}
// Copy from temporary file to actual file...
QFile file(filePath);
if(!temporaryFile.open())
{
return false;
}
if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
return false;
}
if(!copyFile(temporaryFile, file))
{
return false;
}
if(withChanges)
{
m_wasModified = false;
}
return true;
}
bool DocumentView::print(QPrinter* printer, const PrintOptions& printOptions)
{
const int fromPage = printer->fromPage() != 0 ? printer->fromPage() : 1;
const int toPage = printer->toPage() != 0 ? printer->toPage() : m_pages.count();
#ifdef WITH_CUPS
if(m_document->canBePrintedUsingCUPS())
{
return printUsingCUPS(printer, printOptions, fromPage, toPage);
}
#endif // WITH_CUPS
return printUsingQt(printer, printOptions, fromPage, toPage);
}
void DocumentView::previousPage()
{
jumpToPage(m_layout->previousPage(m_currentPage));
}
void DocumentView::nextPage()
{
jumpToPage(m_layout->nextPage(m_currentPage, m_pages.count()));
}
void DocumentView::firstPage()
{
jumpToPage(1);
}
void DocumentView::lastPage()
{
jumpToPage(m_pages.count());
}
void DocumentView::jumpToPage(int page, bool trackChange, qreal newLeft, qreal newTop)
{
if(page >= 1 && page <= m_pages.count())
{
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
if(qIsNaN(newLeft))
{
newLeft = qBound(qreal(0.0), left, qreal(1.0));
}
if(qIsNaN(newTop))
{
newTop = qBound(qreal(0.0), top, qreal(1.0));
}
if(m_currentPage != m_layout->currentPage(page) || qAbs(left - newLeft) > 0.01 || qAbs(top - newTop) > 0.01)
{
if(trackChange)
{
m_past.append(Position(m_currentPage, left, top));
m_future.clear();
emit canJumpChanged(true, false);
}
m_currentPage = m_layout->currentPage(page);
prepareView(newLeft, newTop, false, page);
emit currentPageChanged(m_currentPage, trackChange);
}
}
}
bool DocumentView::canJumpBackward() const
{
return !m_past.isEmpty();
}
void DocumentView::jumpBackward()
{
if(!m_past.isEmpty())
{
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
m_future.prepend(Position(m_currentPage, left, top));
const Position pos = m_past.takeLast();
jumpToPage(pos.page, false, pos.left, pos.top);
emit canJumpChanged(!m_past.isEmpty(), !m_future.isEmpty());
}
}
bool DocumentView::canJumpForward() const
{
return !m_future.isEmpty();
}
void DocumentView::jumpForward()
{
if(!m_future.isEmpty())
{
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
m_past.append(Position(m_currentPage, left, top));
const Position pos = m_future.takeFirst();
jumpToPage(pos.page, false, pos.left, pos.top);
emit canJumpChanged(!m_past.isEmpty(), !m_future.isEmpty());
}
}
void DocumentView::temporaryHighlight(int page, const QRectF& highlight)
{
if(page >= 1 && page <= m_pages.count() && !highlight.isNull())
{
prepareHighlight(page - 1, highlight);
QTimer::singleShot(s_settings->documentView().highlightDuration(), this, SLOT(on_temporaryHighlight_timeout()));
}
}
void DocumentView::startSearch(const QString& text, bool matchCase, bool wholeWords)
{
cancelSearch();
clearResults();
m_searchTask->start(m_pages, text, matchCase, wholeWords, m_currentPage, s_settings->documentView().parallelSearchExecution());
}
void DocumentView::cancelSearch()
{
m_searchTask->cancel();
m_searchTask->wait();
}
void DocumentView::clearResults()
{
s_searchModel->clearResults(this);
m_currentResult = QModelIndex();
m_highlight->setVisible(false);
foreach(PageItem* page, m_pageItems)
{
page->setHighlights(QList< QRectF >());
}
foreach(ThumbnailItem* page, m_thumbnailItems)
{
page->setHighlights(QList< QRectF >());
}
if(s_settings->documentView().limitThumbnailsToResults())
{
prepareThumbnailsScene();
}
}
void DocumentView::findPrevious()
{
checkResult();
m_currentResult = s_searchModel->findResult(this, m_currentResult, m_currentPage, SearchModel::FindPrevious);
applyResult();
}
void DocumentView::findNext()
{
checkResult();
m_currentResult = s_searchModel->findResult(this, m_currentResult, m_currentPage, SearchModel::FindNext);
applyResult();
}
void DocumentView::findResult(const QModelIndex& index)
{
const int page = pageOfResult(index);
const QRectF rect = rectOfResult(index);
if(page >= 1 && page <= m_pages.count() && !rect.isEmpty())
{
m_currentResult = index;
applyResult();
}
}
void DocumentView::zoomIn()
{
if(scaleMode() != ScaleFactorMode)
{
const qreal currentScaleFactor = m_pageItems.at(m_currentPage - 1)->renderParam().scaleFactor();
setScaleFactor(qMin(currentScaleFactor * s_settings->documentView().zoomFactor(),
s_settings->documentView().maximumScaleFactor()));
setScaleMode(ScaleFactorMode);
}
else
{
setScaleFactor(qMin(m_scaleFactor * s_settings->documentView().zoomFactor(),
s_settings->documentView().maximumScaleFactor()));
}
}
void DocumentView::zoomOut()
{
if(scaleMode() != ScaleFactorMode)
{
const qreal currentScaleFactor = m_pageItems.at(m_currentPage - 1)->renderParam().scaleFactor();
setScaleFactor(qMax(currentScaleFactor / s_settings->documentView().zoomFactor(),
s_settings->documentView().minimumScaleFactor()));
setScaleMode(ScaleFactorMode);
}
else
{
setScaleFactor(qMax(m_scaleFactor / s_settings->documentView().zoomFactor(),
s_settings->documentView().minimumScaleFactor()));
}
}
void DocumentView::originalSize()
{
setScaleFactor(1.0);
setScaleMode(ScaleFactorMode);
}
void DocumentView::rotateLeft()
{
switch(m_rotation)
{
default:
case RotateBy0:
setRotation(RotateBy270);
break;
case RotateBy90:
setRotation(RotateBy0);
break;
case RotateBy180:
setRotation(RotateBy90);
break;
case RotateBy270:
setRotation(RotateBy180);
break;
}
}
void DocumentView::rotateRight()
{
switch(m_rotation)
{
default:
case RotateBy0:
setRotation(RotateBy90);
break;
case RotateBy90:
setRotation(RotateBy180);
break;
case RotateBy180:
setRotation(RotateBy270);
break;
case RotateBy270:
setRotation(RotateBy0);
break;
}
}
void DocumentView::startPresentation()
{
const int screen = s_settings->presentationView().screen();
PresentationView* presentationView = new PresentationView(m_pages);
presentationView->setGeometry(QApplication::desktop()->screenGeometry(screen));
presentationView->show();
presentationView->setAttribute(Qt::WA_DeleteOnClose);
connect(this, SIGNAL(destroyed()), presentationView, SLOT(close()));
connect(this, SIGNAL(documentChanged()), presentationView, SLOT(close()));
presentationView->setRotation(rotation());
presentationView->setRenderFlags(renderFlags());
presentationView->jumpToPage(currentPage(), false);
if(s_settings->presentationView().synchronize())
{
connect(this, SIGNAL(currentPageChanged(int,bool)), presentationView, SLOT(jumpToPage(int,bool)));
connect(presentationView, SIGNAL(currentPageChanged(int,bool)), this, SLOT(jumpToPage(int,bool)));
}
}
void DocumentView::on_verticalScrollBar_valueChanged()
{
if(m_verticalScrollBarChangedBlocked || !m_continuousMode)
{
return;
}
int currentPage = -1;
const QRectF visibleRect = mapToScene(viewport()->rect()).boundingRect();
for(int index = 0, count = m_pageItems.count(); index < count; ++index)
{
PageItem* page = m_pageItems.at((m_currentPage - 1 + index) % count);
const int pageNumber = page->index() + 1;
const QRectF pageRect = page->boundingRect().translated(page->pos());
if(!pageRect.intersects(visibleRect))
{
page->cancelRender();
}
else if(currentPage == -1 &&
m_layout->currentPage(pageNumber) == pageNumber &&
m_layout->isCurrentPage(visibleRect, pageRect))
{
currentPage = pageNumber;
}
}
if(currentPage != -1 && m_currentPage != currentPage)
{
m_currentPage = currentPage;
emit currentPageChanged(m_currentPage);
if(s_settings->documentView().highlightCurrentThumbnail())
{
for(int index = 0; index < m_thumbnailItems.count(); ++index)
{
m_thumbnailItems.at(index)->setHighlighted(index == m_currentPage - 1);
}
}
}
}
void DocumentView::on_autoRefresh_timeout()
{
if(m_fileInfo.exists())
{
refresh();
}
else
{
m_wasModified = true;
emit documentModified();
}
}
void DocumentView::on_prefetch_timeout()
{
const QPair< int, int > prefetchRange = m_layout->prefetchRange(m_currentPage, m_pages.count());
const int maxCost = prefetchRange.second - prefetchRange.first + 1;
int cost = 0;
for(int index = m_currentPage - 1; index <= prefetchRange.second - 1; ++index)
{
cost += m_pageItems.at(index)->startRender(true);
if(cost >= maxCost)
{
return;
}
}
for(int index = m_currentPage - 1; index >= prefetchRange.first - 1; --index)
{
cost += m_pageItems.at(index)->startRender(true);
if(cost >= maxCost)
{
return;
}
}
}
void DocumentView::on_temporaryHighlight_timeout()
{
m_highlight->setVisible(false);
}
void DocumentView::on_searchTask_progressChanged(int progress)
{
s_searchModel->updateProgress(this);
emit searchProgressChanged(progress);
}
void DocumentView::on_searchTask_resultsReady(int index, const QList< QRectF >& results)
{
if(m_searchTask->wasCanceled())
{
return;
}
s_searchModel->insertResults(this, index + 1, results);
if(m_highlightAll)
{
m_pageItems.at(index)->setHighlights(results);
m_thumbnailItems.at(index)->setHighlights(results);
}
if(s_settings->documentView().limitThumbnailsToResults())
{
prepareThumbnailsScene();
}
if(!results.isEmpty() && !m_currentResult.isValid())
{
setFocus();
findNext();
}
}
void DocumentView::on_pages_cropRectChanged()
{
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
prepareScene();
prepareView(left, top);
}
void DocumentView::on_thumbnails_cropRectChanged()
{
prepareThumbnailsScene();
}
void DocumentView::on_pages_linkClicked(bool newTab, int page, qreal left, qreal top)
{
if(newTab)
{
emit linkClicked(page);
}
else
{
jumpToPage(page, true, left, top);
}
}
void DocumentView::on_pages_linkClicked(bool newTab, const QString& fileName, int page)
{
emit linkClicked(newTab, resolveFileName(fileName), page);
}
void DocumentView::on_pages_linkClicked(const QString& url)
{
if(s_settings->documentView().openUrl())
{
QDesktopServices::openUrl(resolveUrl(url));
}
else
{
QMessageBox::information(this, tr("Information"), tr("Opening URL is disabled in the settings."));
}
}
void DocumentView::on_pages_rubberBandFinished()
{
setRubberBandMode(ModifiersMode);
}
void DocumentView::on_pages_zoomToSelection(int page, const QRectF& rect)
{
if(rect.isEmpty())
{
return;
}
const qreal visibleWidth = m_layout->visibleWidth(viewport()->width());
const qreal visibleHeight = m_layout->visibleHeight(viewport()->height());
const QSizeF displayedSize = m_pageItems.at(page - 1)->displayedSize();
setScaleFactor(qMin(qMin(visibleWidth / displayedSize.width() / rect.width(),
visibleHeight / displayedSize.height() / rect.height()),
Defaults::DocumentView::maximumScaleFactor()));
setScaleMode(ScaleFactorMode);
jumpToPage(page, false, rect.left(), rect.top());
}
void DocumentView::on_pages_openInSourceEditor(int page, QPointF pos)
{
#ifdef WITH_SYNCTEX
if(const DocumentView::SourceLink sourceLink = scanForSourceLink(m_fileInfo.absoluteFilePath(), page, pos))
{
openInSourceEditor(sourceLink);
}
else
{
QMessageBox::warning(this, tr("Warning"), tr("SyncTeX data for '%1' could not be found.").arg(m_fileInfo.absoluteFilePath()));
}
#else
Q_UNUSED(page);
Q_UNUSED(pos);
#endif // WITH_SYNCTEX
}
void DocumentView::on_pages_wasModified()
{
m_wasModified = true;
emit documentModified();
}
void DocumentView::resizeEvent(QResizeEvent* event)
{
qreal left = 0.0, top = 0.0;
saveLeftAndTop(left, top);
QGraphicsView::resizeEvent(event);
if(m_scaleMode != ScaleFactorMode)
{
prepareScene();
prepareView(left, top);
}
}
void DocumentView::keyPressEvent(QKeyEvent* event)
{
foreach(const PageItem* page, m_pageItems)
{
if(page->showsAnnotationOverlay() || page->showsFormFieldOverlay())
{
QGraphicsView::keyPressEvent(event);
return;
}
}
const QKeySequence keySequence(event->modifiers() + event->key());
int maskedKey = -1;
bool maskedKeyActive = false;
switch(event->key())
{
case Qt::Key_PageUp:
case Qt::Key_PageDown:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Left:
case Qt::Key_Right:
maskedKeyActive = true;
break;
}
if(s_shortcutHandler->matchesSkipBackward(keySequence))
{
maskedKey = Qt::Key_PageUp;
}
else if(s_shortcutHandler->matchesSkipForward(keySequence))
{
maskedKey = Qt::Key_PageDown;
}
else if(s_shortcutHandler->matchesMoveUp(keySequence))
{
maskedKey = Qt::Key_Up;
}
else if(s_shortcutHandler->matchesMoveDown(keySequence))
{
maskedKey = Qt::Key_Down;
}
else if(s_shortcutHandler->matchesMoveLeft(keySequence))
{
maskedKey = Qt::Key_Left;
}
else if(s_shortcutHandler->matchesMoveRight(keySequence))
{
maskedKey = Qt::Key_Right;
}
else if(maskedKeyActive)
{
event->ignore();
return;
}
if(maskedKey == -1)
{
QGraphicsView::keyPressEvent(event);
}
else
{
if(!m_continuousMode)
{
if(maskedKey == Qt::Key_PageUp && verticalScrollBar()->value() == verticalScrollBar()->minimum() && m_currentPage != 1)
{
previousPage();
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
event->accept();
return;
}
else if(maskedKey == Qt::Key_PageDown && verticalScrollBar()->value() == verticalScrollBar()->maximum() && m_currentPage != m_layout->currentPage(m_pages.count()))
{
nextPage();
verticalScrollBar()->setValue(verticalScrollBar()->minimum());
event->accept();
return;
}
}
if((maskedKey == Qt::Key_Up && verticalScrollBar()->minimum() == verticalScrollBar()->maximum()) ||
(maskedKey == Qt::Key_Left && !horizontalScrollBar()->isVisible()))
{
previousPage();
event->accept();
return;
}
else if((maskedKey == Qt::Key_Down && verticalScrollBar()->minimum() == verticalScrollBar()->maximum()) ||
(maskedKey == Qt::Key_Right && !horizontalScrollBar()->isVisible()))
{
nextPage();
event->accept();
return;
}
QKeyEvent keyEvent(event->type(), maskedKey, Qt::NoModifier, event->text(), event->isAutoRepeat(), event->count());
QGraphicsView::keyPressEvent(&keyEvent);
}
}
void DocumentView::mousePressEvent(QMouseEvent* event)
{
if(event->button() == Qt::XButton1)
{
event->accept();
jumpBackward();
}
else if(event->button() == Qt::XButton2)
{
event->accept();
jumpForward();
}
QGraphicsView::mousePressEvent(event);
}
void DocumentView::wheelEvent(QWheelEvent* event)
{
const bool noModifiersActive = event->modifiers() == Qt::NoModifier;
const bool zoomModifiersActive = modifiersAreActive(event, s_settings->documentView().zoomModifiers());
const bool rotateModifiersActive = modifiersAreActive(event, s_settings->documentView().rotateModifiers());
const bool scrollModifiersActive = modifiersAreActive(event, s_settings->documentView().scrollModifiers());
if(zoomModifiersActive)
{
if(event->delta() > 0)
{
zoomIn();
}
else
{
zoomOut();
}
event->accept();
return;
}
else if(rotateModifiersActive)
{
if(event->delta() > 0)
{
rotateLeft();
}
else
{
rotateRight();
}
event->accept();
return;
}
else if(scrollModifiersActive)
{
QWheelEvent wheelEvent(event->pos(), event->delta(), event->buttons(), Qt::AltModifier, Qt::Horizontal);
QGraphicsView::wheelEvent(&wheelEvent);
event->accept();
return;
}
else if(noModifiersActive && !m_continuousMode)
{
if(event->delta() > 0 && verticalScrollBar()->value() == verticalScrollBar()->minimum() && m_currentPage != 1)
{
previousPage();
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
event->accept();
return;
}
else if(event->delta() < 0 && verticalScrollBar()->value() == verticalScrollBar()->maximum() && m_currentPage != m_layout->currentPage(m_pages.count()))
{
nextPage();
verticalScrollBar()->setValue(verticalScrollBar()->minimum());
event->accept();
return;
}
}
QGraphicsView::wheelEvent(event);
}
void DocumentView::contextMenuEvent(QContextMenuEvent* event)
{
if(event->reason() == QContextMenuEvent::Mouse && modifiersUseMouseButton(s_settings, Qt::RightButton))
{
event->accept();
return;
}
event->setAccepted(false);
QGraphicsView::contextMenuEvent(event);
if(!event->isAccepted())
{
event->setAccepted(true);
emit customContextMenuRequested(event->pos());
}
}
#ifdef WITH_CUPS
bool DocumentView::printUsingCUPS(QPrinter* printer, const PrintOptions& printOptions, int fromPage, int toPage)
{
int num_dests = 0;
cups_dest_t* dests = 0;
int num_options = 0;
cups_option_t* options = 0;
cups_dest_t* dest = 0;
int jobId = 0;
num_dests = cupsGetDests(&dests);
dest = cupsGetDest(printer->printerName().toUtf8(), 0, num_dests, dests);
if(dest == 0)
{
qWarning() << cupsLastErrorString();
cupsFreeDests(num_dests, dests);
return false;
}
for(int index = 0; index < dest->num_options; ++index)
{
num_options = cupsAddOption(dest->options[index].name, dest->options[index].value, num_options, &options);
}
const QStringList cupsOptions = printer->printEngine()->property(QPrintEngine::PrintEnginePropertyKey(0xfe00)).toStringList();
for(int index = 0; index < cupsOptions.count() - 1; index += 2)
{
num_options = cupsAddOption(cupsOptions.at(index).toUtf8(), cupsOptions.at(index + 1).toUtf8(), num_options, &options);
}
#if QT_VERSION >= QT_VERSION_CHECK(4,7,0)
num_options = cupsAddOption("copies", QString::number(printer->copyCount()).toUtf8(), num_options, &options);
#endif // QT_VERSION
num_options = cupsAddOption("Collate", printer->collateCopies() ? "true" : "false", num_options, &options);
switch(printer->pageOrder())
{
case QPrinter::FirstPageFirst:
num_options = cupsAddOption("outputorder", "normal", num_options, &options);
break;
case QPrinter::LastPageFirst:
num_options = cupsAddOption("outputorder", "reverse", num_options, &options);
break;
}
num_options = cupsAddOption("fit-to-page", printOptions.fitToPage ? "true" : "false", num_options, &options);
switch(printer->orientation())
{
case QPrinter::Portrait:
num_options = cupsAddOption("landscape", "false", num_options, &options);
break;
case QPrinter::Landscape:
num_options = cupsAddOption("landscape", "true", num_options, &options);
break;
}
switch(printer->colorMode())
{
case QPrinter::Color:
num_options = addCMYKorRGBColorModel(dest, num_options, &options);
num_options = cupsAddOption("Ink", "COLOR", num_options, &options);
break;
case QPrinter::GrayScale:
num_options = cupsAddOption("ColorModel", "Gray", num_options, &options);
num_options = cupsAddOption("Ink", "MONO", num_options, &options);
break;
}
switch(printer->duplex())
{
case QPrinter::DuplexNone:
num_options = cupsAddOption("sides", "one-sided", num_options, &options);
break;
case QPrinter::DuplexAuto:
break;
case QPrinter::DuplexLongSide:
num_options = cupsAddOption("sides", "two-sided-long-edge", num_options, &options);
break;
case QPrinter::DuplexShortSide:
num_options = cupsAddOption("sides", "two-sided-short-edge", num_options, &options);
break;
}
int numberUp = 1;
#if QT_VERSION < QT_VERSION_CHECK(5,2,0)
switch(printOptions.numberUp)
{
case PrintOptions::SinglePage:
num_options = cupsAddOption("number-up", "1", num_options, &options);
numberUp = 1;
break;
case PrintOptions::TwoPages:
num_options = cupsAddOption("number-up", "2", num_options, &options);
numberUp = 2;
break;
case PrintOptions::FourPages:
num_options = cupsAddOption("number-up", "4", num_options, &options);
numberUp = 4;
break;
case PrintOptions::SixPages:
num_options = cupsAddOption("number-up", "6", num_options, &options);
numberUp = 6;
break;
case PrintOptions::NinePages:
num_options = cupsAddOption("number-up", "9", num_options, &options);
numberUp = 9;
break;
case PrintOptions::SixteenPages:
num_options = cupsAddOption("number-up", "16", num_options, &options);
numberUp = 16;
break;
}
switch(printOptions.numberUpLayout)
{
case PrintOptions::BottomTopLeftRight:
num_options = cupsAddOption("number-up-layout", "btlr", num_options, &options);
break;
case PrintOptions::BottomTopRightLeft:
num_options = cupsAddOption("number-up-layout", "btrl", num_options, &options);
break;
case PrintOptions::LeftRightBottomTop:
num_options = cupsAddOption("number-up-layout", "lrbt", num_options, &options);
break;
case PrintOptions::LeftRightTopBottom:
num_options = cupsAddOption("number-up-layout", "lrtb", num_options, &options);
break;
case PrintOptions::RightLeftBottomTop:
num_options = cupsAddOption("number-up-layout", "rlbt", num_options, &options);
break;
case PrintOptions::RightLeftTopBottom:
num_options = cupsAddOption("number-up-layout", "rltb", num_options, &options);
break;
case PrintOptions::TopBottomLeftRight:
num_options = cupsAddOption("number-up-layout", "tblr", num_options, &options);
break;
case PrintOptions::TopBottomRightLeft:
num_options = cupsAddOption("number-up-layout", "tbrl", num_options, &options);
break;
}
switch(printOptions.pageSet)
{
case PrintOptions::AllPages:
break;
case PrintOptions::EvenPages:
num_options = cupsAddOption("page-set", "even", num_options, &options);
break;
case PrintOptions::OddPages:
num_options = cupsAddOption("page-set", "odd", num_options, &options);
break;
}
#else
{
bool ok = false;
int value = QString::fromUtf8(cupsGetOption("number-up", num_options, options)).toInt(&ok);
numberUp = ok ? value : 1;
}
#endif // QT_VERSION
fromPage = (fromPage - 1) / numberUp + 1;
toPage = (toPage - 1) / numberUp + 1;
if(cupsGetOption("page-ranges", num_options, options) == 0)
{
if(printOptions.pageRanges.isEmpty())
{
num_options = cupsAddOption("page-ranges", QString("%1-%2").arg(fromPage).arg(toPage).toUtf8(), num_options, &options);
}
else
{
num_options = cupsAddOption("page-ranges", printOptions.pageRanges.toUtf8(), num_options, &options);
}
}
QTemporaryFile temporaryFile;
adjustFileTemplateSuffix(temporaryFile, m_fileInfo.suffix());
if(!temporaryFile.open())
{
cupsFreeDests(num_dests, dests);
cupsFreeOptions(num_options, options);
return false;
}
temporaryFile.close();
if(!m_document->save(temporaryFile.fileName(), true))
{
cupsFreeDests(num_dests, dests);
cupsFreeOptions(num_options, options);
return false;
}
jobId = cupsPrintFile(dest->name, QFile::encodeName(temporaryFile.fileName()), m_fileInfo.completeBaseName().toUtf8(), num_options, options);
if(jobId < 1)
{
qWarning() << cupsLastErrorString();
}
cupsFreeDests(num_dests, dests);
cupsFreeOptions(num_options, options);
return jobId >= 1;
}
#endif // WITH_CUPS
bool DocumentView::printUsingQt(QPrinter* printer, const PrintOptions& printOptions, int fromPage, int toPage)
{
QScopedPointer< QProgressDialog > progressDialog(new QProgressDialog(this));
progressDialog->setLabelText(tr("Printing '%1'...").arg(m_fileInfo.completeBaseName()));
progressDialog->setRange(fromPage - 1, toPage);
QPainter painter;
if(!painter.begin(printer))
{
return false;
}
for(int index = fromPage - 1; index <= toPage - 1; ++index)
{
progressDialog->setValue(index);
QApplication::processEvents();
painter.save();
const Model::Page* page = m_pages.at(index);
if(printOptions.fitToPage)
{
const qreal pageWidth = printer->physicalDpiX() / 72.0 * page->size().width();
const qreal pageHeight = printer->physicalDpiY() / 72.0 * page->size().width();
const qreal scaleFactor = qMin(printer->width() / pageWidth, printer->height() / pageHeight);
painter.setTransform(QTransform::fromScale(scaleFactor, scaleFactor));
}
else
{
const qreal scaleFactorX = static_cast< qreal >(printer->logicalDpiX()) / static_cast< qreal >(printer->physicalDpiX());
const qreal scaleFactorY = static_cast< qreal >(printer->logicalDpiY()) / static_cast< qreal >(printer->physicalDpiY());
painter.setTransform(QTransform::fromScale(scaleFactorX, scaleFactorY));
}
painter.drawImage(QPointF(), page->render(printer->physicalDpiX(), printer->physicalDpiY()));
painter.restore();
if(index < toPage - 1)
{
printer->newPage();
}
QApplication::processEvents();
if(progressDialog->wasCanceled())
{
printer->abort();
return false;
}
}
return true;
}
void DocumentView::saveLeftAndTop(qreal& left, qreal& top) const
{
const PageItem* page = m_pageItems.at(m_currentPage - 1);
const QRectF boundingRect = page->uncroppedBoundingRect().translated(page->pos());
const QPointF topLeft = mapToScene(viewport()->rect().topLeft());
left = (topLeft.x() - boundingRect.x()) / boundingRect.width();
top = (topLeft.y() - boundingRect.y()) / boundingRect.height();
}
bool DocumentView::checkDocument(const QString& filePath, Model::Document* document, QVector< Model::Page* >& pages)
{
if(document->isLocked())
{
QString password = QInputDialog::getText(this, tr("Unlock %1").arg(QFileInfo(filePath).completeBaseName()), tr("Password:"), QLineEdit::Password);
if(document->unlock(password))
{
return false;
}
}
const int numberOfPages = document->numberOfPages();
if(numberOfPages == 0)
{
qWarning() << "No pages were found in document at" << filePath;
return false;
}
pages.reserve(numberOfPages);
for(int index = 0; index < numberOfPages; ++index)
{
Model::Page* page = document->page(index);
if(page == 0)
{
qWarning() << "No page" << index << "was found in document at" << filePath;
return false;
}
pages.append(page);
}
return true;
}
void DocumentView::loadDocumentDefaults()
{
if(m_document->wantsContinuousMode())
{
m_continuousMode = true;
}
if(m_document->wantsSinglePageMode())
{
m_layout.reset(new SinglePageLayout);
}
else if(m_document->wantsTwoPagesMode())
{
m_layout.reset(new TwoPagesLayout);
}
else if(m_document->wantsTwoPagesWithCoverPageMode())
{
m_layout.reset(new TwoPagesWithCoverPageLayout);
}
if(m_document->wantsRightToLeftMode())
{
m_rightToLeftMode = true;
}
}
void DocumentView::adjustScrollBarPolicy()
{
switch(m_scaleMode)
{
default:
case ScaleFactorMode:
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
break;
case FitToPageWidthMode:
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
break;
case FitToPageSizeMode:
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(m_continuousMode ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
break;
}
}
void DocumentView::prepareDocument(Model::Document* document, const QVector< Model::Page* >& pages)
{
m_prefetchTimer->blockSignals(true);
m_prefetchTimer->stop();
cancelSearch();
clearResults();
qDeleteAll(m_pageItems);
qDeleteAll(m_thumbnailItems);
qDeleteAll(m_pages);
m_pages = pages;
delete m_document;
m_document = document;
if(!m_autoRefreshWatcher->files().isEmpty())
{
m_autoRefreshWatcher->removePaths(m_autoRefreshWatcher->files());
}
if(s_settings->documentView().autoRefresh())
{
m_autoRefreshWatcher->addPath(m_fileInfo.filePath());
}
m_document->setPaperColor(s_settings->pageItem().paperColor());
preparePages();
prepareThumbnails();
prepareBackground();
const Model::Outline outline = m_document->outline();
if(!outline.empty())
{
m_outlineModel.reset(new OutlineModel(outline, this));
}
else
{
m_outlineModel.reset(new FallbackOutlineModel(this));
}
Model::Properties properties = m_document->properties();
addFileProperties(properties, m_fileInfo);
m_propertiesModel.reset(new PropertiesModel(properties, this));
if(s_settings->documentView().prefetch())
{
m_prefetchTimer->blockSignals(false);
m_prefetchTimer->start();
}
}
void DocumentView::preparePages()
{
m_pageItems.clear();
m_pageItems.reserve(m_pages.count());
for(int index = 0; index < m_pages.count(); ++index)
{
PageItem* page = new PageItem(m_pages.at(index), index);
page->setRubberBandMode(m_rubberBandMode);
scene()->addItem(page);
m_pageItems.append(page);
connect(page, SIGNAL(cropRectChanged()), SLOT(on_pages_cropRectChanged()));
connect(page, SIGNAL(linkClicked(bool,int,qreal,qreal)), SLOT(on_pages_linkClicked(bool,int,qreal,qreal)));
connect(page, SIGNAL(linkClicked(bool,QString,int)), SLOT(on_pages_linkClicked(bool,QString,int)));
connect(page, SIGNAL(linkClicked(QString)), SLOT(on_pages_linkClicked(QString)));
connect(page, SIGNAL(rubberBandFinished()), SLOT(on_pages_rubberBandFinished()));
connect(page, SIGNAL(zoomToSelection(int,QRectF)), SLOT(on_pages_zoomToSelection(int,QRectF)));
connect(page, SIGNAL(openInSourceEditor(int,QPointF)), SLOT(on_pages_openInSourceEditor(int,QPointF)));
connect(page, SIGNAL(wasModified()), SLOT(on_pages_wasModified()));
}
}
void DocumentView::prepareThumbnails()
{
m_thumbnailItems.clear();
m_thumbnailItems.reserve(m_pages.count());
for(int index = 0; index < m_pages.count(); ++index)
{
ThumbnailItem* page = new ThumbnailItem(m_pages.at(index), pageLabelFromNumber(index + 1), index);
m_thumbnailsScene->addItem(page);
m_thumbnailItems.append(page);
connect(page, SIGNAL(cropRectChanged()), SLOT(on_thumbnails_cropRectChanged()));
connect(page, SIGNAL(linkClicked(bool,int,qreal,qreal)), SLOT(on_pages_linkClicked(bool,int,qreal,qreal)));
}
}
void DocumentView::prepareBackground()
{
QColor backgroundColor;
if(s_settings->pageItem().decoratePages())
{
backgroundColor = s_settings->pageItem().backgroundColor();
}
else
{
backgroundColor = s_settings->pageItem().paperColor();
if(invertColors())
{
backgroundColor.setRgb(~backgroundColor.rgb());
}
}
scene()->setBackgroundBrush(QBrush(backgroundColor));
m_thumbnailsScene->setBackgroundBrush(QBrush(backgroundColor));
}
void DocumentView::prepareScene()
{
// prepare render parameters and adjust scale factor
RenderParam renderParam(logicalDpiX(), logicalDpiY(), 1.0,
scaleFactor(), rotation(), renderFlags());
#if QT_VERSION >= QT_VERSION_CHECK(5,1,0)
if(s_settings->pageItem().useDevicePixelRatio())
{
#if QT_VERSION >= QT_VERSION_CHECK(5,6,0)
renderParam.setDevicePixelRatio(devicePixelRatioF());
#else
renderParam.setDevicePixelRatio(devicePixelRatio());
#endif // QT_VERSION
}
#endif // QT_VERSION
const qreal visibleWidth = m_layout->visibleWidth(viewport()->width());
const qreal visibleHeight = m_layout->visibleHeight(viewport()->height());
foreach(PageItem* page, m_pageItems)
{
const QSizeF displayedSize = page->displayedSize(renderParam);
if(m_scaleMode == FitToPageWidthMode)
{
adjustScaleFactor(renderParam, visibleWidth / displayedSize.width());
}
else if(m_scaleMode == FitToPageSizeMode)
{
adjustScaleFactor(renderParam, qMin(visibleWidth / displayedSize.width(), visibleHeight / displayedSize.height()));
}
page->setRenderParam(renderParam);
}
// prepare layout
qreal left = 0.0;
qreal right = 0.0;
qreal height = s_settings->documentView().pageSpacing();
m_layout->prepareLayout(m_pageItems, m_rightToLeftMode,
left, right, height);
scene()->setSceneRect(left, 0.0, right - left, height);
}
void DocumentView::prepareView(qreal newLeft, qreal newTop, bool forceScroll, int scrollToPage)
{
const QRectF sceneRect = scene()->sceneRect();
qreal top = sceneRect.top();
qreal height = sceneRect.height();
int horizontalValue = 0;
int verticalValue = 0;
scrollToPage = scrollToPage != 0 ? scrollToPage : m_currentPage;
const int highlightIsOnPage = m_currentResult.isValid() ? pageOfResult(m_currentResult) : 0;
const bool highlightCurrentThumbnail = s_settings->documentView().highlightCurrentThumbnail();
for(int index = 0; index < m_pageItems.count(); ++index)
{
PageItem* page = m_pageItems.at(index);
if(m_continuousMode)
{
page->setVisible(true);
}
else
{
if(m_layout->leftIndex(index) == m_currentPage - 1)
{
page->setVisible(true);
const QRectF boundingRect = page->boundingRect().translated(page->pos());
top = boundingRect.top() - s_settings->documentView().pageSpacing();
height = boundingRect.height() + 2.0 * s_settings->documentView().pageSpacing();
}
else
{
page->setVisible(false);
page->cancelRender();
}
}
if(index == scrollToPage - 1)
{
const QRectF boundingRect = page->uncroppedBoundingRect().translated(page->pos());
horizontalValue = qFloor(boundingRect.left() + newLeft * boundingRect.width());
verticalValue = qFloor(boundingRect.top() + newTop * boundingRect.height());
}
if(index == highlightIsOnPage - 1)
{
m_highlight->setPos(page->pos());
m_highlight->setTransform(page->transform());
page->stackBefore(m_highlight);
}
m_thumbnailItems.at(index)->setHighlighted(highlightCurrentThumbnail && (index == m_currentPage - 1));
}
setSceneRect(sceneRect.left(), top, sceneRect.width(), height);
if(!forceScroll && s_settings->documentView().minimalScrolling())
{
setValueIfNotVisible(horizontalScrollBar(), horizontalValue);
setValueIfNotVisible(verticalScrollBar(), verticalValue);
}
else
{
horizontalScrollBar()->setValue(horizontalValue);
verticalScrollBar()->setValue(verticalValue);
}
viewport()->update();
}
void DocumentView::prepareThumbnailsScene()
{
// prepare render parameters and adjust scale factor
RenderParam renderParam(logicalDpiX(), logicalDpiY(), 1.0,
scaleFactor(), rotation(), renderFlags());
#if QT_VERSION >= QT_VERSION_CHECK(5,1,0)
if(s_settings->pageItem().useDevicePixelRatio())
{
#if QT_VERSION >= QT_VERSION_CHECK(5,6,0)
renderParam.setDevicePixelRatio(devicePixelRatioF());
#else
renderParam.setDevicePixelRatio(devicePixelRatio());
#endif // QT_VERSION
}
#endif // QT_VERSION
const qreal thumbnailSize = s_settings->documentView().thumbnailSize();
const qreal thumbnailSpacing = s_settings->documentView().thumbnailSpacing();
qreal visibleWidth = Defaults::DocumentView::thumbnailSize();
qreal visibleHeight = Defaults::DocumentView::thumbnailSize();
if(!m_thumbnailsViewportSize.isNull())
{
visibleWidth = m_thumbnailsViewportSize.width() - 3.0 * thumbnailSpacing;
visibleHeight = m_thumbnailsViewportSize.height() - 3.0 * thumbnailSpacing;
}
foreach(ThumbnailItem* page, m_thumbnailItems)
{
const QSizeF displayedSize = page->displayedSize(renderParam);
if(thumbnailSize != 0.0)
{
adjustScaleFactor(renderParam, qMin(thumbnailSize / displayedSize.width(), thumbnailSize / displayedSize.height()));
}
else
{
if(m_thumbnailsOrientation == Qt::Vertical)
{
adjustScaleFactor(renderParam, visibleWidth / displayedSize.width());
}
else
{
adjustScaleFactor(renderParam, (visibleHeight - page->textHeight()) / displayedSize.height());
}
}
page->setRenderParam(renderParam);
}
// prepare layout
qreal left = 0.0;
qreal right = m_thumbnailsOrientation == Qt::Vertical ? 0.0 : thumbnailSpacing;
qreal top = 0.0;
qreal bottom = m_thumbnailsOrientation == Qt::Vertical ? thumbnailSpacing : 0.0;
const bool limitThumbnailsToResults = s_settings->documentView().limitThumbnailsToResults();
for(int index = 0; index < m_thumbnailItems.count(); ++index)
{
ThumbnailItem* page = m_thumbnailItems.at(index);
// prepare visibility
if(limitThumbnailsToResults && s_searchModel->hasResults(this) && !s_searchModel->hasResultsOnPage(this, index + 1))
{
page->setVisible(false);
page->cancelRender();
continue;
}
page->setVisible(true);
// prepare position
const QRectF boundingRect = page->boundingRect();
if(m_thumbnailsOrientation == Qt::Vertical)
{
page->setPos(-boundingRect.left() - 0.5 * boundingRect.width(), bottom - boundingRect.top());
left = qMin(left, -0.5f * boundingRect.width() - thumbnailSpacing);
right = qMax(right, 0.5f * boundingRect.width() + thumbnailSpacing);
bottom += boundingRect.height() + thumbnailSpacing;
}
else
{
page->setPos(right - boundingRect.left(), -boundingRect.top() - 0.5 * boundingRect.height());
top = qMin(top, -0.5f * boundingRect.height() - thumbnailSpacing);
bottom = qMax(bottom, 0.5f * boundingRect.height() + thumbnailSpacing);
right += boundingRect.width() + thumbnailSpacing;
}
}
m_thumbnailsScene->setSceneRect(left, top, right - left, bottom - top);
}
void DocumentView::prepareHighlight(int index, const QRectF& rect)
{
PageItem* page = m_pageItems.at(index);
m_highlight->setPos(page->pos());
m_highlight->setTransform(page->transform());
m_highlight->setRect(rect.normalized());
m_highlight->setBrush(QBrush(s_settings->pageItem().highlightColor()));
page->stackBefore(m_highlight);
m_highlight->setVisible(true);
{
VerticalScrollBarChangedBlocker verticalScrollBarChangedBlocker(this);
centerOn(m_highlight);
}
viewport()->update();
}
void DocumentView::checkResult()
{
if(m_currentResult.isValid() && m_layout->currentPage(pageOfResult(m_currentResult)) != m_currentPage)
{
m_currentResult = QModelIndex();
}
}
void DocumentView::applyResult()
{
if(m_currentResult.isValid())
{
const int page = pageOfResult(m_currentResult);
const QRectF rect = rectOfResult(m_currentResult);
jumpToPage(page);
prepareHighlight(page - 1, rect);
}
else
{
m_highlight->setVisible(false);
}
}
} // qpdfview