File indexing completed on 2024-04-28 15:51:58

0001 /*
0002     SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "side_reviews.h"
0008 
0009 // qt/kde includes
0010 #include <QHeaderView>
0011 #include <QLayout>
0012 #include <QPaintEvent>
0013 #include <QPainter>
0014 #include <QSizePolicy>
0015 #include <QStringList>
0016 #include <QTextDocument>
0017 #include <QToolBar>
0018 #include <QTreeView>
0019 
0020 #include <KLocalizedString>
0021 #include <KTitleWidget>
0022 #include <QAction>
0023 #include <QIcon>
0024 
0025 #include <kwidgetsaddons_version.h>
0026 
0027 // local includes
0028 #include "annotationmodel.h"
0029 #include "annotationpopup.h"
0030 #include "annotationproxymodels.h"
0031 #include "core/annotations.h"
0032 #include "core/document.h"
0033 #include "core/page.h"
0034 #include "ktreeviewsearchline.h"
0035 #include "settings.h"
0036 
0037 class TreeView : public QTreeView
0038 {
0039     Q_OBJECT
0040 
0041 public:
0042     explicit TreeView(Okular::Document *document, QWidget *parent = Q_NULLPTR)
0043         : QTreeView(parent)
0044         , m_document(document)
0045     {
0046     }
0047 
0048 protected:
0049     void paintEvent(QPaintEvent *event) override
0050     {
0051         bool hasAnnotations = false;
0052         for (uint i = 0; i < m_document->pages(); ++i) {
0053             if (m_document->page(i)->hasAnnotations()) {
0054                 hasAnnotations = true;
0055                 break;
0056             }
0057         }
0058         if (!hasAnnotations) {
0059             QPainter p(viewport());
0060             p.setRenderHint(QPainter::Antialiasing, true);
0061             p.setClipRect(event->rect());
0062 
0063             QTextDocument document;
0064             document.setHtml(
0065                 i18n("<div align=center><h3>No annotations</h3>"
0066                      "To create new annotations press F6 or select <i>Tools -&gt; Annotations</i>"
0067                      " from the menu.</div>"));
0068             document.setTextWidth(width() - 50);
0069 
0070             const uint w = document.size().width() + 20;
0071             const uint h = document.size().height() + 20;
0072 
0073             p.setBrush(palette().window());
0074             p.translate(0.5, 0.5);
0075             p.drawRoundedRect(15, 15, w, h, 3, 3);
0076             p.translate(20, 20);
0077             document.drawContents(&p);
0078 
0079         } else {
0080             QTreeView::paintEvent(event);
0081         }
0082     }
0083 
0084 private:
0085     Okular::Document *m_document;
0086 };
0087 
0088 Reviews::Reviews(QWidget *parent, Okular::Document *document)
0089     : QWidget(parent)
0090     , m_document(document)
0091 {
0092     // create widgets and layout them vertically
0093     QVBoxLayout *vLayout = new QVBoxLayout(this);
0094     vLayout->setSpacing(6);
0095 
0096     KTitleWidget *titleWidget = new KTitleWidget(this);
0097     titleWidget->setLevel(4);
0098     titleWidget->setText(i18n("Annotations"));
0099 
0100     m_view = new TreeView(m_document, this);
0101     m_view->setAlternatingRowColors(true);
0102     m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
0103     m_view->header()->hide();
0104 
0105     QToolBar *toolBar = new QToolBar(this);
0106     toolBar->setObjectName(QStringLiteral("reviewOptsBar"));
0107     QSizePolicy sp = toolBar->sizePolicy();
0108     sp.setVerticalPolicy(QSizePolicy::Minimum);
0109     toolBar->setSizePolicy(sp);
0110 
0111     m_model = new AnnotationModel(m_document, m_view);
0112 
0113     m_filterProxy = new PageFilterProxyModel(m_view);
0114     m_groupProxy = new PageGroupProxyModel(m_view);
0115     m_authorProxy = new AuthorGroupProxyModel(m_view);
0116 
0117     m_filterProxy->setSourceModel(m_model);
0118     m_groupProxy->setSourceModel(m_filterProxy);
0119     m_authorProxy->setSourceModel(m_groupProxy);
0120 
0121     m_view->setModel(m_authorProxy);
0122 
0123     m_searchLine = new KTreeViewSearchLine(this, m_view);
0124     m_searchLine->setPlaceholderText(i18n("Search..."));
0125     m_searchLine->setCaseSensitivity(Okular::Settings::self()->reviewsSearchCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
0126     m_searchLine->setRegularExpression(Okular::Settings::self()->reviewsSearchRegularExpression());
0127     connect(m_searchLine, &KTreeViewSearchLine::searchOptionsChanged, this, &Reviews::saveSearchOptions);
0128     vLayout->addWidget(titleWidget);
0129     vLayout->setAlignment(titleWidget, Qt::AlignHCenter);
0130     vLayout->addWidget(m_searchLine);
0131     vLayout->addWidget(m_view);
0132     vLayout->addWidget(toolBar);
0133 
0134     toolBar->setIconSize(QSize(16, 16));
0135     toolBar->setMovable(false);
0136     // - add Page button
0137     QAction *groupByPageAction = toolBar->addAction(QIcon::fromTheme(QStringLiteral("text-x-generic")), i18n("Group by Page"));
0138     groupByPageAction->setCheckable(true);
0139     connect(groupByPageAction, &QAction::toggled, this, &Reviews::slotPageEnabled);
0140     groupByPageAction->setChecked(Okular::Settings::groupByPage());
0141     // - add Author button
0142     QAction *groupByAuthorAction = toolBar->addAction(QIcon::fromTheme(QStringLiteral("user-identity")), i18n("Group by Author"));
0143     groupByAuthorAction->setCheckable(true);
0144     connect(groupByAuthorAction, &QAction::toggled, this, &Reviews::slotAuthorEnabled);
0145     groupByAuthorAction->setChecked(Okular::Settings::groupByAuthor());
0146 
0147     // - add separator
0148     toolBar->addSeparator();
0149     // - add Current Page Only button
0150     QAction *curPageOnlyAction = toolBar->addAction(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Show annotations for current page only"));
0151     curPageOnlyAction->setCheckable(true);
0152     connect(curPageOnlyAction, &QAction::toggled, this, &Reviews::slotCurrentPageOnly);
0153     curPageOnlyAction->setChecked(Okular::Settings::currentPageOnly());
0154 
0155     // Adds space between left actions, so that the next two buttons are aligned to the right
0156     QWidget *spacer = new QWidget();
0157     spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0158     toolBar->addWidget(spacer);
0159 
0160     QAction *expandAll = toolBar->addAction(QIcon::fromTheme(QStringLiteral("expand-all")), i18n("Expand all elements"));
0161     connect(expandAll, &QAction::triggered, this, &Reviews::slotExpandAll);
0162     QAction *collapseAll = toolBar->addAction(QIcon::fromTheme(QStringLiteral("collapse-all")), i18n("Collapse all elements"));
0163     connect(collapseAll, &QAction::triggered, this, &Reviews::slotCollapseAll);
0164 
0165     connect(m_view, &TreeView::activated, this, &Reviews::activated);
0166 
0167     m_view->setContextMenuPolicy(Qt::CustomContextMenu);
0168     connect(m_view, &TreeView::customContextMenuRequested, this, &Reviews::contextMenuRequested);
0169 }
0170 
0171 Reviews::~Reviews()
0172 {
0173     m_document->removeObserver(this);
0174 }
0175 
0176 // BEGIN DocumentObserver Notifies
0177 void Reviews::notifyCurrentPageChanged(int previousPage, int currentPage)
0178 {
0179     Q_UNUSED(previousPage)
0180 
0181     m_filterProxy->setCurrentPage(currentPage);
0182 }
0183 // END DocumentObserver Notifies
0184 
0185 void Reviews::reparseConfig()
0186 {
0187     m_searchLine->setCaseSensitivity(Okular::Settings::reviewsSearchCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
0188     m_searchLine->setRegularExpression(Okular::Settings::reviewsSearchRegularExpression());
0189     m_view->update();
0190 }
0191 
0192 // BEGIN GUI Slots -> requestListViewUpdate
0193 void Reviews::slotPageEnabled(bool on)
0194 {
0195     // store toggle state in Settings and update the listview
0196     Okular::Settings::setGroupByPage(on);
0197     m_groupProxy->groupByPage(on);
0198 
0199     m_view->expandAll();
0200 }
0201 
0202 void Reviews::slotAuthorEnabled(bool on)
0203 {
0204     // store toggle state in Settings and update the listview
0205     Okular::Settings::setGroupByAuthor(on);
0206     m_authorProxy->groupByAuthor(on);
0207 
0208     m_view->expandAll();
0209 }
0210 
0211 void Reviews::slotCurrentPageOnly(bool on)
0212 {
0213     // store toggle state in Settings and update the listview
0214     Okular::Settings::setCurrentPageOnly(on);
0215     m_filterProxy->groupByCurrentPage(on);
0216 
0217     m_view->expandAll();
0218 }
0219 
0220 void Reviews::slotExpandAll()
0221 {
0222     m_view->expandAll();
0223 }
0224 
0225 void Reviews::slotCollapseAll()
0226 {
0227     m_view->collapseAll();
0228 }
0229 // END GUI Slots
0230 
0231 void Reviews::activated(const QModelIndex &index)
0232 {
0233     const QModelIndex authorIndex = m_authorProxy->mapToSource(index);
0234     const QModelIndex filterIndex = m_groupProxy->mapToSource(authorIndex);
0235     const QModelIndex annotIndex = m_filterProxy->mapToSource(filterIndex);
0236 
0237     Okular::Annotation *annotation = m_model->annotationForIndex(annotIndex);
0238     if (!annotation) {
0239         return;
0240     }
0241 
0242     int pageNumber = m_model->data(annotIndex, AnnotationModel::PageRole).toInt();
0243     const Okular::Page *page = m_document->page(pageNumber);
0244 
0245     // calculating the right coordinates to center the view on the annotation
0246     QRect rect = Okular::AnnotationUtils::annotationGeometry(annotation, page->width(), page->height());
0247     Okular::NormalizedRect nr(rect, (int)page->width(), (int)page->height());
0248     // set the viewport parameters
0249     Okular::DocumentViewport vp;
0250     vp.pageNumber = pageNumber;
0251     vp.rePos.enabled = true;
0252     vp.rePos.pos = Okular::DocumentViewport::Center;
0253     vp.rePos.normalizedX = (nr.right + nr.left) / 2.0;
0254     vp.rePos.normalizedY = (nr.bottom + nr.top) / 2.0;
0255     // setting the viewport
0256     m_document->setViewport(vp, nullptr, true);
0257 }
0258 
0259 QModelIndexList Reviews::retrieveAnnotations(const QModelIndex &idx) const
0260 {
0261     QModelIndexList ret;
0262     if (idx.isValid()) {
0263         const QAbstractItemModel *model = idx.model();
0264         if (model->hasChildren(idx)) {
0265             int rowCount = model->rowCount(idx);
0266             for (int i = 0; i < rowCount; i++) {
0267                 ret += retrieveAnnotations(model->index(i, idx.column(), idx));
0268             }
0269         } else {
0270             ret += idx;
0271         }
0272     }
0273 
0274     return ret;
0275 }
0276 
0277 void Reviews::contextMenuRequested(const QPoint pos)
0278 {
0279     AnnotationPopup popup(m_document, AnnotationPopup::SingleAnnotationMode, this);
0280     connect(&popup, &AnnotationPopup::openAnnotationWindow, this, &Reviews::openAnnotationWindow);
0281 
0282     const QModelIndexList indexes = m_view->selectionModel()->selectedIndexes();
0283     for (const QModelIndex &index : indexes) {
0284         const QModelIndexList annotations = retrieveAnnotations(index);
0285         for (const QModelIndex &idx : annotations) {
0286             const QModelIndex authorIndex = m_authorProxy->mapToSource(idx);
0287             const QModelIndex filterIndex = m_groupProxy->mapToSource(authorIndex);
0288             const QModelIndex annotIndex = m_filterProxy->mapToSource(filterIndex);
0289             Okular::Annotation *annotation = m_model->annotationForIndex(annotIndex);
0290             if (annotation) {
0291                 const int pageNumber = m_model->data(annotIndex, AnnotationModel::PageRole).toInt();
0292                 popup.addAnnotation(annotation, pageNumber);
0293             }
0294         }
0295     }
0296 
0297     popup.exec(m_view->viewport()->mapToGlobal(pos));
0298 }
0299 
0300 void Reviews::saveSearchOptions()
0301 {
0302     Okular::Settings::setReviewsSearchRegularExpression(m_searchLine->regularExpression());
0303     Okular::Settings::setReviewsSearchCaseSensitive(m_searchLine->caseSensitivity() == Qt::CaseSensitive ? true : false);
0304     Okular::Settings::self()->save();
0305 }
0306 
0307 #include "side_reviews.moc"