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