kmail Library API Documentation

kmheaders.cpp

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