mirror of
https://invent.kde.org/graphics/okular
synced 2024-08-27 11:40:07 +00:00
![shenleban tongying](/assets/img/avatar_default.png)
This slightly increases the amount of vertical space available for content. BUG: 462321 FIXED-IN: 23.04
308 lines
10 KiB
C++
308 lines
10 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "side_reviews.h"
|
|
|
|
// qt/kde includes
|
|
#include <QHeaderView>
|
|
#include <QLayout>
|
|
#include <QPaintEvent>
|
|
#include <QPainter>
|
|
#include <QSizePolicy>
|
|
#include <QStringList>
|
|
#include <QTextDocument>
|
|
#include <QToolBar>
|
|
#include <QTreeView>
|
|
|
|
#include <KLocalizedString>
|
|
#include <KTitleWidget>
|
|
#include <QAction>
|
|
#include <QIcon>
|
|
|
|
#include <kwidgetsaddons_version.h>
|
|
|
|
// local includes
|
|
#include "annotationmodel.h"
|
|
#include "annotationpopup.h"
|
|
#include "annotationproxymodels.h"
|
|
#include "core/annotations.h"
|
|
#include "core/document.h"
|
|
#include "core/page.h"
|
|
#include "ktreeviewsearchline.h"
|
|
#include "settings.h"
|
|
|
|
class TreeView : public QTreeView
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit TreeView(Okular::Document *document, QWidget *parent = Q_NULLPTR)
|
|
: QTreeView(parent)
|
|
, m_document(document)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
void paintEvent(QPaintEvent *event) override
|
|
{
|
|
bool hasAnnotations = false;
|
|
for (uint i = 0; i < m_document->pages(); ++i) {
|
|
if (m_document->page(i)->hasAnnotations()) {
|
|
hasAnnotations = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasAnnotations) {
|
|
QPainter p(viewport());
|
|
p.setRenderHint(QPainter::Antialiasing, true);
|
|
p.setClipRect(event->rect());
|
|
|
|
QTextDocument document;
|
|
document.setHtml(
|
|
i18n("<div align=center><h3>No annotations</h3>"
|
|
"To create new annotations press F6 or select <i>Tools -> Annotations</i>"
|
|
" from the menu.</div>"));
|
|
document.setTextWidth(width() - 50);
|
|
|
|
const uint w = document.size().width() + 20;
|
|
const uint h = document.size().height() + 20;
|
|
|
|
p.setBrush(palette().window());
|
|
p.translate(0.5, 0.5);
|
|
p.drawRoundedRect(15, 15, w, h, 3, 3);
|
|
p.translate(20, 20);
|
|
document.drawContents(&p);
|
|
|
|
} else {
|
|
QTreeView::paintEvent(event);
|
|
}
|
|
}
|
|
|
|
private:
|
|
Okular::Document *m_document;
|
|
};
|
|
|
|
Reviews::Reviews(QWidget *parent, Okular::Document *document)
|
|
: QWidget(parent)
|
|
, m_document(document)
|
|
{
|
|
// create widgets and layout them vertically
|
|
QVBoxLayout *vLayout = new QVBoxLayout(this);
|
|
vLayout->setSpacing(6);
|
|
|
|
KTitleWidget *titleWidget = new KTitleWidget(this);
|
|
titleWidget->setLevel(4);
|
|
titleWidget->setText(i18n("Annotations"));
|
|
|
|
m_view = new TreeView(m_document, this);
|
|
m_view->setAlternatingRowColors(true);
|
|
m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
m_view->header()->hide();
|
|
|
|
QToolBar *toolBar = new QToolBar(this);
|
|
toolBar->setObjectName(QStringLiteral("reviewOptsBar"));
|
|
QSizePolicy sp = toolBar->sizePolicy();
|
|
sp.setVerticalPolicy(QSizePolicy::Minimum);
|
|
toolBar->setSizePolicy(sp);
|
|
|
|
m_model = new AnnotationModel(m_document, m_view);
|
|
|
|
m_filterProxy = new PageFilterProxyModel(m_view);
|
|
m_groupProxy = new PageGroupProxyModel(m_view);
|
|
m_authorProxy = new AuthorGroupProxyModel(m_view);
|
|
|
|
m_filterProxy->setSourceModel(m_model);
|
|
m_groupProxy->setSourceModel(m_filterProxy);
|
|
m_authorProxy->setSourceModel(m_groupProxy);
|
|
|
|
m_view->setModel(m_authorProxy);
|
|
|
|
m_searchLine = new KTreeViewSearchLine(this, m_view);
|
|
m_searchLine->setPlaceholderText(i18n("Search..."));
|
|
m_searchLine->setCaseSensitivity(Okular::Settings::self()->reviewsSearchCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
|
m_searchLine->setRegularExpression(Okular::Settings::self()->reviewsSearchRegularExpression());
|
|
connect(m_searchLine, &KTreeViewSearchLine::searchOptionsChanged, this, &Reviews::saveSearchOptions);
|
|
vLayout->addWidget(titleWidget);
|
|
vLayout->setAlignment(titleWidget, Qt::AlignHCenter);
|
|
vLayout->addWidget(m_searchLine);
|
|
vLayout->addWidget(m_view);
|
|
vLayout->addWidget(toolBar);
|
|
|
|
toolBar->setIconSize(QSize(16, 16));
|
|
toolBar->setMovable(false);
|
|
// - add Page button
|
|
QAction *groupByPageAction = toolBar->addAction(QIcon::fromTheme(QStringLiteral("text-x-generic")), i18n("Group by Page"));
|
|
groupByPageAction->setCheckable(true);
|
|
connect(groupByPageAction, &QAction::toggled, this, &Reviews::slotPageEnabled);
|
|
groupByPageAction->setChecked(Okular::Settings::groupByPage());
|
|
// - add Author button
|
|
QAction *groupByAuthorAction = toolBar->addAction(QIcon::fromTheme(QStringLiteral("user-identity")), i18n("Group by Author"));
|
|
groupByAuthorAction->setCheckable(true);
|
|
connect(groupByAuthorAction, &QAction::toggled, this, &Reviews::slotAuthorEnabled);
|
|
groupByAuthorAction->setChecked(Okular::Settings::groupByAuthor());
|
|
|
|
// - add separator
|
|
toolBar->addSeparator();
|
|
// - add Current Page Only button
|
|
QAction *curPageOnlyAction = toolBar->addAction(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Show annotations for current page only"));
|
|
curPageOnlyAction->setCheckable(true);
|
|
connect(curPageOnlyAction, &QAction::toggled, this, &Reviews::slotCurrentPageOnly);
|
|
curPageOnlyAction->setChecked(Okular::Settings::currentPageOnly());
|
|
|
|
// Adds space between left actions, so that the next two buttons are aligned to the right
|
|
QWidget *spacer = new QWidget();
|
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
toolBar->addWidget(spacer);
|
|
|
|
QAction *expandAll = toolBar->addAction(QIcon::fromTheme(QStringLiteral("expand-all")), i18n("Expand all elements"));
|
|
connect(expandAll, &QAction::triggered, this, &Reviews::slotExpandAll);
|
|
QAction *collapseAll = toolBar->addAction(QIcon::fromTheme(QStringLiteral("collapse-all")), i18n("Collapse all elements"));
|
|
connect(collapseAll, &QAction::triggered, this, &Reviews::slotCollapseAll);
|
|
|
|
connect(m_view, &TreeView::activated, this, &Reviews::activated);
|
|
|
|
m_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(m_view, &TreeView::customContextMenuRequested, this, &Reviews::contextMenuRequested);
|
|
}
|
|
|
|
Reviews::~Reviews()
|
|
{
|
|
m_document->removeObserver(this);
|
|
}
|
|
|
|
// BEGIN DocumentObserver Notifies
|
|
void Reviews::notifyCurrentPageChanged(int previousPage, int currentPage)
|
|
{
|
|
Q_UNUSED(previousPage)
|
|
|
|
m_filterProxy->setCurrentPage(currentPage);
|
|
}
|
|
// END DocumentObserver Notifies
|
|
|
|
void Reviews::reparseConfig()
|
|
{
|
|
m_searchLine->setCaseSensitivity(Okular::Settings::reviewsSearchCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
|
m_searchLine->setRegularExpression(Okular::Settings::reviewsSearchRegularExpression());
|
|
m_view->update();
|
|
}
|
|
|
|
// BEGIN GUI Slots -> requestListViewUpdate
|
|
void Reviews::slotPageEnabled(bool on)
|
|
{
|
|
// store toggle state in Settings and update the listview
|
|
Okular::Settings::setGroupByPage(on);
|
|
m_groupProxy->groupByPage(on);
|
|
|
|
m_view->expandAll();
|
|
}
|
|
|
|
void Reviews::slotAuthorEnabled(bool on)
|
|
{
|
|
// store toggle state in Settings and update the listview
|
|
Okular::Settings::setGroupByAuthor(on);
|
|
m_authorProxy->groupByAuthor(on);
|
|
|
|
m_view->expandAll();
|
|
}
|
|
|
|
void Reviews::slotCurrentPageOnly(bool on)
|
|
{
|
|
// store toggle state in Settings and update the listview
|
|
Okular::Settings::setCurrentPageOnly(on);
|
|
m_filterProxy->groupByCurrentPage(on);
|
|
|
|
m_view->expandAll();
|
|
}
|
|
|
|
void Reviews::slotExpandAll()
|
|
{
|
|
m_view->expandAll();
|
|
}
|
|
|
|
void Reviews::slotCollapseAll()
|
|
{
|
|
m_view->collapseAll();
|
|
}
|
|
// END GUI Slots
|
|
|
|
void Reviews::activated(const QModelIndex &index)
|
|
{
|
|
const QModelIndex authorIndex = m_authorProxy->mapToSource(index);
|
|
const QModelIndex filterIndex = m_groupProxy->mapToSource(authorIndex);
|
|
const QModelIndex annotIndex = m_filterProxy->mapToSource(filterIndex);
|
|
|
|
Okular::Annotation *annotation = m_model->annotationForIndex(annotIndex);
|
|
if (!annotation) {
|
|
return;
|
|
}
|
|
|
|
int pageNumber = m_model->data(annotIndex, AnnotationModel::PageRole).toInt();
|
|
const Okular::Page *page = m_document->page(pageNumber);
|
|
|
|
// calculating the right coordinates to center the view on the annotation
|
|
QRect rect = Okular::AnnotationUtils::annotationGeometry(annotation, page->width(), page->height());
|
|
Okular::NormalizedRect nr(rect, (int)page->width(), (int)page->height());
|
|
// set the viewport parameters
|
|
Okular::DocumentViewport vp;
|
|
vp.pageNumber = pageNumber;
|
|
vp.rePos.enabled = true;
|
|
vp.rePos.pos = Okular::DocumentViewport::Center;
|
|
vp.rePos.normalizedX = (nr.right + nr.left) / 2.0;
|
|
vp.rePos.normalizedY = (nr.bottom + nr.top) / 2.0;
|
|
// setting the viewport
|
|
m_document->setViewport(vp, nullptr, true);
|
|
}
|
|
|
|
QModelIndexList Reviews::retrieveAnnotations(const QModelIndex &idx) const
|
|
{
|
|
QModelIndexList ret;
|
|
if (idx.isValid()) {
|
|
const QAbstractItemModel *model = idx.model();
|
|
if (model->hasChildren(idx)) {
|
|
int rowCount = model->rowCount(idx);
|
|
for (int i = 0; i < rowCount; i++) {
|
|
ret += retrieveAnnotations(model->index(i, idx.column(), idx));
|
|
}
|
|
} else {
|
|
ret += idx;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void Reviews::contextMenuRequested(const QPoint pos)
|
|
{
|
|
AnnotationPopup popup(m_document, AnnotationPopup::SingleAnnotationMode, this);
|
|
connect(&popup, &AnnotationPopup::openAnnotationWindow, this, &Reviews::openAnnotationWindow);
|
|
|
|
const QModelIndexList indexes = m_view->selectionModel()->selectedIndexes();
|
|
for (const QModelIndex &index : indexes) {
|
|
const QModelIndexList annotations = retrieveAnnotations(index);
|
|
for (const QModelIndex &idx : annotations) {
|
|
const QModelIndex authorIndex = m_authorProxy->mapToSource(idx);
|
|
const QModelIndex filterIndex = m_groupProxy->mapToSource(authorIndex);
|
|
const QModelIndex annotIndex = m_filterProxy->mapToSource(filterIndex);
|
|
Okular::Annotation *annotation = m_model->annotationForIndex(annotIndex);
|
|
if (annotation) {
|
|
const int pageNumber = m_model->data(annotIndex, AnnotationModel::PageRole).toInt();
|
|
popup.addAnnotation(annotation, pageNumber);
|
|
}
|
|
}
|
|
}
|
|
|
|
popup.exec(m_view->viewport()->mapToGlobal(pos));
|
|
}
|
|
|
|
void Reviews::saveSearchOptions()
|
|
{
|
|
Okular::Settings::setReviewsSearchRegularExpression(m_searchLine->regularExpression());
|
|
Okular::Settings::setReviewsSearchCaseSensitive(m_searchLine->caseSensitivity() == Qt::CaseSensitive ? true : false);
|
|
Okular::Settings::self()->save();
|
|
}
|
|
|
|
#include "side_reviews.moc"
|