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