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