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