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