// -*- c-basic-offset:4; indent-tabs-mode:nil -*- // vim: set ts=4 sts=4 sw=4 et: /* This file is part of the KDE libraries Copyright (C) 2000 David Faure Copyright (C) 2003 Alexander Kellett Copyright (C) 2008 Norbert Frese This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kbookmark.h" #include #include #include #include #include #include #include #include #include #include #define METADATA_KDE_OWNER "http://www.kde.org" #define METADATA_FREEDESKTOP_OWNER "http://freedesktop.org" #define METADATA_MIME_OWNER "http://www.freedesktop.org/standards/shared-mime-info" ////// utility functions static QDomNode cd(QDomNode node, const QString &name, bool create) { QDomNode subnode = node.namedItem(name); if (create && subnode.isNull()) { subnode = node.ownerDocument().createElement(name); node.appendChild(subnode); } return subnode; } static QDomNode cd_or_create(QDomNode node, const QString &name) { return cd(node, name, true); } static QDomText get_or_create_text(QDomNode node) { QDomNode subnode = node.firstChild(); if (subnode.isNull()) { subnode = node.ownerDocument().createTextNode(""); node.appendChild(subnode); } return subnode.toText(); } static QDomNode findMetadata(const QString & forOwner, QDomNode& parent, bool create) { bool forOwnerIsKDE = forOwner == METADATA_KDE_OWNER; QDomElement metadataElement; for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) { QDomElement elem = _node.toElement(); if ( !elem.isNull() && elem.tagName() == "metadata" ) { const QString owner = elem.attribute( "owner" ); if ( owner == forOwner ) return elem; if ( owner.isEmpty() && forOwnerIsKDE ) metadataElement = elem; } } if ( create && metadataElement.isNull() ) { metadataElement = parent.ownerDocument().createElement( "metadata" ); parent.appendChild(metadataElement); metadataElement.setAttribute( "owner", forOwner ); } else if (!metadataElement.isNull() && forOwnerIsKDE) { // i'm not sure if this is good, we shouln't take over foreign metatdata metadataElement.setAttribute( "owner", METADATA_KDE_OWNER ); } return metadataElement; } ////// KBookmarkGroup::KBookmarkGroup() : KBookmark( QDomElement() ) { } KBookmarkGroup::KBookmarkGroup( const QDomElement &elem ) : KBookmark(elem) { } bool KBookmarkGroup::isOpen() const { return element.attribute("folded") == "no"; // default is: folded } KBookmark KBookmarkGroup::first() const { return KBookmark( nextKnownTag( element.firstChildElement(), true ) ); } KBookmark KBookmarkGroup::previous( const KBookmark & current ) const { return KBookmark( nextKnownTag( current.element.previousSiblingElement(), false ) ); } KBookmark KBookmarkGroup::next( const KBookmark & current ) const { return KBookmark( nextKnownTag( current.element.nextSiblingElement(), true ) ); } int KBookmarkGroup::indexOf(const KBookmark& child) const { uint counter = 0; for ( KBookmark bk = first(); !bk.isNull(); bk = next(bk), ++counter ) { if ( bk.element == child.element ) return counter; } return -1; } QDomElement KBookmarkGroup::nextKnownTag( const QDomElement &start, bool goNext ) const { static const QString bookmark = QString::fromLatin1("bookmark"); static const QString folder = QString::fromLatin1("folder"); static const QString separator = QString::fromLatin1("separator"); for( QDomElement elem = start; !elem.isNull(); ) { QString tag = elem.tagName(); if (tag == folder || tag == bookmark || tag == separator) return elem; if (goNext) elem = elem.nextSiblingElement(); else elem = elem.previousSiblingElement(); } return QDomElement(); } KBookmarkGroup KBookmarkGroup::createNewFolder( const QString & text ) { if (isNull()) return KBookmarkGroup(); QDomDocument doc = element.ownerDocument(); QDomElement groupElem = doc.createElement( "folder" ); element.appendChild( groupElem ); QDomElement textElem = doc.createElement( "title" ); groupElem.appendChild( textElem ); textElem.appendChild( doc.createTextNode( text ) ); return KBookmarkGroup(groupElem); } KBookmark KBookmarkGroup::createNewSeparator() { if (isNull()) return KBookmark(); QDomDocument doc = element.ownerDocument(); Q_ASSERT(!doc.isNull()); QDomElement sepElem = doc.createElement( "separator" ); element.appendChild( sepElem ); return KBookmark(sepElem); } bool KBookmarkGroup::moveBookmark( const KBookmark & item, const KBookmark & after ) { QDomNode n; if ( !after.isNull() ) n = element.insertAfter( item.element, after.element ); else // first child { if ( element.firstChild().isNull() ) // Empty element -> set as real first child n = element.insertBefore( item.element, QDomElement() ); // we have to skip everything up to the first valid child QDomElement firstChild = nextKnownTag(element.firstChild().toElement(), true); if ( !firstChild.isNull() ) n = element.insertBefore( item.element, firstChild ); else { // No real first child -> append after the etc. n = element.appendChild( item.element ); } } return (!n.isNull()); } KBookmark KBookmarkGroup::addBookmark( const KBookmark &bm ) { element.appendChild( bm.internalElement() ); return bm; } KBookmark KBookmarkGroup::addBookmark( const QString & text, const KUrl & url, const QString & icon ) { if (isNull()) return KBookmark(); QDomDocument doc = element.ownerDocument(); QDomElement elem = doc.createElement( "bookmark" ); elem.setAttribute( "href", url.url() ); // gives us utf8 QDomElement textElem = doc.createElement( "title" ); elem.appendChild( textElem ); textElem.appendChild( doc.createTextNode( text ) ); KBookmark newBookmark = addBookmark( KBookmark( elem ) ); // as icons are moved to metadata, we have to use the KBookmark API for this newBookmark.setIcon(icon.isEmpty() ? KMimeType::iconNameForUrl( url ) : icon ); return newBookmark; } void KBookmarkGroup::deleteBookmark( const KBookmark &bk ) { element.removeChild( bk.element ); } bool KBookmarkGroup::isToolbarGroup() const { return ( element.attribute("toolbar") == "yes" ); } QDomElement KBookmarkGroup::findToolbar() const { if ( element.attribute("toolbar") == "yes" ) return element; for (QDomElement e = element.firstChildElement("folder"); !e.isNull(); e = e.nextSiblingElement("folder") ) { QDomElement result = KBookmarkGroup(e).findToolbar(); if (!result.isNull()) return result; } return QDomElement(); } QList<KUrl> KBookmarkGroup::groupUrlList() const { QList<KUrl> urlList; for ( KBookmark bm = first(); !bm.isNull(); bm = next(bm) ) { if ( bm.isSeparator() || bm.isGroup() ) continue; urlList << bm.url(); } return urlList; } ////// KBookmark::KBookmark() { } KBookmark::KBookmark( const QDomElement &elem ) : element(elem) { } bool KBookmark::isGroup() const { QString tag = element.tagName(); return ( tag == "folder" || tag == "xbel" ); // don't forget the toplevel group } bool KBookmark::isSeparator() const { return (element.tagName() == "separator"); } bool KBookmark::isNull() const { return element.isNull(); } bool KBookmark::hasParent() const { QDomElement parent = element.parentNode().toElement(); return !parent.isNull(); } QString KBookmark::text() const { return KStringHandler::csqueeze( fullText() ); } QString KBookmark::fullText() const { if (isSeparator()) return i18n("--- separator ---"); QString text = element.namedItem("title").toElement().text(); text.replace('\n', ' '); // #140673 return text; } void KBookmark::setFullText(const QString &fullText) { QDomNode titleNode = element.namedItem("title"); if (titleNode.isNull()) { titleNode = element.ownerDocument().createElement("title"); element.appendChild(titleNode); } if (titleNode.firstChild().isNull()) { QDomText domtext = titleNode.ownerDocument().createTextNode(""); titleNode.appendChild(domtext); } QDomText domtext = titleNode.firstChild().toText(); domtext.setData(fullText); } KUrl KBookmark::url() const { return KUrl(element.attribute("href").toLatin1()); // Decodes it from utf8 } void KBookmark::setUrl(const KUrl &url) { element.setAttribute("href", url.url()); } QString KBookmark::icon() const { QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, false); QDomElement iconElement = cd(metaDataNode, "bookmark:icon", false).toElement(); QString icon = iconElement.attribute("name"); if (icon == "bookmark_folder") { return "folder-bookmarks"; } if (icon.isEmpty()) { // Default icon depends on URL for bookmarks, and is default directory // icon for groups. if (isGroup()) { icon = "folder-bookmarks"; } else { if (isSeparator()) { icon = "edit-clear"; // whatever } else { // get icon from mimeType QString _mimeType = mimeType(); if (!_mimeType.isEmpty()) { KMimeType::Ptr mime = KMimeType::mimeType(_mimeType, KMimeType::ResolveAliases); if (mime) { return mime->iconName(); } } // get icon from URL icon = KMimeType::iconNameForUrl(url()); } } } return icon; } void KBookmark::setIcon(const QString &icon) { QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, true); QDomElement iconElement = cd_or_create(metaDataNode, "bookmark:icon").toElement(); iconElement.setAttribute ( "name", icon ); // migration code if(!element.attribute("icon").isEmpty()) element.removeAttribute("icon"); } QString KBookmark::description() const { if (isSeparator()) return QString(); QString description = element.namedItem("desc").toElement().text(); description.replace('\n', ' '); // #140673 return description; } void KBookmark::setDescription(const QString &description) { QDomNode descNode = element.namedItem("desc"); if (descNode.isNull()) { descNode = element.ownerDocument().createElement("desc"); element.appendChild(descNode); } if (descNode.firstChild().isNull()) { QDomText domtext = descNode.ownerDocument().createTextNode(QString()); descNode.appendChild(domtext); } QDomText domtext = descNode.firstChild().toText(); domtext.setData(description); } QString KBookmark::mimeType() const { QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, false); QDomElement mimeTypeElement = cd(metaDataNode, "mime:mime-type", false).toElement(); return mimeTypeElement.attribute("type"); } void KBookmark::setMimeType(const QString &mimeType) { QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, true); QDomElement iconElement = cd_or_create(metaDataNode, "mime:mime-type").toElement(); iconElement.setAttribute ( "type", mimeType ); } bool KBookmark::showInToolbar() const { if(element.hasAttribute("showintoolbar")) { bool show = element.attribute("showintoolbar") == "yes"; const_cast<QDomElement *>(&element)->removeAttribute("showintoolbar"); const_cast<KBookmark *>(this)->setShowInToolbar(show); } return metaDataItem("showintoolbar") == "yes"; } void KBookmark::setShowInToolbar(bool show) { setMetaDataItem("showintoolbar", show ? "yes" : "no"); } KBookmarkGroup KBookmark::parentGroup() const { return KBookmarkGroup( element.parentNode().toElement() ); } KBookmarkGroup KBookmark::toGroup() const { Q_ASSERT( isGroup() ); return KBookmarkGroup(element); } QString KBookmark::address() const { if ( element.tagName() == "xbel" ) return ""; // not QString() ! else { // Use keditbookmarks's DEBUG_ADDRESSES flag to debug this code :) if (element.parentNode().isNull()) { Q_ASSERT(false); return "ERROR"; // Avoid an infinite loop } KBookmarkGroup group = parentGroup(); QString parentAddress = group.address(); int pos = group.indexOf(*this); Q_ASSERT(pos != -1); return parentAddress + '/' + QString::number(pos); } } int KBookmark::positionInParent() const { return parentGroup().indexOf(*this); } QDomElement KBookmark::internalElement() const { return element; } KBookmark KBookmark::standaloneBookmark( const QString & text, const KUrl & url, const QString & icon ) { QDomDocument doc("xbel"); QDomElement elem = doc.createElement("xbel"); doc.appendChild( elem ); KBookmarkGroup grp( elem ); grp.addBookmark( text, url, icon ); return grp.first(); } QString KBookmark::commonParent(const QString &first, const QString &second) { QString A = first; QString B = second; QString error("ERROR"); if(A == error || B == error) return error; A += '/'; B += '/'; uint lastCommonSlash = 0; uint lastPos = A.length() < B.length() ? A.length() : B.length(); for(uint i=0; i < lastPos; ++i) { if(A[i] != B[i]) return A.left(lastCommonSlash); if(A[i] == '/') lastCommonSlash = i; } return A.left(lastCommonSlash); } void KBookmark::updateAccessMetadata() { kDebug(7043) << "KBookmark::updateAccessMetadata " << address() << " " << url().prettyUrl(); const uint timet = QDateTime::currentDateTime().toTime_t(); setMetaDataItem( "time_added", QString::number( timet ), DontOverwriteMetaData ); setMetaDataItem( "time_visited", QString::number( timet ) ); QString countStr = metaDataItem( "visit_count" ); // TODO use spec'ed name bool ok; int currentCount = countStr.toInt(&ok); if (!ok) currentCount = 0; currentCount++; setMetaDataItem( "visit_count", QString::number( currentCount ) ); // TODO - for 4.0 - time_modified } QString KBookmark::parentAddress( const QString & address ) { return address.left( address.lastIndexOf(QLatin1Char('/')) ); } uint KBookmark::positionInParent( const QString & address ) { return address.mid( address.lastIndexOf(QLatin1Char('/')) + 1 ).toInt(); } QString KBookmark::previousAddress( const QString & address ) { uint pp = positionInParent(address); return pp>0 ? parentAddress(address) + QLatin1Char('/') + QString::number(pp-1) : QString(); } QString KBookmark::nextAddress( const QString & address ) { return parentAddress(address) + QLatin1Char('/') + QString::number(positionInParent(address)+1); } QDomNode KBookmark::metaData(const QString &owner, bool create) const { QDomNode infoNode = cd( internalElement(), "info", create); if (infoNode.isNull()) return QDomNode(); return findMetadata(owner, infoNode , create); } QString KBookmark::metaDataItem( const QString &key ) const { QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, false); for ( QDomElement e = metaDataNode.firstChildElement(); !e.isNull(); e = e.nextSiblingElement() ) { if ( e.tagName() == key ) { return e.text(); } } return QString(); } void KBookmark::setMetaDataItem( const QString &key, const QString &value, MetaDataOverwriteMode mode ) { QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, true); QDomNode item = cd_or_create( metaDataNode, key ); QDomText text = get_or_create_text( item ); if ( mode == DontOverwriteMetaData && !text.data().isEmpty() ) { return; } text.setData( value ); } bool KBookmark::operator==(const KBookmark& rhs) const { return element == rhs.element; } //// KBookmarkGroupTraverser::~KBookmarkGroupTraverser() { } void KBookmarkGroupTraverser::traverse(const KBookmarkGroup &root) { QStack<KBookmarkGroup> stack; stack.push(root); KBookmark bk = root.first(); for(;;) { if(bk.isNull()) { if(stack.count() == 1) // only root is on the stack return; if(stack.count() > 0) { visitLeave(stack.top()); bk = stack.pop(); } bk = stack.top().next(bk); } else if(bk.isGroup()) { KBookmarkGroup gp = bk.toGroup(); visitEnter(gp); bk = gp.first(); stack.push(gp); } else { visit(bk); bk = stack.top().next(bk); } } } void KBookmarkGroupTraverser::visit(const KBookmark &) { } void KBookmarkGroupTraverser::visitEnter(const KBookmarkGroup &) { } void KBookmarkGroupTraverser::visitLeave(const KBookmarkGroup &) { } void KBookmark::populateMimeData( QMimeData* mimeData ) const { KBookmark::List bookmarkList; bookmarkList.append( *this ); bookmarkList.populateMimeData( mimeData ); } KBookmark::List::List() : QList<KBookmark>() { } void KBookmark::List::populateMimeData( QMimeData* mimeData ) const { KUrl::List urls; QDomDocument doc( "xbel" ); QDomElement elem = doc.createElement( "xbel" ); doc.appendChild( elem ); for ( const_iterator it = begin(), end = this->end() ; it != end ; ++it ) { urls.append( (*it).url() ); elem.appendChild( (*it).internalElement().cloneNode( true /* deep */ ) ); } // This sets text/uri-list and text/plain into the mimedata urls.populateMimeData( mimeData, KUrl::MetaDataMap() ); mimeData->setData( "application/x-xbel", doc.toByteArray() ); } bool KBookmark::List::canDecode( const QMimeData *mimeData ) { return mimeData->hasFormat( "application/x-xbel" ) || KUrl::List::canDecode(mimeData); } QStringList KBookmark::List::mimeDataTypes() { return QStringList()<<("application/x-xbel")<<KUrl::List::mimeDataTypes(); } KBookmark::List KBookmark::List::fromMimeData( const QMimeData *mimeData, QDomDocument& doc ) { KBookmark::List bookmarks; QByteArray payload = mimeData->data( "application/x-xbel" ); if ( !payload.isEmpty() ) { doc.setContent( payload ); QDomElement elem = doc.documentElement(); const QDomNodeList children = elem.childNodes(); for ( int childno = 0; childno < children.count(); childno++) { bookmarks.append( KBookmark( children.item(childno).toElement() )); } return bookmarks; } const KUrl::List urls = KUrl::List::fromMimeData( mimeData ); if ( !urls.isEmpty() ) { KUrl::List::ConstIterator uit = urls.begin(); KUrl::List::ConstIterator uEnd = urls.end(); for ( ; uit != uEnd ; ++uit ) { //kDebug(7043) << "url=" << (*uit); bookmarks.append( KBookmark::standaloneBookmark( (*uit).prettyUrl(), (*uit) )); } } return bookmarks; }