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 <tqvaluestack.h> 00024 #include <kdebug.h> 00025 #include <kmimetype.h> 00026 #include <kstringhandler.h> 00027 #include <kinputdialog.h> 00028 #include <tdeglobal.h> 00029 #include <tdelocale.h> 00030 #include <assert.h> 00031 #include <tdeapplication.h> 00032 #include <dcopclient.h> 00033 #include <kbookmarkmanager.h> 00034 00035 KBookmarkGroup::KBookmarkGroup() 00036 : KBookmark( TQDomElement() ) 00037 { 00038 } 00039 00040 KBookmarkGroup::KBookmarkGroup( TQDomElement elem ) 00041 : KBookmark(elem) 00042 { 00043 } 00044 00045 TQString 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 TQDomElement firstElement(TQDomNode 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 TQDomElement lastElement(TQDomNode 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 TQDomElement to TQDomNode so that we can get rid of 00089 // firstElement() and lastElement() 00090 TQDomElement KBookmarkGroup::nextKnownTag( TQDomElement start, bool goNext ) const 00091 { 00092 static const TQString & bookmark = TDEGlobal::staticQString("bookmark"); 00093 static const TQString & folder = TDEGlobal::staticQString("folder"); 00094 static const TQString & separator = TDEGlobal::staticQString("separator"); 00095 00096 for( TQDomNode n = start; !n.isNull(); ) 00097 { 00098 TQDomElement elem = n.toElement(); 00099 TQString 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 TQDomElement(); 00108 } 00109 00110 KBookmarkGroup KBookmarkGroup::createNewFolder( KBookmarkManager* mgr, const TQString & text, bool emitSignal ) 00111 { 00112 TQString txt( text ); 00113 if ( text.isEmpty() ) 00114 { 00115 bool ok; 00116 TQString 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 TQString::null, &ok ); 00122 if ( !ok ) 00123 return KBookmarkGroup(); 00124 } 00125 00126 Q_ASSERT(!element.isNull()); 00127 TQDomDocument doc = element.ownerDocument(); 00128 TQDomElement groupElem = doc.createElement( "folder" ); 00129 element.appendChild( groupElem ); 00130 TQDomElement 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 TQDomDocument doc = element.ownerDocument(); 00149 Q_ASSERT(!doc.isNull()); 00150 TQDomElement 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 TQDomNode 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, TQDomElement() ); 00164 00165 // we have to skip everything up to the first valid child 00166 TQDomElement 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 TQString & text, const KURL & url, const TQString & icon, bool emitSignal ) 00196 { 00197 //kdDebug(7043) << "KBookmarkGroup::addBookmark " << text << " into " << m_address << endl; 00198 TQDomDocument doc = element.ownerDocument(); 00199 TQDomElement elem = doc.createElement( "bookmark" ); 00200 elem.setAttribute( "href", url.url( 0, 106 ) ); // write utf8 URL (106 is mib enum for utf8) 00201 TQString _icon = icon; 00202 if ( _icon.isEmpty() ) 00203 _icon = KMimeType::iconForURL( url ); 00204 elem.setAttribute( "icon", _icon ); 00205 00206 TQDomElement 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 TQDomElement KBookmarkGroup::findToolbar() const 00224 { 00225 if ( element.attribute("toolbar") == "yes" ) 00226 return element; 00227 for (TQDomNode n = element.firstChild(); !n.isNull() ; n = n.nextSibling() ) 00228 { 00229 TQDomElement 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 TQDomElement result = KBookmarkGroup(e).findToolbar(); 00238 if (!result.isNull()) 00239 return result; 00240 } 00241 } 00242 } 00243 return TQDomElement(); 00244 } 00245 00246 TQValueList<KURL> KBookmarkGroup::groupUrlList() const 00247 { 00248 TQValueList<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 TQString 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 TQDomElement parent = element.parentNode().toElement(); 00275 return !parent.isNull(); 00276 } 00277 00278 TQString KBookmark::text() const 00279 { 00280 return KStringHandler::csqueeze( fullText() ); 00281 } 00282 00283 TQString 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 TQString KBookmark::icon() const 00297 { 00298 TQString 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 } 00305 else { 00306 if ( isSeparator() ) { 00307 icon = "eraser"; // whatever 00308 } 00309 else { 00310 icon = KMimeType::iconForURL( url() ); 00311 } 00312 } 00313 } 00314 return icon; 00315 } 00316 00317 KBookmarkGroup KBookmark::parentGroup() const 00318 { 00319 return KBookmarkGroup( element.parentNode().toElement() ); 00320 } 00321 00322 KBookmarkGroup KBookmark::toGroup() const 00323 { 00324 Q_ASSERT( isGroup() ); 00325 return KBookmarkGroup(element); 00326 } 00327 00328 TQString KBookmark::address() const 00329 { 00330 if ( element.tagName() == "xbel" ) 00331 return ""; // not TQString::null ! 00332 else 00333 { 00334 // Use keditbookmarks's DEBUG_ADDRESSES flag to debug this code :) 00335 if (!hasParent()) 00336 { 00337 Q_ASSERT(hasParent()); 00338 return "ERROR"; // Avoid an infinite loop 00339 } 00340 KBookmarkGroup group = parentGroup(); 00341 TQString parentAddress = group.address(); 00342 uint counter = 0; 00343 // Implementation note: we don't use QDomNode's childNode list because we 00344 // would have to skip "TEXT", which KBookmarkGroup already does for us. 00345 for ( KBookmark bk = group.first() ; !bk.isNull() ; bk = group.next(bk), ++counter ) 00346 { 00347 if ( bk.element == element ) 00348 return parentAddress + "/" + TQString::number(counter); 00349 } 00350 kdWarning() << "KBookmark::address : this can't happen! " << parentAddress << endl; 00351 return "ERROR"; 00352 } 00353 } 00354 00355 KBookmark KBookmark::standaloneBookmark( const TQString & text, const KURL & url, const TQString & icon ) 00356 { 00357 TQDomDocument doc("xbel"); 00358 TQDomElement elem = doc.createElement("xbel"); 00359 doc.appendChild( elem ); 00360 KBookmarkGroup grp( elem ); 00361 grp.addBookmark( 0L, text, url, icon, false ); 00362 return grp.first(); 00363 } 00364 00365 // For some strange reason TQString("").left(0) returns TQString::null; 00366 // That breaks commonParent() 00367 TQString KBookmark::left(const TQString & str, uint len) 00368 { 00369 //kdDebug()<<"********"<<TQString("").left(0).isNull()<<endl; 00370 if(len == 0) 00371 return TQString(""); 00372 else 00373 return str.left(len); 00374 } 00375 00376 TQString KBookmark::commonParent(TQString A, TQString B) 00377 { 00378 TQString error("ERROR"); 00379 if(A == error || B == error) 00380 return error; 00381 00382 A += "/"; 00383 B += "/"; 00384 00385 uint lastCommonSlash = 0; 00386 uint lastPos = A.length() < B.length() ? A.length() : B.length(); 00387 for(uint i=0; i < lastPos; ++i) 00388 { 00389 if(A[i] != B[i]) 00390 return left(A, lastCommonSlash); 00391 if(A[i] == '/') 00392 lastCommonSlash = i; 00393 } 00394 return left(A, lastCommonSlash); 00395 } 00396 00397 static TQDomNode cd_or_create(TQDomNode node, TQString name) 00398 { 00399 TQDomNode subnode = node.namedItem(name); 00400 if (subnode.isNull()) 00401 { 00402 subnode = node.ownerDocument().createElement(name); 00403 node.appendChild(subnode); 00404 } 00405 return subnode; 00406 } 00407 00408 static TQDomText get_or_create_text(TQDomNode node) 00409 { 00410 TQDomNode subnode = node.firstChild(); 00411 if (subnode.isNull()) 00412 { 00413 subnode = node.ownerDocument().createTextNode(""); 00414 node.appendChild(subnode); 00415 } 00416 return subnode.toText(); 00417 } 00418 00419 // Look for a metadata with owner="http://www.kde.org" or without any owner (for compatibility) 00420 static TQDomNode findOrCreateMetadata( TQDomNode& parent ) 00421 { 00422 static const char kdeOwner[] = "http://www.kde.org"; 00423 TQDomElement metadataElement; 00424 for ( TQDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) { 00425 TQDomElement elem = _node.toElement(); 00426 if ( !elem.isNull() && elem.tagName() == "metadata" ) { 00427 const TQString owner = elem.attribute( "owner" ); 00428 if ( owner == kdeOwner ) 00429 return elem; 00430 if ( owner.isEmpty() ) 00431 metadataElement = elem; 00432 } 00433 } 00434 if ( metadataElement.isNull() ) { 00435 metadataElement = parent.ownerDocument().createElement( "metadata" ); 00436 parent.appendChild(metadataElement); 00437 } 00438 metadataElement.setAttribute( "owner", kdeOwner ); 00439 return metadataElement; 00440 } 00441 00442 bool KBookmark::hasMetaData() const 00443 { 00444 // ### NOTE: this code creates <info> and <metadata>, despite its name and the const. 00445 // It doesn't matter much in practice since it's only called for newly-created bookmarks, 00446 // which will get metadata soon after anyway. 00447 TQDomNode n = cd_or_create( internalElement(), "info" ); 00448 return findOrCreateMetadata( n ).hasChildNodes(); 00449 } 00450 00451 void KBookmark::updateAccessMetadata() 00452 { 00453 kdDebug(7043) << "KBookmark::updateAccessMetadata " << address() << " " << url().prettyURL() << endl; 00454 00455 const uint timet = TQDateTime::currentDateTime().toTime_t(); 00456 setMetaDataItem( "time_added", TQString::number( timet ), DontOverwriteMetaData ); 00457 setMetaDataItem( "time_visited", TQString::number( timet ) ); 00458 00459 TQString countStr = metaDataItem( "visit_count" ); // TODO use spec'ed name 00460 bool ok; 00461 int currentCount = countStr.toInt(&ok); 00462 if (!ok) 00463 currentCount = 0; 00464 currentCount++; 00465 setMetaDataItem( "visit_count", TQString::number( currentCount ) ); 00466 00467 // TODO - for 4.0 - time_modified 00468 } 00469 00470 TQString KBookmark::metaDataItem( const TQString &key ) const 00471 { 00472 TQDomNode infoNode = cd_or_create( internalElement(), "info" ); 00473 infoNode = findOrCreateMetadata( infoNode ); 00474 for ( TQDomNode n = infoNode.firstChild(); !n.isNull(); n = n.nextSibling() ) { 00475 if ( !n.isElement() ) { 00476 continue; 00477 } 00478 const TQDomElement e = n.toElement(); 00479 if ( e.tagName() == key ) { 00480 return e.text(); 00481 } 00482 } 00483 return TQString::null; 00484 } 00485 00486 void KBookmark::setMetaDataItem( const TQString &key, const TQString &value, MetaDataOverwriteMode mode ) 00487 { 00488 TQDomNode infoNode = cd_or_create( internalElement(), "info" ); 00489 infoNode = findOrCreateMetadata( infoNode ); 00490 00491 TQDomNode item = cd_or_create( infoNode, key ); 00492 TQDomText text = get_or_create_text( item ); 00493 if ( mode == DontOverwriteMetaData && !text.data().isEmpty() ) { 00494 return; 00495 } 00496 00497 text.setData( value ); 00498 } 00499 00500 void KBookmarkGroupTraverser::traverse(const KBookmarkGroup &root) 00501 { 00502 // non-recursive bookmark iterator 00503 TQValueStack<KBookmarkGroup> stack; 00504 stack.push(root); 00505 KBookmark bk = stack.top().first(); 00506 for (;;) { 00507 if (bk.isNull()) 00508 { 00509 if (stack.isEmpty()) 00510 return; 00511 if (stack.count() > 1) 00512 visitLeave(stack.top()); 00513 bk = stack.pop(); 00514 bk = stack.top().next(bk); 00515 if (bk.isNull()) 00516 continue; 00517 } 00518 00519 if (bk.isGroup()) 00520 { 00521 KBookmarkGroup gp = bk.toGroup(); 00522 visitEnter(gp); 00523 if (!gp.first().isNull()) 00524 { 00525 stack.push(gp); 00526 bk = gp.first(); 00527 continue; 00528 } 00529 // empty group 00530 visitLeave(gp); 00531 } 00532 else 00533 visit(bk); 00534 00535 bk = stack.top().next(bk); 00536 } 00537 00538 // never reached 00539 } 00540