File indexing completed on 2025-01-19 03:58:03

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2017-05-15
0007  * Description : low level manager for bookmarks
0008  *
0009  * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "bookmarksmngr.h"
0016 
0017 // Qt includes
0018 
0019 #include <QBuffer>
0020 #include <QFile>
0021 #include <QMimeData>
0022 #include <QDragEnterEvent>
0023 #include <QIcon>
0024 #include <QHeaderView>
0025 #include <QMessageBox>
0026 #include <QToolButton>
0027 #include <QDebug>
0028 #include <QApplication>
0029 
0030 // KDE includes
0031 
0032 #include <klocalizedstring.h>
0033 
0034 // Local includes
0035 
0036 #include "dfiledialog.h"
0037 #include "bookmarknode.h"
0038 #include "digikam_debug.h"
0039 
0040 namespace Digikam
0041 {
0042 
0043 RemoveBookmarksCommand::RemoveBookmarksCommand(BookmarksManager* const mngr,
0044                                                BookmarkNode* const parent,
0045                                                int row)
0046     : QUndoCommand(i18n("Remove Bookmark")),
0047       m_row            (row),
0048       m_bookmarkManager(mngr),
0049       m_node           (parent->children().value(row)),
0050       m_parent         (parent),
0051       m_done           (false)
0052 {
0053 }
0054 
0055 RemoveBookmarksCommand::~RemoveBookmarksCommand()
0056 {
0057     if (m_done && !m_node->parent())
0058     {
0059         delete m_node;
0060     }
0061 }
0062 
0063 void RemoveBookmarksCommand::undo()
0064 {
0065     m_parent->add(m_node, m_row);
0066     Q_EMIT m_bookmarkManager->entryAdded(m_node);
0067     m_done = false;
0068 }
0069 
0070 void RemoveBookmarksCommand::redo()
0071 {
0072     m_parent->remove(m_node);
0073     Q_EMIT m_bookmarkManager->entryRemoved(m_parent, m_row, m_node);
0074     m_done = true;
0075 }
0076 
0077 // --------------------------------------------------------------
0078 
0079 InsertBookmarksCommand::InsertBookmarksCommand(BookmarksManager* const mngr,
0080                                                BookmarkNode* const parent,
0081                                                BookmarkNode* const node,
0082                                                int row)
0083     : RemoveBookmarksCommand(mngr, parent, row)
0084 {
0085     setText(i18n("Insert Bookmark"));
0086     m_node = node;
0087 }
0088 
0089 void InsertBookmarksCommand::undo()
0090 {
0091     RemoveBookmarksCommand::redo();
0092 }
0093 
0094 void InsertBookmarksCommand::redo()
0095 {
0096     RemoveBookmarksCommand::undo();
0097 }
0098 
0099 // --------------------------------------------------------------
0100 
0101 class Q_DECL_HIDDEN ChangeBookmarkCommand::Private
0102 {
0103 public:
0104 
0105     explicit Private()
0106       : manager (nullptr),
0107         type    (Url),
0108         node    (nullptr)
0109     {
0110     }
0111 
0112     BookmarksManager* manager;
0113     BookmarkData      type;
0114     QString           oldValue;
0115     QString           newValue;
0116     BookmarkNode*     node;
0117 };
0118 
0119 ChangeBookmarkCommand::ChangeBookmarkCommand(BookmarksManager* const mngr,
0120                                              BookmarkNode* const node,
0121                                              const QString& newValue,
0122                                              BookmarkData type)
0123     : QUndoCommand(),
0124       d           (new Private)
0125 {
0126     d->manager  = mngr;
0127     d->type     = type;
0128     d->newValue = newValue;
0129     d->node     = node;
0130 
0131     switch (d->type)
0132     {
0133         case Title:
0134         {
0135             d->oldValue = d->node->title;
0136             setText(i18n("Title Change"));
0137             break;
0138         }
0139 
0140         case Desc:
0141         {
0142             d->oldValue = d->node->desc;
0143             setText(i18n("Comment Change"));
0144             break;
0145         }
0146 
0147         default:    // Url
0148         {
0149             d->oldValue = d->node->url;
0150             setText(i18n("Address Change"));
0151             break;
0152         }
0153     }
0154 }
0155 
0156 ChangeBookmarkCommand::~ChangeBookmarkCommand()
0157 {
0158     delete d;
0159 }
0160 
0161 void ChangeBookmarkCommand::undo()
0162 {
0163     switch (d->type)
0164     {
0165         case Title:
0166         {
0167             d->node->title = d->oldValue;
0168             break;
0169         }
0170 
0171         case Desc:
0172         {
0173             d->node->desc  = d->oldValue;
0174             break;
0175         }
0176 
0177         default:    // Url
0178         {
0179             d->node->url   = d->oldValue;
0180             break;
0181         }
0182     }
0183 
0184     Q_EMIT d->manager->entryChanged(d->node);
0185 }
0186 
0187 void ChangeBookmarkCommand::redo()
0188 {
0189     switch (d->type)
0190     {
0191         case Title:
0192         {
0193             d->node->title = d->newValue;
0194             break;
0195         }
0196 
0197         case Desc:
0198         {
0199             d->node->desc  = d->newValue;
0200             break;
0201         }
0202 
0203         default:    // Url
0204         {
0205             d->node->url   = d->newValue;
0206             break;
0207         }
0208     }
0209 
0210     Q_EMIT d->manager->entryChanged(d->node);
0211 }
0212 
0213 // --------------------------------------------------------------
0214 
0215 class Q_DECL_HIDDEN BookmarksModel::Private
0216 {
0217 public:
0218 
0219     explicit Private()
0220       : manager (nullptr),
0221         endMacro(false)
0222     {
0223     }
0224 
0225     BookmarksManager* manager;
0226     bool              endMacro;
0227 };
0228 
0229 BookmarksModel::BookmarksModel(BookmarksManager* const mngr, QObject* const parent)
0230     : QAbstractItemModel(parent),
0231       d                 (new Private)
0232 {
0233     d->manager = mngr;
0234 
0235     connect(d->manager, SIGNAL(entryAdded(BookmarkNode*)),
0236             this, SLOT(entryAdded(BookmarkNode*)));
0237 
0238     connect(d->manager, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
0239             this, SLOT(entryRemoved(BookmarkNode*,int,BookmarkNode*)));
0240 
0241     connect(d->manager, SIGNAL(entryChanged(BookmarkNode*)),
0242             this, SLOT(entryChanged(BookmarkNode*)));
0243 }
0244 
0245 BookmarksModel::~BookmarksModel()
0246 {
0247     delete d;
0248 }
0249 
0250 BookmarksManager* BookmarksModel::bookmarksManager() const
0251 {
0252     return d->manager;
0253 }
0254 
0255 QModelIndex BookmarksModel::index(BookmarkNode* node) const
0256 {
0257     BookmarkNode* const parent = node->parent();
0258 
0259     if (!parent)
0260     {
0261         return QModelIndex();
0262     }
0263 
0264     return createIndex(parent->children().indexOf(node), 0, node);
0265 }
0266 
0267 void BookmarksModel::entryAdded(BookmarkNode* item)
0268 {
0269     Q_ASSERT(item && item->parent());
0270 
0271     int row                    = item->parent()->children().indexOf(item);
0272     BookmarkNode* const parent = item->parent();
0273 
0274     // item was already added so remove before beginInsertRows is called
0275 
0276     parent->remove(item);
0277     beginInsertRows(index(parent), row, row);
0278     parent->add(item, row);
0279     endInsertRows();
0280 }
0281 
0282 void BookmarksModel::entryRemoved(BookmarkNode* parent, int row, BookmarkNode* item)
0283 {
0284     // item was already removed, re-add so beginRemoveRows works
0285 
0286     parent->add(item, row);
0287     beginRemoveRows(index(parent), row, row);
0288     parent->remove(item);
0289     endRemoveRows();
0290 }
0291 
0292 void BookmarksModel::entryChanged(BookmarkNode* item)
0293 {
0294     QModelIndex idx = index(item);
0295     Q_EMIT dataChanged(idx, idx);
0296 }
0297 
0298 bool BookmarksModel::removeRows(int row, int count, const QModelIndex& parent)
0299 {
0300     if ((row < 0) || (count <= 0) || ((row + count) > rowCount(parent)))
0301     {
0302         return false;
0303     }
0304 
0305     BookmarkNode* const bookmarkNode = node(parent);
0306 
0307     for (int i = (row + count - 1) ; i >= row ; --i)
0308     {
0309         BookmarkNode* const node = bookmarkNode->children().at(i);
0310         d->manager->removeBookmark(node);
0311     }
0312 
0313     if (d->endMacro)
0314     {
0315         d->manager->undoRedoStack()->endMacro();
0316         d->endMacro = false;
0317     }
0318 
0319     return true;
0320 }
0321 
0322 QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const
0323 {
0324     if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
0325     {
0326         switch (section)
0327         {
0328             case 0:
0329             {
0330                 return i18nc("@title: bookmark header", "Title");
0331             }
0332 
0333             case 1:
0334             {
0335                 return i18nc("@title: bookmark header", "Comment");
0336             }
0337         }
0338     }
0339 
0340     return QAbstractItemModel::headerData(section, orientation, role);
0341 }
0342 
0343 QVariant BookmarksModel::data(const QModelIndex& index, int role) const
0344 {
0345     if (!index.isValid() || (index.model() != this))
0346     {
0347         return QVariant();
0348     }
0349 
0350     const BookmarkNode* const bookmarkNode = node(index);
0351 
0352     switch (role)
0353     {
0354         case Qt::EditRole:
0355         case Qt::DisplayRole:
0356         {
0357             if (bookmarkNode->type() == BookmarkNode::Separator)
0358             {
0359                 switch (index.column())
0360                 {
0361                     case 0:
0362                     {
0363                         return QString(50, QChar(0xB7));
0364                     }
0365 
0366                     case 1:
0367                     {
0368                         return QString();
0369                     }
0370                 }
0371             }
0372 
0373             switch (index.column())
0374             {
0375                 case 0:
0376                 {
0377                     return bookmarkNode->title;
0378                 }
0379 
0380                 case 1:
0381                 {
0382                     return bookmarkNode->desc;
0383                 }
0384             }
0385 
0386             break;
0387         }
0388 
0389         case BookmarksModel::UrlRole:
0390         {
0391             return QUrl(bookmarkNode->url);
0392         }
0393 
0394         case BookmarksModel::UrlStringRole:
0395         {
0396             return bookmarkNode->url;
0397         }
0398 
0399         case BookmarksModel::DateAddedRole:
0400         {
0401             return bookmarkNode->dateAdded;
0402         }
0403 
0404         case BookmarksModel::TypeRole:
0405         {
0406             return bookmarkNode->type();
0407         }
0408 
0409         case BookmarksModel::SeparatorRole:
0410         {
0411             return (bookmarkNode->type() == BookmarkNode::Separator);
0412         }
0413 
0414         case Qt::DecorationRole:
0415         {
0416             if (index.column() == 0)
0417             {
0418                 if (bookmarkNode->type() == BookmarkNode::Bookmark)
0419                 {
0420                     return QIcon::fromTheme(QLatin1String("globe"));
0421                 }
0422                 else
0423                 {
0424                     return QIcon::fromTheme(QLatin1String("folder"));
0425                 }
0426             }
0427         }
0428     }
0429 
0430     return QVariant();
0431 }
0432 
0433 int BookmarksModel::columnCount(const QModelIndex& parent) const
0434 {
0435     return ((parent.column() > 0) ? 0 : 2);
0436 }
0437 
0438 int BookmarksModel::rowCount(const QModelIndex& parent) const
0439 {
0440     if (parent.column() > 0)
0441     {
0442         return 0;
0443     }
0444 
0445     if (!parent.isValid())
0446     {
0447         return d->manager->bookmarks()->children().count();
0448     }
0449 
0450     const BookmarkNode* const item = static_cast<BookmarkNode*>(parent.internalPointer());
0451 
0452     return item->children().count();
0453 }
0454 
0455 QModelIndex BookmarksModel::index(int row, int column, const QModelIndex& parent) const
0456 {
0457     if ((row < 0) || (column < 0) || (row >= rowCount(parent)) || (column >= columnCount(parent)))
0458     {
0459         return QModelIndex();
0460     }
0461 
0462     // get the parent node
0463 
0464     BookmarkNode* const parentNode = node(parent);
0465 
0466     return createIndex(row, column, parentNode->children().at(row));
0467 }
0468 
0469 QModelIndex BookmarksModel::parent(const QModelIndex& index) const
0470 {
0471     if (!index.isValid())
0472     {
0473         return QModelIndex();
0474     }
0475 
0476     BookmarkNode* const itemNode   = node(index);
0477     BookmarkNode* const parentNode = (itemNode ? itemNode->parent() : nullptr);
0478 
0479     if (!parentNode || (parentNode == d->manager->bookmarks()))
0480     {
0481         return QModelIndex();
0482     }
0483 
0484     // get the parent's row
0485 
0486     BookmarkNode* const grandParentNode = parentNode->parent();
0487     int parentRow                       = grandParentNode->children().indexOf(parentNode);
0488 
0489     Q_ASSERT(parentRow >= 0);
0490 
0491     return createIndex(parentRow, 0, parentNode);
0492 }
0493 
0494 bool BookmarksModel::hasChildren(const QModelIndex& parent) const
0495 {
0496     if (!parent.isValid())
0497     {
0498         return true;
0499     }
0500 
0501     const BookmarkNode* const parentNode = node(parent);
0502 
0503     return ((parentNode->type() == BookmarkNode::Folder) ||
0504             (parentNode->type() == BookmarkNode::RootFolder));
0505 }
0506 
0507 Qt::ItemFlags BookmarksModel::flags(const QModelIndex& index) const
0508 {
0509     if (!index.isValid())
0510     {
0511         return Qt::NoItemFlags;
0512     }
0513 
0514     Qt::ItemFlags flags              = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0515     BookmarkNode* const bookmarkNode = node(index);
0516 
0517     if (bookmarkNode->type() != BookmarkNode::RootFolder)
0518     {
0519         flags |= Qt::ItemIsDragEnabled;
0520     }
0521 
0522     if ((bookmarkNode->type() != BookmarkNode::Separator) &&
0523         (bookmarkNode->type() != BookmarkNode::RootFolder))
0524     {
0525         flags |= Qt::ItemIsEditable;
0526     }
0527 
0528     if (hasChildren(index))
0529     {
0530         flags |= Qt::ItemIsDropEnabled;
0531     }
0532 
0533     return flags;
0534 }
0535 
0536 Qt::DropActions BookmarksModel::supportedDropActions() const
0537 {
0538     return (Qt::CopyAction | Qt::MoveAction);
0539 }
0540 
0541 QStringList BookmarksModel::mimeTypes() const
0542 {
0543     QStringList types;
0544     types << QLatin1String("application/bookmarks.xbel");
0545 
0546     return types;
0547 }
0548 
0549 QMimeData* BookmarksModel::mimeData(const QModelIndexList& indexes) const
0550 {
0551     QMimeData* const mimeData = new QMimeData();
0552     QByteArray data;
0553     QDataStream stream(&data, QIODevice::WriteOnly);
0554 
0555     Q_FOREACH (QModelIndex index, indexes)
0556     {
0557         if ((index.column() != 0) || !index.isValid())
0558         {
0559             continue;
0560         }
0561 
0562         QByteArray encodedData;
0563         QBuffer buffer(&encodedData);
0564         buffer.open(QBuffer::ReadWrite);
0565         XbelWriter writer;
0566         const BookmarkNode* const parentNode = node(index);
0567         writer.write(&buffer, parentNode);
0568         stream << encodedData;
0569     }
0570 
0571     mimeData->setData(QLatin1String("application/bookmarks.xbel"), data);
0572 
0573     return mimeData;
0574 }
0575 
0576 bool BookmarksModel::dropMimeData(const QMimeData* data,
0577                                   Qt::DropAction action,
0578                                   int row, int column,
0579                                   const QModelIndex& parent)
0580 {
0581     if (action == Qt::IgnoreAction)
0582     {
0583         return true;
0584     }
0585 
0586     if (!data->hasFormat(QLatin1String("application/bookmarks.xbel")) || column > 0)
0587     {
0588         return false;
0589     }
0590 
0591     QByteArray ba = data->data(QLatin1String("application/bookmarks.xbel"));
0592     QDataStream stream(&ba, QIODevice::ReadOnly);
0593 
0594     if (stream.atEnd())
0595     {
0596         return false;
0597     }
0598 
0599     QUndoStack* const undoStack = d->manager->undoRedoStack();
0600     undoStack->beginMacro(QLatin1String("Move Bookmarks"));
0601 
0602     while (!stream.atEnd())
0603     {
0604         QByteArray encodedData;
0605         stream >> encodedData;
0606         QBuffer buffer(&encodedData);
0607         buffer.open(QBuffer::ReadOnly);
0608 
0609         XbelReader reader;
0610         BookmarkNode* const rootNode  = reader.read(&buffer);
0611         QList<BookmarkNode*> children = rootNode->children();
0612 
0613         for (int i = 0 ; i < children.count() ; ++i)
0614         {
0615             BookmarkNode* const bookmarkNode = children.at(i);
0616             rootNode->remove(bookmarkNode);
0617             row                              = qMax(0, row);
0618             BookmarkNode* const parentNode   = node(parent);
0619             d->manager->addBookmark(parentNode, bookmarkNode, row);
0620             d->endMacro                      = true;
0621         }
0622 
0623         delete rootNode;
0624     }
0625 
0626     return true;
0627 }
0628 
0629 bool BookmarksModel::setData(const QModelIndex& index, const QVariant& value, int role)
0630 {
0631     if (!index.isValid() || (flags(index) & Qt::ItemIsEditable) == 0)
0632     {
0633         return false;
0634     }
0635 
0636     BookmarkNode* const item = node(index);
0637 
0638     switch (role)
0639     {
0640         case Qt::EditRole:
0641         case Qt::DisplayRole:
0642         {
0643             if (index.column() == 0)
0644             {
0645                 d->manager->setTitle(item, value.toString());
0646                 break;
0647             }
0648 
0649             if (index.column() == 1)
0650             {
0651                 d->manager->setComment(item, value.toString());
0652                 break;
0653             }
0654 
0655             return false;
0656         }
0657 
0658         case BookmarksModel::UrlRole:
0659         {
0660             d->manager->setUrl(item, value.toUrl().toString());
0661             break;
0662         }
0663 
0664         case BookmarksModel::UrlStringRole:
0665         {
0666             d->manager->setUrl(item, value.toString());
0667             break;
0668         }
0669 
0670         default:
0671         {
0672             return false;
0673         }
0674     }
0675 
0676     return true;
0677 }
0678 
0679 BookmarkNode* BookmarksModel::node(const QModelIndex& index) const
0680 {
0681     BookmarkNode* const itemNode = static_cast<BookmarkNode*>(index.internalPointer());
0682 
0683     if (!itemNode)
0684     {
0685         return d->manager->bookmarks();
0686     }
0687 
0688     return itemNode;
0689 }
0690 
0691 // --------------------------------------------------------------
0692 
0693 AddBookmarkProxyModel::AddBookmarkProxyModel(QObject* const parent)
0694     : QSortFilterProxyModel(parent)
0695 {
0696 }
0697 
0698 int AddBookmarkProxyModel::columnCount(const QModelIndex& parent) const
0699 {
0700     return qMin(1, QSortFilterProxyModel::columnCount(parent));
0701 }
0702 
0703 bool AddBookmarkProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const
0704 {
0705     QModelIndex idx = sourceModel()->index(srow, 0, sparent);
0706 
0707     return sourceModel()->hasChildren(idx);
0708 }
0709 
0710 // --------------------------------------------------------------
0711 
0712 TreeProxyModel::TreeProxyModel(QObject* const parent)
0713     : QSortFilterProxyModel(parent)
0714 {
0715     setFilterCaseSensitivity(Qt::CaseInsensitive);
0716 }
0717 
0718 int TreeProxyModel::columnCount(const QModelIndex&) const
0719 {
0720     // 1th column : Title
0721     // 2th column : Comment
0722 
0723     return 2;
0724 }
0725 
0726 bool TreeProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const
0727 {
0728     QModelIndex index = sourceModel()->index(srow, 0, sparent);
0729 
0730     if (!index.isValid())
0731     {
0732         return false;
0733     }
0734 
0735 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0736 
0737     if (index.data().toString().contains(filterRegularExpression()))
0738 
0739 #else
0740 
0741     if (index.data().toString().contains(filterRegExp()))
0742 
0743 #endif
0744 
0745     {
0746         return true;
0747     }
0748 
0749     for (int i = 0 ; i < sourceModel()->rowCount(index) ; ++i)
0750     {
0751         if (filterAcceptsRow(i, index))
0752         {
0753             return true;
0754         }
0755     }
0756 
0757     return false;
0758 }
0759 
0760 void TreeProxyModel::emitResult(bool v)
0761 {
0762     Q_EMIT signalFilterAccepts(v);
0763 }
0764 
0765 // --------------------------------------------------------------
0766 
0767 class Q_DECL_HIDDEN BookmarksManager::Private
0768 {
0769 public:
0770 
0771     explicit Private()
0772       : loaded          (false),
0773         bookmarkRootNode(nullptr),
0774         bookmarkModel   (nullptr)
0775     {
0776     }
0777 
0778     bool            loaded;
0779     BookmarkNode*   bookmarkRootNode;
0780     BookmarksModel* bookmarkModel;
0781     QUndoStack      commands;
0782     QString         bookmarksFile;
0783 };
0784 
0785 BookmarksManager::BookmarksManager(const QString& bookmarksFile, QObject* const parent)
0786     : QObject(parent),
0787       d      (new Private)
0788 {
0789     d->bookmarksFile = bookmarksFile;
0790     load();
0791 }
0792 
0793 BookmarksManager::~BookmarksManager()
0794 {
0795     delete d->bookmarkRootNode;
0796     delete d;
0797 }
0798 
0799 void BookmarksManager::changeExpanded()
0800 {
0801 }
0802 
0803 void BookmarksManager::load()
0804 {
0805     if (d->loaded)
0806     {
0807         return;
0808     }
0809 
0810     qCDebug(DIGIKAM_GEOIFACE_LOG) << "Loading GPS bookmarks from" << d->bookmarksFile;
0811     d->loaded = true;
0812 
0813     XbelReader reader;
0814     d->bookmarkRootNode = reader.read(d->bookmarksFile);
0815 
0816     if (reader.error() != QXmlStreamReader::NoError)
0817     {
0818         QMessageBox::warning(nullptr, i18nc("@title:window", "Loading Bookmark"),
0819                              i18n("Error when loading bookmarks on line %1, column %2:\n%3",
0820                                   reader.lineNumber(),
0821                                   reader.columnNumber(),
0822                                   reader.errorString()));
0823     }
0824 }
0825 
0826 void BookmarksManager::save()
0827 {
0828     if (!d->loaded)
0829     {
0830         return;
0831     }
0832 
0833     qCDebug(DIGIKAM_GEOIFACE_LOG) << "Saving GPS bookmarks to" << d->bookmarksFile;
0834 
0835     XbelWriter writer;
0836 
0837     if (!writer.write(d->bookmarksFile, d->bookmarkRootNode))
0838     {
0839         qCWarning(DIGIKAM_GEOIFACE_LOG) << "BookmarkManager: error saving to" << d->bookmarksFile;
0840     }
0841 }
0842 
0843 void BookmarksManager::addBookmark(BookmarkNode* const parent, BookmarkNode* const node, int row)
0844 {
0845     if (!d->loaded)
0846     {
0847         return;
0848     }
0849 
0850     Q_ASSERT(parent);
0851 
0852     InsertBookmarksCommand* const command = new InsertBookmarksCommand(this, parent, node, row);
0853     d->commands.push(command);
0854 }
0855 
0856 void BookmarksManager::removeBookmark(BookmarkNode* const node)
0857 {
0858     if (!d->loaded)
0859     {
0860         return;
0861     }
0862 
0863     Q_ASSERT(node);
0864 
0865     BookmarkNode* const parent            = node->parent();
0866     int row                               = parent->children().indexOf(node);
0867     RemoveBookmarksCommand* const command = new RemoveBookmarksCommand(this, parent, row);
0868     d->commands.push(command);
0869 }
0870 
0871 void BookmarksManager::setTitle(BookmarkNode* const node, const QString& newTitle)
0872 {
0873     if (!d->loaded)
0874     {
0875         return;
0876     }
0877 
0878     Q_ASSERT(node);
0879 
0880     ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newTitle,
0881                                                                      ChangeBookmarkCommand::Title);
0882     d->commands.push(command);
0883 }
0884 
0885 void BookmarksManager::setUrl(BookmarkNode* const node, const QString& newUrl)
0886 {
0887     if (!d->loaded)
0888     {
0889         return;
0890     }
0891 
0892     Q_ASSERT(node);
0893 
0894     ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newUrl,
0895                                                                      ChangeBookmarkCommand::Url);
0896     d->commands.push(command);
0897 }
0898 
0899 void BookmarksManager::setComment(BookmarkNode* const node, const QString& newDesc)
0900 {
0901     if (!d->loaded)
0902     {
0903         return;
0904     }
0905 
0906     Q_ASSERT(node);
0907 
0908     ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newDesc,
0909                                                                      ChangeBookmarkCommand::Desc);
0910     d->commands.push(command);
0911 }
0912 
0913 BookmarkNode* BookmarksManager::bookmarks()
0914 {
0915     if (!d->loaded)
0916     {
0917         load();
0918     }
0919 
0920     return d->bookmarkRootNode;
0921 }
0922 
0923 BookmarksModel* BookmarksManager::bookmarksModel()
0924 {
0925     if (!d->bookmarkModel)
0926     {
0927         d->bookmarkModel = new BookmarksModel(this, this);
0928     }
0929 
0930     return d->bookmarkModel;
0931 }
0932 
0933 QUndoStack* BookmarksManager::undoRedoStack() const
0934 {
0935     return &d->commands;
0936 }
0937 
0938 void BookmarksManager::importBookmarks()
0939 {
0940     QString fileName = DFileDialog::getOpenFileName(nullptr, i18nc("@title:window", "Open File"),
0941                                                     QString(),
0942                                                     i18n("XBEL (*.xbel *.xml)"));
0943     if (fileName.isEmpty())
0944     {
0945         return;
0946     }
0947 
0948     XbelReader reader;
0949     BookmarkNode* const importRootNode = reader.read(fileName);
0950 
0951     if (reader.error() != QXmlStreamReader::NoError)
0952     {
0953         QMessageBox::warning(nullptr, i18nc("@title:window", "Loading Bookmark"),
0954                              i18n("Error when loading bookmarks on line %1, column %2:\n%3",
0955                                   reader.lineNumber(),
0956                                   reader.columnNumber(),
0957                                   reader.errorString()));
0958     }
0959 
0960     importRootNode->setType(BookmarkNode::Folder);
0961     importRootNode->title = i18n("Imported %1", QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
0962     addBookmark(bookmarks(), importRootNode);
0963 }
0964 
0965 void BookmarksManager::exportBookmarks()
0966 {
0967     QString fileName = DFileDialog::getSaveFileName(nullptr, i18nc("@title:window", "Save File"),
0968                                                     i18n("%1 Bookmarks.xbel", QCoreApplication::applicationName()),
0969                                                     i18n("XBEL (*.xbel *.xml)"));
0970     if (fileName.isEmpty())
0971     {
0972         return;
0973     }
0974 
0975     XbelWriter writer;
0976 
0977     if (!writer.write(fileName, d->bookmarkRootNode))
0978     {
0979         QMessageBox::critical(nullptr, i18nc("@title:window", "Export Bookmark"), i18n("Error saving bookmarks"));
0980     }
0981 }
0982 
0983 } // namespace Digikam
0984 
0985 #include "moc_bookmarksmngr.cpp"