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 && 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 void KMHeaders::decryptedCopySelectedToFolder( int menuId )
01687 {
01688   if (mMenuToFolder[menuId])
01689     copyDecryptedMsgToFolder( mMenuToFolder[menuId] );
01690 }
01691 
01692 //-----------------------------------------------------------------------------
01693 void KMHeaders::copyMsgToFolder(KMFolder* destFolder, KMMessage* aMsg)
01694 {
01695   if ( !destFolder )
01696     return;
01697 
01698   KMCommand * command = 0;
01699   if (aMsg)
01700     command = new KMCopyCommand( destFolder, aMsg );
01701   else {
01702     KMMessageList msgList = *selectedMsgs();
01703     command = new KMCopyCommand( destFolder, msgList );
01704   }
01705 
01706   command->start();
01707 }
01708 
01709 //-----------------------------------------------------------------------------
01710 void KMHeaders::copyDecryptedMsgToFolder(KMFolder* destFolder, KMMessage* aMsg)
01711 {
01712   if ( !destFolder )
01713     return;
01714 
01715   kdDebug() << "copyDecryptedMsgToFolder" << endl;
01716   KMCommand * command = 0;
01717   if (aMsg)
01718     command = new KMCopyCommand( destFolder, aMsg, true);
01719   else {
01720     KMMessageList msgList = *selectedMsgs();
01721     command = new KMCopyCommand( destFolder, msgList, true );
01722   }
01723 
01724   command->start();
01725 }
01726 
01727 
01728 //-----------------------------------------------------------------------------
01729 void KMHeaders::setCurrentMsg(int cur)
01730 {
01731   if (!mFolder) return;
01732   if (cur >= mFolder->count()) cur = mFolder->count() - 1;
01733   if ((cur >= 0) && (cur < (int)mItems.size())) {
01734     clearSelection();
01735     setCurrentItem( mItems[cur] );
01736     setSelected( mItems[cur], true );
01737     setSelectionAnchor( currentItem() );
01738   }
01739   makeHeaderVisible();
01740   setFolderInfoStatus();
01741 }
01742 
01743 //-----------------------------------------------------------------------------
01744 void KMHeaders::setSelected( QListViewItem *item, bool selected )
01745 {
01746   if ( !item )
01747     return;
01748 
01749   if ( item->isVisible() )
01750     KListView::setSelected( item, selected );
01751 
01752   // If the item is the parent of a closed thread recursively select
01753   // children .
01754   if ( isThreaded() && !item->isOpen() && item->firstChild() ) {
01755       QListViewItem *nextRoot = item->itemBelow();
01756       QListViewItemIterator it( item->firstChild() );
01757       for( ; (*it) != nextRoot; ++it ) {
01758         if ( (*it)->isVisible() )
01759            (*it)->setSelected( selected );
01760       }
01761   }
01762 }
01763 
01764 void KMHeaders::setSelectedByIndex( QValueList<int> items, bool selected )
01765 {
01766   for ( QValueList<int>::Iterator it = items.begin(); it != items.end(); ++it )
01767   {
01768     if ( ((*it) >= 0) && ((*it) < (int)mItems.size()) )
01769     {
01770       setSelected( mItems[(*it)], selected );
01771     }
01772   }
01773 }
01774 
01775 void KMHeaders::clearSelectableAndAboutToBeDeleted( Q_UINT32 serNum )
01776 {
01777   // fugly, but I see no way around it
01778   for (QListViewItemIterator it(this); it.current(); it++) {
01779     HeaderItem *item = static_cast<HeaderItem*>(it.current());
01780     if ( item->aboutToBeDeleted() ) {
01781       KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
01782       if ( serNum == msgBase->getMsgSerNum() ) {
01783         item->setAboutToBeDeleted ( false );
01784         item->setSelectable ( true );
01785       }
01786     }
01787   }
01788   triggerUpdate();
01789 }
01790 
01791 //-----------------------------------------------------------------------------
01792 KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted)
01793 {
01794   mSelMsgBaseList.clear();
01795   for (QListViewItemIterator it(this); it.current(); it++) {
01796     if ( it.current()->isSelected() && it.current()->isVisible() ) {
01797       HeaderItem *item = static_cast<HeaderItem*>(it.current());
01798       if ( !item->aboutToBeDeleted() ) { // we are already working on this one
01799         if (toBeDeleted) {
01800           // make sure the item is not uselessly rethreaded and not selectable
01801           item->setAboutToBeDeleted ( true );
01802           item->setSelectable ( false );
01803         }
01804         KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
01805         mSelMsgBaseList.append(msgBase);
01806       }
01807     }
01808   }
01809   return &mSelMsgBaseList;
01810 }
01811 
01812 //-----------------------------------------------------------------------------
01813 QValueList<int> KMHeaders::selectedItems()
01814 {
01815   QValueList<int> items;
01816   for ( QListViewItemIterator it(this); it.current(); it++ )
01817   {
01818     if ( it.current()->isSelected() && it.current()->isVisible() )
01819     {
01820       HeaderItem* item = static_cast<HeaderItem*>( it.current() );
01821       items.append( item->msgId() );
01822     }
01823   }
01824   return items;
01825 }
01826 
01827 //-----------------------------------------------------------------------------
01828 int KMHeaders::firstSelectedMsg() const
01829 {
01830   int selectedMsg = -1;
01831   QListViewItem *item;
01832   for (item = firstChild(); item; item = item->itemBelow())
01833     if (item->isSelected()) {
01834       selectedMsg = (static_cast<HeaderItem*>(item))->msgId();
01835       break;
01836     }
01837   return selectedMsg;
01838 }
01839 
01840 //-----------------------------------------------------------------------------
01841 void KMHeaders::nextMessage()
01842 {
01843   QListViewItem *lvi = currentItem();
01844   if (lvi && lvi->itemBelow()) {
01845     clearSelection();
01846     setSelected( lvi, false );
01847     selectNextMessage();
01848     setSelectionAnchor( currentItem() );
01849     ensureCurrentItemVisible();
01850   }
01851 }
01852 
01853 void KMHeaders::selectNextMessage()
01854 {
01855   KMMessage *cm = currentMsg();
01856   if ( cm && cm->isBeingParsed() )
01857     return;
01858   QListViewItem *lvi = currentItem();
01859   if( lvi ) {
01860     QListViewItem *below = lvi->itemBelow();
01861     QListViewItem *temp = lvi;
01862     if (lvi && below ) {
01863       while (temp) {
01864         temp->firstChild();
01865         temp = temp->parent();
01866       }
01867       lvi->repaint();
01868       /* test to see if we need to unselect messages on back track */
01869       (below->isSelected() ? setSelected(lvi, false) : setSelected(below, true));
01870       setCurrentItem(below);
01871       makeHeaderVisible();
01872       setFolderInfoStatus();
01873     }
01874   }
01875 }
01876 
01877 //-----------------------------------------------------------------------------
01878 void KMHeaders::prevMessage()
01879 {
01880   QListViewItem *lvi = currentItem();
01881   if (lvi && lvi->itemAbove()) {
01882     clearSelection();
01883     setSelected( lvi, false );
01884     selectPrevMessage();
01885     setSelectionAnchor( currentItem() );
01886     ensureCurrentItemVisible();
01887   }
01888 }
01889 
01890 void KMHeaders::selectPrevMessage()
01891 {
01892   KMMessage *cm = currentMsg();
01893   if ( cm && cm->isBeingParsed() )
01894     return;
01895   QListViewItem *lvi = currentItem();
01896   if( lvi ) {
01897     QListViewItem *above = lvi->itemAbove();
01898     QListViewItem *temp = lvi;
01899 
01900     if (lvi && above) {
01901       while (temp) {
01902         temp->firstChild();
01903         temp = temp->parent();
01904       }
01905       lvi->repaint();
01906       /* test to see if we need to unselect messages on back track */
01907       (above->isSelected() ? setSelected(lvi, false) : setSelected(above, true));
01908       setCurrentItem(above);
01909       makeHeaderVisible();
01910       setFolderInfoStatus();
01911     }
01912   }
01913 }
01914 
01915 
01916 void KMHeaders::incCurrentMessage()
01917 {
01918   KMMessage *cm = currentMsg();
01919   if ( cm && cm->isBeingParsed() )
01920     return;
01921   QListViewItem *lvi = currentItem();
01922   if ( lvi && lvi->itemBelow() ) {
01923 
01924     disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
01925                this,SLOT(highlightMessage(QListViewItem*)));
01926     setCurrentItem( lvi->itemBelow() );
01927     ensureCurrentItemVisible();
01928     setFocus();
01929     connect(this,SIGNAL(currentChanged(QListViewItem*)),
01930                this,SLOT(highlightMessage(QListViewItem*)));
01931   }
01932 }
01933 
01934 void KMHeaders::decCurrentMessage()
01935 {
01936   KMMessage *cm = currentMsg();
01937   if ( cm && cm->isBeingParsed() )
01938     return;
01939   QListViewItem *lvi = currentItem();
01940   if ( lvi && lvi->itemAbove() ) {
01941     disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
01942                this,SLOT(highlightMessage(QListViewItem*)));
01943     setCurrentItem( lvi->itemAbove() );
01944     ensureCurrentItemVisible();
01945     setFocus();
01946     connect(this,SIGNAL(currentChanged(QListViewItem*)),
01947             this,SLOT(highlightMessage(QListViewItem*)));
01948   }
01949 }
01950 
01951 void KMHeaders::selectCurrentMessage()
01952 {
01953   setCurrentMsg( currentItemIndex() );
01954   highlightMessage( currentItem() );
01955 }
01956 
01957 //-----------------------------------------------------------------------------
01958 void KMHeaders::findUnreadAux( HeaderItem*& item,
01959                                         bool & foundUnreadMessage,
01960                                         bool onlyNew,
01961                                         bool aDirNext )
01962 {
01963   KMMsgBase* msgBase = 0;
01964   HeaderItem *lastUnread = 0;
01965   /* itemAbove() is _slow_ */
01966   if (aDirNext)
01967   {
01968     while (item) {
01969       msgBase = mFolder->getMsgBase(item->msgId());
01970       if (!msgBase) continue;
01971       if (msgBase->isUnread() || msgBase->isNew())
01972         foundUnreadMessage = true;
01973 
01974       if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())) break;
01975       if (onlyNew && msgBase->isNew()) break;
01976       item = static_cast<HeaderItem*>(item->itemBelow());
01977     }
01978   } else {
01979     HeaderItem *newItem = static_cast<HeaderItem*>(firstChild());
01980     while (newItem)
01981     {
01982       msgBase = mFolder->getMsgBase(newItem->msgId());
01983       if (!msgBase) continue;
01984       if (msgBase->isUnread() || msgBase->isNew())
01985         foundUnreadMessage = true;
01986       if ( ( !onlyNew && (msgBase->isUnread() || msgBase->isNew()) )
01987           || ( onlyNew && msgBase->isNew() ) )
01988         lastUnread = newItem;
01989       if (newItem == item) break;
01990       newItem = static_cast<HeaderItem*>(newItem->itemBelow());
01991     }
01992     item = lastUnread;
01993   }
01994 }
01995 
01996 //-----------------------------------------------------------------------------
01997 int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent)
01998 {
01999   HeaderItem *item, *pitem;
02000   bool foundUnreadMessage = false;
02001 
02002   if (!mFolder) return -1;
02003   if (mFolder->count() <= 0) return -1;
02004 
02005   if ((aStartAt >= 0) && (aStartAt < (int)mItems.size()))
02006     item = mItems[aStartAt];
02007   else {
02008     item = currentHeaderItem();
02009     if (!item) {
02010       if (aDirNext)
02011         item = static_cast<HeaderItem*>(firstChild());
02012       else
02013         item = static_cast<HeaderItem*>(lastChild());
02014     }
02015     if (!item)
02016       return -1;
02017 
02018     if ( !acceptCurrent ) {
02019         if (aDirNext) {
02020             item = static_cast<HeaderItem*>(item->itemBelow());
02021         }
02022         else {
02023             item = static_cast<HeaderItem*>(item->itemAbove());
02024         }
02025     }
02026   }
02027 
02028   pitem =  item;
02029 
02030   findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
02031 
02032   // We have found an unread item, but it is not necessary the
02033   // first unread item.
02034   //
02035   // Find the ancestor of the unread item closest to the
02036   // root and recursively sort all of that ancestors children.
02037   if (item) {
02038     QListViewItem *next = item;
02039     while (next->parent())
02040       next = next->parent();
02041     next = static_cast<HeaderItem*>(next)->firstChildNonConst();
02042     while (next && (next != item))
02043       if (static_cast<HeaderItem*>(next)->firstChildNonConst())
02044         next = next->firstChild();
02045       else if (next->nextSibling())
02046         next = next->nextSibling();
02047       else {
02048         while (next && (next != item)) {
02049           next = next->parent();
02050           if (next == item)
02051             break;
02052           if (next && next->nextSibling()) {
02053             next = next->nextSibling();
02054             break;
02055           }
02056         }
02057       }
02058   }
02059 
02060   item = pitem;
02061 
02062   findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
02063   if (item)
02064     return item->msgId();
02065 
02066 
02067   // A kludge to try to keep the number of unread messages in sync
02068   int unread = mFolder->countUnread();
02069   if (((unread == 0) && foundUnreadMessage) ||
02070       ((unread > 0) && !foundUnreadMessage)) {
02071     mFolder->correctUnreadMsgsCount();
02072   }
02073   return -1;
02074 }
02075 
02076 //-----------------------------------------------------------------------------
02077 bool KMHeaders::nextUnreadMessage(bool acceptCurrent)
02078 {
02079   if ( !mFolder || !mFolder->countUnread() ) return false;
02080   int i = findUnread(true, -1, false, acceptCurrent);
02081   if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
02082         GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
02083   {
02084     HeaderItem * first = static_cast<HeaderItem*>(firstChild());
02085     if ( first )
02086       i = findUnread(true, first->msgId(), false, acceptCurrent); // from top
02087   }
02088   if ( i < 0 )
02089     return false;
02090   setCurrentMsg(i);
02091   ensureCurrentItemVisible();
02092   return true;
02093 }
02094 
02095 void KMHeaders::ensureCurrentItemVisible()
02096 {
02097     int i = currentItemIndex();
02098     if ((i >= 0) && (i < (int)mItems.size()))
02099         center( contentsX(), itemPos(mItems[i]), 0, 9.0 );
02100 }
02101 
02102 //-----------------------------------------------------------------------------
02103 bool KMHeaders::prevUnreadMessage()
02104 {
02105   if ( !mFolder || !mFolder->countUnread() ) return false;
02106   int i = findUnread(false);
02107   if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
02108         GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
02109   {
02110     HeaderItem * last = static_cast<HeaderItem*>(lastItem());
02111     if ( last )
02112       i = findUnread(false, last->msgId() ); // from bottom
02113   }
02114   if ( i < 0 )
02115     return false;
02116   setCurrentMsg(i);
02117   ensureCurrentItemVisible();
02118   return true;
02119 }
02120 
02121 
02122 //-----------------------------------------------------------------------------
02123 void KMHeaders::slotNoDrag()
02124 {
02125   // This causes Kolab issue 1569 (encrypted mails sometimes not dragable)
02126   // This was introduced in r73594 to fix interference between dnd and
02127   // pinentry, which is no longer reproducable now. However, since the
02128   // original problem was probably a race and might reappear, let's keep
02129   // this workaround in for now and just disable it.
02130 //   mMousePressed = false;
02131 }
02132 
02133 
02134 //-----------------------------------------------------------------------------
02135 void KMHeaders::makeHeaderVisible()
02136 {
02137   if (currentItem())
02138     ensureItemVisible( currentItem() );
02139 }
02140 
02141 //-----------------------------------------------------------------------------
02142 void KMHeaders::highlightMessage(QListViewItem* lvi, bool markitread)
02143 {
02144   // shouldnt happen but will crash if it does
02145   if (lvi && !lvi->isSelectable()) return;
02146 
02147   HeaderItem *item = static_cast<HeaderItem*>(lvi);
02148   if (lvi != mPrevCurrent) {
02149     if (mPrevCurrent && mFolder)
02150     {
02151       KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId());
02152       if (prevMsg && mReaderWindowActive)
02153       {
02154         mFolder->ignoreJobsForMessage(prevMsg);
02155         if (!prevMsg->transferInProgress())
02156           mFolder->unGetMsg(mPrevCurrent->msgId());
02157       }
02158     }
02159     mPrevCurrent = item;
02160   }
02161 
02162   if (!item) {
02163     emit selected( 0 );
02164     return;
02165   }
02166 
02167   int idx = item->msgId();
02168   if ( idx < 0 ) {
02169     emit selected( 0 );
02170     return;
02171   }
02172 
02173   KMMessage *msg = mFolder->getMsg( idx );
02174   if ( !msg ) {
02175     if ( mReaderWindowActive ) {
02176       mPrevCurrent = 0;
02177     }
02178     emit selected( 0 );
02179     return;
02180   }
02181 
02182   BroadcastStatus::instance()->setStatusMsg("");
02183   if (markitread && idx >= 0) setMsgRead(idx);
02184   mItems[idx]->irefresh();
02185   mItems[idx]->repaint();
02186   emit selected( msg );
02187   setFolderInfoStatus();
02188 }
02189 
02190 void KMHeaders::highlightCurrentThread()
02191 {
02192   QPtrList<QListViewItem> curThread = currentThread();
02193   QPtrListIterator<QListViewItem> it( curThread );
02194 
02195   for ( it.toFirst() ; it.current() ; ++it ) {
02196       QListViewItem *lvi = *it;
02197       lvi->setSelected( true );
02198       lvi->repaint();
02199   }
02200 }
02201 
02202 void KMHeaders::resetCurrentTime()
02203 {
02204     mDate.reset();
02205     // only reset exactly during minute switch
02206     QTimer::singleShot( ( 60-QTime::currentTime().second() ) * 1000,
02207         this, SLOT( resetCurrentTime() ) );
02208 }
02209 
02210 //-----------------------------------------------------------------------------
02211 void KMHeaders::selectMessage(QListViewItem* lvi)
02212 {
02213   HeaderItem *item = static_cast<HeaderItem*>(lvi);
02214   if (!item)
02215     return;
02216 
02217   int idx = item->msgId();
02218   KMMessage *msg = mFolder->getMsg(idx);
02219   if (msg && !msg->transferInProgress())
02220   {
02221     emit activated(mFolder->getMsg(idx));
02222   }
02223 
02224 //  if (kmkernel->folderIsDraftOrOutbox(mFolder))
02225 //    setOpen(lvi, !lvi->isOpen());
02226 }
02227 
02228 
02229 //-----------------------------------------------------------------------------
02230 void KMHeaders::updateMessageList( bool set_selection, bool forceJumpToUnread )
02231 {
02232   mPrevCurrent = 0;
02233   noRepaint = true;
02234   clear();
02235   mItems.resize(0); // will contain deleted pointers
02236   noRepaint = false;
02237   KListView::setSorting( mSortCol, !mSortDescending );
02238   if (!mFolder) {
02239     repaint();
02240     return;
02241   }
02242   readSortOrder( set_selection, forceJumpToUnread );
02243   emit messageListUpdated();
02244 }
02245 
02246 
02247 //-----------------------------------------------------------------------------
02248 // KMail Header list selection/navigation description
02249 //
02250 // If the selection state changes the reader window is updated to show the
02251 // current item.
02252 //
02253 // (The selection state of a message or messages can be changed by pressing
02254 //  space, or normal/shift/cntrl clicking).
02255 //
02256 // The following keyboard events are supported when the messages headers list
02257 // has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End,
02258 // Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do
02259 // not change the selection state.
02260 //
02261 // Exception: When shift selecting either with mouse or key press the reader
02262 // window is updated regardless of whether of not the selection has changed.
02263 void KMHeaders::keyPressEvent( QKeyEvent * e )
02264 {
02265     bool cntrl = (e->state() & ControlButton );
02266     bool shft = (e->state() & ShiftButton );
02267     QListViewItem *cur = currentItem();
02268 
02269     if (!e || !firstChild())
02270       return;
02271 
02272     // If no current item, make some first item current when a key is pressed
02273     if (!cur) {
02274       setCurrentItem( firstChild() );
02275       setSelectionAnchor( currentItem() );
02276       return;
02277     }
02278 
02279     // Handle space key press
02280     if (cur->isSelectable() && e->ascii() == ' ' ) {
02281         setSelected( cur, !cur->isSelected() );
02282         highlightMessage( cur, false);
02283         return;
02284     }
02285 
02286     if (cntrl) {
02287       if (!shft)
02288         disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
02289                    this,SLOT(highlightMessage(QListViewItem*)));
02290       switch (e->key()) {
02291       case Key_Down:
02292       case Key_Up:
02293       case Key_Home:
02294       case Key_End:
02295       case Key_Next:
02296       case Key_Prior:
02297       case Key_Escape:
02298         KListView::keyPressEvent( e );
02299       }
02300       if (!shft)
02301         connect(this,SIGNAL(currentChanged(QListViewItem*)),
02302                 this,SLOT(highlightMessage(QListViewItem*)));
02303     }
02304 }
02305 
02306 //-----------------------------------------------------------------------------
02307 // Handle RMB press, show pop up menu
02308 void KMHeaders::rightButtonPressed( QListViewItem *lvi, const QPoint &, int )
02309 {
02310   if (!lvi)
02311     return;
02312 
02313   if (!(lvi->isSelected())) {
02314     clearSelection();
02315   }
02316   setSelected( lvi, true );
02317   slotRMB();
02318 }
02319 
02320 //-----------------------------------------------------------------------------
02321 void KMHeaders::contentsMousePressEvent(QMouseEvent* e)
02322 {
02323   mPressPos = e->pos();
02324   QListViewItem *lvi = itemAt( contentsToViewport( e->pos() ));
02325   bool wasSelected = false;
02326   bool rootDecoClicked = false;
02327   if (lvi) {
02328      wasSelected = lvi->isSelected();
02329      rootDecoClicked =
02330         (  mPressPos.x() <= header()->cellPos(  header()->mapToActual(  0 ) ) +
02331            treeStepSize() * (  lvi->depth() + (  rootIsDecorated() ? 1 : 0 ) ) + itemMargin() )
02332         && (  mPressPos.x() >= header()->cellPos(  header()->mapToActual(  0 ) ) );
02333 
02334      if ( rootDecoClicked ) {
02335         // Check if our item is the parent of a closed thread and if so, if the root
02336         // decoration of the item was clicked (i.e. the +/- sign) which would expand
02337         // the thread. In that case, deselect all children, so opening the thread
02338         // doesn't cause a flicker.
02339         if ( !lvi->isOpen() && lvi->firstChild() ) {
02340            QListViewItem *nextRoot = lvi->itemBelow();
02341            QListViewItemIterator it( lvi->firstChild() );
02342            for( ; (*it) != nextRoot; ++it )
02343               (*it)->setSelected( false );
02344         }
02345      }
02346   }
02347 
02348   // let klistview do it's thing, expanding/collapsing, selection/deselection
02349   KListView::contentsMousePressEvent(e);
02350   /* QListView's shift-select selects also invisible items. Until that is
02351      fixed, we have to deselect hidden items here manually, so the quick
02352      search doesn't mess things up. */
02353   if ( e->state() & ShiftButton ) {
02354     QListViewItemIterator it( this, QListViewItemIterator::Invisible );
02355     while ( it.current() ) {
02356       it.current()->setSelected( false );
02357       ++it;
02358     }
02359   }
02360 
02361   if ( rootDecoClicked ) {
02362       // select the thread's children after closing if the parent is selected
02363      if ( lvi && !lvi->isOpen() && lvi->isSelected() )
02364         setSelected( lvi, true );
02365   }
02366 
02367   if ( lvi && !rootDecoClicked ) {
02368     if ( lvi != currentItem() )
02369       highlightMessage( lvi );
02370     /* Explicitely set selection state. This is necessary because we want to
02371      * also select all children of closed threads when the parent is selected. */
02372 
02373     // unless ctrl mask, set selected if it isn't already
02374     if ( !( e->state() & ControlButton ) && !wasSelected )
02375       setSelected( lvi, true );
02376     // if ctrl mask, toggle selection
02377     if ( e->state() & ControlButton )
02378       setSelected( lvi, !wasSelected );
02379 
02380     if ((e->button() == LeftButton) )
02381       mMousePressed = true;
02382   }
02383 
02384   // check if we are on a status column and toggle it
02385   if ( lvi && e->button() == LeftButton  && !( e->state() & (ShiftButton | ControlButton | AltButton | MetaButton) ) ) {
02386     bool flagsToggleable = GlobalSettings::self()->allowLocalFlags() || !(mFolder ? mFolder->isReadOnly() : true);
02387     int section = header()->sectionAt( e->pos().x() );
02388     HeaderItem *item = static_cast<HeaderItem*>( lvi );
02389     KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
02390     if ( section == mPaintInfo.flagCol && flagsToggleable ) {
02391       setMsgStatus( KMMsgStatusFlag, true );
02392     } else if ( section == mPaintInfo.importantCol && flagsToggleable ) {
02393       setMsgStatus( KMMsgStatusFlag, true );
02394     } else if ( section == mPaintInfo.todoCol && flagsToggleable ) {
02395       setMsgStatus( KMMsgStatusTodo, true );
02396     } else if ( section == mPaintInfo.watchedIgnoredCol && flagsToggleable ) {
02397       if ( msg->isWatched() || msg->isIgnored() )
02398         setMsgStatus( KMMsgStatusIgnored, true );
02399       else
02400         setMsgStatus( KMMsgStatusWatched, true );
02401     } else if ( section == mPaintInfo.statusCol ) {
02402       if ( msg->isUnread() || msg->isNew() )
02403         setMsgStatus( KMMsgStatusRead );
02404       else
02405         setMsgStatus( KMMsgStatusUnread );
02406     }
02407   }
02408 }
02409 
02410 //-----------------------------------------------------------------------------
02411 void KMHeaders::contentsMouseReleaseEvent(QMouseEvent* e)
02412 {
02413   if (e->button() != RightButton)
02414     KListView::contentsMouseReleaseEvent(e);
02415 
02416   mMousePressed = false;
02417 }
02418 
02419 //-----------------------------------------------------------------------------
02420 void KMHeaders::contentsMouseMoveEvent( QMouseEvent* e )
02421 {
02422   if (mMousePressed &&
02423       (e->pos() - mPressPos).manhattanLength() > KGlobalSettings::dndEventDelay()) {
02424     mMousePressed = false;
02425     QListViewItem *item = itemAt( contentsToViewport(mPressPos) );
02426     if ( item ) {
02427       MailList mailList;
02428       unsigned int count = 0;
02429       for( QListViewItemIterator it(this); it.current(); it++ )
02430         if( it.current()->isSelected() ) {
02431           HeaderItem *item = static_cast<HeaderItem*>(it.current());
02432           KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
02433           // FIXME: msg can be null here which crashes.  I think it's a race
02434           //        because it's very hard to reproduce. (GS)
02435           MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(),
02436                                    msg->subject(), msg->fromStrip(),
02437                                    msg->toStrip(), msg->date() );
02438           mailList.append( mailSummary );
02439           ++count;
02440         }
02441       MailListDrag *d = new MailListDrag( mailList, viewport(), new KMTextSource );
02442 
02443       // Set pixmap
02444       QPixmap pixmap;
02445       if( count == 1 )
02446         pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) );
02447       else
02448         pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) );
02449 
02450       // Calculate hotspot (as in Konqueror)
02451       if( !pixmap.isNull() ) {
02452         QPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 );
02453         d->setPixmap( pixmap, hotspot );
02454       }
02455       if ( mFolder->isReadOnly() )
02456         d->dragCopy();
02457       else
02458         d->drag();
02459     }
02460   }
02461 }
02462 
02463 void KMHeaders::highlightMessage(QListViewItem* i)
02464 {
02465     highlightMessage( i, false );
02466 }
02467 
02468 //-----------------------------------------------------------------------------
02469 void KMHeaders::slotRMB()
02470 {
02471   if (!topLevelWidget()) return; // safe bet
02472   mOwner->updateMessageActions();
02473 
02474   // check if the user clicked into a status column and only show the respective menues
02475   QListViewItem *item = itemAt( viewport()->mapFromGlobal( QCursor::pos() ) );
02476   if ( item ) {
02477     int section = header()->sectionAt( viewportToContents( viewport()->mapFromGlobal( QCursor::pos() ) ).x() );
02478     if ( section == mPaintInfo.flagCol || section == mPaintInfo.importantCol
02479          || section == mPaintInfo.todoCol || section == mPaintInfo.statusCol ) {
02480       mOwner->statusMenu()->popup( QCursor::pos() );
02481       return;
02482     }
02483     if ( section == mPaintInfo.watchedIgnoredCol ) {
02484       mOwner->threadStatusMenu()->popup( QCursor::pos() );
02485       return;
02486     }
02487   }
02488 
02489   QPopupMenu *menu = new QPopupMenu(this);
02490 
02491   mMenuToFolder.clear();
02492 
02493   mOwner->updateMessageMenu();
02494 
02495   bool out_folder = kmkernel->folderIsDraftOrOutbox( mFolder );
02496   bool tem_folder = kmkernel->folderIsTemplates( mFolder );
02497   if ( tem_folder ) {
02498      mOwner->useAction()->plug( menu );
02499   } else {
02500     // show most used actions
02501     mOwner->messageActions()->replyMenu()->plug( menu );
02502     mOwner->forwardMenu()->plug( menu );
02503     if( mOwner->sendAgainAction()->isEnabled() ) {
02504       mOwner->sendAgainAction()->plug( menu );
02505     } else {
02506       mOwner->editAction()->plug( menu );
02507     }
02508   }
02509   menu->insertSeparator();
02510 
02511   QPopupMenu *msgCopyMenu = new QPopupMenu(menu);
02512   mOwner->folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage, this,
02513       &mMenuToFolder, msgCopyMenu );
02514   menu->insertItem(i18n("&Copy To"), msgCopyMenu);
02515 
02516   QPopupMenu *msgDecCopyMenu = new QPopupMenu(menu);
02517   mOwner->folderTree()->folderToPopupMenu( KMFolderTree::DecCopyMessage, this,
02518       &mMenuToFolder, msgDecCopyMenu );
02519   menu->insertItem(i18n("&Decrypted Copy to"), msgDecCopyMenu);
02520 
02521   if ( !mFolder->canDeleteMessages() ) {
02522     int id = menu->insertItem( i18n("&Move To") );
02523     menu->setItemEnabled( id, false );
02524   } else {
02525     QPopupMenu *msgMoveMenu = new QPopupMenu(menu);
02526     mOwner->folderTree()->folderToPopupMenu( KMFolderTree::MoveMessage, this,
02527         &mMenuToFolder, msgMoveMenu );
02528     menu->insertItem(i18n("&Move To"), msgMoveMenu);
02529   }
02530   menu->insertSeparator();
02531   mOwner->statusMenu()->plug( menu ); // Mark Message menu
02532   if ( mOwner->threadStatusMenu()->isEnabled() ) {
02533     mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu
02534   }
02535 
02536   if ( !out_folder && !tem_folder ) {
02537     menu->insertSeparator();
02538     mOwner->filterMenu()->plug( menu ); // Create Filter menu
02539     mOwner->action( "apply_filter_actions" )->plug( menu );
02540   }
02541 
02542   menu->insertSeparator();
02543   mOwner->printAction()->plug(menu);
02544   mOwner->saveAsAction()->plug(menu);
02545   mOwner->saveAttachmentsAction()->plug(menu);
02546   menu->insertSeparator();
02547   if ( mFolder->isTrash() ) {
02548     mOwner->deleteAction()->plug(menu);
02549     if ( mOwner->trashThreadAction()->isEnabled() )
02550       mOwner->deleteThreadAction()->plug(menu);
02551   } else {
02552     mOwner->trashAction()->plug(menu);
02553     if ( mOwner->trashThreadAction()->isEnabled() )
02554       mOwner->trashThreadAction()->plug(menu);
02555   }
02556   menu->insertSeparator();
02557   mOwner->messageActions()->createTodoAction()->plug( menu );
02558 
02559   KAcceleratorManager::manage(menu);
02560   kmkernel->setContextMenuShown( true );
02561   menu->exec(QCursor::pos(), 0);
02562   kmkernel->setContextMenuShown( false );
02563   delete menu;
02564 }
02565 
02566 //-----------------------------------------------------------------------------
02567 KMMessage* KMHeaders::currentMsg()
02568 {
02569   HeaderItem *hi = currentHeaderItem();
02570   if (!hi)
02571     return 0;
02572   else
02573     return mFolder->getMsg(hi->msgId());
02574 }
02575 
02576 //-----------------------------------------------------------------------------
02577 HeaderItem* KMHeaders::currentHeaderItem()
02578 {
02579   return static_cast<HeaderItem*>(currentItem());
02580 }
02581 
02582 //-----------------------------------------------------------------------------
02583 int KMHeaders::currentItemIndex()
02584 {
02585   HeaderItem* item = currentHeaderItem();
02586   if (item)
02587     return item->msgId();
02588   else
02589     return -1;
02590 }
02591 
02592 //-----------------------------------------------------------------------------
02593 void KMHeaders::setCurrentItemByIndex(int msgIdx)
02594 {
02595   if (!mFolder->isOpened()) setFolder(mFolder);
02596 
02597   if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) {
02598     clearSelection();
02599     bool unchanged = (currentItem() == mItems[msgIdx]);
02600     setCurrentItem( mItems[msgIdx] );
02601     setSelected( mItems[msgIdx], true );
02602     setSelectionAnchor( currentItem() );
02603     if (unchanged)
02604        highlightMessage( mItems[msgIdx], false);
02605     makeHeaderVisible();
02606   }
02607 }
02608 
02609 //-----------------------------------------------------------------------------
02610 int KMHeaders::topItemIndex()
02611 {
02612   HeaderItem *item = static_cast<HeaderItem*>( itemAt( QPoint( 1, 1 ) ) );
02613   if ( item )
02614     return item->msgId();
02615   else
02616     return -1;
02617 }
02618 
02619 //-----------------------------------------------------------------------------
02620 void KMHeaders::setTopItemByIndex( int aMsgIdx)
02621 {
02622   if ( aMsgIdx < 0 || static_cast<unsigned int>( aMsgIdx ) >= mItems.size() )
02623     return;
02624   const QListViewItem * const item = mItems[aMsgIdx];
02625   if ( item )
02626     setContentsPos( 0, itemPos( item ) );
02627 }
02628 
02629 //-----------------------------------------------------------------------------
02630 void KMHeaders::setNestedOverride( bool override )
02631 {
02632   mSortInfo.dirty = true;
02633   mNestedOverride = override;
02634   setRootIsDecorated( nestingPolicy != AlwaysOpen
02635                       && isThreaded() );
02636   QString sortFile = mFolder->indexLocation() + ".sorted";
02637   unlink(QFile::encodeName(sortFile));
02638   reset();
02639 }
02640 
02641 //-----------------------------------------------------------------------------
02642 void KMHeaders::setSubjectThreading( bool aSubjThreading )
02643 {
02644   mSortInfo.dirty = true;
02645   mSubjThreading = aSubjThreading;
02646   QString sortFile = mFolder->indexLocation() + ".sorted";
02647   unlink(QFile::encodeName(sortFile));
02648   reset();
02649 }
02650 
02651 //-----------------------------------------------------------------------------
02652 void KMHeaders::setOpen( QListViewItem *item, bool open )
02653 {
02654   if ((nestingPolicy != AlwaysOpen)|| open)
02655       ((HeaderItem*)item)->setOpenRecursive( open );
02656 }
02657 
02658 //-----------------------------------------------------------------------------
02659 const KMMsgBase* KMHeaders::getMsgBaseForItem( const QListViewItem *item ) const
02660 {
02661   const HeaderItem *hi = static_cast<const HeaderItem *> ( item );
02662   return mFolder->getMsgBase( hi->msgId() );
02663 }
02664 
02665 //-----------------------------------------------------------------------------
02666 void KMHeaders::setSorting( int column, bool ascending )
02667 {
02668   if ( mIgnoreSortOrderChanges )
02669     return;
02670 
02671   if (column != -1) {
02672   // carsten: really needed?
02673 //    if (column != mSortCol)
02674 //      setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol ));
02675     if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied
02676         QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
02677         mSortInfo.dirty = true;
02678     }
02679 
02680     assert(column >= 0);
02681     mSortCol = column;
02682     mSortDescending = !ascending;
02683 
02684     if (!ascending && (column == mPaintInfo.dateCol))
02685       mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival;
02686 
02687     if (!ascending && (column == mPaintInfo.subCol))
02688       mPaintInfo.status = !mPaintInfo.status;
02689 
02690     QString colText = i18n( "Date" );
02691     if (mPaintInfo.orderOfArrival)
02692       colText = i18n( "Order of Arrival" );
02693     setColumnText( mPaintInfo.dateCol, colText);
02694 
02695     colText = i18n( "Subject" );
02696     if (mPaintInfo.status)
02697       colText = colText + i18n( " (Status)" );
02698     setColumnText( mPaintInfo.subCol, colText);
02699   }
02700   KListView::setSorting( column, ascending );
02701   ensureCurrentItemVisible();
02702   // Make sure the config and .sorted file are updated, otherwise stale info
02703   // is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ).
02704   if ( mFolder ) {
02705     writeFolderConfig();
02706     writeSortOrder();
02707   }
02708 }
02709 
02710 //Flatten the list and write it to disk
02711 static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid,
02712                               int parent_id, QString key,
02713                               bool update_discover=true)
02714 {
02715   unsigned long msgSerNum;
02716   unsigned long parentSerNum;
02717   msgSerNum = KMMsgDict::instance()->getMsgSerNum( folder, msgid );
02718   if (parent_id >= 0)
02719     parentSerNum = KMMsgDict::instance()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED;
02720   else
02721     parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED);
02722 
02723   fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream);
02724   fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream);
02725   Q_INT32 len = key.length() * sizeof(QChar);
02726   fwrite(&len, sizeof(len), 1, sortStream);
02727   if (len)
02728     fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream);
02729 
02730   if (update_discover) {
02731     //update the discovered change count
02732       Q_INT32 discovered_count = 0;
02733       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
02734       fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
02735       discovered_count++;
02736       fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
02737       fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02738   }
02739 }
02740 
02741 void KMHeaders::folderCleared()
02742 {
02743     mSortCacheItems.clear(); //autoDelete is true
02744     mSubjectLists.clear();
02745     mImperfectlyThreadedList.clear();
02746     mPrevCurrent = 0;
02747     emit selected(0);
02748 }
02749 
02750 
02751 void KMHeaders::folderClosed()
02752 {
02753   if ( mFolder->open( "kmheaders" ) == 0 )
02754     updateMessageList();
02755   else
02756     folderCleared();
02757 }
02758 
02759 bool KMHeaders::writeSortOrder()
02760 {
02761   QString sortFile = KMAIL_SORT_FILE(mFolder);
02762 
02763   if (!mSortInfo.dirty) {
02764     struct stat stat_tmp;
02765     if(stat(QFile::encodeName(sortFile), &stat_tmp) == -1) {
02766         mSortInfo.dirty = true;
02767     }
02768   }
02769   if (mSortInfo.dirty) {
02770     if (!mFolder->count()) {
02771       // Folder is empty now, remove the sort file.
02772       unlink(QFile::encodeName(sortFile));
02773       return true;
02774     }
02775     QString tempName = sortFile + ".temp";
02776     unlink(QFile::encodeName(tempName));
02777     FILE *sortStream = fopen(QFile::encodeName(tempName), "w");
02778     if (!sortStream)
02779       return false;
02780 
02781     mSortInfo.ascending = !mSortDescending;
02782     mSortInfo.dirty = false;
02783     mSortInfo.column = mSortCol;
02784     fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION);
02785     //magic header information
02786     Q_INT32 byteOrder = 0x12345678;
02787     Q_INT32 column = mSortCol;
02788     Q_INT32 ascending= !mSortDescending;
02789     Q_INT32 threaded = isThreaded();
02790     Q_INT32 appended=0;
02791     Q_INT32 discovered_count = 0;
02792     Q_INT32 sorted_count=0;
02793     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
02794     fwrite(&column, sizeof(column), 1, sortStream);
02795     fwrite(&ascending, sizeof(ascending), 1, sortStream);
02796     fwrite(&threaded, sizeof(threaded), 1, sortStream);
02797     fwrite(&appended, sizeof(appended), 1, sortStream);
02798     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02799     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
02800 
02801     QPtrStack<HeaderItem> items;
02802     {
02803       QPtrStack<QListViewItem> s;
02804       for (QListViewItem * i = firstChild(); i; ) {
02805         items.push((HeaderItem *)i);
02806         if ( i->firstChild() ) {
02807           s.push( i );
02808           i = i->firstChild();
02809         } else if( i->nextSibling()) {
02810           i = i->nextSibling();
02811         } else {
02812             for(i=0; !i && s.count(); i = s.pop()->nextSibling())
02813               ;
02814         }
02815       }
02816     }
02817 
02818     KMMsgBase *kmb;
02819     while(HeaderItem *i = items.pop()) {
02820       int parent_id = -1; //no parent, top level
02821       if (threaded) {
02822         kmb = mFolder->getMsgBase( i->msgId() );
02823         assert(kmb); // I have seen 0L come out of this, called from
02824                    // KMHeaders::setFolder(0xgoodpointer, false);
02825                    // I see this crash too. after rebuilding a broken index on a dimap folder. always
02826         QString replymd5 = kmb->replyToIdMD5();
02827         QString replyToAuxId = kmb->replyToAuxIdMD5();
02828         SortCacheItem *p = NULL;
02829         if(!replymd5.isEmpty())
02830           p = mSortCacheItems[replymd5];
02831 
02832         if (p)
02833           parent_id = p->id();
02834         // We now have either found a parent, or set it to -1, which means that
02835         // it will be reevaluated when a message is added, for example. If there
02836         // is no replyToId and no replyToAuxId and the message is not prefixed,
02837         // this message is top level, and will always be, so no need to
02838         // reevaluate it.
02839         if (replymd5.isEmpty()
02840             && replyToAuxId.isEmpty()
02841             && !kmb->subjectIsPrefixed() )
02842           parent_id = -2;
02843         // FIXME also mark messages with -1 as -2 a certain amount of time after
02844         // their arrival, since it becomes very unlikely that a new parent for
02845         // them will show up. (Ingo suggests a month.) -till
02846       }
02847       internalWriteItem(sortStream, mFolder, i->msgId(), parent_id,
02848                         i->key(mSortCol, !mSortDescending), false);
02849       //double check for magic headers
02850       sorted_count++;
02851     }
02852 
02853     //magic header twice, case they've changed
02854     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET);
02855     fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
02856     fwrite(&column, sizeof(column), 1, sortStream);
02857     fwrite(&ascending, sizeof(ascending), 1, sortStream);
02858     fwrite(&threaded, sizeof(threaded), 1, sortStream);
02859     fwrite(&appended, sizeof(appended), 1, sortStream);
02860     fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
02861     fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
02862     if (sortStream && ferror(sortStream)) {
02863         fclose(sortStream);
02864         unlink(QFile::encodeName(sortFile));
02865         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
02866         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
02867         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
02868     }
02869     fclose(sortStream);
02870     ::rename(QFile::encodeName(tempName), QFile::encodeName(sortFile));
02871   }
02872 
02873   return true;
02874 }
02875 
02876 void KMHeaders::appendItemToSortFile(HeaderItem *khi)
02877 {
02878   QString sortFile = KMAIL_SORT_FILE(mFolder);
02879   if(FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+")) {
02880     int parent_id = -1; //no parent, top level
02881 
02882     if (isThreaded()) {
02883       SortCacheItem *sci = khi->sortCacheItem();
02884       KMMsgBase *kmb = mFolder->getMsgBase( khi->msgId() );
02885       if(sci->parent() && !sci->isImperfectlyThreaded())
02886         parent_id = sci->parent()->id();
02887       else if(kmb->replyToIdMD5().isEmpty()
02888            && kmb->replyToAuxIdMD5().isEmpty()
02889            && !kmb->subjectIsPrefixed())
02890         parent_id = -2;
02891     }
02892 
02893     internalWriteItem(sortStream, mFolder, khi->msgId(), parent_id,
02894                       khi->key(mSortCol, !mSortDescending), false);
02895 
02896     //update the appended flag FIXME obsolete?
02897     Q_INT32 appended = 1;
02898     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
02899     fwrite(&appended, sizeof(appended), 1, sortStream);
02900     fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
02901 
02902     if (sortStream && ferror(sortStream)) {
02903         fclose(sortStream);
02904         unlink(QFile::encodeName(sortFile));
02905         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
02906         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
02907         kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
02908     }
02909     fclose(sortStream);
02910   } else {
02911     mSortInfo.dirty = true;
02912   }
02913 }
02914 
02915 void KMHeaders::dirtySortOrder(int column)
02916 {
02917     mSortInfo.dirty = true;
02918     QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
02919     setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true);
02920 }
02921 
02922 // -----------------
02923 void SortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder,
02924                                       bool waiting_for_parent, bool update_discover)
02925 {
02926     if(mSortOffset == -1) {
02927         fseek(sortStream, 0, SEEK_END);
02928         mSortOffset = ftell(sortStream);
02929     } else {
02930         fseek(sortStream, mSortOffset, SEEK_SET);
02931     }
02932 
02933     int parent_id = -1;
02934     if(!waiting_for_parent) {
02935         if(mParent && !isImperfectlyThreaded())
02936             parent_id = mParent->id();
02937     }
02938     internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover);
02939 }
02940 
02941 static bool compare_ascending = false;
02942 static bool compare_toplevel = true;
02943 static int compare_SortCacheItem(const void *s1, const void *s2)
02944 {
02945     if ( !s1 || !s2 )
02946         return 0;
02947     SortCacheItem **b1 = (SortCacheItem **)s1;
02948     SortCacheItem **b2 = (SortCacheItem **)s2;
02949     int ret = (*b1)->key().compare((*b2)->key());
02950     if(compare_ascending || !compare_toplevel)
02951         ret = -ret;
02952     return ret;
02953 }
02954 
02955 // Debugging helpers
02956 void KMHeaders::printSubjectThreadingTree()
02957 {
02958     QDictIterator< QPtrList< SortCacheItem > > it ( mSubjectLists );
02959     kdDebug(5006) << "SubjectThreading tree: " << endl;
02960     for( ; it.current(); ++it ) {
02961       QPtrList<SortCacheItem> list = *( it.current() );
02962       QPtrListIterator<SortCacheItem> it2( list ) ;
02963       kdDebug(5006) << "Subject MD5: " << it.currentKey() << " list: " << endl;
02964       for( ; it2.current(); ++it2 ) {
02965         SortCacheItem *sci = it2.current();
02966         kdDebug(5006) << "     item:" << sci << " sci id: " << sci->id() << endl;
02967       }
02968     }
02969     kdDebug(5006) << endl;
02970 }
02971 
02972 void KMHeaders::printThreadingTree()
02973 {
02974     kdDebug(5006) << "Threading tree: " << endl;
02975     QDictIterator<SortCacheItem> it( mSortCacheItems );
02976     kdDebug(5006) << endl;
02977     for( ; it.current(); ++it ) {
02978       SortCacheItem *sci = it.current();
02979       kdDebug(5006) << "MsgId MD5: " << it.currentKey() << " message id: " << sci->id() << endl;
02980     }
02981     for (int i = 0; i < (int)mItems.size(); ++i) {
02982       HeaderItem *item = mItems[i];
02983       int parentCacheId = item->sortCacheItem()->parent()?item->sortCacheItem()->parent()->id():0;
02984       kdDebug( 5006 ) << "SortCacheItem: " << item->sortCacheItem()->id() << " parent: " << parentCacheId << endl;
02985       kdDebug( 5006 ) << "Item: " << item << " sortCache: " << item->sortCacheItem() << " parent: " << item->sortCacheItem()->parent() << endl;
02986     }
02987     kdDebug(5006) << endl;
02988 }
02989 
02990 // -------------------------------------
02991 
02992 void KMHeaders::buildThreadingTree( QMemArray<SortCacheItem *> sortCache )
02993 {
02994     mSortCacheItems.clear();
02995     mSortCacheItems.resize( mFolder->count() * 2 );
02996 
02997     // build a dict of all message id's
02998     for(int x = 0; x < mFolder->count(); x++) {
02999         KMMsgBase *mi = mFolder->getMsgBase(x);
03000         QString md5 = mi->msgIdMD5();
03001         if(!md5.isEmpty())
03002             mSortCacheItems.replace(md5, sortCache[x]);
03003     }
03004 }
03005 
03006 
03007 void KMHeaders::buildSubjectThreadingTree( QMemArray<SortCacheItem *> sortCache )
03008 {
03009     mSubjectLists.clear();  // autoDelete is true
03010     mSubjectLists.resize( mFolder->count() * 2 );
03011 
03012     for(int x = 0; x < mFolder->count(); x++) {
03013         // Only a lot items that are now toplevel
03014         if ( sortCache[x]->parent()
03015           && sortCache[x]->parent()->id() != -666 ) continue;
03016         KMMsgBase *mi = mFolder->getMsgBase(x);
03017         QString subjMD5 = mi->strippedSubjectMD5();
03018         if (subjMD5.isEmpty()) {
03019             mFolder->getMsgBase(x)->initStrippedSubjectMD5();
03020             subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
03021         }
03022         if( subjMD5.isEmpty() ) continue;
03023 
03024         /* For each subject, keep a list of items with that subject
03025          * (stripped of prefixes) sorted by date. */
03026         if (!mSubjectLists.find(subjMD5))
03027             mSubjectLists.insert(subjMD5, new QPtrList<SortCacheItem>());
03028         /* Insertion sort by date. These lists are expected to be very small.
03029          * Also, since the messages are roughly ordered by date in the store,
03030          * they should mostly be prepended at the very start, so insertion is
03031          * cheap. */
03032         int p=0;
03033         for (QPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
03034                 it.current(); ++it) {
03035             KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
03036             if ( mb->date() < mi->date())
03037                 break;
03038             p++;
03039         }
03040         mSubjectLists[subjMD5]->insert( p, sortCache[x]);
03041         sortCache[x]->setSubjectThreadingList( mSubjectLists[subjMD5] );
03042     }
03043 }
03044 
03045 
03046 SortCacheItem* KMHeaders::findParent(SortCacheItem *item)
03047 {
03048     SortCacheItem *parent = NULL;
03049     if (!item) return parent;
03050     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
03051     QString replyToIdMD5 = msg->replyToIdMD5();
03052     item->setImperfectlyThreaded(true);
03053     /* First, try if the message our Reply-To header points to
03054      * is available to thread below. */
03055     if(!replyToIdMD5.isEmpty()) {
03056         parent = mSortCacheItems[replyToIdMD5];
03057         if (parent)
03058             item->setImperfectlyThreaded(false);
03059     }
03060     if (!parent) {
03061         // If we dont have a replyToId, or if we have one and the
03062         // corresponding message is not in this folder, as happens
03063         // if you keep your outgoing messages in an OUTBOX, for
03064         // example, try the list of references, because the second
03065         // to last will likely be in this folder. replyToAuxIdMD5
03066         // contains the second to last one.
03067         QString  ref = msg->replyToAuxIdMD5();
03068         if (!ref.isEmpty())
03069             parent = mSortCacheItems[ref];
03070     }
03071     return parent;
03072 }
03073 
03074 SortCacheItem* KMHeaders::findParentBySubject(SortCacheItem *item)
03075 {
03076     SortCacheItem *parent = NULL;
03077     if (!item) return parent;
03078 
03079     KMMsgBase *msg =  mFolder->getMsgBase(item->id());
03080 
03081     // Let's try by subject, but only if the  subject is prefixed.
03082     // This is necessary to make for example cvs commit mailing lists
03083     // work as expected without having to turn threading off alltogether.
03084     if (!msg->subjectIsPrefixed())
03085         return parent;
03086 
03087     QString replyToIdMD5 = msg->replyToIdMD5();
03088     QString subjMD5 = msg->strippedSubjectMD5();
03089     if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) {
03090         /* Iterate over the list of potential parents with the same
03091          * subject, and take the closest one by date. */
03092         for (QPtrListIterator<SortCacheItem> it2(*mSubjectLists[subjMD5]);
03093                 it2.current(); ++it2) {
03094             KMMsgBase *mb = mFolder->getMsgBase((*it2)->id());
03095             if ( !mb ) return parent;
03096             // make sure it's not ourselves
03097             if ( item == (*it2) ) continue;
03098             int delta = msg->date() - mb->date();
03099             // delta == 0 is not allowed, to avoid circular threading
03100             // with duplicates.
03101             if (delta > 0 ) {
03102                 // Don't use parents more than 6 weeks older than us.
03103                 if (delta < 3628899)
03104                     parent = (*it2);
03105                 break;
03106             }
03107         }
03108     }
03109     return parent;
03110 }
03111 
03112 bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread )
03113 {
03114     if (!mFolder->isOpened()) mFolder->open("kmheaders");
03115 
03116     //all cases
03117     Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended;
03118     Q_INT32 deleted_count = 0;
03119     bool unread_exists = false;
03120     bool jumpToUnread = (GlobalSettings::self()->actionEnterFolder() ==
03121                          GlobalSettings::EnumActionEnterFolder::SelectFirstUnreadNew) ||
03122                         (GlobalSettings::self()->actionEnterFolder() ==
03123                          GlobalSettings::EnumActionEnterFolder::SelectLastUnreadNew) ||
03124                         forceJumpToUnread;
03125     HeaderItem *oldestItem = 0;
03126     HeaderItem *newestItem = 0;
03127     HeaderItem *oldestNewItem = 0;
03128     HeaderItem *newestNewItem = 0;
03129     HeaderItem *oldestNewOrUnreadItem = 0;
03130     HeaderItem *newestNewOrUnreadItem = 0;
03131     QMemArray<SortCacheItem *> sortCache(mFolder->count());
03132     bool error = false;
03133 
03134     //threaded cases
03135     QPtrList<SortCacheItem> unparented;
03136     mImperfectlyThreadedList.clear();
03137 
03138     //cleanup
03139     mItems.fill( 0, mFolder->count() );
03140     sortCache.fill( 0 );
03141 
03142     mRoot->clearChildren();
03143 
03144     QString sortFile = KMAIL_SORT_FILE(mFolder);
03145     FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+");
03146     mSortInfo.fakeSort = 0;
03147 
03148     if(sortStream) {
03149         mSortInfo.fakeSort = 1;
03150         int version = 0;
03151         if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1)
03152           version = -1;
03153         if(version == KMAIL_SORT_VERSION) {
03154           Q_INT32 byteOrder = 0;
03155           fread(&byteOrder, sizeof(byteOrder), 1, sortStream);
03156           if (byteOrder == 0x12345678)
03157           {
03158             fread(&column, sizeof(column), 1, sortStream);
03159             fread(&ascending, sizeof(ascending), 1, sortStream);
03160             fread(&threaded, sizeof(threaded), 1, sortStream);
03161             fread(&appended, sizeof(appended), 1, sortStream);
03162             fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
03163             fread(&sorted_count, sizeof(sorted_count), 1, sortStream);
03164 
03165             //Hackyness to work around qlistview problems
03166             KListView::setSorting(-1);
03167             header()->setSortIndicator(column, ascending);
03168             QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
03169             //setup mSortInfo here now, as above may change it
03170             mSortInfo.dirty = false;
03171             mSortInfo.column = (short)column;
03172             mSortInfo.ascending = (compare_ascending = ascending);
03173 
03174             SortCacheItem *item;
03175             unsigned long serNum, parentSerNum;
03176             int id, len, parent, x;
03177             QChar *tmp_qchar = 0;
03178             int tmp_qchar_len = 0;
03179             const int mFolderCount = mFolder->count();
03180             QString key;
03181 
03182             CREATE_TIMER(parse);
03183             START_TIMER(parse);
03184             for(x = 0; !feof(sortStream) && !error; x++) {
03185                 off_t offset = ftell(sortStream);
03186                 KMFolder *folder;
03187                 //parse
03188                 if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end
03189                    !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) ||
03190                    !fread(&len, sizeof(len), 1, sortStream)) {
03191                     break;
03192                 }
03193                 if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) {
03194                     kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl;
03195                     error = true;
03196                     continue;
03197                 }
03198                 if(len) {
03199                     if(len > tmp_qchar_len) {
03200                         tmp_qchar = (QChar *)realloc(tmp_qchar, len);
03201                         tmp_qchar_len = len;
03202                     }
03203                     if(!fread(tmp_qchar, len, 1, sortStream))
03204                         break;
03205                     key = QString(tmp_qchar, len / 2);
03206                 } else {
03207                     key = QString(""); //yuck
03208                 }
03209 
03210                 KMMsgDict::instance()->getLocation(serNum, &folder, &id);
03211                 if (folder != mFolder) {
03212                     ++deleted_count;
03213                     continue;
03214                 }
03215                 if (parentSerNum < KMAIL_RESERVED) {
03216                     parent = (int)parentSerNum - KMAIL_RESERVED;
03217                 } else {
03218                     KMMsgDict::instance()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent);
03219                     if (folder != mFolder)
03220                         parent = -1;
03221                 }
03222                 if ((id < 0) || (id >= mFolderCount) ||
03223                     (parent < -2) || (parent >= mFolderCount)) { // sanity checking
03224                     kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl;
03225                     error = true;
03226                     continue;
03227                 }
03228 
03229                 if ((item=sortCache[id])) {
03230                     if (item->id() != -1) {
03231                         kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl;
03232                         error = true;
03233                         continue;
03234                     }
03235                     item->setKey(key);
03236                     item->setId(id);
03237                     item->setOffset(offset);
03238                 } else {
03239                     item = sortCache[id] = new SortCacheItem(id, key, offset);
03240                 }
03241                 if (threaded && parent != -2) {
03242                     if(parent == -1) {
03243                         unparented.append(item);
03244                         mRoot->addUnsortedChild(item);
03245                     } else {
03246                         if( ! sortCache[parent] ) {
03247                             sortCache[parent] = new SortCacheItem;
03248                         }
03249                         sortCache[parent]->addUnsortedChild(item);
03250                     }
03251                 } else {
03252                     if(x < sorted_count )
03253                         mRoot->addSortedChild(item);
03254                     else {
03255                         mRoot->addUnsortedChild(item);
03256                     }
03257                 }
03258             }
03259             if (error || (x != sorted_count + discovered_count)) {// sanity check
03260                 kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl;
03261                 fclose(sortStream);
03262                 sortStream = 0;
03263             }
03264 
03265             if(tmp_qchar)
03266                 free(tmp_qchar);
03267             END_TIMER(parse);
03268             SHOW_TIMER(parse);
03269           }
03270           else {
03271               fclose(sortStream);
03272               sortStream = 0;
03273           }
03274         } else {
03275             fclose(sortStream);
03276             sortStream = 0;
03277         }
03278     }
03279 
03280     if (!sortStream) {
03281         mSortInfo.dirty = true;
03282         mSortInfo.column = column = mSortCol;
03283         mSortInfo.ascending = ascending = !mSortDescending;
03284         threaded = (isThreaded());
03285         sorted_count = discovered_count = appended = 0;
03286         KListView::setSorting( mSortCol, !mSortDescending );
03287     }
03288     //fill in empty holes
03289     if((sorted_count + discovered_count - deleted_count) < mFolder->count()) {
03290         CREATE_TIMER(holes);
03291         START_TIMER(holes);
03292         KMMsgBase *msg = 0;
03293         for(int x = 0; x < mFolder->count(); x++) {
03294             if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) {
03295                 int sortOrder = column;
03296                 if (mPaintInfo.orderOfArrival)
03297                     sortOrder |= (1 << 6);
03298                 if (mPaintInfo.status)
03299                     sortOrder |= (1 << 5);
03300                 sortCache[x] = new SortCacheItem(
03301                     x, HeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder ));
03302                 if(threaded)
03303                     unparented.append(sortCache[x]);
03304                 else
03305                     mRoot->addUnsortedChild(sortCache[x]);
03306                 if(sortStream)
03307                     sortCache[x]->updateSortFile(sortStream, mFolder, true, true);
03308                 discovered_count++;
03309                 appended = 1;
03310             }
03311         }
03312         END_TIMER(holes);
03313         SHOW_TIMER(holes);
03314     }
03315 
03316     // Make sure we've placed everything in parent/child relationship. All
03317     // messages with a parent id of -1 in the sort file are reevaluated here.
03318     if (threaded) buildThreadingTree( sortCache );
03319     QPtrList<SortCacheItem> toBeSubjThreaded;
03320 
03321     if (threaded && !unparented.isEmpty()) {
03322         CREATE_TIMER(reparent);
03323         START_TIMER(reparent);
03324 
03325         for(QPtrListIterator<SortCacheItem> it(unparented); it.current(); ++it) {
03326             SortCacheItem *item = (*it);
03327             SortCacheItem *parent = findParent( item );
03328             // If we have a parent, make sure it's not ourselves
03329             if ( parent && (parent != (*it)) ) {
03330                 parent->addUnsortedChild((*it));
03331                 if(sortStream)
03332                     (*it)->updateSortFile(sortStream, mFolder);
03333             } else {
03334                 // if we will attempt subject threading, add to the list,
03335                 // otherwise to the root with them
03336                 if (mSubjThreading)
03337                   toBeSubjThreaded.append((*it));
03338                 else
03339                   mRoot->addUnsortedChild((*it));
03340             }
03341         }
03342 
03343         if (mSubjThreading) {
03344             buildSubjectThreadingTree( sortCache );
03345             for(QPtrListIterator<SortCacheItem> it(toBeSubjThreaded); it.current(); ++it) {
03346                 SortCacheItem *item = (*it);
03347                 SortCacheItem *parent = findParentBySubject( item );
03348 
03349                 if ( parent ) {
03350                     parent->addUnsortedChild((*it));
03351                     if(sortStream)
03352                       (*it)->updateSortFile(sortStream, mFolder);
03353                 } else {
03354                     //oh well we tried, to the root with you!
03355                     mRoot->addUnsortedChild((*it));
03356                 }
03357             }
03358         }
03359         END_TIMER(reparent);
03360         SHOW_TIMER(reparent);
03361     }
03362     //create headeritems
03363     CREATE_TIMER(header_creation);
03364     START_TIMER(header_creation);
03365     HeaderItem *khi;
03366     SortCacheItem *i, *new_kci;
03367     QPtrQueue<SortCacheItem> s;
03368     s.enqueue(mRoot);
03369     compare_toplevel = true;
03370     do {
03371         i = s.dequeue();
03372         const QPtrList<SortCacheItem> *sorted = i->sortedChildren();
03373         int unsorted_count, unsorted_off=0;
03374         SortCacheItem **unsorted = i->unsortedChildren(unsorted_count);
03375         if(unsorted)
03376             qsort(unsorted, unsorted_count, sizeof(SortCacheItem *), //sort
03377                   compare_SortCacheItem);
03378 
03379         /* The sorted list now contains all sorted children of this item, while
03380          * the (aptly named) unsorted array contains all as of yet unsorted
03381          * ones. It has just been qsorted, so it is in itself sorted. These two
03382          * sorted lists are now merged into one. */
03383         for(QPtrListIterator<SortCacheItem> it(*sorted);
03384             (unsorted && unsorted_off < unsorted_count) || it.current(); ) {
03385             /* As long as we have something in the sorted list and there is
03386                nothing unsorted left, use the item from the sorted list. Also
03387                if we are sorting descendingly and the sorted item is supposed
03388                to be sorted before the unsorted one do so. In the ascending
03389                case we invert the logic for non top level items. */
03390             if( it.current() &&
03391                ( !unsorted || unsorted_off >= unsorted_count
03392                 ||
03393                 ( ( !ascending || (ascending && !compare_toplevel) )
03394                   && (*it)->key() < unsorted[unsorted_off]->key() )
03395                 ||
03396                 (  ascending && (*it)->key() >= unsorted[unsorted_off]->key() )
03397                 )
03398                )
03399             {
03400                 new_kci = (*it);
03401                 ++it;
03402             } else {
03403                 /* Otherwise use the next item of the unsorted list */
03404                 new_kci = unsorted[unsorted_off++];
03405             }
03406             if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent
03407                 continue;
03408 
03409             if(threaded && i->item()) {
03410                 // If the parent is watched or ignored, propagate that to it's
03411                 // children
03412                 if (mFolder->getMsgBase(i->id())->isWatched())
03413                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusWatched);
03414                 if (mFolder->getMsgBase(i->id())->isIgnored())
03415                   mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusIgnored);
03416                 khi = new HeaderItem(i->item(), new_kci->id(), new_kci->key());
03417             } else {
03418                 khi = new HeaderItem(this, new_kci->id(), new_kci->key());
03419             }
03420             new_kci->setItem(mItems[new_kci->id()] = khi);
03421             if(new_kci->hasChildren())
03422                 s.enqueue(new_kci);
03423             // we always jump to new messages, but we only jump to
03424             // unread messages if we are told to do so
03425             if ( ( mFolder->getMsgBase(new_kci->id())->isNew() &&
03426                    (GlobalSettings::self()->actionEnterFolder() ==
03427                     GlobalSettings::EnumActionEnterFolder::SelectFirstNew ||
03428                     GlobalSettings::self()->actionEnterFolder() ==
03429                     GlobalSettings::EnumActionEnterFolder::SelectLastNew ) ) ||
03430                  ( ( mFolder->getMsgBase(new_kci->id())->isNew() ||
03431                      mFolder->getMsgBase(new_kci->id())->isUnread() ) &&
03432                    jumpToUnread ) )
03433             {
03434               unread_exists = true;
03435             }
03436 
03437             if ( !oldestItem || mFolder->getMsgBase( oldestItem->msgId() )->date() >
03438                   mFolder->getMsgBase( new_kci->id() )->date() ) {
03439               oldestItem = khi;
03440             }
03441 
03442             if ( !newestItem || mFolder->getMsgBase( newestItem->msgId() )->date() <
03443                   mFolder->getMsgBase( new_kci->id() )->date() ) {
03444               newestItem = khi;
03445             }
03446 
03447             if ( mFolder->getMsgBase(new_kci->id())->isNew() ) {
03448               if ( !oldestNewItem || mFolder->getMsgBase( oldestNewItem->msgId() )->date() >
03449                    mFolder->getMsgBase( new_kci->id() )->date() ) {
03450                 oldestNewItem = khi;
03451               }
03452 
03453               if ( !newestNewItem || mFolder->getMsgBase( newestNewItem->msgId() )->date() <
03454                     mFolder->getMsgBase( new_kci->id() )->date() ) {
03455                 newestNewItem = khi;
03456               }
03457             }
03458 
03459             if ( mFolder->getMsgBase(new_kci->id())->isNew() ||
03460                  mFolder->getMsgBase(new_kci->id())->isUnread() ) {
03461               if ( !oldestNewOrUnreadItem || mFolder->getMsgBase( oldestNewOrUnreadItem->msgId() )->date() >
03462                    mFolder->getMsgBase( new_kci->id() )->date() ) {
03463                 oldestNewOrUnreadItem = khi;
03464               }
03465 
03466               if ( !newestNewOrUnreadItem || mFolder->getMsgBase( newestNewOrUnreadItem->msgId() )->date() <
03467                     mFolder->getMsgBase( new_kci->id() )->date() ) {
03468                 newestNewOrUnreadItem = khi;
03469               }
03470             }
03471         }
03472         // If we are sorting by date and ascending the top level items are sorted
03473         // ascending and the threads themselves are sorted descending. One wants
03474         // to have new threads on top but the threads themselves top down.
03475         if (mSortCol == paintInfo()->dateCol)
03476           compare_toplevel = false;
03477     } while(!s.isEmpty());
03478 
03479     for(int x = 0; x < mFolder->count(); x++) {     //cleanup
03480         if (!sortCache[x]) { // not yet there?
03481             continue;
03482         }
03483 
03484         if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
03485             kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. "
03486                   << endl << "Please talk to your threading counselor asap. " <<  endl;
03487             khi = new HeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
03488             sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
03489         }
03490         // Add all imperfectly threaded items to a list, so they can be
03491         // reevaluated when a new message arrives which might be a better parent.
03492         // Important for messages arriving out of order.
03493         if (threaded && sortCache[x]->isImperfectlyThreaded()) {
03494             mImperfectlyThreadedList.append(sortCache[x]->item());
03495         }
03496         // Set the reverse mapping HeaderItem -> SortCacheItem. Needed for
03497         // keeping the data structures up to date on removal, for example.
03498         sortCache[x]->item()->setSortCacheItem(sortCache[x]);
03499     }
03500 
03501     if (getNestingPolicy()<2)
03502       for (HeaderItem *khi=static_cast<HeaderItem*>(firstChild()); khi!=0;khi=static_cast<HeaderItem*>(khi->nextSibling()))
03503         khi->setOpen(true);
03504 
03505     END_TIMER(header_creation);
03506     SHOW_TIMER(header_creation);
03507 
03508     if(sortStream) { //update the .sorted file now
03509         // heuristic for when it's time to rewrite the .sorted file
03510         if( discovered_count * discovered_count > sorted_count - deleted_count ) {
03511             mSortInfo.dirty = true;
03512         } else {
03513             //update the appended flag
03514             appended = 0;
03515             fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
03516             fwrite(&appended, sizeof(appended), 1, sortStream);
03517         }
03518     }
03519 
03520     // Select a message, depending on the "When entering a folder:" setting
03521     CREATE_TIMER(selection);
03522     START_TIMER(selection);
03523     if(set_selection) {
03524 
03525         // Search for the id of the first unread/new item, should there be any
03526         int first_unread = -1;
03527         if (unread_exists) {
03528             HeaderItem *item = static_cast<HeaderItem*>(firstChild());
03529             while (item) {
03530               if ( ( mFolder->getMsgBase(item->msgId())->isNew() &&
03531                      (GlobalSettings::self()->actionEnterFolder() ==
03532                       GlobalSettings::EnumActionEnterFolder::SelectFirstNew ||
03533                       GlobalSettings::self()->actionEnterFolder() ==
03534                       GlobalSettings::EnumActionEnterFolder::SelectLastNew ) ) ||
03535                    ( ( mFolder->getMsgBase(item->msgId())->isNew() ||
03536                        mFolder->getMsgBase(item->msgId())->isUnread() ) &&
03537                      jumpToUnread ) )
03538               {
03539                 first_unread = item->msgId();
03540                 break;
03541               }
03542               item = static_cast<HeaderItem*>(item->itemBelow());
03543             }
03544         }
03545 
03546         // No unread message to select, so either select the newest, oldest or lastest selected
03547         if(first_unread == -1 ) {
03548             setTopItemByIndex( mTopItem );
03549 
03550             if ( GlobalSettings::self()->actionEnterFolder() ==
03551                  GlobalSettings::EnumActionEnterFolder::SelectNewest && newestItem != 0 ) {
03552               setCurrentItemByIndex( newestItem->msgId() );
03553             }
03554             else if ( GlobalSettings::self()->actionEnterFolder() ==
03555                       GlobalSettings::EnumActionEnterFolder::SelectOldest && oldestItem != 0 ) {
03556               setCurrentItemByIndex( oldestItem->msgId() );
03557             }
03558             else if ( mCurrentItem >= 0 )
03559               setCurrentItemByIndex( mCurrentItem );
03560             else if ( mCurrentItemSerNum > 0 )
03561               setCurrentItemBySerialNum( mCurrentItemSerNum );
03562             else
03563               setCurrentItemByIndex( 0 );
03564 
03565         // There is an unread item to select, so select it
03566         } else {
03567             if ( GlobalSettings::self()->actionEnterFolder() ==
03568                  GlobalSettings::EnumActionEnterFolder::SelectFirstNew && newestNewItem != 0 ) {
03569               setCurrentItemByIndex( newestNewItem->msgId() );
03570             }
03571             else if ( GlobalSettings::self()->actionEnterFolder() ==
03572                       GlobalSettings::EnumActionEnterFolder::SelectLastNew && oldestNewItem != 0 ) {
03573               setCurrentItemByIndex( oldestNewItem->msgId() );
03574             }
03575             else if ( GlobalSettings::self()->actionEnterFolder() ==
03576                  GlobalSettings::EnumActionEnterFolder::SelectFirstUnreadNew && newestNewOrUnreadItem != 0 ) {
03577               setCurrentItemByIndex( newestNewOrUnreadItem->msgId() );
03578             }
03579             else if ( GlobalSettings::self()->actionEnterFolder() ==
03580                       GlobalSettings::EnumActionEnterFolder::SelectLastUnreadNew && oldestNewOrUnreadItem != 0 ) {
03581               setCurrentItemByIndex( oldestNewOrUnreadItem->msgId() );
03582             }
03583             else {
03584               setCurrentItemByIndex(first_unread);
03585               makeHeaderVisible();
03586               center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 );
03587             }
03588         }
03589 
03590     // we are told to not change the selection
03591     } else {
03592         // only reset the selection if we have no current item
03593         if (mCurrentItem <= 0) {
03594           setTopItemByIndex(mTopItem);
03595           setCurrentItemByIndex(0);
03596         }
03597     }
03598     END_TIMER(selection);
03599     SHOW_TIMER(selection);
03600     if (error || (sortStream && ferror(sortStream))) {
03601         if ( sortStream )
03602             fclose(sortStream);
03603         unlink(QFile::encodeName(sortFile));
03604         kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
03605         kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
03606 
03607         return true;
03608     }
03609     if(sortStream)
03610         fclose(sortStream);
03611 
03612     return true;
03613 }
03614 
03615 //-----------------------------------------------------------------------------
03616 void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum )
03617 {
03618   // Linear search == slow. Don't overuse this method.
03619   // It's currently only used for finding the current item again
03620   // after expiry deleted mails (so the index got invalidated).
03621   for (int i = 0; i < (int)mItems.size() - 1; ++i) {
03622     KMMsgBase *mMsgBase = mFolder->getMsgBase( i );
03623     if ( mMsgBase->getMsgSerNum() == serialNum ) {
03624       bool unchanged = (currentItem() == mItems[i]);
03625       setCurrentItem( mItems[i] );
03626       setSelected( mItems[i], true );
03627       setSelectionAnchor( currentItem() );
03628       if ( unchanged )
03629         highlightMessage( currentItem(), false );
03630       ensureCurrentItemVisible();
03631       return;
03632     }
03633   }
03634   // Not found. Maybe we should select the last item instead?
03635   kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl;
03636 }
03637 
03638 void KMHeaders::copyMessages()
03639 {
03640   mCopiedMessages.clear();
03641   KMMessageList* list = selectedMsgs();
03642   for ( uint i = 0; i < list->count(); ++ i )
03643     mCopiedMessages << list->at( i )->getMsgSerNum();
03644   mMoveMessages = false;
03645   updateActions();
03646   triggerUpdate();
03647 }
03648 
03649 void KMHeaders::cutMessages()
03650 {
03651   mCopiedMessages.clear();
03652   KMMessageList* list = selectedMsgs();
03653   for ( uint i = 0; i < list->count(); ++ i )
03654     mCopiedMessages << list->at( i )->getMsgSerNum();
03655   mMoveMessages = true;
03656   updateActions();
03657   triggerUpdate();
03658 }
03659 
03660 void KMHeaders::pasteMessages()
03661 {
03662   new MessageCopyHelper( mCopiedMessages, folder(), mMoveMessages, this );
03663   if ( mMoveMessages ) {
03664     mCopiedMessages.clear();
03665     updateActions();
03666   }
03667 }
03668 
03669 void KMHeaders::updateActions()
03670 {
03671   KAction *copy = owner()->action( "copy_messages" );
03672   KAction *cut = owner()->action( "cut_messages" );
03673   KAction *paste = owner()->action( "paste_messages" );
03674 
03675   if ( selectedItems().isEmpty() ) {
03676     copy->setEnabled( false );
03677     cut->setEnabled( false );
03678   } else {
03679     copy->setEnabled( true );
03680     if ( folder() && !folder()->canDeleteMessages() )
03681       cut->setEnabled( false );
03682     else
03683       cut->setEnabled( true );
03684   }
03685 
03686   if ( mCopiedMessages.isEmpty() || !folder() || folder()->isReadOnly() )
03687     paste->setEnabled( false );
03688   else
03689     paste->setEnabled( true );
03690 }
03691 
03692 void KMHeaders::setCopiedMessages(const QValueList< Q_UINT32 > & msgs, bool move)
03693 {
03694   mCopiedMessages = msgs;
03695   mMoveMessages = move;
03696   updateActions();
03697 }
03698 
03699 bool KMHeaders::isMessageCut(Q_UINT32 serNum) const
03700 {
03701   return mMoveMessages && mCopiedMessages.contains( serNum );
03702 }
03703 
03704 QValueList< Q_UINT32 > KMHeaders::selectedSernums()
03705 {
03706   QValueList<Q_UINT32> list;
03707   for ( QListViewItemIterator it(this); it.current(); it++ ) {
03708     if ( it.current()->isSelected() && it.current()->isVisible() ) {
03709       HeaderItem* item = static_cast<HeaderItem*>( it.current() );
03710       KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
03711       if ( msgBase ) {
03712         list.append( msgBase->getMsgSerNum() );
03713       }
03714     }
03715   }
03716   return list;
03717 }
03718 
03719 QValueList< Q_UINT32 > KMHeaders::selectedVisibleSernums()
03720 {
03721   QValueList<Q_UINT32> list;
03722   QListViewItemIterator it(this, QListViewItemIterator::Selected|QListViewItemIterator::Visible);
03723   while( it.current() ) {
03724     if ( it.current()->isSelected() && it.current()->isVisible() ) {
03725       if ( it.current()->parent() && ( !it.current()->parent()->isOpen() ) ) {
03726         // the item's parent is closed, don't traverse any more of this subtree
03727         QListViewItem * lastAncestorWithSiblings = it.current()->parent();
03728         // travel towards the root until we find an ancestor with siblings
03729         while ( ( lastAncestorWithSiblings->depth() > 0 ) && !lastAncestorWithSiblings->nextSibling() )
03730           lastAncestorWithSiblings = lastAncestorWithSiblings->parent();
03731         // move the iterator to that ancestor's next sibling
03732         it = QListViewItemIterator( lastAncestorWithSiblings->nextSibling() );
03733         continue;
03734       }
03735       HeaderItem *item = static_cast<HeaderItem*>(it.current());
03736       KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
03737       if ( msgBase ) {
03738         list.append( msgBase->getMsgSerNum() );
03739       }
03740     }
03741     ++it;
03742   }
03743 
03744   return list;
03745 }
03746 
03747 void KMHeaders::batchRemovingStarted()
03748 {
03749   mBatchRemovingInProgress = true;
03750 }
03751 
03752 void KMHeaders::batchRemovingFinished()
03753 {
03754   mBatchRemovingInProgress = false;
03755 }
03756 
03757 #include "kmheaders.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys