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