File indexing completed on 2022-11-29 15:26:23

0001 /* This file is part of the KDE project
0002    Copyright (C) 2005 Daniel Teske <teske@squorn.de>
0003    Copyright (C) 2010 David Faure <faure@kde.org>
0004 
0005    This program is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU General Public
0007    License version 2 or at your option version 3 as published by
0008    the Free Software Foundation.
0009 
0010    This program is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    General Public License for more details.
0014 
0015    You should have received a copy of the GNU General Public License
0016    along with this program; see the file COPYING.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "model.h"
0022 #include "commandhistory.h"
0023 #include "commands.h"
0024 #include "treeitem_p.h"
0025 
0026 #include <KBookmarkManager>
0027 #include <KLocalizedString>
0028 
0029 #include "keditbookmarks_debug.h"
0030 #include <QIcon>
0031 #include <QMimeData>
0032 #include <QStringList>
0033 
0034 class KBookmarkModel::Private
0035 {
0036 public:
0037     Private(KBookmarkModel *qq, const KBookmark &root, CommandHistory *commandHistory)
0038         : q(qq)
0039         , mRoot(root)
0040         , mCommandHistory(commandHistory)
0041         , mInsertionData(nullptr)
0042         , mIgnoreNext(0)
0043     {
0044         mRootItem = new TreeItem(root, nullptr);
0045     }
0046     ~Private()
0047     {
0048         delete mRootItem;
0049         mRootItem = nullptr;
0050     }
0051 
0052     void _kd_slotBookmarksChanged(const QString &groupAddress, const QString &caller = QString());
0053 
0054     KBookmarkModel *q;
0055     TreeItem *mRootItem;
0056     KBookmark mRoot;
0057     CommandHistory *mCommandHistory;
0058 
0059     class InsertionData
0060     {
0061     public:
0062         InsertionData(const QModelIndex &parent, int first, int last)
0063             : mFirst(first)
0064             , mLast(last)
0065         {
0066             mParentItem = static_cast<TreeItem *>(parent.internalPointer());
0067         }
0068         void insertChildren()
0069         {
0070             mParentItem->insertChildren(mFirst, mLast);
0071         }
0072         TreeItem *mParentItem;
0073         int mFirst;
0074         int mLast;
0075     };
0076     InsertionData *mInsertionData;
0077     int mIgnoreNext;
0078 };
0079 
0080 KBookmarkModel::KBookmarkModel(const KBookmark &root, CommandHistory *commandHistory, QObject *parent)
0081     : QAbstractItemModel(parent)
0082     , d(new Private(this, root, commandHistory))
0083 {
0084     connect(commandHistory, &CommandHistory::notifyCommandExecuted, this, &KBookmarkModel::notifyManagers);
0085     Q_ASSERT(bookmarkManager());
0086     // update when the model updates after a D-Bus signal, coming from this
0087     // process or from another one
0088     connect(bookmarkManager(), &KBookmarkManager::changed, this, [this](const QString &groupAddress, const QString &caller) {
0089         d->_kd_slotBookmarksChanged(groupAddress, caller);
0090     });
0091 }
0092 
0093 void KBookmarkModel::setRoot(const KBookmark &root)
0094 {
0095     d->mRoot = root;
0096     resetModel();
0097 }
0098 
0099 KBookmarkModel::~KBookmarkModel()
0100 {
0101     delete d;
0102 }
0103 
0104 void KBookmarkModel::resetModel()
0105 {
0106     beginResetModel();
0107     delete d->mRootItem;
0108     d->mRootItem = new TreeItem(d->mRoot, nullptr);
0109     endResetModel();
0110 }
0111 
0112 QVariant KBookmarkModel::data(const QModelIndex &index, int role) const
0113 {
0114     if (!index.isValid())
0115         return QVariant();
0116 
0117     // Text
0118     if (role == Qt::DisplayRole || role == Qt::EditRole) {
0119         const KBookmark bk = bookmarkForIndex(index);
0120         if (bk.address().isEmpty()) {
0121             if (index.column() == NameColumnId)
0122                 return QVariant(i18nc("name of the container of all browser bookmarks", "Bookmarks"));
0123             else
0124                 return QVariant();
0125         }
0126 
0127         switch (index.column()) {
0128         case NameColumnId:
0129             return bk.fullText();
0130         case UrlColumnId:
0131             return bk.url().url(QUrl::PreferLocalFile);
0132         case CommentColumnId:
0133             return bk.description();
0134         case StatusColumnId: {
0135             QString text1 = bk.metaDataItem(QStringLiteral("favstate")); // favicon state
0136             QString text2 = bk.metaDataItem(QStringLiteral("linkstate"));
0137             if (text1.isEmpty() || text2.isEmpty())
0138                 return QVariant(text1 + text2);
0139             else
0140                 return QVariant(text1 + QLatin1String("  --  ") + text2);
0141         }
0142         default:
0143             return QVariant(); // can't happen
0144         }
0145     }
0146 
0147     // Icon
0148     if (role == Qt::DecorationRole && index.column() == NameColumnId) {
0149         KBookmark bk = bookmarkForIndex(index);
0150         if (bk.address().isEmpty())
0151             return QIcon::fromTheme(QStringLiteral("bookmarks"));
0152         return QIcon::fromTheme(bk.icon());
0153     }
0154 
0155     // Special roles
0156     if (role == KBookmarkRole) {
0157         KBookmark bk = bookmarkForIndex(index);
0158         return QVariant::fromValue(bk);
0159     }
0160     return QVariant();
0161 }
0162 
0163 // FIXME QModelIndex KBookmarkModel::buddy(const QModelIndex & index) //return parent for empty folder padders
0164 // no empty folder padders atm
0165 
0166 Qt::ItemFlags KBookmarkModel::flags(const QModelIndex &index) const
0167 {
0168     const Qt::ItemFlags baseFlags = QAbstractItemModel::flags(index);
0169 
0170     if (!index.isValid())
0171         return (Qt::ItemIsDropEnabled | baseFlags);
0172 
0173     static const Qt::ItemFlags groupFlags = Qt::ItemIsDropEnabled;
0174     static const Qt::ItemFlags groupDragEditFlags = groupFlags | Qt::ItemIsDragEnabled | Qt::ItemIsEditable;
0175     static const Qt::ItemFlags groupEditFlags = groupFlags | Qt::ItemIsEditable;
0176     static const Qt::ItemFlags rootFlags = groupFlags;
0177     static const Qt::ItemFlags bookmarkFlags = Qt::NoItemFlags;
0178     static const Qt::ItemFlags bookmarkDragEditFlags = bookmarkFlags | Qt::ItemIsDragEnabled | Qt::ItemIsEditable;
0179     static const Qt::ItemFlags bookmarkEditFlags = bookmarkFlags | Qt::ItemIsEditable;
0180 
0181     Qt::ItemFlags result = baseFlags;
0182 
0183     const int column = index.column();
0184     const KBookmark bookmark = bookmarkForIndex(index);
0185     if (bookmark.isGroup()) {
0186         const bool isRoot = bookmark.address().isEmpty();
0187         result |= (isRoot) ? rootFlags
0188                            : (column == NameColumnId) ? groupDragEditFlags
0189                                                       : (column == CommentColumnId) ? groupEditFlags :
0190                                                                                     /*else*/ groupFlags;
0191     } else {
0192         result |= (column == NameColumnId) ? bookmarkDragEditFlags
0193                                            : (column != StatusColumnId) ? bookmarkEditFlags
0194                                                                         /* else */
0195                                                                         : bookmarkFlags;
0196     }
0197 
0198     return result;
0199 }
0200 
0201 bool KBookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role)
0202 {
0203     if (index.isValid() && role == Qt::EditRole) {
0204         qCDebug(KEDITBOOKMARKS_LOG) << value.toString();
0205         d->mCommandHistory->addCommand(new EditCommand(this, bookmarkForIndex(index).address(), index.column(), value.toString()));
0206         return true;
0207     }
0208     return false;
0209 }
0210 
0211 QVariant KBookmarkModel::headerData(int section, Qt::Orientation orientation, int role) const
0212 {
0213     if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
0214         QString result;
0215         switch (section) {
0216         case NameColumnId:
0217             result = i18nc("@title:column name of a bookmark", "Name");
0218             break;
0219         case UrlColumnId:
0220             result = i18nc("@title:column name of a bookmark", "Location");
0221             break;
0222         case CommentColumnId:
0223             result = i18nc("@title:column comment for a bookmark", "Comment");
0224             break;
0225         case StatusColumnId:
0226             result = i18nc("@title:column status of a bookmark", "Status");
0227             break;
0228         }
0229         return result;
0230     } else
0231         return QVariant();
0232 }
0233 
0234 QModelIndex KBookmarkModel::index(int row, int column, const QModelIndex &parent) const
0235 {
0236     if (!parent.isValid())
0237         return createIndex(row, column, d->mRootItem);
0238 
0239     TreeItem *item = static_cast<TreeItem *>(parent.internalPointer());
0240     if (row == item->childCount()) { // Received drop below last row, simulate drop on last row
0241         return createIndex(row - 1, column, item->child(row - 1));
0242     }
0243 
0244     return createIndex(row, column, item->child(row));
0245 }
0246 
0247 QModelIndex KBookmarkModel::parent(const QModelIndex &index) const
0248 {
0249     if (!index.isValid()) {
0250         // qt asks for the parent of an invalid parent
0251         // either we are in a inconsistent case or more likely
0252         // we are dragging and dropping and qt didn't check
0253         // what itemAt() returned
0254         return index;
0255     }
0256     KBookmark bk = bookmarkForIndex(index);
0257     ;
0258     const QString rootAddress = d->mRoot.address();
0259 
0260     if (bk.address() == rootAddress)
0261         return QModelIndex();
0262 
0263     KBookmarkGroup parent = bk.parentGroup();
0264     TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
0265     if (parent.address() != rootAddress)
0266         return createIndex(parent.positionInParent(), 0, item->parent());
0267     else // parent is root
0268         return createIndex(0, 0, item->parent());
0269 }
0270 
0271 int KBookmarkModel::rowCount(const QModelIndex &parent) const
0272 {
0273     if (parent.isValid())
0274         return static_cast<TreeItem *>(parent.internalPointer())->childCount();
0275     else // root case
0276         return 1; // Only one child: "Bookmarks"
0277 }
0278 
0279 int KBookmarkModel::columnCount(const QModelIndex &) const
0280 {
0281     return NoOfColumnIds;
0282 }
0283 
0284 QModelIndex KBookmarkModel::indexForBookmark(const KBookmark &bk) const
0285 {
0286     TreeItem *item = d->mRootItem->treeItemForBookmark(bk);
0287     if (!item) {
0288         qCWarning(KEDITBOOKMARKS_LOG) << "Bookmark not found" << bk.address();
0289         Q_ASSERT(item);
0290     }
0291     return createIndex(KBookmark::positionInParent(bk.address()), 0, item);
0292 }
0293 
0294 void KBookmarkModel::emitDataChanged(const KBookmark &bk)
0295 {
0296     QModelIndex idx = indexForBookmark(bk);
0297     qCDebug(KEDITBOOKMARKS_LOG) << idx;
0298     Q_EMIT dataChanged(idx, idx.sibling(idx.row(), columnCount() - 1));
0299 }
0300 
0301 static const char *s_mime_bookmark_addresses = "application/x-kde-bookmarkaddresses";
0302 
0303 QMimeData *KBookmarkModel::mimeData(const QModelIndexList &indexes) const
0304 {
0305     QMimeData *mimeData = new QMimeData;
0306     KBookmark::List bookmarks;
0307     QByteArray addresses;
0308 
0309     for (const auto &it : indexes) {
0310         if (it.column() == NameColumnId) {
0311             bookmarks.push_back(bookmarkForIndex(it));
0312             if (!addresses.isEmpty()) {
0313                 addresses.append(';');
0314             }
0315             addresses.append(bookmarkForIndex(it).address().toLatin1());
0316             qCDebug(KEDITBOOKMARKS_LOG) << "appended" << bookmarkForIndex(it).address();
0317         }
0318     }
0319 
0320     bookmarks.populateMimeData(mimeData);
0321     mimeData->setData(QLatin1String(s_mime_bookmark_addresses), addresses);
0322     return mimeData;
0323 }
0324 
0325 Qt::DropActions KBookmarkModel::supportedDropActions() const
0326 {
0327     return Qt::CopyAction | Qt::MoveAction;
0328 }
0329 
0330 QStringList KBookmarkModel::mimeTypes() const
0331 {
0332     return KBookmark::List::mimeDataTypes();
0333 }
0334 
0335 bool KBookmarkModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0336 {
0337     QModelIndex dropDestIndex;
0338     bool isInsertBetweenOp = false;
0339     if (row == -1) {
0340         dropDestIndex = parent;
0341     } else {
0342         isInsertBetweenOp = true;
0343         dropDestIndex = index(row, column, parent);
0344     }
0345 
0346     KBookmark dropDestBookmark = bookmarkForIndex(dropDestIndex);
0347     if (dropDestBookmark.isNull()) {
0348         // Presumably an invalid index: assume we want to place this in the root bookmark
0349         // folder.
0350         dropDestBookmark = d->mRoot;
0351     }
0352 
0353     QString addr = dropDestBookmark.address();
0354     if (dropDestBookmark.isGroup() && !isInsertBetweenOp) {
0355         addr += QLatin1String("/0");
0356     }
0357     // bookmarkForIndex(...) does not distinguish between the last item in the folder
0358     // and the point *after* the last item in the folder (and its hard to see how to
0359     // modify it so it does), so do the check here.
0360     if (isInsertBetweenOp && row == dropDestBookmark.positionInParent() + 1) {
0361         // Attempting to insert underneath the last item in a folder; adjust the address.
0362         addr = KBookmark::nextAddress(addr);
0363     }
0364 
0365     if (action == Qt::CopyAction) {
0366         KEBMacroCommand *cmd = CmdGen::insertMimeSource(this, QStringLiteral("Copy"), data, addr);
0367         d->mCommandHistory->addCommand(cmd);
0368     } else if (action == Qt::MoveAction) {
0369         if (data->hasFormat(QLatin1String(s_mime_bookmark_addresses))) {
0370             KBookmark::List bookmarks;
0371             QList<QByteArray> addresses = data->data(QLatin1String(s_mime_bookmark_addresses)).split(';');
0372             std::sort(addresses.begin(), addresses.end());
0373             for (const auto &address : std::as_const(addresses)) {
0374                 KBookmark bk = bookmarkManager()->findByAddress(QString::fromLatin1(address));
0375                 qCDebug(KEDITBOOKMARKS_LOG) << "Extracted bookmark:" << bk.address();
0376                 bookmarks.prepend(bk); // reverse order, so that we don't invalidate addresses (#287038)
0377             }
0378 
0379             KEBMacroCommand *cmd = CmdGen::itemsMoved(this, bookmarks, addr, false);
0380             d->mCommandHistory->addCommand(cmd);
0381         } else {
0382             qCDebug(KEDITBOOKMARKS_LOG) << "NO FORMAT";
0383             KEBMacroCommand *cmd = CmdGen::insertMimeSource(this, QStringLiteral("Copy"), data, addr);
0384             d->mCommandHistory->addCommand(cmd);
0385         }
0386     }
0387 
0388     return true;
0389 }
0390 
0391 KBookmark KBookmarkModel::bookmarkForIndex(const QModelIndex &index) const
0392 {
0393     if (!index.isValid()) {
0394         return KBookmark();
0395     }
0396     return static_cast<TreeItem *>(index.internalPointer())->bookmark();
0397 }
0398 
0399 void KBookmarkModel::beginInsert(const KBookmarkGroup &group, int first, int last)
0400 {
0401     Q_ASSERT(!d->mInsertionData);
0402     const QModelIndex parent = indexForBookmark(group);
0403     d->mInsertionData = new Private::InsertionData(parent, first, last);
0404     beginInsertRows(parent, first, last);
0405 }
0406 
0407 void KBookmarkModel::endInsert()
0408 {
0409     Q_ASSERT(d->mInsertionData);
0410     d->mInsertionData->insertChildren();
0411     delete d->mInsertionData;
0412     d->mInsertionData = nullptr;
0413     endInsertRows();
0414 }
0415 
0416 #if 0 // Probably correct, but not needed at the moment
0417 void KBookmarkModel::removeBookmarks(KBookmarkGroup parent, int first, int last)
0418 {
0419     const QModelIndex parentIndex = indexForBookmark(parent);
0420     beginRemoveRows(parentIndex, first, last);
0421     TreeItem* parentItem = static_cast<TreeItem *>(parentIndex.internalPointer());
0422 
0423     // Go to the last bookmark to remove
0424     KBookmark bk = parent.first();
0425     for (int i = 1; i < last; ++i)
0426         bk = parent.next(bk);
0427     // Then remove bookmarks, iterating backwards until 'first'
0428     // (so that numbering still works)
0429     for (int i = last; i >= first; --i) {
0430         KBookmark prev = parent.previous(bk);
0431         parent.deleteBookmark(bk);
0432         bk = prev;
0433     }
0434 
0435     parentItem->deleteChildren(first, last);
0436     endRemoveRows();
0437 }
0438 #endif
0439 
0440 void KBookmarkModel::removeBookmark(const KBookmark &bookmark)
0441 {
0442     KBookmarkGroup parentGroup = bookmark.parentGroup();
0443     const QModelIndex parentIndex = indexForBookmark(parentGroup);
0444     Q_ASSERT(parentIndex.isValid());
0445     const int pos = bookmark.positionInParent();
0446     beginRemoveRows(parentIndex, pos, pos);
0447     TreeItem *parentItem = static_cast<TreeItem *>(parentIndex.internalPointer());
0448     Q_ASSERT(parentItem);
0449 
0450     parentGroup.deleteBookmark(bookmark);
0451 
0452     parentItem->deleteChildren(pos, pos);
0453     endRemoveRows();
0454 }
0455 
0456 CommandHistory *KBookmarkModel::commandHistory()
0457 {
0458     return d->mCommandHistory;
0459 }
0460 
0461 KBookmarkManager *KBookmarkModel::bookmarkManager()
0462 {
0463     return d->mCommandHistory->bookmarkManager();
0464 }
0465 
0466 void KBookmarkModel::Private::_kd_slotBookmarksChanged(const QString &groupAddress, const QString &caller)
0467 {
0468     Q_UNUSED(groupAddress);
0469     Q_UNUSED(caller);
0470     // qCDebug(KEDITBOOKMARKS_LOG) << "_kd_slotBookmarksChanged" << groupAddress << "caller=" << caller << "mIgnoreNext=" << mIgnoreNext;
0471     if (mIgnoreNext > 0) { // We ignore the first changed signal after every change we did
0472         --mIgnoreNext;
0473         return;
0474     }
0475 
0476     // qCDebug(KEDITBOOKMARKS_LOG) << " setRoot!";
0477     q->setRoot(q->bookmarkManager()->root());
0478 
0479     mCommandHistory->clearHistory();
0480 }
0481 
0482 void KBookmarkModel::notifyManagers(const KBookmarkGroup &grp)
0483 {
0484     ++d->mIgnoreNext;
0485     // qCDebug(KEDITBOOKMARKS_LOG) << "notifyManagers -> mIgnoreNext=" << d->mIgnoreNext;
0486     bookmarkManager()->emitChanged(grp);
0487 }
0488 
0489 #include "moc_model.cpp"