kbookmark.cc
00001 // -*- c-basic-offset:4; indent-tabs-mode:nil -*- 00002 // vim: set ts=4 sts=4 sw=4 et: 00003 /* This file is part of the KDE libraries 00004 Copyright (C) 2000 David Faure <faure@kde.org> 00005 Copyright (C) 2003 Alexander Kellett <lypanov@kde.org> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License version 2 as published by the Free Software Foundation. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 Boston, MA 02110-1301, USA. 00020 */ 00021 00022 #include "kbookmark.h" 00023 #include <qvaluestack.h> 00024 #include <kdebug.h> 00025 #include <kmimetype.h> 00026 #include <kstringhandler.h> 00027 #include <kinputdialog.h> 00028 #include <kglobal.h> 00029 #include <klocale.h> 00030 #include <assert.h> 00031 #include <kapplication.h> 00032 #include <dcopclient.h> 00033 #include <kbookmarkmanager.h> 00034 00035 KBookmarkGroup::KBookmarkGroup() 00036 : KBookmark( QDomElement() ) 00037 { 00038 } 00039 00040 KBookmarkGroup::KBookmarkGroup( QDomElement elem ) 00041 : KBookmark(elem) 00042 { 00043 } 00044 00045 QString KBookmarkGroup::groupAddress() const 00046 { 00047 if (m_address.isEmpty()) 00048 m_address = address(); 00049 return m_address; 00050 } 00051 00052 bool KBookmarkGroup::isOpen() const 00053 { 00054 return element.attribute("folded") == "no"; // default is: folded 00055 } 00056 00057 // Returns first element node equal to or after node n 00058 static QDomElement firstElement(QDomNode n) 00059 { 00060 while(!n.isNull() && !n.isElement()) 00061 n = n.nextSibling(); 00062 return n.toElement(); 00063 } 00064 00065 // Returns first element node equal to or before node n 00066 static QDomElement lastElement(QDomNode n) 00067 { 00068 while(!n.isNull() && !n.isElement()) 00069 n = n.previousSibling(); 00070 return n.toElement(); 00071 } 00072 00073 KBookmark KBookmarkGroup::first() const 00074 { 00075 return KBookmark( nextKnownTag( firstElement(element.firstChild()), true ) ); 00076 } 00077 00078 KBookmark KBookmarkGroup::previous( const KBookmark & current ) const 00079 { 00080 return KBookmark( nextKnownTag( lastElement(current.element.previousSibling()), false ) ); 00081 } 00082 00083 KBookmark KBookmarkGroup::next( const KBookmark & current ) const 00084 { 00085 return KBookmark( nextKnownTag( firstElement(current.element.nextSibling()), true ) ); 00086 } 00087 00088 // KDE4: Change QDomElement to QDomNode so that we can get rid of 00089 // firstElement() and lastElement() 00090 QDomElement KBookmarkGroup::nextKnownTag( QDomElement start, bool goNext ) const 00091 { 00092 static const QString & bookmark = KGlobal::staticQString("bookmark"); 00093 static const QString & folder = KGlobal::staticQString("folder"); 00094 static const QString & separator = KGlobal::staticQString("separator"); 00095 00096 for( QDomNode n = start; !n.isNull(); ) 00097 { 00098 QDomElement elem = n.toElement(); 00099 QString tag = elem.tagName(); 00100 if (tag == folder || tag == bookmark || tag == separator) 00101 return elem; 00102 if (goNext) 00103 n = n.nextSibling(); 00104 else 00105 n = n.previousSibling(); 00106 } 00107 return QDomElement(); 00108 } 00109 00110 KBookmarkGroup KBookmarkGroup::createNewFolder( KBookmarkManager* mgr, const QString & text, bool emitSignal ) 00111 { 00112 QString txt( text ); 00113 if ( text.isEmpty() ) 00114 { 00115 bool ok; 00116 QString caption = parentGroup().fullText().isEmpty() ? 00117 i18n( "Create New Bookmark Folder" ) : 00118 i18n( "Create New Bookmark Folder in %1" ) 00119 .arg( parentGroup().text() ); 00120 txt = KInputDialog::getText( caption, i18n( "New folder:" ), 00121 QString::null, &ok ); 00122 if ( !ok ) 00123 return KBookmarkGroup(); 00124 } 00125 00126 Q_ASSERT(!element.isNull()); 00127 QDomDocument doc = element.ownerDocument(); 00128 QDomElement groupElem = doc.createElement( "folder" ); 00129 element.appendChild( groupElem ); 00130 QDomElement textElem = doc.createElement( "title" ); 00131 groupElem.appendChild( textElem ); 00132 textElem.appendChild( doc.createTextNode( txt ) ); 00133 00134 KBookmarkGroup grp(groupElem); 00135 00136 if (emitSignal) 00137 emit mgr->notifier().createdNewFolder( 00138 mgr->path(), grp.fullText(), 00139 grp.address() ); 00140 00141 return grp; 00142 00143 } 00144 00145 KBookmark KBookmarkGroup::createNewSeparator() 00146 { 00147 Q_ASSERT(!element.isNull()); 00148 QDomDocument doc = element.ownerDocument(); 00149 Q_ASSERT(!doc.isNull()); 00150 QDomElement sepElem = doc.createElement( "separator" ); 00151 element.appendChild( sepElem ); 00152 return KBookmark(sepElem); 00153 } 00154 00155 bool KBookmarkGroup::moveItem( const KBookmark & item, const KBookmark & after ) 00156 { 00157 QDomNode n; 00158 if ( !after.isNull() ) 00159 n = element.insertAfter( item.element, after.element ); 00160 else // first child 00161 { 00162 if ( element.firstChild().isNull() ) // Empty element -> set as real first child 00163 n = element.insertBefore( item.element, QDomElement() ); 00164 00165 // we have to skip everything up to the first valid child 00166 QDomElement firstChild = nextKnownTag(element.firstChild().toElement(), true); 00167 if ( !firstChild.isNull() ) 00168 n = element.insertBefore( item.element, firstChild ); 00169 else 00170 { 00171 // No real first child -> append after the <title> etc. 00172 n = element.appendChild( item.element ); 00173 } 00174 } 00175 return (!n.isNull()); 00176 } 00177 00178 KBookmark KBookmarkGroup::addBookmark( KBookmarkManager* mgr, const KBookmark &bm, bool emitSignal ) 00179 { 00180 element.appendChild( bm.internalElement() ); 00181 00182 if (emitSignal) { 00183 if ( bm.hasMetaData() ) { 00184 mgr->notifyCompleteChange( "" ); 00185 } else { 00186 emit mgr->notifier().addedBookmark( 00187 mgr->path(), bm.url().url(), 00188 bm.fullText(), bm.address(), bm.icon() ); 00189 } 00190 } 00191 00192 return bm; 00193 } 00194 00195 KBookmark KBookmarkGroup::addBookmark( KBookmarkManager* mgr, const QString & text, const KURL & url, const QString & icon, bool emitSignal ) 00196 { 00197 //kdDebug(7043) << "KBookmarkGroup::addBookmark " << text << " into " << m_address << endl; 00198 QDomDocument doc = element.ownerDocument(); 00199 QDomElement elem = doc.createElement( "bookmark" ); 00200 elem.setAttribute( "href", url.url( 0, 106 ) ); // write utf8 URL (106 is mib enum for utf8) 00201 QString _icon = icon; 00202 if ( _icon.isEmpty() ) 00203 _icon = KMimeType::iconForURL( url ); 00204 elem.setAttribute( "icon", _icon ); 00205 00206 QDomElement textElem = doc.createElement( "title" ); 00207 elem.appendChild( textElem ); 00208 textElem.appendChild( doc.createTextNode( text ) ); 00209 00210 return addBookmark( mgr, KBookmark( elem ), emitSignal ); 00211 } 00212 00213 void KBookmarkGroup::deleteBookmark( KBookmark bk ) 00214 { 00215 element.removeChild( bk.element ); 00216 } 00217 00218 bool KBookmarkGroup::isToolbarGroup() const 00219 { 00220 return ( element.attribute("toolbar") == "yes" ); 00221 } 00222 00223 QDomElement KBookmarkGroup::findToolbar() const 00224 { 00225 if ( element.attribute("toolbar") == "yes" ) 00226 return element; 00227 for (QDomNode n = element.firstChild(); !n.isNull() ; n = n.nextSibling() ) 00228 { 00229 QDomElement e = n.toElement(); 00230 // Search among the "folder" children only 00231 if ( e.tagName() == "folder" ) 00232 { 00233 if ( e.attribute("toolbar") == "yes" ) 00234 return e; 00235 else 00236 { 00237 QDomElement result = KBookmarkGroup(e).findToolbar(); 00238 if (!result.isNull()) 00239 return result; 00240 } 00241 } 00242 } 00243 return QDomElement(); 00244 } 00245 00246 QValueList<KURL> KBookmarkGroup::groupUrlList() const 00247 { 00248 QValueList<KURL> urlList; 00249 for ( KBookmark bm = first(); !bm.isNull(); bm = next(bm) ) 00250 { 00251 if ( bm.isSeparator() || bm.isGroup() ) 00252 continue; 00253 urlList << bm.url(); 00254 } 00255 return urlList; 00256 } 00257 00259 00260 bool KBookmark::isGroup() const 00261 { 00262 QString tag = element.tagName(); 00263 return ( tag == "folder" 00264 || tag == "xbel" ); // don't forget the toplevel group 00265 } 00266 00267 bool KBookmark::isSeparator() const 00268 { 00269 return (element.tagName() == "separator"); 00270 } 00271 00272 bool KBookmark::hasParent() const 00273 { 00274 QDomElement parent = element.parentNode().toElement(); 00275 return !parent.isNull(); 00276 } 00277 00278 QString KBookmark::text() const 00279 { 00280 return KStringHandler::csqueeze( fullText() ); 00281 } 00282 00283 QString KBookmark::fullText() const 00284 { 00285 if (isSeparator()) 00286 return i18n("--- separator ---"); 00287 00288 return element.namedItem("title").toElement().text(); 00289 } 00290 00291 KURL KBookmark::url() const 00292 { 00293 return KURL(element.attribute("href"), 106); // Decode it from utf8 (106 is mib enum for utf8) 00294 } 00295 00296 QString KBookmark::icon() const 00297 { 00298 QString icon = element.attribute("icon"); 00299 if ( icon.isEmpty() ) 00300 // Default icon depends on URL for bookmarks, and is default directory 00301 // icon for groups. 00302 if ( isGroup() ) 00303 icon = "bookmark_folder"; 00304 else 00305 if ( isSeparator() ) 00306 icon = "eraser"; // whatever 00307 else 00308 icon = KMimeType::iconForURL( url() ); 00309 return icon; 00310 } 00311 00312 KBookmarkGroup KBookmark::parentGroup() const 00313 { 00314 return KBookmarkGroup( element.parentNode().toElement() ); 00315 } 00316 00317 KBookmarkGroup KBookmark::toGroup() const 00318 { 00319 Q_ASSERT( isGroup() ); 00320 return KBookmarkGroup(element); 00321 } 00322 00323 QString KBookmark::address() const 00324 { 00325 if ( element.tagName() == "xbel" ) 00326 return ""; // not QString::null ! 00327 else 00328 { 00329 // Use keditbookmarks's DEBUG_ADDRESSES flag to debug this code :) 00330 if (!hasParent()) 00331 { 00332 Q_ASSERT(hasParent()); 00333 return "ERROR"; // Avoid an infinite loop 00334 } 00335 KBookmarkGroup group = parentGroup(); 00336 QString parentAddress = group.address(); 00337 uint counter = 0; 00338 // Implementation note: we don't use QDomNode's childNode list because we 00339 // would have to skip "TEXT", which KBookmarkGroup already does for us. 00340 for ( KBookmark bk = group.first() ; !bk.isNull() ; bk = group.next(bk), ++counter ) 00341 { 00342 if ( bk.element == element ) 00343 return parentAddress + "/" + QString::number(counter); 00344 } 00345 kdWarning() << "KBookmark::address : this can't happen! " << parentAddress << endl; 00346 return "ERROR"; 00347 } 00348 } 00349 00350 KBookmark KBookmark::standaloneBookmark( const QString & text, const KURL & url, const QString & icon ) 00351 { 00352 QDomDocument doc("xbel"); 00353 QDomElement elem = doc.createElement("xbel"); 00354 doc.appendChild( elem ); 00355 KBookmarkGroup grp( elem ); 00356 grp.addBookmark( 0L, text, url, icon, false ); 00357 return grp.first(); 00358 } 00359 00360 // For some strange reason QString("").left(0) returns QString::null; 00361 // That breaks commonParent() 00362 QString KBookmark::left(const QString & str, uint len) 00363 { 00364 //kdDebug()<<"********"<<QString("").left(0).isNull()<<endl; 00365 if(len == 0) 00366 return QString(""); 00367 else 00368 return str.left(len); 00369 } 00370 00371 QString KBookmark::commonParent(QString A, QString B) 00372 { 00373 QString error("ERROR"); 00374 if(A == error || B == error) 00375 return error; 00376 00377 A += "/"; 00378 B += "/"; 00379 00380 uint lastCommonSlash = 0; 00381 uint lastPos = A.length() < B.length() ? A.length() : B.length(); 00382 for(uint i=0; i < lastPos; ++i) 00383 { 00384 if(A[i] != B[i]) 00385 return left(A, lastCommonSlash); 00386 if(A[i] == '/') 00387 lastCommonSlash = i; 00388 } 00389 return left(A, lastCommonSlash); 00390 } 00391 00392 static QDomNode cd_or_create(QDomNode node, QString name) 00393 { 00394 QDomNode subnode = node.namedItem(name); 00395 if (subnode.isNull()) 00396 { 00397 subnode = node.ownerDocument().createElement(name); 00398 node.appendChild(subnode); 00399 } 00400 return subnode; 00401 } 00402 00403 static QDomText get_or_create_text(QDomNode node) 00404 { 00405 QDomNode subnode = node.firstChild(); 00406 if (subnode.isNull()) 00407 { 00408 subnode = node.ownerDocument().createTextNode(""); 00409 node.appendChild(subnode); 00410 } 00411 return subnode.toText(); 00412 } 00413 00414 // Look for a metadata with owner="http://www.kde.org" or without any owner (for compatibility) 00415 static QDomNode findOrCreateMetadata( QDomNode& parent ) 00416 { 00417 static const char kdeOwner[] = "http://www.kde.org"; 00418 QDomElement metadataElement; 00419 for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) { 00420 QDomElement elem = _node.toElement(); 00421 if ( !elem.isNull() && elem.tagName() == "metadata" ) { 00422 const QString owner = elem.attribute( "owner" ); 00423 if ( owner == kdeOwner ) 00424 return elem; 00425 if ( owner.isEmpty() ) 00426 metadataElement = elem; 00427 } 00428 } 00429 if ( metadataElement.isNull() ) { 00430 metadataElement = parent.ownerDocument().createElement( "metadata" ); 00431 parent.appendChild(metadataElement); 00432 } 00433 metadataElement.setAttribute( "owner", kdeOwner ); 00434 return metadataElement; 00435 } 00436 00437 bool KBookmark::hasMetaData() const 00438 { 00439 // ### NOTE: this code creates <info> and <metadata>, despite its name and the const. 00440 // It doesn't matter much in practice since it's only called for newly-created bookmarks, 00441 // which will get metadata soon after anyway. 00442 QDomNode n = cd_or_create( internalElement(), "info" ); 00443 return findOrCreateMetadata( n ).hasChildNodes(); 00444 } 00445 00446 void KBookmark::updateAccessMetadata() 00447 { 00448 kdDebug(7043) << "KBookmark::updateAccessMetadata " << address() << " " << url().prettyURL() << endl; 00449 00450 const uint timet = QDateTime::currentDateTime().toTime_t(); 00451 setMetaDataItem( "time_added", QString::number( timet ), DontOverwriteMetaData ); 00452 setMetaDataItem( "time_visited", QString::number( timet ) ); 00453 00454 QString countStr = metaDataItem( "visit_count" ); // TODO use spec'ed name 00455 bool ok; 00456 int currentCount = countStr.toInt(&ok); 00457 if (!ok) 00458 currentCount = 0; 00459 currentCount++; 00460 setMetaDataItem( "visit_count", QString::number( currentCount ) ); 00461 00462 // TODO - for 4.0 - time_modified 00463 } 00464 00465 QString KBookmark::metaDataItem( const QString &key ) const 00466 { 00467 QDomNode infoNode = cd_or_create( internalElement(), "info" ); 00468 infoNode = findOrCreateMetadata( infoNode ); 00469 for ( QDomNode n = infoNode.firstChild(); !n.isNull(); n = n.nextSibling() ) { 00470 if ( !n.isElement() ) { 00471 continue; 00472 } 00473 const QDomElement e = n.toElement(); 00474 if ( e.tagName() == key ) { 00475 return e.text(); 00476 } 00477 } 00478 return QString::null; 00479 } 00480 00481 void KBookmark::setMetaDataItem( const QString &key, const QString &value, MetaDataOverwriteMode mode ) 00482 { 00483 QDomNode infoNode = cd_or_create( internalElement(), "info" ); 00484 infoNode = findOrCreateMetadata( infoNode ); 00485 00486 QDomNode item = cd_or_create( infoNode, key ); 00487 QDomText text = get_or_create_text( item ); 00488 if ( mode == DontOverwriteMetaData && !text.data().isEmpty() ) { 00489 return; 00490 } 00491 00492 text.setData( value ); 00493 } 00494 00495 void KBookmarkGroupTraverser::traverse(const KBookmarkGroup &root) 00496 { 00497 // non-recursive bookmark iterator 00498 QValueStack<KBookmarkGroup> stack; 00499 stack.push(root); 00500 KBookmark bk = stack.top().first(); 00501 for (;;) { 00502 if (bk.isNull()) 00503 { 00504 if (stack.isEmpty()) 00505 return; 00506 if (stack.count() > 1) 00507 visitLeave(stack.top()); 00508 bk = stack.pop(); 00509 bk = stack.top().next(bk); 00510 if (bk.isNull()) 00511 continue; 00512 } 00513 00514 if (bk.isGroup()) 00515 { 00516 KBookmarkGroup gp = bk.toGroup(); 00517 visitEnter(gp); 00518 if (!gp.first().isNull()) 00519 { 00520 stack.push(gp); 00521 bk = gp.first(); 00522 continue; 00523 } 00524 // empty group 00525 visitLeave(gp); 00526 } 00527 else 00528 visit(bk); 00529 00530 bk = stack.top().next(bk); 00531 } 00532 00533 // never reached 00534 } 00535