1 /*
2     SPDX-FileCopyrightText: 2004-2006 Albert Astals Cid <aacid@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "toc.h"
8 
9 // qt/kde includes
10 #include <QContextMenuEvent>
11 #include <QHeaderView>
12 #include <QLayout>
13 #include <QTreeView>
14 #include <qdom.h>
15 
16 #include <KLineEdit>
17 #include <KLocalizedString>
18 #include <KTitleWidget>
19 
20 #include <kwidgetsaddons_version.h>
21 
22 // local includes
23 #include "core/action.h"
24 #include "ktreeviewsearchline.h"
25 #include "pageitemdelegate.h"
26 #include "settings.h"
27 #include "tocmodel.h"
28 
TOC(QWidget * parent,Okular::Document * document)29 TOC::TOC(QWidget *parent, Okular::Document *document)
30     : QWidget(parent)
31     , m_document(document)
32 {
33     QVBoxLayout *mainlay = new QVBoxLayout(this);
34     mainlay->setSpacing(6);
35 
36     KTitleWidget *titleWidget = new KTitleWidget(this);
37     titleWidget->setLevel(2);
38     titleWidget->setText(i18n("Contents"));
39     mainlay->addWidget(titleWidget);
40     mainlay->setAlignment(titleWidget, Qt::AlignHCenter);
41     m_searchLine = new KTreeViewSearchLine(this);
42     mainlay->addWidget(m_searchLine);
43     m_searchLine->setPlaceholderText(i18n("Search..."));
44     m_searchLine->setCaseSensitivity(Okular::Settings::self()->contentsSearchCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
45     m_searchLine->setRegularExpression(Okular::Settings::self()->contentsSearchRegularExpression());
46     connect(m_searchLine, &KTreeViewSearchLine::searchOptionsChanged, this, &TOC::saveSearchOptions);
47 
48     m_treeView = new QTreeView(this);
49     mainlay->addWidget(m_treeView);
50     m_model = new TOCModel(document, m_treeView);
51     m_treeView->setModel(m_model);
52     m_treeView->setSortingEnabled(false);
53     m_treeView->setRootIsDecorated(true);
54     m_treeView->setAlternatingRowColors(true);
55     m_treeView->setItemDelegate(new PageItemDelegate(m_treeView));
56     m_treeView->header()->hide();
57     m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
58     connect(m_treeView, &QTreeView::clicked, this, &TOC::slotExecuted);
59     connect(m_treeView, &QTreeView::activated, this, &TOC::slotExecuted);
60     m_searchLine->setTreeView(m_treeView);
61 }
62 
~TOC()63 TOC::~TOC()
64 {
65     m_document->removeObserver(this);
66 }
67 
notifySetup(const QVector<Okular::Page * > &,int setupFlags)68 void TOC::notifySetup(const QVector<Okular::Page *> & /*pages*/, int setupFlags)
69 {
70     if (!(setupFlags & Okular::DocumentObserver::DocumentChanged))
71         return;
72 
73     // clear contents
74     m_model->clear();
75 
76     // request synopsis description (is a dom tree)
77     const Okular::DocumentSynopsis *syn = m_document->documentSynopsis();
78     if (!syn) {
79         if (m_document->isOpened()) {
80             // Make sure we clear the reload old model data
81             m_model->setOldModelData(nullptr, QVector<QModelIndex>());
82         }
83         emit hasTOC(false);
84         return;
85     }
86 
87     m_model->fill(syn);
88     emit hasTOC(!m_model->isEmpty());
89 }
90 
notifyCurrentPageChanged(int,int)91 void TOC::notifyCurrentPageChanged(int, int)
92 {
93     m_model->setCurrentViewport(m_document->viewport());
94 }
95 
prepareForReload()96 void TOC::prepareForReload()
97 {
98     if (m_model->isEmpty())
99         return;
100 
101     const QVector<QModelIndex> list = expandedNodes();
102     TOCModel *m = m_model;
103     m_model = new TOCModel(m_document, m_treeView);
104     m_model->setOldModelData(m, list);
105     m->setParent(nullptr);
106 }
107 
rollbackReload()108 void TOC::rollbackReload()
109 {
110     if (!m_model->hasOldModelData())
111         return;
112 
113     TOCModel *m = m_model;
114     m_model = m->clearOldModelData();
115     m_model->setParent(m_treeView);
116     delete m;
117 }
118 
finishReload()119 void TOC::finishReload()
120 {
121     m_treeView->setModel(m_model);
122     m_model->setParent(m_treeView);
123 }
124 
expandedNodes(const QModelIndex & parent) const125 QVector<QModelIndex> TOC::expandedNodes(const QModelIndex &parent) const
126 {
127     QVector<QModelIndex> list;
128     for (int i = 0; i < m_model->rowCount(parent); i++) {
129         const QModelIndex index = m_model->index(i, 0, parent);
130         if (m_treeView->isExpanded(index)) {
131             list << index;
132         }
133         if (m_model->hasChildren(index)) {
134             list << expandedNodes(index);
135         }
136     }
137     return list;
138 }
139 
reparseConfig()140 void TOC::reparseConfig()
141 {
142     m_searchLine->setCaseSensitivity(Okular::Settings::contentsSearchCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
143     m_searchLine->setRegularExpression(Okular::Settings::contentsSearchRegularExpression());
144     m_treeView->update();
145 }
146 
slotExecuted(const QModelIndex & index)147 void TOC::slotExecuted(const QModelIndex &index)
148 {
149     if (!index.isValid())
150         return;
151 
152     QString url = m_model->urlForIndex(index);
153     if (!url.isEmpty()) {
154         Okular::BrowseAction action(QUrl::fromLocalFile(url));
155         m_document->processAction(&action);
156         return;
157     }
158 
159     QString externalFileName = m_model->externalFileNameForIndex(index);
160     Okular::DocumentViewport viewport = m_model->viewportForIndex(index);
161     if (!externalFileName.isEmpty()) {
162         Okular::GotoAction action(externalFileName, viewport);
163         m_document->processAction(&action);
164     } else if (viewport.isValid()) {
165         m_document->setViewport(viewport);
166     }
167 }
168 
saveSearchOptions()169 void TOC::saveSearchOptions()
170 {
171     Okular::Settings::setContentsSearchRegularExpression(m_searchLine->regularExpression());
172     Okular::Settings::setContentsSearchCaseSensitive(m_searchLine->caseSensitivity() == Qt::CaseSensitive ? true : false);
173     Okular::Settings::self()->save();
174 }
175 
contextMenuEvent(QContextMenuEvent * e)176 void TOC::contextMenuEvent(QContextMenuEvent *e)
177 {
178     QModelIndex index = m_treeView->currentIndex();
179     if (!index.isValid())
180         return;
181 
182     Okular::DocumentViewport viewport = m_model->viewportForIndex(index);
183 
184     emit rightClick(viewport, e->globalPos(), m_model->data(index).toString());
185 }
186 
expandRecursively()187 void TOC::expandRecursively()
188 {
189     QList<QModelIndex> worklist = {m_treeView->currentIndex()};
190     if (!worklist[0].isValid()) {
191         return;
192     }
193     while (!worklist.isEmpty()) {
194         QModelIndex index = worklist.takeLast();
195         m_treeView->expand(index);
196         for (int i = 0; i < m_model->rowCount(index); i++) {
197             worklist += m_model->index(i, 0, index);
198         }
199     }
200 }
201 
collapseRecursively()202 void TOC::collapseRecursively()
203 {
204     QList<QModelIndex> worklist = {m_treeView->currentIndex()};
205     if (!worklist[0].isValid()) {
206         return;
207     }
208     while (!worklist.isEmpty()) {
209         QModelIndex index = worklist.takeLast();
210         m_treeView->collapse(index);
211         for (int i = 0; i < m_model->rowCount(index); i++) {
212             worklist += m_model->index(i, 0, index);
213         }
214     }
215 }
216 
expandAll()217 void TOC::expandAll()
218 {
219     m_treeView->expandAll();
220 }
221 
collapseAll()222 void TOC::collapseAll()
223 {
224     m_treeView->collapseAll();
225 }
226