kmail

kmheaders.cpp

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