File indexing completed on 2024-04-21 05:50:20
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); 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) { 0089 d->_kd_slotBookmarksChanged(groupAddress); 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) 0467 { 0468 Q_UNUSED(groupAddress); 0469 // qCDebug(KEDITBOOKMARKS_LOG) << "_kd_slotBookmarksChanged" << groupAddress << "mIgnoreNext=" << mIgnoreNext; 0470 if (mIgnoreNext > 0) { // We ignore the first changed signal after every change we did 0471 --mIgnoreNext; 0472 return; 0473 } 0474 0475 // qCDebug(KEDITBOOKMARKS_LOG) << " setRoot!"; 0476 q->setRoot(q->bookmarkManager()->root()); 0477 0478 mCommandHistory->clearHistory(); 0479 } 0480 0481 void KBookmarkModel::notifyManagers(const KBookmarkGroup &grp) 0482 { 0483 ++d->mIgnoreNext; 0484 // qCDebug(KEDITBOOKMARKS_LOG) << "notifyManagers -> mIgnoreNext=" << d->mIgnoreNext; 0485 bookmarkManager()->emitChanged(grp); 0486 } 0487 0488 #include "moc_model.cpp"