kmail

kmheaders.cpp

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