// -*- indent-tabs-mode:nil -*- // vim: set ts=4 sts=4 sw=4 et: /* This file is part of the KDE project Copyright (C) 2000 David Faure <faure@kde.org> Copyright (C) 2002-2003 Alexander Kellett <lypanov@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "commands.h" #include "kinsertionsort.h" #include "toplevel.h" #include "listview.h" #include <assert.h> #include <tqvaluevector.h> #include <kdebug.h> #include <tdelocale.h> #include <kbookmarkdrag.h> #include <kbookmarkmanager.h> #include <kurldrag.h> #include <kdesktopfile.h> TQString KEBMacroCommand::affectedBookmarks() const { TQPtrListIterator<KCommand> it(m_commands); TQString affectBook; if(it.current()) affectBook = dynamic_cast<IKEBCommand *>(it.current())->affectedBookmarks(); ++it; for ( ; it.current() ; ++it ) affectBook = KBookmark::commonParent( affectBook, dynamic_cast<IKEBCommand *>(it.current())->affectedBookmarks()); return affectBook; } TQString DeleteManyCommand::prevOrParentAddress(TQString addr) { TQString prev = KBookmark::previousAddress( addr ); if( CurrentMgr::bookmarkAt(prev).hasParent()) return prev; else return KBookmark::parentAddress( addr ); } TQString DeleteManyCommand::preOrderNextAddress(TQString addr) { TQString rootAdr = CurrentMgr::self()->mgr()->root().address(); while(addr != rootAdr) { TQString next = KBookmark::nextAddress(addr); if(CurrentMgr::bookmarkAt( next ).hasParent() ) return next; addr = KBookmark::parentAddress( addr ); } return TQString::null; } bool DeleteManyCommand::isConsecutive(const TQValueList<TQString> & addresses) { TQValueList<TQString>::const_iterator it, end; it = addresses.begin(); end = addresses.end(); TQString addr = *(addresses.begin()); for( ; it != end; ++it) { if( *it != addr ) return false; addr = KBookmark::nextAddress(addr); } return true; } DeleteManyCommand::DeleteManyCommand(const TQString &name, const TQValueList<TQString> & addresses) : KEBMacroCommand(name) { TQValueList<TQString>::const_iterator it, begin; begin = addresses.begin(); it = addresses.end(); while(begin != it) { --it; DeleteCommand * dcmd = new DeleteCommand(*it); addCommand(dcmd); } // Set m_currentAddress if( addresses.count() == 1) { // First try next bookmark if( CurrentMgr::bookmarkAt( KBookmark::nextAddress( *begin ) ).hasParent() ) m_currentAddress = *begin; else { m_currentAddress = preOrderNextAddress( KBookmark::parentAddress( *begin ) ); if(m_currentAddress == TQString::null) m_currentAddress = prevOrParentAddress( *begin ); } } else // multi selection { // Check if all bookmarks are consecutive if(isConsecutive(addresses)) // Mark next bookmark after all selected { // That's a little work... TQValueList<TQString>::const_iterator last = addresses.end(); --last; if( CurrentMgr::bookmarkAt( KBookmark::nextAddress(*last) ).hasParent() ) m_currentAddress = *begin; else { m_currentAddress = preOrderNextAddress( KBookmark::parentAddress( *begin ) ); if( m_currentAddress == TQString::null) m_currentAddress = prevOrParentAddress( *begin ); } } else // not consecutive, select the common parent (This could be more clever) { TQValueList<TQString>::const_iterator jt, end; end = addresses.end(); m_currentAddress = *begin; for( jt = addresses.begin(); jt != end; ++jt) m_currentAddress = KBookmark::commonParent(m_currentAddress, *jt); } } } TQString DeleteManyCommand::currentAddress() const { return m_currentAddress; } TQString CreateCommand::name() const { if (m_separator) { return i18n("Insert Separator"); } else if (m_group) { return i18n("Create Folder"); } else if (!m_originalBookmark.isNull()) { return i18n("Copy %1").arg(m_mytext); } else { return i18n("Create Bookmark"); } } void CreateCommand::execute() { TQString parentAddress = KBookmark::parentAddress(m_to); KBookmarkGroup parentGroup = CurrentMgr::bookmarkAt(parentAddress).toGroup(); TQString previousSibling = KBookmark::previousAddress(m_to); // kdDebug() << "CreateCommand::execute previousSibling=" // << previousSibling << endl; KBookmark prev = (previousSibling.isEmpty()) ? KBookmark(TQDomElement()) : CurrentMgr::bookmarkAt(previousSibling); KBookmark bk = KBookmark(TQDomElement()); if (m_separator) { bk = parentGroup.createNewSeparator(); } else if (m_group) { Q_ASSERT(!m_text.isEmpty()); bk = parentGroup.createNewFolder(CurrentMgr::self()->mgr(), m_text, false); bk.internalElement().setAttribute("folded", (m_open ? "no" : "yes")); if (!m_iconPath.isEmpty()) { bk.internalElement().setAttribute("icon", m_iconPath); } } else if (!m_originalBookmark.isNull()) { // umm.. moveItem needs bk to be a child already! bk = m_originalBookmark; } else { bk = parentGroup.addBookmark(CurrentMgr::self()->mgr(), m_text, m_url, m_iconPath, false); } // move to right position parentGroup.moveItem(bk, prev); if (!(name().isEmpty()) && !parentAddress.isEmpty() ) { // open the parent (useful if it was empty) - only for manual commands Q_ASSERT( parentGroup.internalElement().tagName() != "xbel" ); parentGroup.internalElement().setAttribute("folded", "no"); } Q_ASSERT(bk.address() == m_to); } TQString CreateCommand::finalAddress() const { Q_ASSERT( !m_to.isEmpty() ); return m_to; } void CreateCommand::unexecute() { // kdDebug() << "CreateCommand::unexecute deleting " << m_to << endl; KBookmark bk = CurrentMgr::bookmarkAt(m_to); Q_ASSERT(!bk.isNull() && !bk.parentGroup().isNull()); ListView::self()->invalidate(bk.address()); bk.parentGroup().deleteBookmark(bk); } TQString CreateCommand::affectedBookmarks() const { return KBookmark::parentAddress(m_to); } TQString CreateCommand::currentAddress() const { TQString bk = KBookmark::previousAddress( m_to ); if(CurrentMgr::bookmarkAt( bk).hasParent()) return bk; else return KBookmark::parentAddress( m_to ); } /* -------------------------------------- */ TQString EditCommand::name() const { return i18n("%1 Change").arg(m_mytext); } void EditCommand::execute() { KBookmark bk = CurrentMgr::bookmarkAt(m_address); Q_ASSERT(!bk.isNull()); m_reverseEditions.clear(); TQValueList<Edition>::Iterator it = m_editions.begin(); for ( ; it != m_editions.end() ; ++it) { // backup current value m_reverseEditions.append( Edition((*it).attr, bk.internalElement().attribute((*it).attr))); // set new value bk.internalElement().setAttribute((*it).attr, (*it).value); } } void EditCommand::unexecute() { // code reuse EditCommand cmd(m_address, m_reverseEditions); cmd.execute(); // get the editions back from it, // in case they changed // (hmm, shouldn't happen - TODO CHECK!) m_editions = cmd.m_reverseEditions; } TQString EditCommand::affectedBookmarks() const { return KBookmark::parentAddress(m_address); } void EditCommand::modify(const TQString & a, const TQString & v) { TQValueList<Edition>::Iterator it = m_editions.begin(); TQValueList<Edition>::Iterator end = m_editions.end(); for ( ; it != end; ++it) { if( (*it).attr == a) (*it).value = v; } } /* -------------------------------------- */ TQString NodeEditCommand::name() const { // TODO - make dynamic return i18n("Renaming"); } TQString NodeEditCommand::getNodeText(KBookmark bk, const TQStringList &nodehier) { TQDomNode subnode = bk.internalElement(); for (TQStringList::ConstIterator it = nodehier.begin(); it != nodehier.end(); ++it) { subnode = subnode.namedItem((*it)); if (subnode.isNull()) return TQString::null; } return (subnode.firstChild().isNull()) ? TQString::null : subnode.firstChild().toText().data(); } TQString NodeEditCommand::setNodeText(KBookmark bk, const TQStringList &nodehier, const TQString newValue) { TQDomNode subnode = bk.internalElement(); for (TQStringList::ConstIterator it = nodehier.begin(); it != nodehier.end(); ++it) { subnode = subnode.namedItem((*it)); if (subnode.isNull()) { subnode = bk.internalElement().ownerDocument().createElement((*it)); bk.internalElement().appendChild(subnode); } } if (subnode.firstChild().isNull()) { TQDomText domtext = subnode.ownerDocument().createTextNode(""); subnode.appendChild(domtext); } TQDomText domtext = subnode.firstChild().toText(); TQString oldText = domtext.data(); domtext.setData(newValue); return oldText; } void NodeEditCommand::execute() { // DUPLICATED HEAVILY FROM TDEIO/BOOKMARKS KBookmark bk = CurrentMgr::bookmarkAt(m_address); Q_ASSERT(!bk.isNull()); m_oldText = setNodeText(bk, TQStringList() << m_nodename, m_newText); } void NodeEditCommand::unexecute() { // reuse code NodeEditCommand cmd(m_address, m_oldText, m_nodename); cmd.execute(); // get the old text back from it, in case they changed // (hmm, shouldn't happen) // AK - DUP'ed from above??? m_newText = cmd.m_oldText; } void NodeEditCommand::modify(const TQString & newText) { m_newText = newText; } TQString NodeEditCommand::affectedBookmarks() const { return KBookmark::parentAddress(m_address); } /* -------------------------------------- */ void DeleteCommand::execute() { // kdDebug() << "DeleteCommand::execute " << m_from << endl; KBookmark bk = CurrentMgr::bookmarkAt(m_from); Q_ASSERT(!bk.isNull()); if (m_contentOnly) { TQDomElement groupRoot = bk.internalElement(); TQDomNode n = groupRoot.firstChild(); while (!n.isNull()) { TQDomElement e = n.toElement(); if (!e.isNull()) { // kdDebug() << e.tagName() << endl; } TQDomNode next = n.nextSibling(); groupRoot.removeChild(n); n = next; } return; } // TODO - bug - unparsed xml is lost after undo, // we must store it all therefore if (!m_cmd) { if (bk.isGroup()) { m_cmd = new CreateCommand( m_from, bk.fullText(), bk.icon(), bk.internalElement().attribute("folded") == "no"); m_subCmd = deleteAll(bk.toGroup()); m_subCmd->execute(); } else { m_cmd = (bk.isSeparator()) ? new CreateCommand(m_from) : new CreateCommand(m_from, bk.fullText(), bk.icon(), bk.url()); } } m_cmd->unexecute(); } void DeleteCommand::unexecute() { // kdDebug() << "DeleteCommand::unexecute " << m_from << endl; if (m_contentOnly) { // TODO - recover saved metadata return; } m_cmd->execute(); if (m_subCmd) { m_subCmd->unexecute(); } } TQString DeleteCommand::affectedBookmarks() const { return KBookmark::parentAddress(m_from); } KEBMacroCommand* DeleteCommand::deleteAll(const KBookmarkGroup & parentGroup) { KEBMacroCommand *cmd = new KEBMacroCommand(TQString::null); TQStringList lstToDelete; // we need to delete from the end, to avoid index shifting for (KBookmark bk = parentGroup.first(); !bk.isNull(); bk = parentGroup.next(bk)) lstToDelete.prepend(bk.address()); for (TQStringList::Iterator it = lstToDelete.begin(); it != lstToDelete.end(); ++it) cmd->addCommand(new DeleteCommand((*it))); return cmd; } /* -------------------------------------- */ TQString MoveCommand::name() const { return i18n("Move %1").arg(m_mytext); } void MoveCommand::execute() { // kdDebug() << "MoveCommand::execute moving from=" << m_from // << " to=" << m_to << endl; KBookmark bk = CurrentMgr::bookmarkAt(m_from); Q_ASSERT(!bk.isNull()); // look for m_from in the QDom tree KBookmark oldParent = CurrentMgr::bookmarkAt(KBookmark::parentAddress(m_from)); bool wasFirstChild = (KBookmark::positionInParent(m_from) == 0); KBookmark oldPreviousSibling = wasFirstChild ? KBookmark(TQDomElement()) : CurrentMgr::bookmarkAt( KBookmark::previousAddress(m_from)); // look for m_to in the QDom tree TQString parentAddress = KBookmark::parentAddress(m_to); KBookmark newParent = CurrentMgr::bookmarkAt(parentAddress); Q_ASSERT(!newParent.isNull()); Q_ASSERT(newParent.isGroup()); bool isFirstChild = (KBookmark::positionInParent(m_to) == 0); if (isFirstChild) { newParent.toGroup().moveItem(bk, TQDomElement()); } else { TQString afterAddress = KBookmark::previousAddress(m_to); // kdDebug() << "MoveCommand::execute afterAddress=" // << afterAddress << endl; KBookmark afterNow = CurrentMgr::bookmarkAt(afterAddress); Q_ASSERT(!afterNow.isNull()); bool movedOkay = newParent.toGroup().moveItem(bk, afterNow); Q_ASSERT(movedOkay); // kdDebug() << "MoveCommand::execute after moving in the dom tree" // ": item=" << bk.address() << endl; } // because we moved stuff around, the from/to // addresses can have changed, update m_to = bk.address(); m_from = (wasFirstChild) ? (oldParent.address() + "/0") : KBookmark::nextAddress(oldPreviousSibling.address()); // kdDebug() << "MoveCommand::execute : new addresses from=" // << m_from << " to=" << m_to << endl; } TQString MoveCommand::finalAddress() const { Q_ASSERT( !m_to.isEmpty() ); return m_to; } void MoveCommand::unexecute() { // let's not duplicate code. MoveCommand undoCmd(m_to, m_from); undoCmd.execute(); // get the addresses back from that command, in case they changed m_from = undoCmd.m_to; m_to = undoCmd.m_from; } TQString MoveCommand::affectedBookmarks() const { return KBookmark::commonParent(KBookmark::parentAddress(m_from), KBookmark::parentAddress(m_to)); } /* -------------------------------------- */ class SortItem { public: SortItem(const KBookmark & bk) : m_bk(bk) { ; } bool operator == (const SortItem & s) { return (m_bk.internalElement() == s.m_bk.internalElement()); } bool isNull() const { return m_bk.isNull(); } SortItem previousSibling() const { return m_bk.parentGroup().previous(m_bk); } SortItem nextSibling() const { return m_bk.parentGroup().next(m_bk); } const KBookmark& bookmark() const { return m_bk; } private: KBookmark m_bk; }; class SortByName { public: static TQString key(const SortItem &item) { return (item.bookmark().isGroup() ? "a" : "b") + (item.bookmark().fullText().lower()); } }; /* -------------------------------------- */ void SortCommand::execute() { if (m_commands.isEmpty()) { KBookmarkGroup grp = CurrentMgr::bookmarkAt(m_groupAddress).toGroup(); Q_ASSERT(!grp.isNull()); SortItem firstChild(grp.first()); // this will call moveAfter, which will add // the subcommands for moving the items kInsertionSort<SortItem, SortByName, TQString, SortCommand> (firstChild, (*this)); } else { // don't execute for second time on addCommand(cmd) KEBMacroCommand::execute(); } } void SortCommand::moveAfter(const SortItem &moveMe, const SortItem &afterMe) { TQString destAddress = afterMe.isNull() // move as first child ? KBookmark::parentAddress(moveMe.bookmark().address()) + "/0" // move after "afterMe" : KBookmark::nextAddress(afterMe.bookmark().address()); MoveCommand *cmd = new MoveCommand(moveMe.bookmark().address(), destAddress); cmd->execute(); this->addCommand(cmd); } void SortCommand::unexecute() { KEBMacroCommand::unexecute(); } TQString SortCommand::affectedBookmarks() const { return m_groupAddress; } /* -------------------------------------- */ KEBMacroCommand* CmdGen::setAsToolbar(const KBookmark &bk) { KEBMacroCommand *mcmd = new KEBMacroCommand(i18n("Set as Bookmark Toolbar")); KBookmarkGroup oldToolbar = CurrentMgr::self()->mgr()->toolbar(); if (!oldToolbar.isNull()) { TQValueList<EditCommand::Edition> lst; lst.append(EditCommand::Edition("toolbar", "no")); lst.append(EditCommand::Edition("icon", "")); EditCommand *cmd1 = new EditCommand(oldToolbar.address(), lst); mcmd->addCommand(cmd1); } TQValueList<EditCommand::Edition> lst; lst.append(EditCommand::Edition("toolbar", "yes")); lst.append(EditCommand::Edition("icon", "bookmark_toolbar")); // TODO - see below EditCommand *cmd2 = new EditCommand(bk.address(), lst); mcmd->addCommand(cmd2); return mcmd; } bool CmdGen::shownInToolbar(const KBookmark &bk) { return (bk.internalElement().attribute("showintoolbar") == "yes"); } KEBMacroCommand* CmdGen::setShownInToolbar(const TQValueList<KBookmark> &bks, bool show) { TQString i18n_name = i18n("%1 in Bookmark Toolbar").arg(show ? i18n("Show") : i18n("Hide")); KEBMacroCommand *mcmd = new KEBMacroCommand(i18n_name); TQValueList<KBookmark>::ConstIterator it, end; end = bks.end(); for(it = bks.begin(); it != end; ++it) { TQValueList<EditCommand::Edition> lst; lst.append(EditCommand::Edition("showintoolbar", show ? "yes" : "no")); EditCommand *cmd = new EditCommand((*it).address(), lst); mcmd->addCommand(cmd); } return mcmd; } KEBMacroCommand* CmdGen::insertMimeSource( const TQString &cmdName, TQMimeSource *_data, const TQString &addr ) { TQMimeSource *data = _data; bool modified = false; const char *format = 0; for (int i = 0; format = data->format(i), format; i++) { // qt docs don't say if encodedData(blah) where // blah is not a stored mimetype should return null // or not. so, we search. sucky... if (strcmp(format, "GALEON_BOOKMARK") == 0) { modified = true; TQStoredDrag *mydrag = new TQStoredDrag("application/x-xbel"); mydrag->setEncodedData(data->encodedData("GALEON_BOOKMARK")); data = mydrag; break; } else if( strcmp(format, "application/x-xbel" )==0) { /* nothing we created a kbbookmarks drag when we copy element (slotcopy/slotpaste)*/ break; } else if (strcmp(format, "text/uri-list") == 0) { KURL::List uris; if (!KURLDrag::decode(data, uris)) continue; // break out of format loop KURL::List::ConstIterator uit = uris.begin(); KURL::List::ConstIterator uEnd = uris.end(); TQValueList<KBookmark> urlBks; for ( ; uit != uEnd ; ++uit ) { if (!(*uit).url().endsWith(".desktop")) { urlBks << KBookmark::standaloneBookmark((*uit).prettyURL(), (*uit)); continue; } KDesktopFile df((*uit).path(), true); TQString title = df.readName(); KURL url(df.readURL()); if (title.isNull()) title = url.prettyURL(); urlBks << KBookmark::standaloneBookmark(title, url, df.readIcon()); } KBookmarkDrag *mydrag = KBookmarkDrag::newDrag(urlBks, 0); modified = true; data = mydrag; } } if (!KBookmarkDrag::canDecode(data)) { if (modified) // Shouldn't happen delete data; return 0; } KEBMacroCommand *mcmd = new KEBMacroCommand(cmdName); TQString currentAddress = addr; TQValueList<KBookmark> bookmarks = KBookmarkDrag::decode(data); for (TQValueListConstIterator<KBookmark> it = bookmarks.begin(); it != bookmarks.end(); ++it) { CreateCommand *cmd = new CreateCommand(currentAddress, (*it)); cmd->execute(); mcmd->addCommand(cmd); currentAddress = KBookmark::nextAddress(currentAddress); } if (modified) delete data; return mcmd; } KEBMacroCommand* CmdGen::itemsMoved(const TQValueVector<KEBListViewItem *> & items, const TQString &newAddress, bool copy) { KEBMacroCommand *mcmd = new KEBMacroCommand(copy ? i18n("Copy Items") : i18n("Move Items")); TQValueList<KBookmark> list = ListView::self()->itemsToBookmarks( items ); TQValueList<KBookmark>::const_iterator it, end; it = list.begin(); end = list.end(); TQString bkInsertAddr = newAddress; for (; it != end; ++it) { if (copy) { CreateCommand *cmd; cmd = new CreateCommand( bkInsertAddr, (*it).internalElement() .cloneNode(true).toElement(), (*it).text()); cmd->execute(); mcmd->addCommand(cmd); bkInsertAddr = cmd->finalAddress(); } else /* if (move) */ { TQString oldAddress = (*it).address(); if (bkInsertAddr.startsWith(oldAddress)) //FIXME uses internal representation of address continue; MoveCommand *cmd = new MoveCommand(oldAddress, bkInsertAddr, (*it).text()); cmd->execute(); mcmd->addCommand(cmd); bkInsertAddr = cmd->finalAddress(); } bkInsertAddr = KBookmark::nextAddress(bkInsertAddr); } return mcmd; }