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
)
201 mNewValue
= u
.url( KUrl::LeaveTrailingSlash
);
204 mNewValue
= newValue
;
207 QString
EditCommand::name() const
210 return i18n("%1 Change", i18n("Icon"));
212 return i18n("%1 Change", i18n("Title") );
214 return i18n("%1 Change", i18n("URL"));
216 return i18n("%1 Change", i18n("Comment"));
221 void EditCommand::execute()
223 KBookmark bk
= CurrentMgr::bookmarkAt(mAddress
);
226 mOldValue
= bk
.internalElement().attribute("toolbar");
227 bk
.internalElement().setAttribute("toolbar", mNewValue
);
231 mOldValue
= bk
.icon();
232 bk
.setIcon(mNewValue
);
236 mOldValue
= bk
.fullText();
237 bk
.setFullText(mNewValue
);
241 mOldValue
= bk
.url().prettyUrl();
242 bk
.setUrl(KUrl(mNewValue
));
246 mOldValue
= getNodeText(bk
, QStringList()<<"desc");
247 setNodeText(bk
, QStringList()<<"desc", mNewValue
);
249 CurrentMgr::self()->model()->emitDataChanged(bk
);
252 void EditCommand::unexecute()
255 KBookmark bk
= CurrentMgr::bookmarkAt(mAddress
);
258 bk
.internalElement().setAttribute("toolbar", mOldValue
);
262 bk
.setIcon(mOldValue
);
266 bk
.setFullText(mOldValue
);
270 bk
.setUrl(KUrl(mOldValue
));
274 setNodeText(bk
, QStringList()<<"desc", mOldValue
);
276 CurrentMgr::self()->model()->emitDataChanged(bk
);
279 void EditCommand::modify(const QString
&newValue
)
284 mNewValue
= u
.url( KUrl::LeaveTrailingSlash
);
287 mNewValue
= newValue
;
290 QString
EditCommand::getNodeText(const KBookmark
& bk
, const QStringList
&nodehier
)
292 QDomNode subnode
= bk
.internalElement();
293 for (QStringList::ConstIterator it
= nodehier
.begin();
294 it
!= nodehier
.end(); ++it
)
296 subnode
= subnode
.namedItem((*it
));
297 if (subnode
.isNull())
300 return (subnode
.firstChild().isNull())
302 : subnode
.firstChild().toText().data();
305 QString
EditCommand::setNodeText(const KBookmark
& bk
, const QStringList
&nodehier
,
306 const QString
& newValue
)
308 QDomNode subnode
= bk
.internalElement();
309 for (QStringList::ConstIterator it
= nodehier
.begin();
310 it
!= nodehier
.end(); ++it
)
312 QDomNode parent
= subnode
;
313 subnode
= subnode
.namedItem((*it
));
314 if (subnode
.isNull()) {
315 subnode
= bk
.internalElement().ownerDocument().createElement((*it
));
316 parent
.appendChild(subnode
);
320 if (subnode
.firstChild().isNull()) {
321 QDomText domtext
= subnode
.ownerDocument().createTextNode("");
322 subnode
.appendChild(domtext
);
325 QDomText domtext
= subnode
.firstChild().toText();
327 QString oldText
= domtext
.data();
328 domtext
.setData(newValue
);
332 /* -------------------------------------- */
334 void DeleteCommand::execute() {
335 KBookmark bk
= CurrentMgr::bookmarkAt(m_from
);
336 Q_ASSERT(!bk
.isNull());
339 QDomElement groupRoot
= bk
.internalElement();
341 QDomNode n
= groupRoot
.firstChild();
342 while (!n
.isNull()) {
343 QDomElement e
= n
.toElement();
345 // kDebug() << e.tagName();
347 QDomNode next
= n
.nextSibling();
348 groupRoot
.removeChild(n
);
354 // TODO - bug - unparsed xml is lost after undo,
355 // we must store it all therefore
357 //FIXME this removes the comments, that's bad!
360 m_cmd
= new CreateCommand(
361 m_from
, bk
.fullText(), bk
.icon(),
362 bk
.internalElement().attribute("folded") == "no");
363 m_subCmd
= deleteAll(bk
.toGroup());
367 m_cmd
= (bk
.isSeparator())
368 ? new CreateCommand(m_from
)
369 : new CreateCommand(m_from
, bk
.fullText(),
370 bk
.icon(), bk
.url());
376 void DeleteCommand::unexecute() {
377 // kDebug() << "DeleteCommand::unexecute " << m_from;
380 // TODO - recover saved metadata
387 m_subCmd
->unexecute();
391 QString
DeleteCommand::affectedBookmarks() const
393 return KBookmark::parentAddress(m_from
);
396 KEBMacroCommand
* DeleteCommand::deleteAll(const KBookmarkGroup
& parentGroup
) {
397 KEBMacroCommand
*cmd
= new KEBMacroCommand(QString());
398 QStringList lstToDelete
;
399 // we need to delete from the end, to avoid index shifting
400 for (KBookmark bk
= parentGroup
.first();
401 !bk
.isNull(); bk
= parentGroup
.next(bk
))
402 lstToDelete
.prepend(bk
.address());
403 for (QStringList::Iterator it
= lstToDelete
.begin();
404 it
!= lstToDelete
.end(); ++it
)
405 cmd
->addCommand(new DeleteCommand((*it
)));
409 /* -------------------------------------- */
411 QString
MoveCommand::name() const {
412 return i18n("Move %1", m_mytext
);
415 void MoveCommand::execute() {
416 // kDebug() << "MoveCommand::execute moving from=" << m_from
417 // << " to=" << m_to << endl;
420 KBookmark fromBk
= CurrentMgr::self()->mgr()->findByAddress( m_from
);
422 m_cc
= new CreateCommand(m_to
, fromBk
, "");
425 m_dc
= new DeleteCommand( fromBk
.address() );
429 QString
MoveCommand::finalAddress() const {
430 Q_ASSERT( !m_to
.isEmpty() );
434 void MoveCommand::unexecute() {
440 QString
MoveCommand::affectedBookmarks() const
442 return KBookmark::commonParent(KBookmark::parentAddress(m_from
), KBookmark::parentAddress(m_to
));
445 /* -------------------------------------- */
449 SortItem(const KBookmark
& bk
) : m_bk(bk
) {}
451 bool operator == (const SortItem
& s
) {
452 return (m_bk
.internalElement() == s
.m_bk
.internalElement()); }
454 bool isNull() const {
455 return m_bk
.isNull(); }
457 SortItem
previousSibling() const {
458 return m_bk
.parentGroup().previous(m_bk
); }
460 SortItem
nextSibling() const {
461 return m_bk
.parentGroup().next(m_bk
); }
463 const KBookmark
& bookmark() const {
472 static QString
key(const SortItem
&item
) {
473 return (item
.bookmark().isGroup() ? "a" : "b")
474 + (item
.bookmark().fullText().toLower());
478 /* -------------------------------------- */
480 void SortCommand::execute() {
481 if (commands().isEmpty()) {
482 KBookmarkGroup grp
= CurrentMgr::bookmarkAt(m_groupAddress
).toGroup();
483 Q_ASSERT(!grp
.isNull());
484 SortItem
firstChild(grp
.first());
485 // this will call moveAfter, which will add
486 // the subcommands for moving the items
487 kInsertionSort
<SortItem
, SortByName
, QString
, SortCommand
>
488 (firstChild
, (*this));
491 // don't execute for second time on addCommand(cmd)
492 KEBMacroCommand::execute();
496 void SortCommand::moveAfter(const SortItem
&moveMe
,
497 const SortItem
&afterMe
) {
498 QString destAddress
=
500 // move as first child
501 ? KBookmark::parentAddress(moveMe
.bookmark().address()) + "/0"
502 // move after "afterMe"
503 : KBookmark::nextAddress(afterMe
.bookmark().address());
505 MoveCommand
*cmd
= new MoveCommand(moveMe
.bookmark().address(),
508 this->addCommand(cmd
);
511 void SortCommand::unexecute() {
512 KEBMacroCommand::unexecute();
515 QString
SortCommand::affectedBookmarks() const
517 return m_groupAddress
;
520 /* -------------------------------------- */
522 KEBMacroCommand
* CmdGen::setAsToolbar(const KBookmark
&bk
)
524 KEBMacroCommand
*mcmd
= new KEBMacroCommand(i18n("Set as Bookmark Toolbar"));
526 KBookmarkGroup oldToolbar
= CurrentMgr::self()->mgr()->toolbar();
527 if (!oldToolbar
.isNull())
529 mcmd
->addCommand( new EditCommand(oldToolbar
.address(), -2, "no")); //toolbar
530 mcmd
->addCommand( new EditCommand(oldToolbar
.address(), -1, "")); //icon
533 mcmd
->addCommand( new EditCommand(bk
.address(), -2, "yes"));
534 mcmd
->addCommand( new EditCommand(bk
.address(), -1, "bookmark-toolbar"));
539 KEBMacroCommand
* CmdGen::insertMimeSource(const QString
&cmdName
, const QMimeData
*data
, const QString
&addr
)
541 KEBMacroCommand
*mcmd
= new KEBMacroCommand(cmdName
);
542 QString currentAddress
= addr
;
543 KBookmark::List bookmarks
= KBookmark::List::fromMimeData(data
);
544 KBookmark::List::const_iterator it
, end
;
545 end
= bookmarks
.constEnd();
547 for (it
= bookmarks
.constBegin(); it
!= end
; ++it
)
549 CreateCommand
*cmd
= new CreateCommand(currentAddress
, (*it
));
551 mcmd
->addCommand(cmd
);
552 currentAddress
= KBookmark::nextAddress(currentAddress
);
558 //FIXME copy=true needed? what is the difference with insertMimeSource
559 KEBMacroCommand
* CmdGen::itemsMoved(const QList
<KBookmark
> & items
,
560 const QString
&newAddress
, bool copy
) {
561 KEBMacroCommand
*mcmd
= new KEBMacroCommand(copy
? i18n("Copy Items")
562 : i18n("Move Items"));
564 QList
<KBookmark
>::const_iterator it
, end
;
568 QString bkInsertAddr
= newAddress
;
569 for (; it
!= end
; ++it
) {
572 cmd
= new CreateCommand(
574 KBookmark((*it
).internalElement()
575 .cloneNode(true).toElement()),
579 mcmd
->addCommand(cmd
);
581 bkInsertAddr
= cmd
->finalAddress();
583 } else /* if (move) */ {
584 QString oldAddress
= (*it
).address();
585 if (bkInsertAddr
.startsWith(oldAddress
))
586 continue; // trying to insert a parent into one of its childs, ignore :)
588 MoveCommand
*cmd
= new MoveCommand(oldAddress
, bkInsertAddr
,
591 mcmd
->addCommand(cmd
);
593 bkInsertAddr
= cmd
->finalAddress();
596 bkInsertAddr
= KBookmark::nextAddress(bkInsertAddr
);