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