kmail

kmheaders.cpp

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