/***************************************************************************
                          kmylistbox.cpp  -  description
                             -------------------
    begin                : Tue Oct 16 2001
    copyright            : (C) 2001 by Dominik Seichter
    email                : domseichter@web.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

// TQt includes
#include <tqcursor.h>
#include <tqdir.h>
#include <tqdragobject.h>
#include <tqpainter.h>
#include <tqpalette.h>
#include <tqregexp.h>

// TDE includes
#include <tdeapplication.h>
#include <kdirlister.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdeio/previewjob.h>
#include <tdeio/netaccess.h>
#include <tqptrlist.h>
#include <kurldrag.h>
#include <kurllabel.h>
#include <kpixmap.h>
#include <kpixmapeffect.h> 

// Own includes
#include "kmylistbox.h"
#include "krecursivelister.h"
#include "threadedlister.h"

using namespace TDEIO;

KMyListBox::KMyListBox(TQWidget* parent, const char* name, WFlags fl)
    :TDEListBox(parent, name, fl)
{
    m_running_lister_counter = 0;

    drag = ctrlPressed = shiftPressed = mousePressed = false;
    moving = false;
    m_sorting = UNSORTED;
    
    label = new KURLLabel( TQString(), "<br>" + i18n("Please add some files...") + "<br>", this );
    label->setFrameStyle( TQFrame::GroupBoxPanel | TQFrame::Sunken );
    
    setAcceptDrops( true );
    setSelectionMode(Extended); // was extended before 2.9.0

    connect( this, TQ_SIGNAL(doubleClicked(TQListBoxItem*)), this, TQ_SLOT(openFile(TQListBoxItem*)));
    connect( this, TQ_SIGNAL(returnPressed(TQListBoxItem*)), this, TQ_SLOT(openFile(TQListBoxItem*)));
    connect( label, TQ_SIGNAL( leftClickedURL() ), this, TQ_SIGNAL( addFiles() ) );
    
    positionLabel();
}

KMyListBox::~KMyListBox()
{
}

KURL KMyListBox::url( int index ) const
{
    return static_cast<KMyListBoxItem*>( item( index ) )->url();
}

bool KMyListBox::dir( int index ) const
{
    return static_cast<KMyListBoxItem*>( item( index ) )->dir();
}

void KMyListBox::setPreviewSize( int size )
{
    previewSize = size;
}

void KMyListBox::removeItem( int index )
{
    TDEListBox::removeItem( index );
}

void KMyListBox::addFile( const KURL & filename, bool isfile )
{
    // Check if the URL is a file and no dir
    // Is the file already in our list ?
    if( !isfile && !isFile( filename ) )
        return;

    if( isInList( filename ) )
        return;

    insertItem( new KMyListBoxItem( filename, false ), -1 );

    positionLabel();
}

void KMyListBox::addDirName( const KURL & dirname )
{
    if(!isInList( dirname ) )
    {
        insertItem( new KMyListBoxItem( dirname, true ), -1 );
        positionLabel();
    }
}

bool KMyListBox::isFile( const KURL & f, bool autoadd )
{
    UDSEntry entry;

    NetAccess::stat(f, entry, 0);
    KFileItem file( entry, f );
    if( !file.isReadable() )
        return false;

    if( file.isDir() ) {
        if( autoadd )
            addDir( f, "*", false, true );
        return false;
    }

    return true;
}

void KMyListBox::addDir( const KURL & dirname, const TQString & filter, bool hidden, bool recursively, bool dirnames )
{
    ThreadedLister* thl = new ThreadedLister( &m_add_mutex, &m_running_lister_counter, this );
    thl->setDirname( dirname );
    thl->setDirnames( dirnames );
    thl->setFilter( filter );
    thl->setHidden( hidden );
    thl->setRecursive( recursively );
    
    TDEApplication::setOverrideCursor( TQt::waitCursor );
    thl->start();
}

void KMyListBox::addDirName( const KURL & dirname, const TQString & filter, bool hidden, bool recursive )
{
    TDEApplication::setOverrideCursor( TQt::waitCursor );            

    if( recursive ) {
        ThreadedLister* thl = new ThreadedLister( &m_add_mutex, &m_running_lister_counter, this );
        thl->setDirname( dirname );
        thl->setDirnames( true );
        thl->setFilter( filter );
        thl->setHidden( hidden );
        thl->setRecursive( recursive );
        thl->setRecursiveDirOnlyMode( true );
        
        thl->start();        
    } else {
        // escape hiden directories
        TQString name = dirname.fileName();
        if( !hidden && name.right( 1 ) != TQString::fromLatin1(".") )
            if( !isInList( dirname ) )
                addDirName( dirname );
                
        listerDone( NULL );
    }
}

void KMyListBox::dropEvent(TQDropEvent* e)
{
    if( e->source() != this )
        e->accept(TQTextDrag::canDecode(e));

    KURL::List list;
    if( KURLDrag::decode( e, list ) )
    {
        
        TDEApplication::setOverrideCursor( TQt::waitCursor );

        setUpdatesEnabled( false );

        for( unsigned int i = 0; i < list.count(); i++ )
            addFile( list[i], false );
        
        setUpdatesEnabled( true );
        listerDone( NULL );
    }
}

void KMyListBox::dragEnterEvent(TQDragEnterEvent* e)
{
    if( e->source() != this )
        e->accept(TQTextDrag::canDecode(e));
}

void KMyListBox::viewportMousePressEvent( TQMouseEvent* e )
{
   if( moving )
        move( index( itemAt( e->pos() ) ) );
    else {
        TDEListBox::viewportMousePressEvent( e );
        TQPoint p( e->pos() );
        TQListBoxItem *i = itemAt( p );
        if ( i ) {
                presspos = e->pos();
                mousePressed = true;
        }
    }
}

void KMyListBox::viewportMouseMoveEvent( TQMouseEvent* e )
{
    if ( mousePressed && ( presspos - e->pos() ).manhattanLength() > TDEApplication::startDragDistance() ) {
        mousePressed = false;
        TQListBoxItem *item = itemAt( presspos );
        if ( item ) {
            TQStringList source = text( index( item ) );
            for( int i = 0; i < (signed int)count(); i++ )
                if( isSelected(i) && (i != index(item) ))
                    source.append( text(i) );
            TQUriDrag* ud = new TQUriDrag(viewport());
            ud->setUnicodeUris( source );
            ud->drag();
        }
    } else
        TDEListBox::viewportMouseMoveEvent( e );
}

void KMyListBox::viewportMouseReleaseEvent( TQMouseEvent* e )
{
    mousePressed = false;
    TDEListBox::viewportMouseReleaseEvent( e );
}

void KMyListBox::keyPressEvent( TQKeyEvent* e )
{
    /*
     * TODO: Document all this keyboard commands
     */
    if( e->key() == Key_Control )
        ctrlPressed = true;
    else if( e->key() == Key_Shift )
        shiftPressed = true;
    else if( e->key() == Key_Up )
        setCurrentItem( currentItem()-1 );
    else if( e->key() == Key_Down )
        setCurrentItem( currentItem()+1 );
    else if( e->key() == Key_Space )
        select( item(currentItem()) );
    else if( e->key() == Key_Return )
        openFile( item(currentItem()) );
    else if( e->key() == Key_Delete )
        emit deletePressed();
    else if( e->key() == Key_A )
        for( unsigned int i = 0; i < count(); i++ )
            setSelected( i, true );
    else if( e->key() == Key_M ) {
        moveMode();
        return;
    } else if( e->key() == Key_N )
        for( unsigned int i = 0; i < count(); i++ )
            setSelected( i, false );
    else if( e->key() == Key_End )
        setCurrentItem( count()-1 );
    else if( e->key() == Key_Home )
        setCurrentItem( 0 );
    else
        e->ignore();

    emit updateCount();
    setPreview( KMyListBoxItem::preview() );
    emit updatePreview();
}

void KMyListBox::keyReleaseEvent( TQKeyEvent* e )
{
    if( e->key() == Key_Control )
        ctrlPressed = false;
    else if( e->key() == Key_Shift )
        shiftPressed = false;
}

void KMyListBox::openFile( TQListBoxItem* item )
{
    if( item ) {
        KMyListBoxItem* it = static_cast<KMyListBoxItem*>(item);
        KFileItem* fileItem = new KFileItem( KFileItem::Unknown, KFileItem::Unknown, it->url() );
        fileItem->run();
        delete fileItem;
    }
}

void KMyListBox::moveMode()
{
    if ( !moving ) {
        moving = true;
        TDEApplication::setOverrideCursor( TQt::sizeAllCursor );
    }
}

void KMyListBox::select( TQListBoxItem* item )
{
    if( !ctrlPressed && !shiftPressed )
        /* Single click on the list box,
         * make all items but the clicked
         * one not selected */
        for( int i = 0; i < (signed int)count(); i++ )
            if( i != index(item) )
                setSelected( i, false );

    if( shiftPressed ) {
        if( currentItem() == -1 ) {
            setSelected( item, !isSelected( item ));
            setCurrentItem( item );
            return;
        }
        if( currentItem() > index(item) ) {
            for( int i = index(item); i <= currentItem(); i++ )
                setSelected( i, !isSelected( i ));
            setCurrentItem( item );
        } else if( currentItem() < index(item) ) { /* Works !*/
            for( int i = currentItem()+1; i <= index(item); i++ )
                setSelected( i, !isSelected( i ));
        } else /* c == index(item) */ /* Works !*/
            setSelected( item, !isSelected( item ));
    } else {
        setSelected( item, !isSelected( item ));
        setCurrentItem( item );
    }
}

void KMyListBox::preview( KURL::List list )
{
    TDEIO::PreviewJob* job = TDEIO::filePreview( list, previewSize, previewSize, 0, 100, false, true, 0 );
    connect( job, TQ_SIGNAL( gotPreview( const KFileItem*, const TQPixmap &) ), this, TQ_SLOT( previewDone( const KFileItem*, const TQPixmap &) ) );
    connect( job, TQ_SIGNAL( failed( const KFileItem*)), this, TQ_SLOT( previewFailed( const KFileItem* ) ));
    connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ), this, TQ_SLOT( previewFinished() ) );
    TDEApplication::setOverrideCursor( TQt::waitCursor );
}

void KMyListBox::previewDone( const KFileItem* item, const TQPixmap &pixmap )
{
    for( unsigned int i = 0; i < count(); i++ )
        if( url( i ) == item->url() ) {
            KMyListBoxItem* it = static_cast<KMyListBoxItem*>(this->item( i ));
            if( it && !pixmap.isNull() ) {
                it->setPixmap( pixmap );
                //updateItem( i );
            }
            break;
        }
}

void KMyListBox::previewFailed( const KFileItem* item )
{
    for( unsigned int i = 0; i < count(); i++ )
        if( url( i ) == item->url() ) {
            KMyListBoxItem* it = static_cast<KMyListBoxItem*>(this->item( i ));
            if( it ) {
                it->setPixmap( item->pixmap( getPreviewSize(), TDEIcon::DefaultState ) );
            }
            break;
        }
}

void KMyListBox::previewFinished()
{
    triggerUpdate( true ); //maybe false is enough
    TDEApplication::restoreOverrideCursor();
}

void KMyListBox::setPreview( bool prv )
{
    KMyListBoxItem::setPreview( prv );

    if( prv ) {
        KURL::List list;
        for( unsigned int i = 0; i < count(); i++ ) {
            KMyListBoxItem* it = static_cast<KMyListBoxItem*>(item( i ) );
            if( !it->hasPreview() )
                list.append( it->url() );
        }
        preview( list );
    }
}

void KMyListBox::setName( bool name )
{
    KMyListBoxItem::setName( name );
    setPreview( KMyListBoxItem::preview() );

    if( name ) {
        setColumnMode( FixedNumber );
    } else {
        setColumnMode( FitToWidth );
    }
}

void KMyListBox::move( int i )
{
    TDEApplication::restoreOverrideCursor();
    moving = false;

    if( !count() )
        return;

    int nbSelectedBefore = 0;
    for( unsigned int j = 0 ; j < count() ; j++ ) {
        if( isSelected( j ) ) {
            if( j < (unsigned int)i )
                nbSelectedBefore++;

            KMyListBoxItem* item = new KMyListBoxItem( static_cast<KMyListBoxItem*>(this->item( j )) );
            removeItem( j );
            j--;
            insertItem( item, i - nbSelectedBefore );
        }
    }
}

void KMyListBox::moveUp()
{
    if( count() == 0 )
        return;

    unsigned int i = 0;
    setUpdatesEnabled( false );
    do {
        if( isSelected( i ) && i ) {
            moveUp( i );

            setSelected( i-1, true );
            setCurrentItem( i-1 );
        }
        i++;
    } while( i < count() );
    setUpdatesEnabled( true );
}

void KMyListBox::moveUp( int i )
{
    if( count() == 0 )
        return;

    KMyListBoxItem* item = new KMyListBoxItem( static_cast<KMyListBoxItem*>(this->item( i )) );
    removeItem( i );
    insertItem( item, i - 1 );
}

void KMyListBox::moveDown()
{
    if( count() == 0 )
        return;

    unsigned int i = count();
    setUpdatesEnabled( false );
    do {
        i--;
        if( isSelected( i ) && (i < count()) ) {
            moveDown( i );
            setSelected( i+1, true );
            setCurrentItem( i+1 );
        }
    } while( i > 0 );
    setUpdatesEnabled( true );
}

void KMyListBox::moveDown( int i )
{
    if( count() == 0 )
        return;

    KMyListBoxItem* item = new KMyListBoxItem( static_cast<KMyListBoxItem*>(this->item( i )) );
    removeItem( i );
    insertItem( item, i + 1 );
}

bool KMyListBox::isInList( KURL text )
{
    // TODO: faster find algorithm
    for( unsigned int i = 0; i < count(); i++ ) {
        KMyListBoxItem* it = dynamic_cast<KMyListBoxItem*>(item( i ) );
        if( it && it->url() == text ) {
            return true;
        }
    }

    return false;
}

void KMyListBox::customEvent( TQCustomEvent* e )
{
    if( e->type() == ThreadedLister::TYPE() )
    {
        listerDone( (ThreadedLister*)e->data() );
    }
}

void KMyListBox::listerDone( ThreadedLister* lister )
{
    m_add_mutex.lock();
 
    if( lister )   
        delete lister;
    TDEApplication::restoreOverrideCursor();
    
    setUpdatesEnabled( false );
    setPreview( KMyListBoxItem::preview() );
    sortList();
    setUpdatesEnabled( true );

    emit updateCount();
    emit updatePreview();

    m_add_mutex.unlock(); 
}

unsigned int KMyListBox::runningAddListeners()
{ 
    unsigned int u;

    m_add_mutex.lock();
    u = m_running_lister_counter;
    m_add_mutex.unlock(); 

    return u;
}

void KMyListBox::sortAscending()
{
    m_sorting = ASCENDING;
    
    sortList();
}

void KMyListBox::sortDescending()
{
    m_sorting = DESCENDING;
    
    sortList();
}

void KMyListBox::sortRandom()
{
    m_sorting = RANDOM;
    
    sortList();
}

void KMyListBox::sortUnsorted()
{
    m_sorting = UNSORTED;
    
    sortList();
}

void KMyListBox::sortNummeric()
{
    m_sorting = NUMMERIC;
    
    sortList();
}

void KMyListBox::sortList()
{
    TDEApplication::setOverrideCursor( TQt::WaitCursor );

    if( m_sorting == ASCENDING )
        sort( true );
    else if( m_sorting == DESCENDING )    
        sort( false );
    else if( m_sorting == RANDOM )
    {
        unsigned int p = 0;
        for( unsigned int i = 0;i<count();i++)
        {
            p = TDEApplication::random() % count();
            if( p != i ) // This prevents the creation of a new ListBoxItem
                         // on the same position as before. It would not change
                         // the position of item, but cost a little bit of speed
            {
                KMyListBoxItem* item = new KMyListBoxItem( static_cast<KMyListBoxItem*>(this->item( i )) );
                removeItem( i );
                insertItem( item, p );
            }
        }
    }
    else if( m_sorting == NUMMERIC )
    {
        // a very simple bubble sort which sorts filenames by a number inside them.
        // if no number is found a lexical comparison is performed
        unsigned int z = count();
        while( z-- )
            for( unsigned int i=1;i<=z;i++)
            {
                KURL url1 = url( i - 1 );
                KURL url2 = url( i );
                if( url1.directory() != url2.directory() )
                {
                    // different directory, do a lexical comparison
                    if( text( i - 1 ).compare( text( i ) ) >= 0 )
                       moveDown( i - 1 );
                }
                else
                {
                    if( compareNummeric( url1.filename(), url2.filename() ) >= 0 )
                        moveDown( i - 1 );
                }
            }
    }

    TDEApplication::restoreOverrideCursor();
}

void KMyListBox::setSorting( int s )
{ 
    switch( s )
    {
        default:
        case UNSORTED:
            sortUnsorted();
            break;
        case ASCENDING:
            sortAscending();
            break;
        case DESCENDING:
            sortDescending();
            break;
        case RANDOM:
            sortRandom();
            break;
        case NUMMERIC:
            sortNummeric();
            break;
    }
    
    emit updatePreview();
}

int KMyListBox::compareNummeric( const TQString & s1, const TQString & s2 )
{
    unsigned int z = 0;
    unsigned int max = ( s1.length() > s2.length() ? s1.length() : s2.length() );
    
    TQString num1;
    TQString num2;
    for( z=0;z<max;z++)
    {
        //if( z >= s1.length() || z >= s2.length() )
        //    break;
            
        if( s1[z] != s2[z] )
        {
            if( z < s1.length() && s1[z].isDigit() )
                num1 = findNumInString( z, s1 );
            
            if( z < s2.length() && s2[z].isDigit() )
                num2 = findNumInString( z, s2 );
            
            if( num1.isNull() && num2.isNull() )    
                break;
                
            int a = num1.toInt();
            int b = num2.toInt();
            if( a == b )
                return s1.compare( s2 );
            else
                return ( a > b ) ? 1 : -1;
        }
    }
        
    return s1.compare( s2 );
}

const TQString KMyListBox::findNumInString( unsigned int pos, const TQString & s )
{
    TQString num;
    
    for( int i = (int)pos; i >= 0; i-- )
        if( s[i].isDigit() )
            num.prepend( s[i] );
        else
            break;
            

    for( unsigned int i = pos + 1; i < s.length(); i++ )
        if( s[i].isDigit() )
            num.append( s[i] );
        else
            break;
    
    return num;
}

void KMyListBox::resizeEvent( TQResizeEvent* e )
{
    TDEListBox::resizeEvent( e );
    positionLabel();
}

void KMyListBox::clear()
{
    TDEListBox::clear();
    positionLabel();
}

void KMyListBox::positionLabel()
{
    if( count() )
    {
        label->hide();
    }
    else
    {
        int x = (width() - label->minimumSizeHint().width()) / 2;
        int y = (height() - label->minimumSizeHint().height()) / 2;
        label->setGeometry( x, y, label->minimumSizeHint().width(), label->minimumSizeHint().height() );
        label->show();
    }
}

void KMyListBox::paintEvent( TQPaintEvent* e )
{
    //   tqDebug("Updates=%i", (int)isUpdatesEnabled() );
    //if( isUpdatesEnabled() )
        TDEListBox::paintEvent( e );
}

KMyListBoxItem::KMyListBoxItem( const KMyListBoxItem* item )
    : TQListBoxItem()
{
    m_url = item->url();
    m_dir = item->dir();
    m_has_preview = false;
    pm = *item->pixmap();
}

KMyListBoxItem::KMyListBoxItem( const KURL & u, bool b )
    : TQListBoxItem()
{
    m_url = u;
    m_dir = b;
    m_has_preview = false;
}

void KMyListBoxItem::setPixmap( const TQPixmap & pix )
{
    KMyListBox* box = static_cast<KMyListBox*>(this->listBox());
    pm.resize( box->getPreviewSize(), box->getPreviewSize() );    
    pm.fill( box->colorGroup().base() );
    TQPainter painter( &pm );
    painter.drawPixmap( (pm.width()-pix.width())/2, (pm.height()-pix.height())/2, pix );
    m_has_preview = true;
}

void KMyListBoxItem::setName( bool b )
{
    KMyListBoxItem::m_name = b;
}

void KMyListBoxItem::setPreview( bool b )
{
    KMyListBoxItem::m_preview = b;
}

void KMyListBoxItem::paint( TQPainter *painter )
{
    if( !KMyListBoxItem::m_preview ) {
        int itemHeight = height( listBox() );
        TQFontMetrics fm = painter->fontMetrics();
        int yPos = ( ( itemHeight - fm.height() ) / 2 ) + fm.ascent();
        painter->drawText( 3, yPos, text() );
    } else {
        int itemHeight = height( listBox() );
        int yPos;

        if( pm.isNull() ) {
            KFileItem item( KFileItem::Unknown, KFileItem::Unknown, m_url );
            KMyListBox* box = static_cast<KMyListBox*>(this->listBox());
            setPixmap( item.pixmap( box->getPreviewSize(), TDEIcon::DefaultState ) );
        }

        yPos = ( itemHeight - pm.height() ) / 2;
        if( !isSelected() )
            painter->drawPixmap( 3, yPos, pm);
        else
        {
            KPixmap pix =  KPixmapEffect::selectedPixmap( pm, listBox()->colorGroup().highlight() );
            painter->drawPixmap( 3, yPos, pix );
        }

        if( KMyListBoxItem::m_name && !m_url.isEmpty() ) {
            TQFontMetrics fm = painter->fontMetrics();
            yPos = ( ( itemHeight - fm.height() ) / 2 ) + fm.ascent();
            painter->drawText( pm.width() + 5, yPos, text() );
        }
    }
}

int KMyListBoxItem::height( const TQListBox* lb ) const
{
    if( !KMyListBoxItem::m_preview ) {
        int h = listBox() ? listBox()->fontMetrics().lineSpacing() + 2 : 0;
        return TQMAX( h, TQApplication::globalStrut().height() );
    } else {
        int h;
        if ( KMyListBoxItem::m_name && !m_url.prettyURL().isEmpty() )
            h = pm.height();
        else
            h = TQMAX( pm.height(), lb->fontMetrics().lineSpacing() + 2 );

        return TQMAX( h, TQApplication::globalStrut().height() );
    }
}

int KMyListBoxItem::width( const TQListBox* ) const
{
    if( !KMyListBoxItem::m_preview ) {
        int w = listBox() ? listBox()->fontMetrics().width( text() ) + 6 : 0;
        return TQMAX( w, TQApplication::globalStrut().width() );
    } else {
        if ( m_url.path().isEmpty() || !KMyListBoxItem::m_name)
            return TQMAX( pm.width() + 6, TQApplication::globalStrut().width() );
        
        return TQMAX( pm.width() + listBox()->fontMetrics().width( text() ) + 6, TQApplication::globalStrut().width() );
    }
}

TQString KMyListBoxItem::text() const
{
    return m_url.prettyURL( 0, m_url.isLocalFile() ? KURL::StripFileProtocol : KURL::NoAdjustements );
}

bool KMyListBoxItem::m_preview = false;
bool KMyListBoxItem::m_name = false;

#include "kmylistbox.moc"
