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 -> 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"