kmail Library API Documentation

kmheaders.cpp

00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmheaders.cpp
00003 
00004 #include <config.h>
00005 
00006 #include "kmheaders.h"
00007 
00008 #include "kcursorsaver.h"
00009 #include "kmcommands.h"
00010 #include "kmfolderimap.h"
00011 #include "kmmainwidget.h"
00012 #include "kmcomposewin.h"
00013 #include "kmfiltermgr.h"
00014 #include "undostack.h"
00015 #include "kmmsgdict.h"
00016 #include "kmkernel.h"
00017 #include "kmdebug.h"
00018 using KMail::FolderJob;
00019 #include "broadcaststatus.h"
00020 using KPIM::BroadcastStatus;
00021 #include "actionscheduler.h"
00022 using KMail::ActionScheduler;
00023 #include <maillistdrag.h>
00024 #include "globalsettings.h"
00025 using namespace KPIM;
00026 
00027 #include <kapplication.h>
00028 #include <kaccelmanager.h>
00029 #include <kglobalsettings.h>
00030 #include <kmessagebox.h>
00031 #include <kiconloader.h>
00032 #include <kimageio.h>
00033 #include <kconfig.h>
00034 #include <klocale.h>
00035 #include <kdebug.h>
00036 
00037 #include <qbuffer.h>
00038 #include <qfile.h>
00039 #include <qheader.h>
00040 #include <qptrstack.h>
00041 #include <qptrqueue.h>
00042 #include <qpainter.h>
00043 #include <qtextcodec.h>
00044 #include <qbitmap.h>
00045 #include <qstyle.h>
00046 #include <qlistview.h>
00047 #include <qregexp.h>
00048 
00049 #include <mimelib/enum.h>
00050 #include <mimelib/field.h>
00051 #include <mimelib/mimepp.h>
00052 
00053 #include <stdlib.h>
00054 #include <errno.h>
00055 
00056 QPixmap* KMHeaders::pixNew = 0;
00057 QPixmap* KMHeaders::pixUns = 0;
00058 QPixmap* KMHeaders::pixDel = 0;
00059 QPixmap* KMHeaders::pixRead = 0;
00060 QPixmap* KMHeaders::pixRep = 0;
00061 QPixmap* KMHeaders::pixQueued = 0;
00062 QPixmap* KMHeaders::pixSent = 0;
00063 QPixmap* KMHeaders::pixFwd = 0;
00064 QPixmap* KMHeaders::pixFlag = 0;
00065 QPixmap* KMHeaders::pixWatched = 0;
00066 QPixmap* KMHeaders::pixIgnored = 0;
00067 QPixmap* KMHeaders::pixSpam = 0;
00068 QPixmap* KMHeaders::pixHam = 0;
00069 QPixmap* KMHeaders::pixFullySigned = 0;
00070 QPixmap* KMHeaders::pixPartiallySigned = 0;
00071 QPixmap* KMHeaders::pixUndefinedSigned = 0;
00072 QPixmap* KMHeaders::pixFullyEncrypted = 0;
00073 QPixmap* KMHeaders::pixPartiallyEncrypted = 0;
00074 QPixmap* KMHeaders::pixUndefinedEncrypted = 0;
00075 QPixmap* KMHeaders::pixEncryptionProblematic = 0;
00076 QPixmap* KMHeaders::pixSignatureProblematic = 0;
00077 QPixmap* KMHeaders::pixAttachment = 0;
00078 QPixmap* KMHeaders::pixTodo = 0;
00079 QPixmap* KMHeaders::pixReadFwd = 0;
00080 QPixmap* KMHeaders::pixReadReplied = 0;
00081 QPixmap* KMHeaders::pixReadFwdReplied = 0;
00082 
00083 #define KMAIL_SORT_VERSION 1012
00084 #define KMAIL_SORT_FILE(x) x->indexLocation() + ".sorted"
00085 #define KMAIL_SORT_HEADER "## KMail Sort V%04d\n\t"
00086 #define KMAIL_MAGIC_HEADER_OFFSET 21 //strlen(KMAIL_SORT_HEADER)
00087 #define KMAIL_MAX_KEY_LEN 16384
00088 #define KMAIL_RESERVED 3
00089 
00090 // Placed before KMHeaderItem because it is used there.
00091 class KMSortCacheItem {
00092     KMHeaderItem *mItem;
00093     KMSortCacheItem *mParent;
00094     int mId, mSortOffset;
00095     QString mKey;
00096 
00097     QPtrList<KMSortCacheItem> mSortedChildren;
00098     int mUnsortedCount, mUnsortedSize;
00099     KMSortCacheItem **mUnsortedChildren;
00100     bool mImperfectlyThreaded;
00101 
00102 public:
00103     KMSortCacheItem() : mItem(0), mParent(0), mId(-1), mSortOffset(-1),
00104         mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0),
00105         mImperfectlyThreaded (true) { }
00106     KMSortCacheItem(int i, QString k, int o=-1)
00107         : mItem(0), mParent(0), mId(i), mSortOffset(o), mKey(k),
00108           mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0),
00109           mImperfectlyThreaded (true) { }
00110     ~KMSortCacheItem() { if(mUnsortedChildren) free(mUnsortedChildren); }
00111 
00112     KMSortCacheItem *parent() const { return mParent; } //can't be set, only by the parent
00113     bool isImperfectlyThreaded() const
00114         { return mImperfectlyThreaded; }
00115     void setImperfectlyThreaded (bool val)
00116         { mImperfectlyThreaded = val; }
00117     bool hasChildren() const
00118         { return mSortedChildren.count() || mUnsortedCount; }
00119     const QPtrList<KMSortCacheItem> *sortedChildren() const
00120         { return &mSortedChildren; }
00121     KMSortCacheItem **unsortedChildren(int &count) const
00122         { count = mUnsortedCount; return mUnsortedChildren; }
00123     void addSortedChild(KMSortCacheItem *i) {
00124         i->mParent = this;
00125         mSortedChildren.append(i);
00126     }
00127     void addUnsortedChild(KMSortCacheItem *i) {
00128         i->mParent = this;
00129         if(!mUnsortedChildren)
00130             mUnsortedChildren = (KMSortCacheItem **)malloc((mUnsortedSize = 25) * sizeof(KMSortCacheItem *));
00131         else if(mUnsortedCount >= mUnsortedSize)
00132             mUnsortedChildren = (KMSortCacheItem **)realloc(mUnsortedChildren,
00133                                                             (mUnsortedSize *= 2) * sizeof(KMSortCacheItem *));
00134         mUnsortedChildren[mUnsortedCount++] = i;
00135     }
00136 
00137     KMHeaderItem *item() const { return mItem; }
00138     void setItem(KMHeaderItem *i) { Q_ASSERT(!mItem); mItem = i; }
00139 
00140     const QString &key() const { return mKey; }
00141     void setKey(const QString &key) { mKey = key; }
00142 
00143     int id() const { return mId; }
00144     void setId(int id) { mId = id; }
00145 
00146     int offset() const { return mSortOffset; }
00147     void setOffset(int x) { mSortOffset = x; }
00148 
00149     void updateSortFile( FILE *sortStream, KMFolder *folder,
00150                          bool waiting_for_parent = false,
00151                          bool update_discovered_count = false);
00152 };
00153 
00154 
00155 //-----------------------------------------------------------------------------
00156 // KMHeaderItem method definitions
00157 
00158 class KMHeaderItem : public KListViewItem
00159 {
00160 
00161 public:
00162   int mMsgId;
00163   QString mKey;
00164   // WARNING: Do not add new member variables to the class
00165 
00166   // Constuction a new list view item with the given colors and pixmap
00167     KMHeaderItem( QListView* parent, int msgId, const QString& key = QString::null )
00168     : KListViewItem( parent ),
00169           mMsgId( msgId ),
00170           mKey( key ),
00171           mAboutToBeDeleted( false ),
00172           mSortCacheItem( 0 )
00173   {
00174     irefresh();
00175   }
00176 
00177   // Constuction a new list view item with the given parent, colors, & pixmap
00178     KMHeaderItem( QListViewItem* parent, int msgId, const QString& key = QString::null )
00179     : KListViewItem( parent ),
00180           mMsgId( msgId ),
00181           mKey( key ),
00182           mAboutToBeDeleted( false ),
00183           mSortCacheItem( 0 )
00184   {
00185     irefresh();
00186   }
00187 
00188   ~KMHeaderItem ()
00189   {
00190     delete mSortCacheItem;
00191   }
00192 
00193   // Update the msgId this item corresponds to.
00194   void setMsgId( int aMsgId )
00195   {
00196     mMsgId = aMsgId;
00197   }
00198 
00199   // Profiling note: About 30% of the time taken to initialize the
00200   // listview is spent in this function. About 60% is spent in operator
00201   // new and QListViewItem::QListViewItem.
00202   void irefresh()
00203   {
00204     KMHeaders *headers = static_cast<KMHeaders*>(listView());
00205     NestingPolicy threadingPolicy = headers->getNestingPolicy();
00206     if ((threadingPolicy == AlwaysOpen) ||
00207         (threadingPolicy == DefaultOpen)) {
00208       //Avoid opening items as QListView is currently slow to do so.
00209         setOpen(true);
00210         return;
00211 
00212     }
00213     if (threadingPolicy == DefaultClosed)
00214       return; //default to closed
00215 
00216     // otherwise threadingPolicy == OpenUnread
00217     if (parent() && parent()->isOpen()) {
00218       setOpen(true);
00219       return;
00220     }
00221 
00222     KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId );
00223     if (mMsgBase->isNew() || mMsgBase->isUnread()
00224         || mMsgBase->isImportant() || mMsgBase->isWatched() ) {
00225       setOpen(true);
00226       KMHeaderItem * topOfThread = this;
00227       while(topOfThread->parent())
00228         topOfThread = (KMHeaderItem*)topOfThread->parent();
00229       topOfThread->setOpenRecursive(true);
00230     }
00231   }
00232 
00233   // Return the msgId of the message associated with this item
00234   int msgId() const
00235   {
00236     return mMsgId;
00237   }
00238 
00239   // Update this item to summarise a new folder and message
00240   void reset( int aMsgId )
00241   {
00242     mMsgId = aMsgId;
00243     irefresh();
00244   }
00245 
00246   //Opens all children in the thread
00247   void setOpenRecursive( bool open )
00248   {
00249     if (open){
00250       QListViewItem * lvchild;
00251       lvchild = firstChild();
00252       while (lvchild){
00253         ((KMHeaderItem*)lvchild)->setOpenRecursive( true );
00254         lvchild = lvchild->nextSibling();
00255       }
00256       setOpen( true );
00257     } else {
00258       setOpen( false );
00259     }
00260   }
00261 
00262   QString text( int col) const
00263   {
00264     KMHeaders *headers = static_cast<KMHeaders*>(listView());
00265     KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId );
00266     if ( !mMsgBase ) return QString();
00267     QString tmp;
00268 
00269 
00270     if(col == headers->paintInfo()->flagCol) {
00271       if (headers->paintInfo()->flagCol >= 0)
00272         tmp = QString( QChar( (char)mMsgBase->status() ));
00273 
00274     } else if(col == headers->paintInfo()->senderCol) {
00275        if ( (headers->folder()->whoField().lower() == "to") && !headers->paintInfo()->showReceiver )
00276         tmp = mMsgBase->toStrip();
00277       else
00278         tmp = mMsgBase->fromStrip();
00279       if (tmp.isEmpty())
00280         tmp = i18n("Unknown");
00281       else
00282         tmp = tmp.simplifyWhiteSpace();
00283 
00284     } else if ( col == headers->paintInfo()->receiverCol ) {
00285        tmp = mMsgBase->toStrip();
00286        if (tmp.isEmpty())
00287          tmp = i18n("Unknown");
00288        else
00289          tmp = tmp.simplifyWhiteSpace();
00290 
00291     } else if(col == headers->paintInfo()->subCol) {
00292       tmp = mMsgBase->subject();
00293       if (tmp.isEmpty())
00294         tmp = i18n("No Subject");
00295       else
00296         tmp.remove(QRegExp("[\r\n]"));
00297 
00298     } else if(col == headers->paintInfo()->dateCol) {
00299       tmp = headers->mDate.dateString( mMsgBase->date() );
00300     } else if(col == headers->paintInfo()->sizeCol
00301       && headers->paintInfo()->showSize) {
00302       if ( mMsgBase->parent()->folderType() == KMFolderTypeImap ) {
00303         tmp = KIO::convertSize( mMsgBase->msgSizeServer() );
00304       } else {
00305         tmp = KIO::convertSize( mMsgBase->msgSize() );
00306       }
00307     }
00308     return tmp;
00309   }
00310 
00311   void setup()
00312   {
00313     widthChanged();
00314     const int ph = KMHeaders::pixNew->height();
00315     QListView *v = listView();
00316     int h = QMAX( v->fontMetrics().height(), ph ) + 2*v->itemMargin();
00317     h = QMAX( h, QApplication::globalStrut().height());
00318     if ( h % 2 > 0 )
00319       h++;
00320     setHeight( h );
00321   }
00322 
00323   typedef QValueList<QPixmap> PixmapList;
00324 
00325   QPixmap pixmapMerge( PixmapList pixmaps ) const {
00326       int width = 0;
00327       int height = 0;
00328       for ( PixmapList::ConstIterator it = pixmaps.begin();
00329             it != pixmaps.end(); ++it ) {
00330           width += (*it).width();
00331           height = QMAX( height, (*it).height() );
00332       }
00333 
00334       QPixmap res( width, height );
00335       QBitmap mask( width, height );
00336 
00337       int x = 0;
00338       for ( PixmapList::ConstIterator it = pixmaps.begin();
00339           it != pixmaps.end(); ++it ) {
00340           bitBlt( &res, x, 0, &(*it) );
00341           bitBlt( &mask, x, 0, (*it).mask() );
00342           x += (*it).width();
00343       }
00344 
00345       res.setMask( mask );
00346       return res;
00347   }
00348 
00349 //Backported from 3.5
00350 //Helper methods
00351  const QPixmap * cryptoIcon(KMMsgBase *msgBase) const
00352  {
00353    switch ( msgBase->encryptionState() )
00354    {
00355     case KMMsgFullyEncrypted        : return KMHeaders::pixFullyEncrypted;
00356     case KMMsgPartiallyEncrypted    : return KMHeaders::pixPartiallyEncrypted;
00357     case KMMsgEncryptionStateUnknown: return KMHeaders::pixUndefinedEncrypted;
00358     case KMMsgEncryptionProblematic : return KMHeaders::pixEncryptionProblematic;
00359     default                         : return 0;
00360    }
00361  }
00362 
00363  const QPixmap * signatureIcon(KMMsgBase *msgBase) const
00364  {
00365    switch ( msgBase->signatureState() )
00366    {
00367     case KMMsgFullySigned          : return KMHeaders::pixFullySigned;
00368     case KMMsgPartiallySigned      : return KMHeaders::pixPartiallySigned;
00369     case KMMsgSignatureStateUnknown: return KMHeaders::pixUndefinedSigned;
00370     case KMMsgSignatureProblematic : return KMHeaders::pixSignatureProblematic;
00371     default                        : return 0;
00372    }
00373  }
00374 
00375  const QPixmap * statusIcon(KMMsgBase *msgBase) const
00376  {
00377    // forwarded, replied have precedence over the other states
00378    if (  msgBase->isForwarded() && !msgBase->isReplied() ) return KMHeaders::pixReadFwd;
00379    if ( !msgBase->isForwarded() &&  msgBase->isReplied() ) return KMHeaders::pixReadReplied;
00380    if (  msgBase->isForwarded() &&  msgBase->isReplied() ) return KMHeaders::pixReadFwdReplied;
00381 
00382    // a queued or sent mail is usually also read
00383    if ( msgBase->isQueued() ) return KMHeaders::pixQueued;
00384    if ( msgBase->isSent()   ) return KMHeaders::pixSent;
00385 
00386    if ( msgBase->isNew()                      ) return KMHeaders::pixNew;
00387    if ( msgBase->isRead() || msgBase->isOld() ) return KMHeaders::pixRead;
00388    if ( msgBase->isUnread()                   ) return KMHeaders::pixUns;
00389    if ( msgBase->isDeleted()                  ) return KMHeaders::pixDel;
00390 
00391    return 0;
00392  }
00393 
00394  //Backported from 3.5
00395  const QPixmap * pixmap(int col) const
00396  {
00397    KMHeaders *headers = static_cast<KMHeaders*>(listView());
00398    KMMsgBase *msgBase = headers->folder()->getMsgBase( mMsgId );
00399 
00400    if ( col == headers->paintInfo()->subCol ) {
00401 
00402     PixmapList pixmaps;
00403 
00404     if ( !headers->mPaintInfo.showSpamHam ) {
00405       // Have the spam/ham and watched/ignored icons first, I guess.
00406       if ( msgBase->isSpam() ) pixmaps << *KMHeaders::pixSpam;
00407       if ( msgBase->isHam()  ) pixmaps << *KMHeaders::pixHam;
00408     }
00409 
00410     if ( !headers->mPaintInfo.showWatchedIgnored ) {
00411       if ( msgBase->isIgnored() ) pixmaps << *KMHeaders::pixIgnored;
00412       if ( msgBase->isWatched() ) pixmaps << *KMHeaders::pixWatched;
00413     }
00414 
00415     if ( !headers->mPaintInfo.showStatus ) {
00416       const QPixmap *pix = statusIcon(msgBase);
00417       if ( pix ) pixmaps << *pix;
00418     }
00419 
00420     // Only merge the attachment icon in if that is configured.
00421     if ( headers->paintInfo()->showAttachmentIcon &&
00422         !headers->paintInfo()->showAttachment &&
00423         msgBase->attachmentState() == KMMsgHasAttachment )
00424       pixmaps << *KMHeaders::pixAttachment;
00425 
00426     // Only merge the crypto icons in if that is configured.
00427     if ( headers->paintInfo()->showCryptoIcons ) {
00428       const QPixmap *pix;
00429 
00430       if ( !headers->paintInfo()->showCrypto )
00431         if ( (pix = cryptoIcon(msgBase))    ) pixmaps << *pix;
00432 
00433        if ( !headers->paintInfo()->showSigned )
00434          if ( (pix = signatureIcon(msgBase)) ) pixmaps << *pix;
00435      }
00436 
00437      if ( !headers->mPaintInfo.showImportant )
00438        if ( msgBase->isImportant() ) pixmaps << *KMHeaders::pixFlag;
00439 
00440      if ( !headers->mPaintInfo.showTodo )
00441        if ( msgBase->isTodo() ) pixmaps << *KMHeaders::pixTodo;
00442 
00443      static QPixmap mergedpix;
00444      mergedpix = pixmapMerge( pixmaps );
00445      return &mergedpix;
00446    }
00447    else if ( col == headers->paintInfo()->statusCol ) {
00448      return statusIcon(msgBase);
00449    }
00450    else if ( col == headers->paintInfo()->attachmentCol ) {
00451      if ( msgBase->attachmentState() == KMMsgHasAttachment )
00452        return KMHeaders::pixAttachment;
00453    }
00454    else if ( col == headers->paintInfo()->importantCol ) {
00455      if ( msgBase->isImportant() )
00456        return KMHeaders::pixFlag;
00457    }
00458    else if ( col == headers->paintInfo()->todoCol ) {
00459      if ( msgBase->isTodo() )
00460        return KMHeaders::pixTodo;
00461    }
00462    else if ( col == headers->paintInfo()->spamHamCol ) {
00463      if ( msgBase->isSpam() ) return KMHeaders::pixSpam;
00464      if ( msgBase->isHam()  ) return KMHeaders::pixHam;
00465    }
00466    else if ( col == headers->paintInfo()->watchedIgnoredCol ) {
00467      if ( msgBase->isWatched() ) return KMHeaders::pixWatched;
00468      if ( msgBase->isIgnored() ) return KMHeaders::pixIgnored;
00469    }
00470    else if ( col == headers->paintInfo()->signedCol ) {
00471      return signatureIcon(msgBase);
00472    }
00473    else if ( col == headers->paintInfo()->cryptoCol ) {
00474      return cryptoIcon(msgBase);
00475    }
00476    return 0;
00477  }
00478 
00479 
00480   void paintCell( QPainter * p, const QColorGroup & cg,
00481                                 int column, int width, int align )
00482   {
00483     KMHeaders *headers = static_cast<KMHeaders*>(listView());
00484     if (headers->noRepaint) return;
00485     if (!headers->folder()) return;
00486     QColorGroup _cg( cg );
00487     QColor c = _cg.text();
00488     QColor *color;
00489 
00490     KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId );
00491     if (!mMsgBase) return;
00492 
00493     color = (QColor *)(&headers->paintInfo()->colFore);
00494     // new overrides unread, and flagged overrides new.
00495     if (mMsgBase->isUnread()) color = (QColor*)(&headers->paintInfo()->colUnread);
00496     if (mMsgBase->isNew()) color = (QColor*)(&headers->paintInfo()->colNew);
00497     if (mMsgBase->isImportant()) color = (QColor*)(&headers->paintInfo()->colFlag);
00498     if (mMsgBase->isTodo()) color = (QColor*)(&headers->paintInfo()->colTodo);
00499 
00500     _cg.setColor( QColorGroup::Text, *color );
00501 
00502     if( column == headers->paintInfo()->dateCol )
00503       p->setFont(headers->dateFont);
00504 
00505     KListViewItem::paintCell( p, _cg, column, width, align );
00506 
00507     if (aboutToBeDeleted()) {
00508       // strike through
00509       p->drawLine( 0, height()/2, width, height()/2);
00510     }
00511     _cg.setColor( QColorGroup::Text, c );
00512   }
00513 
00514   static QString generate_key( KMHeaders *headers, KMMsgBase *msg, const KPaintInfo *paintInfo, int sortOrder )
00515 {
00516   // It appears, that QListView in Qt-3.0 asks for the key
00517   // in QListView::clear(), which is called from
00518   // readSortOrder()
00519   if (!msg) return QString::null;
00520 
00521   int column = sortOrder & ((1 << 5) - 1);
00522   QString ret = QChar( (char)sortOrder );
00523   QString sortArrival = QString( "%1" ).arg( msg->getMsgSerNum(), 0, 36 );
00524   while (sortArrival.length() < 7) sortArrival = '0' + sortArrival;
00525 
00526   if (column == paintInfo->dateCol) {
00527     if (paintInfo->orderOfArrival)
00528       return ret + sortArrival;
00529     else {
00530       QString d = QString::number(msg->date());
00531       while (d.length() <= 10) d = '0' + d;
00532       return ret + d + sortArrival;
00533     }
00534   } else if (column == paintInfo->senderCol) {
00535     QString tmp;
00536     if ( (headers->folder()->whoField().lower() == "to") && !headers->paintInfo()->showReceiver )
00537       tmp = msg->toStrip();
00538     else
00539       tmp = msg->fromStrip();
00540     return ret + tmp.lower() + ' ' + sortArrival;
00541   } else if (column == paintInfo->receiverCol) {
00542     QString tmp = msg->toStrip();
00543     return ret + tmp.lower() + ' ' + sortArrival;
00544   } else if (column == paintInfo->subCol) {
00545     QString tmp;
00546     tmp = ret;
00547     if (paintInfo->status) {
00548       tmp += msg->statusToSortRank() + ' ';
00549     }
00550     tmp += KMMessage::stripOffPrefixes( msg->subject().lower() ) + ' ' + sortArrival;
00551     return tmp;
00552   }
00553   else if (column == paintInfo->sizeCol) {
00554     QString len;
00555     if ( msg->parent()->folderType() == KMFolderTypeImap )
00556     {
00557       len = QString::number( msg->msgSizeServer() );
00558     } else {
00559       len = QString::number( msg->msgSize() );
00560     }
00561     while (len.length() < 9) len = '0' + len;
00562     return ret + len + sortArrival;
00563   }
00564   else if (column == paintInfo->statusCol) {
00565     QString s;
00566     if      ( msg->isNew()                            ) s = "1";
00567     else if ( msg->isUnread()                         ) s = "2";
00568     else if (!msg->isForwarded() &&  msg->isReplied() ) s = "3";
00569     else if ( msg->isForwarded() &&  msg->isReplied() ) s = "4";
00570     else if ( msg->isForwarded() && !msg->isReplied() ) s = "5";
00571     else if ( msg->isRead() || msg->isOld()           ) s = "6";
00572     else if ( msg->isQueued()                         ) s = "7";
00573     else if ( msg->isSent()                           ) s = "8";
00574     else if ( msg->isDeleted()                        ) s = "9";
00575     return ret + s + sortArrival;
00576   }
00577   else if (column == paintInfo->attachmentCol) {
00578     QString s(msg->attachmentState() == KMMsgHasAttachment ? "1" : "0");
00579     return ret + s + sortArrival;
00580   }
00581   else if (column == paintInfo->importantCol) {
00582     QString s(msg->isImportant() ? "1" : "0");
00583     return ret + s + sortArrival;
00584   }
00585   else if ( column == paintInfo->todoCol ) {
00586     QString s( msg->isTodo() ? "1": "0" );
00587     return ret + s + sortArrival;
00588   }
00589   else if (column == paintInfo->spamHamCol) {
00590     QString s((msg->isSpam() || msg->isHam()) ? "1" : "0");
00591     return ret + s + sortArrival;
00592   }
00593   else if (column == paintInfo->watchedIgnoredCol) {
00594     QString s((msg->isWatched() || msg->isIgnored()) ? "1" : "0");
00595     return ret + s + sortArrival;
00596   }
00597   else if (column == paintInfo->signedCol) {
00598     QString s;
00599     switch ( msg->signatureState() )
00600     {
00601       case KMMsgFullySigned          : s = "1"; break;
00602       case KMMsgPartiallySigned      : s = "2"; break;
00603       case KMMsgSignatureStateUnknown: s = "3"; break;
00604       case KMMsgSignatureProblematic : s = "4"; break;
00605       default                        : s = "5"; break;
00606     }
00607     return ret + s + sortArrival;
00608   }
00609   else if (column == paintInfo->cryptoCol) {
00610     QString s;
00611     switch ( msg->encryptionState() )
00612     {
00613       case KMMsgFullyEncrypted        : s = "1"; break;
00614       case KMMsgPartiallyEncrypted    : s = "2"; break;
00615       case KMMsgEncryptionStateUnknown: s = "3"; break;
00616       case KMMsgEncryptionProblematic : s = "4"; break;
00617       default                         : s = "5"; break;
00618     }
00619     return ret + s + sortArrival;
00620   }
00621   return ret + "missing key"; //you forgot something!!
00622 }
00623 
00624   virtual QString key( int column, bool /*ascending*/ ) const
00625   {
00626     KMHeaders *headers = static_cast<KMHeaders*>(listView());
00627     int sortOrder = column;
00628     if (headers->mPaintInfo.orderOfArrival)
00629       sortOrder |= (1 << 6);
00630     if (headers->mPaintInfo.status)
00631       sortOrder |= (1 << 5);
00632     //This code should stay pretty much like this, if you are adding new
00633     //columns put them in generate_key
00634     if(mKey.isEmpty() || mKey[0] != (char)sortOrder) {
00635       KMHeaders *headers = static_cast<KMHeaders*>(listView());
00636       KMMsgBase *msgBase = headers->folder()->getMsgBase( mMsgId );
00637       return ((KMHeaderItem *)this)->mKey =
00638         generate_key( headers, msgBase, headers->paintInfo(), sortOrder );
00639     }
00640     return mKey;
00641   }
00642 
00643   void setTempKey( QString key ) {
00644     mKey = key;
00645   }
00646 
00647  int compare( QListViewItem *i, int col, bool ascending ) const
00648  {
00649    //kdDebug(5006) << k_funcinfo << col << " " << ascending << endl;
00650    int res = 0;
00651    KMHeaders *headers = static_cast<KMHeaders*>(listView());
00652    if ( ( col == headers->paintInfo()->statusCol         ) ||
00653        ( col == headers->paintInfo()->sizeCol           ) ||
00654        ( col == headers->paintInfo()->attachmentCol     ) ||
00655        ( col == headers->paintInfo()->importantCol      ) ||
00656        ( col == headers->paintInfo()->todoCol           ) ||
00657        ( col == headers->paintInfo()->spamHamCol        ) ||
00658        ( col == headers->paintInfo()->signedCol         ) ||
00659        ( col == headers->paintInfo()->cryptoCol         ) ||
00660        ( col == headers->paintInfo()->watchedIgnoredCol ) ) {
00661      res = key( col, ascending ).compare( i->key( col, ascending ) );
00662    }  else if ( col == headers->paintInfo()->dateCol ) {
00663      res = key( col, ascending ).compare( i->key( col, ascending ) );
00664      if (i->parent() && !ascending)
00665        res = -res;
00666    } else if ( col == headers->paintInfo()->subCol ||
00667        col == headers->paintInfo()->senderCol ||
00668        col == headers->paintInfo()->receiverCol ) {
00669      res = key( col, ascending ).localeAwareCompare( i->key( col, ascending ) );
00670    }
00671    return res;
00672  }
00673 
00674   QListViewItem* firstChildNonConst() /* Non const! */ {
00675     enforceSortOrder(); // Try not to rely on QListView implementation details
00676     return firstChild();
00677   }
00678 
00679   bool mAboutToBeDeleted;
00680   bool aboutToBeDeleted() const { return mAboutToBeDeleted; }
00681   void setAboutToBeDeleted( bool val ) { mAboutToBeDeleted = val; }
00682 
00683   KMSortCacheItem *mSortCacheItem;
00684   void setSortCacheItem( KMSortCacheItem *item ) { mSortCacheItem = item; }
00685   KMSortCacheItem* sortCacheItem() const { return mSortCacheItem; }
00686 };
00687 
00688 //-----------------------------------------------------------------------------
00689 KMHeaders::KMHeaders(KMMainWidget *aOwner, QWidget *parent,
00690                      const char *name) :
00691   KListView(parent, name)
00692 {
00693   static bool pixmapsLoaded = false;
00694   //qInitImageIO();
00695   KImageIO::registerFormats();
00696   mOwner  = aOwner;
00697   mFolder = 0;
00698   noRepaint = false;
00699   getMsgIndex = -1;
00700   mTopItem = 0;
00701   setSelectionMode( QListView::Extended );
00702   setAllColumnsShowFocus( true );
00703   mNested = false;
00704   nestingPolicy = OpenUnread;
00705   mNestedOverride = false;
00706   mSubjThreading = true;
00707   mMousePressed = false;
00708   mSortInfo.dirty = true;
00709   mSortInfo.fakeSort = 0;
00710   mSortInfo.removed = 0;
00711   mSortInfo.column = 0;
00712   mSortInfo.ascending = false;
00713   mReaderWindowActive = false;
00714   setStyleDependantFrameWidth();
00715   // popup-menu
00716   header()->setClickEnabled(true);
00717   header()->installEventFilter(this);
00718   mPopup = new KPopupMenu(this);
00719   mPopup->insertTitle(i18n("View Columns"));
00720   mPopup->setCheckable(true);
00721   mPopup->insertItem(i18n("Status"),          KPaintInfo::COL_STATUS);
00722   mPopup->insertItem(i18n("Important"),       KPaintInfo::COL_IMPORTANT);
00723   mPopup->insertItem(i18n("Todo"),            KPaintInfo::COL_TODO);
00724   mPopup->insertItem(i18n("Attachment"),      KPaintInfo::COL_ATTACHMENT);
00725   mPopup->insertItem(i18n("Spam/Ham"),        KPaintInfo::COL_SPAM_HAM);
00726   mPopup->insertItem(i18n("Watched/Ignored"), KPaintInfo::COL_WATCHED_IGNORED);
00727   mPopup->insertItem(i18n("Signature"),       KPaintInfo::COL_SIGNED);
00728   mPopup->insertItem(i18n("Encryption"),      KPaintInfo::COL_CRYPTO);
00729   mPopup->insertItem(i18n("Size"),            KPaintInfo::COL_SIZE);
00730   mPopup->insertItem(i18n("Receiver"),        KPaintInfo::COL_RECEIVER);
00731 
00732   connect(mPopup, SIGNAL(activated(int)), this, SLOT(slotToggleColumn(int)));
00733 
00734   mPaintInfo.flagCol = -1;
00735   mPaintInfo.subCol    = mPaintInfo.flagCol   + 1;
00736   mPaintInfo.senderCol = mPaintInfo.subCol    + 1;
00737   mPaintInfo.dateCol   = mPaintInfo.senderCol + 1;
00738   mPaintInfo.orderOfArrival = false;
00739   mPaintInfo.status = false;
00740   mSortCol = KMMsgList::sfDate;
00741   mSortDescending = false;
00742 
00743   setShowSortIndicator(true);
00744   setFocusPolicy( WheelFocus );
00745 
00746   if (!pixmapsLoaded)
00747   {
00748     pixmapsLoaded = true;
00749     pixNew   = new QPixmap( UserIcon("kmmsgnew") );
00750     pixUns   = new QPixmap( UserIcon("kmmsgunseen") );
00751     pixDel   = new QPixmap( UserIcon("kmmsgdel") );
00752     pixRead   = new QPixmap( UserIcon("kmmsgread") );
00753     pixRep   = new QPixmap( UserIcon("kmmsgreplied") );
00754     pixQueued= new QPixmap( UserIcon("kmmsgqueued") );
00755     pixSent  = new QPixmap( UserIcon("kmmsgsent") );
00756     pixFwd   = new QPixmap( UserIcon("kmmsgforwarded") );
00757     pixFlag  = new QPixmap( UserIcon("kmmsgflag") );
00758     pixWatched  = new QPixmap( UserIcon("kmmsgwatched") );
00759     pixIgnored  = new QPixmap( UserIcon("kmmsgignored") );
00760     pixSpam  = new QPixmap( UserIcon("kmmsgspam") );
00761     pixHam  = new QPixmap( UserIcon("kmmsgham") );
00762     pixFullySigned = new QPixmap( UserIcon( "kmmsgfullysigned" ) );
00763     pixPartiallySigned = new QPixmap( UserIcon( "kmmsgpartiallysigned" ) );
00764     pixUndefinedSigned = new QPixmap( UserIcon( "kmmsgundefinedsigned" ) );
00765     pixFullyEncrypted = new QPixmap( UserIcon( "kmmsgfullyencrypted" ) );
00766     pixPartiallyEncrypted = new QPixmap( UserIcon( "kmmsgpartiallyencrypted" ) );
00767     pixUndefinedEncrypted = new QPixmap( UserIcon( "kmmsgundefinedencrypted" ) );
00768     pixEncryptionProblematic = new QPixmap( UserIcon( "kmmsgencryptionproblematic" ) );
00769     pixSignatureProblematic = new QPixmap( UserIcon( "kmmsgsignatureproblematic" ) );
00770     pixAttachment  = new QPixmap( UserIcon( "kmmsgattachment" ) );
00771     pixTodo                  = new QPixmap( UserIcon( "kmmsgtodo"                  ) );
00772     pixReadFwd               = new QPixmap( UserIcon( "kmmsgread_fwd"              ) );
00773     pixReadReplied           = new QPixmap( UserIcon( "kmmsgread_replied"          ) );
00774     pixReadFwdReplied        = new QPixmap( UserIcon( "kmmsgread_fwd_replied"      ) );
00775   }
00776 
00777   header()->setStretchEnabled( false );
00778   header()->setResizeEnabled( false );
00779 
00780   mPaintInfo.subCol      = addColumn( i18n("Subject"), 310 );
00781   mPaintInfo.senderCol   = addColumn( i18n("Sender"),  170 );
00782   mPaintInfo.dateCol     = addColumn( i18n("Date"),    170 );
00783   mPaintInfo.sizeCol     = addColumn( i18n("Size"),      0 );
00784   mPaintInfo.receiverCol = addColumn( i18n("Receiver"),  0 );
00785 
00786   mPaintInfo.statusCol         = addColumn( *pixNew           , "", 0 );
00787   mPaintInfo.importantCol      = addColumn( *pixFlag          , "", 0 );
00788   mPaintInfo.todoCol           = addColumn( *pixTodo          , "", 0 );
00789   mPaintInfo.attachmentCol     = addColumn( *pixAttachment    , "", 0 );
00790   mPaintInfo.spamHamCol        = addColumn( *pixSpam          , "", 0 );
00791   mPaintInfo.watchedIgnoredCol = addColumn( *pixWatched       , "", 0 );
00792   mPaintInfo.signedCol         = addColumn( *pixFullySigned   , "", 0 );
00793   mPaintInfo.cryptoCol         = addColumn( *pixFullyEncrypted, "", 0 );
00794 
00795   setResizeMode( QListView::NoColumn );
00796 
00797   // only the non-optional columns shall be resizeable
00798   header()->setResizeEnabled( true, mPaintInfo.subCol );
00799   header()->setResizeEnabled( true, mPaintInfo.senderCol );
00800   header()->setResizeEnabled( true, mPaintInfo.dateCol );
00801 
00802   readConfig();
00803   restoreLayout(KMKernel::config(), "Header-Geometry");
00804 
00805   connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int )),
00806            this, SLOT( rightButtonPressed( QListViewItem*, const QPoint &, int )));
00807   connect(this, SIGNAL(doubleClicked(QListViewItem*)),
00808           this,SLOT(selectMessage(QListViewItem*)));
00809   connect(this,SIGNAL(currentChanged(QListViewItem*)),
00810           this,SLOT(highlightMessage(QListViewItem*)));
00811   resetCurrentTime();
00812 
00813   mSubjectLists.setAutoDelete( true );
00814 }
00815 
00816 
00817 //-----------------------------------------------------------------------------
00818 KMHeaders::~KMHeaders ()
00819 {
00820   if (mFolder)
00821   {
00822     writeFolderConfig();
00823     writeSortOrder();
00824     mFolder->close();
00825   }
00826   writeConfig();
00827 }
00828 
00829 //Backported from 3.5
00830 //-----------------------------------------------------------------------------
00831 bool KMHeaders::eventFilter ( QObject *o, QEvent *e )
00832 {
00833   if ( e->type() == QEvent::MouseButtonPress &&
00834       static_cast<QMouseEvent*>(e)->button() == RightButton &&
00835       o->isA("QHeader") )
00836   {
00837     // if we currently only show one of either sender/receiver column
00838     // modify the popup text in the way, that it displays the text of the other of the two
00839     if ( mPaintInfo.showReceiver )
00840       mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
00841     else
00842       if ( mFolder && (mFolder->whoField().lower() == "to") )
00843         mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Sender"));
00844       else
00845         mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
00846 
00847     mPopup->popup( static_cast<QMouseEvent*>(e)->globalPos() );
00848     return true;
00849   }
00850   return KListView::eventFilter(o, e);
00851 }
00852 
00853 //Backported from 3.5 : void KMHeaders::slotToggleColumn(int id, int mode)
00854 void KMHeaders::slotToggleColumn(int id, int mode)
00855 {
00856   bool *show = 0;
00857   int  *col  = 0;
00858   int  width = 0;
00859 
00860   switch ( static_cast<KPaintInfo::ColumnIds>(id) )
00861   {
00862     case KPaintInfo::COL_SIZE:
00863     {
00864       show  = &mPaintInfo.showSize;
00865       col   = &mPaintInfo.sizeCol;
00866       width = 80;
00867       break;
00868     }
00869     case KPaintInfo::COL_ATTACHMENT:
00870     {
00871       show  = &mPaintInfo.showAttachment;
00872       col   = &mPaintInfo.attachmentCol;
00873       width = pixAttachment->width();
00874       break;
00875     }
00876     case KPaintInfo::COL_IMPORTANT:
00877     {
00878       show  = &mPaintInfo.showImportant;
00879       col   = &mPaintInfo.importantCol;
00880       width = pixFlag->width();
00881       break;
00882     }
00883     case KPaintInfo::COL_TODO:
00884     {
00885       show  = &mPaintInfo.showTodo;
00886       col   = &mPaintInfo.todoCol;
00887       width = pixTodo->width();
00888       break;
00889     }
00890     case KPaintInfo::COL_SPAM_HAM:
00891     {
00892       show  = &mPaintInfo.showSpamHam;
00893       col   = &mPaintInfo.spamHamCol;
00894       width = pixSpam->width();
00895       break;
00896     }
00897     case KPaintInfo::COL_WATCHED_IGNORED:
00898     {
00899       show  = &mPaintInfo.showWatchedIgnored;
00900       col   = &mPaintInfo.watchedIgnoredCol;
00901       width = pixWatched->width();
00902       break;
00903     }
00904     case KPaintInfo::COL_STATUS:
00905     {
00906       show  = &mPaintInfo.showStatus;
00907       col   = &mPaintInfo.statusCol;
00908       width = pixNew->width();
00909       break;
00910     }
00911     case KPaintInfo::COL_SIGNED:
00912     {
00913       show  = &mPaintInfo.showSigned;
00914       col   = &mPaintInfo.signedCol;
00915       width = pixFullySigned->width();
00916       break;
00917     }
00918     case KPaintInfo::COL_CRYPTO:
00919     {
00920       show  = &mPaintInfo.showCrypto;
00921       col   = &mPaintInfo.cryptoCol;
00922       width = pixFullyEncrypted->width();
00923       break;
00924     }
00925     case KPaintInfo::COL_RECEIVER:
00926     {
00927       show  = &mPaintInfo.showReceiver;
00928       col   = &mPaintInfo.receiverCol;
00929       width = 170;
00930       break;
00931     }
00932     case KPaintInfo::COL_SCORE: ; // only used by KNode
00933     // don't use default, so that the compiler tells us you forgot to code here for a new column
00934   }
00935 
00936   assert(show);
00937 
00938   if (mode == -1)
00939     *show = !*show;
00940   else
00941     *show = mode;
00942 
00943   mPopup->setItemChecked(id, *show);
00944 
00945   if (*show) {
00946     header()->setResizeEnabled(true, *col);
00947     setColumnWidth(*col, width);
00948   }
00949   else {
00950     header()->setResizeEnabled(false, *col);
00951     header()->setStretchEnabled(false, *col);
00952     hideColumn(*col);
00953   }
00954 
00955   // if we change the visibility of the receiver column,
00956   // the sender column has to show either the sender or the receiver
00957   if ( static_cast<KPaintInfo::ColumnIds>(id) ==  KPaintInfo::COL_RECEIVER ) {
00958     QString colText = i18n( "Sender" );
00959     if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
00960       colText = i18n( "Receiver" );
00961     setColumnText( mPaintInfo.senderCol, colText );
00962   }
00963 
00964   if (mode == -1)
00965     writeConfig();
00966 }
00967 
00968 //-----------------------------------------------------------------------------
00969 // Support for backing pixmap
00970 void KMHeaders::paintEmptyArea( QPainter * p, const QRect & rect )
00971 {
00972   if (mPaintInfo.pixmapOn)
00973     p->drawTiledPixmap( rect.left(), rect.top(), rect.width(), rect.height(),
00974                         mPaintInfo.pixmap,
00975                         rect.left() + contentsX(),
00976                         rect.top() + contentsY() );
00977   else
00978     p->fillRect( rect, colorGroup().base() );
00979 }
00980 
00981 bool KMHeaders::event(QEvent *e)
00982 {
00983   bool result = KListView::event(e);
00984   if (e->type() == QEvent::ApplicationPaletteChange)
00985   {
00986      readColorConfig();
00987   }
00988   return result;
00989 }
00990 
00991 
00992 //-----------------------------------------------------------------------------
00993 void KMHeaders::readColorConfig (void)
00994 {
00995   KConfig* config = KMKernel::config();
00996   // Custom/System colors
00997   KConfigGroupSaver saver(config, "Reader");
00998   QColor c1=QColor(kapp->palette().active().text());
00999   QColor c2=QColor("red");
01000   QColor c3=QColor("blue");
01001   QColor c4=QColor(kapp->palette().active().base());
01002   QColor c5=QColor(0,0x7F,0);
01003   QColor c6=KGlobalSettings::alternateBackgroundColor();
01004 
01005   if (!config->readBoolEntry("defaultColors",true)) {
01006     mPaintInfo.colFore = config->readColorEntry("ForegroundColor",&c1);
01007     mPaintInfo.colBack = config->readColorEntry("BackgroundColor",&c4);
01008     QPalette newPal = kapp->palette();
01009     newPal.setColor( QColorGroup::Base, mPaintInfo.colBack );
01010     newPal.setColor( QColorGroup::Text, mPaintInfo.colFore );
01011     setPalette( newPal );
01012     mPaintInfo.colNew = config->readColorEntry("NewMessage",&c2);
01013     mPaintInfo.colUnread = config->readColorEntry("UnreadMessage",&c3);
01014     mPaintInfo.colFlag = config->readColorEntry("FlagMessage",&c5);
01015     c6 = config->readColorEntry("AltBackgroundColor",&c6);
01016   }
01017   else {
01018     mPaintInfo.colFore = c1;
01019     mPaintInfo.colBack = c4;
01020     QPalette newPal = kapp->palette();
01021     newPal.setColor( QColorGroup::Base, c4 );
01022     newPal.setColor( QColorGroup::Text, c1 );
01023     setPalette( newPal );
01024     mPaintInfo.colNew = c2;
01025     mPaintInfo.colUnread = c3;
01026     mPaintInfo.colFlag = c5;
01027   }
01028   setAlternateBackground(c6);
01029 }
01030 
01031 //-----------------------------------------------------------------------------
01032 void KMHeaders::readConfig (void)
01033 {
01034   KConfig* config = KMKernel::config();
01035 
01036   // Backing pixmap support
01037   { // area for config group "Pixmaps"
01038     KConfigGroupSaver saver(config, "Pixmaps");
01039     QString pixmapFile = config->readEntry("Headers");
01040     mPaintInfo.pixmapOn = false;
01041     if (!pixmapFile.isEmpty()) {
01042       mPaintInfo.pixmapOn = true;
01043       mPaintInfo.pixmap = QPixmap( pixmapFile );
01044     }
01045   }
01046 
01047   //backported from 3.5
01048   { // area for config group "General"
01049     KConfigGroupSaver saver(config, "General");
01050     bool show = config->readBoolEntry("showMessageSize");
01051     slotToggleColumn(KPaintInfo::COL_SIZE, show);
01052 
01053     show = config->readBoolEntry("showAttachmentColumn");
01054     slotToggleColumn(KPaintInfo::COL_ATTACHMENT, show);
01055 
01056     show = config->readBoolEntry("showImportantColumn");
01057     slotToggleColumn(KPaintInfo::COL_IMPORTANT, show);
01058 
01059     show = config->readBoolEntry("showTodoColumn");
01060     slotToggleColumn(KPaintInfo::COL_TODO, show);
01061 
01062     show = config->readBoolEntry("showSpamHamColumn");
01063     slotToggleColumn(KPaintInfo::COL_SPAM_HAM, show);
01064 
01065     show = config->readBoolEntry("showWatchedIgnoredColumn");
01066     slotToggleColumn(KPaintInfo::COL_WATCHED_IGNORED, show);
01067 
01068     show = config->readBoolEntry("showStatusColumn");
01069     slotToggleColumn(KPaintInfo::COL_STATUS, show);
01070 
01071     show = config->readBoolEntry("showSignedColumn");
01072     slotToggleColumn(KPaintInfo::COL_SIGNED, show);
01073 
01074     show = config->readBoolEntry("showCryptoColumn");
01075     slotToggleColumn(KPaintInfo::COL_CRYPTO, show);
01076 
01077     show = config->readBoolEntry("showReceiverColumn");
01078     slotToggleColumn(KPaintInfo::COL_RECEIVER, show);
01079 
01080     mPaintInfo.showCryptoIcons = config->readBoolEntry( "showCryptoIcons", false );
01081     mPaintInfo.showAttachmentIcon = config->readBoolEntry( "showAttachmentIcon", true );
01082 
01083     KMime::DateFormatter::FormatType t =
01084       (KMime::DateFormatter::FormatType) config->readNumEntry("dateFormat", KMime::DateFormatter::Fancy ) ;
01085     mDate.setCustomFormat( config->readEntry("customDateFormat") );
01086     mDate.setFormat( t );
01087   }
01088 
01089   readColorConfig();
01090 
01091   // Custom/System fonts
01092   { // area for config group "General"
01093     KConfigGroupSaver saver(config, "Fonts");
01094     if (!(config->readBoolEntry("defaultFonts",true)))
01095     {
01096       QFont listFont( KGlobalSettings::generalFont() );
01097       setFont(config->readFontEntry("list-font", &listFont));
01098       dateFont = KGlobalSettings::fixedFont();
01099       dateFont = config->readFontEntry("list-date-font", &dateFont);
01100     } else {
01101       dateFont = KGlobalSettings::generalFont();
01102       setFont(dateFont);
01103     }
01104   }
01105 
01106   // Behavior
01107   {
01108     KConfigGroupSaver saver(config, "Geometry");
01109     mReaderWindowActive = config->readEntry( "readerWindowMode", "below" ) != "hide";
01110   }
01111 }
01112 
01113 //-----------------------------------------------------------------------------
01114 void KMHeaders::reset(void)
01115 {
01116   int top = topItemIndex();
01117   int id = currentItemIndex();
01118   noRepaint = true;
01119   clear();
01120   QString colText = i18n( "Sender" );
01121   if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
01122     colText = i18n( "Receiver" );
01123   setColumnText( mPaintInfo.senderCol, colText );
01124   noRepaint = false;
01125   mItems.resize(0);
01126   updateMessageList();
01127   setCurrentMsg(id);
01128   setTopItemByIndex(top);
01129   ensureCurrentItemVisible();
01130 }
01131 
01132 //-----------------------------------------------------------------------------
01133 void KMHeaders::refreshNestedState(void)
01134 {
01135   bool oldState = isThreaded();
01136   NestingPolicy oldNestPolicy = nestingPolicy;
01137   KConfig* config = KMKernel::config();
01138   KConfigGroupSaver saver(config, "Geometry");
01139   mNested = config->readBoolEntry( "nestedMessages", false );
01140 
01141   nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
01142   if ((nestingPolicy != oldNestPolicy) ||
01143     (oldState != isThreaded()))
01144   {
01145     setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
01146     reset();
01147   }
01148 
01149 }
01150 
01151 //-----------------------------------------------------------------------------
01152 void KMHeaders::readFolderConfig (void)
01153 {
01154   if (!mFolder) return;
01155   KConfig* config = KMKernel::config();
01156 
01157   KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
01158   mNestedOverride = config->readBoolEntry( "threadMessagesOverride", false );
01159   mSortCol = config->readNumEntry("SortColumn", (int)KMMsgList::sfDate);
01160   mSortDescending = (mSortCol < 0);
01161   mSortCol = abs(mSortCol) - 1;
01162 
01163   mTopItem = config->readNumEntry("Top", 0);
01164   mCurrentItem = config->readNumEntry("Current", 0);
01165   mCurrentItemSerNum = config->readNumEntry("CurrentSerialNum", 0);
01166 
01167   mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", true );
01168   mPaintInfo.status = config->readBoolEntry( "Status", false );
01169 
01170   { //area for config group "Geometry"
01171     KConfigGroupSaver saver(config, "Geometry");
01172     mNested = config->readBoolEntry( "nestedMessages", false );
01173     nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
01174   }
01175 
01176   setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
01177   mSubjThreading = config->readBoolEntry( "threadMessagesBySubject", true );
01178 }
01179 
01180 
01181 //-----------------------------------------------------------------------------
01182 void KMHeaders::writeFolderConfig (void)
01183 {
01184   if (!mFolder) return;
01185   KConfig* config = KMKernel::config();
01186   int mSortColAdj = mSortCol + 1;
01187 
01188   KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
01189   config->writeEntry("SortColumn", (mSortDescending ? -mSortColAdj : mSortColAdj));
01190   config->writeEntry("Top", topItemIndex());
01191   config->writeEntry("Current", currentItemIndex());
01192   KMHeaderItem* current = currentHeaderItem();
01193   ulong sernum = 0;
01194   if ( current && mFolder->getMsgBase( current->msgId() ) )
01195     sernum = mFolder->getMsgBase( current->msgId() )->getMsgSerNum();
01196   config->writeEntry("CurrentSerialNum", sernum);
01197 
01198   config->writeEntry("OrderOfArrival", mPaintInfo.orderOfArrival);
01199   config->writeEntry("Status", mPaintInfo.status);
01200 }
01201 
01202 //Backported from 3.5
01203 //-----------------------------------------------------------------------------
01204 void KMHeaders::writeConfig (void)
01205 {
01206   KConfig* config = KMKernel::config();
01207   saveLayout(config, "Header-Geometry");
01208   KConfigGroupSaver saver(config, "General");
01209   config->writeEntry("showMessageSize"         , mPaintInfo.showSize);
01210   config->writeEntry("showAttachmentColumn"    , mPaintInfo.showAttachment);
01211   config->writeEntry("showImportantColumn"     , mPaintInfo.showImportant);
01212   config->writeEntry("showTodoColumn"          , mPaintInfo.showTodo);
01213   config->writeEntry("showSpamHamColumn"       , mPaintInfo.showSpamHam);
01214   config->writeEntry("showWatchedIgnoredColumn", mPaintInfo.showWatchedIgnored);
01215   config->writeEntry("showStatusColumn"        , mPaintInfo.showStatus);
01216   config->writeEntry("showSignedColumn"        , mPaintInfo.showSigned);
01217   config->writeEntry("showCryptoColumn"        , mPaintInfo.showCrypto);
01218   config->writeEntry("showReceiverColumn"      , mPaintInfo.showReceiver);
01219 }
01220 
01221 //-----------------------------------------------------------------------------
01222 void KMHeaders::setFolder( KMFolder *aFolder, bool forceJumpToUnread )
01223 {
01224   CREATE_TIMER(set_folder);
01225   START_TIMER(set_folder);
01226 
01227   int id;
01228   QString str;
01229 
01230   mSortInfo.fakeSort = 0;
01231   if ( mFolder && static_cast<KMFolder*>(mFolder) == aFolder ) {
01232     int top = topItemIndex();
01233     id = currentItemIndex();
01234     writeFolderConfig();
01235     readFolderConfig();
01236     updateMessageList(); // do not change the selection
01237     setCurrentMsg(id);
01238     setTopItemByIndex(top);
01239   } else {
01240     if (mFolder) {
01241     // WABA: Make sure that no KMReaderWin is still using a msg
01242     // from this folder, since it's msg's are about to be deleted.
01243       highlightMessage(0, false);
01244 
01245       disconnect(mFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)),
01246           this, SLOT(setFolderInfoStatus()));
01247 
01248       mFolder->markNewAsUnread();
01249       writeFolderConfig();
01250       disconnect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)),
01251                  this, SLOT(msgHeaderChanged(KMFolder*,int)));
01252       disconnect(mFolder, SIGNAL(msgAdded(int)),
01253                  this, SLOT(msgAdded(int)));
01254       disconnect(mFolder, SIGNAL(msgRemoved(int,QString, QString)),
01255                  this, SLOT(msgRemoved(int,QString, QString)));
01256       disconnect(mFolder, SIGNAL(changed()),
01257                  this, SLOT(msgChanged()));
01258       disconnect(mFolder, SIGNAL(cleared()),
01259                  this, SLOT(folderCleared()));
01260       disconnect(mFolder, SIGNAL(expunged( KMFolder* )),
01261                  this, SLOT(folderCleared()));
01262       disconnect( mFolder, SIGNAL( statusMsg( const QString& ) ),
01263                   BroadcastStatus::instance(), SLOT( setStatusMsg( const QString& ) ) );
01264       writeSortOrder();
01265       mFolder->close();
01266       // System folders remain open but we also should write the index from
01267       // time to time
01268       if (mFolder->dirty()) mFolder->writeIndex();
01269     }
01270 
01271     mSortInfo.removed = 0;
01272     mFolder = aFolder;
01273     mSortInfo.dirty = true;
01274     mOwner->editAction()->setEnabled(mFolder ?
01275         (kmkernel->folderIsDraftOrOutbox(mFolder)): false );
01276     mOwner->replyListAction()->setEnabled(mFolder ?
01277         mFolder->isMailingListEnabled() : false);
01278     if (mFolder)
01279     {
01280       connect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)),
01281               this, SLOT(msgHeaderChanged(KMFolder*,int)));
01282       connect(mFolder, SIGNAL(msgAdded(int)),
01283               this, SLOT(msgAdded(int)));
01284       connect(mFolder, SIGNAL(msgRemoved(int,QString, QString)),
01285               this, SLOT(msgRemoved(int,QString, QString)));
01286       connect(mFolder, SIGNAL(changed()),
01287               this, SLOT(msgChanged()));
01288       connect(mFolder, SIGNAL(cleared()),
01289               this, SLOT(folderCleared()));
01290       connect(mFolder, SIGNAL(expunged( KMFolder* )),
01291                  this, SLOT(folderCleared()));
01292       connect(mFolder, SIGNAL(statusMsg(const QString&)),
01293               BroadcastStatus::instance(), SLOT( setStatusMsg( const QString& ) ) );
01294       connect(mFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)),
01295           this, SLOT(setFolderInfoStatus()));
01296 
01297       // Not very nice, but if we go from nested to non-nested
01298       // in the folderConfig below then we need to do this otherwise
01299       // updateMessageList would do something unspeakable
01300       if (isThreaded()) {
01301         noRepaint = true;
01302         clear();
01303         noRepaint = false;
01304         mItems.resize( 0 );
01305       }
01306 
01307       readFolderConfig();
01308 
01309       CREATE_TIMER(kmfolder_open);
01310       START_TIMER(kmfolder_open);
01311       mFolder->open();
01312       END_TIMER(kmfolder_open);
01313       SHOW_TIMER(kmfolder_open);
01314 
01315       if (isThreaded()) {
01316         noRepaint = true;
01317         clear();
01318         noRepaint = false;
01319         mItems.resize( 0 );
01320       }
01321     }
01322 
01323     CREATE_TIMER(updateMsg);
01324     START_TIMER(updateMsg);
01325     updateMessageList(true, forceJumpToUnread);
01326     END_TIMER(updateMsg);
01327     SHOW_TIMER(updateMsg);
01328     makeHeaderVisible();
01329   }
01330   /* Doesn't the below only need to be done when the folder changed? - till */
01331   setFolderInfoStatus();
01332 
01333   QString colText = i18n( "Sender" );
01334   if (mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
01335     colText = i18n("Receiver");
01336   setColumnText( mPaintInfo.senderCol, colText);
01337 
01338   colText = i18n( "Date" );
01339   if (mPaintInfo.orderOfArrival)
01340     colText = i18n( "Order of Arrival" );
01341   setColumnText( mPaintInfo.dateCol, colText);
01342 
01343   colText = i18n( "Subject" );
01344   if (mPaintInfo.status)
01345     colText = colText + i18n( " (Status)" );
01346   setColumnText( mPaintInfo.subCol, colText);
01347 
01348   END_TIMER(set_folder);
01349   SHOW_TIMER(set_folder);
01350 }
01351 
01352 //-----------------------------------------------------------------------------
01353 void KMHeaders::msgChanged()
01354 {
01355  // emit maybeDeleting();
01356   if (mFolder->count() == 0) { // Folder cleared
01357     clear();
01358     return;
01359   }
01360   int i = topItemIndex();
01361   int cur = currentItemIndex();
01362   if (!isUpdatesEnabled()) return;
01363   QString msgIdMD5;
01364   QListViewItem *item = currentItem();
01365   KMHeaderItem *hi = dynamic_cast<KMHeaderItem*>(item);
01366   if (item && hi) {
01367     KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
01368     if (mb)
01369       msgIdMD5 = mb->msgIdMD5();
01370   }
01371   if (!isUpdatesEnabled()) return;
01372   // prevent IMAP messages from scrolling to top
01373   disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
01374              this,SLOT(highlightMessage(QListViewItem*)));
01375   // remember all selected messages
01376   QValueList<int> curItems = selectedItems();
01377   updateMessageList(); // do not change the selection
01378   // restore the old state
01379   setTopItemByIndex( i );
01380   setCurrentMsg( cur );
01381   setSelectedByIndex( curItems, true );
01382   connect(this,SIGNAL(currentChanged(QListViewItem*)),
01383           this,SLOT(highlightMessage(QListViewItem*)));
01384 
01385   // if the current message has changed then emit
01386   // the selected signal to force an update
01387 
01388   // Normally the serial number of the message would be
01389   // used to do this, but because we don't yet have
01390   // guaranteed serial numbers for IMAP messages fall back
01391   // to using the MD5 checksum of the msgId.
01392   item = currentItem();
01393   hi = dynamic_cast<KMHeaderItem*>(item);
01394   if (item && hi) {
01395     KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
01396     if (mb) {
01397       if (msgIdMD5.isEmpty() || (msgIdMD5 != mb->msgIdMD5()))
01398         emit selected(mFolder->getMsg(hi->msgId()));
01399     } else {
01400       emit selected(0);
01401     }
01402   } else
01403     emit selected(0);
01404 }
01405 
01406 
01407 //-----------------------------------------------------------------------------
01408 void KMHeaders::msgAdded(int id)
01409 {
01410   KMHeaderItem* hi = 0;
01411   if (!isUpdatesEnabled()) return;
01412 
01413   CREATE_TIMER(msgAdded);
01414   START_TIMER(msgAdded);
01415 
01416   assert( mFolder->getMsgBase( id ) ); // otherwise using count() is wrong
01417 
01418   /* Create a new KMSortCacheItem to be used for threading. */
01419   KMSortCacheItem *sci = new KMSortCacheItem;
01420   sci->setId(id);
01421   if (isThreaded()) {
01422     // make sure the id and subject dicts grow, if necessary
01423     if (mSortCacheItems.count() == (uint)mFolder->count()
01424         || mSortCacheItems.count() == 0) {
01425       kdDebug (5006) << "KMHeaders::msgAdded - Resizing id and subject trees of " << mFolder->label()
01426        << ": before=" << mSortCacheItems.count() << " ,after=" << (mFolder->count()*2) << endl;
01427       mSortCacheItems.resize(mFolder->count()*2);
01428       mSubjectLists.resize(mFolder->count()*2);
01429     }
01430     QString msgId = mFolder->getMsgBase(id)->msgIdMD5();
01431     if (msgId.isNull())
01432       msgId = "";
01433     QString replyToId = mFolder->getMsgBase(id)->replyToIdMD5();
01434 
01435     KMSortCacheItem *parent = findParent( sci );
01436     if (!parent && mSubjThreading) {
01437       parent = findParentBySubject( sci );
01438       if (parent && sci->isImperfectlyThreaded()) {
01439         // The parent we found could be by subject, in which case it is
01440         // possible, that it would be preferrable to thread it below us,
01441         // not the other way around. Check that. This is not only
01442         // cosmetic, as getting this wrong leads to circular threading.
01443         if (msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToIdMD5()
01444          || msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToAuxIdMD5())
01445           parent = NULL;
01446       }
01447     }
01448 
01449     if (parent && mFolder->getMsgBase(parent->id())->isWatched())
01450       mFolder->getMsgBase(id)->setStatus( KMMsgStatusWatched );
01451     else if (parent && mFolder->getMsgBase(parent->id())->isIgnored()) {
01452       mFolder->getMsgBase(id)->setStatus( KMMsgStatusIgnored );
01453       mFolder->setStatus( id, KMMsgStatusRead );
01454     }
01455     if (parent)
01456       hi = new KMHeaderItem( parent->item(), id );
01457     else
01458       hi = new KMHeaderItem( this, id );
01459 
01460     // o/` ... my buddy and me .. o/`
01461     hi->setSortCacheItem(sci);
01462     sci->setItem(hi);
01463 
01464     // Update and resize the id trees.
01465     mItems.resize( mFolder->count() );
01466     mItems[id] = hi;
01467 
01468     if ( !msgId.isEmpty() )
01469       mSortCacheItems.replace(msgId, sci);
01470     /* Add to the list of potential parents for subject threading. But only if
01471      * we are top level. */
01472     if (mSubjThreading && parent) {
01473       QString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
01474       if (subjMD5.isEmpty()) {
01475         mFolder->getMsgBase(id)->initStrippedSubjectMD5();
01476         subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
01477       }
01478       if( !subjMD5.isEmpty()) {
01479         if ( !mSubjectLists.find(subjMD5) )
01480           mSubjectLists.insert(subjMD5, new QPtrList<KMSortCacheItem>());
01481         // insertion sort by date. See buildThreadTrees for details.
01482         int p=0;
01483         for (QPtrListIterator<KMSortCacheItem> it(*mSubjectLists[subjMD5]);
01484             it.current(); ++it) {
01485           KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
01486           if ( mb->date() < mFolder->getMsgBase(id)->date())
01487             break;
01488           p++;
01489         }
01490         mSubjectLists[subjMD5]->insert( p, sci);
01491       }
01492     }
01493     // The message we just added might be a better parent for one of the as of
01494     // yet imperfectly threaded messages. Let's find out.
01495 
01496     /* In case the current item is taken during reparenting, prevent qlistview
01497      * from selecting some unrelated item as a result of take() emitting
01498      * currentChanged. */
01499     disconnect( this, SIGNAL(currentChanged(QListViewItem*)),
01500            this, SLOT(highlightMessage(QListViewItem*)));
01501 
01502     if ( !msgId.isEmpty() ) {
01503       QPtrListIterator<KMHeaderItem> it(mImperfectlyThreadedList);
01504       KMHeaderItem *cur;
01505       while ( (cur = it.current()) ) {
01506         ++it;
01507         int tryMe = cur->msgId();
01508         // Check, whether our message is the replyToId or replyToAuxId of
01509         // this one. If so, thread it below our message, unless it is already
01510         // correctly threaded by replyToId.
01511         bool perfectParent = true;
01512         KMMsgBase *otherMsg = mFolder->getMsgBase(tryMe);
01513         if ( !otherMsg ) {
01514           kdDebug(5006) << "otherMsg is NULL !!! tryMe: " << tryMe << endl;
01515           continue;
01516         }
01517         QString otherId = otherMsg->replyToIdMD5();
01518         if (msgId != otherId) {
01519           if (msgId != otherMsg->replyToAuxIdMD5())
01520             continue;
01521           else {
01522             if (!otherId.isEmpty() && mSortCacheItems.find(otherId))
01523               continue;
01524             else
01525               // Thread below us by aux id, but keep on the list of
01526               // imperfectly threaded messages.
01527               perfectParent = false;
01528           }
01529         }
01530         QListViewItem *newParent = mItems[id];
01531         QListViewItem *msg = mItems[tryMe];
01532 
01533         if (msg->parent())
01534           msg->parent()->takeItem(msg);
01535         else
01536           takeItem(msg);
01537         newParent->insertItem(msg);
01538 
01539         makeHeaderVisible();
01540 
01541         if (perfectParent) {
01542           mImperfectlyThreadedList.removeRef (mItems[tryMe]);
01543           // The item was imperfectly thread before, now it's parent
01544           // is there. Update the .sorted file accordingly.
01545           QString sortFile = KMAIL_SORT_FILE(mFolder);
01546           FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+");
01547           if (sortStream) {
01548             mItems[tryMe]->sortCacheItem()->updateSortFile( sortStream, mFolder );
01549             fclose (sortStream);
01550           }
01551         }
01552       }
01553     }
01554     // Add ourselves only now, to avoid circularity above.
01555     if (hi && hi->sortCacheItem()->isImperfectlyThreaded())
01556       mImperfectlyThreadedList.append(hi);
01557   } else {
01558     // non-threaded case
01559     hi = new KMHeaderItem( this, id );
01560     mItems.resize( mFolder->count() );
01561     mItems[id] = hi;
01562     // o/` ... my buddy and me .. o/`
01563     hi->setSortCacheItem(sci);
01564     sci->setItem(hi);
01565   }
01566   if (mSortInfo.fakeSort) {
01567     QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
01568     KListView::setSorting(mSortCol, !mSortDescending );
01569     mSortInfo.fakeSort = 0;
01570   }
01571   appendItemToSortFile(hi); //inserted into sorted list
01572 
01573   msgHeaderChanged(mFolder,id);
01574 
01575   if ((childCount() == 1) && hi) {
01576     setSelected( hi, true );
01577     setCurrentItem( firstChild() );
01578     setSelectionAnchor( currentItem() );
01579     highlightMessage( currentItem() );
01580   }
01581 
01582   /* restore signal */
01583   connect( this, SIGNAL(currentChanged(QListViewItem*)),
01584            this, SLOT(highlightMessage(QListViewItem*)));
01585 
01586   emit msgAddedToListView( hi );
01587   END_TIMER(msgAdded);
01588   SHOW_TIMER(msgAdded);
01589 }
01590 
01591 
01592 //-----------------------------------------------------------------------------
01593 void KMHeaders::msgRemoved(int id, QString msgId, QString strippedSubjMD5)
01594 {
01595   if (!isUpdatesEnabled()) return;
01596 
01597   if ((id < 0) || (id >= (int)mItems.size()))
01598     return;
01599   /*
01600    * qlistview has its own ideas about what to select as the next
01601    * item once this one is removed. Sine we have already selected
01602    * something in prepare/finalizeMove that's counter productive
01603    */
01604   disconnect( this, SIGNAL(currentChanged(QListViewItem*)),
01605               this, SLOT(highlightMessage(QListViewItem*)));
01606 
01607   KMHeaderItem *removedItem = mItems[id];
01608   if (!removedItem) return;
01609   KMHeaderItem *curItem = currentHeaderItem();
01610 
01611   for (int i = id; i < (int)mItems.size() - 1; ++i) {
01612     mItems[i] = mItems[i+1];
01613     mItems[i]->setMsgId( i );
01614     mItems[i]->sortCacheItem()->setId( i );
01615   }
01616 
01617   mItems.resize( mItems.size() - 1 );
01618 
01619   if (isThreaded() && mFolder->count()) {
01620     if ( !msgId.isEmpty() && mSortCacheItems[msgId] ) {
01621       if (mSortCacheItems[msgId] == removedItem->sortCacheItem())
01622         mSortCacheItems.remove(msgId);
01623     }
01624     // Remove the message from the list of potential parents for threading by
01625     // subject.
01626     if (!strippedSubjMD5.isEmpty() &&
01627         mSubjThreading && mSubjectLists[strippedSubjMD5])
01628         mSubjectLists[strippedSubjMD5]->remove(removedItem->sortCacheItem());
01629 
01630     // Reparent children of item.
01631     QListViewItem *myParent = removedItem;
01632     QListViewItem *myChild = myParent->firstChild();
01633     QListViewItem *threadRoot = myParent;
01634     while (threadRoot->parent())
01635       threadRoot = threadRoot->parent();
01636     QString key = static_cast<KMHeaderItem*>(threadRoot)->key(mSortCol, !mSortDescending);
01637 
01638     QPtrList<QListViewItem> childList;
01639     while (myChild) {
01640       KMHeaderItem *item = static_cast<KMHeaderItem*>(myChild);
01641       // Just keep the item at top level, if it will be deleted anyhow
01642       if ( !item->aboutToBeDeleted() ) {
01643         childList.append(myChild);
01644       }
01645       myChild = myChild->nextSibling();
01646       if ( item->aboutToBeDeleted() ) {
01647         myParent->takeItem( item );
01648         insertItem( item );
01649       }
01650       item->setTempKey( key + item->key( mSortCol, !mSortDescending ));
01651       if (mSortInfo.fakeSort) {
01652         QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
01653         KListView::setSorting(mSortCol, !mSortDescending );
01654         mSortInfo.fakeSort = 0;
01655       }
01656     }
01657 
01658     for (QPtrListIterator<QListViewItem> it(childList); it.current() ; ++it ) {
01659       QListViewItem *lvi = *it;
01660       KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi);
01661       KMSortCacheItem *sci = item->sortCacheItem();
01662       KMSortCacheItem *parent = findParent( sci );
01663       if ( !parent && mSubjThreading )
01664         parent = findParentBySubject( sci );
01665       myParent->takeItem(lvi);
01666       if ( parent && parent->item() != item && parent->item() != removedItem )
01667           parent->item()->insertItem(lvi);
01668       else
01669         insertItem(lvi);
01670 
01671       if (!parent || (sci->isImperfectlyThreaded()
01672                       && !mImperfectlyThreadedList.containsRef(item)))
01673         mImperfectlyThreadedList.append(item);
01674       if (parent && !sci->isImperfectlyThreaded()
01675           && mImperfectlyThreadedList.containsRef(item))
01676         mImperfectlyThreadedList.removeRef(item);
01677     }
01678   }
01679   // Make sure our data structures are cleared.
01680   if (!mFolder->count())
01681       folderCleared();
01682 
01683   mImperfectlyThreadedList.removeRef(removedItem);
01684   delete removedItem;
01685   // we might have rethreaded it, in which case its current state will be lost
01686   if ( curItem ) {
01687     if ( curItem != removedItem ) {
01688       setCurrentItem( curItem );
01689       setSelectionAnchor( currentItem() );
01690     } else {
01691       // We've removed the current item, which means it was removed from
01692       // something other than a user move or copy, which would have selected
01693       // the next logical mail. This can happen when the mail is deleted by
01694       // a filter, or some other behind the scenes action. Select something
01695       // sensible, then, and make sure the reader window is cleared.
01696       emit maybeDeleting();
01697       int contentX, contentY;
01698       KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01699       finalizeMove( nextItem, contentX, contentY );
01700     }
01701   }
01702   /* restore signal */
01703   connect( this, SIGNAL(currentChanged(QListViewItem*)),
01704            this, SLOT(highlightMessage(QListViewItem*)));
01705 }
01706 
01707 
01708 //-----------------------------------------------------------------------------
01709 void KMHeaders::msgHeaderChanged(KMFolder*, int msgId)
01710 {
01711   if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return;
01712   KMHeaderItem *item = mItems[msgId];
01713   if (item) {
01714     item->irefresh();
01715     item->repaint();
01716   }
01717 }
01718 
01719 
01720 //-----------------------------------------------------------------------------
01721 void KMHeaders::setMsgStatus (KMMsgStatus status, bool toggle)
01722 {
01723   SerNumList serNums;
01724   for (QListViewItemIterator it(this); it.current(); ++it)
01725     if ( it.current()->isSelected() && it.current()->isVisible() ) {
01726       KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
01727       KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
01728       serNums.append( msgBase->getMsgSerNum() );
01729     }
01730   if (serNums.empty())
01731     return;
01732 
01733   KMCommand *command = new KMSetStatusCommand( status, serNums, toggle );
01734   command->start();
01735 }
01736 
01737 
01738 QPtrList<QListViewItem> KMHeaders::currentThread() const
01739 {
01740   if (!mFolder) return QPtrList<QListViewItem>();
01741 
01742   // starting with the current item...
01743   QListViewItem *curItem = currentItem();
01744   if (!curItem) return QPtrList<QListViewItem>();
01745 
01746   // ...find the top-level item:
01747   QListViewItem *topOfThread = curItem;
01748   while ( topOfThread->parent() )
01749     topOfThread = topOfThread->parent();
01750 
01751   // collect the items in this thread:
01752   QPtrList<QListViewItem> list;
01753   QListViewItem *topOfNextThread = topOfThread->nextSibling();
01754   for ( QListViewItemIterator it( topOfThread ) ;
01755         it.current() && it.current() != topOfNextThread ; ++it )
01756     list.append( it.current() );
01757   return list;
01758 }
01759 
01760 void KMHeaders::setThreadStatus(KMMsgStatus status, bool toggle)
01761 {
01762   QPtrList<QListViewItem> curThread = currentThread();
01763   QPtrListIterator<QListViewItem> it( curThread );
01764   SerNumList serNums;
01765 
01766   for ( it.toFirst() ; it.current() ; ++it ) {
01767     int id = static_cast<KMHeaderItem*>(*it)->msgId();
01768     KMMsgBase *msgBase = mFolder->getMsgBase( id );
01769     serNums.append( msgBase->getMsgSerNum() );
01770   }
01771 
01772   if (serNums.empty())
01773     return;
01774 
01775   KMCommand *command = new KMSetStatusCommand( status, serNums, toggle );
01776   command->start();
01777 }
01778 
01779 //-----------------------------------------------------------------------------
01780 int KMHeaders::slotFilterMsg(KMMessage *msg)
01781 {
01782   if ( !msg ) return 2; // messageRetrieve(0) is always possible
01783   msg->setTransferInProgress(false);
01784   int filterResult = kmkernel->filterMgr()->process(msg,KMFilterMgr::Explicit);
01785   if (filterResult == 2) {
01786     // something went horribly wrong (out of space?)
01787     kmkernel->emergencyExit( i18n("Unable to process messages: " ) + QString::fromLocal8Bit(strerror(errno)));
01788     return 2;
01789   }
01790   if (msg->parent()) { // unGet this msg
01791     int idx = -1;
01792     KMFolder * p = 0;
01793     kmkernel->msgDict()->getLocation( msg, &p, &idx );
01794     assert( p == msg->parent() ); assert( idx >= 0 );
01795     p->unGetMsg( idx );
01796   }
01797 
01798   return filterResult;
01799 }
01800 
01801 
01802 void KMHeaders::slotExpandOrCollapseThread( bool expand )
01803 {
01804   if ( !isThreaded() ) return;
01805   // find top-level parent of currentItem().
01806   QListViewItem *item = currentItem();
01807   if ( !item ) return;
01808   clearSelection();
01809   item->setSelected( true );
01810   while ( item->parent() )
01811     item = item->parent();
01812   KMHeaderItem * hdrItem = static_cast<KMHeaderItem*>(item);
01813   hdrItem->setOpenRecursive( expand );
01814   if ( !expand ) // collapse can hide the current item:
01815     setCurrentMsg( hdrItem->msgId() );
01816   ensureItemVisible( currentItem() );
01817 }
01818 
01819 void KMHeaders::slotExpandOrCollapseAllThreads( bool expand )
01820 {
01821   if ( !isThreaded() ) return;
01822 
01823   QListViewItem * item = currentItem();
01824   if( item ) {
01825     clearSelection();
01826     item->setSelected( true );
01827   }
01828 
01829   for ( QListViewItem *item = firstChild() ;
01830         item ; item = item->nextSibling() )
01831     static_cast<KMHeaderItem*>(item)->setOpenRecursive( expand );
01832   if ( !expand ) { // collapse can hide the current item:
01833     QListViewItem * item = currentItem();
01834     if( item ) {
01835       while ( item->parent() )
01836         item = item->parent();
01837       setCurrentMsg( static_cast<KMHeaderItem*>(item)->msgId() );
01838     }
01839   }
01840   ensureItemVisible( currentItem() );
01841 }
01842 
01843 //-----------------------------------------------------------------------------
01844 void KMHeaders::setStyleDependantFrameWidth()
01845 {
01846   // set the width of the frame to a reasonable value for the current GUI style
01847   int frameWidth;
01848   if( style().isA("KeramikStyle") )
01849     frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth ) - 1;
01850   else
01851     frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth );
01852   if ( frameWidth < 0 )
01853     frameWidth = 0;
01854   if ( frameWidth != lineWidth() )
01855     setLineWidth( frameWidth );
01856 }
01857 
01858 //-----------------------------------------------------------------------------
01859 void KMHeaders::styleChange( QStyle& oldStyle )
01860 {
01861   setStyleDependantFrameWidth();
01862   KListView::styleChange( oldStyle );
01863 }
01864 
01865 //-----------------------------------------------------------------------------
01866 void KMHeaders::setFolderInfoStatus ()
01867 {
01868   if ( !mFolder ) return;
01869   QString str = ( static_cast<KMFolder*>(mFolder) == kmkernel->outboxFolder() )
01870                 ? i18n( "1 unsent", "%n unsent", mFolder->countUnread() )
01871                 : i18n( "1 unread", "%n unread", mFolder->countUnread() );
01872   str = i18n( "1 message, %1.", "%n messages, %1.", mFolder->count() )
01873         .arg( str );
01874   if ( mFolder->isReadOnly() )
01875     str = i18n("%1 = n messages, m unread.", "%1 Folder is read-only.").arg( str );
01876   BroadcastStatus::instance()->setStatusMsg(str);
01877 }
01878 
01879 //-----------------------------------------------------------------------------
01880 void KMHeaders::applyFiltersOnMsg()
01881 {
01882 #if 0 // uses action scheduler
01883   KMFilterMgr::FilterSet set = KMFilterMgr::All;
01884   QPtrList<KMFilter> filters;
01885   filters = *( kmkernel->filterMgr() );
01886   ActionScheduler *scheduler = new ActionScheduler( set, filters, this );
01887   scheduler->setAutoDestruct( true );
01888 
01889   int contentX, contentY;
01890   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01891   QPtrList<KMMsgBase> msgList = *selectedMsgs(true);
01892   finalizeMove( nextItem, contentX, contentY );
01893 
01894   for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
01895     scheduler->execFilters( msg );
01896 #else
01897   int contentX, contentY;
01898   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01899 
01900   KMMessageList* msgList = selectedMsgs();
01901   if (msgList->isEmpty())
01902     return;
01903   finalizeMove( nextItem, contentX, contentY );
01904 
01905   CREATE_TIMER(filter);
01906   START_TIMER(filter);
01907 
01908   for (KMMsgBase* msgBase=msgList->first(); msgBase; msgBase=msgList->next()) {
01909     int idx = msgBase->parent()->find(msgBase);
01910     assert(idx != -1);
01911     KMMessage * msg = msgBase->parent()->getMsg(idx);
01912     if (msg->transferInProgress()) continue;
01913     msg->setTransferInProgress(true);
01914     if ( !msg->isComplete() )
01915     {
01916       FolderJob *job = mFolder->createJob(msg);
01917       connect(job, SIGNAL(messageRetrieved(KMMessage*)),
01918               SLOT(slotFilterMsg(KMMessage*)));
01919       job->start();
01920     } else {
01921       if (slotFilterMsg(msg) == 2) break;
01922     }
01923   }
01924   END_TIMER(filter);
01925   SHOW_TIMER(filter);
01926 #endif
01927 }
01928 
01929 
01930 //-----------------------------------------------------------------------------
01931 void KMHeaders::setMsgRead (int msgId)
01932 {
01933   KMMsgBase *msgBase = mFolder->getMsgBase( msgId );
01934   if (!msgBase)
01935     return;
01936 
01937   SerNumList serNums;
01938   if (msgBase->isNew() || msgBase->isUnread()) {
01939     serNums.append( msgBase->getMsgSerNum() );
01940   }
01941 
01942   KMCommand *command = new KMSetStatusCommand( KMMsgStatusRead, serNums );
01943   command->start();
01944 }
01945 
01946 
01947 //-----------------------------------------------------------------------------
01948 void KMHeaders::deleteMsg ()
01949 {
01950   //make sure we have an associated folder (root of folder tree does not).
01951   if (!mFolder)
01952     return;
01953 
01954   int contentX, contentY;
01955   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01956   KMMessageList msgList = *selectedMsgs(true);
01957   finalizeMove( nextItem, contentX, contentY );
01958 
01959   KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList );
01960   connect( command, SIGNAL( completed( KMCommand * ) ),
01961            this, SLOT( slotMoveCompleted( KMCommand * ) ) );
01962   command->start();
01963 
01964   BroadcastStatus::instance()->setStatusMsg("");
01965   //  triggerUpdate();
01966 }
01967 
01968 
01969 //-----------------------------------------------------------------------------
01970 void KMHeaders::moveSelectedToFolder( int menuId )
01971 {
01972   if (mMenuToFolder[menuId])
01973     moveMsgToFolder( mMenuToFolder[menuId] );
01974 }
01975 
01976 //-----------------------------------------------------------------------------
01977 KMHeaderItem* KMHeaders::prepareMove( int *contentX, int *contentY )
01978 {
01979   KMHeaderItem *ret = 0;
01980   emit maybeDeleting();
01981 
01982   disconnect( this, SIGNAL(currentChanged(QListViewItem*)),
01983               this, SLOT(highlightMessage(QListViewItem*)));
01984 
01985   QListViewItem *curItem;
01986   KMHeaderItem *item;
01987   curItem = currentItem();
01988   while (curItem && curItem->isSelected() && curItem->itemBelow())
01989     curItem = curItem->itemBelow();
01990   while (curItem && curItem->isSelected() && curItem->itemAbove())
01991     curItem = curItem->itemAbove();
01992   item = static_cast<KMHeaderItem*>(curItem);
01993 
01994   *contentX = contentsX();
01995   *contentY = contentsY();
01996 
01997   if (item  && !item->isSelected())
01998     ret = item;
01999 
02000   return ret;
02001 }
02002 
02003 //-----------------------------------------------------------------------------
02004 void KMHeaders::finalizeMove( KMHeaderItem *item, int contentX, int contentY )
02005 {
02006   emit selected( 0 );
02007 
02008   if ( item ) {
02009     clearSelection();
02010     setCurrentItem( item );
02011     setSelected( item, true );
02012     setSelectionAnchor( currentItem() );
02013     mPrevCurrent = 0;
02014     highlightMessage( item, false);
02015   }
02016 
02017   setContentsPos( contentX, contentY );
02018   makeHeaderVisible();
02019   connect( this, SIGNAL(currentChanged(QListViewItem*)),
02020            this, SLOT(highlightMessage(QListViewItem*)));
02021 }
02022 
02023 
02024 //-----------------------------------------------------------------------------
02025 void KMHeaders::moveMsgToFolder ( KMFolder* destFolder, bool askForConfirmation )
02026 {
02027   if ( destFolder == mFolder ) return; // Catch the noop case
02028 
02029   KMMessageList msgList = *selectedMsgs();
02030   if ( msgList.isEmpty() ) return;
02031   if ( !destFolder && askForConfirmation &&    // messages shall be deleted
02032        KMessageBox::warningContinueCancel(this,
02033          i18n("<qt>Do you really want to delete the selected message?<br>"
02034               "Once deleted, it cannot be restored.</qt>",
02035               "<qt>Do you really want to delete the %n selected messages?<br>"
02036               "Once deleted, they cannot be restored.</qt>", msgList.count() ),
02037          i18n("Delete Messages"), KGuiItem(i18n("De&lete"),"editdelete"), "NoConfirmDelete") == KMessageBox::Cancel )
02038     return;  // user canceled the action
02039 
02040   // remember the message to select afterwards
02041   int contentX, contentY;
02042   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
02043   msgList = *selectedMsgs(true);
02044   finalizeMove( nextItem, contentX, contentY );
02045 
02046   KMCommand *command = new KMMoveCommand( destFolder, msgList );
02047   connect( command, SIGNAL( completed( KMCommand * ) ),
02048            this, SLOT( slotMoveCompleted( KMCommand * ) ) );
02049   command->start();
02050 
02051 }
02052 
02053 void KMHeaders::slotMoveCompleted( KMCommand *command )
02054 {
02055   kdDebug(5006) << k_funcinfo << command->result() << endl;
02056   bool deleted = static_cast<KMMoveCommand *>( command )->destFolder() == 0;
02057   if ( command->result() == KMCommand::OK ) {
02058     // make sure the current item is shown
02059     makeHeaderVisible();
02060 #if 0 // enable after the message-freeze
02061     BroadcastStatus::instance()->setStatusMsg(
02062        deleted ? i18nTODO("Messages deleted successfully.") : i18nTODO("Messages moved successfully") );
02063 #else
02064     if ( !deleted ) BroadcastStatus::instance()->setStatusMsg( i18n( "Messages moved successfully" ) );
02065 #endif
02066   } else {
02067     /* The move failed or the user canceled it; reset the state of all
02068      * messages involved and repaint.
02069      *
02070      * Note: This potentially resets too many items if there is more than one
02071      *       move going on. Oh well, I suppose no animals will be harmed.
02072      * */
02073     for (QListViewItemIterator it(this); it.current(); it++) {
02074       KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
02075       if ( item->aboutToBeDeleted() ) {
02076         item->setAboutToBeDeleted ( false );
02077         item->setSelectable ( true );
02078         KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
02079         if ( msgBase->isMessage() ) {
02080           KMMessage *msg = static_cast<KMMessage *>(msgBase);
02081           if ( msg ) msg->setTransferInProgress( false, true );
02082         }
02083       }
02084     }
02085     triggerUpdate();
02086 #if 0 // enable after the message-freeze
02087     if ( command->result() == KMCommand::Failed )
02088       BroadcastStatus::instance()->setStatusMsg(
02089            deleted ? i18nTODO("Deleting messages failed.") : i18nTODO("Moving messages failed.") );
02090     else
02091       BroadcastStatus::instance()->setStatusMsg(
02092            deleted ? i18nTODO("Deleting messages canceled.") : i18nTODO("Moving messages canceled.") );
02093 #else
02094     if ( !deleted ) {
02095       if ( command->result() == KMCommand::Failed )
02096         BroadcastStatus::instance()->setStatusMsg( i18n("Moving messages failed.") );
02097       else
02098         BroadcastStatus::instance()->setStatusMsg( i18n("Moving messages canceled.") );
02099     }
02100 #endif
02101   }
02102 }
02103 
02104 bool KMHeaders::canUndo() const
02105 {
02106     return ( kmkernel->undoStack()->size() > 0 );
02107 }
02108 
02109 //-----------------------------------------------------------------------------
02110 void KMHeaders::undo()
02111 {
02112   kmkernel->undoStack()->undo();
02113 }
02114 
02115 //-----------------------------------------------------------------------------
02116 void KMHeaders::copySelectedToFolder(int menuId )
02117 {
02118   if (mMenuToFolder[menuId])
02119     copyMsgToFolder( mMenuToFolder[menuId] );
02120 }
02121 
02122 
02123 //-----------------------------------------------------------------------------
02124 void KMHeaders::copyMsgToFolder(KMFolder* destFolder, KMMessage* aMsg)
02125 {
02126   if ( !destFolder )
02127     return;
02128 
02129   KMCommand * command = 0;
02130   if (aMsg)
02131     command = new KMCopyCommand( destFolder, aMsg );
02132   else {
02133     KMMessageList msgList = *selectedMsgs();
02134     command = new KMCopyCommand( destFolder, msgList );
02135   }
02136 
02137   command->start();
02138 }
02139 
02140 
02141 //-----------------------------------------------------------------------------
02142 void KMHeaders::setCurrentMsg(int cur)
02143 {
02144   if (!mFolder) return;
02145   if (cur >= mFolder->count()) cur = mFolder->count() - 1;
02146   if ((cur >= 0) && (cur < (int)mItems.size())) {
02147     clearSelection();
02148     setCurrentItem( mItems[cur] );
02149     setSelected( mItems[cur], true );
02150     setSelectionAnchor( currentItem() );
02151   }
02152   makeHeaderVisible();
02153   setFolderInfoStatus();
02154 }
02155 
02156 //-----------------------------------------------------------------------------
02157 void KMHeaders::setSelected( QListViewItem *item, bool selected )
02158 {
02159   if ( !item )
02160     return;
02161 
02162   if ( item->isVisible() )
02163     KListView::setSelected( item, selected );
02164 
02165   // If the item is the parent of a closed thread recursively select
02166   // children .
02167   if ( isThreaded() && !item->isOpen() && item->firstChild() ) {
02168       QListViewItem *nextRoot = item->itemBelow();
02169       QListViewItemIterator it( item->firstChild() );
02170       for( ; (*it) != nextRoot; ++it ) {
02171         if ( (*it)->isVisible() )
02172            (*it)->setSelected( selected );
02173       }
02174   }
02175 }
02176 
02177 void KMHeaders::setSelectedByIndex( QValueList<int> items, bool selected )
02178 {
02179   for ( QValueList<int>::Iterator it = items.begin(); it != items.end(); ++it )
02180   {
02181     if ( ((*it) >= 0) && ((*it) < (int)mItems.size()) )
02182     {
02183       setSelected( mItems[(*it)], selected );
02184     }
02185   }
02186 }
02187 
02188 void KMHeaders::clearSelectableAndAboutToBeDeleted( Q_UINT32 serNum )
02189 {
02190   // fugly, but I see no way around it
02191   for (QListViewItemIterator it(this); it.current(); it++) {
02192     KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
02193     if ( item->aboutToBeDeleted() ) {
02194       KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
02195       if ( serNum == msgBase->getMsgSerNum() ) {
02196         item->setAboutToBeDeleted ( false );
02197         item->setSelectable ( true );
02198       }
02199     }
02200   }
02201   triggerUpdate();
02202 }
02203 
02204 //-----------------------------------------------------------------------------
02205 KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted)
02206 {
02207   mSelMsgBaseList.clear();
02208   for (QListViewItemIterator it(this); it.current(); it++) {
02209     if ( it.current()->isSelected() && it.current()->isVisible() ) {
02210       KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
02211       if ( !item->aboutToBeDeleted() ) { // we are already working on this one
02212         if (toBeDeleted) {
02213           // make sure the item is not uselessly rethreaded and not selectable
02214           item->setAboutToBeDeleted ( true );
02215           item->setSelectable ( false );
02216         }
02217         KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
02218         mSelMsgBaseList.append(msgBase);
02219       }
02220     }
02221   }
02222   return &mSelMsgBaseList;
02223 }
02224 
02225 //-----------------------------------------------------------------------------
02226 QValueList<int> KMHeaders::selectedItems()
02227 {
02228   QValueList<int> items;
02229   for ( QListViewItemIterator it(this); it.current(); it++ )
02230   {
02231     if ( it.current()->isSelected() && it.current()->isVisible() )
02232     {
02233       KMHeaderItem* item = static_cast<KMHeaderItem*>( it.current() );
02234       items.append( item->msgId() );
02235     }
02236   }
02237   return items;
02238 }
02239 
02240 //-----------------------------------------------------------------------------
02241 int KMHeaders::firstSelectedMsg() const
02242 {
02243   int selectedMsg = -1;
02244   QListViewItem *item;
02245   for (item = firstChild(); item; item = item->itemBelow())
02246     if (item->isSelected()) {
02247       selectedMsg = (static_cast<KMHeaderItem*>(item))->msgId();
02248       break;
02249     }
02250   return selectedMsg;
02251 }
02252 
02253 //-----------------------------------------------------------------------------
02254 void KMHeaders::nextMessage()
02255 {
02256   QListViewItem *lvi = currentItem();
02257   if (lvi && lvi->itemBelow()) {
02258     clearSelection();
02259     setSelected( lvi, false );
02260     selectNextMessage();
02261     setSelectionAnchor( currentItem() );
02262     ensureCurrentItemVisible();
02263   }
02264 }
02265 
02266 void KMHeaders::selectNextMessage()
02267 {
02268   QListViewItem *lvi = currentItem();
02269   if( lvi ) {
02270     QListViewItem *below = lvi->itemBelow();
02271     QListViewItem *temp = lvi;
02272     if (lvi && below ) {
02273       while (temp) {
02274         temp->firstChild();
02275         temp = temp->parent();
02276       }
02277       lvi->repaint();
02278       /* test to see if we need to unselect messages on back track */
02279       (below->isSelected() ? setSelected(lvi, false) : setSelected(below, true));
02280       setCurrentItem(below);
02281       makeHeaderVisible();
02282       setFolderInfoStatus();
02283     }
02284   }
02285 }
02286 
02287 //-----------------------------------------------------------------------------
02288 void KMHeaders::prevMessage()
02289 {
02290   QListViewItem *lvi = currentItem();
02291   if (lvi && lvi->itemAbove()) {
02292     clearSelection();
02293     setSelected( lvi, false );
02294     selectPrevMessage();
02295     setSelectionAnchor( currentItem() );
02296     ensureCurrentItemVisible();
02297   }
02298 }
02299 
02300 void KMHeaders::selectPrevMessage()
02301 {
02302   QListViewItem *lvi = currentItem();
02303   if( lvi ) {
02304     QListViewItem *above = lvi->itemAbove();
02305     QListViewItem *temp = lvi;
02306 
02307     if (lvi && above) {
02308       while (temp) {
02309         temp->firstChild();
02310         temp = temp->parent();
02311       }
02312       lvi->repaint();
02313       /* test to see if we need to unselect messages on back track */
02314       (above->isSelected() ? setSelected(lvi, false) : setSelected(above, true));
02315       setCurrentItem(above);
02316       makeHeaderVisible();
02317       setFolderInfoStatus();
02318     }
02319   }
02320 }
02321 
02322 //-----------------------------------------------------------------------------
02323 void KMHeaders::findUnreadAux( KMHeaderItem*& item,
02324                                         bool & foundUnreadMessage,
02325                                         bool onlyNew,
02326                                         bool aDirNext )
02327 {
02328   KMMsgBase* msgBase = 0;
02329   KMHeaderItem *lastUnread = 0;
02330   /* itemAbove() is _slow_ */
02331   if (aDirNext)
02332   {
02333     while (item) {
02334       msgBase = mFolder->getMsgBase(item->msgId());
02335       if (!msgBase) continue;
02336       if (msgBase->isUnread() || msgBase->isNew())
02337         foundUnreadMessage = true;
02338 
02339       if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())) break;
02340       if (onlyNew && msgBase->isNew()) break;
02341       item = static_cast<KMHeaderItem*>(item->itemBelow());
02342     }
02343   } else {
02344     KMHeaderItem *newItem = static_cast<KMHeaderItem*>(firstChild());
02345     while (newItem)
02346     {
02347       msgBase = mFolder->getMsgBase(newItem->msgId());
02348       if (!msgBase) continue;
02349       if (msgBase->isUnread() || msgBase->isNew())
02350         foundUnreadMessage = true;
02351       if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())
02352           || onlyNew && msgBase->isNew())
02353         lastUnread = newItem;
02354       if (newItem == item) break;
02355       newItem = static_cast<KMHeaderItem*>(newItem->itemBelow());
02356     }
02357     item = lastUnread;
02358   }
02359 }
02360 
02361 //-----------------------------------------------------------------------------
02362 int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent)
02363 {
02364   KMHeaderItem *item, *pitem;
02365   bool foundUnreadMessage = false;
02366 
02367   if (!mFolder) return -1;
02368   if (!(mFolder->count()) > 0) return -1;
02369 
02370   if ((aStartAt >= 0) && (aStartAt < (int)mItems.size()))
02371     item = mItems[aStartAt];
02372   else {
02373     item = currentHeaderItem();
02374     if (!item) {
02375       if (aDirNext)
02376         item = static_cast<KMHeaderItem*>(firstChild());
02377       else
02378         item = static_cast<KMHeaderItem*>(lastChild());
02379     }
02380     if (!item)
02381       return -1;
02382 
02383     if ( !acceptCurrent )
02384         if (aDirNext)
02385             item = static_cast<KMHeaderItem*>(item->itemBelow());
02386         else
02387             item = static_cast<KMHeaderItem*>(item->itemAbove());
02388   }
02389 
02390   pitem =  item;
02391 
02392   findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
02393 
02394   // We have found an unread item, but it is not necessary the
02395   // first unread item.
02396   //
02397   // Find the ancestor of the unread item closest to the
02398   // root and recursively sort all of that ancestors children.
02399   if (item) {
02400     QListViewItem *next = item;
02401     while (next->parent())
02402       next = next->parent();
02403     next = static_cast<KMHeaderItem*>(next)->firstChildNonConst();
02404     while (next && (next != item))
02405       if (static_cast<KMHeaderItem*>(next)->firstChildNonConst())
02406         next = next->firstChild();
02407       else if (next->nextSibling())
02408         next = next->nextSibling();
02409       else {
02410         while (next && (next != item)) {
02411           next = next->parent();
02412           if (next == item)
02413             break;
02414           if (next && next->nextSibling()) {
02415             next = next->nextSibling();
02416             break;
02417           }
02418         }
02419       }
02420   }
02421 
02422   item = pitem;
02423 
02424   findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
02425   if (item)
02426     return item->msgId();
02427 
02428 
02429   // A kludge to try to keep the number of unread messages in sync
02430   int unread = mFolder->countUnread();
02431   if (((unread == 0) && foundUnreadMessage) ||
02432       ((unread > 0) && !foundUnreadMessage)) {
02433     mFolder->correctUnreadMsgsCount();
02434   }
02435   return -1;
02436 }
02437 
02438 //-----------------------------------------------------------------------------
02439 bool KMHeaders::nextUnreadMessage(bool acceptCurrent)
02440 {
02441   if ( !mFolder || !mFolder->countUnread() ) return false;
02442   int i = findUnread(true, -1, false, acceptCurrent);
02443   if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
02444         GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
02445   {
02446     KMHeaderItem * first = static_cast<KMHeaderItem*>(firstChild());
02447     if ( first )
02448       i = findUnread(true, first->msgId(), false, acceptCurrent); // from top
02449   }
02450   if ( i < 0 )
02451     return false;
02452   setCurrentMsg(i);
02453   ensureCurrentItemVisible();
02454   return true;
02455 }
02456 
02457 void KMHeaders::ensureCurrentItemVisible()
02458 {
02459     int i = currentItemIndex();
02460     if ((i >= 0) && (i < (int)mItems.size()))
02461         center( contentsX(), itemPos(mItems[i]), 0, 9.0 );
02462 }
02463 
02464 //-----------------------------------------------------------------------------
02465 bool KMHeaders::prevUnreadMessage()
02466 {
02467   if ( !mFolder || !mFolder->countUnread() ) return false;
02468   int i = findUnread(false);
02469   if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
02470         GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
02471   {
02472     KMHeaderItem * last = static_cast<KMHeaderItem*>(lastItem());
02473     if ( last )
02474       i = findUnread(false, last->msgId() ); // from bottom
02475   }
02476   if ( i < 0 )
02477     return false;
02478   setCurrentMsg(i);
02479   ensureCurrentItemVisible();
02480   return true;
02481 }
02482 
02483 
02484 //-----------------------------------------------------------------------------
02485 void KMHeaders::slotNoDrag()
02486 {
02487   mMousePressed = false;
02488 }
02489 
02490 
02491 //-----------------------------------------------------------------------------
02492 void KMHeaders::makeHeaderVisible()
02493 {
02494   if (currentItem())
02495     ensureItemVisible( currentItem() );
02496 }
02497 
02498 //-----------------------------------------------------------------------------
02499 void KMHeaders::highlightMessage(QListViewItem* lvi, bool markitread)
02500 {
02501   // shouldnt happen but will crash if it does
02502   if (lvi && !lvi->isSelectable()) return;
02503 
02504   KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi);
02505   if (lvi != mPrevCurrent) {
02506     if (mPrevCurrent && mFolder)
02507     {
02508       KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId());
02509       if (prevMsg && mReaderWindowActive)
02510       {
02511         mFolder->ignoreJobsForMessage(prevMsg);
02512         if (!prevMsg->transferInProgress())
02513           mFolder->unGetMsg(mPrevCurrent->msgId());
02514       }
02515     }
02516     mPrevCurrent = item;
02517   }
02518 
02519   if (!item)
02520   {
02521     emit selected( 0 ); return;
02522   }
02523 
02524   int idx = item->msgId();
02525   if (mReaderWindowActive)
02526   {
02527     KMMessage *msg = mFolder->getMsg(idx);
02528     if (!msg || msg->transferInProgress())
02529     {
02530       emit selected( 0 );
02531       mPrevCurrent = 0;
02532       return;
02533     }
02534   }
02535 
02536   BroadcastStatus::instance()->setStatusMsg("");
02537   if (markitread && idx >= 0) setMsgRead(idx);
02538   mItems[idx]->irefresh();
02539   mItems[idx]->repaint();
02540   emit selected(mFolder->getMsg(idx));
02541   setFolderInfoStatus();
02542 }
02543 
02544 void KMHeaders::resetCurrentTime()
02545 {
02546     mDate.reset();
02547     QTimer::singleShot( 1000, this, SLOT( resetCurrentTime() ) );
02548 }
02549 
02550 //-----------------------------------------------------------------------------
02551 void KMHeaders::selectMessage(QListViewItem* lvi)
02552 {
02553   KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi);
02554   if (!item)
02555     return;
02556 
02557   int idx = item->msgId();
02558   KMMessage *msg = mFolder->getMsg(idx);
02559   if (!msg->transferInProgress())
02560   {
02561     emit activated(mFolder->getMsg(idx));
02562   }
02563 
02564 //  if (kmkernel->folderIsDraftOrOutbox(mFolder))
02565 //    setOpen(lvi, !lvi->isOpen());
02566 }
02567 
02568 
02569 //-----------------------------------------------------------------------------
02570 void KMHeaders::updateMessageList( bool set_selection, bool forceJumpToUnread )
02571 {
02572   mPrevCurrent = 0;
02573   noRepaint = true;
02574   clear();
02575   noRepaint = false;
02576   KListView::setSorting( mSortCol, !mSortDescending );
02577   if (!mFolder) {
02578     mItems.resize(0);
02579     repaint();
02580     return;
02581   }
02582   readSortOrder( set_selection, forceJumpToUnread );
02583   emit messageListUpdated();
02584 }
02585 
02586 
02587 //-----------------------------------------------------------------------------
02588 // KMail Header list selection/navigation description
02589 //
02590 // If the selection state changes the reader window is updated to show the
02591 // current item.
02592 //
02593 // (The selection state of a message or messages can be changed by pressing
02594 //  space, or normal/shift/cntrl clicking).
02595 //
02596 // The following keyboard events are supported when the messages headers list
02597 // has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End,
02598 // Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do
02599 // not change the selection state.
02600 //
02601 // Exception: When shift selecting either with mouse or key press the reader
02602 // window is updated regardless of whether of not the selection has changed.
02603 void KMHeaders::keyPressEvent( QKeyEvent * e )
02604 {
02605     bool cntrl = (e->state() & ControlButton );
02606     bool shft = (e->state() & ShiftButton );
02607     QListViewItem *cur = currentItem();
02608 
02609     if (!e || !firstChild())
02610       return;
02611 
02612     // If no current item, make some first item current when a key is pressed
02613     if (!cur) {
02614       setCurrentItem( firstChild() );
02615       setSelectionAnchor( currentItem() );
02616       return;
02617     }
02618 
02619     // Handle space key press
02620     if (cur->isSelectable() && e->ascii() == ' ' ) {
02621         setSelected( cur, !cur->isSelected() );
02622         highlightMessage( cur, false);
02623         return;
02624     }
02625 
02626     if (cntrl) {
02627       if (!shft)
02628         disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
02629                    this,SLOT(highlightMessage(QListViewItem*)));
02630       switch (e->key()) {
02631       case Key_Down:
02632       case Key_Up:
02633       case Key_Home:
02634       case Key_End:
02635       case Key_Next:
02636       case Key_Prior:
02637       case Key_Escape:
02638         KListView::keyPressEvent( e );
02639       }
02640       if (!shft)
02641         connect(this,SIGNAL(currentChanged(QListViewItem*)),
02642                 this,SLOT(highlightMessage(QListViewItem*)));
02643     }
02644 }
02645 
02646 //-----------------------------------------------------------------------------
02647 // Handle RMB press, show pop up menu
02648 void KMHeaders::rightButtonPressed( QListViewItem *lvi, const QPoint &, int )
02649 {
02650   if (!lvi)
02651     return;
02652 
02653   if (!(lvi->isSelected())) {
02654     clearSelection();
02655   }
02656   setSelected( lvi, true );
02657   slotRMB();
02658 }
02659 
02660 //-----------------------------------------------------------------------------
02661 void KMHeaders::contentsMousePressEvent(QMouseEvent* e)
02662 {
02663   mPressPos = e->pos();
02664   QListViewItem *lvi = itemAt( contentsToViewport( e->pos() ));
02665   bool wasSelected = false;
02666   bool rootDecoClicked = false;
02667   if (lvi) {
02668      wasSelected = lvi->isSelected();
02669      rootDecoClicked =
02670         (  mPressPos.x() <= header()->cellPos(  header()->mapToActual(  0 ) ) +
02671            treeStepSize() * (  lvi->depth() + (  rootIsDecorated() ? 1 : 0 ) ) + itemMargin() )
02672         && (  mPressPos.x() >= header()->cellPos(  header()->mapToActual(  0 ) ) );
02673 
02674      if ( rootDecoClicked ) {
02675         // Check if our item is the parent of a closed thread and if so, if the root
02676         // decoration of the item was clicked (i.e. the +/- sign) which would expand
02677         // the thread. In that case, deselect all children, so opening the thread
02678         // doesn't cause a flicker.
02679         if ( !lvi->isOpen() && lvi->firstChild() ) {
02680            QListViewItem *nextRoot = lvi->itemBelow();
02681            QListViewItemIterator it( lvi->firstChild() );
02682            for( ; (*it) != nextRoot; ++it )
02683               (*it)->setSelected( false );
02684         }
02685      }
02686   }
02687 
02688   // let klistview do it's thing, expanding/collapsing, selection/deselection
02689   KListView::contentsMousePressEvent(e);
02690   /* QListView's shift-select selects also invisible items. Until that is
02691      fixed, we have to deselect hidden items here manually, so the quick
02692      search doesn't mess things up. */
02693   if ( e->state() & ShiftButton ) {
02694     QListViewItemIterator it( this, QListViewItemIterator::Invisible );
02695     while ( it.current() ) {
02696       it.current()->setSelected( false );
02697       ++it;
02698     }
02699   }
02700 
02701   if ( rootDecoClicked ) {
02702       // select the thread's children after closing if the parent is selected
02703      if ( lvi && !lvi->isOpen() && lvi->isSelected() )
02704         setSelected( lvi, true );
02705   }
02706 
02707   if ( lvi && !rootDecoClicked ) {
02708     if ( lvi != currentItem() )
02709       highlightMessage( lvi );
02710     /* Explicitely set selection state. This is necessary because we want to
02711      * also select all children of closed threads when the parent is selected. */
02712 
02713     // unless ctrl mask, set selected if it isn't already
02714     if ( !( e->state() & ControlButton ) && !wasSelected )
02715       setSelected( lvi, true );
02716     // if ctrl mask, toggle selection
02717     if ( e->state() & ControlButton )
02718       setSelected( lvi, !wasSelected );
02719 
02720     if ((e->button() == LeftButton) )
02721       mMousePressed = true;
02722   }
02723 }
02724 
02725 //-----------------------------------------------------------------------------
02726 void KMHeaders::contentsMouseReleaseEvent(QMouseEvent* e)
02727 {
02728   if (e->button() != RightButton)
02729     KListView::contentsMouseReleaseEvent(e);
02730 
02731   mMousePressed = false;
02732 }
02733 
02734 //-----------------------------------------------------------------------------
02735 void KMHeaders::contentsMouseMoveEvent( QMouseEvent* e )
02736 {
02737   if (mMousePressed &&
02738       (e->pos() - mPressPos).manhattanLength() > KGlobalSettings::dndEventDelay()) {
02739     mMousePressed = false;
02740     QListViewItem *item = itemAt( contentsToViewport(mPressPos) );
02741     if ( item ) {
02742       MailList mailList;
02743       unsigned int count = 0;
02744       for( QListViewItemIterator it(this); it.current(); it++ )
02745         if( it.current()->isSelected() ) {
02746           KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
02747       KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
02748       MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(),
02749                    msg->subject(), msg->fromStrip(),
02750                    msg->toStrip(), msg->date() );
02751       mailList.append( mailSummary );
02752       ++count;
02753         }
02754       MailListDrag *d = new MailListDrag( mailList, viewport() );
02755 
02756       // Set pixmap
02757       QPixmap pixmap;
02758       if( count == 1 )
02759         pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) );
02760       else
02761         pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) );
02762 
02763       // Calculate hotspot (as in Konqueror)
02764       if( !pixmap.isNull() ) {
02765         QPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 );
02766         d->setPixmap( pixmap, hotspot );
02767       }
02768       d->drag();
02769     }
02770   }
02771 }
02772 
02773 void KMHeaders::highlightMessage(QListViewItem* i)
02774 {
02775     highlightMessage( i, false );
02776 }
02777 
02778 //-----------------------------------------------------------------------------
02779 void KMHeaders::slotRMB()
02780 {
02781   if (!topLevelWidget()) return; // safe bet
02782 
02783   QPopupMenu *menu = new QPopupMenu(this);
02784 
02785   mMenuToFolder.clear();
02786 
02787   mOwner->updateMessageMenu();
02788 
02789   bool out_folder = kmkernel->folderIsDraftOrOutbox(mFolder);
02790   if ( out_folder )
02791      mOwner->editAction()->plug(menu);
02792   else {
02793      // show most used actions
02794      mOwner->replyAction()->plug(menu);
02795      mOwner->replyAllAction()->plug(menu);
02796      mOwner->replyAuthorAction()->plug( menu );
02797      mOwner->replyListAction()->plug(menu);
02798      mOwner->forwardMenu()->plug(menu);
02799      mOwner->bounceAction()->plug(menu);
02800      mOwner->sendAgainAction()->plug(menu);
02801   }
02802   menu->insertSeparator();
02803 
02804   QPopupMenu *msgCopyMenu = new QPopupMenu(menu);
02805   KMCopyCommand::folderToPopupMenu( false, this, &mMenuToFolder, msgCopyMenu );
02806   menu->insertItem(i18n("&Copy To"), msgCopyMenu);
02807 
02808   if ( mFolder->isReadOnly() ) {
02809     int id = menu->insertItem( i18n("&Move To") );
02810     menu->setItemEnabled( id, false );
02811   } else {
02812     QPopupMenu *msgMoveMenu = new QPopupMenu(menu);
02813     KMMoveCommand::folderToPopupMenu( true, this, &mMenuToFolder, msgMoveMenu );
02814     menu->insertItem(i18n("&Move To"), msgMoveMenu);
02815   }
02816 
02817   if ( !out_folder ) {
02818     mOwner->statusMenu()->plug( menu ); // Mark Message menu
02819     if ( mOwner->threadStatusMenu()->isEnabled() ) {
02820       mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu
02821     }
02822   }
02823 
02824   if (mOwner->watchThreadAction()->isEnabled() ) {
02825     menu->insertSeparator();
02826     mOwner->watchThreadAction()->plug(menu);
02827     mOwner->ignoreThreadAction()->plug(menu);
02828   }
02829   menu->insertSeparator();
02830   mOwner->trashAction()->plug(menu);
02831   mOwner->deleteAction()->plug(menu);
02832 
02833   menu->insertSeparator();
02834   mOwner->saveAsAction()->plug(menu);
02835   mOwner->saveAttachmentsAction()->plug(menu);
02836   mOwner->printAction()->plug(menu);
02837 
02838   if ( !out_folder ) {
02839     menu->insertSeparator();
02840     mOwner->action("apply_filters")->plug(menu);
02841     mOwner->filterMenu()->plug( menu ); // Create Filter menu
02842   }
02843 
02844   mOwner->action("apply_filter_actions")->plug(menu);
02845 
02846   KAcceleratorManager::manage(menu);
02847   kmkernel->setContextMenuShown( true );
02848   menu->exec(QCursor::pos(), 0);
02849   kmkernel->setContextMenuShown( false );
02850   delete menu;
02851 }
02852 
02853 //-----------------------------------------------------------------------------
02854 KMMessage* KMHeaders::currentMsg()
02855 {
02856   KMHeaderItem *hi = currentHeaderItem();
02857   if (!hi)
02858     return 0;
02859   else
02860     return mFolder->getMsg(hi->msgId());
02861 }
02862 
02863 //-----------------------------------------------------------------------------
02864 KMHeaderItem* KMHeaders::currentHeaderItem()
02865 {
02866   return static_cast<KMHeaderItem*>(currentItem());
02867 }
02868 
02869 //-----------------------------------------------------------------------------
02870 int KMHeaders::currentItemIndex()
02871 {
02872   KMHeaderItem* item = currentHeaderItem();
02873   if (item)
02874     return item->msgId();
02875   else
02876     return -1;
02877 }
02878 
02879 //-----------------------------------------------------------------------------
02880 void KMHeaders::setCurrentItemByIndex(int msgIdx)
02881 {
02882   if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) {
02883     clearSelection();
02884     bool unchanged = (currentItem() == mItems[msgIdx]);
02885     setCurrentItem( mItems[msgIdx] );
02886     setSelected( mItems[msgIdx], true );
02887     setSelectionAnchor( currentItem() );
02888     if (unchanged)
02889        highlightMessage( mItems[msgIdx], false);
02890   }
02891 }
02892 
02893 //-----------------------------------------------------------------------------
02894 int KMHeaders::topItemIndex()
02895 {
02896   KMHeaderItem *item = static_cast<KMHeaderItem*>(itemAt(QPoint(1,1)));
02897   if (item)
02898     return item->msgId();
02899   else
02900     return -1;
02901 }
02902 
02903 // If sorting ascending by date/ooa then try to scroll list when new mail
02904 // arrives to show it, but don't scroll current item out of view.
02905 void KMHeaders::showNewMail()
02906 {
02907   if (mSortCol != mPaintInfo.dateCol)
02908     return;
02909  for( int i = 0; i < (int)mItems.size(); ++i)
02910    if (mFolder->getMsgBase(i)->isNew()) {
02911      if (!mSortDescending)
02912        setTopItemByIndex( currentItemIndex() );
02913      break;
02914    }
02915 }
02916 
02917 //-----------------------------------------------------------------------------
02918 void KMHeaders::setTopItemByIndex( int aMsgIdx)
02919 {
02920   int msgIdx = aMsgIdx;
02921   if (msgIdx < 0)
02922     msgIdx = 0;
02923   else if (msgIdx >= (int)mItems.size())
02924     msgIdx = mItems.size() - 1;
02925   if ((msgIdx >= 0) && (msgIdx < (int)mItems.size()))
02926     setContentsPos( 0, itemPos( mItems[msgIdx] ));
02927 }
02928 
02929 //-----------------------------------------------------------------------------
02930 void KMHeaders::setNestedOverride( bool override )
02931 {
02932   mSortInfo.dirty = true;
02933   mNestedOverride = override;
02934   setRootIsDecorated( nestingPolicy != AlwaysOpen
02935                       && isThreaded() );
02936   QString sortFile = mFolder->indexLocation() + ".sorted";
02937   unlink(QFile::encodeName(sortFile));
02938   reset();
02939 }
02940 
02941 //-----------------------------------------------------------------------------
02942 void KMHeaders::setSubjectThreading( bool aSubjThreading )
02943 {
02944   mSortInfo.dirty = true;
02945   mSubjThreading = aSubjThreading;
02946   QString sortFile = mFolder->indexLocation() + ".sorted";
02947   unlink(QFile::encodeName(sortFile));
02948   reset();
02949 }
02950 
02951 //-----------------------------------------------------------------------------
02952 void KMHeaders::setOpen( QListViewItem *item, bool open )
02953 {
02954   if ((nestingPolicy != AlwaysOpen)|| open)
02955       ((KMHeaderItem*)item)->setOpenRecursive( open );
02956 }
02957 
02958 //-----------------------------------------------------------------------------
02959 const KMMsgBase* KMHeaders::getMsgBaseForItem( const QListViewItem *item ) const
02960 {
02961   const KMHeaderItem *hi = static_cast<const KMHeaderItem *> ( item );
02962   return mFolder->getMsgBase( hi->msgId() );
02963 }
02964 
02965 //-----------------------------------------------------------------------------
02966 void KMHeaders::setSorting( int column, bool ascending )
02967 {
02968   kdDebug(5006) << k_funcinfo << column << " " << ascending << endl;
02969 
02970   if (column != -1) {
02971   // carsten: really needed?
02972 //    if (column != mSortCol)
02973 //      setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol ));
02974     if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied
02975         QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
02976         mSortInfo.dirty = true;
02977     }
02978 
02979     mSortCol = column;
02980     mSortDescending = !ascending;
02981 
02982     if (!ascending && (column == mPaintInfo.dateCol))
02983       mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival;
02984 
02985     if (!ascending && (column == mPaintInfo.subCol))
02986       mPaintInfo.status = !mPaintInfo.status;
02987 
02988     QString colText = i18n( "Date" );
02989     if (mPaintInfo.orderOfArrival)
02990       colText = i18n( "Order of Arrival" );
02991     setColumnText( mPaintInfo.dateCol, colText);
02992 
02993     colText = i18n( "Subject" );
02994     if (mPaintInfo.status)
02995       colText = colText + i18n( " (Status)" );
02996     setColumnText( mPaintInfo.subCol, colText);
02997   }
02998   KListView::setSorting( column, ascending );
02999   ensureCurrentItemVisible();
03000   // Make sure the config and .sorted file are updated, otherwise stale info
03001   // is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ).
03002   if ( mFolder ) {
03003     writeFolderConfig();
03004     writeSortOrder();
03005   }
03006 }
03007 
03008 //Flatten the list and write it to disk
03009 static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid,
03010                               int parent_id, QString key,
03011                               bool update_discover=true)
03012 {
03013   unsigned long msgSerNum;
03014   unsigned long parentSerNum;
03015   msgSerNum = kmkernel->msgDict()->getMsgSerNum( folder, msgid );
03016   if (parent_id >= 0)
03017     parentSerNum = kmkernel->msgDict()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED;
03018   else
03019     parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED);
03020 
03021   fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream);
03022   fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream);
03023   Q_INT32 len = key.length() * sizeof(QChar);
03024   fwrite(&len, sizeof(len), 1, sortStream);
03025   if (len)
03026     fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream);
03027 
03028   if (update_discover) {
03029     //update the discovered change count
03030       Q_INT32 discovered_count = 0;
03031       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
03032       fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
03033       discovered_count++;
03034       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
03035       fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
03036   }
03037 }
03038 
03039 void KMHeaders::folderCleared()
03040 {
03041     mSortCacheItems.clear(); //autoDelete is true
03042     mSubjectLists.clear();
03043     mImperfectlyThreadedList.clear();
03044     mPrevCurrent = 0;
03045     emit selected(0);
03046 }
03047 
03048 bool KMHeaders::writeSortOrder()
03049 {
03050   QString sortFile = KMAIL_SORT_FILE(mFolder);
03051 
03052   if (!mSortInfo.dirty) {
03053     struct stat stat_tmp;
03054     if(stat(QFile::encodeName(sortFile), &stat_tmp) == -1) {
03055         mSortInfo.dirty = true;
03056     }
03057   }
03058   if (mSortInfo.dirty) {
03059     if (!mFolder->count()) {
03060       // Folder is empty now, remove the sort file.
03061       unlink(QFile::encodeName(sortFile));
03062       return true;
03063     }
03064     QString tempName = sortFile + ".temp";
03065     unlink(QFile::encodeName(tempName));
03066     FILE *sortStream = fopen(QFile::encodeName(tempName), "w");
03067     if (!sortStream)
03068       return false;
03069 
03070     mSortInfo.ascending = !mSortDescending;
03071     mSortInfo.dirty = false;
03072     mSortInfo.column = mSortCol;
03073     fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION);
03074     //magic header information
03075     Q_INT32 byteOrder = 0x12345678;
03076     Q_INT32 column = mSortCol;
03077     Q_INT32 ascending= !mSortDescending;
03078     Q_INT32 threaded = isThreaded();
03079     Q_INT32 appended=0;
03080     Q_INT32 discovered_count = 0;
03081     Q_INT32 sorted_count=0;
03082     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
03083     fwrite(&column, sizeof(column), 1, sortStream);
03084     fwrite(&ascending, sizeof(ascending), 1, sortStream);
03085     fwrite(&threaded, sizeof(threaded), 1, sortStream);
03086     fwrite(&appended, sizeof(appended), 1, sortStream);
03087     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
03088     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
03089 
03090     QPtrStack<KMHeaderItem> items;
03091     {
03092       QPtrStack<QListViewItem> s;
03093       for (QListViewItem * i = firstChild(); i; ) {
03094         items.push((KMHeaderItem *)i);
03095         if ( i->firstChild() ) {
03096           s.push( i );
03097           i = i->firstChild();
03098         } else if( i->nextSibling()) {
03099           i = i->nextSibling();
03100         } else {
03101             for(i=0; !i && s.count(); i = s.pop()->nextSibling());
03102         }
03103       }
03104     }
03105 
03106     KMMsgBase *kmb;
03107     while(KMHeaderItem *i = items.pop()) {
03108       int parent_id = -1; //no parent, top level
03109       if (threaded) {
03110         kmb = mFolder->getMsgBase( i->mMsgId );
03111         assert(kmb); // I have seen 0L come out of this, called from
03112                    // KMHeaders::setFolder(0xgoodpointer, false);
03113         QString replymd5 = kmb->replyToIdMD5();
03114         QString replyToAuxId = kmb->replyToAuxIdMD5();
03115         KMSortCacheItem *p = NULL;
03116         if(!replymd5.isEmpty())
03117           p = mSortCacheItems[replymd5];
03118 
03119         if (p)
03120           parent_id = p->id();
03121         // We now have either found a parent, or set it to -1, which means that
03122         // it will be reevaluated when a message is added, for example. If there
03123         // is no replyToId and no replyToAuxId and the message is not prefixed,
03124         // this message is top level, and will always be, so no need to
03125         // reevaluate it.
03126         if (replymd5.isEmpty()
03127             && replyToAuxId.isEmpty()
03128             && !kmb->subjectIsPrefixed() )
03129           parent_id = -2;
03130         // FIXME also mark messages with -1 as -2 a certain amount of time after
03131         // their arrival, since it becomes very unlikely that a new parent for
03132         // them will show up. (Ingo suggests a month.) -till
03133       }
03134       internalWriteItem(sortStream, mFolder, i->mMsgId, parent_id,
03135                         i->key(mSortCol, !mSortDescending), false);
03136       //double check for magic headers
03137       sorted_count++;
03138     }
03139 
03140     //magic header twice, case they've changed
03141     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET);
03142     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
03143     fwrite(&column, sizeof(column), 1, sortStream);
03144     fwrite(&ascending, sizeof(ascending), 1, sortStream);
03145     fwrite(&threaded, sizeof(threaded), 1, sortStream);
03146     fwrite(&appended, sizeof(appended), 1, sortStream);
03147     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
03148     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
03149     if (sortStream && ferror(sortStream)) {
03150         fclose(sortStream);
03151         unlink(QFile::encodeName(sortFile));
03152         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
03153         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
03154         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
03155     }
03156     fclose(sortStream);
03157     ::rename(QFile::encodeName(tempName), QFile::encodeName(sortFile));
03158   }
03159 
03160   return true;
03161 }
03162 
03163 void KMHeaders::appendItemToSortFile(KMHeaderItem *khi)
03164 {
03165   QString sortFile = KMAIL_SORT_FILE(mFolder);
03166   if(FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+")) {
03167     int parent_id = -1; //no parent, top level
03168 
03169     if (isThreaded()) {
03170       KMSortCacheItem *sci = khi->sortCacheItem();
03171       KMMsgBase *kmb = mFolder->getMsgBase( khi->mMsgId );
03172       if(sci->parent() && !sci->isImperfectlyThreaded())
03173         parent_id = sci->parent()->id();
03174       else if(kmb->replyToIdMD5().isEmpty()
03175            && kmb->replyToAuxIdMD5().isEmpty()
03176            && !kmb->subjectIsPrefixed())
03177         parent_id = -2;
03178     }
03179 
03180     internalWriteItem(sortStream, mFolder, khi->mMsgId, parent_id,
03181                       khi->key(mSortCol, !mSortDescending), false);
03182 
03183     //update the appended flag FIXME obsolete?
03184     Q_INT32 appended = 1;
03185     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
03186     fwrite(&appended, sizeof(appended), 1, sortStream);
03187     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
03188 
03189     if (sortStream && ferror(sortStream)) {
03190         fclose(sortStream);
03191         unlink(QFile::encodeName(sortFile));
03192         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
03193         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
03194         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
03195     }
03196     fclose(sortStream);
03197   } else {
03198     mSortInfo.dirty = true;
03199   }
03200 }
03201 
03202 void KMHeaders::dirtySortOrder(int column)
03203 {
03204     mSortInfo.dirty = true;
03205     QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
03206     setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true);
03207 }
03208 void KMSortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder,
03209                                       bool waiting_for_parent, bool update_discover)
03210 {
03211     if(mSortOffset == -1) {
03212         fseek(sortStream, 0, SEEK_END);
03213         mSortOffset = ftell(sortStream);
03214     } else {
03215         fseek(sortStream, mSortOffset, SEEK_SET);
03216     }
03217 
03218     int parent_id = -1;
03219     if(!waiting_for_parent) {
03220         if(mParent && !isImperfectlyThreaded())
03221             parent_id = mParent->id();
03222     }
03223     internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover);
03224 }
03225 
03226 static bool compare_ascending = false;
03227 static bool compare_toplevel = true;
03228 static int compare_KMSortCacheItem(const void *s1, const void *s2)
03229 {
03230     if ( !s1 || !s2 )
03231         return 0;
03232     KMSortCacheItem **b1 = (KMSortCacheItem **)s1;
03233     KMSortCacheItem **b2 = (KMSortCacheItem **)s2;
03234     int ret = (*b1)->key().compare((*b2)->key());
03235     if(compare_ascending || !compare_toplevel)
03236         ret = -ret;
03237     return ret;
03238 }
03239 
03240 
03241 void KMHeaders::buildThreadingTree( QMemArray<KMSortCacheItem *> sortCache )
03242 {
03243     mSortCacheItems.clear();
03244     mSortCacheItems.resize( mFolder->count() * 2 );
03245 
03246     // build a dict of all message id's
03247     for(int x = 0; x < mFolder->count(); x++) {
03248         KMMsgBase *mi = mFolder->getMsgBase(x);
03249         QString md5 = mi->msgIdMD5();
03250         if(!md5.isEmpty())
03251             mSortCacheItems.replace(md5, sortCache[x]);
03252     }
03253 }
03254 
03255 
03256 void KMHeaders::buildSubjectThreadingTree( QMemArray<KMSortCacheItem *> sortCache )
03257 {
03258     mSubjectLists.clear();  // autoDelete is true
03259     mSubjectLists.resize( mFolder->count() * 2 );
03260 
03261     for(int x = 0; x < mFolder->count(); x++) {
03262         // Only a lot items that are now toplevel
03263         if ( sortCache[x]->parent()
03264           && sortCache[x]->parent()->id() != -666 ) continue;
03265         KMMsgBase *mi = mFolder->getMsgBase(x);
03266         QString subjMD5 = mi->strippedSubjectMD5();
03267         if (subjMD5.isEmpty()) {
03268             mFolder->getMsgBase(x)->initStrippedSubjectMD5();
03269             subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
03270         }
03271         if( subjMD5.isEmpty() ) continue;
03272 
03273         /* For each subject, keep a list of items with that subject
03274          * (stripped of prefixes) sorted by date. */
03275         if (!mSubjectLists.find(subjMD5))
03276             mSubjectLists.insert(subjMD5, new QPtrList<KMSortCacheItem>());
03277         /* Insertion sort by date. These lists are expected to be very small.
03278          * Also, since the messages are roughly ordered by date in the store,
03279          * they should mostly be prepended at the very start, so insertion is
03280          * cheap. */
03281         int p=0;
03282         for (QPtrListIterator<KMSortCacheItem> it(*mSubjectLists[subjMD5]);
03283                 it.current(); ++it) {
03284             KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
03285             if ( mb->date() < mi->date())
03286                 break;
03287             p++;
03288         }
03289         mSubjectLists[subjMD5]->insert( p, sortCache[x]);
03290     }
03291 }
03292 
03293 
03294 KMSortCacheItem* KMHeaders::findParent(KMSortCacheItem *item)
03295 {
03296     KMSortCacheItem *parent = NULL;
03297     if (!item) return parent;
03298     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
03299     QString replyToIdMD5 = msg->replyToIdMD5();
03300     item->setImperfectlyThreaded(true);
03301     /* First, try if the message our Reply-To header points to
03302      * is available to thread below. */
03303     if(!replyToIdMD5.isEmpty()) {
03304         parent = mSortCacheItems[replyToIdMD5];
03305         if (parent)
03306             item->setImperfectlyThreaded(false);
03307     }
03308     if (!parent) {
03309         // If we dont have a replyToId, or if we have one and the
03310         // corresponding message is not in this folder, as happens
03311         // if you keep your outgoing messages in an OUTBOX, for
03312         // example, try the list of references, because the second
03313         // to last will likely be in this folder. replyToAuxIdMD5
03314         // contains the second to last one.
03315         QString  ref = msg->replyToAuxIdMD5();
03316         if (!ref.isEmpty())
03317             parent = mSortCacheItems[ref];
03318     }
03319     return parent;
03320 }
03321 
03322 KMSortCacheItem* KMHeaders::findParentBySubject(KMSortCacheItem *item)
03323 {
03324     KMSortCacheItem *parent = NULL;
03325     if (!item) return parent;
03326 
03327     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
03328 
03329     // Let's try by subject, but only if the  subject is prefixed.
03330     // This is necessary to make for example cvs commit mailing lists
03331     // work as expected without having to turn threading off alltogether.
03332     if (!msg->subjectIsPrefixed())
03333         return parent;
03334 
03335     QString replyToIdMD5 = msg->replyToIdMD5();
03336     QString subjMD5 = msg->strippedSubjectMD5();
03337     if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) {
03338         /* Iterate over the list of potential parents with the same
03339          * subject, and take the closest one by date. */
03340         for (QPtrListIterator<KMSortCacheItem> it2(*mSubjectLists[subjMD5]);
03341                 it2.current(); ++it2) {
03342             KMMsgBase *mb = mFolder->getMsgBase((*it2)->id());
03343             if ( !mb ) return parent;
03344             // make sure it's not ourselves
03345             if ( item == (*it2) ) continue;
03346             int delta = msg->date() - mb->date();
03347             // delta == 0 is not allowed, to avoid circular threading
03348             // with duplicates.
03349             if (delta > 0 ) {
03350                 // Don't use parents more than 6 weeks older than us.
03351                 if (delta < 3628899)
03352                     parent = (*it2);
03353                 break;
03354             }
03355         }
03356     }
03357     return parent;
03358 }
03359 
03360 bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread )
03361 {
03362     //all cases
03363     Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended;
03364     Q_INT32 deleted_count = 0;
03365     bool unread_exists = false;
03366     bool jumpToUnread = GlobalSettings::self()->jumpToUnread() ||
03367                         forceJumpToUnread;
03368     QMemArray<KMSortCacheItem *> sortCache(mFolder->count());
03369     KMSortCacheItem root;
03370     root.setId(-666); //mark of the root!
03371     bool error = false;
03372 
03373     //threaded cases
03374     QPtrList<KMSortCacheItem> unparented;
03375     mImperfectlyThreadedList.clear();
03376 
03377     //cleanup
03378     mItems.fill( 0, mFolder->count() );
03379     sortCache.fill( 0 );
03380 
03381     QString sortFile = KMAIL_SORT_FILE(mFolder);
03382     FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+");
03383     mSortInfo.fakeSort = 0;
03384 
03385     if(sortStream) {
03386         mSortInfo.fakeSort = 1;
03387         int version = 0;
03388         if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1)
03389           version = -1;
03390         if(version == KMAIL_SORT_VERSION) {
03391           Q_INT32 byteOrder = 0;
03392           fread(&byteOrder, sizeof(byteOrder), 1, sortStream);
03393           if (byteOrder == 0x12345678)
03394           {
03395             fread(&column, sizeof(column), 1, sortStream);
03396             fread(&ascending, sizeof(ascending), 1, sortStream);
03397             fread(&threaded, sizeof(threaded), 1, sortStream);
03398             fread(&appended, sizeof(appended), 1, sortStream);
03399             fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
03400             fread(&sorted_count, sizeof(sorted_count), 1, sortStream);
03401 
03402             //Hackyness to work around qlistview problems
03403             KListView::setSorting(-1);
03404             header()->setSortIndicator(column, ascending);
03405             QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
03406             //setup mSortInfo here now, as above may change it
03407             mSortInfo.dirty = false;
03408             mSortInfo.column = (short)column;
03409             mSortInfo.ascending = (compare_ascending = ascending);
03410 
03411             KMSortCacheItem *item;
03412             unsigned long serNum, parentSerNum;
03413             int id, len, parent, x;
03414             QChar *tmp_qchar = 0;
03415             int tmp_qchar_len = 0;
03416             const int mFolderCount = mFolder->count();
03417             QString key;
03418 
03419             CREATE_TIMER(parse);
03420             START_TIMER(parse);
03421             for(x = 0; !feof(sortStream) && !error; x++) {
03422                 off_t offset = ftell(sortStream);
03423                 KMFolder *folder;
03424                 //parse
03425                 if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end
03426                    !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) ||
03427                    !fread(&len, sizeof(len), 1, sortStream)) {
03428                     break;
03429                 }
03430                 if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) {
03431                     kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl;
03432                     error = true;
03433                     continue;
03434                 }
03435                 if(len) {
03436                     if(len > tmp_qchar_len) {
03437                         tmp_qchar = (QChar *)realloc(tmp_qchar, len);
03438                         tmp_qchar_len = len;
03439                     }
03440                     if(!fread(tmp_qchar, len, 1, sortStream))
03441                         break;
03442                     key = QString(tmp_qchar, len / 2);
03443                 } else {
03444                     key = QString(""); //yuck
03445                 }
03446 
03447                 kmkernel->msgDict()->getLocation(serNum, &folder, &id);
03448                 if (folder != mFolder) {
03449                     ++deleted_count;
03450                     continue;
03451                 }
03452                 if (parentSerNum < KMAIL_RESERVED) {
03453                     parent = (int)parentSerNum - KMAIL_RESERVED;
03454                 } else {
03455                     kmkernel->msgDict()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent);
03456                     if (folder != mFolder)
03457                         parent = -1;
03458                 }
03459                 if ((id < 0) || (id >= mFolderCount) ||
03460                     (parent < -2) || (parent >= mFolderCount)) { // sanity checking
03461                     kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl;
03462                     error = true;
03463                     continue;
03464                 }
03465 
03466                 if ((item=sortCache[id])) {
03467                     if (item->id() != -1) {
03468                         kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl;
03469                         error = true;
03470                         continue;
03471                     }
03472                     item->setKey(key);
03473                     item->setId(id);
03474                     item->setOffset(offset);
03475                 } else {
03476                     item = sortCache[id] = new KMSortCacheItem(id, key, offset);
03477                 }
03478                 if (threaded && parent != -2) {
03479                     if(parent == -1) {
03480                         unparented.append(item);
03481                         root.addUnsortedChild(item);
03482                     } else {
03483                         if( ! sortCache[parent] )
03484                             sortCache[parent] = new KMSortCacheItem;
03485                         sortCache[parent]->addUnsortedChild(item);
03486                     }
03487                 } else {
03488                     if(x < sorted_count )
03489                         root.addSortedChild(item);
03490                     else {
03491                         root.addUnsortedChild(item);
03492                     }
03493                 }
03494             }
03495             if (error || (x != sorted_count + discovered_count)) {// sanity check
03496                 kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl;
03497                 fclose(sortStream);
03498                 sortStream = 0;
03499             }
03500 
03501             if(tmp_qchar)
03502                 free(tmp_qchar);
03503             END_TIMER(parse);
03504             SHOW_TIMER(parse);
03505           }
03506           else {
03507               fclose(sortStream);
03508               sortStream = 0;
03509           }
03510         } else {
03511             fclose(sortStream);
03512             sortStream = 0;
03513         }
03514     }
03515 
03516     if (!sortStream) {
03517         mSortInfo.dirty = true;
03518         mSortInfo.column = column = mSortCol;
03519         mSortInfo.ascending = ascending = !mSortDescending;
03520         threaded = (isThreaded());
03521         sorted_count = discovered_count = appended = 0;
03522         KListView::setSorting( mSortCol, !mSortDescending );
03523     }
03524     //fill in empty holes
03525     if((sorted_count + discovered_count - deleted_count) < mFolder->count()) {
03526         CREATE_TIMER(holes);
03527         START_TIMER(holes);
03528         KMMsgBase *msg = 0;
03529         for(int x = 0; x < mFolder->count(); x++) {
03530             if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) {
03531                 int sortOrder = column;
03532                 if (mPaintInfo.orderOfArrival)
03533                     sortOrder |= (1 << 6);
03534                 if (mPaintInfo.status)
03535                     sortOrder |= (1 << 5);
03536                 sortCache[x] = new KMSortCacheItem(
03537                     x, KMHeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder ));
03538                 if(threaded)
03539                     unparented.append(sortCache[x]);
03540                 else
03541                     root.addUnsortedChild(sortCache[x]);
03542                 if(sortStream)
03543                     sortCache[x]->updateSortFile(sortStream, mFolder, true, true);
03544                 discovered_count++;
03545                 appended = 1;
03546             }
03547         }
03548         END_TIMER(holes);
03549         SHOW_TIMER(holes);
03550     }
03551 
03552     // Make sure we've placed everything in parent/child relationship. All
03553     // messages with a parent id of -1 in the sort file are reevaluated here.
03554     if (threaded) buildThreadingTree( sortCache );
03555     QPtrList<KMSortCacheItem> toBeSubjThreaded;
03556 
03557     if (threaded && !unparented.isEmpty()) {
03558         CREATE_TIMER(reparent);
03559         START_TIMER(reparent);
03560 
03561         for(QPtrListIterator<KMSortCacheItem> it(unparented); it.current(); ++it) {
03562             KMSortCacheItem *item = (*it);
03563             KMSortCacheItem *parent = findParent( item );
03564             // If we have a parent, make sure it's not ourselves
03565             if ( parent && (parent != (*it)) ) {
03566                 parent->addUnsortedChild((*it));
03567                 if(sortStream)
03568                     (*it)->updateSortFile(sortStream, mFolder);
03569             } else {
03570                 // if we will attempt subject threading, add to the list,
03571                 // otherwise to the root with them
03572                 if (mSubjThreading)
03573                   toBeSubjThreaded.append((*it));
03574                 else
03575                   root.addUnsortedChild((*it));
03576             }
03577         }
03578 
03579         if (mSubjThreading) {
03580             buildSubjectThreadingTree( sortCache );
03581             for(QPtrListIterator<KMSortCacheItem> it(toBeSubjThreaded); it.current(); ++it) {
03582                 KMSortCacheItem *item = (*it);
03583                 KMSortCacheItem *parent = findParentBySubject( item );
03584 
03585                 if ( parent ) {
03586                     parent->addUnsortedChild((*it));
03587                     if(sortStream)
03588                       (*it)->updateSortFile(sortStream, mFolder);
03589                 } else {
03590                     //oh well we tried, to the root with you!
03591                     root.addUnsortedChild((*it));
03592                 }
03593             }
03594         }
03595         END_TIMER(reparent);
03596         SHOW_TIMER(reparent);
03597     }
03598     //create headeritems
03599     CREATE_TIMER(header_creation);
03600     START_TIMER(header_creation);
03601     KMHeaderItem *khi;
03602     KMSortCacheItem *i, *new_kci;
03603     QPtrQueue<KMSortCacheItem> s;
03604     s.enqueue(&root);
03605     compare_toplevel = true;
03606     do {
03607         i = s.dequeue();
03608         const QPtrList<KMSortCacheItem> *sorted = i->sortedChildren();
03609         int unsorted_count, unsorted_off=0;
03610         KMSortCacheItem **unsorted = i->unsortedChildren(unsorted_count);
03611         if(unsorted)
03612             qsort(unsorted, unsorted_count, sizeof(KMSortCacheItem *), //sort
03613                   compare_KMSortCacheItem);
03614 
03615         /* The sorted list now contains all sorted children of this item, while
03616          * the (aptly named) unsorted array contains all as of yet unsorted
03617          * ones. It has just been qsorted, so it is in itself sorted. These two
03618          * sorted lists are now merged into one. */
03619         for(QPtrListIterator<KMSortCacheItem> it(*sorted);
03620             (unsorted && unsorted_off < unsorted_count) || it.current(); ) {
03621             /* As long as we have something in the sorted list and there is
03622                nothing unsorted left, use the item from the sorted list. Also
03623                if we are sorting descendingly and the sorted item is supposed
03624                to be sorted before the unsorted one do so. In the ascending
03625                case we invert the logic for non top level items. */
03626             if( it.current() &&
03627                ( !unsorted || unsorted_off >= unsorted_count
03628                 ||
03629                 ( ( !ascending || (ascending && !compare_toplevel) )
03630                   && (*it)->key() < unsorted[unsorted_off]->key() )
03631                 ||
03632                 (  ascending && (*it)->key() >= unsorted[unsorted_off]->key() )
03633                 )
03634                )
03635             {
03636                 new_kci = (*it);
03637                 ++it;
03638             } else {
03639                 /* Otherwise use the next item of the unsorted list */
03640                 new_kci = unsorted[unsorted_off++];
03641             }
03642             if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent
03643                 continue;
03644 
03645             if(threaded && i->item()) {
03646                 // If the parent is watched or ignored, propagate that to it's
03647                 // children
03648                 if (mFolder->getMsgBase(i->id())->isWatched())
03649                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusWatched);
03650                 if (mFolder->getMsgBase(i->id())->isIgnored()) {
03651                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusIgnored);
03652                   mFolder->setStatus(new_kci->id(), KMMsgStatusRead);
03653                 }
03654                 khi = new KMHeaderItem(i->item(), new_kci->id(), new_kci->key());
03655             } else {
03656                 khi = new KMHeaderItem(this, new_kci->id(), new_kci->key());
03657             }
03658             new_kci->setItem(mItems[new_kci->id()] = khi);
03659             if(new_kci->hasChildren())
03660                 s.enqueue(new_kci);
03661             // we always jump to new messages, but we only jump to
03662             // unread messages if we are told to do so
03663             if ( mFolder->getMsgBase(new_kci->id())->isNew() ||
03664                  ( jumpToUnread &&
03665                    mFolder->getMsgBase(new_kci->id())->isUnread() ) ) {
03666               unread_exists = true;
03667             }
03668         }
03669         // If we are sorting by date and ascending the top level items are sorted
03670         // ascending and the threads themselves are sorted descending. One wants
03671         // to have new threads on top but the threads themselves top down.
03672         if (mSortCol == paintInfo()->dateCol)
03673           compare_toplevel = false;
03674     } while(!s.isEmpty());
03675 
03676     for(int x = 0; x < mFolder->count(); x++) {     //cleanup
03677         if (!sortCache[x]) { // not yet there?
03678             continue;
03679         }
03680 
03681         if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
03682             kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. "
03683                   << endl << "Please talk to your threading counselor asap. " <<  endl;
03684             khi = new KMHeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
03685             sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
03686         }
03687         // Add all imperfectly threaded items to a list, so they can be
03688         // reevaluated when a new message arrives which might be a better parent.
03689         // Important for messages arriving out of order.
03690         if (threaded && sortCache[x]->isImperfectlyThreaded()) {
03691             mImperfectlyThreadedList.append(sortCache[x]->item());
03692         }
03693         // Set the reverse mapping KMHeaderItem -> KMSortCacheItem. Needed for
03694         // keeping the data structures up to date on removal, for example.
03695         sortCache[x]->item()->setSortCacheItem(sortCache[x]);
03696     }
03697 
03698     if (getNestingPolicy()<2)
03699     for (KMHeaderItem *khi=static_cast<KMHeaderItem*>(firstChild()); khi!=0;khi=static_cast<KMHeaderItem*>(khi->nextSibling()))
03700        khi->setOpen(true);
03701 
03702     END_TIMER(header_creation);
03703     SHOW_TIMER(header_creation);
03704 
03705     if(sortStream) { //update the .sorted file now
03706         // heuristic for when it's time to rewrite the .sorted file
03707         if( discovered_count * discovered_count > sorted_count - deleted_count ) {
03708             mSortInfo.dirty = true;
03709         } else {
03710             //update the appended flag
03711             appended = 0;
03712             fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
03713             fwrite(&appended, sizeof(appended), 1, sortStream);
03714         }
03715     }
03716 
03717     //show a message
03718     CREATE_TIMER(selection);
03719     START_TIMER(selection);
03720     if(set_selection) {
03721         int first_unread = -1;
03722         if (unread_exists) {
03723             KMHeaderItem *item = static_cast<KMHeaderItem*>(firstChild());
03724             while (item) {
03725               if ( mFolder->getMsgBase( item->msgId() )->isNew() ||
03726                    ( jumpToUnread &&
03727                      mFolder->getMsgBase( item->msgId() )->isUnread() ) ) {
03728                 first_unread = item->msgId();
03729                 break;
03730               }
03731               item = static_cast<KMHeaderItem*>(item->itemBelow());
03732             }
03733         }
03734 
03735         if(first_unread == -1 ) {
03736             setTopItemByIndex(mTopItem);
03737             if ( mCurrentItem >= 0 )
03738               setCurrentItemByIndex( mCurrentItem );
03739             else if ( mCurrentItemSerNum > 0 )
03740               setCurrentItemBySerialNum( mCurrentItemSerNum );
03741             else
03742               setCurrentItemByIndex( 0 );
03743         } else {
03744             setCurrentItemByIndex(first_unread);
03745             makeHeaderVisible();
03746             center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 );
03747         }
03748     } else {
03749         // only reset the selection if we have no current item
03750         if (mCurrentItem <= 0) {
03751           setTopItemByIndex(mTopItem);
03752           setCurrentItemByIndex(0);
03753         }
03754     }
03755     END_TIMER(selection);
03756     SHOW_TIMER(selection);
03757     if (error || (sortStream && ferror(sortStream))) {
03758         if ( sortStream )
03759             fclose(sortStream);
03760         unlink(QFile::encodeName(sortFile));
03761         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
03762         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
03763         //kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
03764     }
03765     if(sortStream)
03766         fclose(sortStream);
03767 
03768     return true;
03769 }
03770 
03771 //-----------------------------------------------------------------------------
03772 void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum )
03773 {
03774   // Linear search == slow. Don't overuse this method.
03775   // It's currently only used for finding the current item again
03776   // after expiry deleted mails (so the index got invalidated).
03777   for (int i = 0; i < (int)mItems.size() - 1; ++i) {
03778     KMMsgBase *mMsgBase = mFolder->getMsgBase( i );
03779     if ( mMsgBase->getMsgSerNum() == serialNum ) {
03780       bool unchanged = (currentItem() == mItems[i]);
03781       setCurrentItem( mItems[i] );
03782       setSelected( mItems[i], true );
03783       setSelectionAnchor( currentItem() );
03784       if ( unchanged )
03785         highlightMessage( currentItem(), false );
03786       ensureCurrentItemVisible();
03787       return;
03788     }
03789   }
03790   // Not found. Maybe we should select the last item instead?
03791   kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl;
03792 }
03793 
03794 #include "kmheaders.moc"
KDE Logo
This file is part of the documentation for kmail Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Fri Dec 21 14:24:56 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003