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) parent = findParentBySubject( sci );
01333       myParent->takeItem(lvi);
01334       if (parent && parent->item() != item)
01335           parent->item()->insertItem(lvi);
01336       else
01337         insertItem(lvi);
01338 
01339       if (!parent || (sci->isImperfectlyThreaded()
01340                       && !mImperfectlyThreadedList.containsRef(item)))
01341         mImperfectlyThreadedList.append(item);
01342       if (parent && !sci->isImperfectlyThreaded()
01343           && mImperfectlyThreadedList.containsRef(item))
01344         mImperfectlyThreadedList.removeRef(item);
01345     }
01346   }
01347   // Make sure our data structures are cleared.
01348   if (!mFolder->count())
01349       folderCleared();
01350 
01351   mImperfectlyThreadedList.removeRef(removedItem);
01352   delete removedItem;
01353   // we might have rethreaded it, in which case its current state will be lost
01354   if ( curItem ) {
01355     if ( curItem != removedItem ) {
01356       setCurrentItem( curItem );
01357       setSelectionAnchor( currentItem() );
01358     } else {
01359       // We've removed the current item, which means it was removed from
01360       // something other than a user move or copy, which would have selected
01361       // the next logical mail. This can happen when the mail is deleted by
01362       // a filter, or some other behind the scenes action. Select something
01363       // sensible, then, and make sure the reader window is cleared.
01364       emit maybeDeleting();
01365       int contentX, contentY;
01366       KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01367       finalizeMove( nextItem, contentX, contentY );
01368     }
01369   }
01370   /* restore signal */
01371   connect( this, SIGNAL(currentChanged(QListViewItem*)),
01372            this, SLOT(highlightMessage(QListViewItem*)));
01373 }
01374 
01375 
01376 //-----------------------------------------------------------------------------
01377 void KMHeaders::msgHeaderChanged(KMFolder*, int msgId)
01378 {
01379   if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return;
01380   KMHeaderItem *item = mItems[msgId];
01381   if (item) {
01382     item->irefresh();
01383     item->repaint();
01384   }
01385 }
01386 
01387 
01388 //-----------------------------------------------------------------------------
01389 void KMHeaders::setMsgStatus (KMMsgStatus status, bool toggle)
01390 {
01391   SerNumList serNums;
01392   for (QListViewItemIterator it(this); it.current(); ++it)
01393     if ( it.current()->isSelected() && it.current()->isVisible() ) {
01394       KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
01395       KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
01396       serNums.append( msgBase->getMsgSerNum() );
01397     }
01398   if (serNums.empty())
01399     return;
01400 
01401   KMCommand *command = new KMSetStatusCommand( status, serNums, toggle );
01402   command->start();
01403 }
01404 
01405 
01406 QPtrList<QListViewItem> KMHeaders::currentThread() const
01407 {
01408   if (!mFolder) return QPtrList<QListViewItem>();
01409 
01410   // starting with the current item...
01411   QListViewItem *curItem = currentItem();
01412   if (!curItem) return QPtrList<QListViewItem>();
01413 
01414   // ...find the top-level item:
01415   QListViewItem *topOfThread = curItem;
01416   while ( topOfThread->parent() )
01417     topOfThread = topOfThread->parent();
01418 
01419   // collect the items in this thread:
01420   QPtrList<QListViewItem> list;
01421   QListViewItem *topOfNextThread = topOfThread->nextSibling();
01422   for ( QListViewItemIterator it( topOfThread ) ;
01423         it.current() && it.current() != topOfNextThread ; ++it )
01424     list.append( it.current() );
01425   return list;
01426 }
01427 
01428 void KMHeaders::setThreadStatus(KMMsgStatus status, bool toggle)
01429 {
01430   QPtrList<QListViewItem> curThread = currentThread();
01431   QPtrListIterator<QListViewItem> it( curThread );
01432   SerNumList serNums;
01433 
01434   for ( it.toFirst() ; it.current() ; ++it ) {
01435     int id = static_cast<KMHeaderItem*>(*it)->msgId();
01436     KMMsgBase *msgBase = mFolder->getMsgBase( id );
01437     serNums.append( msgBase->getMsgSerNum() );
01438   }
01439 
01440   if (serNums.empty())
01441     return;
01442 
01443   KMCommand *command = new KMSetStatusCommand( status, serNums, toggle );
01444   command->start();
01445 }
01446 
01447 //-----------------------------------------------------------------------------
01448 int KMHeaders::slotFilterMsg(KMMessage *msg)
01449 {
01450   msg->setTransferInProgress(false);
01451   int filterResult = kmkernel->filterMgr()->process(msg,KMFilterMgr::Explicit);
01452   if (filterResult == 2) {
01453     // something went horribly wrong (out of space?)
01454     kmkernel->emergencyExit( i18n("Unable to process messages: " ) + QString::fromLocal8Bit(strerror(errno)));
01455     return 2;
01456   }
01457   if (msg->parent()) { // unGet this msg
01458     int idx = -1;
01459     KMFolder * p = 0;
01460     kmkernel->msgDict()->getLocation( msg, &p, &idx );
01461     assert( p == msg->parent() ); assert( idx >= 0 );
01462     p->unGetMsg( idx );
01463   }
01464 
01465   return filterResult;
01466 }
01467 
01468 
01469 void KMHeaders::slotExpandOrCollapseThread( bool expand )
01470 {
01471   if ( !isThreaded() ) return;
01472   // find top-level parent of currentItem().
01473   QListViewItem *item = currentItem();
01474   if ( !item ) return;
01475   clearSelection();
01476   item->setSelected( true );
01477   while ( item->parent() )
01478     item = item->parent();
01479   KMHeaderItem * hdrItem = static_cast<KMHeaderItem*>(item);
01480   hdrItem->setOpenRecursive( expand );
01481   if ( !expand ) // collapse can hide the current item:
01482     setCurrentMsg( hdrItem->msgId() );
01483   ensureItemVisible( currentItem() );
01484 }
01485 
01486 void KMHeaders::slotExpandOrCollapseAllThreads( bool expand )
01487 {
01488   if ( !isThreaded() ) return;
01489 
01490   QListViewItem * item = currentItem();
01491   if( item ) {
01492     clearSelection();
01493     item->setSelected( true );
01494   }
01495 
01496   for ( QListViewItem *item = firstChild() ;
01497         item ; item = item->nextSibling() )
01498     static_cast<KMHeaderItem*>(item)->setOpenRecursive( expand );
01499   if ( !expand ) { // collapse can hide the current item:
01500     QListViewItem * item = currentItem();
01501     if( item ) {
01502       while ( item->parent() )
01503         item = item->parent();
01504       setCurrentMsg( static_cast<KMHeaderItem*>(item)->msgId() );
01505     }
01506   }
01507   ensureItemVisible( currentItem() );
01508 }
01509 
01510 //-----------------------------------------------------------------------------
01511 void KMHeaders::setStyleDependantFrameWidth()
01512 {
01513   // set the width of the frame to a reasonable value for the current GUI style
01514   int frameWidth;
01515   if( style().isA("KeramikStyle") )
01516     frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth ) - 1;
01517   else
01518     frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth );
01519   if ( frameWidth < 0 )
01520     frameWidth = 0;
01521   if ( frameWidth != lineWidth() )
01522     setLineWidth( frameWidth );
01523 }
01524 
01525 //-----------------------------------------------------------------------------
01526 void KMHeaders::styleChange( QStyle& oldStyle )
01527 {
01528   setStyleDependantFrameWidth();
01529   KListView::styleChange( oldStyle );
01530 }
01531 
01532 //-----------------------------------------------------------------------------
01533 void KMHeaders::setFolderInfoStatus ()
01534 {
01535   if ( !mFolder ) return;
01536   QString str = ( static_cast<KMFolder*>(mFolder) == kmkernel->outboxFolder() )
01537                 ? i18n( "1 unsent", "%n unsent", mFolder->countUnread() )
01538                 : i18n( "1 unread", "%n unread", mFolder->countUnread() );
01539   str = i18n( "1 message, %1.", "%n messages, %1.", mFolder->count() )
01540         .arg( str );
01541   if ( mFolder->isReadOnly() )
01542     str = i18n("%1 = n messages, m unread.", "%1 Folder is read-only.").arg( str );
01543   BroadcastStatus::instance()->setStatusMsg(str);
01544 }
01545 
01546 //-----------------------------------------------------------------------------
01547 void KMHeaders::applyFiltersOnMsg()
01548 {
01549 #if 0 // uses action scheduler
01550   KMFilterMgr::FilterSet set = KMFilterMgr::All;
01551   QPtrList<KMFilter> filters;
01552   filters = *( kmkernel->filterMgr() );
01553   ActionScheduler *scheduler = new ActionScheduler( set, filters, this );
01554   scheduler->setAutoDestruct( true );
01555 
01556   int contentX, contentY;
01557   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01558   QPtrList<KMMsgBase> msgList = *selectedMsgs(true);
01559   finalizeMove( nextItem, contentX, contentY );
01560 
01561   for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
01562     scheduler->execFilters( msg );
01563 #else
01564   int contentX, contentY;
01565   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01566 
01567   KMMessageList* msgList = selectedMsgs();
01568   if (msgList->isEmpty())
01569     return;
01570   finalizeMove( nextItem, contentX, contentY );
01571 
01572   CREATE_TIMER(filter);
01573   START_TIMER(filter);
01574 
01575   for (KMMsgBase* msgBase=msgList->first(); msgBase; msgBase=msgList->next()) {
01576     int idx = msgBase->parent()->find(msgBase);
01577     assert(idx != -1);
01578     KMMessage * msg = msgBase->parent()->getMsg(idx);
01579     if (msg->transferInProgress()) continue;
01580     msg->setTransferInProgress(true);
01581     if ( !msg->isComplete() )
01582     {
01583       FolderJob *job = mFolder->createJob(msg);
01584       connect(job, SIGNAL(messageRetrieved(KMMessage*)),
01585               SLOT(slotFilterMsg(KMMessage*)));
01586       job->start();
01587     } else {
01588       if (slotFilterMsg(msg) == 2) break;
01589     }
01590   }
01591   END_TIMER(filter);
01592   SHOW_TIMER(filter);
01593 #endif
01594 }
01595 
01596 
01597 //-----------------------------------------------------------------------------
01598 void KMHeaders::setMsgRead (int msgId)
01599 {
01600   KMMsgBase *msgBase = mFolder->getMsgBase( msgId );
01601   if (!msgBase)
01602     return;
01603 
01604   SerNumList serNums;
01605   if (msgBase->isNew() || msgBase->isUnread()) {
01606     serNums.append( msgBase->getMsgSerNum() );
01607   }
01608 
01609   KMCommand *command = new KMSetStatusCommand( KMMsgStatusRead, serNums );
01610   command->start();
01611 }
01612 
01613 
01614 //-----------------------------------------------------------------------------
01615 void KMHeaders::deleteMsg ()
01616 {
01617   //make sure we have an associated folder (root of folder tree does not).
01618   if (!mFolder)
01619     return;
01620 
01621   int contentX, contentY;
01622   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01623   KMMessageList msgList = *selectedMsgs(true);
01624   finalizeMove( nextItem, contentX, contentY );
01625 
01626   KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList );
01627   connect( command, SIGNAL( completed( KMCommand * ) ),
01628            this, SLOT( slotMoveCompleted( KMCommand * ) ) );
01629   command->start();
01630 
01631   BroadcastStatus::instance()->setStatusMsg("");
01632   //  triggerUpdate();
01633 }
01634 
01635 
01636 //-----------------------------------------------------------------------------
01637 void KMHeaders::moveSelectedToFolder( int menuId )
01638 {
01639   if (mMenuToFolder[menuId])
01640     moveMsgToFolder( mMenuToFolder[menuId] );
01641 }
01642 
01643 //-----------------------------------------------------------------------------
01644 KMHeaderItem* KMHeaders::prepareMove( int *contentX, int *contentY )
01645 {
01646   KMHeaderItem *ret = 0;
01647   emit maybeDeleting();
01648 
01649   disconnect( this, SIGNAL(currentChanged(QListViewItem*)),
01650               this, SLOT(highlightMessage(QListViewItem*)));
01651 
01652   QListViewItem *curItem;
01653   KMHeaderItem *item;
01654   curItem = currentItem();
01655   while (curItem && curItem->isSelected() && curItem->itemBelow())
01656     curItem = curItem->itemBelow();
01657   while (curItem && curItem->isSelected() && curItem->itemAbove())
01658     curItem = curItem->itemAbove();
01659   item = static_cast<KMHeaderItem*>(curItem);
01660 
01661   *contentX = contentsX();
01662   *contentY = contentsY();
01663 
01664   if (item  && !item->isSelected())
01665     ret = item;
01666 
01667   return ret;
01668 }
01669 
01670 //-----------------------------------------------------------------------------
01671 void KMHeaders::finalizeMove( KMHeaderItem *item, int contentX, int contentY )
01672 {
01673   emit selected( 0 );
01674 
01675   if ( item ) {
01676     clearSelection();
01677     setCurrentItem( item );
01678     setSelected( item, true );
01679     setSelectionAnchor( currentItem() );
01680     mPrevCurrent = 0;
01681     highlightMessage( item, false);
01682   }
01683 
01684   setContentsPos( contentX, contentY );
01685   makeHeaderVisible();
01686   connect( this, SIGNAL(currentChanged(QListViewItem*)),
01687            this, SLOT(highlightMessage(QListViewItem*)));
01688 }
01689 
01690 
01691 //-----------------------------------------------------------------------------
01692 void KMHeaders::moveMsgToFolder ( KMFolder* destFolder, bool askForConfirmation )
01693 {
01694   if ( destFolder == mFolder ) return; // Catch the noop case
01695 
01696   KMMessageList msgList = *selectedMsgs();
01697   if ( !destFolder && askForConfirmation &&    // messages shall be deleted
01698        KMessageBox::warningContinueCancel(this,
01699          i18n("<qt>Do you really want to delete the selected message?<br>"
01700               "Once deleted, it cannot be restored.</qt>",
01701               "<qt>Do you really want to delete the %n selected messages?<br>"
01702               "Once deleted, they cannot be restored.</qt>", msgList.count() ),
01703          i18n("Delete Messages"), KGuiItem(i18n("De&lete"),"editdelete"), "NoConfirmDelete") == KMessageBox::Cancel )
01704     return;  // user canceled the action
01705 
01706   // remember the message to select afterwards
01707   int contentX, contentY;
01708   KMHeaderItem *nextItem = prepareMove( &contentX, &contentY );
01709   msgList = *selectedMsgs(true);
01710   finalizeMove( nextItem, contentX, contentY );
01711 
01712   KMCommand *command = new KMMoveCommand( destFolder, msgList );
01713   connect( command, SIGNAL( completed( KMCommand * ) ),
01714            this, SLOT( slotMoveCompleted( KMCommand * ) ) );
01715   command->start();
01716 
01717 }
01718 
01719 void KMHeaders::slotMoveCompleted( KMCommand *command )
01720 {
01721   kdDebug(5006) << k_funcinfo << command->result() << endl;
01722   bool deleted = static_cast<KMMoveCommand *>( command )->destFolder() == 0;
01723   if ( command->result() == KMCommand::OK ) {
01724     // make sure the current item is shown
01725     makeHeaderVisible();
01726 #if 0 // enable after the message-freeze
01727     BroadcastStatus::instance()->setStatusMsg(
01728        deleted ? i18nTODO("Messages deleted successfully.") : i18nTODO("Messages moved successfully") );
01729 #else
01730     if ( !deleted ) BroadcastStatus::instance()->setStatusMsg( i18n( "Messages moved successfully" ) );
01731 #endif
01732   } else {
01733     /* The move failed or the user canceled it; reset the state of all
01734      * messages involved and repaint.
01735      *
01736      * Note: This potentially resets too many items if there is more than one
01737      *       move going on. Oh well, I suppose no animals will be harmed.
01738      * */
01739     for (QListViewItemIterator it(this); it.current(); it++) {
01740       KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
01741       if ( item->aboutToBeDeleted() ) {
01742         item->setAboutToBeDeleted ( false );
01743         item->setSelectable ( true );
01744         KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
01745         if ( msgBase->isMessage() ) {
01746           KMMessage *msg = static_cast<KMMessage *>(msgBase);
01747           if ( msg ) msg->setTransferInProgress( false, true );
01748         }
01749       }
01750     }
01751     triggerUpdate();
01752 #if 0 // enable after the message-freeze
01753     if ( command->result() == KMCommand::Failed )
01754       BroadcastStatus::instance()->setStatusMsg(
01755            deleted ? i18nTODO("Deleting messages failed.") : i18nTODO("Moving messages failed.") );
01756     else
01757       BroadcastStatus::instance()->setStatusMsg(
01758            deleted ? i18nTODO("Deleting messages canceled.") : i18nTODO("Moving messages canceled.") );
01759 #else
01760     if ( !deleted ) {
01761       if ( command->result() == KMCommand::Failed )
01762         BroadcastStatus::instance()->setStatusMsg( i18n("Moving messages failed.") );
01763       else
01764         BroadcastStatus::instance()->setStatusMsg( i18n("Moving messages canceled.") );
01765     }
01766 #endif
01767   }
01768 }
01769 
01770 bool KMHeaders::canUndo() const
01771 {
01772     return ( kmkernel->undoStack()->size() > 0 );
01773 }
01774 
01775 //-----------------------------------------------------------------------------
01776 void KMHeaders::undo()
01777 {
01778   kmkernel->undoStack()->undo();
01779 }
01780 
01781 //-----------------------------------------------------------------------------
01782 void KMHeaders::copySelectedToFolder(int menuId )
01783 {
01784   if (mMenuToFolder[menuId])
01785     copyMsgToFolder( mMenuToFolder[menuId] );
01786 }
01787 
01788 
01789 //-----------------------------------------------------------------------------
01790 void KMHeaders::copyMsgToFolder(KMFolder* destFolder, KMMessage* aMsg)
01791 {
01792   if ( !destFolder )
01793     return;
01794 
01795   KMCommand * command = 0;
01796   if (aMsg)
01797     command = new KMCopyCommand( destFolder, aMsg );
01798   else {
01799     KMMessageList msgList = *selectedMsgs();
01800     command = new KMCopyCommand( destFolder, msgList );
01801   }
01802 
01803   command->start();
01804 }
01805 
01806 
01807 //-----------------------------------------------------------------------------
01808 void KMHeaders::setCurrentMsg(int cur)
01809 {
01810   if (!mFolder) return;
01811   if (cur >= mFolder->count()) cur = mFolder->count() - 1;
01812   if ((cur >= 0) && (cur < (int)mItems.size())) {
01813     clearSelection();
01814     setCurrentItem( mItems[cur] );
01815     setSelected( mItems[cur], true );
01816     setSelectionAnchor( currentItem() );
01817   }
01818   makeHeaderVisible();
01819   setFolderInfoStatus();
01820 }
01821 
01822 //-----------------------------------------------------------------------------
01823 void KMHeaders::setSelected( QListViewItem *item, bool selected )
01824 {
01825   if ( !item )
01826     return;
01827 
01828   if ( item->isVisible() )
01829     KListView::setSelected( item, selected );
01830 
01831   // If the item is the parent of a closed thread recursively select
01832   // children .
01833   if ( isThreaded() && !item->isOpen() && item->firstChild() ) {
01834       QListViewItem *nextRoot = item->itemBelow();
01835       QListViewItemIterator it( item->firstChild() );
01836       for( ; (*it) != nextRoot; ++it ) {
01837         if ( (*it)->isVisible() )
01838            (*it)->setSelected( selected );
01839       }
01840   }
01841 }
01842 
01843 void KMHeaders::setSelectedByIndex( QValueList<int> items, bool selected )
01844 {
01845   for ( QValueList<int>::Iterator it = items.begin(); it != items.end(); ++it )
01846   {
01847     if ( ((*it) >= 0) && ((*it) < (int)mItems.size()) )
01848     {
01849       setSelected( mItems[(*it)], selected );
01850     }
01851   }
01852 }
01853 
01854 void KMHeaders::clearSelectableAndAboutToBeDeleted( Q_UINT32 serNum )
01855 {
01856   // fugly, but I see no way around it
01857   for (QListViewItemIterator it(this); it.current(); it++) {
01858     KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
01859     if ( item->aboutToBeDeleted() ) {
01860       KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
01861       if ( serNum == msgBase->getMsgSerNum() ) {
01862         item->setAboutToBeDeleted ( false );
01863         item->setSelectable ( true );
01864       }
01865     }
01866   }
01867   triggerUpdate();
01868 }
01869 
01870 //-----------------------------------------------------------------------------
01871 KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted)
01872 {
01873   mSelMsgBaseList.clear();
01874   for (QListViewItemIterator it(this); it.current(); it++) {
01875     if ( it.current()->isSelected() && it.current()->isVisible() ) {
01876       KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
01877       if (toBeDeleted) {
01878         // make sure the item is not uselessly rethreaded and not selectable
01879         item->setAboutToBeDeleted ( true );
01880         item->setSelectable ( false );
01881       }
01882       KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
01883       mSelMsgBaseList.append(msgBase);
01884     }
01885   }
01886   return &mSelMsgBaseList;
01887 }
01888 
01889 //-----------------------------------------------------------------------------
01890 QValueList<int> KMHeaders::selectedItems()
01891 {
01892   QValueList<int> items;
01893   for ( QListViewItemIterator it(this); it.current(); it++ )
01894   {
01895     if ( it.current()->isSelected() && it.current()->isVisible() )
01896     {
01897       KMHeaderItem* item = static_cast<KMHeaderItem*>( it.current() );
01898       items.append( item->msgId() );
01899     }
01900   }
01901   return items;
01902 }
01903 
01904 //-----------------------------------------------------------------------------
01905 int KMHeaders::firstSelectedMsg() const
01906 {
01907   int selectedMsg = -1;
01908   QListViewItem *item;
01909   for (item = firstChild(); item; item = item->itemBelow())
01910     if (item->isSelected()) {
01911       selectedMsg = (static_cast<KMHeaderItem*>(item))->msgId();
01912       break;
01913     }
01914   return selectedMsg;
01915 }
01916 
01917 //-----------------------------------------------------------------------------
01918 void KMHeaders::nextMessage()
01919 {
01920   QListViewItem *lvi = currentItem();
01921   if (lvi && lvi->itemBelow()) {
01922     clearSelection();
01923     setSelected( lvi, false );
01924     selectNextMessage();
01925     setSelectionAnchor( currentItem() );
01926     ensureCurrentItemVisible();
01927   }
01928 }
01929 
01930 void KMHeaders::selectNextMessage()
01931 {
01932   QListViewItem *lvi = currentItem();
01933   if( lvi ) {
01934     QListViewItem *below = lvi->itemBelow();
01935     QListViewItem *temp = lvi;
01936     if (lvi && below ) {
01937       while (temp) {
01938         temp->firstChild();
01939         temp = temp->parent();
01940       }
01941       lvi->repaint();
01942       /* test to see if we need to unselect messages on back track */
01943       (below->isSelected() ? setSelected(lvi, false) : setSelected(below, true));
01944       setCurrentItem(below);
01945       makeHeaderVisible();
01946       setFolderInfoStatus();
01947     }
01948   }
01949 }
01950 
01951 //-----------------------------------------------------------------------------
01952 void KMHeaders::prevMessage()
01953 {
01954   QListViewItem *lvi = currentItem();
01955   if (lvi && lvi->itemAbove()) {
01956     clearSelection();
01957     setSelected( lvi, false );
01958     selectPrevMessage();
01959     setSelectionAnchor( currentItem() );
01960     ensureCurrentItemVisible();
01961   }
01962 }
01963 
01964 void KMHeaders::selectPrevMessage()
01965 {
01966   QListViewItem *lvi = currentItem();
01967   if( lvi ) {
01968     QListViewItem *above = lvi->itemAbove();
01969     QListViewItem *temp = lvi;
01970 
01971     if (lvi && above) {
01972       while (temp) {
01973         temp->firstChild();
01974         temp = temp->parent();
01975       }
01976       lvi->repaint();
01977       /* test to see if we need to unselect messages on back track */
01978       (above->isSelected() ? setSelected(lvi, false) : setSelected(above, true));
01979       setCurrentItem(above);
01980       makeHeaderVisible();
01981       setFolderInfoStatus();
01982     }
01983   }
01984 }
01985 
01986 //-----------------------------------------------------------------------------
01987 void KMHeaders::findUnreadAux( KMHeaderItem*& item,
01988                                         bool & foundUnreadMessage,
01989                                         bool onlyNew,
01990                                         bool aDirNext )
01991 {
01992   KMMsgBase* msgBase = 0;
01993   KMHeaderItem *lastUnread = 0;
01994   /* itemAbove() is _slow_ */
01995   if (aDirNext)
01996   {
01997     while (item) {
01998       msgBase = mFolder->getMsgBase(item->msgId());
01999       if (!msgBase) continue;
02000       if (msgBase->isUnread() || msgBase->isNew())
02001         foundUnreadMessage = true;
02002 
02003       if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())) break;
02004       if (onlyNew && msgBase->isNew()) break;
02005       item = static_cast<KMHeaderItem*>(item->itemBelow());
02006     }
02007   } else {
02008     KMHeaderItem *newItem = static_cast<KMHeaderItem*>(firstChild());
02009     while (newItem)
02010     {
02011       msgBase = mFolder->getMsgBase(newItem->msgId());
02012       if (!msgBase) continue;
02013       if (msgBase->isUnread() || msgBase->isNew())
02014         foundUnreadMessage = true;
02015       if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())
02016           || onlyNew && msgBase->isNew())
02017         lastUnread = newItem;
02018       if (newItem == item) break;
02019       newItem = static_cast<KMHeaderItem*>(newItem->itemBelow());
02020     }
02021     item = lastUnread;
02022   }
02023 }
02024 
02025 //-----------------------------------------------------------------------------
02026 int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent)
02027 {
02028   KMHeaderItem *item, *pitem;
02029   bool foundUnreadMessage = false;
02030 
02031   if (!mFolder) return -1;
02032   if (!(mFolder->count()) > 0) return -1;
02033 
02034   if ((aStartAt >= 0) && (aStartAt < (int)mItems.size()))
02035     item = mItems[aStartAt];
02036   else {
02037     item = currentHeaderItem();
02038     if (!item) {
02039       if (aDirNext)
02040         item = static_cast<KMHeaderItem*>(firstChild());
02041       else
02042         item = static_cast<KMHeaderItem*>(lastChild());
02043     }
02044     if (!item)
02045       return -1;
02046 
02047     if ( !acceptCurrent )
02048         if (aDirNext)
02049             item = static_cast<KMHeaderItem*>(item->itemBelow());
02050         else
02051             item = static_cast<KMHeaderItem*>(item->itemAbove());
02052   }
02053 
02054   pitem =  item;
02055 
02056   findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
02057 
02058   // We have found an unread item, but it is not necessary the
02059   // first unread item.
02060   //
02061   // Find the ancestor of the unread item closest to the
02062   // root and recursively sort all of that ancestors children.
02063   if (item) {
02064     QListViewItem *next = item;
02065     while (next->parent())
02066       next = next->parent();
02067     next = static_cast<KMHeaderItem*>(next)->firstChildNonConst();
02068     while (next && (next != item))
02069       if (static_cast<KMHeaderItem*>(next)->firstChildNonConst())
02070         next = next->firstChild();
02071       else if (next->nextSibling())
02072         next = next->nextSibling();
02073       else {
02074         while (next && (next != item)) {
02075           next = next->parent();
02076           if (next == item)
02077             break;
02078           if (next && next->nextSibling()) {
02079             next = next->nextSibling();
02080             break;
02081           }
02082         }
02083       }
02084   }
02085 
02086   item = pitem;
02087 
02088   findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
02089   if (item)
02090     return item->msgId();
02091 
02092 
02093   // A kludge to try to keep the number of unread messages in sync
02094   int unread = mFolder->countUnread();
02095   if (((unread == 0) && foundUnreadMessage) ||
02096       ((unread > 0) && !foundUnreadMessage)) {
02097     mFolder->correctUnreadMsgsCount();
02098   }
02099   return -1;
02100 }
02101 
02102 //-----------------------------------------------------------------------------
02103 bool KMHeaders::nextUnreadMessage(bool acceptCurrent)
02104 {
02105   if ( !mFolder || !mFolder->countUnread() ) return false;
02106   int i = findUnread(true, -1, false, acceptCurrent);
02107   if ( i < 0 && GlobalSettings::loopOnGotoUnread() !=
02108         GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
02109   {
02110     KMHeaderItem * first = static_cast<KMHeaderItem*>(firstChild());
02111     if ( first )
02112       i = findUnread(true, first->msgId(), false, acceptCurrent); // from top
02113   }
02114   if ( i < 0 )
02115     return false;
02116   setCurrentMsg(i);
02117   ensureCurrentItemVisible();
02118   return true;
02119 }
02120 
02121 void KMHeaders::ensureCurrentItemVisible()
02122 {
02123     int i = currentItemIndex();
02124     if ((i >= 0) && (i < (int)mItems.size()))
02125         center( contentsX(), itemPos(mItems[i]), 0, 9.0 );
02126 }
02127 
02128 //-----------------------------------------------------------------------------
02129 bool KMHeaders::prevUnreadMessage()
02130 {
02131   if ( !mFolder || !mFolder->countUnread() ) return false;
02132   int i = findUnread(false);
02133   if ( i < 0 && GlobalSettings::loopOnGotoUnread() !=
02134         GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
02135   {
02136     KMHeaderItem * last = static_cast<KMHeaderItem*>(lastItem());
02137     if ( last )
02138       i = findUnread(false, last->msgId() ); // from bottom
02139   }
02140   if ( i < 0 )
02141     return false;
02142   setCurrentMsg(i);
02143   ensureCurrentItemVisible();
02144   return true;
02145 }
02146 
02147 
02148 //-----------------------------------------------------------------------------
02149 void KMHeaders::slotNoDrag()
02150 {
02151   mMousePressed = false;
02152 }
02153 
02154 
02155 //-----------------------------------------------------------------------------
02156 void KMHeaders::makeHeaderVisible()
02157 {
02158   if (currentItem())
02159     ensureItemVisible( currentItem() );
02160 }
02161 
02162 //-----------------------------------------------------------------------------
02163 void KMHeaders::highlightMessage(QListViewItem* lvi, bool markitread)
02164 {
02165   // shouldnt happen but will crash if it does
02166   if (lvi && !lvi->isSelectable()) return;
02167 
02168   KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi);
02169   if (lvi != mPrevCurrent) {
02170     if (mPrevCurrent && mFolder)
02171     {
02172       KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId());
02173       if (prevMsg && mReaderWindowActive)
02174       {
02175         mFolder->ignoreJobsForMessage(prevMsg);
02176         if (!prevMsg->transferInProgress())
02177           mFolder->unGetMsg(mPrevCurrent->msgId());
02178       }
02179     }
02180     mPrevCurrent = item;
02181   }
02182 
02183   if (!item)
02184   {
02185     emit selected( 0 ); return;
02186   }
02187 
02188   int idx = item->msgId();
02189   if (mReaderWindowActive)
02190   {
02191     KMMessage *msg = mFolder->getMsg(idx);
02192     if (!msg )
02193     {
02194       emit selected( 0 );
02195       mPrevCurrent = 0;
02196       return;
02197     }
02198   }
02199 
02200   BroadcastStatus::instance()->setStatusMsg("");
02201   if (markitread && idx >= 0) setMsgRead(idx);
02202   mItems[idx]->irefresh();
02203   mItems[idx]->repaint();
02204   emit selected(mFolder->getMsg(idx));
02205   setFolderInfoStatus();
02206 }
02207 
02208 void KMHeaders::resetCurrentTime()
02209 {
02210     mDate.reset();
02211     QTimer::singleShot( 1000, this, SLOT( resetCurrentTime() ) );
02212 }
02213 
02214 //-----------------------------------------------------------------------------
02215 void KMHeaders::selectMessage(QListViewItem* lvi)
02216 {
02217   KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi);
02218   if (!item)
02219     return;
02220 
02221   int idx = item->msgId();
02222   KMMessage *msg = mFolder->getMsg(idx);
02223   if (!msg->transferInProgress())
02224   {
02225     emit activated(mFolder->getMsg(idx));
02226   }
02227 
02228 //  if (kmkernel->folderIsDraftOrOutbox(mFolder))
02229 //    setOpen(lvi, !lvi->isOpen());
02230 }
02231 
02232 
02233 //-----------------------------------------------------------------------------
02234 void KMHeaders::updateMessageList( bool set_selection, bool forceJumpToUnread )
02235 {
02236   mPrevCurrent = 0;
02237   noRepaint = true;
02238   clear();
02239   noRepaint = false;
02240   KListView::setSorting( mSortCol, !mSortDescending );
02241   if (!mFolder) {
02242     mItems.resize(0);
02243     repaint();
02244     return;
02245   }
02246   readSortOrder( set_selection, forceJumpToUnread );
02247   emit messageListUpdated();
02248 }
02249 
02250 
02251 //-----------------------------------------------------------------------------
02252 // KMail Header list selection/navigation description
02253 //
02254 // If the selection state changes the reader window is updated to show the
02255 // current item.
02256 //
02257 // (The selection state of a message or messages can be changed by pressing
02258 //  space, or normal/shift/cntrl clicking).
02259 //
02260 // The following keyboard events are supported when the messages headers list
02261 // has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End,
02262 // Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do
02263 // not change the selection state.
02264 //
02265 // Exception: When shift selecting either with mouse or key press the reader
02266 // window is updated regardless of whether of not the selection has changed.
02267 void KMHeaders::keyPressEvent( QKeyEvent * e )
02268 {
02269     bool cntrl = (e->state() & ControlButton );
02270     bool shft = (e->state() & ShiftButton );
02271     QListViewItem *cur = currentItem();
02272 
02273     if (!e || !firstChild())
02274       return;
02275 
02276     // If no current item, make some first item current when a key is pressed
02277     if (!cur) {
02278       setCurrentItem( firstChild() );
02279       setSelectionAnchor( currentItem() );
02280       return;
02281     }
02282 
02283     // Handle space key press
02284     if (cur->isSelectable() && e->ascii() == ' ' ) {
02285         setSelected( cur, !cur->isSelected() );
02286         highlightMessage( cur, false);
02287         return;
02288     }
02289 
02290     if (cntrl) {
02291       if (!shft)
02292         disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
02293                    this,SLOT(highlightMessage(QListViewItem*)));
02294       switch (e->key()) {
02295       case Key_Down:
02296       case Key_Up:
02297       case Key_Home:
02298       case Key_End:
02299       case Key_Next:
02300       case Key_Prior:
02301       case Key_Escape:
02302         KListView::keyPressEvent( e );
02303       }
02304       if (!shft)
02305         connect(this,SIGNAL(currentChanged(QListViewItem*)),
02306                 this,SLOT(highlightMessage(QListViewItem*)));
02307     }
02308 }
02309 
02310 //-----------------------------------------------------------------------------
02311 // Handle RMB press, show pop up menu
02312 void KMHeaders::rightButtonPressed( QListViewItem *lvi, const QPoint &, int )
02313 {
02314   if (!lvi)
02315     return;
02316 
02317   if (!(lvi->isSelected())) {
02318     clearSelection();
02319   }
02320   setSelected( lvi, true );
02321   slotRMB();
02322 }
02323 
02324 //-----------------------------------------------------------------------------
02325 void KMHeaders::contentsMousePressEvent(QMouseEvent* e)
02326 {
02327   mPressPos = e->pos();
02328   QListViewItem *lvi = itemAt( contentsToViewport( e->pos() ));
02329   bool wasSelected = false;
02330   bool rootDecoClicked = false;
02331   if (lvi) {
02332      wasSelected = lvi->isSelected();
02333      rootDecoClicked =
02334         (  mPressPos.x() <= header()->cellPos(  header()->mapToActual(  0 ) ) +
02335            treeStepSize() * (  lvi->depth() + (  rootIsDecorated() ? 1 : 0 ) ) + itemMargin() )
02336         && (  mPressPos.x() >= header()->cellPos(  header()->mapToActual(  0 ) ) );
02337 
02338      if ( rootDecoClicked ) {
02339         // Check if our item is the parent of a closed thread and if so, if the root
02340         // decoration of the item was clicked (i.e. the +/- sign) which would expand
02341         // the thread. In that case, deselect all children, so opening the thread
02342         // doesn't cause a flicker.
02343         if ( !lvi->isOpen() && lvi->firstChild() ) {
02344            QListViewItem *nextRoot = lvi->itemBelow();
02345            QListViewItemIterator it( lvi->firstChild() );
02346            for( ; (*it) != nextRoot; ++it )
02347               (*it)->setSelected( false );
02348         }
02349      }
02350   }
02351 
02352   // let klistview do it's thing, expanding/collapsing, selection/deselection
02353   KListView::contentsMousePressEvent(e);
02354   /* QListView's shift-select selects also invisible items. Until that is
02355      fixed, we have to deselect hidden items here manually, so the quick
02356      search doesn't mess things up. */
02357   if ( e->state() & ShiftButton ) {
02358     QListViewItemIterator it( this, QListViewItemIterator::Invisible );
02359     while ( it.current() ) {
02360       it.current()->setSelected( false );
02361       ++it;
02362     }
02363   }
02364 
02365   if ( rootDecoClicked ) {
02366       // select the thread's children after closing if the parent is selected
02367      if ( lvi && !lvi->isOpen() && lvi->isSelected() )
02368         setSelected( lvi, true );
02369   }
02370 
02371   if ( lvi && !rootDecoClicked ) {
02372     if ( lvi != currentItem() )
02373       highlightMessage( lvi );
02374     /* Explicitely set selection state. This is necessary because we want to
02375      * also select all children of closed threads when the parent is selected. */
02376 
02377     // unless ctrl mask, set selected if it isn't already
02378     if ( !( e->state() & ControlButton ) && !wasSelected )
02379       setSelected( lvi, true );
02380     // if ctrl mask, toggle selection
02381     if ( e->state() & ControlButton )
02382       setSelected( lvi, !wasSelected );
02383 
02384     if ((e->button() == LeftButton) )
02385       mMousePressed = true;
02386   }
02387 }
02388 
02389 //-----------------------------------------------------------------------------
02390 void KMHeaders::contentsMouseReleaseEvent(QMouseEvent* e)
02391 {
02392   if (e->button() != RightButton)
02393     KListView::contentsMouseReleaseEvent(e);
02394 
02395   mMousePressed = false;
02396 }
02397 
02398 //-----------------------------------------------------------------------------
02399 void KMHeaders::contentsMouseMoveEvent( QMouseEvent* e )
02400 {
02401   if (mMousePressed &&
02402       (e->pos() - mPressPos).manhattanLength() > KGlobalSettings::dndEventDelay()) {
02403     mMousePressed = false;
02404     QListViewItem *item = itemAt( contentsToViewport(mPressPos) );
02405     if ( item ) {
02406       MailList mailList;
02407       unsigned int count = 0;
02408       for( QListViewItemIterator it(this); it.current(); it++ )
02409         if( it.current()->isSelected() ) {
02410           KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current());
02411       KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
02412       MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(),
02413                    msg->subject(), msg->fromStrip(),
02414                    msg->toStrip(), msg->date() );
02415       mailList.append( mailSummary );
02416       ++count;
02417         }
02418       MailListDrag *d = new MailListDrag( mailList, viewport() );
02419 
02420       // Set pixmap
02421       QPixmap pixmap;
02422       if( count == 1 )
02423         pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) );
02424       else
02425         pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) );
02426 
02427       // Calculate hotspot (as in Konqueror)
02428       if( !pixmap.isNull() ) {
02429         QPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 );
02430         d->setPixmap( pixmap, hotspot );
02431       }
02432       d->drag();
02433     }
02434   }
02435 }
02436 
02437 void KMHeaders::highlightMessage(QListViewItem* i)
02438 {
02439     highlightMessage( i, false );
02440 }
02441 
02442 //-----------------------------------------------------------------------------
02443 void KMHeaders::slotRMB()
02444 {
02445   if (!topLevelWidget()) return; // safe bet
02446 
02447   QPopupMenu *menu = new QPopupMenu(this);
02448 
02449   mMenuToFolder.clear();
02450 
02451   mOwner->updateMessageMenu();
02452 
02453   bool out_folder = kmkernel->folderIsDraftOrOutbox(mFolder);
02454   if ( out_folder )
02455      mOwner->editAction()->plug(menu);
02456   else {
02457      // show most used actions
02458      mOwner->replyAction()->plug(menu);
02459      mOwner->replyAllAction()->plug(menu);
02460      mOwner->replyAuthorAction()->plug( menu );
02461      mOwner->replyListAction()->plug(menu);
02462      mOwner->forwardMenu()->plug(menu);
02463      mOwner->bounceAction()->plug(menu);
02464      mOwner->sendAgainAction()->plug(menu);
02465   }
02466   menu->insertSeparator();
02467 
02468   QPopupMenu *msgCopyMenu = new QPopupMenu(menu);
02469   KMCopyCommand::folderToPopupMenu( false, this, &mMenuToFolder, msgCopyMenu );
02470   menu->insertItem(i18n("&Copy To"), msgCopyMenu);
02471 
02472   if ( mFolder->isReadOnly() ) {
02473     int id = menu->insertItem( i18n("&Move To") );
02474     menu->setItemEnabled( id, false );
02475   } else {
02476     QPopupMenu *msgMoveMenu = new QPopupMenu(menu);
02477     KMMoveCommand::folderToPopupMenu( true, this, &mMenuToFolder, msgMoveMenu );
02478     menu->insertItem(i18n("&Move To"), msgMoveMenu);
02479   }
02480 
02481   if ( !out_folder ) {
02482     mOwner->statusMenu()->plug( menu ); // Mark Message menu
02483     if ( mOwner->threadStatusMenu()->isEnabled() ) {
02484       mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu
02485     }
02486   }
02487 
02488   if (mOwner->watchThreadAction()->isEnabled() ) {
02489     menu->insertSeparator();
02490     mOwner->watchThreadAction()->plug(menu);
02491     mOwner->ignoreThreadAction()->plug(menu);
02492   }
02493   menu->insertSeparator();
02494   mOwner->trashAction()->plug(menu);
02495   mOwner->deleteAction()->plug(menu);
02496 
02497   menu->insertSeparator();
02498   mOwner->saveAsAction()->plug(menu);
02499   mOwner->saveAttachmentsAction()->plug(menu);
02500   mOwner->printAction()->plug(menu);
02501 
02502   if ( !out_folder ) {
02503     menu->insertSeparator();
02504     mOwner->action("apply_filters")->plug(menu);
02505     mOwner->filterMenu()->plug( menu ); // Create Filter menu
02506   }
02507 
02508   mOwner->action("apply_filter_actions")->plug(menu);
02509 
02510   KAcceleratorManager::manage(menu);
02511   kmkernel->setContextMenuShown( true );
02512   menu->exec(QCursor::pos(), 0);
02513   kmkernel->setContextMenuShown( false );
02514   delete menu;
02515 }
02516 
02517 //-----------------------------------------------------------------------------
02518 KMMessage* KMHeaders::currentMsg()
02519 {
02520   KMHeaderItem *hi = currentHeaderItem();
02521   if (!hi)
02522     return 0;
02523   else
02524     return mFolder->getMsg(hi->msgId());
02525 }
02526 
02527 //-----------------------------------------------------------------------------
02528 KMHeaderItem* KMHeaders::currentHeaderItem()
02529 {
02530   return static_cast<KMHeaderItem*>(currentItem());
02531 }
02532 
02533 //-----------------------------------------------------------------------------
02534 int KMHeaders::currentItemIndex()
02535 {
02536   KMHeaderItem* item = currentHeaderItem();
02537   if (item)
02538     return item->msgId();
02539   else
02540     return -1;
02541 }
02542 
02543 //-----------------------------------------------------------------------------
02544 void KMHeaders::setCurrentItemByIndex(int msgIdx)
02545 {
02546   if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) {
02547     clearSelection();
02548     bool unchanged = (currentItem() == mItems[msgIdx]);
02549     setCurrentItem( mItems[msgIdx] );
02550     setSelected( mItems[msgIdx], true );
02551     setSelectionAnchor( currentItem() );
02552     if (unchanged)
02553        highlightMessage( mItems[msgIdx], false);
02554   }
02555 }
02556 
02557 //-----------------------------------------------------------------------------
02558 int KMHeaders::topItemIndex()
02559 {
02560   KMHeaderItem *item = static_cast<KMHeaderItem*>(itemAt(QPoint(1,1)));
02561   if (item)
02562     return item->msgId();
02563   else
02564     return -1;
02565 }
02566 
02567 // If sorting ascending by date/ooa then try to scroll list when new mail
02568 // arrives to show it, but don't scroll current item out of view.
02569 void KMHeaders::showNewMail()
02570 {
02571   if (mSortCol != mPaintInfo.dateCol)
02572     return;
02573  for( int i = 0; i < (int)mItems.size(); ++i)
02574    if (mFolder->getMsgBase(i)->isNew()) {
02575      if (!mSortDescending)
02576        setTopItemByIndex( currentItemIndex() );
02577      break;
02578    }
02579 }
02580 
02581 //-----------------------------------------------------------------------------
02582 void KMHeaders::setTopItemByIndex( int aMsgIdx)
02583 {
02584   int msgIdx = aMsgIdx;
02585   if (msgIdx < 0)
02586     msgIdx = 0;
02587   else if (msgIdx >= (int)mItems.size())
02588     msgIdx = mItems.size() - 1;
02589   if ((msgIdx >= 0) && (msgIdx < (int)mItems.size()))
02590     setContentsPos( 0, itemPos( mItems[msgIdx] ));
02591 }
02592 
02593 //-----------------------------------------------------------------------------
02594 void KMHeaders::setNestedOverride( bool override )
02595 {
02596   mSortInfo.dirty = true;
02597   mNestedOverride = override;
02598   setRootIsDecorated( nestingPolicy != AlwaysOpen
02599                       && isThreaded() );
02600   QString sortFile = mFolder->indexLocation() + ".sorted";
02601   unlink(QFile::encodeName(sortFile));
02602   reset();
02603 }
02604 
02605 //-----------------------------------------------------------------------------
02606 void KMHeaders::setSubjectThreading( bool aSubjThreading )
02607 {
02608   mSortInfo.dirty = true;
02609   mSubjThreading = aSubjThreading;
02610   QString sortFile = mFolder->indexLocation() + ".sorted";
02611   unlink(QFile::encodeName(sortFile));
02612   reset();
02613 }
02614 
02615 //-----------------------------------------------------------------------------
02616 void KMHeaders::setOpen( QListViewItem *item, bool open )
02617 {
02618   if ((nestingPolicy != AlwaysOpen)|| open)
02619       ((KMHeaderItem*)item)->setOpenRecursive( open );
02620 }
02621 
02622 //-----------------------------------------------------------------------------
02623 const KMMsgBase* KMHeaders::getMsgBaseForItem( const QListViewItem *item ) const
02624 {
02625   const KMHeaderItem *hi = static_cast<const KMHeaderItem *> ( item );
02626   return mFolder->getMsgBase( hi->msgId() );
02627 }
02628 
02629 //-----------------------------------------------------------------------------
02630 void KMHeaders::setSorting( int column, bool ascending )
02631 {
02632   if (column != -1) {
02633   // carsten: really needed?
02634 //    if (column != mSortCol)
02635 //      setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol ));
02636     if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied
02637         QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
02638         mSortInfo.dirty = true;
02639     }
02640 
02641     mSortCol = column;
02642     mSortDescending = !ascending;
02643 
02644     if (!ascending && (column == mPaintInfo.dateCol))
02645       mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival;
02646 
02647     if (!ascending && (column == mPaintInfo.subCol))
02648       mPaintInfo.status = !mPaintInfo.status;
02649 
02650     QString colText = i18n( "Date" );
02651     if (mPaintInfo.orderOfArrival)
02652       colText = i18n( "Date (Order of Arrival)" );
02653     setColumnText( mPaintInfo.dateCol, colText);
02654 
02655     colText = i18n( "Subject" );
02656     if (mPaintInfo.status)
02657       colText = colText + i18n( " (Status)" );
02658     setColumnText( mPaintInfo.subCol, colText);
02659   }
02660   KListView::setSorting( column, ascending );
02661   ensureCurrentItemVisible();
02662   // Make sure the config and .sorted file are updated, otherwise stale info
02663   // is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ).
02664   if ( mFolder ) {
02665     writeFolderConfig();
02666     writeSortOrder();
02667   }
02668 }
02669 
02670 //Flatten the list and write it to disk
02671 static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid,
02672                               int parent_id, QString key,
02673                               bool update_discover=true)
02674 {
02675   unsigned long msgSerNum;
02676   unsigned long parentSerNum;
02677   msgSerNum = kmkernel->msgDict()->getMsgSerNum( folder, msgid );
02678   if (parent_id >= 0)
02679     parentSerNum = kmkernel->msgDict()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED;
02680   else
02681     parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED);
02682 
02683   fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream);
02684   fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream);
02685   Q_INT32 len = key.length() * sizeof(QChar);
02686   fwrite(&len, sizeof(len), 1, sortStream);
02687   if (len)
02688     fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream);
02689 
02690   if (update_discover) {
02691     //update the discovered change count
02692       Q_INT32 discovered_count = 0;
02693       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
02694       fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
02695       discovered_count++;
02696       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
02697       fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02698   }
02699 }
02700 
02701 void KMHeaders::folderCleared()
02702 {
02703     mSortCacheItems.clear(); //autoDelete is true
02704     mSubjectLists.clear();
02705     mImperfectlyThreadedList.clear();
02706     mPrevCurrent = 0;
02707     emit selected(0);
02708 }
02709 
02710 bool KMHeaders::writeSortOrder()
02711 {
02712   QString sortFile = KMAIL_SORT_FILE(mFolder);
02713 
02714   if (!mSortInfo.dirty) {
02715     struct stat stat_tmp;
02716     if(stat(QFile::encodeName(sortFile), &stat_tmp) == -1) {
02717         mSortInfo.dirty = true;
02718     }
02719   }
02720   if (mSortInfo.dirty) {
02721     if (!mFolder->count()) {
02722       // Folder is empty now, remove the sort file.
02723       unlink(QFile::encodeName(sortFile));
02724       return true;
02725     }
02726     QString tempName = sortFile + ".temp";
02727     unlink(QFile::encodeName(tempName));
02728     FILE *sortStream = fopen(QFile::encodeName(tempName), "w");
02729     if (!sortStream)
02730       return false;
02731 
02732     mSortInfo.ascending = !mSortDescending;
02733     mSortInfo.dirty = false;
02734     mSortInfo.column = mSortCol;
02735     fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION);
02736     //magic header information
02737     Q_INT32 byteOrder = 0x12345678;
02738     Q_INT32 column = mSortCol;
02739     Q_INT32 ascending= !mSortDescending;
02740     Q_INT32 threaded = isThreaded();
02741     Q_INT32 appended=0;
02742     Q_INT32 discovered_count = 0;
02743     Q_INT32 sorted_count=0;
02744     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
02745     fwrite(&column, sizeof(column), 1, sortStream);
02746     fwrite(&ascending, sizeof(ascending), 1, sortStream);
02747     fwrite(&threaded, sizeof(threaded), 1, sortStream);
02748     fwrite(&appended, sizeof(appended), 1, sortStream);
02749     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02750     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
02751 
02752     QPtrStack<KMHeaderItem> items;
02753     {
02754       QPtrStack<QListViewItem> s;
02755       for (QListViewItem * i = firstChild(); i; ) {
02756         items.push((KMHeaderItem *)i);
02757         if ( i->firstChild() ) {
02758           s.push( i );
02759           i = i->firstChild();
02760         } else if( i->nextSibling()) {
02761           i = i->nextSibling();
02762         } else {
02763             for(i=0; !i && s.count(); i = s.pop()->nextSibling());
02764         }
02765       }
02766     }
02767 
02768     KMMsgBase *kmb;
02769     while(KMHeaderItem *i = items.pop()) {
02770       int parent_id = -1; //no parent, top level
02771       if (threaded) {
02772         kmb = mFolder->getMsgBase( i->mMsgId );
02773         assert(kmb); // I have seen 0L come out of this, called from
02774                    // KMHeaders::setFolder(0xgoodpointer, false);
02775         QString replymd5 = kmb->replyToIdMD5();
02776         QString replyToAuxId = kmb->replyToAuxIdMD5();
02777         KMSortCacheItem *p = NULL;
02778         if(!replymd5.isEmpty())
02779           p = mSortCacheItems[replymd5];
02780 
02781         if (p)
02782           parent_id = p->id();
02783         // We now have either found a parent, or set it to -1, which means that
02784         // it will be reevaluated when a message is added, for example. If there
02785         // is no replyToId and no replyToAuxId and the message is not prefixed,
02786         // this message is top level, and will always be, so no need to
02787         // reevaluate it.
02788         if (replymd5.isEmpty()
02789             && replyToAuxId.isEmpty()
02790             && !kmb->subjectIsPrefixed() )
02791           parent_id = -2;
02792         // FIXME also mark messages with -1 as -2 a certain amount of time after
02793         // their arrival, since it becomes very unlikely that a new parent for
02794         // them will show up. (Ingo suggests a month.) -till
02795       }
02796       internalWriteItem(sortStream, mFolder, i->mMsgId, parent_id,
02797                         i->key(mSortCol, !mSortDescending), false);
02798       //double check for magic headers
02799       sorted_count++;
02800     }
02801 
02802     //magic header twice, case they've changed
02803     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET);
02804     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
02805     fwrite(&column, sizeof(column), 1, sortStream);
02806     fwrite(&ascending, sizeof(ascending), 1, sortStream);
02807     fwrite(&threaded, sizeof(threaded), 1, sortStream);
02808     fwrite(&appended, sizeof(appended), 1, sortStream);
02809     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02810     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
02811     if (sortStream && ferror(sortStream)) {
02812         fclose(sortStream);
02813         unlink(QFile::encodeName(sortFile));
02814         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
02815         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
02816         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
02817     }
02818     fclose(sortStream);
02819     ::rename(QFile::encodeName(tempName), QFile::encodeName(sortFile));
02820   }
02821 
02822   return true;
02823 }
02824 
02825 void KMHeaders::appendItemToSortFile(KMHeaderItem *khi)
02826 {
02827   QString sortFile = KMAIL_SORT_FILE(mFolder);
02828   if(FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+")) {
02829     int parent_id = -1; //no parent, top level
02830 
02831     if (isThreaded()) {
02832       KMSortCacheItem *sci = khi->sortCacheItem();
02833       KMMsgBase *kmb = mFolder->getMsgBase( khi->mMsgId );
02834       if(sci->parent() && !sci->isImperfectlyThreaded())
02835         parent_id = sci->parent()->id();
02836       else if(kmb->replyToIdMD5().isEmpty()
02837            && kmb->replyToAuxIdMD5().isEmpty()
02838            && !kmb->subjectIsPrefixed())
02839         parent_id = -2;
02840     }
02841 
02842     internalWriteItem(sortStream, mFolder, khi->mMsgId, parent_id,
02843                       khi->key(mSortCol, !mSortDescending), false);
02844 
02845     //update the appended flag FIXME obsolete?
02846     Q_INT32 appended = 1;
02847     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
02848     fwrite(&appended, sizeof(appended), 1, sortStream);
02849     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
02850 
02851     if (sortStream && ferror(sortStream)) {
02852         fclose(sortStream);
02853         unlink(QFile::encodeName(sortFile));
02854         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
02855         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
02856         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
02857     }
02858     fclose(sortStream);
02859   } else {
02860     mSortInfo.dirty = true;
02861   }
02862 }
02863 
02864 void KMHeaders::dirtySortOrder(int column)
02865 {
02866     mSortInfo.dirty = true;
02867     QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
02868     setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true);
02869 }
02870 void KMSortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder,
02871                                       bool waiting_for_parent, bool update_discover)
02872 {
02873     if(mSortOffset == -1) {
02874         fseek(sortStream, 0, SEEK_END);
02875         mSortOffset = ftell(sortStream);
02876     } else {
02877         fseek(sortStream, mSortOffset, SEEK_SET);
02878     }
02879 
02880     int parent_id = -1;
02881     if(!waiting_for_parent) {
02882         if(mParent && !isImperfectlyThreaded())
02883             parent_id = mParent->id();
02884     }
02885     internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover);
02886 }
02887 
02888 static bool compare_ascending = false;
02889 static bool compare_toplevel = true;
02890 static int compare_KMSortCacheItem(const void *s1, const void *s2)
02891 {
02892     if ( !s1 || !s2 )
02893         return 0;
02894     KMSortCacheItem **b1 = (KMSortCacheItem **)s1;
02895     KMSortCacheItem **b2 = (KMSortCacheItem **)s2;
02896     int ret = (*b1)->key().compare((*b2)->key());
02897     if(compare_ascending || !compare_toplevel)
02898         ret = -ret;
02899     return ret;
02900 }
02901 
02902 
02903 void KMHeaders::buildThreadingTree( QMemArray<KMSortCacheItem *> sortCache )
02904 {
02905     mSortCacheItems.clear();
02906     mSortCacheItems.resize( mFolder->count() * 2 );
02907 
02908     // build a dict of all message id's
02909     for(int x = 0; x < mFolder->count(); x++) {
02910         KMMsgBase *mi = mFolder->getMsgBase(x);
02911         QString md5 = mi->msgIdMD5();
02912         if(!md5.isEmpty())
02913             mSortCacheItems.replace(md5, sortCache[x]);
02914     }
02915 }
02916 
02917 
02918 void KMHeaders::buildSubjectThreadingTree( QMemArray<KMSortCacheItem *> sortCache )
02919 {
02920     mSubjectLists.clear();  // autoDelete is true
02921     mSubjectLists.resize( mFolder->count() * 2 );
02922 
02923     for(int x = 0; x < mFolder->count(); x++) {
02924         // Only a lot items that are now toplevel
02925         if ( sortCache[x]->parent()
02926           && sortCache[x]->parent()->id() != -666 ) continue;
02927         KMMsgBase *mi = mFolder->getMsgBase(x);
02928         QString subjMD5 = mi->strippedSubjectMD5();
02929         if (subjMD5.isEmpty()) {
02930             mFolder->getMsgBase(x)->initStrippedSubjectMD5();
02931             subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
02932         }
02933         if( subjMD5.isEmpty() ) continue;
02934 
02935         /* For each subject, keep a list of items with that subject
02936          * (stripped of prefixes) sorted by date. */
02937         if (!mSubjectLists.find(subjMD5))
02938             mSubjectLists.insert(subjMD5, new QPtrList<KMSortCacheItem>());
02939         /* Insertion sort by date. These lists are expected to be very small.
02940          * Also, since the messages are roughly ordered by date in the store,
02941          * they should mostly be prepended at the very start, so insertion is
02942          * cheap. */
02943         int p=0;
02944         for (QPtrListIterator<KMSortCacheItem> it(*mSubjectLists[subjMD5]);
02945                 it.current(); ++it) {
02946             KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
02947             if ( mb->date() < mi->date())
02948                 break;
02949             p++;
02950         }
02951         mSubjectLists[subjMD5]->insert( p, sortCache[x]);
02952     }
02953 }
02954 
02955 
02956 KMSortCacheItem* KMHeaders::findParent(KMSortCacheItem *item)
02957 {
02958     KMSortCacheItem *parent = NULL;
02959     if (!item) return parent;
02960     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
02961     QString replyToIdMD5 = msg->replyToIdMD5();
02962     item->setImperfectlyThreaded(true);
02963     /* First, try if the message our Reply-To header points to
02964      * is available to thread below. */
02965     if(!replyToIdMD5.isEmpty()) {
02966         parent = mSortCacheItems[replyToIdMD5];
02967         if (parent)
02968             item->setImperfectlyThreaded(false);
02969     }
02970     if (!parent) {
02971         // If we dont have a replyToId, or if we have one and the
02972         // corresponding message is not in this folder, as happens
02973         // if you keep your outgoing messages in an OUTBOX, for
02974         // example, try the list of references, because the second
02975         // to last will likely be in this folder. replyToAuxIdMD5
02976         // contains the second to last one.
02977         QString  ref = msg->replyToAuxIdMD5();
02978         if (!ref.isEmpty())
02979             parent = mSortCacheItems[ref];
02980     }
02981     return parent;
02982 }
02983 
02984 KMSortCacheItem* KMHeaders::findParentBySubject(KMSortCacheItem *item)
02985 {
02986     KMSortCacheItem *parent = NULL;
02987     if (!item) return parent;
02988 
02989     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
02990 
02991     // Let's try by subject, but only if the  subject is prefixed.
02992     // This is necessary to make for example cvs commit mailing lists
02993     // work as expected without having to turn threading off alltogether.
02994     if (!msg->subjectIsPrefixed())
02995         return parent;
02996 
02997     QString replyToIdMD5 = msg->replyToIdMD5();
02998     QString subjMD5 = msg->strippedSubjectMD5();
02999     if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) {
03000         /* Iterate over the list of potential parents with the same
03001          * subject, and take the closest one by date. */
03002         for (QPtrListIterator<KMSortCacheItem> it2(*mSubjectLists[subjMD5]);
03003                 it2.current(); ++it2) {
03004             KMMsgBase *mb = mFolder->getMsgBase((*it2)->id());
03005             if ( !mb ) return parent;
03006             // make sure it's not ourselves
03007             if ( item == (*it2) ) continue;
03008             int delta = msg->date() - mb->date();
03009             // delta == 0 is not allowed, to avoid circular threading
03010             // with duplicates.
03011             if (delta > 0 ) {
03012                 // Don't use parents more than 6 weeks older than us.
03013                 if (delta < 3628899)
03014                     parent = (*it2);
03015                 break;
03016             }
03017         }
03018     }
03019     return parent;
03020 }
03021 
03022 bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread )
03023 {
03024     //all cases
03025     Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended;
03026     Q_INT32 deleted_count = 0;
03027     bool unread_exists = false;
03028     bool jumpToUnread = GlobalSettings::jumpToUnread() ||
03029                         forceJumpToUnread;
03030     QMemArray<KMSortCacheItem *> sortCache(mFolder->count());
03031     KMSortCacheItem root;
03032     root.setId(-666); //mark of the root!
03033     bool error = false;
03034 
03035     //threaded cases
03036     QPtrList<KMSortCacheItem> unparented;
03037     mImperfectlyThreadedList.clear();
03038 
03039     //cleanup
03040     mItems.fill( 0, mFolder->count() );
03041     sortCache.fill( 0 );
03042 
03043     QString sortFile = KMAIL_SORT_FILE(mFolder);
03044     FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+");
03045     mSortInfo.fakeSort = 0;
03046 
03047     if(sortStream) {
03048         mSortInfo.fakeSort = 1;
03049         int version = 0;
03050         if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1)
03051           version = -1;
03052         if(version == KMAIL_SORT_VERSION) {
03053           Q_INT32 byteOrder = 0;
03054           fread(&byteOrder, sizeof(byteOrder), 1, sortStream);
03055           if (byteOrder == 0x12345678)
03056           {
03057             fread(&column, sizeof(column), 1, sortStream);
03058             fread(&ascending, sizeof(ascending), 1, sortStream);
03059             fread(&threaded, sizeof(threaded), 1, sortStream);
03060             fread(&appended, sizeof(appended), 1, sortStream);
03061             fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
03062             fread(&sorted_count, sizeof(sorted_count), 1, sortStream);
03063 
03064             //Hackyness to work around qlistview problems
03065             KListView::setSorting(-1);
03066             header()->setSortIndicator(column, ascending);
03067             QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
03068             //setup mSortInfo here now, as above may change it
03069             mSortInfo.dirty = false;
03070             mSortInfo.column = (short)column;
03071             mSortInfo.ascending = (compare_ascending = ascending);
03072 
03073             KMSortCacheItem *item;
03074             unsigned long serNum, parentSerNum;
03075             int id, len, parent, x;
03076             QChar *tmp_qchar = 0;
03077             int tmp_qchar_len = 0;
03078             const int mFolderCount = mFolder->count();
03079             QString key;
03080 
03081             CREATE_TIMER(parse);
03082             START_TIMER(parse);
03083             for(x = 0; !feof(sortStream) && !error; x++) {
03084                 off_t offset = ftell(sortStream);
03085                 KMFolder *folder;
03086                 //parse
03087                 if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end
03088                    !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) ||
03089                    !fread(&len, sizeof(len), 1, sortStream)) {
03090                     break;
03091                 }
03092                 if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) {
03093                     kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl;
03094                     error = true;
03095                     continue;
03096                 }
03097                 if(len) {
03098                     if(len > tmp_qchar_len) {
03099                         tmp_qchar = (QChar *)realloc(tmp_qchar, len);
03100                         tmp_qchar_len = len;
03101                     }
03102                     if(!fread(tmp_qchar, len, 1, sortStream))
03103                         break;
03104                     key = QString(tmp_qchar, len / 2);
03105                 } else {
03106                     key = QString(""); //yuck
03107                 }
03108 
03109                 kmkernel->msgDict()->getLocation(serNum, &folder, &id);
03110                 if (folder != mFolder) {
03111                     ++deleted_count;
03112                     continue;
03113                 }
03114                 if (parentSerNum < KMAIL_RESERVED) {
03115                     parent = (int)parentSerNum - KMAIL_RESERVED;
03116                 } else {
03117                     kmkernel->msgDict()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent);
03118                     if (folder != mFolder)
03119                         parent = -1;
03120                 }
03121                 if ((id < 0) || (id >= mFolderCount) ||
03122                     (parent < -2) || (parent >= mFolderCount)) { // sanity checking
03123                     kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl;
03124                     error = true;
03125                     continue;
03126                 }
03127 
03128                 if ((item=sortCache[id])) {
03129                     if (item->id() != -1) {
03130                         kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl;
03131                         error = true;
03132                         continue;
03133                     }
03134                     item->setKey(key);
03135                     item->setId(id);
03136                     item->setOffset(offset);
03137                 } else {
03138                     item = sortCache[id] = new KMSortCacheItem(id, key, offset);
03139                 }
03140                 if (threaded && parent != -2) {
03141                     if(parent == -1) {
03142                         unparented.append(item);
03143                         root.addUnsortedChild(item);
03144                     } else {
03145                         if( ! sortCache[parent] )
03146                             sortCache[parent] = new KMSortCacheItem;
03147                         sortCache[parent]->addUnsortedChild(item);
03148                     }
03149                 } else {
03150                     if(x < sorted_count )
03151                         root.addSortedChild(item);
03152                     else {
03153                         root.addUnsortedChild(item);
03154                     }
03155                 }
03156             }
03157             if (error || (x != sorted_count + discovered_count)) {// sanity check
03158                 kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl;
03159                 fclose(sortStream);
03160                 sortStream = 0;
03161             }
03162 
03163             if(tmp_qchar)
03164                 free(tmp_qchar);
03165             END_TIMER(parse);
03166             SHOW_TIMER(parse);
03167           }
03168           else {
03169               fclose(sortStream);
03170               sortStream = 0;
03171           }
03172         } else {
03173             fclose(sortStream);
03174             sortStream = 0;
03175         }
03176     }
03177 
03178     if (!sortStream) {
03179         mSortInfo.dirty = true;
03180         mSortInfo.column = column = mSortCol;
03181         mSortInfo.ascending = ascending = !mSortDescending;
03182         threaded = (isThreaded());
03183         sorted_count = discovered_count = appended = 0;
03184         KListView::setSorting( mSortCol, !mSortDescending );
03185     }
03186     //fill in empty holes
03187     if((sorted_count + discovered_count - deleted_count) < mFolder->count()) {
03188         CREATE_TIMER(holes);
03189         START_TIMER(holes);
03190         KMMsgBase *msg = 0;
03191         for(int x = 0; x < mFolder->count(); x++) {
03192             if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) {
03193                 int sortOrder = column;
03194                 if (mPaintInfo.orderOfArrival)
03195                     sortOrder |= (1 << 6);
03196                 if (mPaintInfo.status)
03197                     sortOrder |= (1 << 5);
03198                 sortCache[x] = new KMSortCacheItem(
03199                     x, KMHeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder ));
03200                 if(threaded)
03201                     unparented.append(sortCache[x]);
03202                 else
03203                     root.addUnsortedChild(sortCache[x]);
03204                 if(sortStream)
03205                     sortCache[x]->updateSortFile(sortStream, mFolder, true, true);
03206                 discovered_count++;
03207                 appended = 1;
03208             }
03209         }
03210         END_TIMER(holes);
03211         SHOW_TIMER(holes);
03212     }
03213 
03214     // Make sure we've placed everything in parent/child relationship. All
03215     // messages with a parent id of -1 in the sort file are reevaluated here.
03216     if (threaded) buildThreadingTree( sortCache );
03217     QPtrList<KMSortCacheItem> toBeSubjThreaded;
03218 
03219     if (threaded && !unparented.isEmpty()) {
03220         CREATE_TIMER(reparent);
03221         START_TIMER(reparent);
03222 
03223         for(QPtrListIterator<KMSortCacheItem> it(unparented); it.current(); ++it) {
03224             KMSortCacheItem *item = (*it);
03225             KMSortCacheItem *parent = findParent( item );
03226             // If we have a parent, make sure it's not ourselves
03227             if ( parent && (parent != (*it)) ) {
03228                 parent->addUnsortedChild((*it));
03229                 if(sortStream)
03230                     (*it)->updateSortFile(sortStream, mFolder);
03231             } else {
03232                 // if we will attempt subject threading, add to the list,
03233                 // otherwise to the root with them
03234                 if (mSubjThreading)
03235                   toBeSubjThreaded.append((*it));
03236                 else
03237                   root.addUnsortedChild((*it));
03238             }
03239         }
03240 
03241         if (mSubjThreading) {
03242             buildSubjectThreadingTree( sortCache );
03243             for(QPtrListIterator<KMSortCacheItem> it(toBeSubjThreaded); it.current(); ++it) {
03244                 KMSortCacheItem *item = (*it);
03245                 KMSortCacheItem *parent = findParentBySubject( item );
03246 
03247                 if ( parent ) {
03248                     parent->addUnsortedChild((*it));
03249                     if(sortStream)
03250                       (*it)->updateSortFile(sortStream, mFolder);
03251                 } else {
03252                     //oh well we tried, to the root with you!
03253                     root.addUnsortedChild((*it));
03254                 }
03255             }
03256         }
03257         END_TIMER(reparent);
03258         SHOW_TIMER(reparent);
03259     }
03260     //create headeritems
03261     CREATE_TIMER(header_creation);
03262     START_TIMER(header_creation);
03263     KMHeaderItem *khi;
03264     KMSortCacheItem *i, *new_kci;
03265     QPtrQueue<KMSortCacheItem> s;
03266     s.enqueue(&root);
03267     compare_toplevel = true;
03268     do {
03269         i = s.dequeue();
03270         const QPtrList<KMSortCacheItem> *sorted = i->sortedChildren();
03271         int unsorted_count, unsorted_off=0;
03272         KMSortCacheItem **unsorted = i->unsortedChildren(unsorted_count);
03273         if(unsorted)
03274             qsort(unsorted, unsorted_count, sizeof(KMSortCacheItem *), //sort
03275                   compare_KMSortCacheItem);
03276 
03277         /* The sorted list now contains all sorted children of this item, while
03278          * the (aptly named) unsorted array contains all as of yet unsorted
03279          * ones. It has just been qsorted, so it is in itself sorted. These two
03280          * sorted lists are now merged into one. */
03281         for(QPtrListIterator<KMSortCacheItem> it(*sorted);
03282             (unsorted && unsorted_off < unsorted_count) || it.current(); ) {
03283             /* As long as we have something in the sorted list and there is
03284                nothing unsorted left, use the item from the sorted list. Also
03285                if we are sorting descendingly and the sorted item is supposed
03286                to be sorted before the unsorted one do so. In the ascending
03287                case we invert the logic for non top level items. */
03288             if( it.current() &&
03289                ( !unsorted || unsorted_off >= unsorted_count
03290                 ||
03291                 ( ( !ascending || (ascending && !compare_toplevel) )
03292                   && (*it)->key() < unsorted[unsorted_off]->key() )
03293                 ||
03294                 (  ascending && (*it)->key() >= unsorted[unsorted_off]->key() )
03295                 )
03296                )
03297             {
03298                 new_kci = (*it);
03299                 ++it;
03300             } else {
03301                 /* Otherwise use the next item of the unsorted list */
03302                 new_kci = unsorted[unsorted_off++];
03303             }
03304             if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent
03305                 continue;
03306 
03307             if(threaded && i->item()) {
03308                 // If the parent is watched or ignored, propagate that to it's
03309                 // children
03310                 if (mFolder->getMsgBase(i->id())->isWatched())
03311                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusWatched);
03312                 if (mFolder->getMsgBase(i->id())->isIgnored()) {
03313                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusIgnored);
03314                   mFolder->setStatus(new_kci->id(), KMMsgStatusRead);
03315                 }
03316                 khi = new KMHeaderItem(i->item(), new_kci->id(), new_kci->key());
03317             } else {
03318                 khi = new KMHeaderItem(this, new_kci->id(), new_kci->key());
03319             }
03320             new_kci->setItem(mItems[new_kci->id()] = khi);
03321             if(new_kci->hasChildren())
03322                 s.enqueue(new_kci);
03323             // we always jump to new messages, but we only jump to
03324             // unread messages if we are told to do so
03325             if ( mFolder->getMsgBase(new_kci->id())->isNew() ||
03326                  ( jumpToUnread &&
03327                    mFolder->getMsgBase(new_kci->id())->isUnread() ) ) {
03328               unread_exists = true;
03329             }
03330         }
03331         // If we are sorting by date and ascending the top level items are sorted
03332         // ascending and the threads themselves are sorted descending. One wants
03333         // to have new threads on top but the threads themselves top down.
03334         if (mSortCol == paintInfo()->dateCol)
03335           compare_toplevel = false;
03336     } while(!s.isEmpty());
03337 
03338     for(int x = 0; x < mFolder->count(); x++) {     //cleanup
03339         if (!sortCache[x]) { // not yet there?
03340             continue;
03341         }
03342 
03343         if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
03344             kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. "
03345                   << endl << "Please talk to your threading counselor asap. " <<  endl;
03346             khi = new KMHeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
03347             sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
03348         }
03349         // Add all imperfectly threaded items to a list, so they can be
03350         // reevaluated when a new message arrives which might be a better parent.
03351         // Important for messages arriving out of order.
03352         if (threaded && sortCache[x]->isImperfectlyThreaded()) {
03353             mImperfectlyThreadedList.append(sortCache[x]->item());
03354         }
03355         // Set the reverse mapping KMHeaderItem -> KMSortCacheItem. Needed for
03356         // keeping the data structures up to date on removal, for example.
03357         sortCache[x]->item()->setSortCacheItem(sortCache[x]);
03358     }
03359 
03360     if (getNestingPolicy()<2)
03361     for (KMHeaderItem *khi=static_cast<KMHeaderItem*>(firstChild()); khi!=0;khi=static_cast<KMHeaderItem*>(khi->nextSibling()))
03362        khi->setOpen(true);
03363 
03364     END_TIMER(header_creation);
03365     SHOW_TIMER(header_creation);
03366 
03367     if(sortStream) { //update the .sorted file now
03368         // heuristic for when it's time to rewrite the .sorted file
03369         if( discovered_count * discovered_count > sorted_count - deleted_count ) {
03370             mSortInfo.dirty = true;
03371         } else {
03372             //update the appended flag
03373             appended = 0;
03374             fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
03375             fwrite(&appended, sizeof(appended), 1, sortStream);
03376         }
03377     }
03378 
03379     //show a message
03380     CREATE_TIMER(selection);
03381     START_TIMER(selection);
03382     if(set_selection) {
03383         int first_unread = -1;
03384         if (unread_exists) {
03385             KMHeaderItem *item = static_cast<KMHeaderItem*>(firstChild());
03386             while (item) {
03387               if ( mFolder->getMsgBase( item->msgId() )->isNew() ||
03388                    ( jumpToUnread &&
03389                      mFolder->getMsgBase( item->msgId() )->isUnread() ) ) {
03390                 first_unread = item->msgId();
03391                 break;
03392               }
03393               item = static_cast<KMHeaderItem*>(item->itemBelow());
03394             }
03395         }
03396 
03397         if(first_unread == -1 ) {
03398             setTopItemByIndex(mTopItem);
03399             if ( mCurrentItem >= 0 )
03400               setCurrentItemByIndex( mCurrentItem );
03401             else if ( mCurrentItemSerNum > 0 )
03402               setCurrentItemBySerialNum( mCurrentItemSerNum );
03403             else
03404               setCurrentItemByIndex( 0 );
03405         } else {
03406             setCurrentItemByIndex(first_unread);
03407             makeHeaderVisible();
03408             center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 );
03409         }
03410     } else {
03411         // only reset the selection if we have no current item
03412         if (mCurrentItem <= 0) {
03413           setTopItemByIndex(mTopItem);
03414           setCurrentItemByIndex(0);
03415         }
03416     }
03417     END_TIMER(selection);
03418     SHOW_TIMER(selection);
03419     if (error || (sortStream && ferror(sortStream))) {
03420         if ( sortStream )
03421             fclose(sortStream);
03422         unlink(QFile::encodeName(sortFile));
03423         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
03424         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
03425         //kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
03426     }
03427     if(sortStream)
03428         fclose(sortStream);
03429 
03430     return true;
03431 }
03432 
03433 //-----------------------------------------------------------------------------
03434 void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum )
03435 {
03436   // Linear search == slow. Don't overuse this method.
03437   // It's currently only used for finding the current item again
03438   // after expiry deleted mails (so the index got invalidated).
03439   for (int i = 0; i < (int)mItems.size() - 1; ++i) {
03440     KMMsgBase *mMsgBase = mFolder->getMsgBase( i );
03441     if ( mMsgBase->getMsgSerNum() == serialNum ) {
03442       bool unchanged = (currentItem() == mItems[i]);
03443       setCurrentItem( mItems[i] );
03444       setSelected( mItems[i], true );
03445       setSelectionAnchor( currentItem() );
03446       if ( unchanged )
03447         highlightMessage( currentItem(), false );
03448       ensureCurrentItemVisible();
03449       return;
03450     }
03451   }
03452   // Not found. Maybe we should select the last item instead?
03453   kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl;
03454 }
03455 
03456 #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 06:44:10 2005 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003