kmail

kmheaders.cpp

00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmheaders.cpp
00003 
00004 #include <config.h>
00005 
00006 #include "kmheaders.h"
00007 #include "headeritem.h"
00008 using KMail::HeaderItem;
00009 
00010 #include "kcursorsaver.h"
00011 #include "kmcommands.h"
00012 #include "kmmainwidget.h"
00013 #include "kmfiltermgr.h"
00014 #include "undostack.h"
00015 #include "kmmsgdict.h"
00016 #include "kmdebug.h"
00017 #include "kmfoldertree.h"
00018 #include "folderjob.h"
00019 using KMail::FolderJob;
00020 #include "actionscheduler.h"
00021 using KMail::ActionScheduler;
00022 #include "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", false );
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     mOwner->messageActions()->replyMenu()->plug( menu );
02417     mOwner->forwardMenu()->plug( menu );
02418     if( mOwner->sendAgainAction()->isEnabled() ) {
02419       mOwner->sendAgainAction()->plug( menu );
02420     } else {
02421       mOwner->editAction()->plug( menu );
02422     }
02423   }
02424   menu->insertSeparator();
02425 
02426   QPopupMenu *msgCopyMenu = new QPopupMenu(menu);
02427   mOwner->folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage, this,
02428       &mMenuToFolder, msgCopyMenu );
02429   menu->insertItem(i18n("&Copy To"), msgCopyMenu);
02430 
02431   if ( !mFolder->canDeleteMessages() ) {
02432     int id = menu->insertItem( i18n("&Move To") );
02433     menu->setItemEnabled( id, false );
02434   } else {
02435     QPopupMenu *msgMoveMenu = new QPopupMenu(menu);
02436     mOwner->folderTree()->folderToPopupMenu( KMFolderTree::MoveMessage, this,
02437         &mMenuToFolder, msgMoveMenu );
02438     menu->insertItem(i18n("&Move To"), msgMoveMenu);
02439   }
02440   menu->insertSeparator();
02441   mOwner->statusMenu()->plug( menu ); // Mark Message menu
02442   if ( mOwner->threadStatusMenu()->isEnabled() ) {
02443     mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu
02444   }
02445 
02446   if ( !out_folder && !tem_folder ) {
02447     menu->insertSeparator();
02448     mOwner->filterMenu()->plug( menu ); // Create Filter menu
02449     mOwner->action( "apply_filter_actions" )->plug( menu );
02450   }
02451 
02452   menu->insertSeparator();
02453   mOwner->printAction()->plug(menu);
02454   mOwner->saveAsAction()->plug(menu);
02455   mOwner->saveAttachmentsAction()->plug(menu);
02456   menu->insertSeparator();
02457   if ( mFolder->isTrash() ) {
02458     mOwner->deleteAction()->plug(menu);
02459     if ( mOwner->trashThreadAction()->isEnabled() )
02460       mOwner->deleteThreadAction()->plug(menu);
02461   } else {
02462     mOwner->trashAction()->plug(menu);
02463     if ( mOwner->trashThreadAction()->isEnabled() )
02464       mOwner->trashThreadAction()->plug(menu);
02465   }
02466   menu->insertSeparator();
02467   mOwner->messageActions()->createTodoAction()->plug( menu );
02468 
02469   KAcceleratorManager::manage(menu);
02470   kmkernel->setContextMenuShown( true );
02471   menu->exec(QCursor::pos(), 0);
02472   kmkernel->setContextMenuShown( false );
02473   delete menu;
02474 }
02475 
02476 //-----------------------------------------------------------------------------
02477 KMMessage* KMHeaders::currentMsg()
02478 {
02479   HeaderItem *hi = currentHeaderItem();
02480   if (!hi)
02481     return 0;
02482   else
02483     return mFolder->getMsg(hi->msgId());
02484 }
02485 
02486 //-----------------------------------------------------------------------------
02487 HeaderItem* KMHeaders::currentHeaderItem()
02488 {
02489   return static_cast<HeaderItem*>(currentItem());
02490 }
02491 
02492 //-----------------------------------------------------------------------------
02493 int KMHeaders::currentItemIndex()
02494 {
02495   HeaderItem* item = currentHeaderItem();
02496   if (item)
02497     return item->msgId();
02498   else
02499     return -1;
02500 }
02501 
02502 //-----------------------------------------------------------------------------
02503 void KMHeaders::setCurrentItemByIndex(int msgIdx)
02504 {
02505   if (!mFolder->isOpened()) setFolder(mFolder);
02506 
02507   if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) {
02508     clearSelection();
02509     bool unchanged = (currentItem() == mItems[msgIdx]);
02510     setCurrentItem( mItems[msgIdx] );
02511     setSelected( mItems[msgIdx], true );
02512     setSelectionAnchor( currentItem() );
02513     if (unchanged)
02514        highlightMessage( mItems[msgIdx], false);
02515     makeHeaderVisible();
02516   }
02517 }
02518 
02519 //-----------------------------------------------------------------------------
02520 int KMHeaders::topItemIndex()
02521 {
02522   HeaderItem *item = static_cast<HeaderItem*>( itemAt( QPoint( 1, 1 ) ) );
02523   if ( item )
02524     return item->msgId();
02525   else
02526     return -1;
02527 }
02528 
02529 //-----------------------------------------------------------------------------
02530 void KMHeaders::setTopItemByIndex( int aMsgIdx)
02531 {
02532   if ( aMsgIdx < 0 || static_cast<unsigned int>( aMsgIdx ) >= mItems.size() )
02533     return;
02534   const QListViewItem * const item = mItems[aMsgIdx];
02535   if ( item )
02536     setContentsPos( 0, itemPos( item ) );
02537 }
02538 
02539 //-----------------------------------------------------------------------------
02540 void KMHeaders::setNestedOverride( bool override )
02541 {
02542   mSortInfo.dirty = true;
02543   mNestedOverride = override;
02544   setRootIsDecorated( nestingPolicy != AlwaysOpen
02545                       && isThreaded() );
02546   QString sortFile = mFolder->indexLocation() + ".sorted";
02547   unlink(QFile::encodeName(sortFile));
02548   reset();
02549 }
02550 
02551 //-----------------------------------------------------------------------------
02552 void KMHeaders::setSubjectThreading( bool aSubjThreading )
02553 {
02554   mSortInfo.dirty = true;
02555   mSubjThreading = aSubjThreading;
02556   QString sortFile = mFolder->indexLocation() + ".sorted";
02557   unlink(QFile::encodeName(sortFile));
02558   reset();
02559 }
02560 
02561 //-----------------------------------------------------------------------------
02562 void KMHeaders::setOpen( QListViewItem *item, bool open )
02563 {
02564   if ((nestingPolicy != AlwaysOpen)|| open)
02565       ((HeaderItem*)item)->setOpenRecursive( open );
02566 }
02567 
02568 //-----------------------------------------------------------------------------
02569 const KMMsgBase* KMHeaders::getMsgBaseForItem( const QListViewItem *item ) const
02570 {
02571   const HeaderItem *hi = static_cast<const HeaderItem *> ( item );
02572   return mFolder->getMsgBase( hi->msgId() );
02573 }
02574 
02575 //-----------------------------------------------------------------------------
02576 void KMHeaders::setSorting( int column, bool ascending )
02577 {
02578   if (column != -1) {
02579   // carsten: really needed?
02580 //    if (column != mSortCol)
02581 //      setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol ));
02582     if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied
02583         QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
02584         mSortInfo.dirty = true;
02585     }
02586 
02587     assert(column >= 0);
02588     mSortCol = column;
02589     mSortDescending = !ascending;
02590 
02591     if (!ascending && (column == mPaintInfo.dateCol))
02592       mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival;
02593 
02594     if (!ascending && (column == mPaintInfo.subCol))
02595       mPaintInfo.status = !mPaintInfo.status;
02596 
02597     QString colText = i18n( "Date" );
02598     if (mPaintInfo.orderOfArrival)
02599       colText = i18n( "Order of Arrival" );
02600     setColumnText( mPaintInfo.dateCol, colText);
02601 
02602     colText = i18n( "Subject" );
02603     if (mPaintInfo.status)
02604       colText = colText + i18n( " (Status)" );
02605     setColumnText( mPaintInfo.subCol, colText);
02606   }
02607   KListView::setSorting( column, ascending );
02608   ensureCurrentItemVisible();
02609   // Make sure the config and .sorted file are updated, otherwise stale info
02610   // is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ).
02611   if ( mFolder ) {
02612     writeFolderConfig();
02613     writeSortOrder();
02614   }
02615 }
02616 
02617 //Flatten the list and write it to disk
02618 static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid,
02619                               int parent_id, QString key,
02620                               bool update_discover=true)
02621 {
02622   unsigned long msgSerNum;
02623   unsigned long parentSerNum;
02624   msgSerNum = KMMsgDict::instance()->getMsgSerNum( folder, msgid );
02625   if (parent_id >= 0)
02626     parentSerNum = KMMsgDict::instance()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED;
02627   else
02628     parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED);
02629 
02630   fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream);
02631   fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream);
02632   Q_INT32 len = key.length() * sizeof(QChar);
02633   fwrite(&len, sizeof(len), 1, sortStream);
02634   if (len)
02635     fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream);
02636 
02637   if (update_discover) {
02638     //update the discovered change count
02639       Q_INT32 discovered_count = 0;
02640       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
02641       fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
02642       discovered_count++;
02643       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
02644       fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02645   }
02646 }
02647 
02648 void KMHeaders::folderCleared()
02649 {
02650     mSortCacheItems.clear(); //autoDelete is true
02651     mSubjectLists.clear();
02652     mImperfectlyThreadedList.clear();
02653     mPrevCurrent = 0;
02654     emit selected(0);
02655 }
02656 
02657 
02658 void KMHeaders::folderClosed()
02659 {
02660     mFolder->open( "kmheaders" );
02661     folderCleared();
02662 }
02663 
02664 bool KMHeaders::writeSortOrder()
02665 {
02666   QString sortFile = KMAIL_SORT_FILE(mFolder);
02667 
02668   if (!mSortInfo.dirty) {
02669     struct stat stat_tmp;
02670     if(stat(QFile::encodeName(sortFile), &stat_tmp) == -1) {
02671         mSortInfo.dirty = true;
02672     }
02673   }
02674   if (mSortInfo.dirty) {
02675     if (!mFolder->count()) {
02676       // Folder is empty now, remove the sort file.
02677       unlink(QFile::encodeName(sortFile));
02678       return true;
02679     }
02680     QString tempName = sortFile + ".temp";
02681     unlink(QFile::encodeName(tempName));
02682     FILE *sortStream = fopen(QFile::encodeName(tempName), "w");
02683     if (!sortStream)
02684       return false;
02685 
02686     mSortInfo.ascending = !mSortDescending;
02687     mSortInfo.dirty = false;
02688     mSortInfo.column = mSortCol;
02689     fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION);
02690     //magic header information
02691     Q_INT32 byteOrder = 0x12345678;
02692     Q_INT32 column = mSortCol;
02693     Q_INT32 ascending= !mSortDescending;
02694     Q_INT32 threaded = isThreaded();
02695     Q_INT32 appended=0;
02696     Q_INT32 discovered_count = 0;
02697     Q_INT32 sorted_count=0;
02698     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
02699     fwrite(&column, sizeof(column), 1, sortStream);
02700     fwrite(&ascending, sizeof(ascending), 1, sortStream);
02701     fwrite(&threaded, sizeof(threaded), 1, sortStream);
02702     fwrite(&appended, sizeof(appended), 1, sortStream);
02703     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02704     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
02705 
02706     QPtrStack<HeaderItem> items;
02707     {
02708       QPtrStack<QListViewItem> s;
02709       for (QListViewItem * i = firstChild(); i; ) {
02710         items.push((HeaderItem *)i);
02711         if ( i->firstChild() ) {
02712           s.push( i );
02713           i = i->firstChild();
02714         } else if( i->nextSibling()) {
02715           i = i->nextSibling();
02716         } else {
02717             for(i=0; !i && s.count(); i = s.pop()->nextSibling())
02718               ;
02719         }
02720       }
02721     }
02722 
02723     KMMsgBase *kmb;
02724     while(HeaderItem *i = items.pop()) {
02725       int parent_id = -1; //no parent, top level
02726       if (threaded) {
02727         kmb = mFolder->getMsgBase( i->msgId() );
02728         assert(kmb); // I have seen 0L come out of this, called from
02729                    // KMHeaders::setFolder(0xgoodpointer, false);
02730                    // I see this crash too. after rebuilding a broken index on a dimap folder. always
02731         QString replymd5 = kmb->replyToIdMD5();
02732         QString replyToAuxId = kmb->replyToAuxIdMD5();
02733         SortCacheItem *p = NULL;
02734         if(!replymd5.isEmpty())
02735           p = mSortCacheItems[replymd5];
02736 
02737         if (p)
02738           parent_id = p->id();
02739         // We now have either found a parent, or set it to -1, which means that
02740         // it will be reevaluated when a message is added, for example. If there
02741         // is no replyToId and no replyToAuxId and the message is not prefixed,
02742         // this message is top level, and will always be, so no need to
02743         // reevaluate it.
02744         if (replymd5.isEmpty()
02745             && replyToAuxId.isEmpty()
02746             && !kmb->subjectIsPrefixed() )
02747           parent_id = -2;
02748         // FIXME also mark messages with -1 as -2 a certain amount of time after
02749         // their arrival, since it becomes very unlikely that a new parent for
02750         // them will show up. (Ingo suggests a month.) -till
02751       }
02752       internalWriteItem(sortStream, mFolder, i->msgId(), parent_id,
02753                         i->key(mSortCol, !mSortDescending), false);
02754       //double check for magic headers
02755       sorted_count++;
02756     }
02757 
02758     //magic header twice, case they've changed
02759     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET);
02760     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
02761     fwrite(&column, sizeof(column), 1, sortStream);
02762     fwrite(&ascending, sizeof(ascending), 1, sortStream);
02763     fwrite(&threaded, sizeof(threaded), 1, sortStream);
02764     fwrite(&appended, sizeof(appended), 1, sortStream);
02765     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02766     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
02767     if (sortStream && ferror(sortStream)) {
02768         fclose(sortStream);
02769         unlink(QFile::encodeName(sortFile));
02770         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
02771         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
02772         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
02773     }
02774     fclose(sortStream);
02775     ::rename(QFile::encodeName(tempName), QFile::encodeName(sortFile));
02776   }
02777 
02778   return true;
02779 }
02780 
02781 void KMHeaders::appendItemToSortFile(HeaderItem *khi)
02782 {
02783   QString sortFile = KMAIL_SORT_FILE(mFolder);
02784   if(FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+")) {
02785     int parent_id = -1; //no parent, top level
02786 
02787     if (isThreaded()) {
02788       SortCacheItem *sci = khi->sortCacheItem();
02789       KMMsgBase *kmb = mFolder->getMsgBase( khi->msgId() );
02790       if(sci->parent() && !sci->isImperfectlyThreaded())
02791         parent_id = sci->parent()->id();
02792       else if(kmb->replyToIdMD5().isEmpty()
02793            && kmb->replyToAuxIdMD5().isEmpty()
02794            && !kmb->subjectIsPrefixed())
02795         parent_id = -2;
02796     }
02797 
02798     internalWriteItem(sortStream, mFolder, khi->msgId(), parent_id,
02799                       khi->key(mSortCol, !mSortDescending), false);
02800 
02801     //update the appended flag FIXME obsolete?
02802     Q_INT32 appended = 1;
02803     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
02804     fwrite(&appended, sizeof(appended), 1, sortStream);
02805     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
02806 
02807     if (sortStream && ferror(sortStream)) {
02808         fclose(sortStream);
02809         unlink(QFile::encodeName(sortFile));
02810         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
02811         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
02812         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
02813     }
02814     fclose(sortStream);
02815   } else {
02816     mSortInfo.dirty = true;
02817   }
02818 }
02819 
02820 void KMHeaders::dirtySortOrder(int column)
02821 {
02822     mSortInfo.dirty = true;
02823     QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
02824     setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true);
02825 }
02826 
02827 // -----------------
02828 void SortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder,
02829                                       bool waiting_for_parent, bool update_discover)
02830 {
02831     if(mSortOffset == -1) {
02832         fseek(sortStream, 0, SEEK_END);
02833         mSortOffset = ftell(sortStream);
02834     } else {
02835         fseek(sortStream, mSortOffset, SEEK_SET);
02836     }
02837 
02838     int parent_id = -1;
02839     if(!waiting_for_parent) {
02840         if(mParent && !isImperfectlyThreaded())
02841             parent_id = mParent->id();
02842     }
02843     internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover);
02844 }
02845 
02846 static bool compare_ascending = false;
02847 static bool compare_toplevel = true;
02848 static int compare_SortCacheItem(const void *s1, const void *s2)
02849 {
02850     if ( !s1 || !s2 )
02851         return 0;
02852     SortCacheItem **b1 = (SortCacheItem **)s1;
02853     SortCacheItem **b2 = (SortCacheItem **)s2;
02854     int ret = (*b1)->key().compare((*b2)->key());
02855     if(compare_ascending || !compare_toplevel)
02856         ret = -ret;
02857     return ret;
02858 }
02859 
02860 // Debugging helpers
02861 void KMHeaders::printSubjectThreadingTree()
02862 {
02863     QDictIterator< QPtrList< SortCacheItem > > it ( mSubjectLists );
02864     kdDebug(5006) << "SubjectThreading tree: " << endl;
02865     for( ; it.current(); ++it ) {
02866       QPtrList<SortCacheItem> list = *( it.current() );
02867       QPtrListIterator<SortCacheItem> it2( list ) ;
02868       kdDebug(5006) << "Subject MD5: " << it.currentKey() << " list: " << endl;
02869       for( ; it2.current(); ++it2 ) {
02870         SortCacheItem *sci = it2.current();
02871         kdDebug(5006) << "     item:" << sci << " sci id: " << sci->id() << endl;
02872       }
02873     }
02874     kdDebug(5006) << endl;
02875 }
02876 
02877 void KMHeaders::printThreadingTree()
02878 {
02879     kdDebug(5006) << "Threading tree: " << endl;
02880     QDictIterator<SortCacheItem> it( mSortCacheItems );
02881     kdDebug(5006) << endl;
02882     for( ; it.current(); ++it ) {
02883       SortCacheItem *sci = it.current();
02884       kdDebug(5006) << "MsgId MD5: " << it.currentKey() << " message id: " << sci->id() << endl;
02885     }
02886     for (int i = 0; i < (int)mItems.size(); ++i) {
02887       HeaderItem *item = mItems[i];
02888       int parentCacheId = item->sortCacheItem()->parent()?item->sortCacheItem()->parent()->id():0;
02889       kdDebug( 5006 ) << "SortCacheItem: " << item->sortCacheItem()->id() << " parent: " << parentCacheId << endl;
02890       kdDebug( 5006 ) << "Item: " << item << " sortCache: " << item->sortCacheItem() << " parent: " << item->sortCacheItem()->parent() << endl;
02891     }
02892     kdDebug(5006) << endl;
02893 }
02894 
02895 // -------------------------------------
02896 
02897 void KMHeaders::buildThreadingTree( QMemArray<SortCacheItem *> sortCache )
02898 {
02899     mSortCacheItems.clear();
02900     mSortCacheItems.resize( mFolder->count() * 2 );
02901 
02902     // build a dict of all message id's
02903     for(int x = 0; x < mFolder->count(); x++) {
02904         KMMsgBase *mi = mFolder->getMsgBase(x);
02905         QString md5 = mi->msgIdMD5();
02906         if(!md5.isEmpty())
02907             mSortCacheItems.replace(md5, sortCache[x]);
02908     }
02909 }
02910 
02911 
02912 void KMHeaders::buildSubjectThreadingTree( QMemArray<SortCacheItem *> sortCache )
02913 {
02914     mSubjectLists.clear();  // autoDelete is true
02915     mSubjectLists.resize( mFolder->count() * 2 );
02916 
02917     for(int x = 0; x < mFolder->count(); x++) {
02918         // Only a lot items that are now toplevel
02919         if ( sortCache[x]->parent()
02920           && sortCache[x]->parent()->id() != -666 ) continue;
02921         KMMsgBase *mi = mFolder->getMsgBase(x);
02922         QString subjMD5 = mi->strippedSubjectMD5();
02923         if (subjMD5.isEmpty()) {
02924             mFolder->getMsgBase(x)->initStrippedSubjectMD5();
02925             subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
02926         }
02927         if( subjMD5.isEmpty() ) continue;
02928 
02929         /* For each subject, keep a list of items with that subject
02930          * (stripped of prefixes) sorted by date. */
02931         if (!mSubjectLists.find(subjMD5))
02932             mSubjectLists.insert(subjMD5, new QPtrList<SortCacheItem>());
02933         /* Insertion sort by date. These lists are expected to be very small.
02934          * Also, since the messages are roughly ordered by date in the store,
02935          * they should mostly be prepended at the very start, so insertion is
02936          * cheap. */
02937         int p=0;
02938         for (QPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
02939                 it.current(); ++it) {
02940             KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
02941             if ( mb->date() < mi->date())
02942                 break;
02943             p++;
02944         }
02945         mSubjectLists[subjMD5]->insert( p, sortCache[x]);
02946         sortCache[x]->setSubjectThreadingList( mSubjectLists[subjMD5] );
02947     }
02948 }
02949 
02950 
02951 SortCacheItem* KMHeaders::findParent(SortCacheItem *item)
02952 {
02953     SortCacheItem *parent = NULL;
02954     if (!item) return parent;
02955     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
02956     QString replyToIdMD5 = msg->replyToIdMD5();
02957     item->setImperfectlyThreaded(true);
02958     /* First, try if the message our Reply-To header points to
02959      * is available to thread below. */
02960     if(!replyToIdMD5.isEmpty()) {
02961         parent = mSortCacheItems[replyToIdMD5];
02962         if (parent)
02963             item->setImperfectlyThreaded(false);
02964     }
02965     if (!parent) {
02966         // If we dont have a replyToId, or if we have one and the
02967         // corresponding message is not in this folder, as happens
02968         // if you keep your outgoing messages in an OUTBOX, for
02969         // example, try the list of references, because the second
02970         // to last will likely be in this folder. replyToAuxIdMD5
02971         // contains the second to last one.
02972         QString  ref = msg->replyToAuxIdMD5();
02973         if (!ref.isEmpty())
02974             parent = mSortCacheItems[ref];
02975     }
02976     return parent;
02977 }
02978 
02979 SortCacheItem* KMHeaders::findParentBySubject(SortCacheItem *item)
02980 {
02981     SortCacheItem *parent = NULL;
02982     if (!item) return parent;
02983 
02984     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
02985 
02986     // Let's try by subject, but only if the  subject is prefixed.
02987     // This is necessary to make for example cvs commit mailing lists
02988     // work as expected without having to turn threading off alltogether.
02989     if (!msg->subjectIsPrefixed())
02990         return parent;
02991 
02992     QString replyToIdMD5 = msg->replyToIdMD5();
02993     QString subjMD5 = msg->strippedSubjectMD5();
02994     if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) {
02995         /* Iterate over the list of potential parents with the same
02996          * subject, and take the closest one by date. */
02997         for (QPtrListIterator<SortCacheItem> it2(*mSubjectLists[subjMD5]);
02998                 it2.current(); ++it2) {
02999             KMMsgBase *mb = mFolder->getMsgBase((*it2)->id());
03000             if ( !mb ) return parent;
03001             // make sure it's not ourselves
03002             if ( item == (*it2) ) continue;
03003             int delta = msg->date() - mb->date();
03004             // delta == 0 is not allowed, to avoid circular threading
03005             // with duplicates.
03006             if (delta > 0 ) {
03007                 // Don't use parents more than 6 weeks older than us.
03008                 if (delta < 3628899)
03009                     parent = (*it2);
03010                 break;
03011             }
03012         }
03013     }
03014     return parent;
03015 }
03016 
03017 bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread )
03018 {
03019     if (!mFolder->isOpened()) mFolder->open("kmheaders");
03020 
03021     //all cases
03022     Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended;
03023     Q_INT32 deleted_count = 0;
03024     bool unread_exists = false;
03025     bool jumpToUnread = (GlobalSettings::self()->actionEnterFolder() ==
03026                          GlobalSettings::EnumActionEnterFolder::SelectFirstUnreadNew) ||
03027                         forceJumpToUnread;
03028     QMemArray<SortCacheItem *> sortCache(mFolder->count());
03029     bool error = false;
03030 
03031     //threaded cases
03032     QPtrList<SortCacheItem> unparented;
03033     mImperfectlyThreadedList.clear();
03034 
03035     //cleanup
03036     mItems.fill( 0, mFolder->count() );
03037     sortCache.fill( 0 );
03038 
03039     mRoot->clearChildren();
03040 
03041     QString sortFile = KMAIL_SORT_FILE(mFolder);
03042     FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+");
03043     mSortInfo.fakeSort = 0;
03044 
03045     if(sortStream) {
03046         mSortInfo.fakeSort = 1;
03047         int version = 0;
03048         if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1)
03049           version = -1;
03050         if(version == KMAIL_SORT_VERSION) {
03051           Q_INT32 byteOrder = 0;
03052           fread(&byteOrder, sizeof(byteOrder), 1, sortStream);
03053           if (byteOrder == 0x12345678)
03054           {
03055             fread(&column, sizeof(column), 1, sortStream);
03056             fread(&ascending, sizeof(ascending), 1, sortStream);
03057             fread(&threaded, sizeof(threaded), 1, sortStream);
03058             fread(&appended, sizeof(appended), 1, sortStream);
03059             fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
03060             fread(&sorted_count, sizeof(sorted_count), 1, sortStream);
03061 
03062             //Hackyness to work around qlistview problems
03063             KListView::setSorting(-1);
03064             header()->setSortIndicator(column, ascending);
03065             QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
03066             //setup mSortInfo here now, as above may change it
03067             mSortInfo.dirty = false;
03068             mSortInfo.column = (short)column;
03069             mSortInfo.ascending = (compare_ascending = ascending);
03070 
03071             SortCacheItem *item;
03072             unsigned long serNum, parentSerNum;
03073             int id, len, parent, x;
03074             QChar *tmp_qchar = 0;
03075             int tmp_qchar_len = 0;
03076             const int mFolderCount = mFolder->count();
03077             QString key;
03078 
03079             CREATE_TIMER(parse);
03080             START_TIMER(parse);
03081             for(x = 0; !feof(sortStream) && !error; x++) {
03082                 off_t offset = ftell(sortStream);
03083                 KMFolder *folder;
03084                 //parse
03085                 if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end
03086                    !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) ||
03087                    !fread(&len, sizeof(len), 1, sortStream)) {
03088                     break;
03089                 }
03090                 if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) {
03091                     kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl;
03092                     error = true;
03093                     continue;
03094                 }
03095                 if(len) {
03096                     if(len > tmp_qchar_len) {
03097                         tmp_qchar = (QChar *)realloc(tmp_qchar, len);
03098                         tmp_qchar_len = len;
03099                     }
03100                     if(!fread(tmp_qchar, len, 1, sortStream))
03101                         break;
03102                     key = QString(tmp_qchar, len / 2);
03103                 } else {
03104                     key = QString(""); //yuck
03105                 }
03106 
03107                 KMMsgDict::instance()->getLocation(serNum, &folder, &id);
03108                 if (folder != mFolder) {
03109                     ++deleted_count;
03110                     continue;
03111                 }
03112                 if (parentSerNum < KMAIL_RESERVED) {
03113                     parent = (int)parentSerNum - KMAIL_RESERVED;
03114                 } else {
03115                     KMMsgDict::instance()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent);
03116                     if (folder != mFolder)
03117                         parent = -1;
03118                 }
03119                 if ((id < 0) || (id >= mFolderCount) ||
03120                     (parent < -2) || (parent >= mFolderCount)) { // sanity checking
03121                     kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl;
03122                     error = true;
03123                     continue;
03124                 }
03125 
03126                 if ((item=sortCache[id])) {
03127                     if (item->id() != -1) {
03128                         kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl;
03129                         error = true;
03130                         continue;
03131                     }
03132                     item->setKey(key);
03133                     item->setId(id);
03134                     item->setOffset(offset);
03135                 } else {
03136                     item = sortCache[id] = new SortCacheItem(id, key, offset);
03137                 }
03138                 if (threaded && parent != -2) {
03139                     if(parent == -1) {
03140                         unparented.append(item);
03141                         mRoot->addUnsortedChild(item);
03142                     } else {
03143                         if( ! sortCache[parent] ) {
03144                             sortCache[parent] = new SortCacheItem;
03145                         }
03146                         sortCache[parent]->addUnsortedChild(item);
03147                     }
03148                 } else {
03149                     if(x < sorted_count )
03150                         mRoot->addSortedChild(item);
03151                     else {
03152                         mRoot->addUnsortedChild(item);
03153                     }
03154                 }
03155             }
03156             if (error || (x != sorted_count + discovered_count)) {// sanity check
03157                 kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl;
03158                 fclose(sortStream);
03159                 sortStream = 0;
03160             }
03161 
03162             if(tmp_qchar)
03163                 free(tmp_qchar);
03164             END_TIMER(parse);
03165             SHOW_TIMER(parse);
03166           }
03167           else {
03168               fclose(sortStream);
03169               sortStream = 0;
03170           }
03171         } else {
03172             fclose(sortStream);
03173             sortStream = 0;
03174         }
03175     }
03176 
03177     if (!sortStream) {
03178         mSortInfo.dirty = true;
03179         mSortInfo.column = column = mSortCol;
03180         mSortInfo.ascending = ascending = !mSortDescending;
03181         threaded = (isThreaded());
03182         sorted_count = discovered_count = appended = 0;
03183         KListView::setSorting( mSortCol, !mSortDescending );
03184     }
03185     //fill in empty holes
03186     if((sorted_count + discovered_count - deleted_count) < mFolder->count()) {
03187         CREATE_TIMER(holes);
03188         START_TIMER(holes);
03189         KMMsgBase *msg = 0;
03190         for(int x = 0; x < mFolder->count(); x++) {
03191             if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) {
03192                 int sortOrder = column;
03193                 if (mPaintInfo.orderOfArrival)
03194                     sortOrder |= (1 << 6);
03195                 if (mPaintInfo.status)
03196                     sortOrder |= (1 << 5);
03197                 sortCache[x] = new SortCacheItem(
03198                     x, HeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder ));
03199                 if(threaded)
03200                     unparented.append(sortCache[x]);
03201                 else
03202                     mRoot->addUnsortedChild(sortCache[x]);
03203                 if(sortStream)
03204                     sortCache[x]->updateSortFile(sortStream, mFolder, true, true);
03205                 discovered_count++;
03206                 appended = 1;
03207             }
03208         }
03209         END_TIMER(holes);
03210         SHOW_TIMER(holes);
03211     }
03212 
03213     // Make sure we've placed everything in parent/child relationship. All
03214     // messages with a parent id of -1 in the sort file are reevaluated here.
03215     if (threaded) buildThreadingTree( sortCache );
03216     QPtrList<SortCacheItem> toBeSubjThreaded;
03217 
03218     if (threaded && !unparented.isEmpty()) {
03219         CREATE_TIMER(reparent);
03220         START_TIMER(reparent);
03221 
03222         for(QPtrListIterator<SortCacheItem> it(unparented); it.current(); ++it) {
03223             SortCacheItem *item = (*it);
03224             SortCacheItem *parent = findParent( item );
03225             // If we have a parent, make sure it's not ourselves
03226             if ( parent && (parent != (*it)) ) {
03227                 parent->addUnsortedChild((*it));
03228                 if(sortStream)
03229                     (*it)->updateSortFile(sortStream, mFolder);
03230             } else {
03231                 // if we will attempt subject threading, add to the list,
03232                 // otherwise to the root with them
03233                 if (mSubjThreading)
03234                   toBeSubjThreaded.append((*it));
03235                 else
03236                   mRoot->addUnsortedChild((*it));
03237             }
03238         }
03239 
03240         if (mSubjThreading) {
03241             buildSubjectThreadingTree( sortCache );
03242             for(QPtrListIterator<SortCacheItem> it(toBeSubjThreaded); it.current(); ++it) {
03243                 SortCacheItem *item = (*it);
03244                 SortCacheItem *parent = findParentBySubject( item );
03245 
03246                 if ( parent ) {
03247                     parent->addUnsortedChild((*it));
03248                     if(sortStream)
03249                       (*it)->updateSortFile(sortStream, mFolder);
03250                 } else {
03251                     //oh well we tried, to the root with you!
03252                     mRoot->addUnsortedChild((*it));
03253                 }
03254             }
03255         }
03256         END_TIMER(reparent);
03257         SHOW_TIMER(reparent);
03258     }
03259     //create headeritems
03260     CREATE_TIMER(header_creation);
03261     START_TIMER(header_creation);
03262     HeaderItem *khi;
03263     SortCacheItem *i, *new_kci;
03264     QPtrQueue<SortCacheItem> s;
03265     s.enqueue(mRoot);
03266     compare_toplevel = true;
03267     do {
03268         i = s.dequeue();
03269         const QPtrList<SortCacheItem> *sorted = i->sortedChildren();
03270         int unsorted_count, unsorted_off=0;
03271         SortCacheItem **unsorted = i->unsortedChildren(unsorted_count);
03272         if(unsorted)
03273             qsort(unsorted, unsorted_count, sizeof(SortCacheItem *), //sort
03274                   compare_SortCacheItem);
03275 
03276         /* The sorted list now contains all sorted children of this item, while
03277          * the (aptly named) unsorted array contains all as of yet unsorted
03278          * ones. It has just been qsorted, so it is in itself sorted. These two
03279          * sorted lists are now merged into one. */
03280         for(QPtrListIterator<SortCacheItem> it(*sorted);
03281             (unsorted && unsorted_off < unsorted_count) || it.current(); ) {
03282             /* As long as we have something in the sorted list and there is
03283                nothing unsorted left, use the item from the sorted list. Also
03284                if we are sorting descendingly and the sorted item is supposed
03285                to be sorted before the unsorted one do so. In the ascending
03286                case we invert the logic for non top level items. */
03287             if( it.current() &&
03288                ( !unsorted || unsorted_off >= unsorted_count
03289                 ||
03290                 ( ( !ascending || (ascending && !compare_toplevel) )
03291                   && (*it)->key() < unsorted[unsorted_off]->key() )
03292                 ||
03293                 (  ascending && (*it)->key() >= unsorted[unsorted_off]->key() )
03294                 )
03295                )
03296             {
03297                 new_kci = (*it);
03298                 ++it;
03299             } else {
03300                 /* Otherwise use the next item of the unsorted list */
03301                 new_kci = unsorted[unsorted_off++];
03302             }
03303             if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent
03304                 continue;
03305 
03306             if(threaded && i->item()) {
03307                 // If the parent is watched or ignored, propagate that to it's
03308                 // children
03309                 if (mFolder->getMsgBase(i->id())->isWatched())
03310                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusWatched);
03311                 if (mFolder->getMsgBase(i->id())->isIgnored())
03312                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusIgnored);
03313                 khi = new HeaderItem(i->item(), new_kci->id(), new_kci->key());
03314             } else {
03315                 khi = new HeaderItem(this, new_kci->id(), new_kci->key());
03316             }
03317             new_kci->setItem(mItems[new_kci->id()] = khi);
03318             if(new_kci->hasChildren())
03319                 s.enqueue(new_kci);
03320             // we always jump to new messages, but we only jump to
03321             // unread messages if we are told to do so
03322             if ( ( mFolder->getMsgBase(new_kci->id())->isNew() &&
03323                    GlobalSettings::self()->actionEnterFolder() ==
03324                    GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
03325                  ( ( mFolder->getMsgBase(new_kci->id())->isNew() ||
03326                      mFolder->getMsgBase(new_kci->id())->isUnread() ) &&
03327                    jumpToUnread ) )
03328             {
03329               unread_exists = true;
03330             }
03331         }
03332         // If we are sorting by date and ascending the top level items are sorted
03333         // ascending and the threads themselves are sorted descending. One wants
03334         // to have new threads on top but the threads themselves top down.
03335         if (mSortCol == paintInfo()->dateCol)
03336           compare_toplevel = false;
03337     } while(!s.isEmpty());
03338 
03339     for(int x = 0; x < mFolder->count(); x++) {     //cleanup
03340         if (!sortCache[x]) { // not yet there?
03341             continue;
03342         }
03343 
03344         if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
03345             kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. "
03346                   << endl << "Please talk to your threading counselor asap. " <<  endl;
03347             khi = new HeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
03348             sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
03349         }
03350         // Add all imperfectly threaded items to a list, so they can be
03351         // reevaluated when a new message arrives which might be a better parent.
03352         // Important for messages arriving out of order.
03353         if (threaded && sortCache[x]->isImperfectlyThreaded()) {
03354             mImperfectlyThreadedList.append(sortCache[x]->item());
03355         }
03356         // Set the reverse mapping HeaderItem -> SortCacheItem. Needed for
03357         // keeping the data structures up to date on removal, for example.
03358         sortCache[x]->item()->setSortCacheItem(sortCache[x]);
03359     }
03360 
03361     if (getNestingPolicy()<2)
03362       for (HeaderItem *khi=static_cast<HeaderItem*>(firstChild()); khi!=0;khi=static_cast<HeaderItem*>(khi->nextSibling()))
03363         khi->setOpen(true);
03364 
03365     END_TIMER(header_creation);
03366     SHOW_TIMER(header_creation);
03367 
03368     if(sortStream) { //update the .sorted file now
03369         // heuristic for when it's time to rewrite the .sorted file
03370         if( discovered_count * discovered_count > sorted_count - deleted_count ) {
03371             mSortInfo.dirty = true;
03372         } else {
03373             //update the appended flag
03374             appended = 0;
03375             fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
03376             fwrite(&appended, sizeof(appended), 1, sortStream);
03377         }
03378     }
03379 
03380     //show a message
03381     CREATE_TIMER(selection);
03382     START_TIMER(selection);
03383     if(set_selection) {
03384         int first_unread = -1;
03385         if (unread_exists) {
03386             HeaderItem *item = static_cast<HeaderItem*>(firstChild());
03387             while (item) {
03388               if ( ( mFolder->getMsgBase(item->msgId())->isNew() &&
03389                      GlobalSettings::self()->actionEnterFolder() ==
03390                      GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
03391                    ( ( mFolder->getMsgBase(item->msgId())->isNew() ||
03392                        mFolder->getMsgBase(item->msgId())->isUnread() ) &&
03393                      jumpToUnread ) )
03394               {
03395                 first_unread = item->msgId();
03396                 break;
03397               }
03398               item = static_cast<HeaderItem*>(item->itemBelow());
03399             }
03400         }
03401 
03402         if(first_unread == -1 ) {
03403             setTopItemByIndex(mTopItem);
03404             if ( mCurrentItem >= 0 )
03405               setCurrentItemByIndex( mCurrentItem );
03406             else if ( mCurrentItemSerNum > 0 )
03407               setCurrentItemBySerialNum( mCurrentItemSerNum );
03408             else
03409               setCurrentItemByIndex( 0 );
03410         } else {
03411             setCurrentItemByIndex(first_unread);
03412             makeHeaderVisible();
03413             center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 );
03414         }
03415     } else {
03416         // only reset the selection if we have no current item
03417         if (mCurrentItem <= 0) {
03418           setTopItemByIndex(mTopItem);
03419           setCurrentItemByIndex(0);
03420         }
03421     }
03422     END_TIMER(selection);
03423     SHOW_TIMER(selection);
03424     if (error || (sortStream && ferror(sortStream))) {
03425         if ( sortStream )
03426             fclose(sortStream);
03427         unlink(QFile::encodeName(sortFile));
03428         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
03429         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
03430 
03431         return true;
03432     }
03433     if(sortStream)
03434         fclose(sortStream);
03435 
03436     return true;
03437 }
03438 
03439 //-----------------------------------------------------------------------------
03440 void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum )
03441 {
03442   // Linear search == slow. Don't overuse this method.
03443   // It's currently only used for finding the current item again
03444   // after expiry deleted mails (so the index got invalidated).
03445   for (int i = 0; i < (int)mItems.size() - 1; ++i) {
03446     KMMsgBase *mMsgBase = mFolder->getMsgBase( i );
03447     if ( mMsgBase->getMsgSerNum() == serialNum ) {
03448       bool unchanged = (currentItem() == mItems[i]);
03449       setCurrentItem( mItems[i] );
03450       setSelected( mItems[i], true );
03451       setSelectionAnchor( currentItem() );
03452       if ( unchanged )
03453         highlightMessage( currentItem(), false );
03454       ensureCurrentItemVisible();
03455       return;
03456     }
03457   }
03458   // Not found. Maybe we should select the last item instead?
03459   kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl;
03460 }
03461 
03462 void KMHeaders::copyMessages()
03463 {
03464   mCopiedMessages.clear();
03465   KMMessageList* list = selectedMsgs();
03466   for ( uint i = 0; i < list->count(); ++ i )
03467     mCopiedMessages << list->at( i )->getMsgSerNum();
03468   mMoveMessages = false;
03469   updateActions();
03470   triggerUpdate();
03471 }
03472 
03473 void KMHeaders::cutMessages()
03474 {
03475   mCopiedMessages.clear();
03476   KMMessageList* list = selectedMsgs();
03477   for ( uint i = 0; i < list->count(); ++ i )
03478     mCopiedMessages << list->at( i )->getMsgSerNum();
03479   mMoveMessages = true;
03480   updateActions();
03481   triggerUpdate();
03482 }
03483 
03484 void KMHeaders::pasteMessages()
03485 {
03486   new MessageCopyHelper( mCopiedMessages, folder(), mMoveMessages, this );
03487   if ( mMoveMessages ) {
03488     mCopiedMessages.clear();
03489     updateActions();
03490   }
03491 }
03492 
03493 void KMHeaders::updateActions()
03494 {
03495   KAction *copy = owner()->action( "copy_messages" );
03496   KAction *cut = owner()->action( "cut_messages" );
03497   KAction *paste = owner()->action( "paste_messages" );
03498 
03499   if ( selectedItems().isEmpty() ) {
03500     copy->setEnabled( false );
03501     cut->setEnabled( false );
03502   } else {
03503     copy->setEnabled( true );
03504     if ( folder() && !folder()->canDeleteMessages() )
03505       cut->setEnabled( false );
03506     else
03507       cut->setEnabled( true );
03508   }
03509 
03510   if ( mCopiedMessages.isEmpty() || !folder() || folder()->isReadOnly() )
03511     paste->setEnabled( false );
03512   else
03513     paste->setEnabled( true );
03514 }
03515 
03516 void KMHeaders::setCopiedMessages(const QValueList< Q_UINT32 > & msgs, bool move)
03517 {
03518   mCopiedMessages = msgs;
03519   mMoveMessages = move;
03520   updateActions();
03521 }
03522 
03523 bool KMHeaders::isMessageCut(Q_UINT32 serNum) const
03524 {
03525   return mMoveMessages && mCopiedMessages.contains( serNum );
03526 }
03527 
03528 QValueList< Q_UINT32 > KMHeaders::selectedSernums()
03529 {
03530   QValueList<Q_UINT32> list;
03531   for ( QListViewItemIterator it(this); it.current(); it++ ) {
03532     if ( it.current()->isSelected() && it.current()->isVisible() ) {
03533       HeaderItem* item = static_cast<HeaderItem*>( it.current() );
03534       KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
03535       list.append( msgBase->getMsgSerNum() );
03536     }
03537   }
03538   return list;
03539 }
03540 
03541 QValueList< Q_UINT32 > KMHeaders::selectedVisibleSernums()
03542 {
03543   QValueList<Q_UINT32> list;
03544   QListViewItemIterator it(this, QListViewItemIterator::Selected|QListViewItemIterator::Visible);
03545   while( it.current() ) {
03546     if ( it.current()->isSelected() && it.current()->isVisible() ) {
03547       if ( it.current()->parent() && ( !it.current()->parent()->isOpen() ) ) {
03548         // the item's parent is closed, don't traverse any more of this subtree
03549         QListViewItem * lastAncestorWithSiblings = it.current()->parent();
03550         // travel towards the root until we find an ancestor with siblings
03551         while ( ( lastAncestorWithSiblings->depth() > 0 ) && !lastAncestorWithSiblings->nextSibling() )
03552           lastAncestorWithSiblings = lastAncestorWithSiblings->parent();
03553         // move the iterator to that ancestor's next sibling
03554         it = QListViewItemIterator( lastAncestorWithSiblings->nextSibling() );
03555         continue;
03556       }
03557       HeaderItem *item = static_cast<HeaderItem*>(it.current());
03558       KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
03559       list.append( msgBase->getMsgSerNum() );
03560     }
03561     ++it;
03562   }
03563 
03564   return list;
03565 }
03566 
03567 #include "kmheaders.moc"