/* This file is part of the KDEproject Copyright (C) 2000 David Faure 2000 Carsten Pfeiffer 2002 Klaas Freitag 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 #include #include #include #include #include #include #include "tdefiletreeviewitem.h" #include "tdefiletreebranch.h" /* --- KFileTreeViewToplevelItem --- */ KFileTreeBranch::KFileTreeBranch( KFileTreeView *parent, const KURL& url, const TQString& name, const TQPixmap& pix, bool showHidden, KFileTreeViewItem *branchRoot ) : KDirLister( false ), m_root( branchRoot ), m_startURL( url ), m_name ( name ), m_rootIcon( pix ), m_openRootIcon( pix ), m_recurseChildren(true), m_showExtensions(true) { kdDebug( 250) << "Creating branch for url " << url.prettyURL() << endl; /* if non exists, create one */ if( ! branchRoot ) { m_root = new KFileTreeViewItem( parent, new KFileItem( url, "inode/directory", S_IFDIR ), this ); } m_root->setExpandable( true ); m_root->setPixmap( 0, pix ); m_root->setText( 0, name ); setShowingDotFiles( showHidden ); connect( this, TQT_SIGNAL( refreshItems(const KFileItemList&)), this, TQT_SLOT ( slotRefreshItems( const KFileItemList& ))); connect( this, TQT_SIGNAL( newItems(const KFileItemList&)), this, TQT_SLOT ( addItems( const KFileItemList& ))); connect( this, TQT_SIGNAL( completed(const KURL& )), this, TQT_SLOT(slCompleted(const KURL&))); connect( this, TQT_SIGNAL( started( const KURL& )), this, TQT_SLOT( slotListerStarted( const KURL& ))); connect( this, TQT_SIGNAL( deleteItem( KFileItem* )), this, TQT_SLOT( slotDeleteItem( KFileItem* ))); connect( this, TQT_SIGNAL( canceled(const KURL&) ), this, TQT_SLOT( slotCanceled(const KURL&) )); connect( this, TQT_SIGNAL( clear()), this, TQT_SLOT( slotDirlisterClear())); connect( this, TQT_SIGNAL( clear(const KURL&)), this, TQT_SLOT( slotDirlisterClearURL(const KURL&))); connect( this, TQT_SIGNAL( redirection( const KURL& , const KURL& ) ), this, TQT_SLOT( slotRedirect( const KURL&, const KURL& ))); m_openChildrenURLs.append( url ); } void KFileTreeBranch::setOpenPixmap( const TQPixmap& pix ) { m_openRootIcon = pix; if( root()->isOpen()) { root()->setPixmap( 0, pix ); } } void KFileTreeBranch::slotListerStarted( const KURL &url ) { /* set the parent correct if it is zero. */ kdDebug( 250) << "Starting to list " << url.prettyURL() << endl; } KFileTreeViewItem *KFileTreeBranch::parentKFTVItem( KFileItem *item ) { KFileTreeViewItem *parent = 0; if( ! item ) return 0; /* If it is a directory, check, if it exists in the dict. If not, go one up * and check again. */ KURL url = item->url(); // kdDebug(250) << "Item's url is " << url.prettyURL() << endl; KURL dirUrl( url ); dirUrl.setFileName( TQString::null ); // kdDebug(250) << "Directory url is " << dirUrl.prettyURL() << endl; parent = findTVIByURL( dirUrl ); // kdDebug(250) << "Returning as parent item <" << parent << ">" << endl; return( parent ); } void KFileTreeBranch::slotRefreshItems( const KFileItemList& list ) { KFileItemListIterator it( list ); kdDebug(250) << "Refreshing " << list.count() << " items !" << endl; KFileItem *currItem; KFileTreeViewItem *item = 0; while ( (currItem = it.current()) != 0 ) { item = findTVIByURL(currItem->url()); if (item) { item->setPixmap(0, item->fileItem()->pixmap( TDEIcon::SizeSmall )); item->setText( 0, item->fileItem()->text()); } ++it; } } void KFileTreeBranch::addItems( const KFileItemList& list ) { KFileItemListIterator it( list ); kdDebug(250) << "Adding " << list.count() << " items !" << endl; KFileItem *currItem; KFileTreeViewItemList treeViewItList; KFileTreeViewItem *parentItem = 0; while ( (currItem = it.current()) != 0 ) { parentItem = parentKFTVItem( currItem ); /* Only create a new KFileTreeViewItem if it does not yet exist */ KFileTreeViewItem *newKFTVI = static_cast(currItem->extraData( this )); if( ! newKFTVI ) { newKFTVI = createTreeViewItem( parentItem, currItem ); if (!newKFTVI) { // TODO: Don't fail if parentItem == 0 ++it; continue; } currItem->setExtraData( this, newKFTVI ); /* Cut off the file extension in case it is not a directory */ if( !m_showExtensions && !currItem->isDir() ) /* Need to cut the extension */ { TQString name = currItem->text(); int mPoint = name.findRev( '.' ); if( mPoint > 0 ) name = name.left( mPoint ); newKFTVI->setText( 0, name ); } } /* Now try to find out if there are children for dirs in the treeview */ /* This stats a directory on the local file system and checks the */ /* hardlink entry in the stat-buf. This works only for local directories. */ if( dirOnlyMode() && !m_recurseChildren && currItem->isLocalFile( ) && currItem->isDir() ) { KURL url = currItem->url(); TQString filename = url.directory( false, true ) + url.fileName(); /* do the stat trick of Carsten. The problem is, that the hardlink * count only contains directory links. Thus, this method only seem * to work in dir-only mode */ kdDebug(250) << "Doing stat on " << filename << endl; KDE_struct_stat statBuf; if( KDE_stat( TQFile::encodeName( filename ), &statBuf ) == 0 ) { int hardLinks = statBuf.st_nlink; /* Count of dirs */ kdDebug(250) << "stat succeeded, hardlinks: " << hardLinks << endl; // If the link count is > 2, the directory likely has subdirs. If it's < 2 // it's something weird like a mounted SMB share. In that case we don't know // if there are subdirs, thus show it as expandable. if( hardLinks != 2 ) { newKFTVI->setExpandable(true); } else { newKFTVI->setExpandable(false); } if( hardLinks >= 2 ) // "Normal" directory with subdirs { kdDebug(250) << "Emitting for " << url.prettyURL() << endl; emit( directoryChildCount( newKFTVI, hardLinks-2)); // parentItem, hardLinks-1 )); } } else { kdDebug(250) << "stat of " << filename << " failed !" << endl; } } ++it; treeViewItList.append( newKFTVI ); } emit newTreeViewItems( this, treeViewItList ); } KFileTreeViewItem* KFileTreeBranch::createTreeViewItem( KFileTreeViewItem *parent, KFileItem *fileItem ) { KFileTreeViewItem *tvi = 0; if( parent && fileItem ) { tvi = new KFileTreeViewItem( parent, fileItem, this ); } else { kdDebug(250) << "createTreeViewItem: Have no parent" << endl; } return( tvi ); } void KFileTreeBranch::setChildRecurse( bool t ) { m_recurseChildren = t; if( t == false ) m_openChildrenURLs.clear(); } void KFileTreeBranch::setShowExtensions( bool visible ) { m_showExtensions = visible; } bool KFileTreeBranch::showExtensions( ) const { return( m_showExtensions ); } /* * The signal that tells that a directory was deleted may arrive before the signal * for its children arrive. Thus, we must walk through the children of a dir and * remove them before removing the dir itself. */ void KFileTreeBranch::slotDeleteItem( KFileItem *it ) { if( !it ) return; kdDebug(250) << "Slot Delete Item hitted for " << it->url().prettyURL() << endl; KFileTreeViewItem *kfti = static_cast(it->extraData(this)); if( kfti ) { kdDebug( 250 ) << "Child count: " << kfti->childCount() << endl; if( kfti->childCount() > 0 ) { KFileTreeViewItem *child = static_cast(kfti->firstChild()); while( child ) { kdDebug(250) << "Calling child to be deleted !" << endl; KFileTreeViewItem *nextChild = static_cast(child->nextSibling()); slotDeleteItem( child->fileItem()); child = nextChild; } } kdDebug(250) << "Found corresponding KFileTreeViewItem" << endl; if( m_lastFoundURL.equals(it->url(), true )) { m_lastFoundURL = KURL(); m_lastFoundItem = 0L; } delete( kfti ); } else { kdDebug(250) << "Error: tdefiletreeviewitem: "<< kfti << endl; } } void KFileTreeBranch::slotCanceled( const KURL& url ) { // ### anything else to do? // remove the url from the childrento-recurse-list m_openChildrenURLs.remove( url); // stop animations etc. KFileTreeViewItem *item = findTVIByURL(url); if (!item) return; // Uh oh... emit populateFinished(item); } void KFileTreeBranch::slotDirlisterClear() { kdDebug(250)<< "*** Clear all !" << endl; /* this slots needs to clear all listed items, but NOT the root item */ if( m_root ) deleteChildrenOf( m_root ); } void KFileTreeBranch::slotDirlisterClearURL( const KURL& url ) { kdDebug(250)<< "*** Clear for URL !" << url.prettyURL() << endl; KFileItem *item = findByURL( url ); if( item ) { KFileTreeViewItem *ftvi = static_cast(item->extraData( this )); deleteChildrenOf( ftvi ); } } void KFileTreeBranch::deleteChildrenOf( TQListViewItem *parent ) { // for some strange reason, slotDirlisterClearURL() sometimes calls us // with a 0L parent. if ( !parent ) return; while ( parent->firstChild() ) delete parent->firstChild(); } void KFileTreeBranch::slotRedirect( const KURL& oldUrl, const KURL&newUrl ) { if( oldUrl.equals( m_startURL, true )) { m_startURL = newUrl; } } KFileTreeViewItem* KFileTreeBranch::findTVIByURL( const KURL& url ) { KFileTreeViewItem *resultItem = 0; if( m_startURL.equals(url, true) ) { kdDebug(250) << "findByURL: Returning root as a parent !" << endl; resultItem = m_root; } else if( m_lastFoundURL.equals( url, true )) { kdDebug(250) << "findByURL: Returning from lastFoundURL!" << endl; resultItem = m_lastFoundItem; } else { kdDebug(250) << "findByURL: searching by dirlister: " << url.url() << endl; KFileItem *it = findByURL( url ); if( it ) { resultItem = static_cast(it->extraData(this)); m_lastFoundItem = resultItem; m_lastFoundURL = url; } } return( resultItem ); } void KFileTreeBranch::slCompleted( const KURL& url ) { kdDebug(250) << "SlotCompleted hit for " << url.prettyURL() << endl; KFileTreeViewItem *currParent = findTVIByURL( url ); if( ! currParent ) return; kdDebug(250) << "current parent " << currParent << " is already listed: " << currParent->alreadyListed() << endl; emit( populateFinished(currParent)); emit( directoryChildCount(currParent, currParent->childCount())); /* This is a walk through the children of the last populated directory. * Here we start the dirlister on every child of the dir and wait for its * finish. When it has finished, we go to the next child. * This must be done for non local file systems in dirOnly- and Full-Mode * and for local file systems only in full mode, because the stat trick * (see addItem-Method) does only work for dirs, not for files in the directory. */ /* Set bit that the parent dir was listed completely */ currParent->setListed(true); kdDebug(250) << "recurseChildren: " << m_recurseChildren << endl; kdDebug(250) << "isLocalFile: " << m_startURL.isLocalFile() << endl; kdDebug(250) << "dirOnlyMode: " << dirOnlyMode() << endl; if( m_recurseChildren && (!m_startURL.isLocalFile() || ! dirOnlyMode()) ) { bool wantRecurseUrl = false; /* look if the url is in the list for url to recurse */ for ( KURL::List::Iterator it = m_openChildrenURLs.begin(); it != m_openChildrenURLs.end(); ++it ) { /* it is only interesting that the url _is_in_ the list. */ if( (*it).equals( url, true ) ) wantRecurseUrl = true; } KFileTreeViewItem *nextChild = 0; kdDebug(250) << "Recursing " << url.prettyURL() << "? " << wantRecurseUrl << endl; if( wantRecurseUrl && currParent ) { /* now walk again through the tree and populate the children to get +-signs */ /* This is the starting point. The visible folder has finished, processing the children has not yet started */ nextChild = static_cast (static_cast(currParent)->firstChild()); if( ! nextChild ) { /* This happens if there is no child at all */ kdDebug( 250 ) << "No children to recuse" << endl; } /* Since we have listed the children to recurse, we can remove the entry * in the list of the URLs to see the children. */ m_openChildrenURLs.remove(url); } if( nextChild ) /* This implies that idx > -1 */ { /* Next child is defined. We start a dirlister job on every child item * which is a directory to find out how much children are in the child * of the last opened dir */ /* Skip non directory entries */ while( nextChild ) { if( nextChild->isDir() && ! nextChild->alreadyListed()) { KFileItem *kfi = nextChild->fileItem(); if( kfi && kfi->isReadable()) { KURL recurseUrl = kfi->url(); kdDebug(250) << "Starting to recurse NOW " << recurseUrl.prettyURL() << endl; openURL( recurseUrl, true ); } } nextChild = static_cast(static_cast(nextChild->nextSibling())); // kdDebug(250) << "Next child " << m_nextChild << endl; } } } else { kdDebug(250) << "skipping to recurse in complete-slot" << endl; } } /* This slot is called when a treeviewitem is expanded in the gui */ bool KFileTreeBranch::populate( const KURL& url, KFileTreeViewItem *currItem ) { bool ret = false; if( ! currItem ) return ret; kdDebug(250) << "Populating <" << url.prettyURL() << ">" << endl; /* Add this url to the list of urls to recurse for children */ if( m_recurseChildren ) { m_openChildrenURLs.append( url ); kdDebug(250) << "Appending to list " << url.prettyURL() << endl; } if( ! currItem->alreadyListed() ) { /* start the lister */ ret = openURL( url, true ); } else { kdDebug(250) << "Children already existing in treeview!" << endl; slCompleted( url ); ret = true; } return ret; } void KFileTreeBranch::virtual_hook( int id, void* data ) { KDirLister::virtual_hook( id, data ); } #include "tdefiletreebranch.moc"