File indexing completed on 2024-04-28 15:51:41
0001 /* 0002 SPDX-FileCopyrightText: 2006 Pino Toscano <pino@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "bookmarklist.h" 0008 0009 // qt/kde includes 0010 #include <QAction> 0011 #include <QCheckBox> 0012 #include <QCursor> 0013 #include <QDebug> 0014 #include <QHeaderView> 0015 #include <QIcon> 0016 #include <QLayout> 0017 #include <QMenu> 0018 #include <QToolButton> 0019 #include <QTreeWidget> 0020 0021 #include <KLocalizedString> 0022 #include <KTitleWidget> 0023 #include <KTreeWidgetSearchLine> 0024 0025 #include <kwidgetsaddons_version.h> 0026 0027 #include "core/action.h" 0028 #include "core/bookmarkmanager.h" 0029 #include "core/document.h" 0030 #include "gui/tocmodel.h" 0031 #include "pageitemdelegate.h" 0032 0033 static const int BookmarkItemType = QTreeWidgetItem::UserType + 1; 0034 static const int FileItemType = QTreeWidgetItem::UserType + 2; 0035 static const int UrlRole = Qt::UserRole + 1; 0036 0037 class BookmarkItem : public QTreeWidgetItem 0038 { 0039 public: 0040 explicit BookmarkItem(const KBookmark &bm) 0041 : QTreeWidgetItem(BookmarkItemType) 0042 , m_bookmark(bm) 0043 { 0044 setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable); 0045 m_url = m_bookmark.url(); 0046 m_viewport = Okular::DocumentViewport(m_url.fragment(QUrl::FullyDecoded)); 0047 m_url.setFragment(QString()); 0048 setText(0, m_bookmark.fullText()); 0049 if (m_viewport.isValid()) { 0050 setData(0, TOCModel::PageRole, QString::number(m_viewport.pageNumber + 1)); 0051 } 0052 } 0053 0054 BookmarkItem(const BookmarkItem &) = delete; 0055 BookmarkItem &operator=(const BookmarkItem &) = delete; 0056 0057 QVariant data(int column, int role) const override 0058 { 0059 switch (role) { 0060 case Qt::ToolTipRole: 0061 return m_bookmark.fullText(); 0062 } 0063 return QTreeWidgetItem::data(column, role); 0064 } 0065 0066 bool operator<(const QTreeWidgetItem &other) const override 0067 { 0068 if (other.type() == BookmarkItemType) { 0069 const BookmarkItem *cmp = static_cast<const BookmarkItem *>(&other); 0070 return m_viewport < cmp->m_viewport; 0071 } 0072 return QTreeWidgetItem::operator<(other); 0073 } 0074 0075 KBookmark &bookmark() 0076 { 0077 return m_bookmark; 0078 } 0079 0080 const Okular::DocumentViewport &viewport() const 0081 { 0082 return m_viewport; 0083 } 0084 0085 QUrl url() const 0086 { 0087 return m_url; 0088 } 0089 0090 private: 0091 KBookmark m_bookmark; 0092 QUrl m_url; 0093 Okular::DocumentViewport m_viewport; 0094 }; 0095 0096 class FileItem : public QTreeWidgetItem 0097 { 0098 public: 0099 FileItem(const QUrl &url, QTreeWidget *tree, Okular::Document *document) 0100 : QTreeWidgetItem(tree, FileItemType) 0101 { 0102 setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable); 0103 const QString fileString = document->bookmarkManager()->titleForUrl(url); 0104 setText(0, fileString); 0105 setData(0, UrlRole, QVariant::fromValue(url)); 0106 } 0107 0108 FileItem(const FileItem &) = delete; 0109 FileItem &operator=(const FileItem &) = delete; 0110 0111 QVariant data(int column, int role) const override 0112 { 0113 switch (role) { 0114 case Qt::ToolTipRole: 0115 return i18ncp("%1 is the file name", "%1\n\nOne bookmark", "%1\n\n%2 bookmarks", text(0), childCount()); 0116 } 0117 return QTreeWidgetItem::data(column, role); 0118 } 0119 }; 0120 0121 BookmarkList::BookmarkList(Okular::Document *document, QWidget *parent) 0122 : QWidget(parent) 0123 , m_document(document) 0124 , m_currentDocumentItem(nullptr) 0125 { 0126 QVBoxLayout *mainlay = new QVBoxLayout(this); 0127 mainlay->setSpacing(6); 0128 0129 KTitleWidget *titleWidget = new KTitleWidget(this); 0130 titleWidget->setLevel(4); 0131 titleWidget->setText(i18n("Bookmarks")); 0132 mainlay->addWidget(titleWidget); 0133 mainlay->setAlignment(titleWidget, Qt::AlignHCenter); 0134 0135 m_showForAllDocumentsCheckbox = new QCheckBox(i18n("Show for all documents"), this); 0136 m_showForAllDocumentsCheckbox->setChecked(true); // this setting isn't saved 0137 connect(m_showForAllDocumentsCheckbox, &QCheckBox::toggled, this, &BookmarkList::slotShowAllBookmarks); 0138 mainlay->addWidget(m_showForAllDocumentsCheckbox); 0139 0140 m_searchLine = new KTreeWidgetSearchLine(this); 0141 mainlay->addWidget(m_searchLine); 0142 m_searchLine->setPlaceholderText(i18n("Search...")); 0143 0144 m_tree = new QTreeWidget(this); 0145 mainlay->addWidget(m_tree); 0146 QStringList cols; 0147 cols.append(QStringLiteral("Bookmarks")); 0148 m_tree->setContextMenuPolicy(Qt::CustomContextMenu); 0149 m_tree->setHeaderLabels(cols); 0150 m_tree->setSortingEnabled(false); 0151 m_tree->setRootIsDecorated(true); 0152 m_tree->setAlternatingRowColors(true); 0153 m_tree->setItemDelegate(new PageItemDelegate(m_tree)); 0154 m_tree->header()->hide(); 0155 m_tree->setSelectionBehavior(QAbstractItemView::SelectRows); 0156 m_tree->setEditTriggers(QAbstractItemView::EditKeyPressed); 0157 connect(m_tree, &QTreeWidget::itemActivated, this, &BookmarkList::slotExecuted); 0158 connect(m_tree, &QTreeWidget::customContextMenuRequested, this, &BookmarkList::slotContextMenu); 0159 m_searchLine->addTreeWidget(m_tree); 0160 0161 connect(m_document->bookmarkManager(), &Okular::BookmarkManager::bookmarksChanged, this, &BookmarkList::slotBookmarksChanged); 0162 0163 rebuildTree(m_showForAllDocumentsCheckbox->isChecked()); 0164 0165 m_showAllToolButton = new QToolButton(this); 0166 m_showAllToolButton->setAutoRaise(true); 0167 m_showAllToolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0168 mainlay->addWidget(m_showAllToolButton); 0169 } 0170 0171 BookmarkList::~BookmarkList() 0172 { 0173 m_document->removeObserver(this); 0174 } 0175 0176 void BookmarkList::notifySetup(const QVector<Okular::Page *> &pages, int setupFlags) 0177 { 0178 Q_UNUSED(pages); 0179 if (!(setupFlags & Okular::DocumentObserver::UrlChanged)) { 0180 return; 0181 } 0182 0183 // clear contents 0184 m_searchLine->clear(); 0185 0186 if (!m_showForAllDocumentsCheckbox->isChecked()) { 0187 rebuildTree(m_showForAllDocumentsCheckbox->isChecked()); 0188 } else { 0189 disconnect(m_tree, &QTreeWidget::itemChanged, this, &BookmarkList::slotChanged); 0190 if (m_currentDocumentItem && m_currentDocumentItem != m_tree->invisibleRootItem()) { 0191 m_currentDocumentItem->setIcon(0, QIcon()); 0192 } 0193 m_currentDocumentItem = itemForUrl(m_document->currentDocument()); 0194 if (m_currentDocumentItem && m_currentDocumentItem != m_tree->invisibleRootItem()) { 0195 m_currentDocumentItem->setIcon(0, QIcon::fromTheme(QStringLiteral("bookmarks"))); 0196 m_currentDocumentItem->setExpanded(true); 0197 } 0198 connect(m_tree, &QTreeWidget::itemChanged, this, &BookmarkList::slotChanged); 0199 } 0200 } 0201 0202 void BookmarkList::setAddBookmarkAction(QAction *addBookmarkAction) 0203 { 0204 m_showAllToolButton->setDefaultAction(addBookmarkAction); 0205 } 0206 0207 void BookmarkList::slotShowAllBookmarks(bool showAll) 0208 { 0209 rebuildTree(showAll); 0210 } 0211 0212 void BookmarkList::slotExecuted(QTreeWidgetItem *item) 0213 { 0214 BookmarkItem *bmItem = dynamic_cast<BookmarkItem *>(item); 0215 if (!bmItem || !bmItem->viewport().isValid()) { 0216 return; 0217 } 0218 0219 goTo(bmItem); 0220 } 0221 0222 void BookmarkList::slotChanged(QTreeWidgetItem *item) 0223 { 0224 BookmarkItem *bmItem = dynamic_cast<BookmarkItem *>(item); 0225 if (bmItem && bmItem->viewport().isValid()) { 0226 bmItem->bookmark().setFullText(bmItem->text(0)); 0227 m_document->bookmarkManager()->save(); 0228 } 0229 0230 FileItem *fItem = dynamic_cast<FileItem *>(item); 0231 if (fItem) { 0232 const QUrl url = fItem->data(0, UrlRole).value<QUrl>(); 0233 m_document->bookmarkManager()->renameBookmark(url, fItem->text(0)); 0234 m_document->bookmarkManager()->save(); 0235 } 0236 } 0237 0238 void BookmarkList::slotContextMenu(const QPoint p) 0239 { 0240 QTreeWidgetItem *item = m_tree->itemAt(p); 0241 BookmarkItem *bmItem = item ? dynamic_cast<BookmarkItem *>(item) : nullptr; 0242 if (bmItem) { 0243 contextMenuForBookmarkItem(p, bmItem); 0244 } else if (FileItem *fItem = dynamic_cast<FileItem *>(item)) { 0245 contextMenuForFileItem(p, fItem); 0246 } 0247 } 0248 0249 void BookmarkList::contextMenuForBookmarkItem(const QPoint p, BookmarkItem *bmItem) 0250 { 0251 Q_UNUSED(p); 0252 if (!bmItem || !bmItem->viewport().isValid()) { 0253 return; 0254 } 0255 0256 QMenu menu(this); 0257 const QAction *gotobm = menu.addAction(i18n("Go to This Bookmark")); 0258 const QAction *editbm = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Bookmark")); 0259 const QAction *removebm = menu.addAction(QIcon::fromTheme(QStringLiteral("bookmark-remove"), QIcon::fromTheme(QStringLiteral("edit-delete-bookmark"))), i18n("Remove Bookmark")); 0260 const QAction *res = menu.exec(QCursor::pos()); 0261 if (!res) { 0262 return; 0263 } 0264 0265 if (res == gotobm) { 0266 goTo(bmItem); 0267 } else if (res == editbm) { 0268 m_tree->editItem(bmItem, 0); 0269 } else if (res == removebm) { 0270 m_document->bookmarkManager()->removeBookmark(bmItem->url(), bmItem->bookmark()); 0271 } 0272 } 0273 0274 void BookmarkList::contextMenuForFileItem(const QPoint p, FileItem *fItem) 0275 { 0276 Q_UNUSED(p); 0277 if (!fItem) { 0278 return; 0279 } 0280 0281 const QUrl itemurl = fItem->data(0, UrlRole).value<QUrl>(); 0282 const bool thisdoc = itemurl == m_document->currentDocument(); 0283 0284 QMenu menu(this); 0285 QAction *open = nullptr; 0286 if (!thisdoc) { 0287 open = menu.addAction(i18nc("Opens the selected document", "Open Document")); 0288 } 0289 const QAction *editbm = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Bookmark")); 0290 const QAction *removebm = menu.addAction(QIcon::fromTheme(QStringLiteral("bookmark-remove"), QIcon::fromTheme(QStringLiteral("edit-delete-bookmark"))), i18n("Remove all Bookmarks for this Document")); 0291 const QAction *res = menu.exec(QCursor::pos()); 0292 if (!res) { 0293 return; 0294 } 0295 0296 if (res == open) { 0297 Okular::GotoAction action(itemurl.toDisplayString(QUrl::PreferLocalFile), Okular::DocumentViewport()); 0298 m_document->processAction(&action); 0299 } else if (res == editbm) { 0300 m_tree->editItem(fItem, 0); 0301 } else if (res == removebm) { 0302 KBookmark::List list; 0303 for (int i = 0; i < fItem->childCount(); ++i) { 0304 list.append(static_cast<BookmarkItem *>(fItem->child(i))->bookmark()); 0305 } 0306 m_document->bookmarkManager()->removeBookmarks(itemurl, list); 0307 } 0308 } 0309 0310 void BookmarkList::slotBookmarksChanged(const QUrl &url) 0311 { 0312 // special case here, as m_currentDocumentItem could represent 0313 // the invisible root item 0314 if (url == m_document->currentDocument()) { 0315 selectiveUrlUpdate(m_document->currentDocument(), m_currentDocumentItem); 0316 return; 0317 } 0318 0319 // we are showing the bookmarks for the current document only 0320 if (!m_showForAllDocumentsCheckbox->isChecked()) { 0321 return; 0322 } 0323 0324 QTreeWidgetItem *item = itemForUrl(url); 0325 selectiveUrlUpdate(url, item); 0326 } 0327 0328 QList<QTreeWidgetItem *> createItems(const QUrl &baseurl, const KBookmark::List &bmlist) 0329 { 0330 Q_UNUSED(baseurl) 0331 QList<QTreeWidgetItem *> ret; 0332 for (const KBookmark &bm : bmlist) { 0333 // qCDebug(OkularUiDebug).nospace() << "checking '" << tmp << "'"; 0334 // qCDebug(OkularUiDebug).nospace() << " vs '" << baseurl << "'"; 0335 // TODO check that bm and baseurl are the same (#ref excluded) 0336 QTreeWidgetItem *item = new BookmarkItem(bm); 0337 ret.append(item); 0338 } 0339 return ret; 0340 } 0341 0342 void BookmarkList::rebuildTree(bool showAll) 0343 { 0344 // disconnect and reconnect later, otherwise we'll get many itemChanged() 0345 // signals for all the current items 0346 disconnect(m_tree, &QTreeWidget::itemChanged, this, &BookmarkList::slotChanged); 0347 0348 m_currentDocumentItem = nullptr; 0349 m_tree->clear(); 0350 0351 const QList<QUrl> urls = m_document->bookmarkManager()->files(); 0352 if (!showAll) { 0353 if (m_document->isOpened()) { 0354 for (const QUrl &url : urls) { 0355 if (url == m_document->currentDocument()) { 0356 m_tree->addTopLevelItems(createItems(url, m_document->bookmarkManager()->bookmarks(url))); 0357 m_currentDocumentItem = m_tree->invisibleRootItem(); 0358 break; 0359 } 0360 } 0361 } 0362 } else { 0363 QTreeWidgetItem *currenturlitem = nullptr; 0364 for (const QUrl &url : urls) { 0365 QList<QTreeWidgetItem *> subitems = createItems(url, m_document->bookmarkManager()->bookmarks(url)); 0366 if (!subitems.isEmpty()) { 0367 FileItem *item = new FileItem(url, m_tree, m_document); 0368 item->addChildren(subitems); 0369 if (!currenturlitem && url == m_document->currentDocument()) { 0370 currenturlitem = item; 0371 } 0372 } 0373 } 0374 if (currenturlitem) { 0375 currenturlitem->setExpanded(true); 0376 currenturlitem->setIcon(0, QIcon::fromTheme(QStringLiteral("bookmarks"))); 0377 m_tree->scrollToItem(currenturlitem, QAbstractItemView::PositionAtTop); 0378 m_currentDocumentItem = currenturlitem; 0379 } 0380 } 0381 0382 m_tree->sortItems(0, Qt::AscendingOrder); 0383 0384 connect(m_tree, &QTreeWidget::itemChanged, this, &BookmarkList::slotChanged); 0385 } 0386 0387 void BookmarkList::goTo(BookmarkItem *item) 0388 { 0389 if (item->url() == m_document->currentDocument()) { 0390 m_document->setViewport(item->viewport(), nullptr, true); 0391 } else { 0392 Okular::GotoAction action(item->url().toDisplayString(QUrl::PreferLocalFile), item->viewport()); 0393 m_document->processAction(&action); 0394 } 0395 } 0396 0397 void BookmarkList::selectiveUrlUpdate(const QUrl &url, QTreeWidgetItem *&item) 0398 { 0399 disconnect(m_tree, &QTreeWidget::itemChanged, this, &BookmarkList::slotChanged); 0400 0401 const KBookmark::List urlbookmarks = m_document->bookmarkManager()->bookmarks(url); 0402 if (urlbookmarks.isEmpty()) { 0403 if (item != m_tree->invisibleRootItem()) { 0404 m_tree->invisibleRootItem()->removeChild(item); 0405 item = nullptr; 0406 } else if (item) { 0407 for (int i = item->childCount(); i >= 0; --i) { 0408 item->removeChild(item->child(i)); 0409 } 0410 } 0411 } else { 0412 bool fileitem_created = false; 0413 0414 if (item) { 0415 for (int i = item->childCount() - 1; i >= 0; --i) { 0416 item->removeChild(item->child(i)); 0417 } 0418 } else { 0419 item = new FileItem(url, m_tree, m_document); 0420 fileitem_created = true; 0421 } 0422 if (m_document->isOpened() && url == m_document->currentDocument()) { 0423 item->setIcon(0, QIcon::fromTheme(QStringLiteral("bookmarks"))); 0424 item->setExpanded(true); 0425 } 0426 item->addChildren(createItems(url, urlbookmarks)); 0427 0428 if (fileitem_created) { 0429 // we need to sort also the parent of the new file item, 0430 // so it can be properly shown in the correct place 0431 m_tree->invisibleRootItem()->sortChildren(0, Qt::AscendingOrder); 0432 } 0433 item->sortChildren(0, Qt::AscendingOrder); 0434 } 0435 0436 connect(m_tree, &QTreeWidget::itemChanged, this, &BookmarkList::slotChanged); 0437 } 0438 0439 QTreeWidgetItem *BookmarkList::itemForUrl(const QUrl &url) const 0440 { 0441 const int count = m_tree->topLevelItemCount(); 0442 for (int i = 0; i < count; ++i) { 0443 QTreeWidgetItem *item = m_tree->topLevelItem(i); 0444 const QUrl itemurl = item->data(0, UrlRole).value<QUrl>(); 0445 if (itemurl.isValid() && itemurl == url) { 0446 return item; 0447 } 0448 } 0449 return nullptr; 0450 } 0451 0452 #include "moc_bookmarklist.cpp"