// -*- 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 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 "kbookmarkmanager.h" #include "kbookmarkmenu.h" #include "kbookmarkmenu_p.h" #include "kbookmarkimporter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dptrtemplate.h" class KBookmarkManagerPrivate : public dPtrTemplate { public: KBookmarkManagerPrivate() { m_browserEditor = true; } TQString m_editorCaption; bool m_browserEditor; }; template<> TQPtrDict* dPtrTemplate::d_ptr = 0; KBookmarkManagerPrivate* KBookmarkManager::dptr() const { return KBookmarkManagerPrivate::d( this ); } // TODO - clean this stuff up by just using the above dptrtemplate? TQPtrList* KBookmarkManager::s_pSelf; static KStaticDeleter > sdbm; class KBookmarkMap : private KBookmarkGroupTraverser { public: KBookmarkMap( KBookmarkManager * ); void update(); TQValueList find( const TQString &url ) const { return m_bk_map[url]; } private: virtual void visit(const KBookmark &); virtual void visitEnter(const KBookmarkGroup &) { ; } virtual void visitLeave(const KBookmarkGroup &) { ; } private: typedef TQValueList KBookmarkList; TQMap m_bk_map; KBookmarkManager *m_manager; }; static KBookmarkMap *s_bk_map = 0; KBookmarkMap::KBookmarkMap( KBookmarkManager *manager ) { m_manager = manager; } void KBookmarkMap::update() { m_bk_map.clear(); KBookmarkGroup root = m_manager->root(); traverse(root); } void KBookmarkMap::visit(const KBookmark &bk) { if (!bk.isSeparator()) { // add bookmark to url map m_bk_map[bk.internalElement().attribute("href")].append(bk); } } KBookmarkManager* KBookmarkManager::managerForFile( const TQString& bookmarksFile, bool bImportDesktopFiles ) { if ( !s_pSelf ) { sdbm.setObject( s_pSelf, new TQPtrList ); s_pSelf->setAutoDelete( true ); } TQPtrListIterator it ( *s_pSelf ); for ( ; it.current() ; ++it ) if ( it.current()->path() == bookmarksFile ) return it.current(); KBookmarkManager* mgr = new KBookmarkManager( bookmarksFile, bImportDesktopFiles ); s_pSelf->append( mgr ); return mgr; } // principally used for filtered toolbars KBookmarkManager* KBookmarkManager::createTempManager() { if ( !s_pSelf ) { sdbm.setObject( s_pSelf, new TQPtrList ); s_pSelf->setAutoDelete( true ); } KBookmarkManager* mgr = new KBookmarkManager(); s_pSelf->append( mgr ); return mgr; } #define PI_DATA "version=\"1.0\" encoding=\"UTF-8\"" KBookmarkManager::KBookmarkManager( const TQString & bookmarksFile, bool bImportDesktopFiles ) : DCOPObject(TQCString("KBookmarkManager-")+bookmarksFile.utf8()), m_doc("xbel"), m_docIsLoaded(false) { m_toolbarDoc.clear(); m_update = true; m_showNSBookmarks = true; Q_ASSERT( !bookmarksFile.isEmpty() ); m_bookmarksFile = bookmarksFile; if ( !TQFile::exists(m_bookmarksFile) ) { TQDomElement topLevel = m_doc.createElement("xbel"); m_doc.appendChild( topLevel ); m_doc.insertBefore( m_doc.createProcessingInstruction( "xml", PI_DATA), topLevel ); if ( bImportDesktopFiles ) importDesktopFiles(); m_docIsLoaded = true; } connectDCOPSignal(0, objId(), "bookmarksChanged(TQString)", "notifyChanged(TQString)", false); connectDCOPSignal(0, objId(), "bookmarkConfigChanged()", "notifyConfigChanged()", false); } KBookmarkManager::KBookmarkManager( ) : DCOPObject(TQCString("KBookmarkManager-generated")), m_doc("xbel"), m_docIsLoaded(true) { m_toolbarDoc.clear(); // strange ;-) m_update = false; // TODO - make it read/write m_showNSBookmarks = true; m_bookmarksFile = TQString::null; // AK - check all codepaths for this one TQDomElement topLevel = m_doc.createElement("xbel"); m_doc.appendChild( topLevel ); m_doc.insertBefore( m_doc.createProcessingInstruction( "xml", PI_DATA), topLevel ); // TODO - enable this via some sort of api and fix the above DCOPObject script somehow #if 0 connectDCOPSignal(0, objId(), "bookmarksChanged(TQString)", "notifyChanged(TQString)", false); connectDCOPSignal(0, objId(), "bookmarkConfigChanged()", "notifyConfigChanged()", false); #endif } KBookmarkManager::~KBookmarkManager() { if ( s_pSelf ) s_pSelf->removeRef( this ); } void KBookmarkManager::setUpdate( bool update ) { m_update = update; } const TQDomDocument &KBookmarkManager::internalDocument() const { if(!m_docIsLoaded) { parse(); m_toolbarDoc.clear(); } return m_doc; } void KBookmarkManager::parse() const { m_docIsLoaded = true; //kdDebug(7043) << "KBookmarkManager::parse " << m_bookmarksFile << endl; TQFile file( m_bookmarksFile ); if ( !file.open( IO_ReadOnly ) ) { kdWarning() << "Can't open " << m_bookmarksFile << endl; return; } m_doc = TQDomDocument("xbel"); m_doc.setContent( &file ); TQDomElement docElem = m_doc.documentElement(); if ( docElem.isNull() ) kdWarning() << "KBookmarkManager::parse : can't parse " << m_bookmarksFile << endl; else { TQString mainTag = docElem.tagName(); if ( mainTag == "BOOKMARKS" ) { kdWarning() << "Old style bookmarks found. Calling convertToXBEL." << endl; docElem.setTagName("xbel"); if ( docElem.hasAttribute( "HIDE_NSBK" ) && m_showNSBookmarks ) // non standard either, but we need it { docElem.setAttribute( "hide_nsbk", docElem.attribute( "HIDE_NSBK" ) == "1" ? "yes" : "no" ); docElem.removeAttribute( "HIDE_NSBK" ); } convertToXBEL( docElem ); save(); } else if ( mainTag != "xbel" ) kdWarning() << "KBookmarkManager::parse : unknown main tag " << mainTag << endl; TQDomNode n = m_doc.documentElement().previousSibling(); if ( n.isProcessingInstruction() ) { TQDomProcessingInstruction pi = n.toProcessingInstruction(); pi.parentNode().removeChild(pi); } TQDomProcessingInstruction pi; pi = m_doc.createProcessingInstruction( "xml", PI_DATA ); m_doc.insertBefore( pi, docElem ); } file.close(); if ( !s_bk_map ) s_bk_map = new KBookmarkMap( const_cast( this ) ); s_bk_map->update(); } void KBookmarkManager::convertToXBEL( TQDomElement & group ) { TQDomNode n = group.firstChild(); while( !n.isNull() ) { TQDomElement e = n.toElement(); if ( !e.isNull() ) { if ( e.tagName() == "TEXT" ) { e.setTagName("title"); } else if ( e.tagName() == "SEPARATOR" ) { e.setTagName("separator"); // so close... } else if ( e.tagName() == "GROUP" ) { e.setTagName("folder"); convertAttribute(e, "ICON","icon"); // non standard, but we need it if ( e.hasAttribute( "TOOLBAR" ) ) // non standard either, but we need it { e.setAttribute( "toolbar", e.attribute( "TOOLBAR" ) == "1" ? "yes" : "no" ); e.removeAttribute( "TOOLBAR" ); } convertAttribute(e, "NETSCAPEINFO","netscapeinfo"); // idem bool open = (e.attribute("OPEN") == "1"); e.removeAttribute("OPEN"); e.setAttribute("folded", open ? "no" : "yes"); convertToXBEL( e ); } else { if ( e.tagName() == "BOOKMARK" ) { e.setTagName("bookmark"); // so much difference :-) convertAttribute(e, "ICON","icon"); // non standard, but we need it convertAttribute(e, "NETSCAPEINFO","netscapeinfo"); // idem convertAttribute(e, "URL","href"); TQString text = e.text(); while ( !e.firstChild().isNull() ) // clean up the old contained text e.removeChild(e.firstChild()); TQDomElement titleElem = e.ownerDocument().createElement("title"); e.appendChild( titleElem ); // should be the only child anyway titleElem.appendChild( e.ownerDocument().createTextNode( text ) ); } else { kdWarning(7043) << "Unknown tag " << e.tagName() << endl; } } } n = n.nextSibling(); } } void KBookmarkManager::convertAttribute( TQDomElement elem, const TQString & oldName, const TQString & newName ) { if ( elem.hasAttribute( oldName ) ) { elem.setAttribute( newName, elem.attribute( oldName ) ); elem.removeAttribute( oldName ); } } void KBookmarkManager::importDesktopFiles() { KBookmarkImporter importer( const_cast(&internalDocument()) ); TQString path(KGlobal::dirs()->saveLocation("data", "kfm/bookmarks", true)); importer.import( path ); //kdDebug(7043) << internalDocument().toCString() << endl; save(); } bool KBookmarkManager::save( bool toolbarCache ) const { return saveAs( m_bookmarksFile, toolbarCache ); } bool KBookmarkManager::saveAs( const TQString & filename, bool toolbarCache ) const { kdDebug(7043) << "KBookmarkManager::save " << filename << endl; // Save the bookmark toolbar folder for quick loading // but only when it will actually make things quicker const TQString cacheFilename = filename + TQString::fromLatin1(".tbcache"); if(toolbarCache && !root().isToolbarGroup()) { KSaveFile cacheFile( cacheFilename ); if ( cacheFile.status() == 0 ) { TQString str; TQTextStream stream(&str, IO_WriteOnly); stream << root().findToolbar(); TQCString cstr = str.utf8(); cacheFile.file()->writeBlock( cstr.data(), cstr.length() ); cacheFile.close(); } } else // remove any (now) stale cache { TQFile::remove( cacheFilename ); } KSaveFile file( filename ); if ( file.status() == 0 ) { file.backupFile( file.name(), TQString::null, ".bak" ); TQCString cstr; cstr = internalDocument().toCString(); // is in UTF8 file.file()->writeBlock( cstr.data(), cstr.length() ); if ( file.close() ) return true; } static int hadSaveError = false; file.abort(); if ( !hadSaveError ) { TQString error = i18n("Unable to save bookmarks in %1. Reported error was: %2. " "This error message will only be shown once. The cause " "of the error needs to be fixed as quickly as possible, " "which is most likely a full hard drive.") .arg(filename).arg(TQString::fromLocal8Bit(strerror(file.status()))); if (tqApp->type() != TQApplication::Tty) KMessageBox::error( 0L, error ); else kdError() << error << endl; } hadSaveError = true; return false; } KBookmarkGroup KBookmarkManager::root() const { return KBookmarkGroup(internalDocument().documentElement()); } KBookmarkGroup KBookmarkManager::toolbar() { kdDebug(7043) << "KBookmarkManager::toolbar begin" << endl; // Only try to read from a toolbar cache if the full document isn't loaded if(!m_docIsLoaded) { kdDebug(7043) << "KBookmarkManager::toolbar trying cache" << endl; const TQString cacheFilename = m_bookmarksFile + TQString::fromLatin1(".tbcache"); TQFileInfo bmInfo(m_bookmarksFile); TQFileInfo cacheInfo(cacheFilename); if (m_toolbarDoc.isNull() && TQFile::exists(cacheFilename) && bmInfo.lastModified() < cacheInfo.lastModified()) { kdDebug(7043) << "KBookmarkManager::toolbar reading file" << endl; TQFile file( cacheFilename ); if ( file.open( IO_ReadOnly ) ) { m_toolbarDoc = TQDomDocument("cache"); m_toolbarDoc.setContent( &file ); kdDebug(7043) << "KBookmarkManager::toolbar opened" << endl; } } if (!m_toolbarDoc.isNull()) { kdDebug(7043) << "KBookmarkManager::toolbar returning element" << endl; TQDomElement elem = m_toolbarDoc.firstChild().toElement(); return KBookmarkGroup(elem); } } // Fallback to the normal way if there is no cache or if the bookmark file // is already loaded TQDomElement elem = root().findToolbar(); if (elem.isNull()) return root(); // Root is the bookmark toolbar if none has been set. else return KBookmarkGroup(root().findToolbar()); } KBookmark KBookmarkManager::findByAddress( const TQString & address, bool tolerant ) { //kdDebug(7043) << "KBookmarkManager::findByAddress " << address << endl; KBookmark result = root(); // The address is something like /5/10/2+ TQStringList addresses = TQStringList::split(TQRegExp("[/+]"),address); // kdWarning() << addresses.join(",") << endl; for ( TQStringList::Iterator it = addresses.begin() ; it != addresses.end() ; ) { bool append = ((*it) == "+"); uint number = (*it).toUInt(); Q_ASSERT(result.isGroup()); KBookmarkGroup group = result.toGroup(); KBookmark bk = group.first(), lbk = bk; // last non-null bookmark for ( uint i = 0 ; ( (i restart search from the beginning ch = parentBookmark.first(); break; } else { // this exact URL already exists return TQString::null; } } ch = parentBookmark.next( ch ); } } while ( !ch.isNull() ); return uniqueTitle; } KBookmarkGroup KBookmarkManager::addBookmarkDialog( const TQString & _url, const TQString & _title, const TQString & _parentBookmarkAddress ) { TQString url = _url; TQString title = _title; TQString parentBookmarkAddress = _parentBookmarkAddress; if ( url.isEmpty() ) { KMessageBox::error( 0L, i18n("Cannot add bookmark with empty URL.")); return KBookmarkGroup(); } if ( title.isEmpty() ) title = url; if ( KBookmarkSettings::self()->m_advancedaddbookmark) { KBookmarkEditDialog dlg( title, url, this, KBookmarkEditDialog::InsertionMode, parentBookmarkAddress ); if ( dlg.exec() != KDialogBase::Accepted ) return KBookmarkGroup(); title = dlg.finalTitle(); url = dlg.finalUrl(); parentBookmarkAddress = dlg.finalAddress(); } KBookmarkGroup parentBookmark; parentBookmark = findByAddress( parentBookmarkAddress ).toGroup(); Q_ASSERT( !parentBookmark.isNull() ); TQString uniqueTitle = pickUnusedTitle( parentBookmark, title, url ); if ( !uniqueTitle.isNull() ) parentBookmark.addBookmark( this, uniqueTitle, KURL( url )); return parentBookmark; } void KBookmarkManager::emitChanged( /*KDE4 const*/ KBookmarkGroup & group ) { save(); // Tell the other processes too // kdDebug(7043) << "KBookmarkManager::emitChanged : broadcasting change " << group.address() << endl; TQByteArray data; TQDataStream ds( data, IO_WriteOnly ); ds << group.address(); emitDCOPSignal("bookmarksChanged(TQString)", data); // We do get our own broadcast, so no need for this anymore //emit changed( group ); } void KBookmarkManager::emitConfigChanged() { emitDCOPSignal("bookmarkConfigChanged()", TQByteArray()); } void KBookmarkManager::notifyCompleteChange( TQString caller ) // DCOP call { if (!m_update) return; //kdDebug(7043) << "KBookmarkManager::notifyCompleteChange" << endl; // The bk editor tells us we should reload everything // Reparse parse(); // Tell our GUI // (emit where group is "" to directly mark the root menu as dirty) emit changed( "", caller ); } void KBookmarkManager::notifyConfigChanged() // DCOP call { kdDebug() << "reloaded bookmark config!" << endl; KBookmarkSettings::self()->readSettings(); parse(); // reload, and thusly recreate the menus } void KBookmarkManager::notifyChanged( TQString groupAddress ) // DCOP call { if (!m_update) return; // Reparse (the whole file, no other choice) // if someone else notified us if (callingDcopClient()->senderId() != DCOPClient::mainClient()->appId()) parse(); //kdDebug(7043) << "KBookmarkManager::notifyChanged " << groupAddress << endl; //KBookmarkGroup group = findByAddress( groupAddress ).toGroup(); //Q_ASSERT(!group.isNull()); emit changed( groupAddress, TQString::null ); } bool KBookmarkManager::showNSBookmarks() const { return KBookmarkMenu::showDynamicBookmarks("netscape").show; } void KBookmarkManager::setShowNSBookmarks( bool show ) { m_showNSBookmarks = show; if (this->path() != userBookmarksFile()) return; KBookmarkMenu::DynMenuInfo info = KBookmarkMenu::showDynamicBookmarks("netscape"); info.show = show; KBookmarkMenu::setDynamicBookmarks("netscape", info); } void KBookmarkManager::setEditorOptions( const TQString& caption, bool browser ) { dptr()->m_editorCaption = caption; dptr()->m_browserEditor = browser; } void KBookmarkManager::slotEditBookmarks() { KProcess proc; proc << TQString::fromLatin1("keditbookmarks"); if (!dptr()->m_editorCaption.isNull()) proc << TQString::fromLatin1("--customcaption") << dptr()->m_editorCaption; if (!dptr()->m_browserEditor) proc << TQString::fromLatin1("--nobrowser"); proc << m_bookmarksFile; proc.start(KProcess::DontCare); } void KBookmarkManager::slotEditBookmarksAtAddress( const TQString& address ) { KProcess proc; proc << TQString::fromLatin1("keditbookmarks") << TQString::fromLatin1("--address") << address << m_bookmarksFile; proc.start(KProcess::DontCare); } /////// void KBookmarkOwner::openBookmarkURL( const TQString& url ) { (void) new KRun(KURL( url )); } void KBookmarkOwner::virtual_hook( int, void* ) { /*BASE::virtual_hook( id, data );*/ } bool KBookmarkManager::updateAccessMetadata( const TQString & url, bool emitSignal ) { if (!s_bk_map) { s_bk_map = new KBookmarkMap(this); s_bk_map->update(); } TQValueList list = s_bk_map->find(url); if ( list.count() == 0 ) return false; for ( TQValueList::iterator it = list.begin(); it != list.end(); ++it ) (*it).updateAccessMetadata(); if (emitSignal) emit notifier().updatedAccessMetadata( path(), url ); return true; } void KBookmarkManager::updateFavicon( const TQString &url, const TQString &faviconurl, bool emitSignal ) { Q_UNUSED(faviconurl); if (!s_bk_map) { s_bk_map = new KBookmarkMap(this); s_bk_map->update(); } TQValueList list = s_bk_map->find(url); for ( TQValueList::iterator it = list.begin(); it != list.end(); ++it ) { // TODO - update favicon data based on faviconurl // but only when the previously used icon // isn't a manually set one. } if (emitSignal) { // TODO // emit notifier().updatedFavicon( path(), url, faviconurl ); } } TQString KBookmarkManager::userBookmarksFile() { return locateLocal("data", TQString::fromLatin1("konqueror/bookmarks.xml")); } KBookmarkManager* KBookmarkManager::userBookmarksManager() { return KBookmarkManager::managerForFile( userBookmarksFile() ); } KBookmarkSettings* KBookmarkSettings::s_self = 0; void KBookmarkSettings::readSettings() { KConfig config("kbookmarkrc", false, false); config.setGroup("Bookmarks"); // add bookmark dialog usage - no reparse s_self->m_advancedaddbookmark = config.readBoolEntry("AdvancedAddBookmarkDialog", false); // these three alter the menu, therefore all need a reparse s_self->m_contextmenu = config.readBoolEntry("ContextMenuActions", true); s_self->m_quickactions = config.readBoolEntry("QuickActionSubmenu", false); s_self->m_filteredtoolbar = config.readBoolEntry("FilteredToolbar", false); } KBookmarkSettings *KBookmarkSettings::self() { if (!s_self) { s_self = new KBookmarkSettings; readSettings(); } return s_self; } #include "kbookmarkmanager.moc"