kmail

kmheaders.cpp

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