Logo Search packages:      
Sourcecode: adept version File versions  Download package

lister.cpp

// -*- Mode: C++; c-basic-offset: 4; -*-
#include <qlabel.h>
#include <qtimer.h>
#include <qpainter.h>
#include <qpushbutton.h>
#include <qthread.h>
#include <qtextbrowser.h>

#include <kpopupmenu.h>
#include <kdebug.h>
#include <klineedit.h>
#include <klocale.h>
#include <kapplication.h>
#include <kglobal.h>
#include <kiconloader.h>

#include <functional>
#include <iostream>

#include <apt-front/cache/cache.h>
#include <apt-front/actor.h>
#include <apt-front/cache/component/packages.h>
#include <apt-front/cache/component/state.h>
#include <apt-front/predicate/factory.h>

#include <adept/utils.h>
#include <adept/lister.h>
#include <adept/packageinfo.h>

using namespace aptFront;
using namespace aptFront::predicate;
using namespace aptFront::cache;
using namespace aptFront::utils;
using namespace adept;

Lister::Lister( QWidget *parent, const char *name )
    : ExtendableList( parent, name ),
      m_rangeProvider( 0 ),
      m_baseF( predicate::True< entity::Entity >() ),
      m_interactiveF( True<entity::Entity>() ), m_itemCount( -1 ),
      m_rebuildScheduled( false ), m_inRebuild( false ), m_cancelRebuild( false ),
      m_openToplevel( false ), m_rebuildMutex( true )
{
    observeComponent< component::State >();
    observeComponent< component::Packages >();
    observeComponent< component::PackageTags >();
    setRootIsDecorated( false );
    setSelectionModeExt( Extended );
    setAllColumnsShowFocus (true);

    m_icons[ u8( "package-install" ) ] = u8( "adept_install" );
    m_icons[ u8( "package-remove" ) ] = u8( "adept_remove" );
    m_icons[ u8( "package-upgrade" ) ] = u8( "adept_upgrade" );
    m_icons[ u8( "package-keep" )] = u8( "adept_keep" );
    m_icons[ u8( "package-reinstall" )] = u8( "adept_reinstall" );
    m_icons[ u8( "package-purge" )] = u8( "adept_purge" );

    setSorting( -1 );
    // addColumn(" ", 40);
    // addColumn(" ", 18);
    QFontMetrics met( font() );
    addColumn( i18n( "Package" ), 180);
    addColumn( i18n( "Status" ), met.width( i18n( "not installed" ) ) + 10 );
    addColumn( i18n( "Requested" ), met.width( i18n( "Requested" ) ) + 10 );
    addColumn( i18n( "Description" ), 300);
    setToggleColumn( 0 );

    for ( int col = 1; col < 4; ++col )
        setColumnAlignment( col, Qt::AlignLeft | Qt::AlignVCenter );

    setResizeMode( LastColumn );
    connect( this, SIGNAL( selectionChanged() ), SLOT( updateActions() ) );
    connect( this,
             SIGNAL( contextMenuRequested( QListViewItem *,
                                           const QPoint &, int ) ),
             this, SLOT(
                 contextMenu( QListViewItem *, const QPoint &, int) ) );
    m_tip = 0;
    // m_tip = new ListerTooltip( viewport(), this );
}

Lister::~Lister()
{
    delete m_tip;
}

void Lister::scheduleRebuild()
{
    if (!m_rebuildScheduled) {
        // kdDebug() << "Lister scheduling rebuild" << endl;
        QTimer::singleShot( 0, this, SLOT( rebuild() ) );
    }
    m_rebuildScheduled = true;
}

void Lister::updateActions()
{
    emit actionsChanged( this );
}

void Lister::notifyPostChange( component::Base * )
{
    kdDebug() << "notifyRefresh()" << endl;
    updateActions();
    triggerUpdate();
}

void Lister::notifyPreRebuild( component::Base *b )
{
    kdDebug() << "Lister::notifyPreRebuild( " << b << " )" << endl;
    Cache &c = cache::Global::get( m_cache );
    setEnabled( false );
    if ( dynamic_cast< component::PackageTags * >( b ) != 0 ) {
        kdDebug() << "clearing cardinality" << endl;
        m_cardinality.clear();
    }
    if ( dynamic_cast< component::Packages * >( b ) != 0 ) {
        kdDebug() << "clearing lister" << endl;
        clear();
        m_items.clear();
        m_cardinality.clear();
    }
}

void Lister::notifyPostRebuild( component::Base *b )
{
    kdDebug() << "Lister::notifyPostRebuild( " << b << " )" << endl;
    scheduleRebuild();
    if ( dynamic_cast< component::State * >( b ) != 0 ) {
        setEnabled( true );
    }
}

bool lessByName( const entity::Entity &e1, const entity::Entity &e2 )
{
    if ( e1.is< entity::Named >() && e2.is< entity::Named >() ) {
        entity::Named &n1 = downcast< entity::Named >( e1 ),
                      &n2 = downcast< entity::Named >( e2 );
        return n1.name() < n2.name();
    }
    return e1 < e2;
}

bool Lister::cancelRebuild() {
    // kdDebug() << "cancel rebuild: " << m_inRebuild << ", " << m_cancelRebuild << endl;
    if ( m_inRebuild ) {
        m_rebuildScheduled = false;
        m_cancelRebuild = true;
    }
    if ( !cache::Global::get( m_cache ).isOpen() ) {
        m_rebuildScheduled = false;
        return true;
    }
    return m_cancelRebuild;
}

void Lister::cleanRebuild()
{
    scheduleRebuild();
}

Lister::CreateItem::CreateItem( Lister *_l, ListerItem *p )
    : l( _l ), time( 0 ), items( 0 ), last( 0 ), parent( p )
{
}

Lister::CreateItem::~CreateItem()
{
    // delete timer;
}

Lister::Map::value_type Lister::CreateItem::operator()( entity::Entity e )
{
    items ++;
    if ( l->m_cancelRebuild ) throw 0; // XXX proper exception please
    // kdDebug() << "trying to acquire mutex" << endl;
    l->m_rebuildMutex.lock();
    // kdDebug() << "mutex acquired" << endl;

    // count tags
    if ( e.is< entity::Package >() ) {
        const entity::Tag::Set &tags = downcast< entity::Package >( e ).tags();
        for (entity::Tag::Set::iterator i = tags.begin(); i != tags.end(); ++ i )
            l->m_cardinality[ *i ] ++;
    }

    if ( last ) {
        if ( parent )
            last = new ListerItem( parent, last, e );
        else
            last = new ListerItem( l, last, e );
    } else {
        if ( parent )
            last = new ListerItem( parent, e );
        else
            last = new ListerItem( l, e );
    }
    l->m_rebuildMutex.unlock();
    if ( e.is< entity::Relation >() )
        // this should be safe because the parent thread is waiting
        // for us and ensures that universe (libapt-front) is kept
        // unchanged while we run
        l->insertRangeInternal( InsertRangePair(
                                    last, downcast< entity::Relation >( e ).targetPackages() ) );
        // we may want to use recursive async call instead? why?
        /* Threads::enqueue(
            asyncCall( std::bind2nd( std::mem_fun( &Lister::insertRangeInternal ),
                                     InsertRangePair(
                                         last,
                                         downcast< entity::Relation >( e ).targetPackages() ) ),
                                         l ), &(l->m_rebuildMutex) );
        */
    return std::make_pair( e, last );
}

void Lister::reallyUpdate()
{
    bool en = isUpdatesEnabled();
    setUpdatesEnabled( true );
    triggerUpdate();
    setUpdatesEnabled( en );
}

void Lister::insertRange( Range r ) {
    insertRangeInternal( InsertRangePair( 0, r ) );
}
void Lister::insertRangeInternal( InsertRangePair a )
{
    // kdDebug() << "insertRange running..." << endl;
    try {
        std::transform( a.second, a.second.end(),
                        inserter( m_items, m_items.begin() ),
                        CreateItem( this, a.first ) );
    } catch ( ... ) {}
    m_itemCount = m_items.size();
}

/* void Lister::rebuildInsertRange( Range r ) {
    insertRange( 0, r );
    } */

void Lister::rebuild()
{
    Cache &c = cache::Global::get( m_cache );
    if ( cancelRebuild() ) {
        scheduleRebuild();
        return;
    }

    m_inRebuild = true;
    m_rebuildMutex.lock();

    emit rebuildStarted();

    c.progress().OverallProgress( 0, 0, 0, i18n( "Filtering" ) );
    kdDebug() << "rebuild running" << endl;
    clock_t _c = clock(), C;
    for ( Cardinality::iterator i = m_cardinality.begin();
          i != m_cardinality.end(); ++i )
        i->second = 0;

    kdDebug() << "querying m_rangeProvider " << m_rangeProvider << "..." << endl;

    Range r = filteredRange( m_rangeProvider ?
                             m_rangeProvider->listerRange() : range( VectorRange() ),
                             m_baseF );
    C = (clock() - _c) / 1000; _c = clock();

    setUpdatesEnabled( false );
    kdDebug() << "clearing..." << endl;
    clear();
    m_items.clear();

    kdDebug() << "asyncCall to rebuildInsertRange..." << endl;
    QThread *t = asyncCall( std::bind2nd(
                                std::mem_fun( &Lister::insertRangeInternal ),
                                InsertRangePair( 0, r ) ),
                            this );

    kdDebug() << "starting the thread..." << endl;

    QTimer timer;
    connect( &timer, SIGNAL( timeout() ),
             this, SLOT( reallyUpdate() ) );
    timer.start( 0 );

    m_rebuildMutex.unlock();
    Threads::enqueue( t, &m_rebuildMutex );
    Threads::wait();

    timer.stop();

    kdDebug() << "thread finished..." << endl;
    C = (clock() - _c) / 1000; _c = clock();
    kdDebug() << m_items.size() << " entities synced, time = " << C << endl;

    setUpdatesEnabled( true );
    c.progress().Done();
    if ( m_openToplevel ) openToplevel();
    triggerUpdate();

    if ( !m_cancelRebuild ) {
        for ( Cardinality::iterator i = m_cardinality.begin();
              i != m_cardinality.end(); ++i )
            if ( i->second == m_itemCount )
                i->second = -1;
        emit cardinalityChanged( m_cardinality );
    }

    m_inRebuild = false;
    m_rebuildScheduled = false;
    m_cancelRebuild = false;
    emit rebuildFinished();
}

void Lister::baseAnd( Predicate o )
{
    m_baseF = predicate::predicate( m_baseF and o );
    // emit filterChanged( m_baseF );
    cancelRebuild();
    scheduleRebuild();
}

void Lister::baseSet( Predicate o )
{
    m_baseF = o;
    // emit filterChanged( m_baseF );
    cancelRebuild();
    scheduleRebuild();
}

void Lister::interactiveAnd( Predicate o )
{
    m_interactiveF = predicate::predicate( m_interactiveF and o );
    cancelRebuild();
    scheduleRebuild();
}

void Lister::interactiveDrop( Predicate o )
{
    m_interactiveF = predicate::remove( m_interactiveF, o );
    cancelRebuild();
    scheduleRebuild();
}

bool Lister::itemSelected( Map::value_type i )
{
    return not i.second->isSelected();
}

entity::Entity Lister::extractKey( Map::value_type i )
{
    return i.first;
}

Lister::VectorRange Lister::selection()
{
    VectorRange ret;
    Map m;
    std::remove_copy_if( m_items.begin(), m_items.end(),
                         inserter( m, m.begin() ),
                         itemSelected );
    std::transform( m.begin(), m.end(),
                    consumer( ret ),
                    extractKey );
    return ret;
}

Lister::VectorRange Lister::content()
{
    VectorRange ret;
    std::transform( m_items.begin(), m_items.end(),
                    consumer( ret ),
                    extractKey );
    return ret;
}

QString ListerItem::text( int column ) const
{
    // if (column == 0) return ""; // until we redo paintcell for the col
    if (entity().is<entity::Package>()) {
        entity::Package p = entity();
        switch (column) {
        case 0: return u8( p.name( u8( i18n( "n/a" ) ) ) );
        case 1: return u8( p.statusString( u8( i18n( "n/a" ) ) ) );
        case 2: return u8( p.actionString( u8( i18n( "n/a" ) ) ) );
        case 3: return u8( p.shortDescription( u8( i18n( "n/a" ) ) ) );
            // case 2: return p.candidateVersion().versionString();
        }
    }
    if ( entity().is< entity::Relation >() && column == 0 )
        return downcast< entity::Relation >( entity() ).format();
    return u8( "" );
}

void ListerItem::paintCell ( QPainter *p, const QColorGroup &cg,
                             int column, int width, int alignment )
{
    if ( width <= 0 )
        return;
    QColorGroup _cg( cg );
    QColor c = _cg.text();
    QPixmap pm( width, height() );
    QPainter _p( &pm );
    if ( entity().is<entity::Package>() ) {
        entity::Package p = entity();
        if ( column == 1 )
            c = statusColor( p );
        if ( column == 2 )
            c = actionColor( p );
    }
    _cg.setColor( QColorGroup::Text, c );
    if ( extender() ) { // make the icon appear at top... this
                        // probably breaks big-text displays?
                        // --> somewhat, but not too badly
        alignment &= ~AlignVertical_Mask;
        alignment |= AlignTop;
    }
    KListViewItem::paintCell( &_p, _cg, column, width, alignment );
    p->drawPixmap( 0, 0, pm );
}

void Lister::contextMenu( QListViewItem *it, const QPoint &pt, int /*c*/ )
{
    if (! it) // check for actor when we have one...
        return;
    m_context = dynamic_cast< ListerItem * >( it );
    VectorRange sel = selection();
    // entity::Package p = (dynamic_cast<ListerItem *>(it)->entity());
    KPopupMenu *m = new KPopupMenu (this);
    utils::Range< Actor > r = actor::Global< entity::Package >::list();
    int id = 8;
    try {
        for (; r != r.end(); ++r) {
            m->insertItem( SmallIconSet( m_icons[ u8( r->name() ) ] ),
                           r->prettyName(), id );
            m->setItemEnabled(
                id, r->possible( utils::upcastRange< entity::Package >( sel ) ) );
            ++id;
        }
    } catch ( std::bad_cast ) {} // ignore (this is broken, but
                                 // easiest fix)
    bool open = m_context->extender();
    m->insertItem( open ? i18n( "Hide details" ) :
                   i18n( "Show details" ), open ? 1 : 0 );
    connect(m, SIGNAL(activated(int)), this, SLOT(contextActivated(int)));
    m->exec(pt);
    delete m;
}

void Lister::contextActivated( int id )
{
    VectorRange sel = selection();
    try {
        if (id >= 8) {
            utils::Range< Actor > r = actor::Global< entity::Package >::list();
            std::advance( r, id - 8 );
            (*r)( utils::upcastRange< entity::Package >( sel ) );
            updateActions();
        }
        if (id < 8) {
            VectorRange i = sel.begin();
            while (i != i.end()) {
                if (id == 0)
                    m_items[*i]->showExtender();
                if (id == 1)
                    m_items[*i]->hideExtender();
                ++ i;
            }
        }
    } catch ( std::bad_cast ) {} // ignore (this is broken, but
}

ListerItemExtender::~ListerItemExtender()
{
}

ListerItemExtender::ListerItemExtender( QWidget *parent, const char * n)
    : ListerItemExtenderUi( parent, n )
{
    observeComponent< component::State >();
    adjustFontSize( m_description, -1 );
    connect( m_details, SIGNAL( clicked() ),
             this, SLOT( detailsClicked() ) );

    m_packageInfo->adjustFontSize( -1 );
    m_packageInfo->hideStatus();
}

void ListerItemExtender::detailsClicked() {
    detailsRequested( m_entity );
}

ListerItem *ListerItemExtender::item()
{
    return dynamic_cast< ListerItem * >( m_item );
}

void ListerItemExtender::mouseReleaseEvent( QMouseEvent *e ) {
    e->ignore();
    if ( childAt( e->pos() ) != static_cast< QWidget * >( m_name ) )
        e->accept();
}
void ListerItemExtender::setItem( ExtendableItem *i )
{
    ItemExtender::setItem( i );
    m_entity = item()->entity();
    // setupColors();

    kdDebug() << "ListerItemExtender::setItem connecting" << endl;
    connect( this, SIGNAL( detailsRequested( Lister::Entity ) ),
             item()->list(), SIGNAL( detailsRequested( Lister::Entity ) ) );

    entity::Version v;
    entity::Package p;

    if ( m_entity.is< entity::Version >() ) {
        v = m_entity;
        p = v.package();
    }

    if ( m_entity.is< entity::Package >() ) {
        p = m_entity;
        v = p.anyVersion();
    }

    if ( !v.valid() ) {
        m_logical->setText( i18n( "Immutable" ) );
        m_logical->setEnabled( false );
        m_details->setEnabled( false );
        return;
    }

    m_name->setText( /* QString( "<b>" ) + */
        v.package().name( std::string( "oops" ) ) /* + "</b>" */ );
    QString l = u8( v.longDescription(
                        u8( i18n( "No long description available" ) ) ) );

    m_description->setText( QString( "<qt>" )
                            + formatLongDescription( l ) + "</qt>" );
    m_description->adjustSize();
    m_description->installEventFilter( this );

    m_packageInfo->setVersion( v, m_entity.is< entity::Version >() );

    notifyPostChange( 0 );
}

void ListerItemExtender::notifyPostRebuild( component::Base *b )
{ // need to catch undo/redo effects
    return notifyPostChange( b );
}

void ListerItemExtender::notifyPostChange( component::Base * )
{
    // without the timer to break it, there could be a loop where
    // we connect the clicked() signal to a slot which would be
    // invoked right away when we return -> evil
    QTimer::singleShot( 0, this, SLOT( updateLogical() ) );
}

void ListerItemExtender::updateLogical() {
    entity::Package pkg = entity();
    EntityActor *a = 0;

    m_status->setText( colorify(
                           statusColor( pkg ),
                           u8( pkg.statusString( u8( i18n( "Unknown" ) ) ) ) ) );
    m_change->setText( colorify(
                           actionColor( pkg ),
                           u8( pkg.actionString( u8( i18n( "Unknown" ) ) ) ) ) );


    m_logical->setEnabled( true );
    if (pkg.canKeep()) {
        a = new EntityActor( pkg.keep() );
    } else if (pkg.canUpgrade()) {
        a = new EntityActor( pkg.upgrade() );
    } else if (pkg.canInstall()) {
        a = new EntityActor( pkg.install() );
    } else if (pkg.canRemove()) {
        a = new EntityActor( pkg.remove() );
    }

    if (a) {
        m_logical->setText( u8( a->actor().prettyName() ) );
        connect( m_logical, SIGNAL( clicked() ),
                 a, SLOT( destructiveAct() ) );
    } else {
        m_logical->setText( i18n( "Immutable" ) );
        m_logical->setEnabled( false );
    }

}

bool ListerItemExtender::eventFilter( QObject *o, QEvent *e )
{
    if (o == m_description && e->type() == QEvent::Wheel) {
        // kdDebug() << "discarding wheel event..." << endl;
        QApplication::sendEvent( this, e );
        return true;
    }
    return false;
}

void ListerItemExtender::resize( int w, int h )
{
    // XXX the magic constants are probably style-dependent... AW
    int namew = - item()->lister()->extenderOffset( item() )
                - layout()->margin()
                - layout()->spacing()
                + item()->lister()->columnWidth( 0 );
    int statw = item()->lister()->columnWidth( 1 )
                - layout()->spacing();
    int chw = item()->lister()->columnWidth( 2 )
              - layout()->spacing() - 3; // wth...
    m_name->setMinimumWidth( namew );
    m_status->setMinimumWidth( statw );
    m_change->setMinimumWidth( chw );
    m_packageInfo->adjustSize();
    m_leftHeight = m_name->height() + m_packageInfo->height()
                   + m_logical->height() + 20;
    QWidget::resize( w, 500 );
    QWidget::resize(
        w,
        QMAX( m_description->contentsHeight() + 16,
              m_leftHeight ) );
}

bool entityLess::operator()( entity::Entity e1, entity::Entity e2 )
{
    if ( e1.is< entity::Package >() ) {
        if ( e2.is< entity::Package >() )
            return e1 < e2;
        return true;
    }

    if ( e1.is< entity::Version >() ) {
        if ( e2.is< entity::Package >() )
            return false;
        if ( e2.is< entity::Version >() )
            return e1 < e2;
        return true;
    }

    if ( e1.is< entity::Relation >() ) {
        if ( e2.is< entity::Package >() )
            return false;
        if ( e2.is< entity::Version >() )
            return false;
        if ( e2.is< entity::Relation >() )
            return e1 < e2;
        return true;
    }
    return true;
}

bool ListerItem::less( const ExtendableItem *b ) const
{
    entity::Entity e1 = entity(), e2 = dynamic_cast< const ListerItem * >( b )->entity();
    return entityLess()( e1, e2 );
}

bool ListerItem::keepLess( const ListerItem *o ) const
{
    const ListerItem *b = o;
    while ( b != 0 ) {
        if ( b == this )
            return false;
        b = b->m_previous;
    }
    while ( o != 0 ) {
        o = dynamic_cast< const ListerItem * >( o->nextSibling() );
        if ( o == this )
            return true;
    }
    return false;
}

QString ListerTooltip::format( const QString &what,
                               const QString &txt, bool nobr )
{
    QString ret = "<b>" + what + "</b>&nbsp;" + (nobr ? "<nobr>" : "")
                  + txt + (nobr ? "</nobr>" : "") + "<br>";
    return ret;
}

void ListerTooltip::maybeTip( const QPoint &pt )
{
    if ( !m_parent )
        return;
    kdDebug() << "ListTreeWidgetTooltip::maybeTip ()" << endl;
    ListerItem *x = dynamic_cast<ListerItem *>( m_parent->itemAt( pt ) );
    if ( !x )
        return;
    if ( x->extender() )
        return; // no tips for extended items, thank you
    QString str = u8( "<qt>" );
    QString descr, cand, cur;
    descr = cand = cur = i18n( "<i>Not available</i>" );
    entity::Package p( x->entity() );
    descr = p.shortDescription( std::string(
                                    i18n( "<i>Not available</i>" ).local8Bit() ) );
    try {
        cand = u8( p.candidateVersion().versionString() );
    } catch (...) {}
    try {
        cur = u8( p.installedVersion().versionString() );
    } catch (...) {}

    str += format( i18n( "Package:" ), u8( p.name() ) );
    str += format( i18n( "Description:" ), descr );
    str += format( i18n( "Current&nbsp;Version:" ), cur );
    str += format( i18n( "Candidate&nbsp;Version:" ), cand );

    str.append( u8( "</qt>" ) );
    tip( m_parent->itemRect( x ), str );
}

Generated by  Doxygen 1.6.0   Back to index