• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

kaddressbook

cardview.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of KAddressBook.
00003     Copyright (c) 2002 Mike Pilone <mpilone@slac.com>
00004 
00005     This program is free software; you can redistribute it and/or modify
00006     it under the terms of the GNU General Public License as published by
00007     the Free Software Foundation; either version 2 of the License, or
00008     (at your option) any later version.
00009 
00010     This program is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00013     GNU General Public License for more details.
00014 
00015     You should have received a copy of the GNU General Public License
00016     along with this program; if not, write to the Free Software
00017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00018 
00019     As a special exception, permission is given to link this program
00020     with any edition of Qt, and distribute the resulting executable,
00021     without including the source code for Qt in the source distribution.
00022 */
00023 
00024 #include <limits.h>
00025 
00026 #include <Qt3Support/Q3PtrList>
00027 #include <QtCore/QDateTime>
00028 #include <QtCore/QEvent>
00029 #include <QtCore/QTimer>
00030 #include <QtGui/QCursor>
00031 #include <QtGui/QFocusEvent>
00032 #include <QtGui/QKeyEvent>
00033 #include <QtGui/QLabel>
00034 #include <QtGui/QMouseEvent>
00035 #include <QtGui/QPainter>
00036 #include <QtGui/QResizeEvent>
00037 #include <QtGui/QStyle>
00038 #include <QtGui/QStyleOption>
00039 #include <QtGui/QToolTip>
00040 #include <QtGui/QWheelEvent>
00041 
00042 #include <kdebug.h>
00043 #include <kglobalsettings.h>
00044 
00045 #include "cardview.h"
00046 
00047 #define MIN_ITEM_WIDTH 80
00048 
00049 class CardViewTip : public QLabel
00050 {
00051   public:
00052     CardViewTip( QWidget *parent = 0, const char *name = 0 )
00053       : QLabel( parent )
00054     {
00055       setObjectName( name );
00056       setPalette( QToolTip::palette() );
00057       setFrameStyle( Panel | Plain );
00058       setMidLineWidth( 0 );
00059       setIndent( 1 );
00060     }
00061 
00062     ~CardViewTip() {}
00063 
00064   protected:
00065     void leaveEvent( QEvent* )
00066     {
00067       hide();
00068     }
00069 };
00070 
00071 //
00072 // Warning: make sure you use findRef() instead of find() to find an
00073 //          item! Only the pointer value is unique in the list.
00074 //
00075 class CardViewItemList : public Q3PtrList<CardViewItem>
00076 {
00077   protected:
00078     virtual int compareItems( Q3PtrCollection::Item item1,
00079                               Q3PtrCollection::Item item2 )
00080     {
00081       CardViewItem *cItem1 = (CardViewItem*)item1;
00082       CardViewItem *cItem2 = (CardViewItem*)item2;
00083 
00084       if ( cItem1 == cItem2 )
00085           return 0;
00086 
00087       if ( (cItem1 == 0) || (cItem2 == 0) )
00088           return cItem1 ? -1 : 1;
00089 
00090       if ( cItem1->caption() < cItem2->caption() )
00091         return -1;
00092       else if ( cItem1->caption() > cItem2->caption() )
00093         return 1;
00094 
00095       return 0;
00096     }
00097 };
00098 
00099 class CardViewSeparator
00100 {
00101   friend class CardView;
00102 
00103   public:
00104     CardViewSeparator( CardView *view )
00105       : mView( view )
00106     {
00107       mRect = QRect( 0, 0, view->separatorWidth(), 0 );
00108     }
00109 
00110     ~CardViewSeparator() {}
00111 
00112     void paintSeparator( QPainter *p, const QPalette &pal )
00113     {
00114       p->fillRect( 0, 0, mRect.width(), mRect.height(),
00115                    pal.brush(QPalette::Button) );
00116     }
00117 
00118     void repaintSeparator()
00119     {
00120       mView->repaintContents( mRect );
00121     }
00122 
00123   private:
00124     CardView *mView;
00125     QRect mRect;
00126 };
00127 
00128 class CardViewPrivate
00129 {
00130   public:
00131     CardViewPrivate()
00132      : mSelectionMode( CardView::Multi ),
00133        mDrawCardBorder( true ),
00134        mDrawFieldLabels( true ),
00135        mDrawSeparators( true),
00136        mSepWidth( 2 ),
00137        mShowEmptyFields( false ),
00138        mLayoutDirty( true ),
00139        mLastClickOnItem( false ),
00140        mItemMargin( 0 ),
00141        mItemSpacing( 10 ),
00142        mItemWidth( 200 ),
00143        mMaxFieldLines( INT_MAX ),
00144        mCurrentItem( 0L ),
00145        mLastClickPos( QPoint(0, 0) ),
00146        mRubberBandAnchor( 0 ),
00147        mCompText( QString() )
00148     {}
00149 
00150     CardViewItemList mItemList;
00151     Q3PtrList<CardViewSeparator> mSeparatorList;
00152     QFontMetrics *mFm;
00153     QFontMetrics *mBFm;
00154     QFont mHeaderFont;
00155     CardView::SelectionMode mSelectionMode;
00156     bool mDrawCardBorder;
00157     bool mDrawFieldLabels;
00158     bool mDrawSeparators;
00159     int mSepWidth;
00160     bool mShowEmptyFields;
00161     bool mLayoutDirty;
00162     bool mLastClickOnItem;
00163     uint mItemMargin;           // internal margin in items
00164     uint mItemSpacing;          // spacing between items, column seperators and border
00165     int mItemWidth;             // width of all items
00166     uint mMaxFieldLines;        // Max lines to dispaly pr field
00167     CardViewItem *mCurrentItem;
00168     QPoint mLastClickPos;
00169     QTimer *mTimer;             // times out if mouse rests for more than 500 msecs
00170     CardViewTip *mTip;          // passed to the item under a resting cursor to display full text
00171     bool mOnSeparator;          // set/reset on mouse movement
00172     // for resizing by dragging the separators
00173     int mResizeAnchor;          // uint, ulong? the mouse down separator left
00174     int mRubberBandAnchor;      // for erasing rubber bands
00175     // data used for resizing.
00176     // as they are beeded by each mouse move while resizing, we store them here,
00177     // saving 8 calculations in each mouse move.
00178     int mColspace;               // amount of space between items pr column
00179     uint mFirst;                 // the first col to anchor at for painting rubber bands
00180     int mFirstX;                 // X position of first in pixel
00181     int mPressed;                // the colummn that was pressed on at resizing start
00182     int mSpan;                   // pressed - first
00183     // key completion
00184     QString mCompText;          // current completion string
00185     QDateTime mCompUpdated;     // ...was updated at this time
00186 };
00187 
00188 class CardViewItemPrivate
00189 {
00190   public:
00191     CardViewItemPrivate() {}
00192 
00193     QString mCaption;
00194     Q3PtrList< CardViewItem::Field > mFieldList;
00195     bool mSelected;
00196     int x;                      // horizontal position, set by the view
00197     int y;                      // vertical position, set by the view
00198     int maxLabelWidth;          // the width of the widest label, according to the view font.
00199     int hcache;                 // height cache
00200 };
00201 
00202 
00203 CardViewItem::CardViewItem( CardView *parent, const QString &caption )
00204   : d( new CardViewItemPrivate() ), mView( parent )
00205 {
00206   d->mCaption = caption;
00207 
00208   initialize();
00209 }
00210 
00211 CardViewItem::~CardViewItem()
00212 {
00213   // Remove ourself from the view
00214   if ( mView != 0 )
00215     mView->takeItem( this );
00216 
00217   delete d;
00218   d = 0;
00219 }
00220 
00221 void CardViewItem::initialize()
00222 {
00223   d->mSelected = false;
00224   d->mFieldList.setAutoDelete( true );
00225   d->maxLabelWidth = 0;
00226   d->hcache = 0;
00227 
00228   // Add ourself to the view
00229   if ( mView != 0 )
00230     mView->insertItem( this );
00231 }
00232 
00233 void CardViewItem::paintCard( QPainter *p, const QPalette &pal )
00234 {
00235   if ( !mView )
00236     return;
00237 
00238   QPen pen;
00239   QBrush brush;
00240   QFontMetrics fm = *(mView->d->mFm);
00241   QFontMetrics bFm = *(mView->d->mBFm);
00242   bool drawLabels = mView->d->mDrawFieldLabels;
00243   bool drawBorder = mView->d->mDrawCardBorder;
00244   int mg = mView->itemMargin();
00245   int w = mView->itemWidth() - ( mg * 2 );
00246   int h = height() - ( mg * 2 );
00247   const int colonWidth( fm.width( ":" ) );
00248   int labelXPos = 2 + mg;
00249   int labelWidth = qMin( w / 2 - 4 - mg, d->maxLabelWidth + colonWidth + 4 );
00250   int valueXPos = labelWidth + 4 + mg;
00251   int valueWidth = w - labelWidth - 4 - mg;
00252 
00253   p->setFont( mView->font() );
00254   labelWidth -= colonWidth; // extra space for the colon
00255 
00256   if ( !drawLabels ) {
00257     valueXPos = labelXPos;
00258     valueWidth = w - 4;
00259   }
00260 
00261   // Draw a simple box
00262   if ( isSelected() )
00263     pen = QPen( pal.color( QPalette::Highlight ), 1 );
00264   else
00265     pen = QPen( pal.color( QPalette::Button), 1 );
00266   p->setPen( pen );
00267 
00268   // Draw the border - this is only draw if the user asks for it.
00269   if ( drawBorder )
00270     p->drawRect( mg, mg, w, h );
00271 
00272   // set the proper pen color for the caption box
00273   if ( isSelected() )
00274     brush = pal.brush( QPalette::Highlight );
00275   else
00276     brush = pal.brush( QPalette::Button );
00277 
00278   p->fillRect( mg, mg, w, 4 + bFm.height(), brush );
00279 
00280   // Now paint the caption
00281   p->save();
00282   QFont bFont = mView->headerFont();
00283   p->setFont( bFont );
00284   if ( isSelected() )
00285     p->setPen( pal.color( QPalette::HighlightedText) );
00286   else
00287     p->setPen( pal.color( QPalette::ButtonText) );
00288 
00289   p->drawText( 2 + mg, 2 + mg + bFm.ascent(), trimString( d->mCaption, w - 4, bFm ) );
00290   p->restore();
00291 
00292   // Go through the fields and draw them
00293   Q3PtrListIterator<CardViewItem::Field> iter( d->mFieldList );
00294   QString label, value;
00295   int yPos = mg + 4 + bFm.height() + fm.height();
00296   p->setPen( pal.text().color() );
00297 
00298   int fh = fm.height();
00299   int cln( 0 );
00300   QString tmp;
00301   int maxLines = mView->maxFieldLines();
00302   for ( iter.toFirst(); iter.current(); ++iter ) {
00303     value = (*iter)->second;
00304     if ( value.isEmpty() && ! mView->d->mShowEmptyFields )
00305       continue;
00306 
00307     if ( drawLabels ) {
00308       label = trimString( (*iter)->first, labelWidth, fm );
00309       p->drawText( labelXPos, yPos, label + ':' );
00310     }
00311 
00312     for ( cln = 0; cln <= maxLines; cln++ ) {
00313       tmp = value.section( '\n', cln, cln );
00314       if ( !tmp.isEmpty() )
00315         p->drawText( valueXPos, yPos + cln * fh, trimString( tmp, valueWidth, fm ) );
00316       else
00317         break;
00318     }
00319 
00320     if ( cln == 0 )
00321       cln = 1;
00322     yPos += cln * fh + 2;
00323   }
00324 
00325   // if we are the current item and the view has focus, draw focus rect
00326   if ( mView->currentItem() == this && mView->hasFocus() ) {
00327 
00328     QStyleOption opt;
00329     /* FIXME port me properly
00330         QRect( 0, 0, mView->itemWidth(), h + (2 * mg) ), cg,
00331         QStyle::State_FocusAtBorder,
00332         QStyleOption( isSelected() ? cg.highlight() : cg.base() ) );
00333     */
00334     mView->style()->drawPrimitive( QStyle::PE_FrameFocusRect, &opt, p, 0 );
00335   }
00336 }
00337 
00338 const QString &CardViewItem::caption() const
00339 {
00340   return d->mCaption;
00341 }
00342 
00343 
00344 int CardViewItem::height( bool allowCache ) const
00345 {
00346   // use cache
00347   if ( allowCache && d->hcache )
00348     return d->hcache;
00349 
00350   // Base height:
00351   //  2 for line width
00352   //  2 for top caption pad
00353   //  2 for bottom caption pad
00354   //   2 pad for the end
00355   // + 2 times the advised margin
00356   int baseHeight = 8 + ( 2 * mView->itemMargin() );
00357 
00358   //  size of font for each field
00359   //  2 pad for each field
00360 
00361   bool sef = mView->showEmptyFields();
00362   int fh = mView->d->mFm->height();
00363   int fieldHeight = 0;
00364   int lines;
00365   int maxLines( mView->maxFieldLines() );
00366   Q3PtrListIterator<CardViewItem::Field> iter( d->mFieldList );
00367   for ( iter.toFirst(); iter.current(); ++iter ) {
00368     if ( !sef && (*iter)->second.isEmpty() )
00369       continue;
00370     lines = qMin( (*iter)->second.count( '\n' ) + 1, maxLines );
00371     fieldHeight += ( lines * fh ) + 2;
00372   }
00373 
00374   // height of caption font (bold)
00375   fieldHeight += mView->d->mBFm->height();
00376   d->hcache = baseHeight + fieldHeight;
00377   return d->hcache;
00378 }
00379 
00380 bool CardViewItem::isSelected() const
00381 {
00382   return d->mSelected;
00383 }
00384 
00385 void CardViewItem::setSelected( bool selected )
00386 {
00387   d->mSelected = selected;
00388 }
00389 
00390 void CardViewItem::insertField( const QString &label, const QString &value )
00391 {
00392   CardViewItem::Field *f = new CardViewItem::Field( label, value );
00393   d->mFieldList.append( f );
00394   d->hcache = 0;
00395 
00396   if ( mView ) {
00397     mView->setLayoutDirty( true );
00398     d->maxLabelWidth = qMax( mView->d->mFm->width( label ), d->maxLabelWidth );
00399   }
00400 }
00401 
00402 void CardViewItem::removeField( const QString &label )
00403 {
00404   CardViewItem::Field *f;
00405 
00406   Q3PtrListIterator<CardViewItem::Field> iter( d->mFieldList );
00407   for ( iter.toFirst(); iter.current(); ++iter ) {
00408     f = *iter;
00409     if ( f->first == label )
00410       break;
00411   }
00412 
00413   if (*iter)
00414     d->mFieldList.remove( *iter );
00415   d->hcache = 0;
00416 
00417   if ( mView )
00418     mView->setLayoutDirty( true );
00419 }
00420 
00421 void CardViewItem::clearFields()
00422 {
00423   d->mFieldList.clear();
00424   d->hcache = 0;
00425 
00426   if ( mView )
00427     mView->setLayoutDirty( true );
00428 }
00429 
00430 QString CardViewItem::trimString( const QString &text, int width,
00431                                   QFontMetrics &fm ) const
00432 {
00433   if ( fm.width( text ) <= width )
00434     return text;
00435 
00436   QString dots = "...";
00437   int dotWidth = fm.width( dots );
00438   QString trimmed;
00439   int charNum = 0;
00440 
00441   while ( fm.width( trimmed ) + dotWidth < width ) {
00442     trimmed += text[ charNum ];
00443     charNum++;
00444   }
00445 
00446   // Now trim the last char, since it put the width over the top
00447   trimmed = trimmed.left( trimmed.length() - 1 );
00448   trimmed += dots;
00449 
00450   return trimmed;
00451 }
00452 
00453 CardViewItem *CardViewItem::nextItem() const
00454 {
00455   CardViewItem *item = 0;
00456 
00457   if ( mView )
00458     item = mView->itemAfter( this );
00459 
00460   return item;
00461 }
00462 
00463 void CardViewItem::repaintCard()
00464 {
00465   if ( mView )
00466     mView->repaintItem( this );
00467 }
00468 
00469 void CardViewItem::setCaption( const QString &caption )
00470 {
00471   d->mCaption = caption;
00472   repaintCard();
00473 }
00474 
00475 QString CardViewItem::fieldValue( const QString &label ) const
00476 {
00477   Q3PtrListIterator<CardViewItem::Field> iter( d->mFieldList );
00478   for ( iter.toFirst(); iter.current(); ++iter )
00479     if ( (*iter)->first == label )
00480         return (*iter)->second;
00481 
00482   return QString();
00483 }
00484 
00485 
00486 void CardViewItem::showFullString( const QPoint &itempos, CardViewTip *tip )
00487 {
00488   bool trimmed( false );
00489   QString s;
00490   int mrg = mView->itemMargin();
00491   int y = mView->d->mBFm->height() + 6 + mrg;
00492   int w = mView->itemWidth() - (2 * mrg);
00493   int lw;
00494   bool drawLabels = mView->drawFieldLabels();
00495   bool isLabel = drawLabels && itempos.x() < w / 2 ? true : false;
00496 
00497   if ( itempos.y() < y ) {
00498     if ( itempos.y() < 8 + mrg || itempos.y() > y - 4 )
00499       return;
00500     // this is the caption
00501     s = caption();
00502     trimmed = mView->d->mBFm->width( s ) > w - 4;
00503     y = 2 + mrg;
00504     lw = 0;
00505     isLabel = true;
00506   } else {
00507     // find the field
00508     Field *f = fieldAt( itempos );
00509     if ( !f || ( !mView->showEmptyFields() && f->second.isEmpty() ) )
00510       return;
00511 
00512     // y position:
00513     // header font height + 4px hader margin + 2px leading + item margin
00514     // + actual field index * (fontheight + 2px leading)
00515     int maxLines = mView->maxFieldLines();
00516     bool se = mView->showEmptyFields();
00517     int fh = mView->d->mFm->height();
00518 
00519     Field *_f;
00520     for ( _f = d->mFieldList.first(); _f != f; _f = d->mFieldList.next() )
00521       if ( se || ! _f->second.isEmpty() )
00522         y += ( qMin( _f->second.count( '\n' ) + 1, maxLines ) * fh ) + 2;
00523 
00524     if ( isLabel && itempos.y() > y + fh )
00525       return;
00526 
00527     s = isLabel ? f->first : f->second;
00528 
00529     int colonWidth = mView->d->mFm->width(":");
00530     lw = drawLabels ? qMin( w / 2 - 4 - mrg, d->maxLabelWidth + colonWidth + 4 ) : 0;
00531     int mw = isLabel ? lw - colonWidth : w - lw - ( mrg * 2 );
00532     if ( isLabel ) {
00533       trimmed = mView->d->mFm->width( s ) > mw - colonWidth;
00534     } else {
00535       QRect r( mView->d->mFm->boundingRect( 0, 0, INT_MAX, INT_MAX, Qt::AlignTop|Qt::AlignLeft, s ) );
00536       trimmed = r.width() > mw || r.height() / fh >  qMin( s.count( '\n' ) + 1, maxLines );
00537     }
00538   }
00539 
00540   if ( trimmed ) {
00541     tip->setFont( (isLabel && !lw) ? mView->headerFont() : mView->font() );
00542     tip->setText( s );
00543     tip->adjustSize();
00544     // find a proper position
00545     int lx;
00546     lx = isLabel || !drawLabels ? mrg : lw + mrg + 2;
00547     QPoint pnt( mView->contentsToViewport( QPoint( d->x, d->y ) ) );
00548     pnt += QPoint( lx, y );
00549     if ( pnt.x() < 0 )
00550       pnt.setX( 0 );
00551     if ( pnt.x() + tip->width() > mView->visibleWidth() )
00552       pnt.setX( mView->visibleWidth() - tip->width() );
00553     if ( pnt.y() + tip->height() > mView->visibleHeight() )
00554       pnt.setY( qMax( 0, mView->visibleHeight() - tip->height() ) );
00555     // show
00556     tip->move( pnt );
00557     tip->show();
00558   }
00559 }
00560 
00561 CardViewItem::Field *CardViewItem::fieldAt( const QPoint & itempos ) const
00562 {
00563   int ypos = mView->d->mBFm->height() + 7 + mView->d->mItemMargin;
00564   int iy = itempos.y();
00565   // skip below caption
00566   if ( iy <= ypos )
00567     return 0;
00568   // try find a field
00569   bool showEmpty = mView->showEmptyFields();
00570   int fh = mView->d->mFm->height();
00571   int maxLines = mView->maxFieldLines();
00572   Field *f;
00573   for ( f = d->mFieldList.first(); f; f = d->mFieldList.next() ) {
00574     if ( showEmpty || !f->second.isEmpty() )
00575       ypos += (qMin( f->second.count( '\n' )+1, maxLines ) * fh) + 2;
00576     if ( iy <= ypos )
00577       break;
00578   }
00579 
00580   return f ? f : 0;
00581 }
00582 
00583 
00584 CardView::CardView( QWidget *parent, const char *name )
00585   : Q3ScrollView( parent, name ),
00586     d( new CardViewPrivate() )
00587 {
00588   d->mItemList.setAutoDelete( true );
00589   d->mSeparatorList.setAutoDelete( true );
00590 
00591   QFont f = font();
00592   d->mFm = new QFontMetrics( f );
00593   f.setBold( true );
00594   d->mHeaderFont = f;
00595   d->mBFm = new QFontMetrics( f );
00596   d->mTip = new CardViewTip( viewport() );
00597   d->mTip->hide();
00598   d->mTimer = new QTimer( this );
00599   d->mTimer->setObjectName( "mouseTimer" );
00600 
00601   viewport()->setMouseTracking( true );
00602   viewport()->setFocusProxy( this );
00603   viewport()->setFocusPolicy( Qt::WheelFocus );
00604   viewport()->setBackgroundRole( QPalette::Base );
00605 
00606   connect( d->mTimer, SIGNAL( timeout() ), this, SLOT( tryShowFullText() ) );
00607 
00608   setBackgroundRole( QPalette::Background );
00609 
00610   // no reason for a vertical scrollbar
00611   setVScrollBarMode( AlwaysOff );
00612 }
00613 
00614 CardView::~CardView()
00615 {
00616   delete d->mFm;
00617   delete d->mBFm;
00618   delete d;
00619   d = 0;
00620 }
00621 
00622 void CardView::insertItem( CardViewItem *item )
00623 {
00624   d->mItemList.inSort( item );
00625   setLayoutDirty( true );
00626 }
00627 
00628 void CardView::takeItem( CardViewItem *item )
00629 {
00630   if ( d->mCurrentItem == item )
00631     d->mCurrentItem = item->nextItem();
00632   d->mItemList.take( d->mItemList.findRef( item ) );
00633 
00634   setLayoutDirty( true );
00635 }
00636 
00637 void CardView::clear()
00638 {
00639   d->mItemList.clear();
00640 
00641   setLayoutDirty( true );
00642 }
00643 
00644 CardViewItem *CardView::currentItem() const
00645 {
00646   if ( !d->mCurrentItem && d->mItemList.count() )
00647     d->mCurrentItem = d->mItemList.first();
00648 
00649   return d->mCurrentItem;
00650 }
00651 
00652 void CardView::setCurrentItem( CardViewItem *item )
00653 {
00654   if ( !item )
00655     return;
00656   else if ( item->cardView() != this ) {
00657     kDebug(5720)<<"CardView::setCurrentItem: Item ("<<item<<") not owned! Backing out..";
00658     return;
00659   } else if ( item == currentItem() ) {
00660     return;
00661   }
00662 
00663   if ( d->mSelectionMode == Single ) {
00664     setSelected( item, true );
00665   } else {
00666     CardViewItem *it = d->mCurrentItem;
00667     d->mCurrentItem = item;
00668     if ( it )
00669       it->repaintCard();
00670 
00671     item->repaintCard();
00672   }
00673 
00674   if ( ! d->mOnSeparator )
00675     ensureItemVisible( item );
00676 
00677   emit currentChanged( item );
00678 }
00679 
00680 CardViewItem *CardView::itemAt( const QPoint &viewPos ) const
00681 {
00682   CardViewItem *item = 0;
00683   Q3PtrListIterator<CardViewItem> iter( d->mItemList );
00684   bool found = false;
00685   for ( iter.toFirst(); iter.current() && !found; ++iter ) {
00686     item = *iter;
00687     if ( QRect( item->d->x, item->d->y, d->mItemWidth, item->height() ).contains( viewPos ) )
00688       found = true;
00689   }
00690 
00691   if ( found )
00692     return item;
00693 
00694   return 0;
00695 }
00696 
00697 QRect CardView::itemRect( const CardViewItem *item ) const
00698 {
00699   return QRect( item->d->x, item->d->y, d->mItemWidth, item->height() );
00700 }
00701 
00702 void CardView::ensureItemVisible( const CardViewItem *item )
00703 {
00704   ensureVisible( item->d->x, item->d->y, d->mItemSpacing, 0 );
00705   ensureVisible( item->d->x + d->mItemWidth, item->d->y, d->mItemSpacing, 0 );
00706 }
00707 
00708 void CardView::repaintItem( const CardViewItem *item )
00709 {
00710   repaintContents( QRect( item->d->x, item->d->y, d->mItemWidth, item->height() ) );
00711 }
00712 
00713 void CardView::setSelectionMode( CardView::SelectionMode mode )
00714 {
00715   selectAll( false );
00716 
00717   d->mSelectionMode = mode;
00718 }
00719 
00720 CardView::SelectionMode CardView::selectionMode() const
00721 {
00722   return d->mSelectionMode;
00723 }
00724 
00725 void CardView::selectAll( bool state )
00726 {
00727   Q3PtrListIterator<CardViewItem> iter( d->mItemList );
00728   if ( !state ) {
00729     for ( iter.toFirst(); iter.current(); ++iter ) {
00730       if ( (*iter)->isSelected() ) {
00731         (*iter)->setSelected( false );
00732         (*iter)->repaintCard();
00733       }
00734     }
00735 
00736     emit selectionChanged( 0 );
00737   } else if ( d->mSelectionMode != CardView::Single ) {
00738     for ( iter.toFirst(); iter.current(); ++iter ) {
00739       (*iter)->setSelected( true );
00740     }
00741 
00742     if ( d->mItemList.count() > 0 ) {
00743       // emit, since there must have been at least one selected
00744       emit selectionChanged();
00745       viewport()->update();
00746     }
00747   }
00748 }
00749 
00750 void CardView::setSelected( CardViewItem *item, bool selected )
00751 {
00752   if ( (item == 0) || (item->isSelected() == selected) )
00753     return;
00754 
00755   if ( selected && d->mCurrentItem != item ) {
00756     CardViewItem *it = d->mCurrentItem;
00757     d->mCurrentItem = item;
00758     if ( it )
00759       it->repaintCard();
00760   }
00761 
00762   if ( d->mSelectionMode == CardView::Single ) {
00763     bool b = signalsBlocked();
00764     blockSignals( true );
00765     selectAll( false );
00766     blockSignals( b );
00767 
00768     if ( selected ) {
00769       item->setSelected( selected );
00770       item->repaintCard();
00771       emit selectionChanged();
00772       emit selectionChanged( item );
00773     } else {
00774       emit selectionChanged();
00775       emit selectionChanged( 0 );
00776     }
00777   } else if ( d->mSelectionMode == CardView::Multi ) {
00778     item->setSelected( selected );
00779     item->repaintCard();
00780     emit selectionChanged();
00781   } else if ( d->mSelectionMode == CardView::Extended ) {
00782     bool b = signalsBlocked();
00783     blockSignals( true );
00784     selectAll( false );
00785     blockSignals( b );
00786 
00787     item->setSelected( selected );
00788     item->repaintCard();
00789     emit selectionChanged();
00790   }
00791 }
00792 
00793 bool CardView::isSelected( CardViewItem *item ) const
00794 {
00795   return (item && item->isSelected());
00796 }
00797 
00798 CardViewItem *CardView::selectedItem() const
00799 {
00800   // find the first selected item
00801   Q3PtrListIterator<CardViewItem> iter( d->mItemList );
00802   for ( iter.toFirst(); iter.current(); ++iter ) {
00803     if ( (*iter)->isSelected() )
00804       return *iter;
00805   }
00806 
00807   return 0;
00808 }
00809 
00810 CardViewItem *CardView::firstItem() const
00811 {
00812   return d->mItemList.first();
00813 }
00814 
00815 int CardView::childCount() const
00816 {
00817   return d->mItemList.count();
00818 }
00819 
00820 CardViewItem *CardView::findItem( const QString &text, const QString &label,
00821                                   Q3ListBox::StringComparisonMode compare ) const
00822 {
00823   // If the text is empty, we will return null, since empty text will
00824   // match anything!
00825   if ( text.isEmpty() )
00826     return 0;
00827 
00828   Q3PtrListIterator<CardViewItem> iter( d->mItemList );
00829   if ( compare & Q3ListBox::BeginsWith ) {
00830     QString value;
00831     for ( iter.toFirst(); iter.current(); ++iter ) {
00832       value = (*iter)->fieldValue( label ).toUpper();
00833       if ( value.startsWith( text.toUpper() ) )
00834         return *iter;
00835     }
00836   } else {
00837     kDebug(5720) <<"CardView::findItem: search method not implemented";
00838   }
00839 
00840   return 0;
00841 }
00842 
00843 uint CardView::columnWidth() const
00844 {
00845   return d->mDrawSeparators ?
00846     d->mItemWidth + ( 2 * d->mItemSpacing ) + d->mSepWidth :
00847     d->mItemWidth + d->mItemSpacing;
00848 }
00849 
00850 void CardView::drawContents( QPainter *p, int clipx, int clipy,
00851                              int clipw, int cliph )
00852 {
00853   Q3ScrollView::drawContents( p, clipx, clipy, clipw, cliph );
00854 
00855  if ( d->mLayoutDirty )
00856    calcLayout();
00857 
00858   // allow setting costum colors in the viewport pale
00859   QPalette pal = viewport()->palette();
00860 
00861   QRect clipRect( clipx, clipy, clipw, cliph );
00862   QRect cardRect;
00863   QRect sepRect;
00864   CardViewItem *item;
00865   CardViewSeparator *sep;
00866 
00867   // Now tell the cards to draw, if they are in the clip region
00868   Q3PtrListIterator<CardViewItem> iter( d->mItemList );
00869   for ( iter.toFirst(); iter.current(); ++iter) {
00870     item = *iter;
00871     cardRect.setRect( item->d->x, item->d->y, d->mItemWidth, item->height() );
00872 
00873     if ( clipRect.intersects( cardRect ) || clipRect.contains( cardRect ) ) {
00874       // Tell the card to paint
00875       p->save();
00876       p->translate( cardRect.x(), cardRect.y() );
00877       item->paintCard( p, pal );
00878       p->restore();
00879     }
00880   }
00881 
00882   // Followed by the separators if they are in the clip region
00883   Q3PtrListIterator<CardViewSeparator> sepIter( d->mSeparatorList );
00884   for ( sepIter.toFirst(); sepIter.current(); ++sepIter ) {
00885     sep = *sepIter;
00886     sepRect = sep->mRect;
00887 
00888     if ( clipRect.intersects( sepRect ) || clipRect.contains( sepRect ) ) {
00889       p->save();
00890       p->translate( sepRect.x(), sepRect.y() );
00891       sep->paintSeparator( p, pal );
00892       p->restore();
00893     }
00894   }
00895 }
00896 
00897 void CardView::resizeEvent( QResizeEvent *event )
00898 {
00899   Q3ScrollView::resizeEvent( event );
00900 
00901   setLayoutDirty( true );
00902 }
00903 
00904 void CardView::calcLayout()
00905 {
00906   // Start in the upper left corner and layout all the
00907   // cars using their height and width
00908   int maxWidth = 0;
00909   int maxHeight = 0;
00910   int xPos = 0;
00911   int yPos = 0;
00912   int cardSpacing = d->mItemSpacing;
00913 
00914   // delete the old separators
00915   d->mSeparatorList.clear();
00916 
00917   Q3PtrListIterator<CardViewItem> iter( d->mItemList );
00918   CardViewItem *item = 0;
00919   CardViewSeparator *sep = 0;
00920   xPos += cardSpacing;
00921 
00922   for ( iter.toFirst(); iter.current(); ++iter ) {
00923     item = *iter;
00924 
00925     yPos += cardSpacing;
00926 
00927     if ( yPos + item->height() + cardSpacing >= height() - horizontalScrollBar()->height() ) {
00928       maxHeight = qMax( maxHeight, yPos );
00929 
00930       // Drawing in this column would be greater than the height
00931       // of the scroll view, so move to next column
00932       yPos = cardSpacing;
00933       xPos += cardSpacing + maxWidth;
00934       if ( d->mDrawSeparators ) {
00935         // Create a separator since the user asked
00936         sep = new CardViewSeparator( this );
00937         sep->mRect.moveTopLeft( QPoint( xPos, yPos + d->mItemMargin ) );
00938         xPos += d->mSepWidth + cardSpacing;
00939         d->mSeparatorList.append( sep );
00940       }
00941 
00942       maxWidth = 0;
00943     }
00944 
00945     item->d->x = xPos;
00946     item->d->y = yPos;
00947 
00948     yPos += item->height();
00949     maxWidth = qMax( maxWidth, d->mItemWidth );
00950   }
00951 
00952   xPos += maxWidth;
00953   resizeContents( xPos + cardSpacing, maxHeight );
00954 
00955   // Update the height of all the separators now that we know the
00956   // max height of a column
00957   Q3PtrListIterator<CardViewSeparator> sepIter( d->mSeparatorList );
00958   for ( sepIter.toFirst(); sepIter.current(); ++sepIter )
00959     (*sepIter)->mRect.setHeight( maxHeight - 2 * cardSpacing - 2 * d->mItemMargin );
00960 
00961   d->mLayoutDirty = false;
00962 }
00963 
00964 CardViewItem *CardView::itemAfter( const CardViewItem *item ) const
00965 {
00966   d->mItemList.findRef( item );
00967   return d->mItemList.next();
00968 }
00969 
00970 uint CardView::itemMargin() const
00971 {
00972   return d->mItemMargin;
00973 }
00974 
00975 void CardView::setItemMargin( uint margin )
00976 {
00977   if ( margin == d->mItemMargin )
00978     return;
00979 
00980   d->mItemMargin = margin;
00981   setLayoutDirty( true );
00982 }
00983 
00984 uint CardView::itemSpacing() const
00985 {
00986   return d->mItemSpacing;
00987 }
00988 
00989 void CardView::setItemSpacing( uint spacing )
00990 {
00991   if ( spacing == d->mItemSpacing )
00992     return;
00993 
00994   d->mItemSpacing = spacing;
00995   setLayoutDirty( true );
00996 }
00997 
00998 void CardView::contentsMousePressEvent( QMouseEvent *e )
00999 {
01000   Q3ScrollView::contentsMousePressEvent( e );
01001 
01002   QPoint pos = contentsToViewport( e->pos() );
01003   d->mLastClickPos = e->pos();
01004 
01005   CardViewItem *item = itemAt( e->pos() );
01006 
01007   if ( item == 0 ) {
01008     d->mLastClickOnItem = false;
01009     if ( d->mOnSeparator) {
01010       d->mResizeAnchor = e->x() + contentsX();
01011       d->mColspace = (2 * d->mItemSpacing);
01012       int ccw = d->mItemWidth + d->mColspace + d->mSepWidth;
01013       d->mFirst = (contentsX() + d->mSepWidth) / ccw;
01014       d->mPressed = (d->mResizeAnchor + d->mSepWidth) / ccw;
01015       d->mSpan = d->mPressed - d->mFirst;
01016       d->mFirstX = d->mFirst * ccw;
01017       if ( d->mFirstX )
01018         d->mFirstX -= d->mSepWidth;
01019     } else {
01020       selectAll( false );
01021     }
01022 
01023     return;
01024   }
01025 
01026   d->mLastClickOnItem = true;
01027 
01028   CardViewItem *other = d->mCurrentItem;
01029   setCurrentItem( item );
01030 
01031   // Always emit the selection
01032   emit clicked( item );
01033 
01034   // The RMB click
01035   if (