File indexing completed on 2024-04-21 05:50:19
0001 // -*- indent-tabs-mode:nil -*- 0002 // vim: set ts=4 sts=4 sw=4 et: 0003 /* This file is part of the KDE project 0004 Copyright (C) 2000 David Faure <faure@kde.org> 0005 Copyright (C) 2002-2003 Alexander Kellett <lypanov@kde.org> 0006 0007 This program is free software; you can redistribute it and/or 0008 modify it under the terms of the GNU General Public 0009 License version 2 or at your option version 3 as published by 0010 the Free Software Foundation. 0011 0012 This program is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 General Public License for more details. 0016 0017 You should have received a copy of the GNU General Public License 0018 along with this program; see the file COPYING. If not, write to 0019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0020 Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "commands.h" 0024 #include "commands_p.h" 0025 #include "kinsertionsort_p.h" 0026 #include "model.h" 0027 0028 #include "keditbookmarks_debug.h" 0029 #include <KBookmarkManager> 0030 #include <KLocalizedString> 0031 0032 QString KEBMacroCommand::affectedBookmarks() const 0033 { 0034 const int commandCount = childCount(); 0035 if (commandCount == 0) { 0036 return QString(); 0037 } 0038 // Need to use dynamic_cast here due to going cross-hierarchy, but it should never return 0. 0039 int i = 0; 0040 QString affectBook = dynamic_cast<const IKEBCommand *>(child(i))->affectedBookmarks(); 0041 ++i; 0042 for (; i < commandCount; ++i) { 0043 affectBook = KBookmark::commonParent(affectBook, dynamic_cast<const IKEBCommand *>(child(i))->affectedBookmarks()); 0044 } 0045 return affectBook; 0046 } 0047 0048 DeleteManyCommand::DeleteManyCommand(KBookmarkModel *model, const QString &name, const QList<KBookmark> &bookmarks) 0049 : KEBMacroCommand(name) 0050 { 0051 QList<KBookmark>::const_iterator it, begin; 0052 begin = bookmarks.constBegin(); 0053 it = bookmarks.constEnd(); 0054 while (begin != it) { 0055 --it; 0056 new DeleteCommand(model, (*it).address(), false, this); 0057 } 0058 } 0059 0060 //// 0061 0062 CreateCommand::CreateCommand(KBookmarkModel *model, const QString &address, QUndoCommand *parent) 0063 : QUndoCommand(parent) 0064 , m_model(model) 0065 , m_to(address) 0066 , m_group(false) 0067 , m_separator(true) 0068 , m_originalBookmark(QDomElement()) 0069 { 0070 setText(i18nc("(qtundo-format)", "Insert Separator")); 0071 } 0072 0073 CreateCommand::CreateCommand(KBookmarkModel *model, const QString &address, const QString &text, const QString &iconPath, const QUrl &url, QUndoCommand *parent) 0074 : QUndoCommand(parent) 0075 , m_model(model) 0076 , m_to(address) 0077 , m_text(text) 0078 , m_iconPath(iconPath) 0079 , m_url(url) 0080 , m_group(false) 0081 , m_separator(false) 0082 , m_originalBookmark(QDomElement()) 0083 { 0084 setText(i18nc("(qtundo-format)", "Create Bookmark")); 0085 } 0086 0087 CreateCommand::CreateCommand(KBookmarkModel *model, const QString &address, const QString &text, const QString &iconPath, bool open, QUndoCommand *parent) 0088 : QUndoCommand(parent) 0089 , m_model(model) 0090 , m_to(address) 0091 , m_text(text) 0092 , m_iconPath(iconPath) 0093 , m_group(true) 0094 , m_separator(false) 0095 , m_open(open) 0096 , m_originalBookmark(QDomElement()) 0097 { 0098 setText(i18nc("(qtundo-format)", "Create Folder")); 0099 } 0100 0101 CreateCommand::CreateCommand(KBookmarkModel *model, const QString &address, const KBookmark &original, const QString &name, QUndoCommand *parent) 0102 : QUndoCommand(parent) 0103 , m_model(model) 0104 , m_to(address) 0105 , m_group(false) 0106 , m_separator(false) 0107 , m_open(false) 0108 , m_originalBookmark(original) 0109 , m_originalBookmarkDocRef(m_originalBookmark.internalElement().ownerDocument()) 0110 { 0111 setText(i18nc("(qtundo-format)", "Copy %1", name)); 0112 } 0113 0114 void CreateCommand::redo() 0115 { 0116 QString parentAddress = KBookmark::parentAddress(m_to); 0117 KBookmarkGroup parentGroup = m_model->bookmarkManager()->findByAddress(parentAddress).toGroup(); 0118 0119 QString previousSibling = KBookmark::previousAddress(m_to); 0120 0121 // qCDebug(KEDITBOOKMARKS_LOG) << "previousSibling=" << previousSibling; 0122 KBookmark prev = (previousSibling.isEmpty()) ? KBookmark(QDomElement()) : m_model->bookmarkManager()->findByAddress(previousSibling); 0123 0124 KBookmark bk = KBookmark(QDomElement()); 0125 const int pos = KBookmark::positionInParent(m_to); 0126 m_model->beginInsert(parentGroup, pos, pos); 0127 0128 if (m_separator) { 0129 bk = parentGroup.createNewSeparator(); 0130 0131 } else if (m_group) { 0132 Q_ASSERT(!m_text.isEmpty()); 0133 bk = parentGroup.createNewFolder(m_text); 0134 bk.internalElement().setAttribute(QStringLiteral("folded"), (m_open ? QLatin1String("no") : QLatin1String("yes"))); 0135 if (!m_iconPath.isEmpty()) { 0136 bk.setIcon(m_iconPath); 0137 } 0138 } else if (!m_originalBookmark.isNull()) { 0139 QDomElement element = m_originalBookmark.internalElement().cloneNode().toElement(); 0140 bk = KBookmark(element); 0141 parentGroup.addBookmark(bk); 0142 } else { 0143 bk = parentGroup.addBookmark(m_text, m_url, m_iconPath); 0144 } 0145 0146 // move to right position 0147 bool ok = parentGroup.moveBookmark(bk, prev); 0148 Q_UNUSED(ok); 0149 // TODO (requires KBookmarks >= 5.25) 0150 // Q_ASSERT(ok); 0151 if (!(text().isEmpty()) && !parentAddress.isEmpty()) { 0152 // open the parent (useful if it was empty) - only for manual commands 0153 Q_ASSERT(parentGroup.internalElement().tagName() != QLatin1String("xbel")); 0154 parentGroup.internalElement().setAttribute(QStringLiteral("folded"), QStringLiteral("no")); 0155 } 0156 0157 Q_ASSERT(bk.address() == m_to); 0158 m_model->endInsert(); 0159 } 0160 0161 QString CreateCommand::finalAddress() const 0162 { 0163 Q_ASSERT(!m_to.isEmpty()); 0164 return m_to; 0165 } 0166 0167 void CreateCommand::undo() 0168 { 0169 KBookmark bk = m_model->bookmarkManager()->findByAddress(m_to); 0170 Q_ASSERT(!bk.isNull() && !bk.parentGroup().isNull()); 0171 0172 m_model->removeBookmark(bk); 0173 } 0174 0175 QString CreateCommand::affectedBookmarks() const 0176 { 0177 return KBookmark::parentAddress(m_to); 0178 } 0179 0180 /* -------------------------------------- */ 0181 0182 EditCommand::EditCommand(KBookmarkModel *model, const QString &address, int col, const QString &newValue, QUndoCommand *parent) 0183 : QUndoCommand(parent) 0184 , m_model(model) 0185 , mAddress(address) 0186 , mCol(col) 0187 { 0188 qCDebug(KEDITBOOKMARKS_LOG) << address << col << newValue; 0189 if (mCol == 1) { 0190 const QUrl u(newValue); 0191 if (!(u.isEmpty() && !newValue.isEmpty())) // prevent emptied line if the currently entered url is invalid 0192 mNewValue = u.toString(); 0193 else 0194 mNewValue = newValue; 0195 } else 0196 mNewValue = newValue; 0197 0198 // -2 is "toolbar" attribute change, but that's only used internally. 0199 if (mCol == -1) 0200 setText(i18nc("(qtundo-format)", "Icon Change")); 0201 else if (mCol == 0) 0202 setText(i18nc("(qtundo-format)", "Title Change")); 0203 else if (mCol == 1) 0204 setText(i18nc("(qtundo-format)", "URL Change")); 0205 else if (mCol == 2) 0206 setText(i18nc("(qtundo-format)", "Comment Change")); 0207 } 0208 0209 void EditCommand::redo() 0210 { 0211 KBookmark bk = m_model->bookmarkManager()->findByAddress(mAddress); 0212 if (mCol == -2) { 0213 if (mOldValue.isEmpty()) 0214 mOldValue = bk.internalElement().attribute(QStringLiteral("toolbar")); 0215 bk.internalElement().setAttribute(QStringLiteral("toolbar"), mNewValue); 0216 } else if (mCol == -1) { 0217 if (mOldValue.isEmpty()) 0218 mOldValue = bk.icon(); 0219 bk.setIcon(mNewValue); 0220 } else if (mCol == 0) { 0221 if (mOldValue.isEmpty()) // only the first time, not when compressing changes in modify() 0222 mOldValue = bk.fullText(); 0223 qCDebug(KEDITBOOKMARKS_LOG) << "mOldValue=" << mOldValue; 0224 bk.setFullText(mNewValue); 0225 } else if (mCol == 1) { 0226 if (mOldValue.isEmpty()) 0227 mOldValue = bk.url().toDisplayString(); 0228 const QUrl newUrl(mNewValue); 0229 if (!(newUrl.isEmpty() && !mNewValue.isEmpty())) // prevent emptied line if the currently entered url is invalid 0230 bk.setUrl(newUrl); 0231 } else if (mCol == 2) { 0232 if (mOldValue.isEmpty()) 0233 mOldValue = bk.description(); 0234 bk.setDescription(mNewValue); 0235 } 0236 m_model->emitDataChanged(bk); 0237 } 0238 0239 void EditCommand::undo() 0240 { 0241 qCDebug(KEDITBOOKMARKS_LOG) << "Setting old value" << mOldValue << "in bk" << mAddress << "col" << mCol; 0242 KBookmark bk = m_model->bookmarkManager()->findByAddress(mAddress); 0243 if (mCol == -2) { 0244 bk.internalElement().setAttribute(QStringLiteral("toolbar"), mOldValue); 0245 } else if (mCol == -1) { 0246 bk.setIcon(mOldValue); 0247 } else if (mCol == 0) { 0248 bk.setFullText(mOldValue); 0249 } else if (mCol == 1) { 0250 bk.setUrl(QUrl(mOldValue)); 0251 } else if (mCol == 2) { 0252 bk.setDescription(mOldValue); 0253 } 0254 m_model->emitDataChanged(bk); 0255 } 0256 0257 void EditCommand::modify(const QString &newValue) 0258 { 0259 if (mCol == 1) { 0260 const QUrl u(newValue); 0261 if (!(u.isEmpty() && !newValue.isEmpty())) // prevent emptied line if the currently entered url is invalid 0262 mNewValue = u.toString(); 0263 else 0264 mNewValue = newValue; 0265 } else 0266 mNewValue = newValue; 0267 } 0268 0269 /* -------------------------------------- */ 0270 0271 DeleteCommand::DeleteCommand(KBookmarkModel *model, const QString &from, bool contentOnly, QUndoCommand *parent) 0272 : QUndoCommand(parent) 0273 , m_model(model) 0274 , m_from(from) 0275 , m_cmd(nullptr) 0276 , m_subCmd(nullptr) 0277 , m_contentOnly(contentOnly) 0278 { 0279 // NOTE - DeleteCommand needs no text, it is always embedded in a macrocommand 0280 } 0281 0282 void DeleteCommand::redo() 0283 { 0284 KBookmark bk = m_model->bookmarkManager()->findByAddress(m_from); 0285 Q_ASSERT(!bk.isNull()); 0286 0287 if (m_contentOnly) { 0288 QDomElement groupRoot = bk.internalElement(); 0289 0290 QDomNode n = groupRoot.firstChild(); 0291 while (!n.isNull()) { 0292 QDomElement e = n.toElement(); 0293 if (!e.isNull()) { 0294 // qCDebug(KEDITBOOKMARKS_LOG) << e.tagName(); 0295 } 0296 QDomNode next = n.nextSibling(); 0297 groupRoot.removeChild(n); 0298 n = next; 0299 } 0300 return; 0301 } 0302 0303 // TODO - bug - unparsed xml is lost after undo, 0304 // we must store it all therefore 0305 0306 // FIXME this removes the comments, that's bad! 0307 if (!m_cmd) { 0308 if (bk.isGroup()) { 0309 m_cmd = 0310 new CreateCommand(m_model, m_from, bk.fullText(), bk.icon(), bk.internalElement().attribute(QStringLiteral("folded")) == QLatin1String("no")); 0311 m_subCmd = deleteAll(m_model, bk.toGroup()); 0312 m_subCmd->redo(); 0313 0314 } else { 0315 m_cmd = (bk.isSeparator()) ? new CreateCommand(m_model, m_from) : new CreateCommand(m_model, m_from, bk.fullText(), bk.icon(), bk.url()); 0316 } 0317 } 0318 m_cmd->undo(); 0319 } 0320 0321 void DeleteCommand::undo() 0322 { 0323 // qCDebug(KEDITBOOKMARKS_LOG) << "DeleteCommand::undo " << m_from; 0324 0325 if (m_contentOnly) { 0326 // TODO - recover saved metadata 0327 return; 0328 } 0329 0330 m_cmd->redo(); 0331 0332 if (m_subCmd) { 0333 m_subCmd->undo(); 0334 } 0335 } 0336 0337 QString DeleteCommand::affectedBookmarks() const 0338 { 0339 return KBookmark::parentAddress(m_from); 0340 } 0341 0342 KEBMacroCommand *DeleteCommand::deleteAll(KBookmarkModel *model, const KBookmarkGroup &parentGroup) 0343 { 0344 KEBMacroCommand *cmd = new KEBMacroCommand(QString()); 0345 QStringList lstToDelete; 0346 // we need to delete from the end, to avoid index shifting 0347 for (KBookmark bk = parentGroup.first(); !bk.isNull(); bk = parentGroup.next(bk)) 0348 lstToDelete.prepend(bk.address()); 0349 for (QStringList::const_iterator it = lstToDelete.constBegin(); it != lstToDelete.constEnd(); ++it) { 0350 new DeleteCommand(model, (*it), false, cmd); 0351 } 0352 return cmd; 0353 } 0354 0355 /* -------------------------------------- */ 0356 0357 MoveCommand::MoveCommand(KBookmarkModel *model, const QString &from, const QString &to, const QString &name, QUndoCommand *parent) 0358 : QUndoCommand(parent) 0359 , m_model(model) 0360 , m_from(from) 0361 , m_to(to) 0362 , m_cc(nullptr) 0363 , m_dc(nullptr) 0364 { 0365 setText(i18nc("(qtundo-format)", "Move %1", name)); 0366 } 0367 0368 void MoveCommand::redo() 0369 { 0370 // qCDebug(KEDITBOOKMARKS_LOG) << "Moving from=" << m_from << "to=" << m_to; 0371 0372 KBookmark fromBk = m_model->bookmarkManager()->findByAddress(m_from); 0373 Q_ASSERT(fromBk.address() == m_from); 0374 0375 // qCDebug(KEDITBOOKMARKS_LOG) << " 1) creating" << m_to; 0376 m_cc = new CreateCommand(m_model, m_to, fromBk, QString()); 0377 m_cc->redo(); 0378 0379 // qCDebug(KEDITBOOKMARKS_LOG) << " 2) deleting" << fromBk.address(); 0380 m_dc = new DeleteCommand(m_model, fromBk.address()); 0381 m_dc->redo(); 0382 } 0383 0384 QString MoveCommand::finalAddress() const 0385 { 0386 Q_ASSERT(!m_to.isEmpty()); 0387 return m_to; 0388 } 0389 0390 void MoveCommand::undo() 0391 { 0392 m_dc->undo(); 0393 m_cc->undo(); 0394 } 0395 0396 QString MoveCommand::affectedBookmarks() const 0397 { 0398 return KBookmark::commonParent(KBookmark::parentAddress(m_from), KBookmark::parentAddress(m_to)); 0399 } 0400 0401 /* -------------------------------------- */ 0402 0403 class SortItem 0404 { 0405 public: 0406 SortItem(const KBookmark &bk) 0407 : m_bk(bk) 0408 { 0409 } 0410 0411 bool operator==(const SortItem &s) 0412 { 0413 return (m_bk.internalElement() == s.m_bk.internalElement()); 0414 } 0415 0416 bool isNull() const 0417 { 0418 return m_bk.isNull(); 0419 } 0420 0421 SortItem previousSibling() const 0422 { 0423 return m_bk.parentGroup().previous(m_bk); 0424 } 0425 0426 SortItem nextSibling() const 0427 { 0428 return m_bk.parentGroup().next(m_bk); 0429 } 0430 0431 const KBookmark &bookmark() const 0432 { 0433 return m_bk; 0434 } 0435 0436 private: 0437 KBookmark m_bk; 0438 }; 0439 0440 class SortByName 0441 { 0442 public: 0443 static QString key(const SortItem &item) 0444 { 0445 return (item.bookmark().isGroup() ? QStringLiteral("a") : QStringLiteral("b")) + (item.bookmark().fullText().toLower()); 0446 } 0447 }; 0448 0449 /* -------------------------------------- */ 0450 0451 SortCommand::SortCommand(KBookmarkModel *model, const QString &name, const QString &groupAddress, QUndoCommand *parent) 0452 : KEBMacroCommand(name, parent) 0453 , m_model(model) 0454 , m_groupAddress(groupAddress) 0455 { 0456 } 0457 0458 void SortCommand::redo() 0459 { 0460 if (childCount() == 0) { 0461 KBookmarkGroup grp = m_model->bookmarkManager()->findByAddress(m_groupAddress).toGroup(); 0462 Q_ASSERT(!grp.isNull()); 0463 SortItem firstChild(grp.first()); 0464 // this will call moveAfter, which will add 0465 // the subcommands for moving the items 0466 kInsertionSort<SortItem, SortByName, QString, SortCommand>(firstChild, (*this)); 0467 0468 } else { 0469 // don't redo for second time on addCommand(cmd) 0470 KEBMacroCommand::redo(); 0471 } 0472 } 0473 0474 void SortCommand::moveAfter(const SortItem &moveMe, const SortItem &afterMe) 0475 { 0476 const QString destAddress = afterMe.isNull() 0477 // move as first child 0478 ? KBookmark::parentAddress(moveMe.bookmark().address()) + QStringLiteral("/0") 0479 // move after "afterMe" 0480 : KBookmark::nextAddress(afterMe.bookmark().address()); 0481 0482 MoveCommand *cmd = new MoveCommand(m_model, moveMe.bookmark().address(), destAddress, QString(), this); 0483 cmd->redo(); 0484 } 0485 0486 void SortCommand::undo() 0487 { 0488 KEBMacroCommand::undo(); 0489 } 0490 0491 QString SortCommand::affectedBookmarks() const 0492 { 0493 return m_groupAddress; 0494 } 0495 0496 /* -------------------------------------- */ 0497 0498 KEBMacroCommand *CmdGen::setAsToolbar(KBookmarkModel *model, const KBookmark &bk) 0499 { 0500 KEBMacroCommand *mcmd = new KEBMacroCommand(i18nc("(qtundo-format)", "Set as Bookmark Toolbar")); 0501 0502 KBookmarkGroup oldToolbar = model->bookmarkManager()->toolbar(); 0503 if (!oldToolbar.isNull()) { 0504 new EditCommand(model, oldToolbar.address(), -2, QStringLiteral("no"), mcmd); // toolbar 0505 new EditCommand(model, oldToolbar.address(), -1, QLatin1String(""), mcmd); // icon 0506 } 0507 0508 new EditCommand(model, bk.address(), -2, QStringLiteral("yes"), mcmd); 0509 new EditCommand(model, bk.address(), -1, QStringLiteral("bookmark-toolbar"), mcmd); 0510 0511 return mcmd; 0512 } 0513 0514 KEBMacroCommand *CmdGen::insertMimeSource(KBookmarkModel *model, const QString &cmdName, const QMimeData *data, const QString &addr) 0515 { 0516 KEBMacroCommand *mcmd = new KEBMacroCommand(cmdName); 0517 QString currentAddress = addr; 0518 QDomDocument doc; 0519 const auto bookmarks = KBookmark::List::fromMimeData(data, doc); 0520 for (const KBookmark &bk : bookmarks) { 0521 new CreateCommand(model, currentAddress, bk, QString(), mcmd); 0522 currentAddress = KBookmark::nextAddress(currentAddress); 0523 } 0524 return mcmd; 0525 } 0526 0527 KEBMacroCommand *CmdGen::itemsMoved(KBookmarkModel *model, const QList<KBookmark> &items, const QString &newAddress, bool copy) 0528 { 0529 Q_ASSERT(!copy); // always called for a move, never for a copy (that's what insertMimeSource is about) 0530 Q_UNUSED(copy); // TODO: remove 0531 0532 KEBMacroCommand *mcmd = new KEBMacroCommand(copy ? i18nc("(qtundo-format)", "Copy Items") : i18nc("(qtundo-format)", "Move Items")); 0533 QString bkInsertAddr = newAddress; 0534 for (const KBookmark &bk : items) { 0535 new CreateCommand(model, bkInsertAddr, KBookmark(bk.internalElement().cloneNode(true).toElement()), bk.text(), mcmd); 0536 bkInsertAddr = KBookmark::nextAddress(bkInsertAddr); 0537 } 0538 0539 // Do the copying, and get the updated addresses of the bookmarks to remove. 0540 mcmd->redo(); 0541 QStringList addresses; 0542 for (const KBookmark &bk : items) { 0543 addresses.append(bk.address()); 0544 } 0545 mcmd->undo(); 0546 0547 for (const auto &address : std::as_const(addresses)) { 0548 new DeleteCommand(model, address, false, mcmd); 0549 } 0550 0551 return mcmd; 0552 }