# kio Library API Documentation

Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Source Files

# kfiletreeview.cpp

    00001 /* This file is part of the KDEproject
    00002    Copyright (C) 2000 David Faure <faure@kde.org>
    00003                  2000 Carsten Pfeiffer <pfeiffer@kde.org>
    00004
    00005    This library is free software; you can redistribute it and/or
    00006    modify it under the terms of the GNU Library General Public
    00007    License version 2 as published by the Free Software Foundation.
    00008
    00009    This library is distributed in the hope that it will be useful,
    00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
    00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    00012    Library General Public License for more details.
    00013
    00014    You should have received a copy of the GNU Library General Public License
    00015    along with this library; see the file COPYING.LIB.  If not, write to
    00016    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    00017    Boston, MA 02111-1307, USA.
    00018 */
    00019
    00020 #include <qheader.h>
    00021 #include <qtimer.h>
    00022 #include <kdebug.h>
    00023 #include <kdirnotify_stub.h>
    00024 #include <kglobalsettings.h>
    00025 #include <kfileitem.h>
    00026 #include <kio/global.h>
    00027 #include <kmimetype.h>
    00028 #include <kstandarddirs.h>
    00029 #include <kurldrag.h>
    00030 #include <stdlib.h>
    00031 #include <assert.h>
    00032 #include <kfiletreeview.h>
    00033 #include <kio/job.h>
    00034 #include <kio/global.h>
    00035 #include <kurldrag.h>
    00036 #include <kiconloader.h>
    00037
    00038
    00039 #include "kfiletreeview.h"
    00040 #include "kfiletreebranch.h"
    00041 #include "kfiletreeviewitem.h"
    00042
    00043 static const int autoOpenTimeout = 750;
    00044
    00045
    00046 KFileTreeView::KFileTreeView( QWidget *parent, const char *name )
    00047     : KListView( parent, name ),
    00048       m_wantOpenFolderPixmaps( true ),
    00049       m_toolTip( this )
    00050 {
    00051     setSelectionModeExt( KListView::Single );
    00052
    00053     m_animationTimer = new QTimer( this );
    00054     connect( m_animationTimer, SIGNAL( timeout() ),
    00055              this, SLOT( slotAnimation() ) );
    00056
    00057     m_currentBeforeDropItem = 0;
    00058     m_dropItem = 0;
    00059
    00060     m_autoOpenTimer = new QTimer( this );
    00061     connect( m_autoOpenTimer, SIGNAL( timeout() ),
    00062              this, SLOT( slotAutoOpenFolder() ) );
    00063
    00064     /* The executed-Slot only opens  a path, while the expanded-Slot populates it */
    00065     connect( this, SIGNAL( executed( QListViewItem * ) ),
    00066              this, SLOT( slotExecuted( QListViewItem * ) ) );
    00067     connect( this, SIGNAL( expanded ( QListViewItem *) ),
    00068              this, SLOT( slotExpanded( QListViewItem *) ));
    00069     connect( this, SIGNAL( collapsed( QListViewItem *) ),
    00070              this, SLOT( slotCollapsed( QListViewItem* )));
    00071
    00072
    00073     /* connections from the konqtree widget */
    00074     connect( this, SIGNAL( selectionChanged() ),
    00075              this, SLOT( slotSelectionChanged() ) );
    00076     connect( this, SIGNAL( onItem( QListViewItem * )),
    00077              this, SLOT( slotOnItem( QListViewItem * ) ) );
    00078     connect( this, SIGNAL(itemRenamed(QListViewItem*, const QString &, int)),
    00079              this, SLOT(slotItemRenamed(QListViewItem*, const QString &, int)));
    00080
    00081
    00082     m_bDrag = false;
    00083     m_branches.setAutoDelete( true );
    00084
    00085     m_openFolderPixmap = SmallIcon( "folder_open" );
    00086 }
    00087
    00088 KFileTreeView::~KFileTreeView()
    00089 {
    00090    // we must make sure that the KFileTreeViewItems are deleted _before_ the
    00091    // branches are deleted. Otherwise, the KFileItems would be destroyed
    00092    // and the KFileTreeViewItems had dangling pointers to them.
    00093    hide();
    00094    clear();
    00095    m_branches.clear(); // finally delete the branches and KFileItems
    00096 }
    00097
    00098
    00099 void KFileTreeView::contentsDragEnterEvent( QDragEnterEvent *ev )
    00100 {
    00101    if ( ! acceptDrag( ev ) )
    00102    {
    00103       ev->ignore();
    00104       return;
    00105    }
    00106    ev->acceptAction();
    00107    m_currentBeforeDropItem = selectedItem();
    00108
    00109    QListViewItem *item = itemAt( contentsToViewport( ev->pos() ) );
    00110    if( item )
    00111    {
    00112       m_dropItem = item;
    00113       m_autoOpenTimer->start( autoOpenTimeout );
    00114    }
    00115    else
    00116    {
    00117    m_dropItem = 0;
    00118 }
    00119 }
    00120
    00121 void KFileTreeView::contentsDragMoveEvent( QDragMoveEvent *e )
    00122 {
    00123    if( ! acceptDrag( e ) )
    00124    {
    00125       e->ignore();
    00126       return;
    00127    }
    00128    e->acceptAction();
    00129
    00130
    00131    QListViewItem *item; // itemAt( contentsToViewport( e->pos() ) );
    00132    QListViewItem *par;
    00133
    00134    findDrop( e->pos(), par, item );
    00135
    00136    if( item && item->isSelectable() )
    00137    {
    00138       setSelected( item, true );
    00139       if( item != m_dropItem ) {
    00140          m_autoOpenTimer->stop();
    00141          m_dropItem = item;
    00142          m_autoOpenTimer->start( autoOpenTimeout );
    00143       }
    00144    }
    00145    else
    00146    {
    00147       m_autoOpenTimer->stop();
    00148       m_dropItem = 0;
    00149    }
    00150 }
    00151
    00152 void KFileTreeView::contentsDragLeaveEvent( QDragLeaveEvent * )
    00153 {
    00154
    00155    // Restore the current item to what it was before the dragging (#17070)
    00156    if ( m_currentBeforeDropItem )
    00157    {
    00158       setSelected( m_currentBeforeDropItem, true );
    00159       ensureItemVisible( m_currentBeforeDropItem );
    00160    }
    00161    else
    00162       setSelected( m_dropItem, false ); // no item selected
    00163    m_currentBeforeDropItem = 0;
    00164    m_dropItem = 0;
    00165
    00166 }
    00167
    00168 void KFileTreeView::contentsDropEvent( QDropEvent *e )
    00169 {
    00170
    00171     m_autoOpenTimer->stop();
    00172     m_dropItem = 0;
    00173
    00174     kdDebug(250) << "contentsDropEvent !" << endl;
    00175     if( ! acceptDrag( e ) ) {
    00176        e->ignore();
    00177        return;
    00178     }
    00179
    00180     e->acceptAction();
    00181     QListViewItem *afterme;
    00182     QListViewItem *parent;
    00183     findDrop(e->pos(), parent, afterme);
    00184
    00185     if (e->source() == viewport() && itemsMovable())
    00186         movableDropEvent(parent, afterme);
    00187     else
    00188     {
    00189        emit dropped(e, afterme);
    00190        emit dropped(this, e, afterme);
    00191        emit dropped(e, parent, afterme);
    00192        emit dropped(this, e, parent, afterme);
    00193
    00194
    00195        KURL parentURL;
    00196        if( afterme )
    00197        {
    00198           if(  static_cast<KFileTreeViewItem*>(afterme)->isDir() )
    00199              parentURL = static_cast<KFileTreeViewItem*>(afterme)->url();
    00200           else
    00201              parentURL = static_cast<KFileTreeViewItem*>(parent)->url();
    00202        }
    00203
    00204     KURL::List urls;
    00205     KURLDrag::decode( e, urls );
    00206     emit dropped( this, e, urls );
    00207        emit dropped( urls, parentURL );
    00208     }
    00209 }
    00210
    00211 bool KFileTreeView::acceptDrag(QDropEvent* e ) const
    00212 {
    00213
    00214    bool ancestOK= acceptDrops();
    00215    // kdDebug(250) << "Do accept drops: " << parentOK << endl;
    00216    ancestOK = ancestOK && itemsMovable();
    00217    // kdDebug(250) << "ismovable: " << parentOK << endl;
    00218
    00219    /*  KListView::acceptDrag(e);  */
    00220    /* this is what KListView does:
    00221     * acceptDrops() && itemsMovable() && (e->source()==viewport());
    00222     * ask acceptDrops and itemsMovable, but not the third
    00223     */
    00224    return ancestOK && KURLDrag::canDecode( e ) &&
    00225       ( e->action() == QDropEvent::Copy
    00226         || e->action() == QDropEvent::Move
    00227         || e->action() == QDropEvent::Link );
    00228 }
    00229
    00230
    00231
    00232 QDragObject * KFileTreeView::dragObject()
    00233 {
    00234
    00235    KURL::List urls;
    00236    const QPtrList<QListViewItem> fileList = selectedItems();
    00237    QPtrListIterator<QListViewItem> it( fileList );
    00238    for ( ; it.current(); ++it )
    00239    {
    00240       urls.append( static_cast<KFileTreeViewItem*>(it.current())->url() );
    00241    }
    00242    QPoint hotspot;
    00243    QPixmap pixmap;
    00244    if( urls.count() > 1 ){
    00245       pixmap = DesktopIcon( "kmultiple", 16 );
    00246    }
    00247    if( pixmap.isNull() )
    00248       pixmap = currentKFileTreeViewItem()->fileItem()->pixmap( 16 );
    00249    hotspot.setX( pixmap.width() / 2 );
    00250    hotspot.setY( pixmap.height() / 2 );
    00251    QDragObject* dragObject = KURLDrag::newDrag( urls, this );
    00252    if( dragObject )
    00253       dragObject->setPixmap( pixmap, hotspot );
    00254    return dragObject;
    00255 }
    00256
    00257
    00258
    00259 void KFileTreeView::slotCollapsed( QListViewItem *item )
    00260 {
    00261    KFileTreeViewItem *kftvi = static_cast<KFileTreeViewItem*>(item);
    00262    kdDebug(250) << "hit slotCollapsed" << endl;
    00263    if( kftvi && kftvi->isDir())
    00264    {
    00265       item->setPixmap( 0, itemIcon(kftvi));
    00266    }
    00267
    00268 }
    00269
    00270 void KFileTreeView::slotExpanded( QListViewItem *item )
    00271 {
    00272    kdDebug(250) << "slotExpanded here !" << endl;
    00273
    00274    if( ! item ) return;
    00275
    00276    KFileTreeViewItem *it = static_cast<KFileTreeViewItem*>(item);
    00277    KFileTreeBranch *branch = it->branch();
    00278
    00279    /* Start the animation for the branch object */
    00280    if( it->isDir() && branch && item->childCount() == 0 )
    00281    {
    00282       /* check here if the branch really needs to be populated again */
    00283       kdDebug(250 ) << "starting to open " << it->url().prettyURL() << endl;
    00284       startAnimation( it );
    00285       bool branchAnswer = branch->populate( it->url(), it );
    00286       kdDebug(250) << "Branches answer: " << branchAnswer << endl;
    00287       if( ! branchAnswer )
    00288       {
    00289          kdDebug(250) << "ERR: Could not populate!" << endl;
    00290          stopAnimation( it );
    00291       }
    00292    }
    00293
    00294    /* set a pixmap 'open folder' */
    00295    if( it->isDir() && isOpen( item ) )
    00296    {
    00297       kdDebug(250)<< "Setting open Pixmap" << endl;
    00298       item->setPixmap( 0, itemIcon( it )); // 0, m_openFolderPixmap );
    00299    }
    00300 }
    00301
    00302
    00303
    00304 void KFileTreeView::slotExecuted( QListViewItem *item )
    00305 {
    00306     if ( !item )
    00307         return;
    00308     /* This opens the dir and causes the Expanded-slot to be called,
    00309      * which strolls through the children.
    00310      */
    00311     if( static_cast<KFileTreeViewItem*>(item)->isDir())
    00312     {
    00313        item->setOpen( !item->isOpen() );
    00314     }
    00315 }
    00316
    00317
    00318 void KFileTreeView::slotAutoOpenFolder()
    00319 {
    00320    m_autoOpenTimer->stop();
    00321
    00322    if ( !m_dropItem || m_dropItem->isOpen() )
    00323       return;
    00324
    00325    m_dropItem->setOpen( true );
    00326    m_dropItem->repaint();
    00327 }
    00328
    00329
    00330 void KFileTreeView::slotSelectionChanged()
    00331 {
    00332    if ( !m_dropItem ) // don't do this while the dragmove thing
    00333    {
    00334    }
    00335 }
    00336
    00337
    00338 KFileTreeBranch* KFileTreeView::addBranch( const KURL &path, const QString& name,
    00339                               bool showHidden )
    00340 {
    00341     const QPixmap& folderPix = KMimeType::mimeType("inode/directory")->pixmap( KIcon::Small );
    00342
    00343     return addBranch( path, name, folderPix, showHidden);
    00344 }
    00345
    00346 KFileTreeBranch* KFileTreeView::addBranch( const KURL &path, const QString& name,
    00347                               const QPixmap& pix, bool showHidden )
    00348 {
    00349    kdDebug(250) << "adding another root " << path.prettyURL() << endl;
    00350
    00351    /* Open a new branch */
    00352    KFileTreeBranch *newBranch = new KFileTreeBranch( this, path, name, pix,
    00353                                                      showHidden );
    00354    return addBranch(newBranch);
    00355 }
    00356
    00357 KFileTreeBranch *KFileTreeView::addBranch(KFileTreeBranch *newBranch)
    00358 {
    00359    connect( newBranch, SIGNAL(populateFinished( KFileTreeViewItem* )),
    00360             this, SLOT( slotPopulateFinished( KFileTreeViewItem* )));
    00361
    00362    connect( newBranch, SIGNAL( newTreeViewItems( KFileTreeBranch*,
    00363                                const KFileTreeViewItemList& )),
    00364             this, SLOT( slotNewTreeViewItems( KFileTreeBranch*,
    00365                         const KFileTreeViewItemList& )));
    00366
    00367    m_branches.append( newBranch );
    00368    return( newBranch );
    00369 }
    00370
    00371 KFileTreeBranch *KFileTreeView::branch( const QString& searchName )
    00372 {
    00373    KFileTreeBranch *branch = 0;
    00374    QPtrListIterator<KFileTreeBranch> it( m_branches );
    00375
    00376    while ( (branch = it.current()) != 0 ) {
    00377       ++it;
    00378       QString bname = branch->name();
    00379       kdDebug(250) << "This is the branches name: " << bname << endl;
    00380       if( bname == searchName )
    00381       {
    00382          kdDebug(250) << "Found branch " << bname << " and return ptr" << endl;
    00383          return( branch );
    00384       }
    00385    }
    00386    return ( 0L );
    00387 }
    00388
    00389 KFileTreeBranchList& KFileTreeView::branches()
    00390 {
    00391    return( m_branches );
    00392 }
    00393
    00394
    00395 bool KFileTreeView::removeBranch( KFileTreeBranch *branch )
    00396 {
    00397    if(m_branches.contains(branch))
    00398    {
    00399       delete (branch->root());
    00400       m_branches.remove( branch );
    00401       return true;
    00402    }
    00403    else
    00404    {
    00405       return false;
    00406    }
    00407 }
    00408
    00409 void KFileTreeView::setDirOnlyMode( KFileTreeBranch* branch, bool bom )
    00410 {
    00411    if( branch )
    00412    {
    00413       branch->setDirOnlyMode( bom );
    00414    }
    00415 }
    00416
    00417
    00418 void KFileTreeView::slotPopulateFinished( KFileTreeViewItem *it )
    00419 {
    00420    if( it && it->isDir())
    00421     stopAnimation( it );
    00422 }
    00423
    00424 void KFileTreeView::slotNewTreeViewItems( KFileTreeBranch* branch, const KFileTreeViewItemList& itemList )
    00425 {
    00426    if( ! branch ) return;
    00427    kdDebug(250) << "hitting slotNewTreeViewItems" << endl;
    00428
    00429    /* Sometimes it happens that new items should become selected, i.e. if the user
    00430     * creates a new dir, he probably wants it to be selected. This can not be done
    00431     * right after creating the directory or file, because it takes some time until
    00432     * the item appears here in the treeview. Thus, the creation code sets the member
    00433     * m_neUrlToSelect to the required url. If this url appears here, the item becomes
    00434     * selected and the member nextUrlToSelect will be cleared.
    00435     */
    00436    if( ! m_nextUrlToSelect.isEmpty() )
    00437    {
    00438       KFileTreeViewItemListIterator it( itemList );
    00439
    00440       bool end = false;
    00441       for( ; !end && it.current(); ++it )
    00442       {
    00443          KURL url = (*it)->url();
    00444
    00445          if( m_nextUrlToSelect.cmp(url, true ))   // ignore trailing / on dirs
    00446          {
    00447             setCurrentItem( static_cast<QListViewItem*>(*it) );
    00448             m_nextUrlToSelect = KURL();
    00449             end = true;
    00450          }
    00451       }
    00452    }
    00453 }
    00454
    00455 QPixmap KFileTreeView::itemIcon( KFileTreeViewItem *item, int gap ) const
    00456 {
    00457    QPixmap pix;
    00458    kdDebug(250) << "Setting icon for column " << gap << endl;
    00459
    00460    if( item )
    00461    {
    00462       /* Check if it is a branch root */
    00463       KFileTreeBranch *brnch = item->branch();
    00464       if( item == brnch->root() )
    00465       {
    00466          pix = brnch->pixmap();
    00467          if( m_wantOpenFolderPixmaps && brnch->root()->isOpen() )
    00468          {
    00469             pix = brnch->openPixmap();
    00470          }
    00471       }
    00472       else
    00473       {
    00474       // TODO: different modes, user Pixmaps ?
    00475       pix = item->fileItem()->pixmap( KIcon::SizeSmall ); // , KIcon::DefaultState);
    00476
    00477       /* Only if it is a dir and the user wants open dir pixmap and it is open,
    00478        * change the fileitem's pixmap to the open folder pixmap. */
    00479       if( item->isDir() && m_wantOpenFolderPixmaps )
    00480       {
    00481          if( isOpen( static_cast<QListViewItem*>(item)))
    00482              pix = m_openFolderPixmap;
    00483       }
    00484    }
    00485    }
    00486
    00487    return pix;
    00488 }
    00489
    00490
    00491 void KFileTreeView::slotAnimation()
    00492 {
    00493    MapCurrentOpeningFolders::Iterator it = m_mapCurrentOpeningFolders.begin();
    00494    MapCurrentOpeningFolders::Iterator end = m_mapCurrentOpeningFolders.end();
    00495    for (; it != end; ++it )
    00496    {
    00497       uint & iconNumber = it.data().iconNumber;
    00498       QString icon = QString::fromLatin1( it.data().iconBaseName ).append( QString::number( iconNumber ) );
    00499       // kdDebug(250) << "Loading icon " << icon << endl;
    00500       it.key()->setPixmap( 0, SmallIcon( icon )); // KFileTreeViewFactory::instance() ) );
    00501
    00502       iconNumber++;
    00503       if ( iconNumber > it.data().iconCount )
    00504          iconNumber = 1;
    00505    }
    00506 }
    00507
    00508
    00509 void KFileTreeView::startAnimation( KFileTreeViewItem * item, const char * iconBaseName, uint iconCount )
    00510 {
    00511    /* TODO: allow specific icons */
    00512    if( ! item )
    00513    {
    00514       kdDebug(250) << " startAnimation Got called without valid item !" << endl;
    00515       return;
    00516    }
    00517
    00518    m_mapCurrentOpeningFolders.insert( item,
    00519                                       AnimationInfo( iconBaseName,
    00520                                                      iconCount,
    00521                                                      itemIcon(item, 0) ) );
    00522    if ( !m_animationTimer->isActive() )
    00523       m_animationTimer->start( 50 );
    00524 }
    00525
    00526 void KFileTreeView::stopAnimation( KFileTreeViewItem * item )
    00527 {
    00528    if( ! item ) return;
    00529
    00530    kdDebug(250) << "Stoping Animation !" << endl;
    00531
    00532    MapCurrentOpeningFolders::Iterator it = m_mapCurrentOpeningFolders.find(item);
    00533    if ( it != m_mapCurrentOpeningFolders.end() )
    00534    {
    00535       if( item->isDir() && isOpen(static_cast<QListViewItem*>(item)))
    00536       {
    00537          kdDebug(250) << "Setting folder open pixmap !" << endl;
    00538          item->setPixmap( 0, itemIcon( item ));
    00539       }
    00540       else
    00541       {
    00542          item->setPixmap( 0, it.data().originalPixmap );
    00543       }
    00544       m_mapCurrentOpeningFolders.remove( item );
    00545    }
    00546    else
    00547    {
    00548       if( item )
    00549          kdDebug(250)<< "StopAnimation - could not find item " << item->url().prettyURL()<< endl;
    00550       else
    00551          kdDebug(250)<< "StopAnimation - item is zero !" << endl;
    00552    }
    00553    if (m_mapCurrentOpeningFolders.isEmpty())
    00554       m_animationTimer->stop();
    00555 }
    00556
    00557 KFileTreeViewItem * KFileTreeView::currentKFileTreeViewItem() const
    00558 {
    00559    return static_cast<KFileTreeViewItem *>( selectedItem() );
    00560 }
    00561
    00562 KURL KFileTreeView::currentURL() const
    00563 {
    00564     KFileTreeViewItem *item = currentKFileTreeViewItem();
    00565     if ( item )
    00566         return currentKFileTreeViewItem()->url();
    00567     else
    00568         return KURL();
    00569 }
    00570
    00571 void KFileTreeView::slotOnItem( QListViewItem *item )
    00572 {
    00573     KFileTreeViewItem *i = static_cast<KFileTreeViewItem *>( item );
    00574     if( i )
    00575     {
    00576        const KURL& url = i->url();
    00577        if ( url.isLocalFile() )
    00578           emit( onItem( url.path() ));
    00579        else
    00580           emit( onItem( url.prettyURL()));
    00581     }
    00582 }
    00583
    00584 void KFileTreeView::slotItemRenamed(QListViewItem* item, const QString &name, int col)
    00585 {
    00586    (void) item;
    00587    kdDebug(250) << "Do not bother: " << name << col << endl;
    00588 }
    00589
    00590 KFileTreeViewItem *KFileTreeView::findItem( const QString& branchName, const QString& relUrl )
    00591 {
    00592    KFileTreeBranch *br = branch( branchName );
    00593    return( findItem( br, relUrl ));
    00594 }
    00595
    00596 KFileTreeViewItem *KFileTreeView::findItem( KFileTreeBranch* brnch, const QString& relUrl )
    00597 {
    00598    KFileTreeViewItem *ret = 0;
    00599    if( brnch )
    00600    {
    00601       KURL url = brnch->rootUrl();
    00602
    00603       if( ! relUrl.isEmpty() && relUrl != QString::fromLatin1("/") )
    00604       {
    00605          QString partUrl( relUrl );
    00606
    00607          if( partUrl.endsWith("/"))
    00608             partUrl.truncate( relUrl.length()-1 );
    00609
    00610          url.addPath( partUrl );
    00611
    00612          kdDebug(250) << "assembled complete dir string " << url.prettyURL() << endl;
    00613
    00614          KFileItem *fi = brnch->find( url );
    00615          if( fi )
    00616          {
    00617             ret = static_cast<KFileTreeViewItem*>( fi->extraData( brnch ));
    00618             kdDebug(250) << "Found item !" <<ret << endl;
    00619          }
    00620       }
    00621       else
    00622       {
    00623          ret = brnch->root();
    00624       }
    00625    }
    00626    return( ret );
    00627 }
    00628
    00631
    00632
    00633 void KFileTreeViewToolTip::maybeTip( const QPoint & )
    00634 {
    00635 #if 0
    00636     QListViewItem *item = m_view->itemAt( point );
    00637     if ( item ) {
    00638         QString text = static_cast<KFileViewItem*>( item )->toolTipText();
    00639         if ( !text.isEmpty() )
    00640             tip ( m_view->itemRect( item ), text );
    00641     }
    00642 #endif
    00643 }
    00644
    00645 void KFileTreeView::virtual_hook( int id, void* data )
    00646 { KListView::virtual_hook( id, data ); }
    00647
    00648 #include "kfiletreeview.moc"

This file is part of the documentation for kdelibs Version 3.1.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Jan 28 13:13:35 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001