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.
24 #include "bookmarkmodel.h"
26 #include "kinsertionsort.h"
34 #include <kbookmarkmanager.h>
35 #include <kdesktopfile.h>
37 #include "treeitem_p.h" // TODO REMOVEME
39 class KBookmarkModelInsertSentry
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());
52 ~KBookmarkModelInsertSentry()
54 mt
->insertChildren(mf
, ml
);
55 CurrentMgr::self()->model()->endInsertRows();
61 class KBookmarkModelRemoveSentry
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());
74 ~KBookmarkModelRemoveSentry()
76 mt
->deleteChildren(mf
, ml
);
77 CurrentMgr::self()->model()->endRemoveRows();
84 QString
KEBMacroCommand::affectedBookmarks() const
86 const QList
<K3Command
*> commandList
= commands();
87 QList
<K3Command
*>::const_iterator it
= commandList
.constBegin();
88 if ( it
== commandList
.constEnd() )
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();
93 for( ; it
!= commandList
.constEnd() ; ++it
)
95 affectBook
= KBookmark::commonParent( affectBook
, dynamic_cast<IKEBCommand
*>(*it
)->affectedBookmarks());
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();
109 DeleteCommand
* dcmd
= new DeleteCommand( (*it
).address() );
115 QString
CreateCommand::name() const {
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
);
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
));
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
);
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() );
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
)
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
);
204 mNewValue
= newValue
;
207 mNewValue
= newValue
;
210 QString
EditCommand::name() const
213 return i18n("%1 Change", i18n("Icon"));
215 return i18n("%1 Change", i18n("Title") );
217 return i18n("%1 Change", i18n("URL"));
219 return i18n("%1 Change", i18n("Comment"));
224 void EditCommand::execute()
226 KBookmark bk
= CurrentMgr::bookmarkAt(mAddress
);
229 mOldValue
= bk
.internalElement().attribute("toolbar");
230 bk
.internalElement().setAttribute("toolbar", mNewValue
);
234 mOldValue
= bk
.icon();
235 bk
.setIcon(mNewValue
);
239 mOldValue
= bk
.fullText();
240 bk
.setFullText(mNewValue
);
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
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
);
263 bk
.internalElement().setAttribute("toolbar", mOldValue
);
267 bk
.setIcon(mOldValue
);
271 bk
.setFullText(mOldValue
);
275 bk
.setUrl(KUrl(mOldValue
));
279 setNodeText(bk
, QStringList()<<"desc", mOldValue
);
281 CurrentMgr::self()->model()->emitDataChanged(bk
);
284 void EditCommand::modify(const QString
&newValue
)
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
);
292 mNewValue
= newValue
;
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())
308 return (subnode
.firstChild().isNull())
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
);
340 /* -------------------------------------- */
342 void DeleteCommand::execute() {
343 KBookmark bk
= CurrentMgr::bookmarkAt(m_from
);
344 Q_ASSERT(!bk
.isNull());
347 QDomElement groupRoot
= bk
.internalElement();
349 QDomNode n
= groupRoot
.firstChild();
350 while (!n
.isNull()) {
351 QDomElement e
= n
.toElement();
353 // kDebug() << e.tagName();
355 QDomNode next
= n
.nextSibling();
356 groupRoot
.removeChild(n
);
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!
368 m_cmd
= new CreateCommand(
369 m_from
, bk
.fullText(), bk
.icon(),
370 bk
.internalElement().attribute("folded") == "no");
371 m_subCmd
= deleteAll(bk
.toGroup());
375 m_cmd
= (bk
.isSeparator())
376 ? new CreateCommand(m_from
)
377 : new CreateCommand(m_from
, bk
.fullText(),
378 bk
.icon(), bk
.url());
384 void DeleteCommand::unexecute() {
385 // kDebug() << "DeleteCommand::unexecute " << m_from;
388 // TODO - recover saved metadata
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
)));
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
, "");
433 m_dc
= new DeleteCommand( fromBk
.address() );
437 QString
MoveCommand::finalAddress() const {
438 Q_ASSERT( !m_to
.isEmpty() );
442 void MoveCommand::unexecute() {
448 QString
MoveCommand::affectedBookmarks() const
450 return KBookmark::commonParent(KBookmark::parentAddress(m_from
), KBookmark::parentAddress(m_to
));
453 /* -------------------------------------- */
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 {
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));
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
=
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(),
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"));
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
));
559 mcmd
->addCommand(cmd
);
560 currentAddress
= KBookmark::nextAddress(currentAddress
);
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
) {
580 cmd
= new CreateCommand(
582 KBookmark((*it
).internalElement()
583 .cloneNode(true).toElement()),
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
,
599 mcmd
->addCommand(cmd
);
601 bkInsertAddr
= cmd
->finalAddress();
604 bkInsertAddr
= KBookmark::nextAddress(bkInsertAddr
);