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