File indexing completed on 2024-04-28 04:32:37

0001 /*
0002     SPDX-FileCopyrightText: 2006 Pino Toscano <pino@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "bookmarkmanager.h"
0008 
0009 // qt/kde includes
0010 #include <KBookmarkAction>
0011 #include <KBookmarkManager>
0012 #include <KBookmarkMenu>
0013 #include <KBookmarkOwner>
0014 #include <QDebug>
0015 #include <QFileInfo>
0016 #include <QGuiApplication>
0017 #include <QHash>
0018 #include <QSet>
0019 #include <QStandardPaths>
0020 #include <QUrl>
0021 
0022 // local includes
0023 #include "document_p.h"
0024 #include "observer.h"
0025 
0026 using namespace Okular;
0027 
0028 #define foreachObserver(cmd)                                                                                                                                                                                                                   \
0029     {                                                                                                                                                                                                                                          \
0030         QSet<DocumentObserver *>::const_iterator it = d->document->m_observers.constBegin(), end = d->document->m_observers.constEnd();                                                                                                        \
0031         for (; it != end; ++it) {                                                                                                                                                                                                              \
0032             (*it)->cmd;                                                                                                                                                                                                                        \
0033         }                                                                                                                                                                                                                                      \
0034     }
0035 
0036 #define foreachObserverD(cmd)                                                                                                                                                                                                                  \
0037     {                                                                                                                                                                                                                                          \
0038         QSet<DocumentObserver *>::const_iterator it = document->m_observers.constBegin(), end = document->m_observers.constEnd();                                                                                                              \
0039         for (; it != end; ++it) {                                                                                                                                                                                                              \
0040             (*it)->cmd;                                                                                                                                                                                                                        \
0041         }                                                                                                                                                                                                                                      \
0042     }
0043 
0044 class OkularBookmarkAction : public KBookmarkAction
0045 {
0046     Q_OBJECT
0047 public:
0048     OkularBookmarkAction(const Okular::DocumentViewport &vp, const KBookmark &bk, KBookmarkOwner *owner, QObject *parent)
0049         : KBookmarkAction(bk, owner, parent)
0050     {
0051         if (vp.isValid()) {
0052             setText(QString::number(vp.pageNumber + 1) + QStringLiteral(" - ") + text());
0053         }
0054         setProperty("pageNumber", vp.pageNumber + 1);
0055         setProperty("htmlRef", bk.url().fragment(QUrl::FullyDecoded));
0056     }
0057 
0058     inline int pageNumber() const
0059     {
0060         return property("pageNumber").toInt();
0061     }
0062 
0063     inline QString htmlRef() const
0064     {
0065         return property("htmlRef").toString();
0066     }
0067 };
0068 
0069 static inline bool documentViewportFuzzyCompare(const DocumentViewport &vp1, const DocumentViewport &vp2)
0070 {
0071     bool equal = vp1.isValid() && vp2.isValid() && (vp1.pageNumber == vp2.pageNumber) && (vp1.rePos.pos == vp2.rePos.pos);
0072 
0073     if (!equal) {
0074         return false;
0075     }
0076 
0077     if (qAbs(vp1.rePos.normalizedX - vp2.rePos.normalizedX) >= 0.000001) {
0078         return false;
0079     }
0080 
0081     if (qAbs(vp1.rePos.normalizedY - vp2.rePos.normalizedY) >= 0.000001) {
0082         return false;
0083     }
0084 
0085     return true;
0086 }
0087 
0088 static inline bool bookmarkLessThan(const KBookmark &b1, const KBookmark &b2)
0089 {
0090     DocumentViewport vp1(b1.url().fragment(QUrl::FullyDecoded));
0091     DocumentViewport vp2(b2.url().fragment(QUrl::FullyDecoded));
0092 
0093     return vp1 < vp2;
0094 }
0095 
0096 static inline bool okularBookmarkActionLessThan(QAction *a1, QAction *a2)
0097 {
0098     DocumentViewport vp1(static_cast<OkularBookmarkAction *>(a1)->htmlRef());
0099     DocumentViewport vp2(static_cast<OkularBookmarkAction *>(a2)->htmlRef());
0100 
0101     return vp1 < vp2;
0102 }
0103 
0104 static QUrl mostCanonicalUrl(const QUrl &url)
0105 {
0106     if (!url.isLocalFile()) {
0107         return url;
0108     }
0109 
0110     const QFileInfo fi(url.toLocalFile());
0111     return QUrl::fromLocalFile(fi.canonicalFilePath());
0112 }
0113 
0114 class BookmarkManager::Private : public KBookmarkOwner
0115 {
0116 public:
0117     explicit Private(BookmarkManager *qq)
0118         : KBookmarkOwner()
0119         , q(qq)
0120         , document(nullptr)
0121         , file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/okular/bookmarks.xml"))
0122         , manager(KBookmarkManager(file))
0123     {
0124     }
0125 
0126     ~Private() override
0127     {
0128         knownFiles.clear();
0129         // no need to delete the manager, it's automatically done by KBookmarkManager
0130         // delete manager;
0131     }
0132 
0133     Private(const Private &) = delete;
0134     Private &operator=(const Private &) = delete;
0135 
0136     QUrl currentUrl() const override;
0137     QString currentTitle() const override;
0138     bool enableOption(BookmarkOption option) const override;
0139     void openBookmark(const KBookmark &bm, Qt::MouseButtons, Qt::KeyboardModifiers) override;
0140 
0141     QHash<QUrl, QString>::iterator bookmarkFind(const QUrl &url, bool doCreate, KBookmarkGroup *result = nullptr);
0142 
0143     // slots
0144     void _o_changed(const QString &groupAddress);
0145 
0146     BookmarkManager *q;
0147     QUrl url;
0148     QHash<int, int> urlBookmarks;
0149     DocumentPrivate *document;
0150     QString file;
0151     KBookmarkManager manager;
0152     QHash<QUrl, QString> knownFiles;
0153 };
0154 
0155 static inline QUrl urlForGroup(const KBookmark &group)
0156 {
0157     if (group.url().isValid()) {
0158         return group.url();
0159     } else {
0160         return QUrl::fromUserInput(group.fullText());
0161     }
0162 }
0163 
0164 BookmarkManager::BookmarkManager(DocumentPrivate *document)
0165     : QObject(document->m_parent)
0166     , d(new Private(this))
0167 {
0168     setObjectName(QStringLiteral("Okular::BookmarkManager"));
0169 
0170     d->document = document;
0171 
0172     connect(&d->manager, &KBookmarkManager::changed, this, [this](const QString &groupAddress) { d->_o_changed(groupAddress); });
0173 }
0174 
0175 BookmarkManager::~BookmarkManager()
0176 {
0177     delete d;
0178 }
0179 
0180 // BEGIN Reimplementations from KBookmarkOwner
0181 QUrl BookmarkManager::Private::currentUrl() const
0182 {
0183     return url;
0184 }
0185 
0186 QString BookmarkManager::Private::currentTitle() const
0187 {
0188     return url.toDisplayString();
0189 }
0190 
0191 bool BookmarkManager::Private::enableOption(BookmarkOption option) const
0192 {
0193     Q_UNUSED(option)
0194     return false;
0195 }
0196 
0197 void BookmarkManager::Private::openBookmark(const KBookmark &bm, Qt::MouseButtons, Qt::KeyboardModifiers)
0198 {
0199     Q_EMIT q->openUrl(bm.url());
0200 }
0201 // END Reimplementations from KBookmarkOwner
0202 
0203 void BookmarkManager::Private::_o_changed(const QString &groupAddress)
0204 {
0205     if (groupAddress.isEmpty()) {
0206         return;
0207     }
0208 
0209     QUrl referurl;
0210     // first, try to find the bookmark group whom change notification was just received
0211     QHash<QUrl, QString>::iterator it = knownFiles.begin(), itEnd = knownFiles.end();
0212     for (; it != itEnd; ++it) {
0213         if (it.value() == groupAddress) {
0214             referurl = it.key();
0215             knownFiles.erase(it);
0216             break;
0217         }
0218     }
0219     if (!referurl.isValid()) {
0220         const KBookmark bm = manager.findByAddress(groupAddress);
0221         // better be safe than sorry
0222         if (bm.isNull()) {
0223             return;
0224         }
0225         Q_ASSERT(bm.isGroup());
0226         referurl = urlForGroup(bm);
0227     }
0228     Q_ASSERT(referurl.isValid());
0229     Q_EMIT q->bookmarksChanged(referurl);
0230     // case for the url representing the current document
0231     // (this might happen if the same document is open in another place;
0232     // in such case, make really sure to be in sync)
0233     if (referurl == url) {
0234         // save the old bookmarks for the current url
0235         const QHash<int, int> oldUrlBookmarks = urlBookmarks;
0236         // set the same url again, so we reload the information we have about it
0237         q->setUrl(referurl);
0238         // then notify the observers about the changes in the bookmarks
0239         for (int i = 0; i < qMax(oldUrlBookmarks.size(), urlBookmarks.size()); i++) {
0240             bool oldContains = oldUrlBookmarks.contains(i) && oldUrlBookmarks[i] > 0;
0241             bool curContains = urlBookmarks.contains(i) && urlBookmarks[i] > 0;
0242 
0243             if (oldContains != curContains) {
0244                 foreachObserverD(notifyPageChanged(i, DocumentObserver::Bookmark));
0245             } else if (oldContains && oldUrlBookmarks[i] != urlBookmarks[i]) {
0246                 foreachObserverD(notifyPageChanged(i, DocumentObserver::Bookmark));
0247             }
0248         }
0249     }
0250     Q_EMIT q->saved();
0251 }
0252 
0253 QList<QUrl> BookmarkManager::files() const
0254 {
0255     QList<QUrl> ret;
0256     KBookmarkGroup group = d->manager.root();
0257     for (KBookmark bm = group.first(); !bm.isNull(); bm = group.next(bm)) {
0258         if (bm.isSeparator() || !bm.isGroup()) {
0259             continue;
0260         }
0261 
0262         ret.append(urlForGroup(bm));
0263     }
0264     return ret;
0265 }
0266 
0267 KBookmark::List BookmarkManager::bookmarks(const QUrl &documentUrl) const
0268 {
0269     const QUrl url = mostCanonicalUrl(documentUrl);
0270     KBookmark::List ret;
0271     KBookmarkGroup group = d->manager.root();
0272     for (KBookmark bm = group.first(); !bm.isNull(); bm = group.next(bm)) {
0273         if (!bm.isGroup() || urlForGroup(bm) != url) {
0274             continue;
0275         }
0276 
0277         KBookmarkGroup group = bm.toGroup();
0278         for (KBookmark b = group.first(); !b.isNull(); b = group.next(b)) {
0279             if (b.isSeparator() || b.isGroup()) {
0280                 continue;
0281             }
0282 
0283             ret.append(b);
0284         }
0285         break;
0286     }
0287 
0288     return ret;
0289 }
0290 
0291 KBookmark::List BookmarkManager::bookmarks() const
0292 {
0293     return bookmarks(d->url);
0294 }
0295 
0296 KBookmark::List BookmarkManager::bookmarks(int page) const
0297 {
0298     const KBookmark::List bmarks = bookmarks();
0299     KBookmark::List ret;
0300     for (const KBookmark &bm : bmarks) {
0301         DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0302         if (vp.isValid() && vp.pageNumber == page) {
0303             ret.append(bm);
0304         }
0305     }
0306 
0307     return ret;
0308 }
0309 
0310 KBookmark BookmarkManager::bookmark(int page) const
0311 {
0312     const KBookmark::List bmarks = bookmarks();
0313     for (const KBookmark &bm : bmarks) {
0314         DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0315         if (vp.isValid() && vp.pageNumber == page) {
0316             return bm;
0317         }
0318     }
0319     return KBookmark();
0320 }
0321 
0322 KBookmark BookmarkManager::bookmark(const DocumentViewport &viewport) const
0323 {
0324     if (!viewport.isValid() || !isBookmarked(viewport.pageNumber)) {
0325         return KBookmark();
0326     }
0327 
0328     KBookmarkGroup thebg;
0329     QHash<QUrl, QString>::iterator it = d->bookmarkFind(d->url, false, &thebg);
0330     if (it == d->knownFiles.end()) {
0331         return KBookmark();
0332     }
0333 
0334     for (KBookmark bm = thebg.first(); !bm.isNull(); bm = thebg.next(bm)) {
0335         if (bm.isSeparator() || bm.isGroup()) {
0336             continue;
0337         }
0338 
0339         DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0340         if (documentViewportFuzzyCompare(vp, viewport)) {
0341             return bm;
0342         }
0343     }
0344 
0345     return KBookmark();
0346 }
0347 
0348 void BookmarkManager::save() const
0349 {
0350     d->manager.emitChanged();
0351     Q_EMIT const_cast<BookmarkManager *>(this)->saved();
0352 }
0353 
0354 QHash<QUrl, QString>::iterator BookmarkManager::Private::bookmarkFind(const QUrl &url, bool doCreate, KBookmarkGroup *result)
0355 {
0356     QHash<QUrl, QString>::iterator it = knownFiles.find(url);
0357     if (it == knownFiles.end()) {
0358         // if the url we want to add a new entry for is not in the hash of the
0359         // known files, then first try to find the file among the top-level
0360         // "folder" names
0361         bool found = false;
0362         KBookmarkGroup root = manager.root();
0363         for (KBookmark bm = root.first(); !found && !bm.isNull(); bm = root.next(bm)) {
0364             if (bm.isSeparator() || !bm.isGroup()) {
0365                 continue;
0366             }
0367 
0368             QUrl tmpurl(urlForGroup(bm));
0369             if (tmpurl == url) {
0370                 // got it! place it the hash of known files
0371                 KBookmarkGroup bg = bm.toGroup();
0372                 it = knownFiles.insert(url, bg.address());
0373                 found = true;
0374                 if (result) {
0375                     *result = bg;
0376                 }
0377                 break;
0378             }
0379         }
0380         if (!found && doCreate) {
0381             // folder not found :(
0382             // then, in a single step create a new folder and add it in our cache :)
0383             QString purl = url.isLocalFile() ? url.toLocalFile() : url.toDisplayString();
0384             KBookmarkGroup newbg = root.createNewFolder(purl);
0385             newbg.setUrl(url);
0386             it = knownFiles.insert(url, newbg.address());
0387             if (result) {
0388                 *result = newbg;
0389             }
0390         }
0391     } else if (result) {
0392         const KBookmark bm = manager.findByAddress(it.value());
0393         Q_ASSERT(bm.isGroup());
0394         *result = bm.toGroup();
0395     }
0396     return it;
0397 }
0398 
0399 void BookmarkManager::addBookmark(int page)
0400 {
0401     if (page >= 0 && page < (int)d->document->m_pagesVector.count()) {
0402         if (setPageBookmark(page))
0403             foreachObserver(notifyPageChanged(page, DocumentObserver::Bookmark));
0404     }
0405 }
0406 
0407 void BookmarkManager::addBookmark(const DocumentViewport &vp)
0408 {
0409     addBookmark(d->url, vp);
0410 }
0411 
0412 bool BookmarkManager::addBookmark(const QUrl &documentUrl, const Okular::DocumentViewport &vp, const QString &title)
0413 {
0414     if (!documentUrl.isValid() || !vp.isValid()) {
0415         return false;
0416     }
0417 
0418     if (vp.pageNumber < 0 || vp.pageNumber >= d->document->m_pagesVector.count()) {
0419         return false;
0420     }
0421 
0422     const QUrl referurl = mostCanonicalUrl(documentUrl);
0423 
0424     KBookmarkGroup thebg;
0425     QHash<QUrl, QString>::iterator it = d->bookmarkFind(referurl, true, &thebg);
0426     Q_ASSERT(it != d->knownFiles.end());
0427 
0428     int count = 0; // Number of bookmarks in the current page
0429     bool found = false;
0430     // Check if the bookmark already exists
0431     for (KBookmark bm = thebg.first(); !found && !bm.isNull(); bm = thebg.next(bm)) {
0432         if (bm.isSeparator() || bm.isGroup()) {
0433             continue;
0434         }
0435 
0436         DocumentViewport bmViewport(bm.url().fragment(QUrl::FullyDecoded));
0437         if (bmViewport.isValid() && bmViewport.pageNumber == vp.pageNumber) {
0438             ++count;
0439 
0440             if (documentViewportFuzzyCompare(bmViewport, vp)) {
0441                 found = true;
0442             }
0443         }
0444     }
0445 
0446     if (found) {
0447         return false;
0448     }
0449 
0450     QString newtitle;
0451     if (title.isEmpty()) {
0452         // if we have no title specified for the new bookmark, then give it the
0453         // name '#p' where p is the page number where the bookmark is located.
0454         // if there's more than one bookmark per page, give the name '#p-n'
0455         // where n is the index of this bookmark among the ones of its page.
0456         if (count > 0) {
0457             newtitle = QStringLiteral("#%1-%2").arg(vp.pageNumber + 1).arg(count);
0458         } else {
0459             newtitle = QStringLiteral("#%1").arg(vp.pageNumber + 1);
0460         }
0461     } else {
0462         newtitle = title;
0463     }
0464 
0465     QUrl newurl = referurl;
0466     newurl.setFragment(vp.toString(), QUrl::DecodedMode);
0467     thebg.addBookmark(newtitle, newurl, QString());
0468     if (referurl == d->document->m_url) {
0469         d->urlBookmarks[vp.pageNumber]++;
0470         foreachObserver(notifyPageChanged(vp.pageNumber, DocumentObserver::Bookmark));
0471     }
0472     d->manager.emitChanged(thebg);
0473     return true;
0474 }
0475 
0476 void BookmarkManager::removeBookmark(int page)
0477 {
0478     if (page >= 0 && page < (int)d->document->m_pagesVector.count()) {
0479         if (removePageBookmark(page))
0480             foreachObserver(notifyPageChanged(page, DocumentObserver::Bookmark));
0481     }
0482 }
0483 
0484 void BookmarkManager::removeBookmark(const DocumentViewport &vp)
0485 {
0486     int page = vp.pageNumber;
0487     if (page >= 0 && page < d->document->m_pagesVector.count()) {
0488         removeBookmark(d->url, bookmark(vp));
0489     }
0490 }
0491 
0492 void BookmarkManager::renameBookmark(KBookmark *bm, const QString &newName)
0493 {
0494     KBookmarkGroup thebg;
0495     QHash<QUrl, QString>::iterator it = d->bookmarkFind(d->url, false, &thebg);
0496     Q_ASSERT(it != d->knownFiles.end());
0497     if (it == d->knownFiles.end()) {
0498         return;
0499     }
0500 
0501     bm->setFullText(newName);
0502     d->manager.emitChanged(thebg);
0503 }
0504 
0505 void BookmarkManager::renameBookmark(const QUrl &documentUrl, const QString &newName)
0506 {
0507     if (!documentUrl.isValid()) {
0508         return;
0509     }
0510 
0511     const QUrl referurl = mostCanonicalUrl(documentUrl);
0512 
0513     KBookmarkGroup thebg;
0514     QHash<QUrl, QString>::iterator it = d->bookmarkFind(referurl, false, &thebg);
0515     Q_ASSERT(it != d->knownFiles.end());
0516     if (it == d->knownFiles.end()) {
0517         return;
0518     }
0519 
0520     thebg.setFullText(newName);
0521     d->manager.emitChanged(thebg);
0522 }
0523 
0524 QString BookmarkManager::titleForUrl(const QUrl &documentUrl) const
0525 {
0526     KBookmarkGroup thebg;
0527     QHash<QUrl, QString>::iterator it = d->bookmarkFind(mostCanonicalUrl(documentUrl), false, &thebg);
0528     Q_ASSERT(it != d->knownFiles.end());
0529 
0530     return thebg.fullText();
0531 }
0532 
0533 int BookmarkManager::removeBookmark(const QUrl &documentUrl, const KBookmark &bm)
0534 {
0535     if (!documentUrl.isValid() || bm.isNull() || bm.isGroup() || bm.isSeparator()) {
0536         return -1;
0537     }
0538 
0539     DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0540     if (!vp.isValid()) {
0541         return -1;
0542     }
0543 
0544     const QUrl referurl = mostCanonicalUrl(documentUrl);
0545 
0546     KBookmarkGroup thebg;
0547     QHash<QUrl, QString>::iterator it = d->bookmarkFind(referurl, false, &thebg);
0548     if (it == d->knownFiles.end()) {
0549         return -1;
0550     }
0551 
0552     thebg.deleteBookmark(bm);
0553 
0554     if (referurl == d->document->m_url) {
0555         d->urlBookmarks[vp.pageNumber]--;
0556         foreachObserver(notifyPageChanged(vp.pageNumber, DocumentObserver::Bookmark));
0557     }
0558     d->manager.emitChanged(thebg);
0559 
0560     return vp.pageNumber;
0561 }
0562 
0563 void BookmarkManager::removeBookmarks(const QUrl &documentUrl, const KBookmark::List &list)
0564 {
0565     if (!documentUrl.isValid() || list.isEmpty()) {
0566         return;
0567     }
0568 
0569     const QUrl referurl = mostCanonicalUrl(documentUrl);
0570 
0571     KBookmarkGroup thebg;
0572     QHash<QUrl, QString>::iterator it = d->bookmarkFind(referurl, false, &thebg);
0573     if (it == d->knownFiles.end()) {
0574         return;
0575     }
0576 
0577     const QHash<int, int> oldUrlBookmarks = d->urlBookmarks;
0578     bool deletedAny = false;
0579     for (const KBookmark &bm : list) {
0580         if (bm.parentGroup() == thebg) {
0581             thebg.deleteBookmark(bm);
0582             deletedAny = true;
0583 
0584             DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0585             if (referurl == d->document->m_url) {
0586                 d->urlBookmarks[vp.pageNumber]--;
0587             }
0588         }
0589     }
0590 
0591     if (referurl == d->document->m_url) {
0592         for (int i = 0; i < qMax(oldUrlBookmarks.size(), d->urlBookmarks.size()); i++) {
0593             bool oldContains = oldUrlBookmarks.contains(i) && oldUrlBookmarks[i] > 0;
0594             bool curContains = d->urlBookmarks.contains(i) && d->urlBookmarks[i] > 0;
0595 
0596             if (oldContains != curContains) {
0597                 foreachObserver(notifyPageChanged(i, DocumentObserver::Bookmark));
0598             } else if (oldContains && oldUrlBookmarks[i] != d->urlBookmarks[i]) {
0599                 foreachObserver(notifyPageChanged(i, DocumentObserver::Bookmark));
0600             }
0601         }
0602     }
0603     if (deletedAny) {
0604         d->manager.emitChanged(thebg);
0605     }
0606 }
0607 
0608 QList<QAction *> BookmarkManager::actionsForUrl(const QUrl &documentUrl) const
0609 {
0610     const QUrl url = mostCanonicalUrl(documentUrl);
0611     QList<QAction *> ret;
0612     KBookmarkGroup group = d->manager.root();
0613     for (KBookmark bm = group.first(); !bm.isNull(); bm = group.next(bm)) {
0614         if (!bm.isGroup() || urlForGroup(bm) != url) {
0615             continue;
0616         }
0617 
0618         KBookmarkGroup group = bm.toGroup();
0619         for (KBookmark b = group.first(); !b.isNull(); b = group.next(b)) {
0620             if (b.isSeparator() || b.isGroup()) {
0621                 continue;
0622             }
0623 
0624             ret.append(new OkularBookmarkAction(DocumentViewport(b.url().fragment(QUrl::FullyDecoded)), b, d, nullptr));
0625         }
0626         break;
0627     }
0628     std::sort(ret.begin(), ret.end(), okularBookmarkActionLessThan);
0629     return ret;
0630 }
0631 
0632 void BookmarkManager::setUrl(const QUrl &url)
0633 {
0634     d->url = mostCanonicalUrl(url);
0635     d->urlBookmarks.clear();
0636     KBookmarkGroup thebg;
0637     QHash<QUrl, QString>::iterator it = d->bookmarkFind(d->url, false, &thebg);
0638     if (it != d->knownFiles.end()) {
0639         for (KBookmark bm = thebg.first(); !bm.isNull(); bm = thebg.next(bm)) {
0640             if (bm.isSeparator() || bm.isGroup()) {
0641                 continue;
0642             }
0643 
0644             DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0645             if (!vp.isValid()) {
0646                 continue;
0647             }
0648 
0649             d->urlBookmarks[vp.pageNumber]++;
0650         }
0651     }
0652 }
0653 
0654 bool BookmarkManager::setPageBookmark(int page)
0655 {
0656     KBookmarkGroup thebg;
0657     QHash<QUrl, QString>::iterator it = d->bookmarkFind(d->url, true, &thebg);
0658     Q_ASSERT(it != d->knownFiles.end());
0659 
0660     bool found = false;
0661     bool added = false;
0662     for (KBookmark bm = thebg.first(); !found && !bm.isNull(); bm = thebg.next(bm)) {
0663         if (bm.isSeparator() || bm.isGroup()) {
0664             continue;
0665         }
0666 
0667         DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0668         if (vp.isValid() && vp.pageNumber == page) {
0669             found = true;
0670         }
0671     }
0672     if (!found) {
0673         d->urlBookmarks[page]++;
0674         DocumentViewport vp;
0675         vp.pageNumber = page;
0676         QUrl newurl = d->url;
0677         newurl.setFragment(vp.toString(), QUrl::DecodedMode);
0678         thebg.addBookmark(QLatin1String("#") + QString::number(vp.pageNumber + 1), newurl, QString());
0679         added = true;
0680         d->manager.emitChanged(thebg);
0681     }
0682     return added;
0683 }
0684 
0685 bool BookmarkManager::removePageBookmark(int page)
0686 {
0687     KBookmarkGroup thebg;
0688     QHash<QUrl, QString>::iterator it = d->bookmarkFind(d->url, false, &thebg);
0689     if (it == d->knownFiles.end()) {
0690         return false;
0691     }
0692 
0693     bool found = false;
0694     for (KBookmark bm = thebg.first(); !found && !bm.isNull(); bm = thebg.next(bm)) {
0695         if (bm.isSeparator() || bm.isGroup()) {
0696             continue;
0697         }
0698 
0699         DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0700         if (vp.isValid() && vp.pageNumber == page) {
0701             found = true;
0702             thebg.deleteBookmark(bm);
0703             d->urlBookmarks[page]--;
0704             d->manager.emitChanged(thebg);
0705         }
0706     }
0707     return found;
0708 }
0709 
0710 bool BookmarkManager::isBookmarked(int page) const
0711 {
0712     return d->urlBookmarks.contains(page) && d->urlBookmarks[page] > 0;
0713 }
0714 
0715 bool BookmarkManager::isBookmarked(const DocumentViewport &viewport) const
0716 {
0717     KBookmark bm = bookmark(viewport);
0718 
0719     return !bm.isNull();
0720 }
0721 
0722 KBookmark BookmarkManager::nextBookmark(const DocumentViewport &viewport) const
0723 {
0724     KBookmark::List bmarks = bookmarks();
0725     std::sort(bmarks.begin(), bmarks.end(), bookmarkLessThan);
0726 
0727     KBookmark bookmark;
0728     for (const KBookmark &bm : std::as_const(bmarks)) {
0729         DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0730         if (viewport < vp) {
0731             bookmark = bm;
0732             break;
0733         }
0734     }
0735 
0736     return bookmark;
0737 }
0738 
0739 KBookmark BookmarkManager::previousBookmark(const DocumentViewport &viewport) const
0740 {
0741     KBookmark::List bmarks = bookmarks();
0742     std::sort(bmarks.begin(), bmarks.end(), bookmarkLessThan);
0743 
0744     KBookmark bookmark;
0745     for (KBookmark::List::const_iterator it = bmarks.constEnd(); it != bmarks.constBegin(); --it) {
0746         KBookmark bm = *(it - 1);
0747         DocumentViewport vp(bm.url().fragment(QUrl::FullyDecoded));
0748         if (vp < viewport) {
0749             bookmark = bm;
0750             break;
0751         }
0752     }
0753 
0754     return bookmark;
0755 }
0756 
0757 #undef foreachObserver
0758 #undef foreachObserverD
0759 
0760 #include "bookmarkmanager.moc"
0761 
0762 /* kate: replace-tabs on; indent-width 4; */