kmail

kmheaders.cpp

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