Fix crash if key bindings specified in profile cannot be found. Improve
[personal-kdebase.git] / apps / keditbookmarks / commands.cpp
blobda14f69f6a82cf60ade1bc48d1f3ef864e4b0846
1 // -*- indent-tabs-mode:nil -*-
2 // vim: set ts=4 sts=4 sw=4 et:
3 /* This file is part of the KDE project
4 Copyright (C) 2000 David Faure <faure@kde.org>
5 Copyright (C) 2002-2003 Alexander Kellett <lypanov@kde.org>
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public
9 License version 2 or at your option version 3 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; see the file COPYING. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
23 #include "commands.h"
24 #include "bookmarkmodel.h"
26 #include "kinsertionsort.h"
28 #include "toplevel.h"
30 #include <assert.h>
32 #include <kdebug.h>
33 #include <klocale.h>
34 #include <kbookmarkmanager.h>
35 #include <kdesktopfile.h>
37 #include "treeitem_p.h" // TODO REMOVEME
39 class KBookmarkModelInsertSentry
41 public:
42 KBookmarkModelInsertSentry(const KBookmark& parent, int first, int last)
44 QModelIndex mParent = CurrentMgr::self()->model()->indexForBookmark(parent);
45 CurrentMgr::self()->model()->beginInsertRows( mParent, first, last);
47 // TODO REMOVEME use of internal class TreeItem!
48 mt = static_cast<TreeItem *>(mParent.internalPointer());
49 mf = first;
50 ml = last;
52 ~KBookmarkModelInsertSentry()
54 mt->insertChildren(mf, ml);
55 CurrentMgr::self()->model()->endInsertRows();
57 private:
58 TreeItem * mt;
59 int mf, ml;
61 class KBookmarkModelRemoveSentry
63 public:
64 KBookmarkModelRemoveSentry(const KBookmark& parent, int first, int last)
66 QModelIndex mParent = CurrentMgr::self()->model()->indexForBookmark(parent);
68 CurrentMgr::self()->model()->beginRemoveRows( mParent, first, last);
70 mt = static_cast<TreeItem *>(mParent.internalPointer());
71 mf = first;
72 ml = last;
74 ~KBookmarkModelRemoveSentry()
76 mt->deleteChildren(mf, ml);
77 CurrentMgr::self()->model()->endRemoveRows();
79 private:
80 TreeItem * mt;
81 int mf, ml;
84 QString KEBMacroCommand::affectedBookmarks() const
86 const QList<K3Command *> commandList = commands();
87 QList<K3Command*>::const_iterator it = commandList.constBegin();
88 if ( it == commandList.constEnd() )
89 return QString();
90 // Need to use dynamic_cast here due to going cross-hierarchy, but it should never return 0.
91 QString affectBook = dynamic_cast<IKEBCommand *>(*it)->affectedBookmarks();
92 ++it;
93 for( ; it != commandList.constEnd() ; ++it )
95 affectBook = KBookmark::commonParent( affectBook, dynamic_cast<IKEBCommand *>(*it)->affectedBookmarks());
97 return affectBook;
100 DeleteManyCommand::DeleteManyCommand(const QString &name, const QList<KBookmark> & bookmarks)
101 : KEBMacroCommand(name)
103 QList<KBookmark>::const_iterator it, begin;
104 begin = bookmarks.constBegin();
105 it = bookmarks.constEnd();
106 while(begin != it)
108 --it;
109 DeleteCommand * dcmd = new DeleteCommand( (*it).address() );
110 addCommand(dcmd);
115 QString CreateCommand::name() const {
116 if (m_separator) {
117 return i18n("Insert Separator");
118 } else if (m_group) {
119 return i18n("Create Folder");
120 } else if (!m_originalBookmark.isNull()) {
121 return i18n("Copy %1", m_mytext);
122 } else {
123 return i18n("Create Bookmark");
127 void CreateCommand::execute()
129 QString parentAddress = KBookmark::parentAddress(m_to);
130 KBookmarkGroup parentGroup =
131 CurrentMgr::bookmarkAt(parentAddress).toGroup();
133 QString previousSibling = KBookmark::previousAddress(m_to);
135 // kDebug() << "CreateCommand::execute previousSibling="
136 // << previousSibling << endl;
137 KBookmark prev = (previousSibling.isEmpty())
138 ? KBookmark(QDomElement())
139 : CurrentMgr::bookmarkAt(previousSibling);
141 KBookmark bk = KBookmark(QDomElement());
142 // TODO use m_to.positionInParent()
143 KBookmarkModelInsertSentry guard(parentGroup, KBookmark::positionInParent(m_to), KBookmark::positionInParent(m_to));
145 if (m_separator) {
146 bk = parentGroup.createNewSeparator();
148 } else if (m_group) {
149 Q_ASSERT(!m_text.isEmpty());
150 bk = parentGroup.createNewFolder(m_text);
151 bk.internalElement().setAttribute("folded", (m_open ? "no" : "yes"));
152 if (!m_iconPath.isEmpty()) {
153 bk.setIcon(m_iconPath);
155 } else if(!m_originalBookmark.isNull()) {
156 QDomElement element = m_originalBookmark.internalElement().cloneNode().toElement();
157 bk = KBookmark(element);
158 parentGroup.addBookmark(bk);
159 } else {
160 bk = parentGroup.addBookmark(m_text, m_url, m_iconPath);
163 // move to right position
164 parentGroup.moveBookmark(bk, prev);
165 if (!(name().isEmpty()) && !parentAddress.isEmpty() ) {
166 // open the parent (useful if it was empty) - only for manual commands
167 Q_ASSERT( parentGroup.internalElement().tagName() != "xbel" );
168 parentGroup.internalElement().setAttribute("folded", "no");
171 Q_ASSERT(bk.address() == m_to);
174 QString CreateCommand::finalAddress() const {
175 Q_ASSERT( !m_to.isEmpty() );
176 return m_to;
179 void CreateCommand::unexecute() {
180 KBookmark bk = CurrentMgr::bookmarkAt(m_to);
181 Q_ASSERT(!bk.isNull() && !bk.parentGroup().isNull());
183 // TODO use bk.positionInParent()
184 KBookmarkModelRemoveSentry(bk.parentGroup(), KBookmark::positionInParent(bk.address()), KBookmark::positionInParent(bk.address()));
185 bk.parentGroup().deleteBookmark(bk);
188 QString CreateCommand::affectedBookmarks() const
190 return KBookmark::parentAddress(m_to);
193 /* -------------------------------------- */
195 EditCommand::EditCommand(const QString & address, int col, const QString & newValue)
196 : K3Command(), mAddress(address), mCol(col)
198 if(mCol == 1)
200 const KUrl u(newValue);
201 if (!(u.isEmpty() && !newValue.isEmpty())) // prevent emptied line if the currently entered url is invalid
202 mNewValue = u.url(KUrl::LeaveTrailingSlash);
203 else
204 mNewValue = newValue;
206 else
207 mNewValue = newValue;
210 QString EditCommand::name() const
212 if(mCol==-1)
213 return i18n("%1 Change", i18n("Icon"));
214 else if(mCol==0)
215 return i18n("%1 Change", i18n("Title") );
216 else if(mCol==1)
217 return i18n("%1 Change", i18n("URL"));
218 else if(mCol==2)
219 return i18n("%1 Change", i18n("Comment"));
220 //Never reached
221 return QString("");
224 void EditCommand::execute()
226 KBookmark bk = CurrentMgr::bookmarkAt(mAddress);
227 if(mCol==-2)
229 mOldValue = bk.internalElement().attribute("toolbar");
230 bk.internalElement().setAttribute("toolbar", mNewValue);
232 else if(mCol==-1)
234 mOldValue = bk.icon();
235 bk.setIcon(mNewValue);
237 else if(mCol==0)
239 mOldValue = bk.fullText();
240 bk.setFullText(mNewValue);
242 else if(mCol==1)
244 mOldValue = bk.url().prettyUrl();
245 const KUrl newUrl(mNewValue);
246 if (!(newUrl.isEmpty() && !mNewValue.isEmpty())) // prevent emptied line if the currently entered url is invalid
247 bk.setUrl(newUrl);
249 else if(mCol==2)
251 mOldValue = getNodeText(bk, QStringList()<<"desc");
252 setNodeText(bk, QStringList()<<"desc", mNewValue);
254 CurrentMgr::self()->model()->emitDataChanged(bk);
257 void EditCommand::unexecute()
260 KBookmark bk = CurrentMgr::bookmarkAt(mAddress);
261 if(mCol==-2)
263 bk.internalElement().setAttribute("toolbar", mOldValue);
265 else if(mCol==-1)
267 bk.setIcon(mOldValue);
269 else if(mCol==0)
271 bk.setFullText(mOldValue);
273 else if(mCol==1)
275 bk.setUrl(KUrl(mOldValue));
277 else if(mCol==2)
279 setNodeText(bk, QStringList()<<"desc", mOldValue);
281 CurrentMgr::self()->model()->emitDataChanged(bk);
284 void EditCommand::modify(const QString &newValue)
286 if(mCol == 1)
288 const KUrl u(newValue);
289 if (!(u.isEmpty() && !newValue.isEmpty())) // prevent emptied line if the currently entered url is invalid
290 mNewValue = u.url(KUrl::LeaveTrailingSlash);
291 else
292 mNewValue = newValue;
294 else
295 mNewValue = newValue;
298 QString EditCommand::getNodeText(const KBookmark& bk, const QStringList &nodehier)
300 QDomNode subnode = bk.internalElement();
301 for (QStringList::ConstIterator it = nodehier.begin();
302 it != nodehier.end(); ++it)
304 subnode = subnode.namedItem((*it));
305 if (subnode.isNull())
306 return QString();
308 return (subnode.firstChild().isNull())
309 ? QString()
310 : subnode.firstChild().toText().data();
313 QString EditCommand::setNodeText(const KBookmark& bk, const QStringList &nodehier,
314 const QString& newValue)
316 QDomNode subnode = bk.internalElement();
317 for (QStringList::ConstIterator it = nodehier.begin();
318 it != nodehier.end(); ++it)
320 QDomNode parent = subnode;
321 subnode = subnode.namedItem((*it));
322 if (subnode.isNull()) {
323 subnode = bk.internalElement().ownerDocument().createElement((*it));
324 parent.appendChild(subnode);
328 if (subnode.firstChild().isNull()) {
329 QDomText domtext = subnode.ownerDocument().createTextNode("");
330 subnode.appendChild(domtext);
333 QDomText domtext = subnode.firstChild().toText();
335 QString oldText = domtext.data();
336 domtext.setData(newValue);
337 return oldText;
340 /* -------------------------------------- */
342 void DeleteCommand::execute() {
343 KBookmark bk = CurrentMgr::bookmarkAt(m_from);
344 Q_ASSERT(!bk.isNull());
346 if (m_contentOnly) {
347 QDomElement groupRoot = bk.internalElement();
349 QDomNode n = groupRoot.firstChild();
350 while (!n.isNull()) {
351 QDomElement e = n.toElement();
352 if (!e.isNull()) {
353 // kDebug() << e.tagName();
355 QDomNode next = n.nextSibling();
356 groupRoot.removeChild(n);
357 n = next;
359 return;
362 // TODO - bug - unparsed xml is lost after undo,
363 // we must store it all therefore
365 //FIXME this removes the comments, that's bad!
366 if (!m_cmd) {
367 if (bk.isGroup()) {
368 m_cmd = new CreateCommand(
369 m_from, bk.fullText(), bk.icon(),
370 bk.internalElement().attribute("folded") == "no");
371 m_subCmd = deleteAll(bk.toGroup());
372 m_subCmd->execute();
374 } else {
375 m_cmd = (bk.isSeparator())
376 ? new CreateCommand(m_from)
377 : new CreateCommand(m_from, bk.fullText(),
378 bk.icon(), bk.url());
381 m_cmd->unexecute();
384 void DeleteCommand::unexecute() {
385 // kDebug() << "DeleteCommand::unexecute " << m_from;
387 if (m_contentOnly) {
388 // TODO - recover saved metadata
389 return;
392 m_cmd->execute();
394 if (m_subCmd) {
395 m_subCmd->unexecute();
399 QString DeleteCommand::affectedBookmarks() const
401 return KBookmark::parentAddress(m_from);
404 KEBMacroCommand* DeleteCommand::deleteAll(const KBookmarkGroup & parentGroup) {
405 KEBMacroCommand *cmd = new KEBMacroCommand(QString());
406 QStringList lstToDelete;
407 // we need to delete from the end, to avoid index shifting
408 for (KBookmark bk = parentGroup.first();
409 !bk.isNull(); bk = parentGroup.next(bk))
410 lstToDelete.prepend(bk.address());
411 for (QStringList::const_iterator it = lstToDelete.constBegin();
412 it != lstToDelete.constEnd(); ++it)
413 cmd->addCommand(new DeleteCommand((*it)));
414 return cmd;
417 /* -------------------------------------- */
419 QString MoveCommand::name() const {
420 return i18n("Move %1", m_mytext);
423 void MoveCommand::execute() {
424 // kDebug() << "MoveCommand::execute moving from=" << m_from
425 // << " to=" << m_to << endl;
428 KBookmark fromBk = CurrentMgr::self()->mgr()->findByAddress( m_from );
430 m_cc = new CreateCommand(m_to, fromBk, "");
431 m_cc->execute();
433 m_dc = new DeleteCommand( fromBk.address() );
434 m_dc->execute();
437 QString MoveCommand::finalAddress() const {
438 Q_ASSERT( !m_to.isEmpty() );
439 return m_to;
442 void MoveCommand::unexecute() {
444 m_dc->unexecute();
445 m_cc->unexecute();
448 QString MoveCommand::affectedBookmarks() const
450 return KBookmark::commonParent(KBookmark::parentAddress(m_from), KBookmark::parentAddress(m_to));
453 /* -------------------------------------- */
455 class SortItem {
456 public:
457 SortItem(const KBookmark & bk) : m_bk(bk) {}
459 bool operator == (const SortItem & s) {
460 return (m_bk.internalElement() == s.m_bk.internalElement()); }
462 bool isNull() const {
463 return m_bk.isNull(); }
465 SortItem previousSibling() const {
466 return m_bk.parentGroup().previous(m_bk); }
468 SortItem nextSibling() const {
469 return m_bk.parentGroup().next(m_bk); }
471 const KBookmark& bookmark() const {
472 return m_bk; }
474 private:
475 KBookmark m_bk;
478 class SortByName {
479 public:
480 static QString key(const SortItem &item) {
481 return (item.bookmark().isGroup() ? "a" : "b")
482 + (item.bookmark().fullText().toLower());
486 /* -------------------------------------- */
488 void SortCommand::execute() {
489 if (commands().isEmpty()) {
490 KBookmarkGroup grp = CurrentMgr::bookmarkAt(m_groupAddress).toGroup();
491 Q_ASSERT(!grp.isNull());
492 SortItem firstChild(grp.first());
493 // this will call moveAfter, which will add
494 // the subcommands for moving the items
495 kInsertionSort<SortItem, SortByName, QString, SortCommand>
496 (firstChild, (*this));
498 } else {
499 // don't execute for second time on addCommand(cmd)
500 KEBMacroCommand::execute();
504 void SortCommand::moveAfter(const SortItem &moveMe,
505 const SortItem &afterMe) {
506 QString destAddress =
507 afterMe.isNull()
508 // move as first child
509 ? KBookmark::parentAddress(moveMe.bookmark().address()) + "/0"
510 // move after "afterMe"
511 : KBookmark::nextAddress(afterMe.bookmark().address());
513 MoveCommand *cmd = new MoveCommand(moveMe.bookmark().address(),
514 destAddress);
515 cmd->execute();
516 this->addCommand(cmd);
519 void SortCommand::unexecute() {
520 KEBMacroCommand::unexecute();
523 QString SortCommand::affectedBookmarks() const
525 return m_groupAddress;
528 /* -------------------------------------- */
530 KEBMacroCommand* CmdGen::setAsToolbar(const KBookmark &bk)
532 KEBMacroCommand *mcmd = new KEBMacroCommand(i18n("Set as Bookmark Toolbar"));
534 KBookmarkGroup oldToolbar = CurrentMgr::self()->mgr()->toolbar();
535 if (!oldToolbar.isNull())
537 mcmd->addCommand( new EditCommand(oldToolbar.address(), -2, "no")); //toolbar
538 mcmd->addCommand( new EditCommand(oldToolbar.address(), -1, "")); //icon
541 mcmd->addCommand( new EditCommand(bk.address(), -2, "yes"));
542 mcmd->addCommand( new EditCommand(bk.address(), -1, "bookmark-toolbar"));
544 return mcmd;
547 KEBMacroCommand* CmdGen::insertMimeSource(const QString &cmdName, const QMimeData *data, const QString &addr)
549 KEBMacroCommand *mcmd = new KEBMacroCommand(cmdName);
550 QString currentAddress = addr;
551 KBookmark::List bookmarks = KBookmark::List::fromMimeData(data);
552 KBookmark::List::const_iterator it, end;
553 end = bookmarks.constEnd();
555 for (it = bookmarks.constBegin(); it != end; ++it)
557 CreateCommand *cmd = new CreateCommand(currentAddress, (*it));
558 cmd->execute();
559 mcmd->addCommand(cmd);
560 currentAddress = KBookmark::nextAddress(currentAddress);
562 return mcmd;
566 //FIXME copy=true needed? what is the difference with insertMimeSource
567 KEBMacroCommand* CmdGen::itemsMoved(const QList<KBookmark> & items,
568 const QString &newAddress, bool copy) {
569 KEBMacroCommand *mcmd = new KEBMacroCommand(copy ? i18n("Copy Items")
570 : i18n("Move Items"));
572 QList<KBookmark>::const_iterator it, end;
573 it = items.constBegin();
574 end = items.constEnd();
576 QString bkInsertAddr = newAddress;
577 for (; it != end; ++it) {
578 if (copy) {
579 CreateCommand *cmd;
580 cmd = new CreateCommand(
581 bkInsertAddr,
582 KBookmark((*it).internalElement()
583 .cloneNode(true).toElement()),
584 (*it).text());
586 cmd->execute();
587 mcmd->addCommand(cmd);
589 bkInsertAddr = cmd->finalAddress();
591 } else /* if (move) */ {
592 QString oldAddress = (*it).address();
593 if (bkInsertAddr.startsWith(oldAddress))
594 continue; // trying to insert a parent into one of its children, ignore :)
596 MoveCommand *cmd = new MoveCommand(oldAddress, bkInsertAddr,
597 (*it).text());
598 cmd->execute();
599 mcmd->addCommand(cmd);
601 bkInsertAddr = cmd->finalAddress();
604 bkInsertAddr = KBookmark::nextAddress(bkInsertAddr);
607 return mcmd;