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