kmail

kmheaders.cpp

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