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 CREATE_TIMER(updateMsg); 00992 START_TIMER(updateMsg); 00993 updateMessageList(true, forceJumpToUnread); 00994 END_TIMER(updateMsg); 00995 SHOW_TIMER(updateMsg); 00996 makeHeaderVisible(); 00997 } 00998 /* Doesn't the below only need to be done when the folder changed? - till */ 00999 setFolderInfoStatus(); 01000 01001 QString colText = i18n( "Sender" ); 01002 if (mFolder && (mFolder->whoField().lower() == "to")) 01003 colText = i18n("Receiver"); 01004 setColumnText( mPaintInfo.senderCol, colText); 01005 01006 colText = i18n( "Date" ); 01007 if (mPaintInfo.orderOfArrival) 01008 colText = i18n( "Date (Order of Arrival)" ); 01009 setColumnText( mPaintInfo.dateCol, colText); 01010 01011 colText = i18n( "Subject" ); 01012 if (mPaintInfo.status) 01013 colText = colText + i18n( " (Status)" ); 01014 setColumnText( mPaintInfo.subCol, colText); 01015 01016 END_TIMER(set_folder); 01017 SHOW_TIMER(set_folder); 01018 } 01019 01020 //----------------------------------------------------------------------------- 01021 void KMHeaders::msgChanged() 01022 { 01023 emit maybeDeleting(); 01024 if (mFolder->count() == 0) { // Folder cleared 01025 clear(); 01026 return; 01027 } 01028 int i = topItemIndex(); 01029 int cur = currentItemIndex(); 01030 if (!isUpdatesEnabled()) return; 01031 QString msgIdMD5; 01032 QListViewItem *item = currentItem(); 01033 KMHeaderItem *hi = dynamic_cast<KMHeaderItem*>(item); 01034 if (item && hi) { 01035 KMMsgBase *mb = mFolder->getMsgBase(hi->msgId()); 01036 if (mb) 01037 msgIdMD5 = mb->msgIdMD5(); 01038 } 01039 if (!isUpdatesEnabled()) return; 01040 // prevent IMAP messages from scrolling to top 01041 disconnect(this,SIGNAL(currentChanged(QListViewItem*)), 01042 this,SLOT(highlightMessage(QListViewItem*))); 01043 // remember all selected messages 01044 QValueList<int> curItems = selectedItems(); 01045 updateMessageList(); // do not change the selection 01046 // restore the old state 01047 setTopItemByIndex( i ); 01048 setCurrentMsg( cur ); 01049 setSelectedByIndex( curItems, true ); 01050 connect(this,SIGNAL(currentChanged(QListViewItem*)), 01051 this,SLOT(highlightMessage(QListViewItem*))); 01052 01053 // if the current message has changed then emit 01054 // the selected signal to force an update 01055 01056 // Normally the serial number of the message would be 01057 // used to do this, but because we don't yet have 01058 // guaranteed serial numbers for IMAP messages fall back 01059 // to using the MD5 checksum of the msgId. 01060 item = currentItem(); 01061 hi = dynamic_cast<KMHeaderItem*>(item); 01062 if (item && hi) { 01063 KMMsgBase *mb = mFolder->getMsgBase(hi->msgId()); 01064 if (mb) { 01065 if (msgIdMD5.isEmpty() || (msgIdMD5 != mb->msgIdMD5())) 01066 emit selected(mFolder->getMsg(hi->msgId())); 01067 } else { 01068 emit selected(0); 01069 } 01070 } else 01071 emit selected(0); 01072 } 01073 01074 01075 //----------------------------------------------------------------------------- 01076 void KMHeaders::msgAdded(int id) 01077 { 01078 KMHeaderItem* hi = 0; 01079 if (!isUpdatesEnabled()) return; 01080 01081 CREATE_TIMER(msgAdded); 01082 START_TIMER(msgAdded); 01083 01084 assert( mFolder->getMsgBase( id ) ); // otherwise using count() is wrong 01085 01086 /* Create a new KMSortCacheItem to be used for threading. */ 01087 KMSortCacheItem *sci = new KMSortCacheItem; 01088 sci->setId(id); 01089 if (isThreaded()) { 01090 // make sure the id and subject dicts grow, if necessary 01091 if (mSortCacheItems.count() == (uint)mFolder->count() 01092 || mSortCacheItems.count() == 0) { 01093 kdDebug (5006) << "KMHeaders::msgAdded - Resizing id and subject trees of " << mFolder->label() 01094 << ": before=" << mSortCacheItems.count() << " ,after=" << (mFolder->count()*2) << endl; 01095 mSortCacheItems.resize(mFolder->count()*2); 01096 mSubjectLists.resize(mFolder->count()*2); 01097 } 01098 QString msgId = mFolder->getMsgBase(id)->msgIdMD5(); 01099 if (msgId.isNull()) 01100 msgId = ""; 01101 QString replyToId = mFolder->getMsgBase(id)->replyToIdMD5(); 01102 01103 KMSortCacheItem *parent = findParent( sci ); 01104 if (!parent && mSubjThreading) { 01105 parent = findParentBySubject( sci ); 01106 if (parent && sci->isImperfectlyThreaded()) { 01107 // The parent we found could be by subject, in which case it is 01108 // possible, that it would be preferrable to thread it below us, 01109 // not the other way around. Check that. This is not only 01110 // cosmetic, as getting this wrong leads to circular threading. 01111 if (msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToIdMD5() 01112 || msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToAuxIdMD5()) 01113 parent = NULL; 01114 } 01115 } 01116 01117 if (parent && mFolder->getMsgBase(parent->id())->isWatched()) 01118 mFolder->getMsgBase(id)->setStatus( KMMsgStatusWatched ); 01119 else if (parent && mFolder->getMsgBase(parent->id())->isIgnored()) { 01120 mFolder->getMsgBase(id)->setStatus( KMMsgStatusIgnored ); 01121 mFolder->setStatus( id, KMMsgStatusRead ); 01122 } 01123 if (parent) 01124 hi = new KMHeaderItem( parent->item(), id ); 01125 else 01126 hi = new KMHeaderItem( this, id ); 01127 01128 // o/` ... my buddy and me .. o/` 01129 hi->setSortCacheItem(sci); 01130 sci->setItem(hi); 01131 01132 // Update and resize the id trees. 01133 mItems.resize( mFolder->count() ); 01134 mItems[id] = hi; 01135 01136 if ( !msgId.isEmpty() ) 01137 mSortCacheItems.replace(msgId, sci); 01138 /* Add to the list of potential parents for subject threading. But only if 01139 * we are top level. */ 01140 if (mSubjThreading && parent) { 01141 QString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5(); 01142 if (subjMD5.isEmpty()) { 01143 mFolder->getMsgBase(id)->initStrippedSubjectMD5(); 01144 subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5(); 01145 } 01146 if( !subjMD5.isEmpty()) { 01147 if ( !mSubjectLists.find(subjMD5) ) 01148 mSubjectLists.insert(subjMD5, new QPtrList<KMSortCacheItem>()); 01149 // insertion sort by date. See buildThreadTrees for details. 01150 int p=0; 01151 for (QPtrListIterator<KMSortCacheItem> it(*mSubjectLists[subjMD5]); 01152 it.current(); ++it) { 01153 KMMsgBase *mb = mFolder->getMsgBase((*it)->id()); 01154 if ( mb->date() < mFolder->getMsgBase(id)->date()) 01155 break; 01156 p++; 01157 } 01158 mSubjectLists[subjMD5]->insert( p, sci); 01159 } 01160 } 01161 // The message we just added might be a better parent for one of the as of 01162 // yet imperfectly threaded messages. Let's find out. 01163 01164 /* In case the current item is taken during reparenting, prevent qlistview 01165 * from selecting some unrelated item as a result of take() emitting 01166 * currentChanged. */ 01167 disconnect( this, SIGNAL(currentChanged(QListViewItem*)), 01168 this, SLOT(highlightMessage(QListViewItem*))); 01169 01170 if ( !msgId.isEmpty() ) { 01171 QPtrListIterator<KMHeaderItem> it(mImperfectlyThreadedList); 01172 KMHeaderItem *cur; 01173 while ( (cur = it.current()) ) { 01174 ++it; 01175 int tryMe = cur->msgId(); 01176 // Check, whether our message is the replyToId or replyToAuxId of 01177 // this one. If so, thread it below our message, unless it is already 01178 // correctly threaded by replyToId. 01179 bool perfectParent = true; 01180 KMMsgBase *otherMsg = mFolder->getMsgBase(tryMe); 01181 if ( !otherMsg ) { 01182 kdDebug(5006) << "otherMsg is NULL !!! tryMe: " << tryMe << endl; 01183 continue; 01184 } 01185 QString otherId = otherMsg->replyToIdMD5(); 01186 if (msgId != otherId) { 01187 if (msgId != otherMsg->replyToAuxIdMD5()) 01188 continue; 01189 else { 01190 if (!otherId.isEmpty() && mSortCacheItems.find(otherId)) 01191 continue; 01192 else 01193 // Thread below us by aux id, but keep on the list of 01194 // imperfectly threaded messages. 01195 perfectParent = false; 01196 } 01197 } 01198 QListViewItem *newParent = mItems[id]; 01199 QListViewItem *msg = mItems[tryMe]; 01200 01201 if (msg->parent()) 01202 msg->parent()->takeItem(msg); 01203 else 01204 takeItem(msg); 01205 newParent->insertItem(msg); 01206 01207 makeHeaderVisible(); 01208 01209 if (perfectParent) { 01210 mImperfectlyThreadedList.removeRef (mItems[tryMe]); 01211 // The item was imperfectly thread before, now it's parent 01212 // is there. Update the .sorted file accordingly. 01213 QString sortFile = KMAIL_SORT_FILE(mFolder); 01214 FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+"); 01215 if (sortStream) { 01216 mItems[tryMe]->sortCacheItem()->updateSortFile( sortStream, mFolder ); 01217 fclose (sortStream); 01218 } 01219 } 01220 } 01221 } 01222 // Add ourselves only now, to avoid circularity above. 01223 if (hi && hi->sortCacheItem()->isImperfectlyThreaded()) 01224 mImperfectlyThreadedList.append(hi); 01225 } else { 01226 // non-threaded case 01227 hi = new KMHeaderItem( this, id ); 01228 mItems.resize( mFolder->count() ); 01229 mItems[id] = hi; 01230 // o/` ... my buddy and me .. o/` 01231 hi->setSortCacheItem(sci); 01232 sci->setItem(hi); 01233 } 01234 if (mSortInfo.fakeSort) { 01235 QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); 01236 KListView::setSorting(mSortCol, !mSortDescending ); 01237 mSortInfo.fakeSort = 0; 01238 } 01239 appendItemToSortFile(hi); //inserted into sorted list 01240 01241 msgHeaderChanged(mFolder,id); 01242 01243 if ((childCount() == 1) && hi) { 01244 setSelected( hi, true ); 01245 setCurrentItem( firstChild() ); 01246 setSelectionAnchor( currentItem() ); 01247 highlightMessage( currentItem() ); 01248 } 01249 01250 /* restore signal */ 01251 connect( this, SIGNAL(currentChanged(QListViewItem*)), 01252 this, SLOT(highlightMessage(QListViewItem*))); 01253 01254 emit messageListUpdated(); 01255 END_TIMER(msgAdded); 01256 SHOW_TIMER(msgAdded); 01257 } 01258 01259 01260 //----------------------------------------------------------------------------- 01261 void KMHeaders::msgRemoved(int id, QString msgId, QString strippedSubjMD5) 01262 { 01263 if (!isUpdatesEnabled()) return; 01264 01265 if ((id < 0) || (id >= (int)mItems.size())) 01266 return; 01267 /* 01268 * qlistview has its own ideas about what to select as the next 01269 * item once this one is removed. Sine we have already selected 01270 * something in prepare/finalizeMove that's counter productive 01271 */ 01272 disconnect( this, SIGNAL(currentChanged(QListViewItem*)), 01273 this, SLOT(highlightMessage(QListViewItem*))); 01274 01275 KMHeaderItem *removedItem = mItems[id]; 01276 if (!removedItem) return; 01277 KMHeaderItem *curItem = currentHeaderItem(); 01278 01279 for (int i = id; i < (int)mItems.size() - 1; ++i) { 01280 mItems[i] = mItems[i+1]; 01281 mItems[i]->setMsgId( i ); 01282 mItems[i]->sortCacheItem()->setId( i ); 01283 } 01284 01285 mItems.resize( mItems.size() - 1 ); 01286 01287 if (isThreaded() && mFolder->count()) { 01288 if ( !msgId.isEmpty() && mSortCacheItems[msgId] ) { 01289 if (mSortCacheItems[msgId] == removedItem->sortCacheItem()) 01290 mSortCacheItems.remove(msgId); 01291 } 01292 // Remove the message from the list of potential parents for threading by 01293 // subject. 01294 if (!strippedSubjMD5.isEmpty() && 01295 mSubjThreading && mSubjectLists[strippedSubjMD5]) 01296 mSubjectLists[strippedSubjMD5]->remove(removedItem->sortCacheItem()); 01297 01298 // Reparent children of item. 01299 QListViewItem *myParent = removedItem; 01300 QListViewItem *myChild = myParent->firstChild(); 01301 QListViewItem *threadRoot = myParent; 01302 while (threadRoot->parent()) 01303 threadRoot = threadRoot->parent(); 01304 QString key = static_cast<KMHeaderItem*>(threadRoot)->key(mSortCol, !mSortDescending); 01305 01306 QPtrList<QListViewItem> childList; 01307 while (myChild) { 01308 KMHeaderItem *item = static_cast<KMHeaderItem*>(myChild); 01309 // Just keep the item at top level, if it will be deleted anyhow 01310 if ( !item->aboutToBeDeleted() ) { 01311 childList.append(myChild); 01312 } 01313 myChild = myChild->nextSibling(); 01314 if ( item->aboutToBeDeleted() ) { 01315 myParent->takeItem( item ); 01316 insertItem( item ); 01317 } 01318 item->setTempKey( key + item->key( mSortCol, !mSortDescending )); 01319 if (mSortInfo.fakeSort) { 01320 QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); 01321 KListView::setSorting(mSortCol, !mSortDescending ); 01322 mSortInfo.fakeSort = 0; 01323 } 01324 } 01325 01326 for (QPtrListIterator<QListViewItem> it(childList); it.current() ; ++it ) { 01327 QListViewItem *lvi = *it; 01328 KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi); 01329 KMSortCacheItem *sci = item->sortCacheItem(); 01330 KMSortCacheItem *parent = findParent( sci ); 01331 if (!parent) parent = findParentBySubject( sci ); 01332 myParent->takeItem(lvi); 01333 if (parent && parent->item() != item) 01334 parent->item()->insertItem(lvi); 01335 else 01336 insertItem(lvi); 01337 01338 if (!parent || (sci->isImperfectlyThreaded() 01339 && !mImperfectlyThreadedList.containsRef(item))) 01340 mImperfectlyThreadedList.append(item); 01341 if (parent && !sci->isImperfectlyThreaded() 01342 && mImperfectlyThreadedList.containsRef(item)) 01343 mImperfectlyThreadedList.removeRef(item); 01344 } 01345 } 01346 // Make sure our data structures are cleared. 01347 if (!mFolder->count()) 01348 folderCleared(); 01349 01350 mImperfectlyThreadedList.removeRef(removedItem); 01351 delete removedItem; 01352 // we might have rethreaded it, in which case its current state will be lost 01353 if ( curItem ) { 01354 if ( curItem != removedItem ) { 01355 setCurrentItem( curItem ); 01356 setSelectionAnchor( currentItem() ); 01357 } else { 01358 emit maybeDeleting(); 01359 } 01360 } 01361 01362 /* restore signal */ 01363 connect( this, SIGNAL(currentChanged(QListViewItem*)), 01364 this, SLOT(highlightMessage(QListViewItem*))); 01365 01366 } 01367 01368 01369 //----------------------------------------------------------------------------- 01370 void KMHeaders::msgHeaderChanged(KMFolder*, int msgId) 01371 { 01372 if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return; 01373 KMHeaderItem *item = mItems[msgId]; 01374 if (item) { 01375 item->irefresh(); 01376 item->repaint(); 01377 } 01378 } 01379 01380 01381 //----------------------------------------------------------------------------- 01382 void KMHeaders::setMsgStatus (KMMsgStatus status, bool toggle) 01383 { 01384 SerNumList serNums; 01385 for (QListViewItemIterator it(this); it.current(); ++it) 01386 if ( it.current()->isSelected() && it.current()->isVisible() ) { 01387 KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); 01388 KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); 01389 serNums.append( msgBase->getMsgSerNum() ); 01390 } 01391 if (serNums.empty()) 01392 return; 01393 01394 KMCommand *command = new KMSetStatusCommand( status, serNums, toggle ); 01395 command->start(); 01396 } 01397 01398 01399 QPtrList<QListViewItem> KMHeaders::currentThread() const 01400 { 01401 if (!mFolder) return QPtrList<QListViewItem>(); 01402 01403 // starting with the current item... 01404 QListViewItem *curItem = currentItem(); 01405 if (!curItem) return QPtrList<QListViewItem>(); 01406 01407 // ...find the top-level item: 01408 QListViewItem *topOfThread = curItem; 01409 while ( topOfThread->parent() ) 01410 topOfThread = topOfThread->parent(); 01411 01412 // collect the items in this thread: 01413 QPtrList<QListViewItem> list; 01414 QListViewItem *topOfNextThread = topOfThread->nextSibling(); 01415 for ( QListViewItemIterator it( topOfThread ) ; 01416 it.current() && it.current() != topOfNextThread ; ++it ) 01417 list.append( it.current() ); 01418 return list; 01419 } 01420 01421 void KMHeaders::setThreadStatus(KMMsgStatus status, bool toggle) 01422 { 01423 QPtrList<QListViewItem> curThread = currentThread(); 01424 QPtrListIterator<QListViewItem> it( curThread ); 01425 SerNumList serNums; 01426 01427 for ( it.toFirst() ; it.current() ; ++it ) { 01428 int id = static_cast<KMHeaderItem*>(*it)->msgId(); 01429 KMMsgBase *msgBase = mFolder->getMsgBase( id ); 01430 serNums.append( msgBase->getMsgSerNum() ); 01431 } 01432 01433 if (serNums.empty()) 01434 return; 01435 01436 KMCommand *command = new KMSetStatusCommand( status, serNums, toggle ); 01437 command->start(); 01438 } 01439 01440 //----------------------------------------------------------------------------- 01441 int KMHeaders::slotFilterMsg(KMMessage *msg) 01442 { 01443 msg->setTransferInProgress(false); 01444 int filterResult = kmkernel->filterMgr()->process(msg,KMFilterMgr::Explicit); 01445 if (filterResult == 2) { 01446 // something went horribly wrong (out of space?) 01447 kmkernel->emergencyExit( i18n("Unable to process messages: " ) + QString::fromLocal8Bit(strerror(errno))); 01448 return 2; 01449 } 01450 if (msg->parent()) { // unGet this msg 01451 int idx = -1; 01452 KMFolder * p = 0; 01453 kmkernel->msgDict()->getLocation( msg, &p, &idx ); 01454 assert( p == msg->parent() ); assert( idx >= 0 ); 01455 p->unGetMsg( idx ); 01456 } 01457 01458 return filterResult; 01459 } 01460 01461 01462 void KMHeaders::slotExpandOrCollapseThread( bool expand ) 01463 { 01464 if ( !isThreaded() ) return; 01465 // find top-level parent of currentItem(). 01466 QListViewItem *item = currentItem(); 01467 if ( !item ) return; 01468 clearSelection(); 01469 item->setSelected( true ); 01470 while ( item->parent() ) 01471 item = item->parent(); 01472 KMHeaderItem * hdrItem = static_cast<KMHeaderItem*>(item); 01473 hdrItem->setOpenRecursive( expand ); 01474 if ( !expand ) // collapse can hide the current item: 01475 setCurrentMsg( hdrItem->msgId() ); 01476 ensureItemVisible( currentItem() ); 01477 } 01478 01479 void KMHeaders::slotExpandOrCollapseAllThreads( bool expand ) 01480 { 01481 if ( !isThreaded() ) return; 01482 01483 QListViewItem * item = currentItem(); 01484 if( item ) { 01485 clearSelection(); 01486 item->setSelected( true ); 01487 } 01488 01489 for ( QListViewItem *item = firstChild() ; 01490 item ; item = item->nextSibling() ) 01491 static_cast<KMHeaderItem*>(item)->setOpenRecursive( expand ); 01492 if ( !expand ) { // collapse can hide the current item: 01493 QListViewItem * item = currentItem(); 01494 if( item ) { 01495 while ( item->parent() ) 01496 item = item->parent(); 01497 setCurrentMsg( static_cast<KMHeaderItem*>(item)->msgId() ); 01498 } 01499 } 01500 ensureItemVisible( currentItem() ); 01501 } 01502 01503 //----------------------------------------------------------------------------- 01504 void KMHeaders::setStyleDependantFrameWidth() 01505 { 01506 // set the width of the frame to a reasonable value for the current GUI style 01507 int frameWidth; 01508 if( style().isA("KeramikStyle") ) 01509 frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth ) - 1; 01510 else 01511 frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth ); 01512 if ( frameWidth < 0 ) 01513 frameWidth = 0; 01514 if ( frameWidth != lineWidth() ) 01515 setLineWidth( frameWidth ); 01516 } 01517 01518 //----------------------------------------------------------------------------- 01519 void KMHeaders::styleChange( QStyle& oldStyle ) 01520 { 01521 setStyleDependantFrameWidth(); 01522 KListView::styleChange( oldStyle ); 01523 } 01524 01525 //----------------------------------------------------------------------------- 01526 void KMHeaders::setFolderInfoStatus () 01527 { 01528 if ( !mFolder ) return; 01529 QString str = ( static_cast<KMFolder*>(mFolder) == kmkernel->outboxFolder() ) 01530 ? i18n( "1 unsent", "%n unsent", mFolder->countUnread() ) 01531 : i18n( "1 unread", "%n unread", mFolder->countUnread() ); 01532 str = i18n( "1 message, %1.", "%n messages, %1.", mFolder->count() ) 01533 .arg( str ); 01534 if ( mFolder->isReadOnly() ) 01535 str = i18n("%1 = n messages, m unread.", "%1 Folder is read-only.").arg( str ); 01536 BroadcastStatus::instance()->setStatusMsg(str); 01537 } 01538 01539 //----------------------------------------------------------------------------- 01540 void KMHeaders::applyFiltersOnMsg() 01541 { 01542 #if 0 // uses action scheduler 01543 KMFilterMgr::FilterSet set = KMFilterMgr::All; 01544 QPtrList<KMFilter> filters; 01545 filters = *( kmkernel->filterMgr() ); 01546 ActionScheduler *scheduler = new ActionScheduler( set, filters, this ); 01547 scheduler->setAutoDestruct( true ); 01548 01549 int contentX, contentY; 01550 KMHeaderItem *nextItem = prepareMove( &contentX, &contentY ); 01551 QPtrList<KMMsgBase> msgList = *selectedMsgs(true); 01552 finalizeMove( nextItem, contentX, contentY ); 01553 01554 for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next()) 01555 scheduler->execFilters( msg ); 01556 #else 01557 emit maybeDeleting(); 01558 01559 disconnect(this,SIGNAL(currentChanged(QListViewItem*)), 01560 this,SLOT(highlightMessage(QListViewItem*))); 01561 KMMessageList* msgList = selectedMsgs(); 01562 int topX = contentsX(); 01563 int topY = contentsY(); 01564 01565 if (msgList->isEmpty()) 01566 return; 01567 QListViewItem *qlvi = currentItem(); 01568 QListViewItem *next = qlvi; 01569 while (next && next->isSelected()) 01570 next = next->itemBelow(); 01571 if (!next || (next && next->isSelected())) { 01572 next = qlvi; 01573 while (next && next->isSelected()) 01574 next = next->itemAbove(); 01575 } 01576 01577 clearSelection(); 01578 for (KMMsgBase* msgBase=msgList->first(); msgBase; msgBase=msgList->next()) { 01579 int idx = msgBase->parent()->find(msgBase); 01580 assert(idx != -1); 01581 KMMessage * msg = msgBase->parent()->getMsg(idx); 01582 if (msg->transferInProgress()) continue; 01583 msg->setTransferInProgress(true); 01584 if ( !msg->isComplete() ) 01585 { 01586 FolderJob *job = mFolder->createJob(msg); 01587 connect(job, SIGNAL(messageRetrieved(KMMessage*)), 01588 SLOT(slotFilterMsg(KMMessage*))); 01589 job->start(); 01590 } else { 01591 if (slotFilterMsg(msg) == 2) break; 01592 } 01593 } 01594 01595 setContentsPos( topX, topY ); 01596 emit selected( 0 ); 01597 if (next) { 01598 setCurrentItem( next ); 01599 setSelected( next, true ); 01600 setSelectionAnchor( currentItem() ); 01601 highlightMessage( next ); 01602 } 01603 else if (currentItem()) { 01604 setSelected( currentItem(), true ); 01605 setSelectionAnchor( currentItem() ); 01606 highlightMessage( currentItem() ); 01607 } 01608 else 01609 emit selected( 0 ); 01610 01611 makeHeaderVisible(); 01612 connect(this,SIGNAL(currentChanged(QListViewItem*)), 01613 this,SLOT(highlightMessage(QListViewItem*))); 01614 #endif 01615 } 01616 01617 01618 //----------------------------------------------------------------------------- 01619 void KMHeaders::setMsgRead (int msgId) 01620 { 01621 KMMsgBase *msgBase = mFolder->getMsgBase( msgId ); 01622 if (!msgBase) 01623 return; 01624 01625 SerNumList serNums; 01626 if (msgBase->isNew() || msgBase->isUnread()) { 01627 serNums.append( msgBase->getMsgSerNum() ); 01628 } 01629 01630 KMCommand *command = new KMSetStatusCommand( KMMsgStatusRead, serNums ); 01631 command->start(); 01632 } 01633 01634 01635 //----------------------------------------------------------------------------- 01636 void KMHeaders::deleteMsg () 01637 { 01638 //make sure we have an associated folder (root of folder tree does not). 01639 if (!mFolder) 01640 return; 01641 01642 int contentX, contentY; 01643 KMHeaderItem *nextItem = prepareMove( &contentX, &contentY ); 01644 KMMessageList msgList = *selectedMsgs(true); 01645 finalizeMove( nextItem, contentX, contentY ); 01646 01647 KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList ); 01648 connect( command, SIGNAL( completed( KMCommand * ) ), 01649 this, SLOT( slotMoveCompleted( KMCommand * ) ) ); 01650 command->start(); 01651 01652 BroadcastStatus::instance()->setStatusMsg(""); 01653 // triggerUpdate(); 01654 } 01655 01656 01657 //----------------------------------------------------------------------------- 01658 void KMHeaders::moveSelectedToFolder( int menuId ) 01659 { 01660 if (mMenuToFolder[menuId]) 01661 moveMsgToFolder( mMenuToFolder[menuId] ); 01662 } 01663 01664 //----------------------------------------------------------------------------- 01665 KMHeaderItem* KMHeaders::prepareMove( int *contentX, int *contentY ) 01666 { 01667 KMHeaderItem *ret = 0; 01668 emit maybeDeleting(); 01669 01670 disconnect( this, SIGNAL(currentChanged(QListViewItem*)), 01671 this, SLOT(highlightMessage(QListViewItem*))); 01672 01673 QListViewItem *curItem; 01674 KMHeaderItem *item; 01675 curItem = currentItem(); 01676 while (curItem && curItem->isSelected() && curItem->itemBelow()) 01677 curItem = curItem->itemBelow(); 01678 while (curItem && curItem->isSelected() && curItem->itemAbove()) 01679 curItem = curItem->itemAbove(); 01680 item = static_cast<KMHeaderItem*>(curItem); 01681 01682 *contentX = contentsX(); 01683 *contentY = contentsY(); 01684 01685 if (item && !item->isSelected()) 01686 ret = item; 01687 01688 return ret; 01689 } 01690 01691 //----------------------------------------------------------------------------- 01692 void KMHeaders::finalizeMove( KMHeaderItem *item, int contentX, int contentY ) 01693 { 01694 emit selected( 0 ); 01695 01696 if ( item ) { 01697 clearSelection(); 01698 setCurrentItem( item ); 01699 setSelected( item, true ); 01700 setSelectionAnchor( currentItem() ); 01701 mPrevCurrent = 0; 01702 highlightMessage( item, false); 01703 } 01704 01705 setContentsPos( contentX, contentY ); 01706 makeHeaderVisible(); 01707 connect( this, SIGNAL(currentChanged(QListViewItem*)), 01708 this, SLOT(highlightMessage(QListViewItem*))); 01709 } 01710 01711 01712 //----------------------------------------------------------------------------- 01713 void KMHeaders::moveMsgToFolder ( KMFolder* destFolder, bool askForConfirmation ) 01714 { 01715 if ( destFolder == mFolder ) return; // Catch the noop case 01716 01717 KMMessageList msgList = *selectedMsgs(); 01718 if ( !destFolder && askForConfirmation && // messages shall be deleted 01719 KMessageBox::warningContinueCancel(this, 01720 i18n("<qt>Do you really want to delete the selected message?<br>" 01721 "Once deleted, it cannot be restored.</qt>", 01722 "<qt>Do you really want to delete the %n selected messages?<br>" 01723 "Once deleted, they cannot be restored.</qt>", msgList.count() ), 01724 i18n("Delete Messages"), KGuiItem(i18n("De&lete"),"editdelete"), "NoConfirmDelete") == KMessageBox::Cancel ) 01725 return; // user canceled the action 01726 01727 // remember the message to select afterwards 01728 int contentX, contentY; 01729 KMHeaderItem *nextItem = prepareMove( &contentX, &contentY ); 01730 msgList = *selectedMsgs(true); 01731 finalizeMove( nextItem, contentX, contentY ); 01732 01733 KMCommand *command = new KMMoveCommand( destFolder, msgList ); 01734 connect( command, SIGNAL( completed( KMCommand * ) ), 01735 this, SLOT( slotMoveCompleted( KMCommand * ) ) ); 01736 command->start(); 01737 01738 } 01739 01740 void KMHeaders::slotMoveCompleted( KMCommand *command ) 01741 { 01742 kdDebug(5006) << k_funcinfo << command->result() << endl; 01743 bool deleted = static_cast<KMMoveCommand *>( command )->destFolder() == 0; 01744 if ( command->result() == KMCommand::OK ) { 01745 // make sure the current item is shown 01746 makeHeaderVisible(); 01747 #if 0 // enable after the message-freeze 01748 BroadcastStatus::instance()->setStatusMsg( 01749 deleted ? i18nTODO("Messages deleted successfully.") : i18nTODO("Messages moved successfully") ); 01750 #else 01751 if ( !deleted ) BroadcastStatus::instance()->setStatusMsg( i18n( "Messages moved successfully" ) ); 01752 #endif 01753 } else { 01754 /* The move failed or the user canceled it; reset the state of all 01755 * messages involved and repaint. 01756 * 01757 * Note: This potentially resets too many items if there is more than one 01758 * move going on. Oh well, I suppose no animals will be harmed. 01759 * */ 01760 for (QListViewItemIterator it(this); it.current(); it++) { 01761 KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); 01762 if ( item->aboutToBeDeleted() ) { 01763 item->setAboutToBeDeleted ( false ); 01764 item->setSelectable ( true ); 01765 KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); 01766 if ( msgBase->isMessage() ) { 01767 KMMessage *msg = static_cast<KMMessage *>(msgBase); 01768 if ( msg ) msg->setTransferInProgress( false, true ); 01769 } 01770 } 01771 } 01772 triggerUpdate(); 01773 #if 0 // enable after the message-freeze 01774 if ( command->result() == KMCommand::Failed ) 01775 BroadcastStatus::instance()->setStatusMsg( 01776 deleted ? i18nTODO("Deleting messages failed.") : i18nTODO("Moving messages failed.") ); 01777 else 01778 BroadcastStatus::instance()->setStatusMsg( 01779 deleted ? i18nTODO("Deleting messages canceled.") : i18nTODO("Moving messages canceled.") ); 01780 #else 01781 if ( !deleted ) { 01782 if ( command->result() == KMCommand::Failed ) 01783 BroadcastStatus::instance()->setStatusMsg( i18n("Moving messages failed.") ); 01784 else 01785 BroadcastStatus::instance()->setStatusMsg( i18n("Moving messages canceled.") ); 01786 } 01787 #endif 01788 } 01789 } 01790 01791 bool KMHeaders::canUndo() const 01792 { 01793 return ( kmkernel->undoStack()->size() > 0 ); 01794 } 01795 01796 //----------------------------------------------------------------------------- 01797 void KMHeaders::undo() 01798 { 01799 kmkernel->undoStack()->undo(); 01800 } 01801 01802 //----------------------------------------------------------------------------- 01803 void KMHeaders::copySelectedToFolder(int menuId ) 01804 { 01805 if (mMenuToFolder[menuId]) 01806 copyMsgToFolder( mMenuToFolder[menuId] ); 01807 } 01808 01809 01810 //----------------------------------------------------------------------------- 01811 void KMHeaders::copyMsgToFolder(KMFolder* destFolder, KMMessage* aMsg) 01812 { 01813 if ( !destFolder ) 01814 return; 01815 01816 KMCommand * command = 0; 01817 if (aMsg) 01818 command = new KMCopyCommand( destFolder, aMsg ); 01819 else { 01820 KMMessageList msgList = *selectedMsgs(); 01821 command = new KMCopyCommand( destFolder, msgList ); 01822 } 01823 01824 command->start(); 01825 } 01826 01827 01828 //----------------------------------------------------------------------------- 01829 void KMHeaders::setCurrentMsg(int cur) 01830 { 01831 if (!mFolder) return; 01832 if (cur >= mFolder->count()) cur = mFolder->count() - 1; 01833 if ((cur >= 0) && (cur < (int)mItems.size())) { 01834 clearSelection(); 01835 setCurrentItem( mItems[cur] ); 01836 setSelected( mItems[cur], true ); 01837 setSelectionAnchor( currentItem() ); 01838 } 01839 makeHeaderVisible(); 01840 setFolderInfoStatus(); 01841 } 01842 01843 //----------------------------------------------------------------------------- 01844 void KMHeaders::setSelected( QListViewItem *item, bool selected ) 01845 { 01846 if ( !item ) 01847 return; 01848 01849 if ( item->isVisible() ) 01850 KListView::setSelected( item, selected ); 01851 01852 // If the item is the parent of a closed thread recursively select 01853 // children . 01854 if ( isThreaded() && !item->isOpen() && item->firstChild() ) { 01855 QListViewItem *nextRoot = item->itemBelow(); 01856 QListViewItemIterator it( item->firstChild() ); 01857 for( ; (*it) != nextRoot; ++it ) { 01858 if ( (*it)->isVisible() ) 01859 (*it)->setSelected( selected ); 01860 } 01861 } 01862 } 01863 01864 void KMHeaders::setSelectedByIndex( QValueList<int> items, bool selected ) 01865 { 01866 for ( QValueList<int>::Iterator it = items.begin(); it != items.end(); ++it ) 01867 { 01868 if ( ((*it) >= 0) && ((*it) < (int)mItems.size()) ) 01869 { 01870 setSelected( mItems[(*it)], selected ); 01871 } 01872 } 01873 } 01874 01875 void KMHeaders::clearSelectableAndAboutToBeDeleted( Q_UINT32 serNum ) 01876 { 01877 // fugly, but I see no way around it 01878 for (QListViewItemIterator it(this); it.current(); it++) { 01879 KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); 01880 if ( item->aboutToBeDeleted() ) { 01881 KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() ); 01882 if ( serNum == msgBase->getMsgSerNum() ) { 01883 item->setAboutToBeDeleted ( false ); 01884 item->setSelectable ( true ); 01885 } 01886 } 01887 } 01888 triggerUpdate(); 01889 } 01890 01891 //----------------------------------------------------------------------------- 01892 KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted) 01893 { 01894 mSelMsgBaseList.clear(); 01895 for (QListViewItemIterator it(this); it.current(); it++) { 01896 if ( it.current()->isSelected() && it.current()->isVisible() ) { 01897 KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); 01898 if (toBeDeleted) { 01899 // make sure the item is not uselessly rethreaded and not selectable 01900 item->setAboutToBeDeleted ( true ); 01901 item->setSelectable ( false ); 01902 } 01903 KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); 01904 mSelMsgBaseList.append(msgBase); 01905 } 01906 } 01907 return &mSelMsgBaseList; 01908 } 01909 01910 //----------------------------------------------------------------------------- 01911 QValueList<int> KMHeaders::selectedItems() 01912 { 01913 QValueList<int> items; 01914 for ( QListViewItemIterator it(this); it.current(); it++ ) 01915 { 01916 if ( it.current()->isSelected() && it.current()->isVisible() ) 01917 { 01918 KMHeaderItem* item = static_cast<KMHeaderItem*>( it.current() ); 01919 items.append( item->msgId() ); 01920 } 01921 } 01922 return items; 01923 } 01924 01925 //----------------------------------------------------------------------------- 01926 int KMHeaders::firstSelectedMsg() const 01927 { 01928 int selectedMsg = -1; 01929 QListViewItem *item; 01930 for (item = firstChild(); item; item = item->itemBelow()) 01931 if (item->isSelected()) { 01932 selectedMsg = (static_cast<KMHeaderItem*>(item))->msgId(); 01933 break; 01934 } 01935 return selectedMsg; 01936 } 01937 01938 //----------------------------------------------------------------------------- 01939 void KMHeaders::nextMessage() 01940 { 01941 QListViewItem *lvi = currentItem(); 01942 if (lvi && lvi->itemBelow()) { 01943 clearSelection(); 01944 setSelected( lvi, false ); 01945 selectNextMessage(); 01946 setSelectionAnchor( currentItem() ); 01947 ensureCurrentItemVisible(); 01948 } 01949 } 01950 01951 void KMHeaders::selectNextMessage() 01952 { 01953 QListViewItem *lvi = currentItem(); 01954 if( lvi ) { 01955 QListViewItem *below = lvi->itemBelow(); 01956 QListViewItem *temp = lvi; 01957 if (lvi && below ) { 01958 while (temp) { 01959 temp->firstChild(); 01960 temp = temp->parent(); 01961 } 01962 lvi->repaint(); 01963 /* test to see if we need to unselect messages on back track */ 01964 (below->isSelected() ? setSelected(lvi, false) : setSelected(below, true)); 01965 setCurrentItem(below); 01966 makeHeaderVisible(); 01967 setFolderInfoStatus(); 01968 } 01969 } 01970 } 01971 01972 //----------------------------------------------------------------------------- 01973 void KMHeaders::prevMessage() 01974 { 01975 QListViewItem *lvi = currentItem(); 01976 if (lvi && lvi->itemAbove()) { 01977 clearSelection(); 01978 setSelected( lvi, false ); 01979 selectPrevMessage(); 01980 setSelectionAnchor( currentItem() ); 01981 ensureCurrentItemVisible(); 01982 } 01983 } 01984 01985 void KMHeaders::selectPrevMessage() 01986 { 01987 QListViewItem *lvi = currentItem(); 01988 if( lvi ) { 01989 QListViewItem *above = lvi->itemAbove(); 01990 QListViewItem *temp = lvi; 01991 01992 if (lvi && above) { 01993 while (temp) { 01994 temp->firstChild(); 01995 temp = temp->parent(); 01996 } 01997 lvi->repaint(); 01998 /* test to see if we need to unselect messages on back track */ 01999 (above->isSelected() ? setSelected(lvi, false) : setSelected(above, true)); 02000 setCurrentItem(above); 02001 makeHeaderVisible(); 02002 setFolderInfoStatus(); 02003 } 02004 } 02005 } 02006 02007 //----------------------------------------------------------------------------- 02008 void KMHeaders::findUnreadAux( KMHeaderItem*& item, 02009 bool & foundUnreadMessage, 02010 bool onlyNew, 02011 bool aDirNext ) 02012 { 02013 KMMsgBase* msgBase = 0; 02014 KMHeaderItem *lastUnread = 0; 02015 /* itemAbove() is _slow_ */ 02016 if (aDirNext) 02017 { 02018 while (item) { 02019 msgBase = mFolder->getMsgBase(item->msgId()); 02020 if (!msgBase) continue; 02021 if (msgBase->isUnread() || msgBase->isNew()) 02022 foundUnreadMessage = true; 02023 02024 if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())) break; 02025 if (onlyNew && msgBase->isNew()) break; 02026 item = static_cast<KMHeaderItem*>(item->itemBelow()); 02027 } 02028 } else { 02029 KMHeaderItem *newItem = static_cast<KMHeaderItem*>(firstChild()); 02030 while (newItem) 02031 { 02032 msgBase = mFolder->getMsgBase(newItem->msgId()); 02033 if (!msgBase) continue; 02034 if (msgBase->isUnread() || msgBase->isNew()) 02035 foundUnreadMessage = true; 02036 if (!onlyNew && (msgBase->isUnread() || msgBase->isNew()) 02037 || onlyNew && msgBase->isNew()) 02038 lastUnread = newItem; 02039 if (newItem == item) break; 02040 newItem = static_cast<KMHeaderItem*>(newItem->itemBelow()); 02041 } 02042 item = lastUnread; 02043 } 02044 } 02045 02046 //----------------------------------------------------------------------------- 02047 int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent) 02048 { 02049 KMHeaderItem *item, *pitem; 02050 bool foundUnreadMessage = false; 02051 02052 if (!mFolder) return -1; 02053 if (!(mFolder->count()) > 0) return -1; 02054 02055 if ((aStartAt >= 0) && (aStartAt < (int)mItems.size())) 02056 item = mItems[aStartAt]; 02057 else { 02058 item = currentHeaderItem(); 02059 if (!item) { 02060 if (aDirNext) 02061 item = static_cast<KMHeaderItem*>(firstChild()); 02062 else 02063 item = static_cast<KMHeaderItem*>(lastChild()); 02064 } 02065 if (!item) 02066 return -1; 02067 02068 if ( !acceptCurrent ) 02069 if (aDirNext) 02070 item = static_cast<KMHeaderItem*>(item->itemBelow()); 02071 else 02072 item = static_cast<KMHeaderItem*>(item->itemAbove()); 02073 } 02074 02075 pitem = item; 02076 02077 findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext ); 02078 02079 // We have found an unread item, but it is not necessary the 02080 // first unread item. 02081 // 02082 // Find the ancestor of the unread item closest to the 02083 // root and recursively sort all of that ancestors children. 02084 if (item) { 02085 QListViewItem *next = item; 02086 while (next->parent()) 02087 next = next->parent(); 02088 next = static_cast<KMHeaderItem*>(next)->firstChildNonConst(); 02089 while (next && (next != item)) 02090 if (static_cast<KMHeaderItem*>(next)->firstChildNonConst()) 02091 next = next->firstChild(); 02092 else if (next->nextSibling()) 02093 next = next->nextSibling(); 02094 else { 02095 while (next && (next != item)) { 02096 next = next->parent(); 02097 if (next == item) 02098 break; 02099 if (next && next->nextSibling()) { 02100 next = next->nextSibling(); 02101 break; 02102 } 02103 } 02104 } 02105 } 02106 02107 item = pitem; 02108 02109 findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext ); 02110 if (item) 02111 return item->msgId(); 02112 02113 02114 // A kludge to try to keep the number of unread messages in sync 02115 int unread = mFolder->countUnread(); 02116 if (((unread == 0) && foundUnreadMessage) || 02117 ((unread > 0) && !foundUnreadMessage)) { 02118 mFolder->correctUnreadMsgsCount(); 02119 } 02120 return -1; 02121 } 02122 02123 //----------------------------------------------------------------------------- 02124 bool KMHeaders::nextUnreadMessage(bool acceptCurrent) 02125 { 02126 if ( !mFolder || !mFolder->countUnread() ) return false; 02127 int i = findUnread(true, -1, false, acceptCurrent); 02128 if ( i < 0 && GlobalSettings::loopOnGotoUnread() != 02129 GlobalSettings::EnumLoopOnGotoUnread::DontLoop ) 02130 { 02131 KMHeaderItem * first = static_cast<KMHeaderItem*>(firstChild()); 02132 if ( first ) 02133 i = findUnread(true, first->msgId(), false, acceptCurrent); // from top 02134 } 02135 if ( i < 0 ) 02136 return false; 02137 setCurrentMsg(i); 02138 ensureCurrentItemVisible(); 02139 return true; 02140 } 02141 02142 void KMHeaders::ensureCurrentItemVisible() 02143 { 02144 int i = currentItemIndex(); 02145 if ((i >= 0) && (i < (int)mItems.size())) 02146 center( contentsX(), itemPos(mItems[i]), 0, 9.0 ); 02147 } 02148 02149 //----------------------------------------------------------------------------- 02150 bool KMHeaders::prevUnreadMessage() 02151 { 02152 if ( !mFolder || !mFolder->countUnread() ) return false; 02153 int i = findUnread(false); 02154 if ( i < 0 && GlobalSettings::loopOnGotoUnread() != 02155 GlobalSettings::EnumLoopOnGotoUnread::DontLoop ) 02156 { 02157 KMHeaderItem * last = static_cast<KMHeaderItem*>(lastItem()); 02158 if ( last ) 02159 i = findUnread(false, last->msgId() ); // from bottom 02160 } 02161 if ( i < 0 ) 02162 return false; 02163 setCurrentMsg(i); 02164 ensureCurrentItemVisible(); 02165 return true; 02166 } 02167 02168 02169 //----------------------------------------------------------------------------- 02170 void KMHeaders::slotNoDrag() 02171 { 02172 mMousePressed = false; 02173 } 02174 02175 02176 //----------------------------------------------------------------------------- 02177 void KMHeaders::makeHeaderVisible() 02178 { 02179 if (currentItem()) 02180 ensureItemVisible( currentItem() ); 02181 } 02182 02183 //----------------------------------------------------------------------------- 02184 void KMHeaders::highlightMessage(QListViewItem* lvi, bool markitread) 02185 { 02186 // shouldnt happen but will crash if it does 02187 if (lvi && !lvi->isSelectable()) return; 02188 02189 KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi); 02190 if (lvi != mPrevCurrent) { 02191 if (mPrevCurrent && mFolder) 02192 { 02193 KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId()); 02194 if (prevMsg && mReaderWindowActive) 02195 { 02196 mFolder->ignoreJobsForMessage(prevMsg); 02197 if (!prevMsg->transferInProgress()) 02198 mFolder->unGetMsg(mPrevCurrent->msgId()); 02199 } 02200 } 02201 mPrevCurrent = item; 02202 } 02203 02204 if (!item) 02205 { 02206 emit selected( 0 ); return; 02207 } 02208 02209 int idx = item->msgId(); 02210 if (mReaderWindowActive) 02211 { 02212 KMMessage *msg = mFolder->getMsg(idx); 02213 if (!msg || msg->transferInProgress()) 02214 { 02215 emit selected( 0 ); 02216 mPrevCurrent = 0; 02217 return; 02218 } 02219 } 02220 02221 BroadcastStatus::instance()->setStatusMsg(""); 02222 if (markitread && idx >= 0) setMsgRead(idx); 02223 mItems[idx]->irefresh(); 02224 mItems[idx]->repaint(); 02225 emit selected(mFolder->getMsg(idx)); 02226 setFolderInfoStatus(); 02227 } 02228 02229 void KMHeaders::resetCurrentTime() 02230 { 02231 mDate.reset(); 02232 QTimer::singleShot( 1000, this, SLOT( resetCurrentTime() ) ); 02233 } 02234 02235 //----------------------------------------------------------------------------- 02236 void KMHeaders::selectMessage(QListViewItem* lvi) 02237 { 02238 KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi); 02239 if (!item) 02240 return; 02241 02242 int idx = item->msgId(); 02243 KMMessage *msg = mFolder->getMsg(idx); 02244 if (!msg->transferInProgress()) 02245 { 02246 emit activated(mFolder->getMsg(idx)); 02247 } 02248 02249 // if (kmkernel->folderIsDraftOrOutbox(mFolder)) 02250 // setOpen(lvi, !lvi->isOpen()); 02251 } 02252 02253 02254 //----------------------------------------------------------------------------- 02255 void KMHeaders::updateMessageList( bool set_selection, bool forceJumpToUnread ) 02256 { 02257 mPrevCurrent = 0; 02258 noRepaint = true; 02259 clear(); 02260 noRepaint = false; 02261 KListView::setSorting( mSortCol, !mSortDescending ); 02262 if (!mFolder) { 02263 mItems.resize(0); 02264 repaint(); 02265 return; 02266 } 02267 readSortOrder( set_selection, forceJumpToUnread ); 02268 emit messageListUpdated(); 02269 } 02270 02271 02272 //----------------------------------------------------------------------------- 02273 // KMail Header list selection/navigation description 02274 // 02275 // If the selection state changes the reader window is updated to show the 02276 // current item. 02277 // 02278 // (The selection state of a message or messages can be changed by pressing 02279 // space, or normal/shift/cntrl clicking). 02280 // 02281 // The following keyboard events are supported when the messages headers list 02282 // has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End, 02283 // Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do 02284 // not change the selection state. 02285 // 02286 // Exception: When shift selecting either with mouse or key press the reader 02287 // window is updated regardless of whether of not the selection has changed. 02288 void KMHeaders::keyPressEvent( QKeyEvent * e ) 02289 { 02290 bool cntrl = (e->state() & ControlButton ); 02291 bool shft = (e->state() & ShiftButton ); 02292 QListViewItem *cur = currentItem(); 02293 02294 if (!e || !firstChild()) 02295 return; 02296 02297 // If no current item, make some first item current when a key is pressed 02298 if (!cur) { 02299 setCurrentItem( firstChild() ); 02300 setSelectionAnchor( currentItem() ); 02301 return; 02302 } 02303 02304 // Handle space key press 02305 if (cur->isSelectable() && e->ascii() == ' ' ) { 02306 setSelected( cur, !cur->isSelected() ); 02307 highlightMessage( cur, false); 02308 return; 02309 } 02310 02311 if (cntrl) { 02312 if (!shft) 02313 disconnect(this,SIGNAL(currentChanged(QListViewItem*)), 02314 this,SLOT(highlightMessage(QListViewItem*))); 02315 switch (e->key()) { 02316 case Key_Down: 02317 case Key_Up: 02318 case Key_Home: 02319 case Key_End: 02320 case Key_Next: 02321 case Key_Prior: 02322 case Key_Escape: 02323 KListView::keyPressEvent( e ); 02324 } 02325 if (!shft) 02326 connect(this,SIGNAL(currentChanged(QListViewItem*)), 02327 this,SLOT(highlightMessage(QListViewItem*))); 02328 } 02329 } 02330 02331 //----------------------------------------------------------------------------- 02332 // Handle RMB press, show pop up menu 02333 void KMHeaders::rightButtonPressed( QListViewItem *lvi, const QPoint &, int ) 02334 { 02335 if (!lvi) 02336 return; 02337 02338 if (!(lvi->isSelected())) { 02339 clearSelection(); 02340 } 02341 setSelected( lvi, true ); 02342 slotRMB(); 02343 } 02344 02345 //----------------------------------------------------------------------------- 02346 void KMHeaders::contentsMousePressEvent(QMouseEvent* e) 02347 { 02348 mPressPos = e->pos(); 02349 QListViewItem *lvi = itemAt( contentsToViewport( e->pos() )); 02350 bool wasSelected = false; 02351 bool rootDecoClicked = false; 02352 if (lvi) { 02353 wasSelected = lvi->isSelected(); 02354 rootDecoClicked = 02355 ( mPressPos.x() <= header()->cellPos( header()->mapToActual( 0 ) ) + 02356 treeStepSize() * ( lvi->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() ) 02357 && ( mPressPos.x() >= header()->cellPos( header()->mapToActual( 0 ) ) ); 02358 02359 if ( rootDecoClicked ) { 02360 // Check if our item is the parent of a closed thread and if so, if the root 02361 // decoration of the item was clicked (i.e. the +/- sign) which would expand 02362 // the thread. In that case, deselect all children, so opening the thread 02363 // doesn't cause a flicker. 02364 if ( !lvi->isOpen() && lvi->firstChild() ) { 02365 QListViewItem *nextRoot = lvi->itemBelow(); 02366 QListViewItemIterator it( lvi->firstChild() ); 02367 for( ; (*it) != nextRoot; ++it ) 02368 (*it)->setSelected( false ); 02369 } 02370 } 02371 } 02372 02373 // let klistview do it's thing, expanding/collapsing, selection/deselection 02374 KListView::contentsMousePressEvent(e); 02375 /* QListView's shift-select selects also invisible items. Until that is 02376 fixed, we have to deselect hidden items here manually, so the quick 02377 search doesn't mess things up. */ 02378 if ( e->state() & ShiftButton ) { 02379 QListViewItemIterator it( this, QListViewItemIterator::Invisible ); 02380 while ( it.current() ) { 02381 it.current()->setSelected( false ); 02382 ++it; 02383 } 02384 } 02385 02386 if ( rootDecoClicked ) { 02387 // select the thread's children after closing if the parent is selected 02388 if ( lvi && !lvi->isOpen() && lvi->isSelected() ) 02389 setSelected( lvi, true ); 02390 } 02391 02392 if ( lvi && !rootDecoClicked ) { 02393 if ( lvi != currentItem() ) 02394 highlightMessage( lvi ); 02395 /* Explicitely set selection state. This is necessary because we want to 02396 * also select all children of closed threads when the parent is selected. */ 02397 02398 // unless ctrl mask, set selected if it isn't already 02399 if ( !( e->state() & ControlButton ) && !wasSelected ) 02400 setSelected( lvi, true ); 02401 // if ctrl mask, toggle selection 02402 if ( e->state() & ControlButton ) 02403 setSelected( lvi, !wasSelected ); 02404 02405 if ((e->button() == LeftButton) ) 02406 mMousePressed = true; 02407 } 02408 } 02409 02410 //----------------------------------------------------------------------------- 02411 void KMHeaders::contentsMouseReleaseEvent(QMouseEvent* e) 02412 { 02413 if (e->button() != RightButton) 02414 KListView::contentsMouseReleaseEvent(e); 02415 02416 mMousePressed = false; 02417 } 02418 02419 //----------------------------------------------------------------------------- 02420 void KMHeaders::contentsMouseMoveEvent( QMouseEvent* e ) 02421 { 02422 if (mMousePressed && 02423 (e->pos() - mPressPos).manhattanLength() > KGlobalSettings::dndEventDelay()) { 02424 mMousePressed = false; 02425 QListViewItem *item = itemAt( contentsToViewport(mPressPos) ); 02426 if ( item ) { 02427 MailList mailList; 02428 unsigned int count = 0; 02429 for( QListViewItemIterator it(this); it.current(); it++ ) 02430 if( it.current()->isSelected() ) { 02431 KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); 02432 KMMsgBase *msg = mFolder->getMsgBase(item->msgId()); 02433 MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(), 02434 msg->subject(), msg->fromStrip(), 02435 msg->toStrip(), msg->date() ); 02436 mailList.append( mailSummary ); 02437 ++count; 02438 } 02439 MailListDrag *d = new MailListDrag( mailList, viewport() ); 02440 02441 // Set pixmap 02442 QPixmap pixmap; 02443 if( count == 1 ) 02444 pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) ); 02445 else 02446 pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) ); 02447 02448 // Calculate hotspot (as in Konqueror) 02449 if( !pixmap.isNull() ) { 02450 QPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 ); 02451 d->setPixmap( pixmap, hotspot ); 02452 } 02453 d->drag(); 02454 } 02455 } 02456 } 02457 02458 void KMHeaders::highlightMessage(QListViewItem* i) 02459 { 02460 highlightMessage( i, false ); 02461 } 02462 02463 //----------------------------------------------------------------------------- 02464 void KMHeaders::slotRMB() 02465 { 02466 if (!topLevelWidget()) return; // safe bet 02467 02468 QPopupMenu *menu = new QPopupMenu(this); 02469 02470 mMenuToFolder.clear(); 02471 02472 mOwner->updateMessageMenu(); 02473 02474 bool out_folder = kmkernel->folderIsDraftOrOutbox(mFolder); 02475 if ( out_folder ) 02476 mOwner->editAction()->plug(menu); 02477 else { 02478 // show most used actions 02479 mOwner->replyAction()->plug(menu); 02480 mOwner->replyAllAction()->plug(menu); 02481 mOwner->replyAuthorAction()->plug( menu ); 02482 mOwner->replyListAction()->plug(menu); 02483 mOwner->forwardMenu()->plug(menu); 02484 mOwner->bounceAction()->plug(menu); 02485 mOwner->sendAgainAction()->plug(menu); 02486 } 02487 menu->insertSeparator(); 02488 02489 QPopupMenu *msgCopyMenu = new QPopupMenu(menu); 02490 KMCopyCommand::folderToPopupMenu( false, this, &mMenuToFolder, msgCopyMenu ); 02491 menu->insertItem(i18n("&Copy To"), msgCopyMenu); 02492 02493 if ( mFolder->isReadOnly() ) { 02494 int id = menu->insertItem( i18n("&Move To") ); 02495 menu->setItemEnabled( id, false ); 02496 } else { 02497 QPopupMenu *msgMoveMenu = new QPopupMenu(menu); 02498 KMMoveCommand::folderToPopupMenu( true, this, &mMenuToFolder, msgMoveMenu ); 02499 menu->insertItem(i18n("&Move To"), msgMoveMenu); 02500 } 02501 02502 if ( !out_folder ) { 02503 mOwner->statusMenu()->plug( menu ); // Mark Message menu 02504 if ( mOwner->threadStatusMenu()->isEnabled() ) { 02505 mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu 02506 } 02507 } 02508 02509 if (mOwner->watchThreadAction()->isEnabled() ) { 02510 menu->insertSeparator(); 02511 mOwner->watchThreadAction()->plug(menu); 02512 mOwner->ignoreThreadAction()->plug(menu); 02513 } 02514 menu->insertSeparator(); 02515 mOwner->trashAction()->plug(menu); 02516 mOwner->deleteAction()->plug(menu); 02517 02518 menu->insertSeparator(); 02519 mOwner->saveAsAction()->plug(menu); 02520 mOwner->saveAttachmentsAction()->plug(menu); 02521 mOwner->printAction()->plug(menu); 02522 02523 if ( !out_folder ) { 02524 menu->insertSeparator(); 02525 mOwner->action("apply_filters")->plug(menu); 02526 mOwner->filterMenu()->plug( menu ); // Create Filter menu 02527 } 02528 02529 mOwner->action("apply_filter_actions")->plug(menu); 02530 02531 KAcceleratorManager::manage(menu); 02532 kmkernel->setContextMenuShown( true ); 02533 menu->exec(QCursor::pos(), 0); 02534 kmkernel->setContextMenuShown( false ); 02535 delete menu; 02536 } 02537 02538 //----------------------------------------------------------------------------- 02539 KMMessage* KMHeaders::currentMsg() 02540 { 02541 KMHeaderItem *hi = currentHeaderItem(); 02542 if (!hi) 02543 return 0; 02544 else 02545 return mFolder->getMsg(hi->msgId()); 02546 } 02547 02548 //----------------------------------------------------------------------------- 02549 KMHeaderItem* KMHeaders::currentHeaderItem() 02550 { 02551 return static_cast<KMHeaderItem*>(currentItem()); 02552 } 02553 02554 //----------------------------------------------------------------------------- 02555 int KMHeaders::currentItemIndex() 02556 { 02557 KMHeaderItem* item = currentHeaderItem(); 02558 if (item) 02559 return item->msgId(); 02560 else 02561 return -1; 02562 } 02563 02564 //----------------------------------------------------------------------------- 02565 void KMHeaders::setCurrentItemByIndex(int msgIdx) 02566 { 02567 if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) { 02568 clearSelection(); 02569 bool unchanged = (currentItem() == mItems[msgIdx]); 02570 setCurrentItem( mItems[msgIdx] ); 02571 setSelected( mItems[msgIdx], true ); 02572 setSelectionAnchor( currentItem() ); 02573 if (unchanged) 02574 highlightMessage( mItems[msgIdx], false); 02575 } 02576 } 02577 02578 //----------------------------------------------------------------------------- 02579 int KMHeaders::topItemIndex() 02580 { 02581 KMHeaderItem *item = static_cast<KMHeaderItem*>(itemAt(QPoint(1,1))); 02582 if (item) 02583 return item->msgId(); 02584 else 02585 return -1; 02586 } 02587 02588 // If sorting ascending by date/ooa then try to scroll list when new mail 02589 // arrives to show it, but don't scroll current item out of view. 02590 void KMHeaders::showNewMail() 02591 { 02592 if (mSortCol != mPaintInfo.dateCol) 02593 return; 02594 for( int i = 0; i < (int)mItems.size(); ++i) 02595 if (mFolder->getMsgBase(i)->isNew()) { 02596 if (!mSortDescending) 02597 setTopItemByIndex( currentItemIndex() ); 02598 break; 02599 } 02600 } 02601 02602 //----------------------------------------------------------------------------- 02603 void KMHeaders::setTopItemByIndex( int aMsgIdx) 02604 { 02605 int msgIdx = aMsgIdx; 02606 if (msgIdx < 0) 02607 msgIdx = 0; 02608 else if (msgIdx >= (int)mItems.size()) 02609 msgIdx = mItems.size() - 1; 02610 if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) 02611 setContentsPos( 0, itemPos( mItems[msgIdx] )); 02612 } 02613 02614 //----------------------------------------------------------------------------- 02615 void KMHeaders::setNestedOverride( bool override ) 02616 { 02617 mSortInfo.dirty = true; 02618 mNestedOverride = override; 02619 setRootIsDecorated( nestingPolicy != AlwaysOpen 02620 && isThreaded() ); 02621 QString sortFile = mFolder->indexLocation() + ".sorted"; 02622 unlink(QFile::encodeName(sortFile)); 02623 reset(); 02624 } 02625 02626 //----------------------------------------------------------------------------- 02627 void KMHeaders::setSubjectThreading( bool aSubjThreading ) 02628 { 02629 mSortInfo.dirty = true; 02630 mSubjThreading = aSubjThreading; 02631 QString sortFile = mFolder->indexLocation() + ".sorted"; 02632 unlink(QFile::encodeName(sortFile)); 02633 reset(); 02634 } 02635 02636 //----------------------------------------------------------------------------- 02637 void KMHeaders::setOpen( QListViewItem *item, bool open ) 02638 { 02639 if ((nestingPolicy != AlwaysOpen)|| open) 02640 ((KMHeaderItem*)item)->setOpenRecursive( open ); 02641 } 02642 02643 //----------------------------------------------------------------------------- 02644 const KMMsgBase* KMHeaders::getMsgBaseForItem( const QListViewItem *item ) const 02645 { 02646 const KMHeaderItem *hi = static_cast<const KMHeaderItem *> ( item ); 02647 return mFolder->getMsgBase( hi->msgId() ); 02648 } 02649 02650 //----------------------------------------------------------------------------- 02651 void KMHeaders::setSorting( int column, bool ascending ) 02652 { 02653 if (column != -1) { 02654 // carsten: really needed? 02655 // if (column != mSortCol) 02656 // setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol )); 02657 if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied 02658 QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); 02659 mSortInfo.dirty = true; 02660 } 02661 02662 mSortCol = column; 02663 mSortDescending = !ascending; 02664 02665 if (!ascending && (column == mPaintInfo.dateCol)) 02666 mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival; 02667 02668 if (!ascending && (column == mPaintInfo.subCol)) 02669 mPaintInfo.status = !mPaintInfo.status; 02670 02671 QString colText = i18n( "Date" ); 02672 if (mPaintInfo.orderOfArrival) 02673 colText = i18n( "Date (Order of Arrival)" ); 02674 setColumnText( mPaintInfo.dateCol, colText); 02675 02676 colText = i18n( "Subject" ); 02677 if (mPaintInfo.status) 02678 colText = colText + i18n( " (Status)" ); 02679 setColumnText( mPaintInfo.subCol, colText); 02680 } 02681 KListView::setSorting( column, ascending ); 02682 ensureCurrentItemVisible(); 02683 // Make sure the config and .sorted file are updated, otherwise stale info 02684 // is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ). 02685 if ( mFolder ) { 02686 writeFolderConfig(); 02687 writeSortOrder(); 02688 } 02689 } 02690 02691 //Flatten the list and write it to disk 02692 static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid, 02693 int parent_id, QString key, 02694 bool update_discover=true) 02695 { 02696 unsigned long msgSerNum; 02697 unsigned long parentSerNum; 02698 msgSerNum = kmkernel->msgDict()->getMsgSerNum( folder, msgid ); 02699 if (parent_id >= 0) 02700 parentSerNum = kmkernel->msgDict()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED; 02701 else 02702 parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED); 02703 02704 fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream); 02705 fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream); 02706 Q_INT32 len = key.length() * sizeof(QChar); 02707 fwrite(&len, sizeof(len), 1, sortStream); 02708 if (len) 02709 fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream); 02710 02711 if (update_discover) { 02712 //update the discovered change count 02713 Q_INT32 discovered_count = 0; 02714 fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET); 02715 fread(&discovered_count, sizeof(discovered_count), 1, sortStream); 02716 discovered_count++; 02717 fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET); 02718 fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream); 02719 } 02720 } 02721 02722 void KMHeaders::folderCleared() 02723 { 02724 mSortCacheItems.clear(); //autoDelete is true 02725 mSubjectLists.clear(); 02726 mImperfectlyThreadedList.clear(); 02727 mPrevCurrent = 0; 02728 emit selected(0); 02729 } 02730 02731 bool KMHeaders::writeSortOrder() 02732 { 02733 QString sortFile = KMAIL_SORT_FILE(mFolder); 02734 02735 if (!mSortInfo.dirty) { 02736 struct stat stat_tmp; 02737 if(stat(QFile::encodeName(sortFile), &stat_tmp) == -1) { 02738 mSortInfo.dirty = true; 02739 } 02740 } 02741 if (mSortInfo.dirty) { 02742 if (!mFolder->count()) { 02743 // Folder is empty now, remove the sort file. 02744 unlink(QFile::encodeName(sortFile)); 02745 return true; 02746 } 02747 QString tempName = sortFile + ".temp"; 02748 unlink(QFile::encodeName(tempName)); 02749 FILE *sortStream = fopen(QFile::encodeName(tempName), "w"); 02750 if (!sortStream) 02751 return false; 02752 02753 mSortInfo.ascending = !mSortDescending; 02754 mSortInfo.dirty = false; 02755 mSortInfo.column = mSortCol; 02756 fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION); 02757 //magic header information 02758 Q_INT32 byteOrder = 0x12345678; 02759 Q_INT32 column = mSortCol; 02760 Q_INT32 ascending= !mSortDescending; 02761 Q_INT32 threaded = isThreaded(); 02762 Q_INT32 appended=0; 02763 Q_INT32 discovered_count = 0; 02764 Q_INT32 sorted_count=0; 02765 fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream); 02766 fwrite(&column, sizeof(column), 1, sortStream); 02767 fwrite(&ascending, sizeof(ascending), 1, sortStream); 02768 fwrite(&threaded, sizeof(threaded), 1, sortStream); 02769 fwrite(&appended, sizeof(appended), 1, sortStream); 02770 fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream); 02771 fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream); 02772 02773 QPtrStack<KMHeaderItem> items; 02774 { 02775 QPtrStack<QListViewItem> s; 02776 for (QListViewItem * i = firstChild(); i; ) { 02777 items.push((KMHeaderItem *)i); 02778 if ( i->firstChild() ) { 02779 s.push( i ); 02780 i = i->firstChild(); 02781 } else if( i->nextSibling()) { 02782 i = i->nextSibling(); 02783 } else { 02784 for(i=0; !i && s.count(); i = s.pop()->nextSibling()); 02785 } 02786 } 02787 } 02788 02789 KMMsgBase *kmb; 02790 while(KMHeaderItem *i = items.pop()) { 02791 int parent_id = -1; //no parent, top level 02792 if (threaded) { 02793 kmb = mFolder->getMsgBase( i->mMsgId ); 02794 assert(kmb); // I have seen 0L come out of this, called from 02795 // KMHeaders::setFolder(0xgoodpointer, false); 02796 QString replymd5 = kmb->replyToIdMD5(); 02797 QString replyToAuxId = kmb->replyToAuxIdMD5(); 02798 KMSortCacheItem *p = NULL; 02799 if(!replymd5.isEmpty()) 02800 p = mSortCacheItems[replymd5]; 02801 02802 if (p) 02803 parent_id = p->id(); 02804 // We now have either found a parent, or set it to -1, which means that 02805 // it will be reevaluated when a message is added, for example. If there 02806 // is no replyToId and no replyToAuxId and the message is not prefixed, 02807 // this message is top level, and will always be, so no need to 02808 // reevaluate it. 02809 if (replymd5.isEmpty() 02810 && replyToAuxId.isEmpty() 02811 && !kmb->subjectIsPrefixed() ) 02812 parent_id = -2; 02813 // FIXME also mark messages with -1 as -2 a certain amount of time after 02814 // their arrival, since it becomes very unlikely that a new parent for 02815 // them will show up. (Ingo suggests a month.) -till 02816 } 02817 internalWriteItem(sortStream, mFolder, i->mMsgId, parent_id, 02818 i->key(mSortCol, !mSortDescending), false); 02819 //double check for magic headers 02820 sorted_count++; 02821 } 02822 02823 //magic header twice, case they've changed 02824 fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET); 02825 fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream); 02826 fwrite(&column, sizeof(column), 1, sortStream); 02827 fwrite(&ascending, sizeof(ascending), 1, sortStream); 02828 fwrite(&threaded, sizeof(threaded), 1, sortStream); 02829 fwrite(&appended, sizeof(appended), 1, sortStream); 02830 fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream); 02831 fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream); 02832 if (sortStream && ferror(sortStream)) { 02833 fclose(sortStream); 02834 unlink(QFile::encodeName(sortFile)); 02835 kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; 02836 kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; 02837 kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); 02838 } 02839 fclose(sortStream); 02840 ::rename(QFile::encodeName(tempName), QFile::encodeName(sortFile)); 02841 } 02842 02843 return true; 02844 } 02845 02846 void KMHeaders::appendItemToSortFile(KMHeaderItem *khi) 02847 { 02848 QString sortFile = KMAIL_SORT_FILE(mFolder); 02849 if(FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+")) { 02850 int parent_id = -1; //no parent, top level 02851 02852 if (isThreaded()) { 02853 KMSortCacheItem *sci = khi->sortCacheItem(); 02854 KMMsgBase *kmb = mFolder->getMsgBase( khi->mMsgId ); 02855 if(sci->parent() && !sci->isImperfectlyThreaded()) 02856 parent_id = sci->parent()->id(); 02857 else if(kmb->replyToIdMD5().isEmpty() 02858 && kmb->replyToAuxIdMD5().isEmpty() 02859 && !kmb->subjectIsPrefixed()) 02860 parent_id = -2; 02861 } 02862 02863 internalWriteItem(sortStream, mFolder, khi->mMsgId, parent_id, 02864 khi->key(mSortCol, !mSortDescending), false); 02865 02866 //update the appended flag FIXME obsolete? 02867 Q_INT32 appended = 1; 02868 fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); 02869 fwrite(&appended, sizeof(appended), 1, sortStream); 02870 fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); 02871 02872 if (sortStream && ferror(sortStream)) { 02873 fclose(sortStream); 02874 unlink(QFile::encodeName(sortFile)); 02875 kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; 02876 kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; 02877 kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); 02878 } 02879 fclose(sortStream); 02880 } else { 02881 mSortInfo.dirty = true; 02882 } 02883 } 02884 02885 void KMHeaders::dirtySortOrder(int column) 02886 { 02887 mSortInfo.dirty = true; 02888 QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); 02889 setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true); 02890 } 02891 void KMSortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder, 02892 bool waiting_for_parent, bool update_discover) 02893 { 02894 if(mSortOffset == -1) { 02895 fseek(sortStream, 0, SEEK_END); 02896 mSortOffset = ftell(sortStream); 02897 } else { 02898 fseek(sortStream, mSortOffset, SEEK_SET); 02899 } 02900 02901 int parent_id = -1; 02902 if(!waiting_for_parent) { 02903 if(mParent && !isImperfectlyThreaded()) 02904 parent_id = mParent->id(); 02905 } 02906 internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover); 02907 } 02908 02909 static bool compare_ascending = false; 02910 static bool compare_toplevel = true; 02911 static int compare_KMSortCacheItem(const void *s1, const void *s2) 02912 { 02913 if ( !s1 || !s2 ) 02914 return 0; 02915 KMSortCacheItem **b1 = (KMSortCacheItem **)s1; 02916 KMSortCacheItem **b2 = (KMSortCacheItem **)s2; 02917 int ret = (*b1)->key().compare((*b2)->key()); 02918 if(compare_ascending || !compare_toplevel) 02919 ret = -ret; 02920 return ret; 02921 } 02922 02923 02924 void KMHeaders::buildThreadingTree( QMemArray<KMSortCacheItem *> sortCache ) 02925 { 02926 mSortCacheItems.clear(); 02927 mSortCacheItems.resize( mFolder->count() * 2 ); 02928 02929 // build a dict of all message id's 02930 for(int x = 0; x < mFolder->count(); x++) { 02931 KMMsgBase *mi = mFolder->getMsgBase(x); 02932 QString md5 = mi->msgIdMD5(); 02933 if(!md5.isEmpty()) 02934 mSortCacheItems.replace(md5, sortCache[x]); 02935 } 02936 } 02937 02938 02939 void KMHeaders::buildSubjectThreadingTree( QMemArray<KMSortCacheItem *> sortCache ) 02940 { 02941 mSubjectLists.clear(); // autoDelete is true 02942 mSubjectLists.resize( mFolder->count() * 2 ); 02943 02944 for(int x = 0; x < mFolder->count(); x++) { 02945 // Only a lot items that are now toplevel 02946 if ( sortCache[x]->parent() 02947 && sortCache[x]->parent()->id() != -666 ) continue; 02948 KMMsgBase *mi = mFolder->getMsgBase(x); 02949 QString subjMD5 = mi->strippedSubjectMD5(); 02950 if (subjMD5.isEmpty()) { 02951 mFolder->getMsgBase(x)->initStrippedSubjectMD5(); 02952 subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5(); 02953 } 02954 if( subjMD5.isEmpty() ) continue; 02955 02956 /* For each subject, keep a list of items with that subject 02957 * (stripped of prefixes) sorted by date. */ 02958 if (!mSubjectLists.find(subjMD5)) 02959 mSubjectLists.insert(subjMD5, new QPtrList<KMSortCacheItem>()); 02960 /* Insertion sort by date. These lists are expected to be very small. 02961 * Also, since the messages are roughly ordered by date in the store, 02962 * they should mostly be prepended at the very start, so insertion is 02963 * cheap. */ 02964 int p=0; 02965 for (QPtrListIterator<KMSortCacheItem> it(*mSubjectLists[subjMD5]); 02966 it.current(); ++it) { 02967 KMMsgBase *mb = mFolder->getMsgBase((*it)->id()); 02968 if ( mb->date() < mi->date()) 02969 break; 02970 p++; 02971 } 02972 mSubjectLists[subjMD5]->insert( p, sortCache[x]); 02973 } 02974 } 02975 02976 02977 KMSortCacheItem* KMHeaders::findParent(KMSortCacheItem *item) 02978 { 02979 KMSortCacheItem *parent = NULL; 02980 if (!item) return parent; 02981 KMMsgBase *msg = mFolder->getMsgBase(item->id()); 02982 QString replyToIdMD5 = msg->replyToIdMD5(); 02983 item->setImperfectlyThreaded(true); 02984 /* First, try if the message our Reply-To header points to 02985 * is available to thread below. */ 02986 if(!replyToIdMD5.isEmpty()) { 02987 parent = mSortCacheItems[replyToIdMD5]; 02988 if (parent) 02989 item->setImperfectlyThreaded(false); 02990 } 02991 if (!parent) { 02992 // If we dont have a replyToId, or if we have one and the 02993 // corresponding message is not in this folder, as happens 02994 // if you keep your outgoing messages in an OUTBOX, for 02995 // example, try the list of references, because the second 02996 // to last will likely be in this folder. replyToAuxIdMD5 02997 // contains the second to last one. 02998 QString ref = msg->replyToAuxIdMD5(); 02999 if (!ref.isEmpty()) 03000 parent = mSortCacheItems[ref]; 03001 } 03002 return parent; 03003 } 03004 03005 KMSortCacheItem* KMHeaders::findParentBySubject(KMSortCacheItem *item) 03006 { 03007 KMSortCacheItem *parent = NULL; 03008 if (!item) return parent; 03009 03010 KMMsgBase *msg = mFolder->getMsgBase(item->id()); 03011 03012 // Let's try by subject, but only if the subject is prefixed. 03013 // This is necessary to make for example cvs commit mailing lists 03014 // work as expected without having to turn threading off alltogether. 03015 if (!msg->subjectIsPrefixed()) 03016 return parent; 03017 03018 QString replyToIdMD5 = msg->replyToIdMD5(); 03019 QString subjMD5 = msg->strippedSubjectMD5(); 03020 if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) { 03021 /* Iterate over the list of potential parents with the same 03022 * subject, and take the closest one by date. */ 03023 for (QPtrListIterator<KMSortCacheItem> it2(*mSubjectLists[subjMD5]); 03024 it2.current(); ++it2) { 03025 KMMsgBase *mb = mFolder->getMsgBase((*it2)->id()); 03026 if ( !mb ) return parent; 03027 // make sure it's not ourselves 03028 if ( item == (*it2) ) continue; 03029 int delta = msg->date() - mb->date(); 03030 // delta == 0 is not allowed, to avoid circular threading 03031 // with duplicates. 03032 if (delta > 0 ) { 03033 // Don't use parents more than 6 weeks older than us. 03034 if (delta < 3628899) 03035 parent = (*it2); 03036 break; 03037 } 03038 } 03039 } 03040 return parent; 03041 } 03042 03043 bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread ) 03044 { 03045 //all cases 03046 Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended; 03047 Q_INT32 deleted_count = 0; 03048 bool unread_exists = false; 03049 bool jumpToUnread = GlobalSettings::jumpToUnread() || 03050 forceJumpToUnread; 03051 QMemArray<KMSortCacheItem *> sortCache(mFolder->count()); 03052 KMSortCacheItem root; 03053 root.setId(-666); //mark of the root! 03054 bool error = false; 03055 03056 //threaded cases 03057 QPtrList<KMSortCacheItem> unparented; 03058 mImperfectlyThreadedList.clear(); 03059 03060 //cleanup 03061 mItems.fill( 0, mFolder->count() ); 03062 sortCache.fill( 0 ); 03063 03064 QString sortFile = KMAIL_SORT_FILE(mFolder); 03065 FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+"); 03066 mSortInfo.fakeSort = 0; 03067 03068 if(sortStream) { 03069 mSortInfo.fakeSort = 1; 03070 int version = 0; 03071 if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1) 03072 version = -1; 03073 if(version == KMAIL_SORT_VERSION) { 03074 Q_INT32 byteOrder = 0; 03075 fread(&byteOrder, sizeof(byteOrder), 1, sortStream); 03076 if (byteOrder == 0x12345678) 03077 { 03078 fread(&column, sizeof(column), 1, sortStream); 03079 fread(&ascending, sizeof(ascending), 1, sortStream); 03080 fread(&threaded, sizeof(threaded), 1, sortStream); 03081 fread(&appended, sizeof(appended), 1, sortStream); 03082 fread(&discovered_count, sizeof(discovered_count), 1, sortStream); 03083 fread(&sorted_count, sizeof(sorted_count), 1, sortStream); 03084 03085 //Hackyness to work around qlistview problems 03086 KListView::setSorting(-1); 03087 header()->setSortIndicator(column, ascending); 03088 QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); 03089 //setup mSortInfo here now, as above may change it 03090 mSortInfo.dirty = false; 03091 mSortInfo.column = (short)column; 03092 mSortInfo.ascending = (compare_ascending = ascending); 03093 03094 KMSortCacheItem *item; 03095 unsigned long serNum, parentSerNum; 03096 int id, len, parent, x; 03097 QChar *tmp_qchar = 0; 03098 int tmp_qchar_len = 0; 03099 const int mFolderCount = mFolder->count(); 03100 QString key; 03101 03102 CREATE_TIMER(parse); 03103 START_TIMER(parse); 03104 for(x = 0; !feof(sortStream) && !error; x++) { 03105 off_t offset = ftell(sortStream); 03106 KMFolder *folder; 03107 //parse 03108 if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end 03109 !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) || 03110 !fread(&len, sizeof(len), 1, sortStream)) { 03111 break; 03112 } 03113 if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) { 03114 kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl; 03115 error = true; 03116 continue; 03117 } 03118 if(len) { 03119 if(len > tmp_qchar_len) { 03120 tmp_qchar = (QChar *)realloc(tmp_qchar, len); 03121 tmp_qchar_len = len; 03122 } 03123 if(!fread(tmp_qchar, len, 1, sortStream)) 03124 break; 03125 key = QString(tmp_qchar, len / 2); 03126 } else { 03127 key = QString(""); //yuck 03128 } 03129 03130 kmkernel->msgDict()->getLocation(serNum, &folder, &id); 03131 if (folder != mFolder) { 03132 ++deleted_count; 03133 continue; 03134 } 03135 if (parentSerNum < KMAIL_RESERVED) { 03136 parent = (int)parentSerNum - KMAIL_RESERVED; 03137 } else { 03138 kmkernel->msgDict()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent); 03139 if (folder != mFolder) 03140 parent = -1; 03141 } 03142 if ((id < 0) || (id >= mFolderCount) || 03143 (parent < -2) || (parent >= mFolderCount)) { // sanity checking 03144 kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl; 03145 error = true; 03146 continue; 03147 } 03148 03149 if ((item=sortCache[id])) { 03150 if (item->id() != -1) { 03151 kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl; 03152 error = true; 03153 continue; 03154 } 03155 item->setKey(key); 03156 item->setId(id); 03157 item->setOffset(offset); 03158 } else { 03159 item = sortCache[id] = new KMSortCacheItem(id, key, offset); 03160 } 03161 if (threaded && parent != -2) { 03162 if(parent == -1) { 03163 unparented.append(item); 03164 root.addUnsortedChild(item); 03165 } else { 03166 if( ! sortCache[parent] ) 03167 sortCache[parent] = new KMSortCacheItem; 03168 sortCache[parent]->addUnsortedChild(item); 03169 } 03170 } else { 03171 if(x < sorted_count ) 03172 root.addSortedChild(item); 03173 else { 03174 root.addUnsortedChild(item); 03175 } 03176 } 03177 } 03178 if (error || (x != sorted_count + discovered_count)) {// sanity check 03179 kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl; 03180 fclose(sortStream); 03181 sortStream = 0; 03182 } 03183 03184 if(tmp_qchar) 03185 free(tmp_qchar); 03186 END_TIMER(parse); 03187 SHOW_TIMER(parse); 03188 } 03189 else { 03190 fclose(sortStream); 03191 sortStream = 0; 03192 } 03193 } else { 03194 fclose(sortStream); 03195 sortStream = 0; 03196 } 03197 } 03198 03199 if (!sortStream) { 03200 mSortInfo.dirty = true; 03201 mSortInfo.column = column = mSortCol; 03202 mSortInfo.ascending = ascending = !mSortDescending; 03203 threaded = (isThreaded()); 03204 sorted_count = discovered_count = appended = 0; 03205 KListView::setSorting( mSortCol, !mSortDescending ); 03206 } 03207 //fill in empty holes 03208 if((sorted_count + discovered_count - deleted_count) < mFolder->count()) { 03209 CREATE_TIMER(holes); 03210 START_TIMER(holes); 03211 KMMsgBase *msg = 0; 03212 for(int x = 0; x < mFolder->count(); x++) { 03213 if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) { 03214 int sortOrder = column; 03215 if (mPaintInfo.orderOfArrival) 03216 sortOrder |= (1 << 6); 03217 if (mPaintInfo.status) 03218 sortOrder |= (1 << 5); 03219 sortCache[x] = new KMSortCacheItem( 03220 x, KMHeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder )); 03221 if(threaded) 03222 unparented.append(sortCache[x]); 03223 else 03224 root.addUnsortedChild(sortCache[x]); 03225 if(sortStream) 03226 sortCache[x]->updateSortFile(sortStream, mFolder, true, true); 03227 discovered_count++; 03228 appended = 1; 03229 } 03230 } 03231 END_TIMER(holes); 03232 SHOW_TIMER(holes); 03233 } 03234 03235 // Make sure we've placed everything in parent/child relationship. All 03236 // messages with a parent id of -1 in the sort file are reevaluated here. 03237 if (threaded) buildThreadingTree( sortCache ); 03238 QPtrList<KMSortCacheItem> toBeSubjThreaded; 03239 03240 if (threaded && !unparented.isEmpty()) { 03241 CREATE_TIMER(reparent); 03242 START_TIMER(reparent); 03243 03244 for(QPtrListIterator<KMSortCacheItem> it(unparented); it.current(); ++it) { 03245 KMSortCacheItem *item = (*it); 03246 KMSortCacheItem *parent = findParent( item ); 03247 // If we have a parent, make sure it's not ourselves 03248 if ( parent && (parent != (*it)) ) { 03249 parent->addUnsortedChild((*it)); 03250 if(sortStream) 03251 (*it)->updateSortFile(sortStream, mFolder); 03252 } else { 03253 // if we will attempt subject threading, add to the list, 03254 // otherwise to the root with them 03255 if (mSubjThreading) 03256 toBeSubjThreaded.append((*it)); 03257 else 03258 root.addUnsortedChild((*it)); 03259 } 03260 } 03261 03262 if (mSubjThreading) { 03263 buildSubjectThreadingTree( sortCache ); 03264 for(QPtrListIterator<KMSortCacheItem> it(toBeSubjThreaded); it.current(); ++it) { 03265 KMSortCacheItem *item = (*it); 03266 KMSortCacheItem *parent = findParentBySubject( item ); 03267 03268 if ( parent ) { 03269 parent->addUnsortedChild((*it)); 03270 if(sortStream) 03271 (*it)->updateSortFile(sortStream, mFolder); 03272 } else { 03273 //oh well we tried, to the root with you! 03274 root.addUnsortedChild((*it)); 03275 } 03276 } 03277 } 03278 END_TIMER(reparent); 03279 SHOW_TIMER(reparent); 03280 } 03281 //create headeritems 03282 CREATE_TIMER(header_creation); 03283 START_TIMER(header_creation); 03284 KMHeaderItem *khi; 03285 KMSortCacheItem *i, *new_kci; 03286 QPtrQueue<KMSortCacheItem> s; 03287 s.enqueue(&root); 03288 compare_toplevel = true; 03289 do { 03290 i = s.dequeue(); 03291 const QPtrList<KMSortCacheItem> *sorted = i->sortedChildren(); 03292 int unsorted_count, unsorted_off=0; 03293 KMSortCacheItem **unsorted = i->unsortedChildren(unsorted_count); 03294 if(unsorted) 03295 qsort(unsorted, unsorted_count, sizeof(KMSortCacheItem *), //sort 03296 compare_KMSortCacheItem); 03297 03298 /* The sorted list now contains all sorted children of this item, while 03299 * the (aptly named) unsorted array contains all as of yet unsorted 03300 * ones. It has just been qsorted, so it is in itself sorted. These two 03301 * sorted lists are now merged into one. */ 03302 for(QPtrListIterator<KMSortCacheItem> it(*sorted); 03303 (unsorted && unsorted_off < unsorted_count) || it.current(); ) { 03304 /* As long as we have something in the sorted list and there is 03305 nothing unsorted left, use the item from the sorted list. Also 03306 if we are sorting descendingly and the sorted item is supposed 03307 to be sorted before the unsorted one do so. In the ascending 03308 case we invert the logic for non top level items. */ 03309 if( it.current() && 03310 ( !unsorted || unsorted_off >= unsorted_count 03311 || 03312 ( ( !ascending || (ascending && !compare_toplevel) ) 03313 && (*it)->key() < unsorted[unsorted_off]->key() ) 03314 || 03315 ( ascending && (*it)->key() >= unsorted[unsorted_off]->key() ) 03316 ) 03317 ) 03318 { 03319 new_kci = (*it); 03320 ++it; 03321 } else { 03322 /* Otherwise use the next item of the unsorted list */ 03323 new_kci = unsorted[unsorted_off++]; 03324 } 03325 if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent 03326 continue; 03327 03328 if(threaded && i->item()) { 03329 // If the parent is watched or ignored, propagate that to it's 03330 // children 03331 if (mFolder->getMsgBase(i->id())->isWatched()) 03332 mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusWatched); 03333 if (mFolder->getMsgBase(i->id())->isIgnored()) { 03334 mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusIgnored); 03335 mFolder->setStatus(new_kci->id(), KMMsgStatusRead); 03336 } 03337 khi = new KMHeaderItem(i->item(), new_kci->id(), new_kci->key()); 03338 } else { 03339 khi = new KMHeaderItem(this, new_kci->id(), new_kci->key()); 03340 } 03341 new_kci->setItem(mItems[new_kci->id()] = khi); 03342 if(new_kci->hasChildren()) 03343 s.enqueue(new_kci); 03344 // we always jump to new messages, but we only jump to 03345 // unread messages if we are told to do so 03346 if ( mFolder->getMsgBase(new_kci->id())->isNew() || 03347 ( jumpToUnread && 03348 mFolder->getMsgBase(new_kci->id())->isUnread() ) ) { 03349 unread_exists = true; 03350 } 03351 } 03352 // If we are sorting by date and ascending the top level items are sorted 03353 // ascending and the threads themselves are sorted descending. One wants 03354 // to have new threads on top but the threads themselves top down. 03355 if (mSortCol == paintInfo()->dateCol) 03356 compare_toplevel = false; 03357 } while(!s.isEmpty()); 03358 03359 for(int x = 0; x < mFolder->count(); x++) { //cleanup 03360 if (!sortCache[x]) { // not yet there? 03361 continue; 03362 } 03363 03364 if (!sortCache[x]->item()) { // we missed a message, how did that happen ? 03365 kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. " 03366 << endl << "Please talk to your threading counselor asap. " << endl; 03367 khi = new KMHeaderItem(this, sortCache[x]->id(), sortCache[x]->key()); 03368 sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi); 03369 } 03370 // Add all imperfectly threaded items to a list, so they can be 03371 // reevaluated when a new message arrives which might be a better parent. 03372 // Important for messages arriving out of order. 03373 if (threaded && sortCache[x]->isImperfectlyThreaded()) { 03374 mImperfectlyThreadedList.append(sortCache[x]->item()); 03375 } 03376 // Set the reverse mapping KMHeaderItem -> KMSortCacheItem. Needed for 03377 // keeping the data structures up to date on removal, for example. 03378 sortCache[x]->item()->setSortCacheItem(sortCache[x]); 03379 } 03380 03381 if (getNestingPolicy()<2) 03382 for (KMHeaderItem *khi=static_cast<KMHeaderItem*>(firstChild()); khi!=0;khi=static_cast<KMHeaderItem*>(khi->nextSibling())) 03383 khi->setOpen(true); 03384 03385 END_TIMER(header_creation); 03386 SHOW_TIMER(header_creation); 03387 03388 if(sortStream) { //update the .sorted file now 03389 // heuristic for when it's time to rewrite the .sorted file 03390 if( discovered_count * discovered_count > sorted_count - deleted_count ) { 03391 mSortInfo.dirty = true; 03392 } else { 03393 //update the appended flag 03394 appended = 0; 03395 fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); 03396 fwrite(&appended, sizeof(appended), 1, sortStream); 03397 } 03398 } 03399 03400 //show a message 03401 CREATE_TIMER(selection); 03402 START_TIMER(selection); 03403 if(set_selection) { 03404 int first_unread = -1; 03405 if (unread_exists) { 03406 KMHeaderItem *item = static_cast<KMHeaderItem*>(firstChild()); 03407 while (item) { 03408 if ( mFolder->getMsgBase( item->msgId() )->isNew() || 03409 ( jumpToUnread && 03410 mFolder->getMsgBase( item->msgId() )->isUnread() ) ) { 03411 first_unread = item->msgId(); 03412 break; 03413 } 03414 item = static_cast<KMHeaderItem*>(item->itemBelow()); 03415 } 03416 } 03417 03418 if(first_unread == -1 ) { 03419 setTopItemByIndex(mTopItem); 03420 if ( mCurrentItem >= 0 ) 03421 setCurrentItemByIndex( mCurrentItem ); 03422 else if ( mCurrentItemSerNum > 0 ) 03423 setCurrentItemBySerialNum( mCurrentItemSerNum ); 03424 else 03425 setCurrentItemByIndex( 0 ); 03426 } else { 03427 setCurrentItemByIndex(first_unread); 03428 makeHeaderVisible(); 03429 center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 ); 03430 } 03431 } else { 03432 // only reset the selection if we have no current item 03433 if (mCurrentItem <= 0) { 03434 setTopItemByIndex(mTopItem); 03435 setCurrentItemByIndex(0); 03436 } 03437 } 03438 END_TIMER(selection); 03439 SHOW_TIMER(selection); 03440 if (error || (sortStream && ferror(sortStream))) { 03441 if ( sortStream ) 03442 fclose(sortStream); 03443 unlink(QFile::encodeName(sortFile)); 03444 kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; 03445 kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; 03446 //kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); 03447 } 03448 if(sortStream) 03449 fclose(sortStream); 03450 03451 return true; 03452 } 03453 03454 //----------------------------------------------------------------------------- 03455 void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum ) 03456 { 03457 // Linear search == slow. Don't overuse this method. 03458 // It's currently only used for finding the current item again 03459 // after expiry deleted mails (so the index got invalidated). 03460 for (int i = 0; i < (int)mItems.size() - 1; ++i) { 03461 KMMsgBase *mMsgBase = mFolder->getMsgBase( i ); 03462 if ( mMsgBase->getMsgSerNum() == serialNum ) { 03463 bool unchanged = (currentItem() == mItems[i]); 03464 setCurrentItem( mItems[i] ); 03465 setSelected( mItems[i], true ); 03466 setSelectionAnchor( currentItem() ); 03467 if ( unchanged ) 03468 highlightMessage( currentItem(), false ); 03469 ensureCurrentItemVisible(); 03470 return; 03471 } 03472 } 03473 // Not found. Maybe we should select the last item instead? 03474 kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl; 03475 } 03476 03477 #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 Oct 1 15:19:21 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003