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