/* -*- c++ -*-
 *
 * infolist.cpp
 *
 * Copyright (C) 2003, 2004 Petter Stokke <ummo@hellokitty.com>
 * Copyright (C) 2003, 2004 Sebastian Sauer <mail@dipe.org>
 * Copyright (C) 2006       Christian Muehlhaeuser <chris@chris.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.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <klistview.h>
#include <kconfig.h>
#include <kmimetype.h>
#include <kpopupmenu.h>
#include <kprogress.h>
#include <kiconloader.h>

#include <qvariant.h>
#include <qpainter.h>
#include <qheader.h>
#include <qcursor.h>

#include <sys/types.h>
#include <time.h>

#include "infolist.h"
#include "infolist.moc"
#include "kmldonkey.h"
#include "imageloader.h"

#include "searchinfo.h"
#include "clientinfo.h"
#include "serverinfo.h"
#include "fileinfo.h"
#include "network.h"

InfoList::InfoList( QWidget* parent, const char* name, bool headervisiblepopup )
    : KListView( parent, name )
{
    if ( name != 0 )
        m_name = name;

    setSelectionModeExt( KListView::Extended );
    setAllColumnsShowFocus( true );
    setShowSortIndicator( true );
    setResizeMode( KListView::NoColumn );
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
    setMinimumSize( 1, 1 );

    m_columnCount = 0;
    m_sortInterval = 0;
    m_sortTimer = new QTimer( this );
    connect( m_sortTimer, SIGNAL( timeout() ), this, SLOT( sortTimer() ) );

    if ( headervisiblepopup )
    {
        m_headerMenu = new KPopupMenu( this );
        m_headerMenu->insertTitle( i18n( "Show" ) );
        m_headerMenu->setCheckable( true );
        connect( m_headerMenu, SIGNAL( activated( int ) ), SLOT( toggleColumnVisible( int ) ) );
    }
    else
        m_headerMenu = 0;
}


InfoList::~InfoList()
{
    saveLayout();
}


void
InfoList::initialise()
{
    KConfig *config = KGlobal::config();
    if ( config && !m_name.isEmpty() )
    {
        config->setGroup( m_name );
        if ( config->hasKey( "HideHeader" ) )
            hideHeader = config->readListEntry( "HideHeader" );
    }

    for ( int i = 0; i < m_columnCount; i++ )
    {
        bool hidecol = m_headerMenu && hideHeader.find( m_columns[i] ) != hideHeader.end();

        if ( m_headerMenu )
        {
            m_headerMenu->insertItem( m_columns[i], i );
            m_headerMenu->setItemChecked( i, ! hidecol );
        }

        if ( !hidecol )
            showColumn( i );
    }

    if ( config && !m_name.isEmpty() )
    {
        restoreLayout( config, m_name );

        QStringList cols = config->readListEntry( "ColumnWidths" );
        int i = 0;

        kdDebug() << m_name << endl;

        QStringList::ConstIterator it = cols.constBegin();
        const QStringList::ConstIterator itEnd = cols.constEnd();
        for ( ; it != itEnd; ++it )
            setColumnWidth( i++, (*it).toInt() );

        if ( sortColumn() >= header()->count() && header()->count() > 0 )
            setSorting( 0 ); // workaround
    }
}


void
InfoList::saveLayout()
{
    KConfig *config = KGlobal::config();
    if ( !config || m_name.isEmpty() )
        return;

    KListView::saveLayout( config, m_name );
    config->setGroup( m_name );

    QStringList widths;
    const int colCount = columns();
    for ( int i = 0; i < colCount; ++i )
        widths << QString::number( columnWidth( i ) );

    config->writeEntry( "ColumnWidths", widths );

    if ( m_headerMenu )
    {
        QStringList hideHeader;
        for ( int i = 0; i < m_columnCount; i++ )
            if ( !m_realVisible.contains( i ) )
                hideHeader.append( m_columns[i] );

        config->writeEntry( "HideHeader", hideHeader );
    }
}


QPtrList<QListViewItem>
InfoList::selectedItems() const
{
    QPtrList<QListViewItem> list;
    QListViewItemIterator it(const_cast<InfoList*>(this), QListViewItemIterator::Selected|QListViewItemIterator::Visible);
    for(; it.current(); ++it) list.append(it.current());
    return list;
}


int
InfoList::addColumn( const QString& label, int width, QListView::WidthMode widthMode, bool hideColumn )
{
    m_columns.insert( m_columnCount, label );
    m_columnWidth.insert( m_columnCount, width );
    m_columnWidthMode.insert( m_columnCount, widthMode );
    if ( hideColumn ) hideHeader.append( label );

    return m_columnCount++;
}


int
InfoList::visibleToReal( int c ) const
{
    return m_visibleReal[c];
}


int
InfoList::realToVisible( int c )
{
    return m_realVisible.contains(c) ? m_realVisible[c] : -1;
}


int
InfoList::realColumnCount() const
{
    return m_columnCount;
}


int
InfoList::sortInterval()
{
    return m_sortInterval;
}


void
InfoList::setSortInterval( int sec )
{
    m_sortInterval = sec;

    if ( m_sortTimer->isActive() )
    {
        if ( m_sortInterval > 0 )
            m_sortTimer->changeInterval( m_sortInterval * 1000 );
        else
            m_sortTimer->stop();
    }
    else
        if ( m_sortInterval > 0 )
            m_sortTimer->start( m_sortInterval * 1000 );
}


void
InfoList::showColumn( int c )
{
    int newpos = -1;
    for ( int i = c + 1; ( newpos = realToVisible( i ) ) < 0 && i < m_columnCount; i++ );

    if ( newpos >= 0 )
    {
        KListView::addColumn( QString::null );
        m_visibleReal.insert( m_visibleReal.at( newpos ), c );

        for( int i = columns() - 1; i >= newpos; i-- )
        {
            int r = visibleToReal( i );
            setColumnText( i, m_columns[r] );

            if ( i > newpos )
                setColumnWidth( i, columnWidth( i-1 ) );
            //else setColumnWidth(i, m_columnWidth[r]); // results in a invisible column...

            setColumnWidthMode( i, m_columnWidthMode[r] );
            m_realVisible.replace( r, i );
        }
    }
    else
    {
        KListView::addColumn( m_columns[c], m_columnWidth[c] );
        setColumnWidthMode( c, m_columnWidthMode[c] );
        m_visibleReal.append( c );
        m_realVisible.replace( c, KListView::columns() - 1 );
    }

    if ( m_headerMenu )
        m_headerMenu->setItemChecked( c, true );
}


void
InfoList::hideColumn( int c )
{
    //FIXME This is a workaround for a crasher that occurs if all columns are removed! There should be a safer way!
    if ( !m_realVisible.contains( c ) || header()->count() < 2 )
        return;

    int p = m_realVisible[c];
    m_columnWidth.replace( c, columnWidth( p ) ); // remember a changed columnwidth
    int sortcol = sortColumn();
    KListView::removeColumn( p );

    if ( sortcol == p && header()->count() > 0 ) //neededtopreventcrashes
        setSorting( (p > 0) ? p - 1 : 0 );

    m_visibleReal.remove( m_visibleReal.at( p ) );
    m_realVisible.remove( c );

    if ( m_headerMenu )
        m_headerMenu->setItemChecked( c, false );

    for ( QMap<int,int>::Iterator it = m_realVisible.begin(); it != m_realVisible.end(); ++it )
        if ( it.data() > p )
            it.data() = it.data() - 1; // decrement positions of following visibled columns
}


void
InfoList::toggleColumnVisible( int c )
{
    if ( m_realVisible.contains( c ) )
        hideColumn( c );
    else
        showColumn( c );

    InfoItem* item = (InfoItem*)firstChild();
    while ( item )
    {
        item->refresh();
        item = (InfoItem*)item->nextSibling();
    }
}


void
InfoList::sortTimer()
{
    sort();
}


bool
InfoList::eventFilter( QObject *watched, QEvent *event )
{
    if ( watched == header() )
    {
        switch ( event->type() )
        {
            case QEvent::MouseButtonPress:
                if ( m_headerMenu && static_cast<QMouseEvent*>(event)->button() == RightButton )
                    m_headerMenu->popup( QCursor::pos() );
                break;

            default:
                break;
        }
    }

    return KListView::eventFilter( watched, event );
}


void
InfoList::focusInEvent( QFocusEvent* event )
{
    KListView::focusInEvent( event );
    emit gotFocus();
    emit focusChanged();
}


void
InfoList::focusOutEvent( QFocusEvent* event )
{
    KListView::focusInEvent( event );
    emit lostFocus();
    emit focusChanged();
}


void
InfoItemCacheEntry::update( const QString& str )
{
    m_str = str;
    m_isNum = false;
}


void
InfoItemCacheEntry::update( const QString& str, double num )
{
    m_str = str;
    m_num = num;
    m_isNum = true;
}


int
InfoItemCacheEntry::compare( const InfoItemCacheEntry* i ) const
{
    // We assume compares are never made against an entry with a different type.
    if ( m_isNum )
        return ( m_num < i->m_num ) ? -1 : ( m_num > i->m_num ) ? 1 : 0;
    else
        return m_str.lower().localeAwareCompare( i->m_str.lower() );
}


InfoItem::InfoItem( KListView *parent, int file )
    : KListViewItem( parent )
{
    fileno = file;
    cache.setAutoDelete( true );
}


InfoItem::~InfoItem()
{
}


void
InfoItem::refresh( bool alsoRepaint )
{
    InfoList* list = (InfoList*)listView();
    int t = list->columns();
    InfoItemCacheEntry* e = cache.first();

    for ( int c = 0; c < t; c++ )
    {
        if ( !e )
            cache.append( e = new InfoItemCacheEntry() );

        int p = list->visibleToReal( c );
        if ( isNumeric( p ) )
            e->update( xtext( p ), numeric( p ) );
        else
            e->update( xtext( p ) );

        e = cache.next();
    }

    if ( alsoRepaint )
        repaint();
}


QString
InfoItem::text( int column ) const
{
    const InfoItemCacheEntry* e = cacheEntry( column );
    if ( !e )
        return QString::null;

    return e->text();
}


void
InfoItem::setFileNo( int file )
{
    fileno = file;
    refresh();
}


int
InfoItem::fileNo()
{
    return fileno;
}


double
InfoItem::numeric( int ) const
{
    return 0.0;
}


bool
InfoItem::isNumeric( int ) const
{
    return false;
}


const InfoItemCacheEntry*
InfoItem::cacheEntry( int column ) const
{
    return ( (QPtrList<InfoItemCacheEntry>)cache ).at( column );
}


int
InfoItem::compare( QListViewItem* item, int col, bool ascending ) const
{
    int i = cacheEntry( col )->compare( static_cast<InfoItem*>(item)->cacheEntry( col ) );
    if ( i == 0 )
    {
        // stable sorting
        // stable sorting means: if two items compare equal, thir order after sorting should equal their order before
        i = ( this->itemPos() < static_cast<InfoItem*>(item)->itemPos() ? 1 : -1 );
        return (ascending) ? -1 * i : i;
    }

    return i;
}


// Utility functions
QString humanReadableSize( int64 rsz )
{
    QString foo;
    double sz = (double)rsz;

    if ( KMLDonkey::App->humanReadableSizes )
    {
        if ( sz >= (100.0 * 1024.0 * 1024.0 * 1024.0) )
        {
            sz = sz / (1024.0 * 1024.0 * 1024.0);
            foo = i18n( "gigabyte suffix", "%1G" ).arg( KGlobal::locale()->formatNumber( sz, 1 ) );
        } else if ( sz >= (10.0 * 1024.0 * 1024.0 * 1024.0) )
        {
            sz = sz / (1024.0 * 1024.0 * 1024.0);
            foo = i18n( "gigabyte suffix", "%1G" ).arg( KGlobal::locale()->formatNumber( sz, 2 ) );
        } else if ( sz >= (1024.0 * 1024.0 * 1024.0) )
        {
            sz = sz / (1024.0 * 1024.0 * 1024.0);
            foo = i18n( "gigabyte suffix", "%1G" ).arg( KGlobal::locale()->formatNumber( sz, 3 ) );
        } else if ( sz >= (1024.0 * 1024.0) )
        {
            sz = sz / (1024.0 * 1024.0);
            foo = i18n( "megabyte suffix", "%1M" ).arg( KGlobal::locale()->formatNumber( sz, 1 ) );
        } else if ( sz >= 1024.0)
        {
            sz = sz / 1024.0;
            foo = i18n( "kilobyte suffix", "%1K" ).arg( KGlobal::locale()->formatNumber( sz, 1 ) );
        } else
            foo = KGlobal::locale()->formatNumber( sz, 0 );
    }
    else
        foo = KGlobal::locale()->formatNumber( sz, 0 );

    return foo;
}


QString humanReadableSpeed( double sp )
{
    if (!sp)
        return i18n( "signifies absence of data in list columns", "-" );
    else
        return KGlobal::locale()->formatNumber( sp / 1024.0, 1 );
}


QString humanReadableTime( time_t t, bool shortFormat )
{
    if ( !t )
        return i18n( "zero seconds", "0s" );
    if ( t < 0 )
        return i18n( "signifies absence of data in list columns", "-" );

    QString foo;
    int f = 0;

    if ( t > 86400 )
    {
        foo += i18n( "number of days", "%1d " ).arg( KGlobal::locale()->formatNumber( t/86400, 0 ) );
        t %= 86400;
        f = 1;
        if ( shortFormat )
            return foo.simplifyWhiteSpace();
    }
    if ( t > 3600 )
    {
        foo += i18n( "number of hours", "%1h " ).arg( KGlobal::locale()->formatNumber( t/3600, 0 ) );
        t %= 3600;
        if ( shortFormat )
            return foo.simplifyWhiteSpace();
    }
    if ( t > 60 )
    {
        foo += i18n( "number of minutes", "%1m " ).arg( KGlobal::locale()->formatNumber( t/60, 0 ) );
        t %= 60;
        if ( shortFormat )
            return foo.simplifyWhiteSpace();
    }

    if ( t && !f )
        foo += i18n( "number of seconds", "%1s" ).arg( KGlobal::locale()->formatNumber( t, 0 ) );

    return foo.simplifyWhiteSpace();
}


double calculateETANumeric( FileInfo* fi )
{
//    if (!fi->fileSpeed() || fi->fileSize() <= fi->fileDownloaded()) return 0;
//    return (double)(fi->fileSize() - fi->fileDownloaded()) / fi->fileSpeed();

    if ( fi->fileSize() <= fi->fileDownloaded() )
        return 0;

    if ( !( fi->fileDownloaded() - fi->fileFirstDownloaded() ) || !( time(0) - fi->fileFirstTime() ) )
        return 3600*24*365;

    return (double)( ( fi->fileSize() - fi->fileDownloaded() ) / ( fi->fileDownloaded() - fi->fileFirstDownloaded() ) * ( time(NULL) - fi->fileFirstTime() ) );
}


QString calculateETA( FileInfo* fi )
{
    if ( fi->fileSize() < fi->fileDownloaded() )
        return i18n( "file should have completed already", "Overdue" );

    if ( fi->fileSize() == fi->fileDownloaded() )
        return i18n( "file is just about to complete", "Imminent" );

    if ( !(fi->fileDownloaded() - fi->fileFirstDownloaded() ) || !( time(0) - fi->fileFirstTime() ) )
        return i18n( "signifies absence of data in list columns", "-" );

    return humanReadableTime( (time_t)( (double)( ( fi->fileSize() - fi->fileDownloaded() ) /
                                                  ( fi->fileDownloaded() - fi->fileFirstDownloaded() ) *
                                                  ( time(0) - fi->fileFirstTime() ) ) ), false );

    //return humanReadableTime((time_t)((double)(fi->fileSize() - fi->fileDownloaded()) / fi->fileSpeed()), false);
}


QString humanReadablePriority( int pri )
{
    if ( pri > 0 )
        return pri > 10 ? i18n( "very high priority", "Very high" ) : i18n( "high priority", "High" );

    if ( pri < 0 )
        return pri < -10 ? i18n( "very low priority", "Very low" ) : i18n( "low priority", "Low" );

    return i18n( "normal priority", "Normal" );
}


DownloadFile::DownloadFile( KListView *parent, int file )
    : InfoItem( parent, file )
    , AvailabilityRenderer( file )
{
    FileInfo* it = KMLDonkey::App->donkey->findDownloadFileNo( file );
    if ( it )
    {
        Network *n = KMLDonkey::App->donkey->findNetworkNo( it->fileNetwork() );
        if ( n )
            setPixmap( 0, ImageLoader::imageForNetwork( n ) );
    }

    m_lastFlush = 0;
    m_progress = 0;
    setRenameEnabled( 0, true );
    refresh();
}


void
DownloadFile::setText( int col, const QString& name )
{
    InfoItem::setText( col, name );
    if ( col )
        return;

    KMLDonkey::App->donkey->renameFile( fileno, name );
}


QString
DownloadFile::xtext( int column ) const
{
    QString foo;
    FileInfo* it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );
    if ( !it )
    {
        if ( !column )
            return i18n( "Unknown file" );

        return i18n( "signifies absence of data in list columns", "-" );
    }

    switch ( column )
    {
        case 0: // name
            return it->fileName();
        case 1: // Priority
            return humanReadablePriority( it->filePriority() );
        case 2: // status
            switch ( it->fileState() )
            {
                case FileInfo::Downloading:
                    if ( !it->fileSpeed() )
                        return i18n( "looking for file sources", "Looking" );
                    return i18n( "file is being downloaded", "Downloading" );

                case FileInfo::Paused: return i18n( "file is paused", "Paused" );
                case FileInfo::Complete: return i18n( "file is complete", "Complete" );
                case FileInfo::Shared: return i18n( "file is being shared", "Shared" );
                case FileInfo::Cancelled: return i18n( "file has been canceled", "Canceled" );
                case FileInfo::New: return i18n( "new file added to list", "New" );
                case FileInfo::Aborted: return i18n( "file has been aborted with a reason", "Aborted: %1" ).arg( it->fileAbortedMsg() );
                case FileInfo::Queued: return i18n( "file has been queued", "Queued" );
                default: return i18n( "unknown host state (unknown ID code)", "Unknown (%1)" ).arg( it->fileState() );
            }

            return i18n( "unknown file state", "Unknown" );
        case 3: // speed
            return ( it->fileSpeed() > 0 ) ? humanReadableSpeed( it->fileSpeed() ) : QString::null;
        case 4: // size
            return humanReadableSize( it->fileSize() );
        case 5: // downloaded
            return humanReadableSize( it->fileDownloaded() );
        case 6: // remaining
            return i18n( "percentage", "%1%" ).arg( KGlobal::locale()->formatNumber( it->fileSize() ? ( it->fileDownloaded() * 100.0 ) / it->fileSize() : 0.0, 1 ) );
        case 7: // eta
            return calculateETA( it );
        case 8: // network
            return KMLDonkey::App->donkey->findNetworkNo( it->fileNetwork() )->networkName();
        case 9: // availability
            return QString::null;
        case 10: // hash
            return it->fileUid();
        case 11: // last seen
            return humanReadableTime( (time_t)it->fileLastSeen(), true );
        case 12: // age
        {
            time_t age = time( 0 ) - it->fileAge();
            return humanReadableTime( age, true );
        }
        case 13: // filetype
            return KMimeType::findByURL( KURL( "file:/" + it->fileName() ), 0, false, true )->comment();

        default:
            return "ERROR!";
    }
}


int DownloadFile::compare( QListViewItem* item, int col, bool b ) const
{
    bool sortdownloadingfirst = KMLDonkey::App->sortDownloadingFirst();
    bool sortpausedlast = KMLDonkey::App->sortPausedLast();

    if ( sortdownloadingfirst || sortpausedlast )
    {
        FileInfo* it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );
        FileInfo* it2 = KMLDonkey::App->donkey->findDownloadFileNo( ((DownloadFile*)item)->fileNo() );

        if ( sortdownloadingfirst )
        {
            if ( it->fileSpeed() < it2->fileSpeed() && it->fileSpeed() == 0 )
                return b ? 1 : -1;
            else if ( it->fileSpeed() > it2->fileSpeed() && it2->fileSpeed() == 0 )
                return b ? -1 : 1;
        }

        if ( sortpausedlast )
        {
            if ( it->fileState() == FileInfo::Paused && it->fileState() != it2->fileState() )
                return b ? 1 : -1;
            if ( it2->fileState() == FileInfo::Paused && it->fileState() != it2->fileState() )
                return b ? -1 : 1;
        }
    }

    return InfoItem::compare( item, col, b );
}


double DownloadFile::numeric( int col ) const
{
    FileInfo* it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );
    if ( !it )
        return 0.0;

    switch ( col )
    {
        case 1:
            return (double)it->filePriority();
        case 4:
            return (double)it->fileSize();
        case 5:
            return (double)it->fileDownloaded();
        case 6:
            return it->fileSize() ? ( (double)it->fileDownloaded() * 100.0 ) / (double)it->fileSize() : 0.0;
        case 7:
            return calculateETANumeric( it );
        case 3:
            return it->fileSpeed();
        case 9:
            return (double)it->fileSources().size();
        case 11:
            return (double)( (time_t)it->fileLastSeen() );
        case 12:
            return (double)( time(0) - (time_t)it->fileAge() );

        default:
            return 0.0;
    }
}


bool DownloadFile::isNumeric( int col ) const
{
    switch ( col )
    {
        case 1:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        case 9:
        case 11:
        case 12:
            return true;

        default:
            return false;
    }
}


void DownloadFile::paintCell( QPainter* p, const QColorGroup& cg, int col, int w, int align )
{
    if ( col == dynamic_cast<InfoList*>(listView())->realToVisible( 9 ) )
    {
        QRect foo( 0, 0, w, height() );
        p->setBrush( Qt::red );
        p->setPen( Qt::green );
        paintAvailability( *p, foo );

        return;
    }

    if ( col == dynamic_cast<InfoList*>(listView())->realToVisible( 6 ) && KMLDonkey::App->displayProgressbar )
    {
        FileInfo* it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );
        if ( !it )
            return;

        if ( !m_progress )
            m_progress = new KProgress( 100, listView() );

        m_progress->setProgress( it->fileSize() ? (int)( ( (double)it->fileDownloaded() * 100.0 ) / (double)it->fileSize() ) : 0 );
        m_progress->setFixedSize( w, height() );
        p->drawPixmap( 0, 0, QPixmap::grabWidget( m_progress ), 0, 0, w, height() );
        return;
    }

    QColorGroup colgrp( cg );
    if ( KMLDonkey::App->coloredViews )
    {
        FileInfo* it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );
        if ( it )
        {
            switch ( it->fileState() )
            {
                case FileInfo::Downloading:
                    if ( it->fileSpeed() > 0 )
                        colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorDownloadDownloading );
                    else if ( it->fileLastSeen()/86400 >= 100 ) // 100 days or older
                        colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorDownloadNotAvailable );
                    else
                        colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorDownloadLooking );
                    break;

                case FileInfo::Paused:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorDownloadPaused );
                    break;

                case FileInfo::Queued:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorDownloadQueued );
                    break;

                default:
                    break;
            }
        }
    }

    p->save();
    InfoItem::paintCell( p, colgrp, col, w, align );
    p->restore();
}


int DownloadFile::width( const QFontMetrics& fm, const QListView* lv, int c ) const
{
    if ( c == 9 )
        return ( chunkNo > 100 ) ? 100 : chunkNo;

    return InfoItem::width( fm, lv, c );
}


void DownloadFile::flush()
{
    // Hardcoded 10 second limit on availability refresh,
    // I doubt this is an unreasonable minimum for anyone.
    if ( m_lastFlush + 10 < time( 0 ) )
    {
        loseCache();
        m_lastFlush = time( 0 );
    }
}


DownloadedFile::DownloadedFile( KListView *parent, int file )
    : InfoItem( parent, file )
{
    FileInfo* it = KMLDonkey::App->donkey->findDownloadedFileNo( file );
    if ( !it )
        it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );

    if ( it )
    {
        Network *n = KMLDonkey::App->donkey->findNetworkNo( it->fileNetwork() );
        if ( n )
            setPixmap( 0, ImageLoader::imageForNetwork( n ) );
    }

    refresh();
}


QString DownloadedFile::xtext( int column ) const
{
    FileInfo* it = KMLDonkey::App->donkey->findDownloadedFileNo( fileno );
    if ( !it )
        it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );

    if ( !it )
    {
        if ( !column )
            return i18n( "Unknown file" );

        return i18n( "signifies absence of data in list columns", "-" );
    }

    switch ( column )
    {
        case 0: // name
            return it->fileName();
        case 1: // size
            return humanReadableSize( it->fileSize() );
        case 2: // format
            return it->fileFormatInfo();
        case 3: // network
            return KMLDonkey::App->donkey->findNetworkNo( it->fileNetwork() )->networkName();
        case 4: // hash
            return it->fileUid();
        case 5: // filetype
            return KMimeType::findByURL( KURL( "file:/" + it->fileName() ), 0, false, true )->comment();

        default:
            return "ERROR!";
    }
}


double DownloadedFile::numeric( int col ) const
{
    FileInfo* it = KMLDonkey::App->donkey->findDownloadedFileNo( fileno );
    if ( !it )
        it = KMLDonkey::App->donkey->findDownloadFileNo( fileno );
    if ( !it )
        return 0.0;

    switch ( col )
    {
        case 1:
            return (double)it->fileSize();

        default:
            return 0.0;
    }
}


bool DownloadedFile::isNumeric( int col ) const
{
    switch ( col )
    {
        case 1:
            return true;

        default:
            return false;
    }
}



ServerInfoItem::ServerInfoItem( KListView *parent, int file )
    : InfoItem( parent, file )
{
    ServerInfo* it = KMLDonkey::App->donkey->findServerNo( fileno );
    if ( it )
    {
        Network *n = KMLDonkey::App->donkey->findNetworkNo( it->serverNetwork() );
        if ( n )
            setPixmap( 0, ImageLoader::imageForNetwork( n ) );
    }

    setRenameEnabled( 0, true );
    refresh();
}


void ServerInfoItem::setText( int col, const QString& name )
{
    InfoItem::setText( col, name );
    if ( col )
        return;

    KMLDonkey::App->donkey->renameServer( fileno, name );
}


QString ServerInfoItem::xtext( int column ) const
{
    ServerInfo* it = KMLDonkey::App->donkey->findServerNo( fileno );
    if ( !it )
    {
        if ( !column )
            return i18n( "Unknown server" );

        return i18n( "signifies absence of data in list columns", "-" );
    }

    switch ( column )
    {
        case 0: // name
            return it->serverName();
        case 1: // network
            return KMLDonkey::App->donkey->findNetworkNo( it->serverNetwork() )->networkName();
        case 2: // status
            switch ( it->serverState() )
            {
                case ClientInfo::NotConnected:
                case ClientInfo::NotConnected2:
                    return i18n( "not connected to host", "Not connected" );
                case ClientInfo::Connecting:
                    return i18n( "connecting to host", "Connecting" );
                case ClientInfo::Initiating:
                    return i18n( "initiating connection to host", "Initiating" );
                case ClientInfo::Downloading:
                    return i18n( "downloading from host", "Downloading" );
                case ClientInfo::Connected:
                case ClientInfo::Connected2:
                case ClientInfo::Connected3:
                    return i18n( "connected to host", "Connected" );
                case ClientInfo::NewHost:
                    return i18n( "new host added to list", "New host" );
                case ClientInfo::Removed:
                    return i18n( "host has been removed", "Removed" );
                case ClientInfo::Blacklisted:
                    return i18n( "host is blacklisted", "Blacklisted" );

                default:
                    return i18n( "unknown host state (unknown ID code)", "Unknown (%1)" ).arg( it->serverState() );
            }
        case 3: // users
            return KGlobal::locale()->formatNumber( it->serverNUsers(), 0 );
        case 4: // files
            return KGlobal::locale()->formatNumber( it->serverNFiles(), 0 );
        case 5: // description
            return it->serverDescription();
        case 6: // address
            return it->serverAddress();
        case 7: // port
            return QString::number( it->serverPort() );
        case 8: // score
            return QString::number( it->serverScore() );
        case 9: // ID
            if ( it->serverState() == ClientInfo::Connected3 )
                return i18n( "high" );
            if ( it->serverState() == ClientInfo::Connected2 )
                return i18n( "normal" );
            if ( it->serverState() == ClientInfo::Connected )
                return i18n( "low" );
            return QString::null;
        case 10: // Preferred
            return it->serverPreferred() ? i18n( "Yes" ) : "";

        default:
            return "ERROR!";
    }
}


double ServerInfoItem::numeric( int col ) const
{
    ServerInfo* it = KMLDonkey::App->donkey->findServerNo( fileno );
    if ( !it )
        return 0.0;

    switch ( col )
    {
        case 3:
            return (double)it->serverNUsers();
        case 4:
            return (double)it->serverNFiles();
        case 7:
            return (double)it->serverPort();
        case 8:
            return (double)it->serverScore();

        default:
            return 0.0;
    }
}


bool ServerInfoItem::isNumeric( int col ) const
{
    switch ( col )
    {
        case 3:
        case 4:
        case 7:
        case 8:
            return true;

        default:
            return false;
    }
}


void ServerInfoItem::paintCell( QPainter* p, const QColorGroup& cg, int col, int w, int align )
{
    QColorGroup colgrp( cg );
    if ( KMLDonkey::App->coloredViews )
    {
        ServerInfo* it = KMLDonkey::App->donkey->findServerNo( fileno );
        if ( it )
        {
            switch ( it->serverState() )
            {
                case ClientInfo::NotConnected:
                case ClientInfo::NotConnected2:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorServerNotConnected );
                break;

                case ClientInfo::Blacklisted:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorServerBlacklisted );
                break;

                case ClientInfo::Connecting:
                case ClientInfo::Initiating:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorServerConnecting );
                break;

                case ClientInfo::Connected:
                case ClientInfo::Connected2:
                case ClientInfo::Connected3:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorServerConnected );
                break;

                default:
                    break;
            }
        }
    }

    p->save();
    InfoItem::paintCell( p, colgrp, col, w, align );
    p->restore();
}


ClientItem::ClientItem( KListView *parent, int file )
    : InfoItem( parent, file )
{
    filesListed = false;
    ClientInfo* it = KMLDonkey::App->donkey->findClientNo( file );
    if ( it )
    {
        Network *n = KMLDonkey::App->donkey->findNetworkNo( it->clientNetwork() );
        if ( n )
            setPixmap( 0, ImageLoader::imageForNetwork( n ) );
    }

    refresh();
}


void ClientItem::setFilesListed( bool listed )
{
    filesListed = listed;
    refresh();
}


QString ClientItem::xtext( int column ) const
{
    ClientInfo* it = KMLDonkey::App->donkey->findClientNo( fileno );
    if ( !it )
    {
        if ( !column )
            return i18n( "Unknown client" );

        return i18n( "signifies absence of data in list columns", "-" );
    }

    switch ( column )
    {
        case 0: // name
            return it->clientName();
        case 1: // network
            return KMLDonkey::App->donkey->findNetworkNo( it->clientNetwork() )->networkName();
        case 2: // type
            switch ( it->clientType() )
            {
                case ClientInfo::NormalClient:
                    return i18n( "a normal client", "Normal" );
                case ClientInfo::FriendClient:
                    return i18n( "a client on the friend list", "Friend" );
                case ClientInfo::ContactClient:
                    return i18n( "a client on the contact list", "Contact" );

                default:
                    return i18n( "client type is unknown", "Unknown" );
            }
        case 3: // kind
            return it->clientKind();
        case 4: // state
            switch ( it->clientState() )
            {
                case ClientInfo::NotConnected:
                case ClientInfo::NotConnected2:
                    return i18n( "not connected to host", "Not connected" );
                case ClientInfo::Connecting:
                    return i18n( "connecting to host", "Connecting" );
                case ClientInfo::Initiating:
                    return i18n( "initiating connection to host", "Initiating" );
                case ClientInfo::Downloading:
                    return filesListed ? i18n( "this friend's file list is present", "Files listed" ) : i18n( "downloading from host", "Downloading" );
                case ClientInfo::Connected:
                case ClientInfo::Connected3:
                    return filesListed ? i18n( "this friend's file list is present", "Files listed" ) : i18n( "connected to host", "Connected" );
                case ClientInfo::Connected2:
                    return filesListed ? i18n( "this friend's file list is present", "Files listed" ) :
                                         i18n( "we're at this position in client's queue", "Queued: %1" )
                                         .arg( KGlobal::locale()->formatNumber( it->clientQueuePosition() ), 0 );
                case ClientInfo::NewHost:
                    return i18n( "new host added to list", "New host" );
                case ClientInfo::Removed:
                    return i18n( "host has been removed", "Removed" );
                case ClientInfo::Blacklisted:
                    return i18n( "host is blacklisted", "Blacklisted" );

                default:
                    return i18n( "unknown host state (unknown ID code)", "Unknown (%1)" ).arg( it->clientState() );
            }

        default:
            return "ERROR!";
    }
}


void ClientItem::paintCell( QPainter* p, const QColorGroup& cg, int col, int w, int align )
{
    QColorGroup colgrp( cg );
    if ( KMLDonkey::App->coloredViews )
    {
        ClientInfo* cl = KMLDonkey::App->donkey->findClientNo( fileno );
        if ( cl )
        {
            switch ( cl->clientState() )
            {
                case ClientInfo::NotConnected:
                case ClientInfo::NotConnected2:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorSourceNotConnected );
                break;

                case ClientInfo::Connecting:
                case ClientInfo::Initiating:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorSourceConnecting );
                break;

                case ClientInfo::Downloading:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorSourceDownloading );
                break;

                case ClientInfo::Connected:
                case ClientInfo::Connected2:
                case ClientInfo::Connected3:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorSourceQueued );
                break;

                case ClientInfo::Blacklisted:
                    colgrp.setColor( QColorGroup::Text, KMLDonkey::App->colorSourceBlacklisted );
                break;

                default:
                    break;
            }
        }
    }

    p->save();
    InfoItem::paintCell( p, colgrp, col, w, align );
    p->restore();
}


ClientFile::ClientFile( KListView *parent, int file )
    : InfoItem( parent, file )
{
    const ResultInfo* it = KMLDonkey::App->donkey->findClientFile( file );
    if ( it )
    {
        Network *n = KMLDonkey::App->donkey->findNetworkNo( it->resultNetwork() );
        if ( n )
            setPixmap( 0, ImageLoader::imageForNetwork( n ) );
    }
    refresh();
}


QString ClientFile::xtext( int column ) const
{
    const ResultInfo* it = KMLDonkey::App->donkey->findClientFile( fileno );
    if ( !it )
    {
        if ( !column )
            return i18n( "Unknown file" );

        return i18n( "signifies absence of data in list columns", "-" );
    }

    switch ( column )
    {
        case 0: // name
            return it->resultName();
        case 1: // network
        {
            Network *net = KMLDonkey::App->donkey->findNetworkNo( it->resultNetwork() );
            return net ? net->networkName() : QString::number( it->resultNetwork() );
        }
        break;

        case 2: // size
            return humanReadableSize( it->resultSize() );
        case 3: // format
            return it->resultFormat();
        case 4: // comment
            return it->resultComment();
        case 5: // hash
            return it->resultUid();

        default:
            return "ERROR!";
    }
}


double ClientFile::numeric( int col ) const
{
    const ResultInfo* it = KMLDonkey::App->donkey->findClientFile( fileno );
    if ( !it )
        return 0.0;

    switch ( col )
    {
        case 2:
            return (double)it->resultSize();

        default:
            return 0.0;
    }
}


bool ClientFile::isNumeric( int col ) const
{
    switch ( col )
    {
        case 2:
            return true;

        default:
            return false;
    }
}
