kmail

kmheaders.cpp
1 // -*- mode: C++; c-file-style: "gnu" -*-
2 // kmheaders.cpp
3 
4 #include <config.h>
5 
6 #include "kmheaders.h"
7 #include "headeritem.h"
9 
10 #include "kcursorsaver.h"
11 #include "kmcommands.h"
12 #include "kmmainwidget.h"
13 #include "kmfiltermgr.h"
14 #include "undostack.h"
15 #include "kmmsgdict.h"
16 #include "kmdebug.h"
17 #include "kmfoldertree.h"
18 #include "folderjob.h"
19 using KMail::FolderJob;
20 #include "actionscheduler.h"
21 using KMail::ActionScheduler;
22 #include "messagecopyhelper.h"
24 #include "broadcaststatus.h"
25 using KPIM::BroadcastStatus;
26 #include "progressmanager.h"
27 using KPIM::ProgressManager;
28 using KPIM::ProgressItem;
29 #include <maillistdrag.h>
30 #include "globalsettings.h"
31 using namespace KPIM;
32 #include "messageactions.h"
33 
34 #include <kapplication.h>
35 #include <kaccelmanager.h>
36 #include <kglobalsettings.h>
37 #include <kmessagebox.h>
38 #include <kiconloader.h>
39 #include <kpopupmenu.h>
40 #include <kimageio.h>
41 #include <kconfig.h>
42 #include <klocale.h>
43 #include <kdebug.h>
44 
45 #include <tqbuffer.h>
46 #include <tqeventloop.h>
47 #include <tqfile.h>
48 #include <tqheader.h>
49 #include <tqptrstack.h>
50 #include <tqptrqueue.h>
51 #include <tqpainter.h>
52 #include <tqtextcodec.h>
53 #include <tqstyle.h>
54 #include <tqlistview.h>
55 
56 #include <mimelib/enum.h>
57 #include <mimelib/field.h>
58 #include <mimelib/mimepp.h>
59 
60 #include <stdlib.h>
61 #include <errno.h>
62 
63 #include "textsource.h"
64 
65 TQPixmap* KMHeaders::pixNew = 0;
66 TQPixmap* KMHeaders::pixUns = 0;
67 TQPixmap* KMHeaders::pixDel = 0;
68 TQPixmap* KMHeaders::pixRead = 0;
69 TQPixmap* KMHeaders::pixRep = 0;
70 TQPixmap* KMHeaders::pixQueued = 0;
71 TQPixmap* KMHeaders::pixTodo = 0;
72 TQPixmap* KMHeaders::pixSent = 0;
73 TQPixmap* KMHeaders::pixFwd = 0;
74 TQPixmap* KMHeaders::pixFlag = 0;
75 TQPixmap* KMHeaders::pixWatched = 0;
76 TQPixmap* KMHeaders::pixIgnored = 0;
77 TQPixmap* KMHeaders::pixSpam = 0;
78 TQPixmap* KMHeaders::pixHam = 0;
79 TQPixmap* KMHeaders::pixFullySigned = 0;
80 TQPixmap* KMHeaders::pixPartiallySigned = 0;
81 TQPixmap* KMHeaders::pixUndefinedSigned = 0;
82 TQPixmap* KMHeaders::pixFullyEncrypted = 0;
83 TQPixmap* KMHeaders::pixPartiallyEncrypted = 0;
84 TQPixmap* KMHeaders::pixUndefinedEncrypted = 0;
85 TQPixmap* KMHeaders::pixEncryptionProblematic = 0;
86 TQPixmap* KMHeaders::pixSignatureProblematic = 0;
87 TQPixmap* KMHeaders::pixAttachment = 0;
88 TQPixmap* KMHeaders::pixInvitation = 0;
89 TQPixmap* KMHeaders::pixReadFwd = 0;
90 TQPixmap* KMHeaders::pixReadReplied = 0;
91 TQPixmap* KMHeaders::pixReadFwdReplied = 0;
92 
93 
94 //-----------------------------------------------------------------------------
95 KMHeaders::KMHeaders(KMMainWidget *aOwner, TQWidget *parent,
96  const char *name) :
97  KListView( parent, name ),
98  mIgnoreSortOrderChanges( false )
99 {
100  static bool pixmapsLoaded = false;
101  //qInitImageIO();
102  KImageIO::registerFormats();
103  mOwner = aOwner;
104  mFolder = 0;
105  noRepaint = false;
106  getMsgIndex = -1;
107  mTopItem = 0;
108  setSelectionMode( TQListView::Extended );
109  setAllColumnsShowFocus( true );
110  mNested = false;
111  nestingPolicy = OpenUnread;
112  mNestedOverride = false;
113  mSubjThreading = true;
114  mMousePressed = false;
115  mSortInfo.dirty = true;
116  mSortInfo.fakeSort = 0;
117  mSortInfo.removed = 0;
118  mSortInfo.column = 0;
119  mSortCol = 2; // 2 == date
120  mSortDescending = false;
121  mSortInfo.ascending = false;
122  mReaderWindowActive = false;
123  mRoot = new SortCacheItem;
124  mRoot->setId(-666); //mark of the root!
125  setStyleDependantFrameWidth();
126  // popup-menu
127  header()->setClickEnabled(true);
128  header()->installEventFilter(this);
129  mPopup = new KPopupMenu(this);
130  mPopup->insertTitle(i18n("View Columns"));
131  mPopup->setCheckable(true);
132  mPopup->insertItem(i18n("Status"), KPaintInfo::COL_STATUS);
133  mPopup->insertItem(i18n("Important"), KPaintInfo::COL_IMPORTANT);
134  mPopup->insertItem(i18n("Action Item"), KPaintInfo::COL_TODO);
135  mPopup->insertItem(i18n("Attachment"), KPaintInfo::COL_ATTACHMENT);
136  mPopup->insertItem(i18n("Invitation"), KPaintInfo::COL_INVITATION);
137  mPopup->insertItem(i18n("Spam/Ham"), KPaintInfo::COL_SPAM_HAM);
138  mPopup->insertItem(i18n("Watched/Ignored"), KPaintInfo::COL_WATCHED_IGNORED);
139  mPopup->insertItem(i18n("Signature"), KPaintInfo::COL_SIGNED);
140  mPopup->insertItem(i18n("Encryption"), KPaintInfo::COL_CRYPTO);
141  mPopup->insertItem(i18n("Size"), KPaintInfo::COL_SIZE);
142  mPopup->insertItem(i18n("Receiver"), KPaintInfo::COL_RECEIVER);
143 
144  connect(mPopup, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotToggleColumn(int)));
145 
146  setShowSortIndicator(true);
147  setFocusPolicy( TQ_WheelFocus );
148 
149  if (!pixmapsLoaded)
150  {
151  pixmapsLoaded = true;
152  pixNew = new TQPixmap( UserIcon( "kmmsgnew" ) );
153  pixUns = new TQPixmap( UserIcon( "kmmsgunseen" ) );
154  pixDel = new TQPixmap( UserIcon( "kmmsgdel" ) );
155  pixRead = new TQPixmap( UserIcon( "kmmsgread" ) );
156  pixRep = new TQPixmap( UserIcon( "kmmsgreplied" ) );
157  pixQueued = new TQPixmap( UserIcon( "kmmsgqueued" ) );
158  pixTodo = new TQPixmap( UserIcon( "kmmsgtodo" ) );
159  pixSent = new TQPixmap( UserIcon( "kmmsgsent" ) );
160  pixFwd = new TQPixmap( UserIcon( "kmmsgforwarded" ) );
161  pixFlag = new TQPixmap( UserIcon( "kmmsgflag" ) );
162  pixWatched = new TQPixmap( UserIcon( "kmmsgwatched" ) );
163  pixIgnored = new TQPixmap( UserIcon( "kmmsgignored" ) );
164  pixSpam = new TQPixmap( UserIcon( "kmmsgspam" ) );
165  pixHam = new TQPixmap( UserIcon( "kmmsgham" ) );
166  pixFullySigned = new TQPixmap( UserIcon( "kmmsgfullysigned" ) );
167  pixPartiallySigned = new TQPixmap( UserIcon( "kmmsgpartiallysigned" ) );
168  pixUndefinedSigned = new TQPixmap( UserIcon( "kmmsgundefinedsigned" ) );
169  pixFullyEncrypted = new TQPixmap( UserIcon( "kmmsgfullyencrypted" ) );
170  pixPartiallyEncrypted = new TQPixmap( UserIcon( "kmmsgpartiallyencrypted" ) );
171  pixUndefinedEncrypted = new TQPixmap( UserIcon( "kmmsgundefinedencrypted" ) );
172  pixEncryptionProblematic = new TQPixmap( UserIcon( "kmmsgencryptionproblematic" ) );
173  pixSignatureProblematic = new TQPixmap( UserIcon( "kmmsgsignatureproblematic" ) );
174  pixAttachment = new TQPixmap( UserIcon( "kmmsgattachment" ) );
175  pixInvitation = new TQPixmap( UserIcon( "kmmsginvitation" ) );
176  pixReadFwd = new TQPixmap( UserIcon( "kmmsgread_fwd" ) );
177  pixReadReplied = new TQPixmap( UserIcon( "kmmsgread_replied" ) );
178  pixReadFwdReplied = new TQPixmap( UserIcon( "kmmsgread_fwd_replied" ) );
179  }
180 
181  header()->setStretchEnabled( false );
182  header()->setResizeEnabled( false );
183 
184  mPaintInfo.subCol = addColumn( i18n("Subject"), 310 );
185  mPaintInfo.senderCol = addColumn( i18n("Sender"), 170 );
186  mPaintInfo.dateCol = addColumn( i18n("Date"), 170 );
187  mPaintInfo.sizeCol = addColumn( i18n("Size"), 0 );
188  mPaintInfo.receiverCol = addColumn( i18n("Receiver"), 0 );
189 
190  mPaintInfo.statusCol = addColumn( *pixNew , "", 0 );
191  mPaintInfo.importantCol = addColumn( *pixFlag , "", 0 );
192  mPaintInfo.todoCol = addColumn( *pixTodo , "", 0 );
193  mPaintInfo.attachmentCol = addColumn( *pixAttachment , "", 0 );
194  mPaintInfo.invitationCol = addColumn( *pixInvitation , "", 0 );
195  mPaintInfo.spamHamCol = addColumn( *pixSpam , "", 0 );
196  mPaintInfo.watchedIgnoredCol = addColumn( *pixWatched , "", 0 );
197  mPaintInfo.signedCol = addColumn( *pixFullySigned , "", 0 );
198  mPaintInfo.cryptoCol = addColumn( *pixFullyEncrypted, "", 0 );
199 
200  setResizeMode( TQListView::NoColumn );
201 
202  // only the non-optional columns shall be resizeable
203  header()->setResizeEnabled( true, mPaintInfo.subCol );
204  header()->setResizeEnabled( true, mPaintInfo.senderCol );
205  header()->setResizeEnabled( true, mPaintInfo.dateCol );
206 
207  connect( this, TQT_SIGNAL( contextMenuRequested( TQListViewItem*, const TQPoint &, int )),
208  this, TQT_SLOT( rightButtonPressed( TQListViewItem*, const TQPoint &, int )));
209  connect(this, TQT_SIGNAL(doubleClicked(TQListViewItem*)),
210  this,TQT_SLOT(selectMessage(TQListViewItem*)));
211  connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
212  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
213  resetCurrentTime();
214 
215  mSubjectLists.setAutoDelete( true );
216 
217  mMoveMessages = false;
218  connect( this, TQT_SIGNAL(selectionChanged()), TQT_SLOT(updateActions()) );
219 }
220 
221 
222 //-----------------------------------------------------------------------------
223 KMHeaders::~KMHeaders ()
224 {
225  if (mFolder)
226  {
228  writeSortOrder();
229  mFolder->close("kmheaders");
230  }
231  writeConfig();
232  delete mRoot;
233 }
234 
235 //-----------------------------------------------------------------------------
236 bool KMHeaders::eventFilter ( TQObject *o, TQEvent *e )
237 {
238  if ( e->type() == TQEvent::MouseButtonPress &&
239  TQT_TQMOUSEEVENT(e)->button() == Qt::RightButton &&
240  o->isA(TQHEADER_OBJECT_NAME_STRING) )
241  {
242  // if we currently only show one of either sender/receiver column
243  // modify the popup text in the way, that it displays the text of the other of the two
244  if ( mPaintInfo.showReceiver )
245  mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
246  else
247  if ( mFolder && (mFolder->whoField().lower() == "to") )
248  mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Sender"));
249  else
250  mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
251 
252  mPopup->popup( TQT_TQMOUSEEVENT(e)->globalPos() );
253  return true;
254  }
255  return KListView::eventFilter(o, e);
256 }
257 
258 //-----------------------------------------------------------------------------
259 
260 void KMHeaders::slotToggleColumn(int id, int mode)
261 {
262  bool *show = 0;
263  int *col = 0;
264  int width = 0;
265  int moveToCol = -1;
266 
267  switch ( static_cast<KPaintInfo::ColumnIds>(id) )
268  {
269  case KPaintInfo::COL_SIZE:
270  {
271  show = &mPaintInfo.showSize;
272  col = &mPaintInfo.sizeCol;
273  width = 80;
274  break;
275  }
276  case KPaintInfo::COL_ATTACHMENT:
277  {
278  show = &mPaintInfo.showAttachment;
279  col = &mPaintInfo.attachmentCol;
280  width = pixAttachment->width() + 8;
281  if ( *col == header()->mapToIndex( *col ) )
282  moveToCol = 0;
283  break;
284  }
285  case KPaintInfo::COL_INVITATION:
286  {
287  show = &mPaintInfo.showInvitation;
288  col = &mPaintInfo.invitationCol;
289  width = pixAttachment->width() + 8;
290  if ( *col == header()->mapToIndex( *col ) )
291  moveToCol = 0;
292  break;
293  }
294  case KPaintInfo::COL_IMPORTANT:
295  {
296  show = &mPaintInfo.showImportant;
297  col = &mPaintInfo.importantCol;
298  width = pixFlag->width() + 8;
299  if ( *col == header()->mapToIndex( *col ) )
300  moveToCol = 0;
301  break;
302  }
303  case KPaintInfo::COL_TODO:
304  {
305  show = &mPaintInfo.showTodo;
306  col = &mPaintInfo.todoCol;
307  width = pixTodo->width() + 8;
308  if ( *col == header()->mapToIndex( *col ) )
309  moveToCol = 0;
310  break;
311  }
312  case KPaintInfo::COL_SPAM_HAM:
313  {
314  show = &mPaintInfo.showSpamHam;
315  col = &mPaintInfo.spamHamCol;
316  width = pixSpam->width() + 8;
317  if ( *col == header()->mapToIndex( *col ) )
318  moveToCol = 0;
319  break;
320  }
321  case KPaintInfo::COL_WATCHED_IGNORED:
322  {
323  show = &mPaintInfo.showWatchedIgnored;
324  col = &mPaintInfo.watchedIgnoredCol;
325  width = pixWatched->width() + 8;
326  if ( *col == header()->mapToIndex( *col ) )
327  moveToCol = 0;
328  break;
329  }
330  case KPaintInfo::COL_STATUS:
331  {
332  show = &mPaintInfo.showStatus;
333  col = &mPaintInfo.statusCol;
334  width = pixNew->width() + 8;
335  if ( *col == header()->mapToIndex( *col ) )
336  moveToCol = 0;
337  break;
338  }
339  case KPaintInfo::COL_SIGNED:
340  {
341  show = &mPaintInfo.showSigned;
342  col = &mPaintInfo.signedCol;
343  width = pixFullySigned->width() + 8;
344  if ( *col == header()->mapToIndex( *col ) )
345  moveToCol = 0;
346  break;
347  }
348  case KPaintInfo::COL_CRYPTO:
349  {
350  show = &mPaintInfo.showCrypto;
351  col = &mPaintInfo.cryptoCol;
352  width = pixFullyEncrypted->width() + 8;
353  if ( *col == header()->mapToIndex( *col ) )
354  moveToCol = 0;
355  break;
356  }
357  case KPaintInfo::COL_RECEIVER:
358  {
359  show = &mPaintInfo.showReceiver;
360  col = &mPaintInfo.receiverCol;
361  width = 170;
362  break;
363  }
364  case KPaintInfo::COL_SCORE: ; // only used by KNode
365  // don't use default, so that the compiler tells us you forgot to code here for a new column
366  }
367 
368  assert(show);
369 
370  if (mode == -1)
371  *show = !*show;
372  else
373  *show = mode;
374 
375  mPopup->setItemChecked(id, *show);
376 
377  if (*show) {
378  header()->setResizeEnabled(true, *col);
379  setColumnWidth(*col, width);
380  if ( moveToCol >= 0 )
381  header()->moveSection( *col, moveToCol );
382  }
383  else {
384  header()->setResizeEnabled(false, *col);
385  header()->setStretchEnabled(false, *col);
386  hideColumn(*col);
387  }
388 
389  // if we change the visibility of the receiver column,
390  // the sender column has to show either the sender or the receiver
391  if ( static_cast<KPaintInfo::ColumnIds>(id) == KPaintInfo::COL_RECEIVER ) {
392  TQString colText = i18n( "Sender" );
393  if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
394  colText = i18n( "Receiver" );
395  setColumnText( mPaintInfo.senderCol, colText );
396  }
397 
398  if (mode == -1)
399  writeConfig();
400 }
401 
402 //-----------------------------------------------------------------------------
403 // Support for backing pixmap
404 void KMHeaders::paintEmptyArea( TQPainter * p, const TQRect & rect )
405 {
406  if (mPaintInfo.pixmapOn)
407  p->drawTiledPixmap( rect.left(), rect.top(), rect.width(), rect.height(),
408  mPaintInfo.pixmap,
409  rect.left() + contentsX(),
410  rect.top() + contentsY() );
411  else
412  p->fillRect( rect, colorGroup().base() );
413 }
414 
415 bool KMHeaders::event(TQEvent *e)
416 {
417  bool result = KListView::event(e);
418  if (e->type() == TQEvent::ApplicationPaletteChange)
419  {
420  readColorConfig();
421  }
422  return result;
423 }
424 
425 
426 //-----------------------------------------------------------------------------
428 {
429  KConfig* config = KMKernel::config();
430  // Custom/System colors
431  KConfigGroupSaver saver(config, "Reader");
432  TQColor c1=TQColor(kapp->palette().active().text());
433  TQColor c2=TQColor("red");
434  TQColor c3=TQColor("blue");
435  TQColor c4=TQColor(kapp->palette().active().base());
436  TQColor c5=TQColor(0,0x7F,0);
437  TQColor c6=TQColor(0,0x98,0);
438  TQColor c7=KGlobalSettings::alternateBackgroundColor();
439 
440  if (!config->readBoolEntry("defaultColors",true)) {
441  mPaintInfo.colFore = config->readColorEntry("ForegroundColor",&c1);
442  mPaintInfo.colBack = config->readColorEntry("BackgroundColor",&c4);
443  TQPalette newPal = kapp->palette();
444  newPal.setColor( TQColorGroup::Base, mPaintInfo.colBack );
445  newPal.setColor( TQColorGroup::Text, mPaintInfo.colFore );
446  setPalette( newPal );
447  mPaintInfo.colNew = config->readColorEntry("NewMessage",&c2);
448  mPaintInfo.colUnread = config->readColorEntry("UnreadMessage",&c3);
449  mPaintInfo.colFlag = config->readColorEntry("FlagMessage",&c5);
450  mPaintInfo.colTodo = config->readColorEntry("TodoMessage",&c6);
451  c7 = config->readColorEntry("AltBackgroundColor",&c7);
452  }
453  else {
454  mPaintInfo.colFore = c1;
455  mPaintInfo.colBack = c4;
456  TQPalette newPal = kapp->palette();
457  newPal.setColor( TQColorGroup::Base, c4 );
458  newPal.setColor( TQColorGroup::Text, c1 );
459  setPalette( newPal );
460  mPaintInfo.colNew = c2;
461  mPaintInfo.colUnread = c3;
462  mPaintInfo.colFlag = c5;
463  mPaintInfo.colTodo = c6;
464  }
465  setAlternateBackground(c7);
466 }
467 
468 //-----------------------------------------------------------------------------
470 {
471  KConfig* config = KMKernel::config();
472 
473  // Backing pixmap support
474  { // area for config group "Pixmaps"
475  KConfigGroupSaver saver(config, "Pixmaps");
476  TQString pixmapFile = config->readEntry("Headers");
477  mPaintInfo.pixmapOn = false;
478  if (!pixmapFile.isEmpty()) {
479  mPaintInfo.pixmapOn = true;
480  mPaintInfo.pixmap = TQPixmap( pixmapFile );
481  }
482  }
483 
484  { // area for config group "General"
485  KConfigGroupSaver saver(config, "General");
486  bool show = config->readBoolEntry("showMessageSize");
487  slotToggleColumn(KPaintInfo::COL_SIZE, show);
488 
489  show = config->readBoolEntry("showAttachmentColumn");
490  slotToggleColumn(KPaintInfo::COL_ATTACHMENT, show);
491 
492  show = config->readBoolEntry("showInvitationColumn");
493  slotToggleColumn(KPaintInfo::COL_INVITATION, show);
494 
495  show = config->readBoolEntry("showImportantColumn");
496  slotToggleColumn(KPaintInfo::COL_IMPORTANT, show);
497 
498  show = config->readBoolEntry("showTodoColumn");
499  slotToggleColumn(KPaintInfo::COL_TODO, show);
500 
501  show = config->readBoolEntry("showSpamHamColumn");
502  slotToggleColumn(KPaintInfo::COL_SPAM_HAM, show);
503 
504  show = config->readBoolEntry("showWatchedIgnoredColumn");
505  slotToggleColumn(KPaintInfo::COL_WATCHED_IGNORED, show);
506 
507  show = config->readBoolEntry("showStatusColumn");
508  slotToggleColumn(KPaintInfo::COL_STATUS, show);
509 
510  show = config->readBoolEntry("showSignedColumn");
511  slotToggleColumn(KPaintInfo::COL_SIGNED, show);
512 
513  show = config->readBoolEntry("showCryptoColumn");
514  slotToggleColumn(KPaintInfo::COL_CRYPTO, show);
515 
516  show = config->readBoolEntry("showReceiverColumn");
517  slotToggleColumn(KPaintInfo::COL_RECEIVER, show);
518 
519  mPaintInfo.showCryptoIcons = config->readBoolEntry( "showCryptoIcons", false );
520  mPaintInfo.showAttachmentIcon = config->readBoolEntry( "showAttachmentIcon", true );
521  mPaintInfo.showInvitationIcon = config->readBoolEntry( "showInvitationIcon", false );
522 
523  KMime::DateFormatter::FormatType t =
524  (KMime::DateFormatter::FormatType) config->readNumEntry("dateFormat", KMime::DateFormatter::Fancy ) ;
525  mDate.setCustomFormat( config->readEntry("customDateFormat") );
526  mDate.setFormat( t );
527  }
528 
529  readColorConfig();
530 
531  // Custom/System fonts
532  { // area for config group "General"
533  KConfigGroupSaver saver(config, "Fonts");
534  if (!(config->readBoolEntry("defaultFonts",true)))
535  {
536  TQFont listFont( KGlobalSettings::generalFont() );
537  listFont = config->readFontEntry( "list-font", &listFont );
538  setFont( listFont );
539  mNewFont = config->readFontEntry( "list-new-font", &listFont );
540  mUnreadFont = config->readFontEntry( "list-unread-font", &listFont );
541  mImportantFont = config->readFontEntry( "list-important-font", &listFont );
542  mTodoFont = config->readFontEntry( "list-todo-font", &listFont );
543  mDateFont = KGlobalSettings::fixedFont();
544  mDateFont = config->readFontEntry( "list-date-font", &mDateFont );
545  } else {
546  mNewFont= mUnreadFont = mImportantFont = mDateFont = mTodoFont =
547  KGlobalSettings::generalFont();
548  setFont( mDateFont );
549  }
550  }
551 
552  // Behavior
553  {
554  KConfigGroupSaver saver(config, "Geometry");
555  mReaderWindowActive = config->readEntry( "readerWindowMode", "below" ) != "hide";
556  }
557 }
558 
559 //-----------------------------------------------------------------------------
560 void KMHeaders::restoreColumnLayout( KConfig *config, const TQString &group )
561 {
562  // KListView::restoreLayout() will call setSorting(), which is reimplemented by us.
563  // We don't want to change the sort order, so we set a flag here that is checked in
564  // setSorting().
565  mIgnoreSortOrderChanges = true;
566  restoreLayout( config, group );
567  mIgnoreSortOrderChanges = false;
568 }
569 
570 //-----------------------------------------------------------------------------
572 {
573  int top = topItemIndex();
574  int id = currentItemIndex();
575  noRepaint = true;
576  clear();
577  TQString colText = i18n( "Sender" );
578  if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
579  colText = i18n( "Receiver" );
580  setColumnText( mPaintInfo.senderCol, colText );
581  noRepaint = false;
582  mItems.resize(0);
583  updateMessageList();
584  setCurrentMsg(id);
585  setTopItemByIndex(top);
586  ensureCurrentItemVisible();
587 }
588 
589 //-----------------------------------------------------------------------------
591 {
592  bool oldState = isThreaded();
593  NestingPolicy oldNestPolicy = nestingPolicy;
594  KConfig* config = KMKernel::config();
595  KConfigGroupSaver saver(config, "Geometry");
596  mNested = config->readBoolEntry( "nestedMessages", false );
597 
598  nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
599  if ((nestingPolicy != oldNestPolicy) ||
600  (oldState != isThreaded()))
601  {
602  setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
603  reset();
604  }
605 
606 }
607 
608 //-----------------------------------------------------------------------------
610 {
611  if (!mFolder) return;
612  KConfig* config = KMKernel::config();
613 
614  KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
615  mNestedOverride = config->readBoolEntry( "threadMessagesOverride", false );
616  mSortCol = config->readNumEntry("SortColumn", mSortCol+1 /* inited to date column */);
617  mSortDescending = (mSortCol < 0);
618  mSortCol = abs(mSortCol) - 1;
619 
620  mTopItem = config->readNumEntry("Top", 0);
621  mCurrentItem = config->readNumEntry("Current", 0);
622  mCurrentItemSerNum = config->readNumEntry("CurrentSerialNum", 0);
623 
624  mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", false );
625  mPaintInfo.status = config->readBoolEntry( "Status", false );
626 
627  { //area for config group "Geometry"
628  KConfigGroupSaver saver(config, "Geometry");
629  mNested = config->readBoolEntry( "nestedMessages", false );
630  nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
631  }
632 
633  setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
634  mSubjThreading = config->readBoolEntry( "threadMessagesBySubject", true );
635 }
636 
637 
638 //-----------------------------------------------------------------------------
640 {
641  if (!mFolder) return;
642  KConfig* config = KMKernel::config();
643  int mSortColAdj = mSortCol + 1;
644 
645  KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
646  config->writeEntry("SortColumn", (mSortDescending ? -mSortColAdj : mSortColAdj));
647  config->writeEntry("Top", topItemIndex());
648  config->writeEntry("Current", currentItemIndex());
649  HeaderItem* current = currentHeaderItem();
650  ulong sernum = 0;
651  if ( current && mFolder->getMsgBase( current->msgId() ) )
652  sernum = mFolder->getMsgBase( current->msgId() )->getMsgSerNum();
653  config->writeEntry("CurrentSerialNum", sernum);
654 
655  config->writeEntry("OrderOfArrival", mPaintInfo.orderOfArrival);
656  config->writeEntry("Status", mPaintInfo.status);
657 }
658 
659 //-----------------------------------------------------------------------------
661 {
662  KConfig* config = KMKernel::config();
663  saveLayout(config, "Header-Geometry");
664  KConfigGroupSaver saver(config, "General");
665  config->writeEntry("showMessageSize" , mPaintInfo.showSize);
666  config->writeEntry("showAttachmentColumn" , mPaintInfo.showAttachment);
667  config->writeEntry("showInvitationColumn" , mPaintInfo.showInvitation);
668  config->writeEntry("showImportantColumn" , mPaintInfo.showImportant);
669  config->writeEntry("showTodoColumn" , mPaintInfo.showTodo);
670  config->writeEntry("showSpamHamColumn" , mPaintInfo.showSpamHam);
671  config->writeEntry("showWatchedIgnoredColumn", mPaintInfo.showWatchedIgnored);
672  config->writeEntry("showStatusColumn" , mPaintInfo.showStatus);
673  config->writeEntry("showSignedColumn" , mPaintInfo.showSigned);
674  config->writeEntry("showCryptoColumn" , mPaintInfo.showCrypto);
675  config->writeEntry("showReceiverColumn" , mPaintInfo.showReceiver);
676 }
677 
678 //-----------------------------------------------------------------------------
679 void KMHeaders::setFolder( KMFolder *aFolder, bool forceJumpToUnread )
680 {
681  CREATE_TIMER(set_folder);
682  START_TIMER(set_folder);
683 
684  int id;
685  TQString str;
686 
687  mSortInfo.fakeSort = 0;
688  if ( mFolder && static_cast<KMFolder*>(mFolder) == aFolder ) {
689  int top = topItemIndex();
690  id = currentItemIndex();
693  updateMessageList(); // do not change the selection
694  setCurrentMsg(id);
695  setTopItemByIndex(top);
696  } else {
697  if (mFolder) {
698  // WABA: Make sure that no KMReaderWin is still using a msg
699  // from this folder, since it's msg's are about to be deleted.
700  highlightMessage(0, false);
701 
702  disconnect(mFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder*)),
703  this, TQT_SLOT(setFolderInfoStatus()));
704 
705  mFolder->markNewAsUnread();
707  disconnect(mFolder, TQT_SIGNAL(msgHeaderChanged(KMFolder*,int)),
708  this, TQT_SLOT(msgHeaderChanged(KMFolder*,int)));
709  disconnect(mFolder, TQT_SIGNAL(msgAdded(int)),
710  this, TQT_SLOT(msgAdded(int)));
711  disconnect(mFolder, TQT_SIGNAL( msgRemoved( int, TQString ) ),
712  this, TQT_SLOT( msgRemoved( int, TQString ) ) );
713  disconnect(mFolder, TQT_SIGNAL(changed()),
714  this, TQT_SLOT(msgChanged()));
715  disconnect(mFolder, TQT_SIGNAL(cleared()),
716  this, TQT_SLOT(folderCleared()));
717  disconnect(mFolder, TQT_SIGNAL(expunged( KMFolder* )),
718  this, TQT_SLOT(folderCleared()));
719  disconnect(mFolder, TQT_SIGNAL(closed()),
720  this, TQT_SLOT(folderClosed()));
721  disconnect( mFolder, TQT_SIGNAL( statusMsg( const TQString& ) ),
722  BroadcastStatus::instance(), TQT_SLOT( setStatusMsg( const TQString& ) ) );
723  disconnect(mFolder, TQT_SIGNAL(viewConfigChanged()), this, TQT_SLOT(reset()));
724  writeSortOrder();
725  mFolder->close("kmheaders");
726  // System folders remain open but we also should write the index from
727  // time to time
728  if (mFolder->dirty()) mFolder->writeIndex();
729  }
730 
731  mSortInfo.removed = 0;
732  mFolder = aFolder;
733  mSortInfo.dirty = true;
734 
735  mOwner->useAction()->setEnabled( mFolder ?
736  ( kmkernel->folderIsTemplates( mFolder ) ) : false );
737  mOwner->messageActions()->replyListAction()->setEnabled( mFolder ?
738  mFolder->isMailingListEnabled() : false );
739  if ( mFolder ) {
740  connect(mFolder, TQT_SIGNAL(msgHeaderChanged(KMFolder*,int)),
741  this, TQT_SLOT(msgHeaderChanged(KMFolder*,int)));
742  connect(mFolder, TQT_SIGNAL(msgAdded(int)),
743  this, TQT_SLOT(msgAdded(int)));
744  connect(mFolder, TQT_SIGNAL(msgRemoved(int,TQString)),
745  this, TQT_SLOT(msgRemoved(int,TQString)));
746  connect(mFolder, TQT_SIGNAL(changed()),
747  this, TQT_SLOT(msgChanged()));
748  connect(mFolder, TQT_SIGNAL(cleared()),
749  this, TQT_SLOT(folderCleared()));
750  connect(mFolder, TQT_SIGNAL(expunged( KMFolder* )),
751  this, TQT_SLOT(folderCleared()));
752  connect(mFolder, TQT_SIGNAL(closed()),
753  this, TQT_SLOT(folderClosed()));
754  connect(mFolder, TQT_SIGNAL(statusMsg(const TQString&)),
755  BroadcastStatus::instance(), TQT_SLOT( setStatusMsg( const TQString& ) ) );
756  connect(mFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder*)),
757  this, TQT_SLOT(setFolderInfoStatus()));
758  connect(mFolder, TQT_SIGNAL(viewConfigChanged()), this, TQT_SLOT(reset()));
759 
760  // Not very nice, but if we go from nested to non-nested
761  // in the folderConfig below then we need to do this otherwise
762  // updateMessageList would do something unspeakable
763  if (isThreaded()) {
764  noRepaint = true;
765  clear();
766  noRepaint = false;
767  mItems.resize( 0 );
768  }
769 
771 
772  CREATE_TIMER(kmfolder_open);
773  START_TIMER(kmfolder_open);
774  mFolder->open("kmheaders");
775  END_TIMER(kmfolder_open);
776  SHOW_TIMER(kmfolder_open);
777 
778  if (isThreaded()) {
779  noRepaint = true;
780  clear();
781  noRepaint = false;
782  mItems.resize( 0 );
783  }
784  }
785 
786  CREATE_TIMER(updateMsg);
787  START_TIMER(updateMsg);
788  updateMessageList(true, forceJumpToUnread);
789  END_TIMER(updateMsg);
790  SHOW_TIMER(updateMsg);
793 
794  TQString colText = i18n( "Sender" );
795  if (mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
796  colText = i18n("Receiver");
797  setColumnText( mPaintInfo.senderCol, colText);
798 
799  colText = i18n( "Date" );
800  if (mPaintInfo.orderOfArrival)
801  colText = i18n( "Order of Arrival" );
802  setColumnText( mPaintInfo.dateCol, colText);
803 
804  colText = i18n( "Subject" );
805  if (mPaintInfo.status)
806  colText = colText + i18n( " (Status)" );
807  setColumnText( mPaintInfo.subCol, colText);
808  }
809 
810  updateActions();
811 
812  END_TIMER(set_folder);
813  SHOW_TIMER(set_folder);
814 }
815 
816 //-----------------------------------------------------------------------------
818 {
819  if (mFolder->count() == 0) { // Folder cleared
820  mItems.resize(0);
821  clear();
822  return;
823  }
824  if (!isUpdatesEnabled()) return;
825 
826  // Remember selected messages, current message and some scrollbar data, as we have to restore it
827  const TQValueList<int> oldSelectedItems = selectedItems();
828  const int oldCurrentItemIndex = currentItemIndex();
829  const bool scrollbarAtTop = verticalScrollBar() &&
830  verticalScrollBar()->value() == verticalScrollBar()->minValue();
831  const bool scrollbarAtBottom = verticalScrollBar() &&
832  verticalScrollBar()->value() == verticalScrollBar()->maxValue();
833  const HeaderItem * const oldFirstVisibleItem = dynamic_cast<HeaderItem*>( itemAt( TQPoint( 0, 0 ) ) );
834  const int oldOffsetOfFirstVisibleItem = itemRect( oldFirstVisibleItem ).y();
835  const uint oldSerNumOfFirstVisibleItem = oldFirstVisibleItem ? oldFirstVisibleItem->msgSerNum() : 0;
836 
837  TQString msgIdMD5;
838  TQListViewItem *item = currentItem();
839  HeaderItem *hi = dynamic_cast<HeaderItem*>(item);
840  if (item && hi) {
841  // get the msgIdMD5 to compare it later
842  KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
843  if (mb)
844  msgIdMD5 = mb->msgIdMD5();
845  }
846 // if (!isUpdatesEnabled()) return;
847  // prevent IMAP messages from scrolling to top
848  disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
849  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
850 
851  updateMessageList(); // do not change the selection
852 
853  // Restore scrollbar state and selected and current messages
854  setCurrentMsg( oldCurrentItemIndex );
855  setSelectedByIndex( oldSelectedItems, true );
856  if ( scrollbarAtTop ) {
857  setContentsPos( 0, 0 );
858  } else if ( scrollbarAtBottom ) {
859  setContentsPos( 0, contentsHeight() );
860  } else if ( oldSerNumOfFirstVisibleItem > 0 ) {
861  for ( uint i = 0; i < mItems.size(); ++i ) {
862  const KMMsgBase * const mMsgBase = mFolder->getMsgBase( i );
863  if ( mMsgBase->getMsgSerNum() == oldSerNumOfFirstVisibleItem ) {
864  setContentsPos( 0, itemPos( mItems[i] ) - oldOffsetOfFirstVisibleItem );
865  break;
866  }
867  }
868  }
869 
870  connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
871  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
872 
873  // if the current message has changed then emit
874  // the selected signal to force an update
875 
876  // Normally the serial number of the message would be
877  // used to do this, but because we don't yet have
878  // guaranteed serial numbers for IMAP messages fall back
879  // to using the MD5 checksum of the msgId.
880  item = currentItem();
881  hi = dynamic_cast<HeaderItem*>(item);
882  if (item && hi) {
883  KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
884  if (mb) {
885  if (msgIdMD5.isEmpty() || (msgIdMD5 != mb->msgIdMD5()))
886  emit selected(mFolder->getMsg(hi->msgId()));
887  } else {
888  emit selected(0);
889  }
890  } else
891  emit selected(0);
892 }
893 
894 
895 //-----------------------------------------------------------------------------
897 {
898  HeaderItem* hi = 0;
899  if (!isUpdatesEnabled()) return;
900 
901  CREATE_TIMER(msgAdded);
902  START_TIMER(msgAdded);
903 
904  assert( mFolder->getMsgBase( id ) ); // otherwise using count() is wrong
905 
906  /* Create a new SortCacheItem to be used for threading. */
907  SortCacheItem *sci = new SortCacheItem;
908  sci->setId(id);
909  if (isThreaded()) {
910  // make sure the id and subject dicts grow, if necessary
911  if (mSortCacheItems.count() == (uint)mFolder->count()
912  || mSortCacheItems.count() == 0) {
913  kdDebug (5006) << "KMHeaders::msgAdded - Resizing id and subject trees of " << mFolder->label()
914  << ": before=" << mSortCacheItems.count() << " ,after=" << (mFolder->count()*2) << endl;
915  mSortCacheItems.resize(mFolder->count()*2);
916  mSubjectLists.resize(mFolder->count()*2);
917  }
918  TQString msgId = mFolder->getMsgBase(id)->msgIdMD5();
919  if (msgId.isNull())
920  msgId = "";
921  TQString replyToId = mFolder->getMsgBase(id)->replyToIdMD5();
922 
923  SortCacheItem *parent = findParent( sci );
924  if (!parent && mSubjThreading) {
925  parent = findParentBySubject( sci );
926  if (parent && sci->isImperfectlyThreaded()) {
927  // The parent we found could be by subject, in which case it is
928  // possible, that it would be preferrable to thread it below us,
929  // not the other way around. Check that. This is not only
930  // cosmetic, as getting this wrong leads to circular threading.
931  if (msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToIdMD5()
932  || msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToAuxIdMD5())
933  parent = NULL;
934  }
935  }
936 
937  if (parent && mFolder->getMsgBase(parent->id())->isWatched())
938  mFolder->getMsgBase(id)->setStatus( KMMsgStatusWatched );
939  else if (parent && mFolder->getMsgBase(parent->id())->isIgnored())
940  mFolder->getMsgBase(id)->setStatus( KMMsgStatusIgnored );
941  if (parent)
942  hi = new HeaderItem( parent->item(), id );
943  else
944  hi = new HeaderItem( this, id );
945 
946  // o/` ... my buddy and me .. o/`
947  hi->setSortCacheItem(sci);
948  sci->setItem(hi);
949 
950  // Update and resize the id trees.
951  mItems.resize( mFolder->count() );
952  mItems[id] = hi;
953 
954  if ( !msgId.isEmpty() )
955  mSortCacheItems.replace(msgId, sci);
956  /* Add to the list of potential parents for subject threading. But only if
957  * we are top level. */
958  if (mSubjThreading && parent) {
959  TQString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
960  if (subjMD5.isEmpty()) {
961  mFolder->getMsgBase(id)->initStrippedSubjectMD5();
962  subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
963  }
964  if( !subjMD5.isEmpty()) {
965  if ( !mSubjectLists.find(subjMD5) )
966  mSubjectLists.insert(subjMD5, new TQPtrList<SortCacheItem>());
967  // insertion sort by date. See buildThreadTrees for details.
968  int p=0;
969  for (TQPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
970  it.current(); ++it) {
971  KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
972  if ( mb->date() < mFolder->getMsgBase(id)->date())
973  break;
974  p++;
975  }
976  mSubjectLists[subjMD5]->insert( p, sci);
977  sci->setSubjectThreadingList( mSubjectLists[subjMD5] );
978  }
979  }
980  // The message we just added might be a better parent for one of the as of
981  // yet imperfectly threaded messages. Let's find out.
982 
983  /* In case the current item is taken during reparenting, prevent qlistview
984  * from selecting some unrelated item as a result of take() emitting
985  * currentChanged. */
986  disconnect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
987  this, TQT_SLOT(highlightMessage(TQListViewItem*)));
988 
989  if ( !msgId.isEmpty() ) {
990  TQPtrListIterator<HeaderItem> it(mImperfectlyThreadedList);
991  HeaderItem *cur;
992  while ( (cur = it.current()) ) {
993  ++it;
994  int tryMe = cur->msgId();
995  // Check, whether our message is the replyToId or replyToAuxId of
996  // this one. If so, thread it below our message, unless it is already
997  // correctly threaded by replyToId.
998  bool perfectParent = true;
999  KMMsgBase *otherMsg = mFolder->getMsgBase(tryMe);
1000  if ( !otherMsg ) {
1001  kdDebug(5006) << "otherMsg is NULL !!! tryMe: " << tryMe << endl;
1002  continue;
1003  }
1004  TQString otherId = otherMsg->replyToIdMD5();
1005  if (msgId != otherId) {
1006  if (msgId != otherMsg->replyToAuxIdMD5())
1007  continue;
1008  else {
1009  if (!otherId.isEmpty() && mSortCacheItems.find(otherId))
1010  continue;
1011  else
1012  // Thread below us by aux id, but keep on the list of
1013  // imperfectly threaded messages.
1014  perfectParent = false;
1015  }
1016  }
1017  TQListViewItem *newParent = mItems[id];
1018  TQListViewItem *msg = mItems[tryMe];
1019 
1020  if (msg->parent())
1021  msg->parent()->takeItem(msg);
1022  else
1023  takeItem(msg);
1024  newParent->insertItem(msg);
1025  HeaderItem *hi = static_cast<HeaderItem*>( newParent );
1026  hi->sortCacheItem()->addSortedChild( cur->sortCacheItem() );
1027 
1029 
1030  if (perfectParent) {
1031  mImperfectlyThreadedList.removeRef (mItems[tryMe]);
1032  // The item was imperfectly thread before, now it's parent
1033  // is there. Update the .sorted file accordingly.
1034  TQString sortFile = KMAIL_SORT_FILE(mFolder);
1035  FILE *sortStream = fopen(TQFile::encodeName(sortFile), "r+");
1036  if (sortStream) {
1037  mItems[tryMe]->sortCacheItem()->updateSortFile( sortStream, mFolder );
1038  fclose (sortStream);
1039  }
1040  }
1041  }
1042  }
1043  // Add ourselves only now, to avoid circularity above.
1044  if (hi && hi->sortCacheItem()->isImperfectlyThreaded())
1045  mImperfectlyThreadedList.append(hi);
1046  } else {
1047  // non-threaded case
1048  hi = new HeaderItem( this, id );
1049  mItems.resize( mFolder->count() );
1050  mItems[id] = hi;
1051  // o/` ... my buddy and me .. o/`
1052  hi->setSortCacheItem(sci);
1053  sci->setItem(hi);
1054  }
1055  if (mSortInfo.fakeSort) {
1056  TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
1057  KListView::setSorting(mSortCol, !mSortDescending );
1058  mSortInfo.fakeSort = 0;
1059  }
1060  appendItemToSortFile(hi); //inserted into sorted list
1061 
1062  msgHeaderChanged(mFolder,id);
1063 
1064  if ((childCount() == 1) && hi) {
1065  setSelected( hi, true );
1066  setCurrentItem( firstChild() );
1067  setSelectionAnchor( currentItem() );
1068  highlightMessage( currentItem() );
1069  }
1070 
1071  /* restore signal */
1072  connect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
1073  this, TQT_SLOT(highlightMessage(TQListViewItem*)));
1074 
1075  emit msgAddedToListView( hi );
1076  END_TIMER(msgAdded);
1077  SHOW_TIMER(msgAdded);
1078 }
1079 
1080 
1081 //-----------------------------------------------------------------------------
1082 void KMHeaders::msgRemoved(int id, TQString msgId )
1083 {
1084  if (!isUpdatesEnabled()) return;
1085 
1086  if ((id < 0) || (id >= (int)mItems.size()))
1087  return;
1088  /*
1089  * qlistview has its own ideas about what to select as the next
1090  * item once this one is removed. Sine we have already selected
1091  * something in prepare/finalizeMove that's counter productive
1092  */
1093  disconnect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
1094  this, TQT_SLOT(highlightMessage(TQListViewItem*)));
1095 
1096  HeaderItem *removedItem = mItems[id];
1097  if (!removedItem) return;
1098  HeaderItem *curItem = currentHeaderItem();
1099 
1100  for (int i = id; i < (int)mItems.size() - 1; ++i) {
1101  mItems[i] = mItems[i+1];
1102  mItems[i]->setMsgId( i );
1103  mItems[i]->sortCacheItem()->setId( i );
1104  }
1105 
1106  mItems.resize( mItems.size() - 1 );
1107 
1108  if (isThreaded() && mFolder->count()) {
1109  if ( !msgId.isEmpty() && mSortCacheItems[msgId] ) {
1110  if (mSortCacheItems[msgId] == removedItem->sortCacheItem())
1111  mSortCacheItems.remove(msgId);
1112  }
1113  // Remove the message from the list of potential parents for threading by
1114  // subject.
1115  if ( mSubjThreading && removedItem->sortCacheItem()->subjectThreadingList() )
1116  removedItem->sortCacheItem()->subjectThreadingList()->removeRef( removedItem->sortCacheItem() );
1117 
1118  // Reparent children of item.
1119  TQListViewItem *myParent = removedItem;
1120  TQListViewItem *myChild = myParent->firstChild();
1121  TQListViewItem *threadRoot = myParent;
1122  while (threadRoot->parent())
1123  threadRoot = threadRoot->parent();
1124  TQString key = static_cast<HeaderItem*>(threadRoot)->key(mSortCol, !mSortDescending);
1125 
1126  TQPtrList<TQListViewItem> childList;
1127  while (myChild) {
1128  HeaderItem *item = static_cast<HeaderItem*>(myChild);
1129  // Just keep the item at top level, if it will be deleted anyhow
1130  if ( !item->aboutToBeDeleted() ) {
1131  childList.append(myChild);
1132  }
1133  myChild = myChild->nextSibling();
1134  if ( item->aboutToBeDeleted() ) {
1135  myParent->takeItem( item );
1136  insertItem( item );
1137  mRoot->addSortedChild( item->sortCacheItem() );
1138  }
1139  item->setTempKey( key + item->key( mSortCol, !mSortDescending ));
1140  if (mSortInfo.fakeSort) {
1141  TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
1142  KListView::setSorting(mSortCol, !mSortDescending );
1143  mSortInfo.fakeSort = 0;
1144  }
1145  }
1146 
1147  for (TQPtrListIterator<TQListViewItem> it(childList); it.current() ; ++it ) {
1148  TQListViewItem *lvi = *it;
1149  HeaderItem *item = static_cast<HeaderItem*>(lvi);
1150  SortCacheItem *sci = item->sortCacheItem();
1151  SortCacheItem *parent = findParent( sci );
1152  if ( !parent && mSubjThreading )
1153  parent = findParentBySubject( sci );
1154 
1155  Q_ASSERT( !parent || parent->item() != removedItem );
1156  myParent->takeItem(lvi);
1157  if ( parent && parent->item() != item && parent->item() != removedItem ) {
1158  parent->item()->insertItem(lvi);
1159  parent->addSortedChild( sci );
1160  } else {
1161  insertItem(lvi);
1162  mRoot->addSortedChild( sci );
1163  }
1164 
1165  if ((!parent || sci->isImperfectlyThreaded())
1166  && !mImperfectlyThreadedList.containsRef(item))
1167  mImperfectlyThreadedList.append(item);
1168 
1169  if (parent && !sci->isImperfectlyThreaded()
1170  && mImperfectlyThreadedList.containsRef(item))
1171  mImperfectlyThreadedList.removeRef(item);
1172  }
1173  }
1174  // Make sure our data structures are cleared.
1175  if (!mFolder->count())
1176  folderCleared();
1177 
1178  mImperfectlyThreadedList.removeRef( removedItem );
1179 #ifdef DEBUG
1180  // This should never happen, in this case the folders are inconsistent.
1181  while ( mImperfectlyThreadedList.findRef( removedItem ) != -1 ) {
1182  mImperfectlyThreadedList.remove();
1183  kdDebug(5006) << "Remove doubled item from mImperfectlyThreadedList: " << removedItem << endl;
1184  }
1185 #endif
1186  delete removedItem;
1187  // we might have rethreaded it, in which case its current state will be lost
1188  if ( curItem ) {
1189  if ( curItem != removedItem ) {
1190  setCurrentItem( curItem );
1191  setSelectionAnchor( currentItem() );
1192  } else {
1193  // We've removed the current item, which means it was removed from
1194  // something other than a user move or copy, which would have selected
1195  // the next logical mail. This can happen when the mail is deleted by
1196  // a filter, or some other behind the scenes action. Select something
1197  // sensible, then, and make sure the reader window is cleared.
1198  emit maybeDeleting();
1199  int contentX, contentY;
1200  HeaderItem *nextItem = prepareMove( &contentX, &contentY );
1201  finalizeMove( nextItem, contentX, contentY );
1202  }
1203  }
1204  /* restore signal */
1205  connect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
1206  this, TQT_SLOT(highlightMessage(TQListViewItem*)));
1207 }
1208 
1209 
1210 //-----------------------------------------------------------------------------
1212 {
1213  if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return;
1214  HeaderItem *item = mItems[msgId];
1215  if (item) {
1216  item->irefresh();
1217  item->repaint();
1218  }
1219 }
1220 
1221 
1222 //-----------------------------------------------------------------------------
1223 void KMHeaders::setMsgStatus (KMMsgStatus status, bool toggle)
1224 {
1225  // kdDebug() << k_funcinfo << endl;
1226  SerNumList serNums = selectedVisibleSernums();
1227  if (serNums.empty())
1228  return;
1229 
1230  KMCommand *command = new KMSeStatusCommand( status, serNums, toggle );
1231  command->start();
1232 }
1233 
1234 
1235 TQPtrList<TQListViewItem> KMHeaders::currentThread() const
1236 {
1237  if (!mFolder) return TQPtrList<TQListViewItem>();
1238 
1239  // starting with the current item...
1240  TQListViewItem *curItem = currentItem();
1241  if (!curItem) return TQPtrList<TQListViewItem>();
1242 
1243  // ...find the top-level item:
1244  TQListViewItem *topOfThread = curItem;
1245  while ( topOfThread->parent() )
1246  topOfThread = topOfThread->parent();
1247 
1248  // collect the items in this thread:
1249  TQPtrList<TQListViewItem> list;
1250  TQListViewItem *topOfNextThread = topOfThread->nextSibling();
1251  for ( TQListViewItemIterator it( topOfThread ) ;
1252  it.current() && it.current() != topOfNextThread ; ++it )
1253  list.append( it.current() );
1254  return list;
1255 }
1256 
1257 void KMHeaders::setThreadStatus(KMMsgStatus status, bool toggle)
1258 {
1259  TQPtrList<TQListViewItem> curThread;
1260 
1261  if (mFolder) {
1262  TQPtrList<TQListViewItem> topOfThreads;
1263 
1264  // for each selected item...
1265  for (TQListViewItem *item = firstChild(); item; item = item->itemBelow())
1266  if (item->isSelected() ) {
1267  // ...find the top-level item:
1268  TQListViewItem *top = item;
1269  while ( top->parent() )
1270  top = top->parent();
1271  if (!topOfThreads.contains(top)) {
1272  topOfThreads.append(top);
1273  }
1274  }
1275 
1276  // for each thread found...
1277  for ( TQPtrListIterator<TQListViewItem> it( topOfThreads ) ;
1278  it.current() ; ++ it ) {
1279  TQListViewItem *top = *it;
1280 
1281  // collect the items in this thread:
1282  TQListViewItem *topOfNextThread = top->nextSibling();
1283  for ( TQListViewItemIterator it( top ) ;
1284  it.current() && it.current() != topOfNextThread ; ++it )
1285  curThread.append( it.current() );
1286  }
1287  }
1288 
1289  TQPtrListIterator<TQListViewItem> it( curThread );
1290  SerNumList serNums;
1291 
1292  for ( it.toFirst() ; it.current() ; ++it ) {
1293  int id = static_cast<HeaderItem*>(*it)->msgId();
1294  KMMsgBase *msgBase = mFolder->getMsgBase( id );
1295  serNums.append( msgBase->getMsgSerNum() );
1296  }
1297 
1298  if (serNums.empty())
1299  return;
1300 
1301  KMCommand *command = new KMSeStatusCommand( status, serNums, toggle );
1302  command->start();
1303 }
1304 
1305 //-----------------------------------------------------------------------------
1307 {
1308  if ( !msg ) return 2; // messageRetrieve(0) is always possible
1309  msg->setTransferInProgress(false);
1310  int filterResult = kmkernel->filterMgr()->process(msg,KMFilterMgr::Explicit);
1311  if (filterResult == 2) {
1312  // something went horribly wrong (out of space?)
1313  kmkernel->emergencyExit( i18n("Unable to process messages: " ) + TQString::fromLocal8Bit(strerror(errno)));
1314  return 2;
1315  }
1316  if (msg->parent()) { // unGet this msg
1317  int idx = -1;
1318  KMFolder * p = 0;
1319  KMMsgDict::instance()->getLocation( msg, &p, &idx );
1320  assert( p == msg->parent() ); assert( idx >= 0 );
1321  p->unGetMsg( idx );
1322  }
1323 
1324  return filterResult;
1325 }
1326 
1327 
1329 {
1330  if ( !isThreaded() ) return;
1331  // find top-level parent of currentItem().
1332  TQListViewItem *item = currentItem();
1333  if ( !item ) return;
1334  clearSelection();
1335  item->setSelected( true );
1336  while ( item->parent() )
1337  item = item->parent();
1338  HeaderItem * hdrItem = static_cast<HeaderItem*>(item);
1339  hdrItem->setOpenRecursive( expand );
1340  if ( !expand ) // collapse can hide the current item:
1341  setCurrentMsg( hdrItem->msgId() );
1342  ensureItemVisible( currentItem() );
1343 }
1344 
1346 {
1347  if ( !isThreaded() ) return;
1348 
1349  TQListViewItem * item = currentItem();
1350  if( item ) {
1351  clearSelection();
1352  item->setSelected( true );
1353  }
1354 
1355  for ( TQListViewItem *item = firstChild() ;
1356  item ; item = item->nextSibling() )
1357  static_cast<HeaderItem*>(item)->setOpenRecursive( expand );
1358  if ( !expand ) { // collapse can hide the current item:
1359  TQListViewItem * item = currentItem();
1360  if( item ) {
1361  while ( item->parent() )
1362  item = item->parent();
1363  setCurrentMsg( static_cast<HeaderItem*>(item)->msgId() );
1364  }
1365  }
1366  ensureItemVisible( currentItem() );
1367 }
1368 
1369 //-----------------------------------------------------------------------------
1371 {
1372  // set the width of the frame to a reasonable value for the current GUI style
1373  int frameWidth;
1374  if( style().isA("KeramikStyle") )
1375  frameWidth = style().pixelMetric( TQStyle::PM_DefaultFrameWidth ) - 1;
1376  else
1377  frameWidth = style().pixelMetric( TQStyle::PM_DefaultFrameWidth );
1378  if ( frameWidth < 0 )
1379  frameWidth = 0;
1380  if ( frameWidth != lineWidth() )
1381  setLineWidth( frameWidth );
1382 }
1383 
1384 //-----------------------------------------------------------------------------
1385 void KMHeaders::styleChange( TQStyle& oldStyle )
1386 {
1388  KListView::styleChange( oldStyle );
1389 }
1390 
1391 //-----------------------------------------------------------------------------
1393 {
1394  if ( !mFolder ) return;
1395  TQString str;
1396  const int unread = mFolder->countUnread();
1397  if ( static_cast<KMFolder*>(mFolder) == kmkernel->outboxFolder() )
1398  str = unread ? i18n( "1 unsent", "%n unsent", unread ) : i18n( "0 unsent" );
1399  else
1400  str = unread ? i18n( "1 unread", "%n unread", unread ) : i18n( "0 unread" );
1401  const int count = mFolder->count();
1402  str = count ? i18n( "1 message, %1.", "%n messages, %1.", count ).arg( str )
1403  : i18n( "0 messages" ); // no need for "0 unread" to be added here
1404  if ( mFolder->isReadOnly() )
1405  str = i18n("%1 = n messages, m unread.", "%1 Folder is read-only.").arg( str );
1406  BroadcastStatus::instance()->setStatusMsg(str);
1407 }
1408 
1409 //-----------------------------------------------------------------------------
1410 void KMHeaders::applyFiltersOnMsg()
1411 {
1412  if (ActionScheduler::isEnabled() ||
1413  kmkernel->filterMgr()->atLeastOneOnlineImapFolderTarget()) {
1414  // uses action scheduler
1415  KMFilterMgr::FilterSet set = KMFilterMgr::Explicit;
1416  TQValueList<KMFilter*> filters = kmkernel->filterMgr()->filters();
1417  ActionScheduler *scheduler = new ActionScheduler( set, filters, this );
1418  scheduler->setAutoDestruct( true );
1419 
1420  int contentX, contentY;
1421  HeaderItem *nextItem = prepareMove( &contentX, &contentY );
1422  TQPtrList<KMMsgBase> msgList = *selectedMsgs(true);
1423  finalizeMove( nextItem, contentX, contentY );
1424 
1425  for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
1426  scheduler->execFilters( msg );
1427  } else {
1428  int contentX, contentY;
1429  HeaderItem *nextItem = prepareMove( &contentX, &contentY );
1430 
1431  //prevent issues with stale message pointers by using serial numbers instead
1432  TQValueList<unsigned long> serNums = KMMsgDict::serNumList( *selectedMsgs() );
1433  if ( serNums.isEmpty() )
1434  return;
1435 
1436  finalizeMove( nextItem, contentX, contentY );
1437  CREATE_TIMER(filter);
1438  START_TIMER(filter);
1439 
1440  KCursorSaver busy( KBusyPtr::busy() );
1441  int msgCount = 0;
1442  int msgCountToFilter = serNums.count();
1443  ProgressItem* progressItem =
1444  ProgressManager::createProgressItem( "filter"+ProgressManager::getUniqueID(),
1445  i18n( "Filtering messages" ) );
1446  progressItem->setTotalItems( msgCountToFilter );
1447 
1448  for ( TQValueList<unsigned long>::ConstIterator it = serNums.constBegin();
1449  it != serNums.constEnd(); ++it ) {
1450  msgCount++;
1451  if ( msgCountToFilter - msgCount < 10 || !( msgCount % 20 ) || msgCount <= 10 ) {
1452  progressItem->updateProgress();
1453  TQString statusMsg = i18n("Filtering message %1 of %2");
1454  statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter );
1455  KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg );
1456  KApplication::kApplication()->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput, 50 );
1457  }
1458 
1459  KMFolder *folder = 0;
1460  int idx;
1461  KMMsgDict::instance()->getLocation( *it, &folder, &idx );
1462  KMMessage *msg = 0;
1463  if (folder)
1464  msg = folder->getMsg(idx);
1465  if (msg) {
1466  if (msg->transferInProgress())
1467  continue;
1468  msg->setTransferInProgress(true);
1469  if (!msg->isComplete()) {
1470  FolderJob *job = mFolder->createJob(msg);
1471  connect(job, TQT_SIGNAL(messageRetrieved(KMMessage*)),
1472  this, TQT_SLOT(slotFilterMsg(KMMessage*)));
1473  job->start();
1474  } else {
1475  if (slotFilterMsg(msg) == 2)
1476  break;
1477  }
1478  } else {
1479  kdDebug (5006) << "####### KMHeaders::applyFiltersOnMsg -"
1480  " A message went missing during filtering " << endl;
1481  }
1482  progressItem->incCompletedItems();
1483  }
1484  progressItem->setComplete();
1485  progressItem = 0;
1486  END_TIMER(filter);
1487  SHOW_TIMER(filter);
1488  }
1489 }
1490 
1491 
1492 //-----------------------------------------------------------------------------
1493 void KMHeaders::setMsgRead (int msgId)
1494 {
1495  KMMsgBase *msgBase = mFolder->getMsgBase( msgId );
1496  if (!msgBase)
1497  return;
1498 
1499  SerNumList serNums;
1500  if (msgBase->isNew() || msgBase->isUnread()) {
1501  serNums.append( msgBase->getMsgSerNum() );
1502  }
1503 
1504  KMCommand *command = new KMSeStatusCommand( KMMsgStatusRead, serNums );
1505  command->start();
1506 }
1507 
1508 
1509 //-----------------------------------------------------------------------------
1510 void KMHeaders::deleteMsg ()
1511 {
1512  //make sure we have an associated folder (root of folder tree does not).
1513  if (!mFolder)
1514  return;
1515 
1516  int contentX, contentY;
1517  HeaderItem *nextItem = prepareMove( &contentX, &contentY );
1518  KMMessageList msgList = *selectedMsgs(true);
1519  finalizeMove( nextItem, contentX, contentY );
1520 
1521  KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList );
1522  connect( command, TQT_SIGNAL( completed( KMCommand * ) ),
1523  this, TQT_SLOT( slotMoveCompleted( KMCommand * ) ) );
1524  command->start();
1525 
1526  BroadcastStatus::instance()->setStatusMsg("");
1527  // triggerUpdate();
1528 }
1529 
1530 
1531 //-----------------------------------------------------------------------------
1533 {
1534  if (mMenuToFolder[menuId])
1535  moveMsgToFolder( mMenuToFolder[menuId] );
1536 }
1537 
1538 //-----------------------------------------------------------------------------
1539 HeaderItem* KMHeaders::prepareMove( int *contentX, int *contentY )
1540 {
1541  HeaderItem *ret = 0;
1542  emit maybeDeleting();
1543 
1544  disconnect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
1545  this, TQT_SLOT(highlightMessage(TQListViewItem*)));
1546 
1547  TQListViewItem *curItem;
1548  HeaderItem *item;
1549  curItem = currentItem();
1550  while (curItem && curItem->isSelected() && curItem->itemBelow())
1551  curItem = curItem->itemBelow();
1552  while (curItem && curItem->isSelected() && curItem->itemAbove())
1553  curItem = curItem->itemAbove();
1554  item = static_cast<HeaderItem*>(curItem);
1555 
1556  *contentX = contentsX();
1557  *contentY = contentsY();
1558 
1559  if (item && !item->isSelected())
1560  ret = item;
1561 
1562  return ret;
1563 }
1564 
1565 //-----------------------------------------------------------------------------
1566 void KMHeaders::finalizeMove( HeaderItem *item, int contentX, int contentY )
1567 {
1568  emit selected( 0 );
1569  clearSelection();
1570 
1571  if ( item ) {
1572  setCurrentItem( item );
1573  setSelected( item, true );
1574  setSelectionAnchor( currentItem() );
1575  mPrevCurrent = 0;
1576  highlightMessage( item, false);
1577  }
1578 
1579  setContentsPos( contentX, contentY );
1581  connect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
1582  this, TQT_SLOT(highlightMessage(TQListViewItem*)));
1583 }
1584 
1585 
1586 //-----------------------------------------------------------------------------
1587 void KMHeaders::moveMsgToFolder ( KMFolder* destFolder, bool askForConfirmation )
1588 {
1589  if ( destFolder == mFolder ) return; // Catch the noop case
1590  if ( mFolder->isReadOnly() ) return;
1591 
1592  KMMessageList msgList = *selectedMsgs();
1593  if ( msgList.isEmpty() ) return;
1594  if ( !destFolder && askForConfirmation && // messages shall be deleted
1595  KMessageBox::warningContinueCancel(this,
1596  i18n("<qt>Do you really want to delete the selected message?<br>"
1597  "Once deleted, it cannot be restored.</qt>",
1598  "<qt>Do you really want to delete the %n selected messages?<br>"
1599  "Once deleted, they cannot be restored.</qt>", msgList.count() ),
1600  msgList.count()>1 ? i18n("Delete Messages") : i18n("Delete Message"), KStdGuiItem::del(),
1601  "NoConfirmDelete") == KMessageBox::Cancel )
1602  return; // user canceled the action
1603 
1604  // remember the message to select afterwards
1605  int contentX, contentY;
1606  HeaderItem *nextItem = prepareMove( &contentX, &contentY );
1607  msgList = *selectedMsgs(true);
1608  finalizeMove( nextItem, contentX, contentY );
1609 
1610  KMCommand *command = new KMMoveCommand( destFolder, msgList );
1611  connect( command, TQT_SIGNAL( completed( KMCommand * ) ),
1612  this, TQT_SLOT( slotMoveCompleted( KMCommand * ) ) );
1613  command->start();
1614 }
1615 
1616 void KMHeaders::slotMoveCompleted( KMCommand *command )
1617 {
1618  kdDebug(5006) << k_funcinfo << command->result() << endl;
1619  bool deleted = static_cast<KMMoveCommand *>( command )->destFolder() == 0;
1620  if ( command->result() == KMCommand::OK ) {
1621  // make sure the current item is shown
1623  BroadcastStatus::instance()->setStatusMsg(
1624  deleted ? i18n("Messages deleted successfully.") : i18n("Messages moved successfully") );
1625  } else {
1626  /* The move failed or the user canceled it; reset the state of all
1627  * messages involved and repaint.
1628  *
1629  * Note: This potentially resets too many items if there is more than one
1630  * move going on. Oh well, I suppose no animals will be harmed.
1631  * */
1632  for (TQListViewItemIterator it(this); it.current(); it++) {
1633  HeaderItem *item = static_cast<HeaderItem*>(it.current());
1634  if ( item->aboutToBeDeleted() ) {
1635  item->setAboutToBeDeleted ( false );
1636  item->setSelectable ( true );
1637  KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
1638  if ( msgBase->isMessage() ) {
1639  KMMessage *msg = static_cast<KMMessage *>(msgBase);
1640  if ( msg ) msg->setTransferInProgress( false, true );
1641  }
1642  }
1643  }
1644  triggerUpdate();
1645  if ( command->result() == KMCommand::Failed )
1646  BroadcastStatus::instance()->setStatusMsg(
1647  deleted ? i18n("Deleting messages failed.") : i18n("Moving messages failed.") );
1648  else
1649  BroadcastStatus::instance()->setStatusMsg(
1650  deleted ? i18n("Deleting messages canceled.") : i18n("Moving messages canceled.") );
1651  }
1652  mOwner->updateMessageActions();
1653 }
1654 
1655 bool KMHeaders::canUndo() const
1656 {
1657  return ( kmkernel->undoStack()->size() > 0 );
1658 }
1659 
1660 //-----------------------------------------------------------------------------
1661 void KMHeaders::undo()
1662 {
1663  kmkernel->undoStack()->undo();
1664 }
1665 
1666 //-----------------------------------------------------------------------------
1668 {
1669  if (mMenuToFolder[menuId])
1670  copyMsgToFolder( mMenuToFolder[menuId] );
1671 }
1672 
1673 
1674 //-----------------------------------------------------------------------------
1676 {
1677  if ( !destFolder )
1678  return;
1679 
1680  KMCommand * command = 0;
1681  if (aMsg)
1682  command = new KMCopyCommand( destFolder, aMsg );
1683  else {
1684  KMMessageList msgList = *selectedMsgs();
1685  command = new KMCopyCommand( destFolder, msgList );
1686  }
1687 
1688  command->start();
1689 }
1690 
1691 
1692 //-----------------------------------------------------------------------------
1694 {
1695  if (!mFolder) return;
1696  if (cur >= mFolder->count()) cur = mFolder->count() - 1;
1697  if ((cur >= 0) && (cur < (int)mItems.size())) {
1698  clearSelection();
1699  setCurrentItem( mItems[cur] );
1700  setSelected( mItems[cur], true );
1701  setSelectionAnchor( currentItem() );
1702  }
1705 }
1706 
1707 //-----------------------------------------------------------------------------
1708 void KMHeaders::setSelected( TQListViewItem *item, bool selected )
1709 {
1710  if ( !item )
1711  return;
1712 
1713  if ( item->isVisible() )
1714  KListView::setSelected( item, selected );
1715 
1716  // If the item is the parent of a closed thread recursively select
1717  // children .
1718  if ( isThreaded() && !item->isOpen() && item->firstChild() ) {
1719  TQListViewItem *nextRoot = item->itemBelow();
1720  TQListViewItemIterator it( item->firstChild() );
1721  for( ; (*it) != nextRoot; ++it ) {
1722  if ( (*it)->isVisible() )
1723  (*it)->setSelected( selected );
1724  }
1725  }
1726 }
1727 
1728 void KMHeaders::setSelectedByIndex( TQValueList<int> items, bool selected )
1729 {
1730  for ( TQValueList<int>::Iterator it = items.begin(); it != items.end(); ++it )
1731  {
1732  if ( ((*it) >= 0) && ((*it) < (int)mItems.size()) )
1733  {
1734  setSelected( mItems[(*it)], selected );
1735  }
1736  }
1737 }
1738 
1740 {
1741  // fugly, but I see no way around it
1742  for (TQListViewItemIterator it(this); it.current(); it++) {
1743  HeaderItem *item = static_cast<HeaderItem*>(it.current());
1744  if ( item->aboutToBeDeleted() ) {
1745  KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
1746  if ( serNum == msgBase->getMsgSerNum() ) {
1747  item->setAboutToBeDeleted ( false );
1748  item->setSelectable ( true );
1749  }
1750  }
1751  }
1752  triggerUpdate();
1753 }
1754 
1755 //-----------------------------------------------------------------------------
1756 KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted)
1757 {
1758  mSelMsgBaseList.clear();
1759  for (TQListViewItemIterator it(this); it.current(); it++) {
1760  if ( it.current()->isSelected() && it.current()->isVisible() ) {
1761  HeaderItem *item = static_cast<HeaderItem*>(it.current());
1762  if ( !item->aboutToBeDeleted() ) { // we are already working on this one
1763  if (toBeDeleted) {
1764  // make sure the item is not uselessly rethreaded and not selectable
1765  item->setAboutToBeDeleted ( true );
1766  item->setSelectable ( false );
1767  }
1768  KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
1769  mSelMsgBaseList.append(msgBase);
1770  }
1771  }
1772  }
1773  return &mSelMsgBaseList;
1774 }
1775 
1776 //-----------------------------------------------------------------------------
1777 TQValueList<int> KMHeaders::selectedItems()
1778 {
1779  TQValueList<int> items;
1780  for ( TQListViewItemIterator it(this); it.current(); it++ )
1781  {
1782  if ( it.current()->isSelected() && it.current()->isVisible() )
1783  {
1784  HeaderItem* item = static_cast<HeaderItem*>( it.current() );
1785  items.append( item->msgId() );
1786  }
1787  }
1788  return items;
1789 }
1790 
1791 //-----------------------------------------------------------------------------
1793 {
1794  int selectedMsg = -1;
1795  TQListViewItem *item;
1796  for (item = firstChild(); item; item = item->itemBelow())
1797  if (item->isSelected()) {
1798  selectedMsg = (static_cast<HeaderItem*>(item))->msgId();
1799  break;
1800  }
1801  return selectedMsg;
1802 }
1803 
1804 //-----------------------------------------------------------------------------
1806 {
1807  TQListViewItem *lvi = currentItem();
1808  if (lvi && lvi->itemBelow()) {
1809  clearSelection();
1810  setSelected( lvi, false );
1812  setSelectionAnchor( currentItem() );
1813  ensureCurrentItemVisible();
1814  }
1815 }
1816 
1818 {
1819  KMMessage *cm = currentMsg();
1820  if ( cm && cm->isBeingParsed() )
1821  return;
1822  TQListViewItem *lvi = currentItem();
1823  if( lvi ) {
1824  TQListViewItem *below = lvi->itemBelow();
1825  TQListViewItem *temp = lvi;
1826  if (lvi && below ) {
1827  while (temp) {
1828  temp->firstChild();
1829  temp = temp->parent();
1830  }
1831  lvi->repaint();
1832  /* test to see if we need to unselect messages on back track */
1833  (below->isSelected() ? setSelected(lvi, false) : setSelected(below, true));
1834  setCurrentItem(below);
1837  }
1838  }
1839 }
1840 
1841 //-----------------------------------------------------------------------------
1843 {
1844  TQListViewItem *lvi = currentItem();
1845  if (lvi && lvi->itemAbove()) {
1846  clearSelection();
1847  setSelected( lvi, false );
1849  setSelectionAnchor( currentItem() );
1850  ensureCurrentItemVisible();
1851  }
1852 }
1853 
1855 {
1856  KMMessage *cm = currentMsg();
1857  if ( cm && cm->isBeingParsed() )
1858  return;
1859  TQListViewItem *lvi = currentItem();
1860  if( lvi ) {
1861  TQListViewItem *above = lvi->itemAbove();
1862  TQListViewItem *temp = lvi;
1863 
1864  if (lvi && above) {
1865  while (temp) {
1866  temp->firstChild();
1867  temp = temp->parent();
1868  }
1869  lvi->repaint();
1870  /* test to see if we need to unselect messages on back track */
1871  (above->isSelected() ? setSelected(lvi, false) : setSelected(above, true));
1872  setCurrentItem(above);
1875  }
1876  }
1877 }
1878 
1879 
1881 {
1882  KMMessage *cm = currentMsg();
1883  if ( cm && cm->isBeingParsed() )
1884  return;
1885  TQListViewItem *lvi = currentItem();
1886  if ( lvi && lvi->itemBelow() ) {
1887 
1888  disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
1889  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
1890  setCurrentItem( lvi->itemBelow() );
1891  ensureCurrentItemVisible();
1892  setFocus();
1893  connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
1894  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
1895  }
1896 }
1897 
1899 {
1900  KMMessage *cm = currentMsg();
1901  if ( cm && cm->isBeingParsed() )
1902  return;
1903  TQListViewItem *lvi = currentItem();
1904  if ( lvi && lvi->itemAbove() ) {
1905  disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
1906  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
1907  setCurrentItem( lvi->itemAbove() );
1908  ensureCurrentItemVisible();
1909  setFocus();
1910  connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
1911  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
1912  }
1913 }
1914 
1916 {
1918  highlightMessage( currentItem() );
1919 }
1920 
1921 //-----------------------------------------------------------------------------
1923  bool & foundUnreadMessage,
1924  bool onlyNew,
1925  bool aDirNext )
1926 {
1927  KMMsgBase* msgBase = 0;
1928  HeaderItem *lastUnread = 0;
1929  /* itemAbove() is _slow_ */
1930  if (aDirNext)
1931  {
1932  while (item) {
1933  msgBase = mFolder->getMsgBase(item->msgId());
1934  if (!msgBase) continue;
1935  if (msgBase->isUnread() || msgBase->isNew())
1936  foundUnreadMessage = true;
1937 
1938  if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())) break;
1939  if (onlyNew && msgBase->isNew()) break;
1940  item = static_cast<HeaderItem*>(item->itemBelow());
1941  }
1942  } else {
1943  HeaderItem *newItem = static_cast<HeaderItem*>(firstChild());
1944  while (newItem)
1945  {
1946  msgBase = mFolder->getMsgBase(newItem->msgId());
1947  if (!msgBase) continue;
1948  if (msgBase->isUnread() || msgBase->isNew())
1949  foundUnreadMessage = true;
1950  if ( ( !onlyNew && (msgBase->isUnread() || msgBase->isNew()) )
1951  || ( onlyNew && msgBase->isNew() ) )
1952  lastUnread = newItem;
1953  if (newItem == item) break;
1954  newItem = static_cast<HeaderItem*>(newItem->itemBelow());
1955  }
1956  item = lastUnread;
1957  }
1958 }
1959 
1960 //-----------------------------------------------------------------------------
1961 int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent)
1962 {
1963  HeaderItem *item, *pitem;
1964  bool foundUnreadMessage = false;
1965 
1966  if (!mFolder) return -1;
1967  if (mFolder->count() <= 0) return -1;
1968 
1969  if ((aStartAt >= 0) && (aStartAt < (int)mItems.size()))
1970  item = mItems[aStartAt];
1971  else {
1972  item = currentHeaderItem();
1973  if (!item) {
1974  if (aDirNext)
1975  item = static_cast<HeaderItem*>(firstChild());
1976  else
1977  item = static_cast<HeaderItem*>(lastChild());
1978  }
1979  if (!item)
1980  return -1;
1981 
1982  if ( !acceptCurrent ) {
1983  if (aDirNext) {
1984  item = static_cast<HeaderItem*>(item->itemBelow());
1985  }
1986  else {
1987  item = static_cast<HeaderItem*>(item->itemAbove());
1988  }
1989  }
1990  }
1991 
1992  pitem = item;
1993 
1994  findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
1995 
1996  // We have found an unread item, but it is not necessary the
1997  // first unread item.
1998  //
1999  // Find the ancestor of the unread item closest to the
2000  // root and recursively sort all of that ancestors children.
2001  if (item) {
2002  TQListViewItem *next = item;
2003  while (next->parent())
2004  next = next->parent();
2005  next = static_cast<HeaderItem*>(next)->firstChildNonConst();
2006  while (next && (next != item))
2007  if (static_cast<HeaderItem*>(next)->firstChildNonConst())
2008  next = next->firstChild();
2009  else if (next->nextSibling())
2010  next = next->nextSibling();
2011  else {
2012  while (next && (next != item)) {
2013  next = next->parent();
2014  if (next == item)
2015  break;
2016  if (next && next->nextSibling()) {
2017  next = next->nextSibling();
2018  break;
2019  }
2020  }
2021  }
2022  }
2023 
2024  item = pitem;
2025 
2026  findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
2027  if (item)
2028  return item->msgId();
2029 
2030 
2031  // A kludge to try to keep the number of unread messages in sync
2032  int unread = mFolder->countUnread();
2033  if (((unread == 0) && foundUnreadMessage) ||
2034  ((unread > 0) && !foundUnreadMessage)) {
2035  mFolder->correctUnreadMsgsCount();
2036  }
2037  return -1;
2038 }
2039 
2040 //-----------------------------------------------------------------------------
2041 bool KMHeaders::nextUnreadMessage(bool acceptCurrent)
2042 {
2043  if ( !mFolder || !mFolder->countUnread() ) return false;
2044  int i = findUnread(true, -1, false, acceptCurrent);
2045  if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
2046  GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
2047  {
2048  HeaderItem * first = static_cast<HeaderItem*>(firstChild());
2049  if ( first )
2050  i = findUnread(true, first->msgId(), false, acceptCurrent); // from top
2051  }
2052  if ( i < 0 )
2053  return false;
2054  setCurrentMsg(i);
2055  ensureCurrentItemVisible();
2056  return true;
2057 }
2058 
2059 void KMHeaders::ensureCurrentItemVisible()
2060 {
2061  int i = currentItemIndex();
2062  if ((i >= 0) && (i < (int)mItems.size()))
2063  center( contentsX(), itemPos(mItems[i]), 0, 9.0 );
2064 }
2065 
2066 //-----------------------------------------------------------------------------
2068 {
2069  if ( !mFolder || !mFolder->countUnread() ) return false;
2070  int i = findUnread(false);
2071  if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
2072  GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
2073  {
2074  HeaderItem * last = static_cast<HeaderItem*>(lastItem());
2075  if ( last )
2076  i = findUnread(false, last->msgId() ); // from bottom
2077  }
2078  if ( i < 0 )
2079  return false;
2080  setCurrentMsg(i);
2081  ensureCurrentItemVisible();
2082  return true;
2083 }
2084 
2085 
2086 //-----------------------------------------------------------------------------
2088 {
2089  // This causes Kolab issue 1569 (encrypted mails sometimes not dragable)
2090  // This was introduced in r73594 to fix interference between dnd and
2091  // pinentry, which is no longer reproducable now. However, since the
2092  // original problem was probably a race and might reappear, let's keep
2093  // this workaround in for now and just disable it.
2094 // mMousePressed = false;
2095 }
2096 
2097 
2098 //-----------------------------------------------------------------------------
2100 {
2101  if (currentItem())
2102  ensureItemVisible( currentItem() );
2103 }
2104 
2105 //-----------------------------------------------------------------------------
2106 void KMHeaders::highlightMessage(TQListViewItem* lvi, bool markitread)
2107 {
2108  // shouldnt happen but will crash if it does
2109  if (lvi && !lvi->isSelectable()) return;
2110 
2111  HeaderItem *item = static_cast<HeaderItem*>(lvi);
2112  if (lvi != mPrevCurrent) {
2113  if (mPrevCurrent && mFolder)
2114  {
2115  KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId());
2116  if (prevMsg && mReaderWindowActive)
2117  {
2118  mFolder->ignoreJobsForMessage(prevMsg);
2119  if (!prevMsg->transferInProgress())
2120  mFolder->unGetMsg(mPrevCurrent->msgId());
2121  }
2122  }
2123  mPrevCurrent = item;
2124  }
2125 
2126  if (!item) {
2127  emit selected( 0 ); return;
2128  }
2129 
2130  int idx = item->msgId();
2131  KMMessage *msg = mFolder->getMsg(idx);
2132  if (mReaderWindowActive && !msg) {
2133  emit selected( 0 );
2134  mPrevCurrent = 0;
2135  return;
2136  }
2137 
2138  BroadcastStatus::instance()->setStatusMsg("");
2139  if (markitread && idx >= 0) setMsgRead(idx);
2140  mItems[idx]->irefresh();
2141  mItems[idx]->repaint();
2142  emit selected( msg );
2144 }
2145 
2146 void KMHeaders::highlightCurrentThread()
2147 {
2148  TQPtrList<TQListViewItem> curThread = currentThread();
2149  TQPtrListIterator<TQListViewItem> it( curThread );
2150 
2151  for ( it.toFirst() ; it.current() ; ++it ) {
2152  TQListViewItem *lvi = *it;
2153  lvi->setSelected( true );
2154  lvi->repaint();
2155  }
2156 }
2157 
2159 {
2160  mDate.reset();
2161  // only reset exactly during minute switch
2162  TQTimer::singleShot( ( 60-TQTime::currentTime().second() ) * 1000,
2163  this, TQT_SLOT( resetCurrentTime() ) );
2164 }
2165 
2166 //-----------------------------------------------------------------------------
2167 void KMHeaders::selectMessage(TQListViewItem* lvi)
2168 {
2169  HeaderItem *item = static_cast<HeaderItem*>(lvi);
2170  if (!item)
2171  return;
2172 
2173  int idx = item->msgId();
2174  KMMessage *msg = mFolder->getMsg(idx);
2175  if (msg && !msg->transferInProgress())
2176  {
2177  emit activated(mFolder->getMsg(idx));
2178  }
2179 
2180 // if (kmkernel->folderIsDraftOrOutbox(mFolder))
2181 // setOpen(lvi, !lvi->isOpen());
2182 }
2183 
2184 
2185 //-----------------------------------------------------------------------------
2186 void KMHeaders::updateMessageList( bool set_selection, bool forceJumpToUnread )
2187 {
2188  mPrevCurrent = 0;
2189  noRepaint = true;
2190  clear();
2191  mItems.resize(0); // will contain deleted pointers
2192  noRepaint = false;
2193  KListView::setSorting( mSortCol, !mSortDescending );
2194  if (!mFolder) {
2195  repaint();
2196  return;
2197  }
2198  readSortOrder( set_selection, forceJumpToUnread );
2199  emit messageListUpdated();
2200 }
2201 
2202 
2203 //-----------------------------------------------------------------------------
2204 // KMail Header list selection/navigation description
2205 //
2206 // If the selection state changes the reader window is updated to show the
2207 // current item.
2208 //
2209 // (The selection state of a message or messages can be changed by pressing
2210 // space, or normal/shift/cntrl clicking).
2211 //
2212 // The following keyboard events are supported when the messages headers list
2213 // has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End,
2214 // Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do
2215 // not change the selection state.
2216 //
2217 // Exception: When shift selecting either with mouse or key press the reader
2218 // window is updated regardless of whether of not the selection has changed.
2219 void KMHeaders::keyPressEvent( TQKeyEvent * e )
2220 {
2221  bool cntrl = (e->state() & ControlButton );
2222  bool shft = (e->state() & ShiftButton );
2223  TQListViewItem *cur = currentItem();
2224 
2225  if (!e || !firstChild())
2226  return;
2227 
2228  // If no current item, make some first item current when a key is pressed
2229  if (!cur) {
2230  setCurrentItem( firstChild() );
2231  setSelectionAnchor( currentItem() );
2232  return;
2233  }
2234 
2235  // Handle space key press
2236  if (cur->isSelectable() && e->ascii() == ' ' ) {
2237  setSelected( cur, !cur->isSelected() );
2238  highlightMessage( cur, false);
2239  return;
2240  }
2241 
2242  if (cntrl) {
2243  if (!shft)
2244  disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
2245  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
2246  switch (e->key()) {
2247  case Key_Down:
2248  case Key_Up:
2249  case Key_Home:
2250  case Key_End:
2251  case Key_Next:
2252  case Key_Prior:
2253  case Key_Escape:
2254  KListView::keyPressEvent( e );
2255  }
2256  if (!shft)
2257  connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
2258  this,TQT_SLOT(highlightMessage(TQListViewItem*)));
2259  }
2260 }
2261 
2262 //-----------------------------------------------------------------------------
2263 // Handle RMB press, show pop up menu
2264 void KMHeaders::rightButtonPressed( TQListViewItem *lvi, const TQPoint &, int )
2265 {
2266  if (!lvi)
2267  return;
2268 
2269  if (!(lvi->isSelected())) {
2270  clearSelection();
2271  }
2272  setSelected( lvi, true );
2273  slotRMB();
2274 }
2275 
2276 //-----------------------------------------------------------------------------
2278 {
2279  mPressPos = e->pos();
2280  TQListViewItem *lvi = itemAt( contentsToViewport( e->pos() ));
2281  bool wasSelected = false;
2282  bool rootDecoClicked = false;
2283  if (lvi) {
2284  wasSelected = lvi->isSelected();
2285  rootDecoClicked =
2286  ( mPressPos.x() <= header()->cellPos( header()->mapToActual( 0 ) ) +
2287  treeStepSize() * ( lvi->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() )
2288  && ( mPressPos.x() >= header()->cellPos( header()->mapToActual( 0 ) ) );
2289 
2290  if ( rootDecoClicked ) {
2291  // Check if our item is the parent of a closed thread and if so, if the root
2292  // decoration of the item was clicked (i.e. the +/- sign) which would expand
2293  // the thread. In that case, deselect all children, so opening the thread
2294  // doesn't cause a flicker.
2295  if ( !lvi->isOpen() && lvi->firstChild() ) {
2296  TQListViewItem *nextRoot = lvi->itemBelow();
2297  TQListViewItemIterator it( lvi->firstChild() );
2298  for( ; (*it) != nextRoot; ++it )
2299  (*it)->setSelected( false );
2300  }
2301  }
2302  }
2303 
2304  // let klistview do it's thing, expanding/collapsing, selection/deselection
2305  KListView::contentsMousePressEvent(e);
2306  /* TQListView's shift-select selects also invisible items. Until that is
2307  fixed, we have to deselect hidden items here manually, so the quick
2308  search doesn't mess things up. */
2309  if ( e->state() & ShiftButton ) {
2310  TQListViewItemIterator it( this, TQListViewItemIterator::Invisible );
2311  while ( it.current() ) {
2312  it.current()->setSelected( false );
2313  ++it;
2314  }
2315  }
2316 
2317  if ( rootDecoClicked ) {
2318  // select the thread's children after closing if the parent is selected
2319  if ( lvi && !lvi->isOpen() && lvi->isSelected() )
2320  setSelected( lvi, true );
2321  }
2322 
2323  if ( lvi && !rootDecoClicked ) {
2324  if ( lvi != currentItem() )
2325  highlightMessage( lvi );
2326  /* Explicitely set selection state. This is necessary because we want to
2327  * also select all children of closed threads when the parent is selected. */
2328 
2329  // unless ctrl mask, set selected if it isn't already
2330  if ( !( e->state() & ControlButton ) && !wasSelected )
2331  setSelected( lvi, true );
2332  // if ctrl mask, toggle selection
2333  if ( e->state() & ControlButton )
2334  setSelected( lvi, !wasSelected );
2335 
2336  if ((e->button() == Qt::LeftButton) )
2337  mMousePressed = true;
2338  }
2339 
2340  // check if we are on a status column and toggle it
2341  if ( lvi && e->button() == Qt::LeftButton && !( e->state() & (ShiftButton | ControlButton | AltButton | MetaButton) ) ) {
2342  bool flagsToggleable = GlobalSettings::self()->allowLocalFlags() || !(mFolder ? mFolder->isReadOnly() : true);
2343  int section = header()->sectionAt( e->pos().x() );
2344  HeaderItem *item = static_cast<HeaderItem*>( lvi );
2345  KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
2346  if ( section == mPaintInfo.flagCol && flagsToggleable ) {
2347  setMsgStatus( KMMsgStatusFlag, true );
2348  } else if ( section == mPaintInfo.importantCol && flagsToggleable ) {
2349  setMsgStatus( KMMsgStatusFlag, true );
2350  } else if ( section == mPaintInfo.todoCol && flagsToggleable ) {
2351  setMsgStatus( KMMsgStatusTodo, true );
2352  } else if ( section == mPaintInfo.watchedIgnoredCol && flagsToggleable ) {
2353  if ( msg->isWatched() || msg->isIgnored() )
2354  setMsgStatus( KMMsgStatusIgnored, true );
2355  else
2356  setMsgStatus( KMMsgStatusWatched, true );
2357  } else if ( section == mPaintInfo.statusCol ) {
2358  if ( msg->isUnread() || msg->isNew() )
2359  setMsgStatus( KMMsgStatusRead );
2360  else
2361  setMsgStatus( KMMsgStatusUnread );
2362  }
2363  }
2364 }
2365 
2366 //-----------------------------------------------------------------------------
2367 void KMHeaders::contentsMouseReleaseEvent(TQMouseEvent* e)
2368 {
2369  if (e->button() != Qt::RightButton)
2370  KListView::contentsMouseReleaseEvent(e);
2371 
2372  mMousePressed = false;
2373 }
2374 
2375 //-----------------------------------------------------------------------------
2376 void KMHeaders::contentsMouseMoveEvent( TQMouseEvent* e )
2377 {
2378  if (mMousePressed &&
2379  (e->pos() - mPressPos).manhattanLength() > KGlobalSettings::dndEventDelay()) {
2380  mMousePressed = false;
2381  TQListViewItem *item = itemAt( contentsToViewport(mPressPos) );
2382  if ( item ) {
2383  MailList mailList;
2384  unsigned int count = 0;
2385  for( TQListViewItemIterator it(this); it.current(); it++ )
2386  if( it.current()->isSelected() ) {
2387  HeaderItem *item = static_cast<HeaderItem*>(it.current());
2388  KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
2389  // FIXME: msg can be null here which crashes. I think it's a race
2390  // because it's very hard to reproduce. (GS)
2391  MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(),
2392  msg->subject(), msg->fromStrip(),
2393  msg->toStrip(), msg->date() );
2394  mailList.append( mailSummary );
2395  ++count;
2396  }
2397  MailListDrag *d = new MailListDrag( mailList, viewport(), new KMTextSource );
2398 
2399  // Set pixmap
2400  TQPixmap pixmap;
2401  if( count == 1 )
2402  pixmap = TQPixmap( DesktopIcon("message", KIcon::SizeSmall) );
2403  else
2404  pixmap = TQPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) );
2405 
2406  // Calculate hotspot (as in Konqueror)
2407  if( !pixmap.isNull() ) {
2408  TQPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 );
2409  d->setPixmap( pixmap, hotspot );
2410  }
2411  if ( mFolder->isReadOnly() )
2412  d->dragCopy();
2413  else
2414  d->drag();
2415  }
2416  }
2417 }
2418 
2419 void KMHeaders::highlightMessage(TQListViewItem* i)
2420 {
2421  highlightMessage( i, false );
2422 }
2423 
2424 //-----------------------------------------------------------------------------
2426 {
2427  if (!topLevelWidget()) return; // safe bet
2428  mOwner->updateMessageActions();
2429 
2430  // check if the user clicked into a status column and only show the respective menues
2431  TQListViewItem *item = itemAt( viewport()->mapFromGlobal( TQCursor::pos() ) );
2432  if ( item ) {
2433  int section = header()->sectionAt( viewportToContents( viewport()->mapFromGlobal( TQCursor::pos() ) ).x() );
2434  if ( section == mPaintInfo.flagCol || section == mPaintInfo.importantCol
2435  || section == mPaintInfo.todoCol || section == mPaintInfo.statusCol ) {
2436  mOwner->statusMenu()->popup( TQCursor::pos() );
2437  return;
2438  }
2439  if ( section == mPaintInfo.watchedIgnoredCol ) {
2440  mOwner->threadStatusMenu()->popup( TQCursor::pos() );
2441  return;
2442  }
2443  }
2444 
2445  TQPopupMenu *menu = new TQPopupMenu(this);
2446 
2447  mMenuToFolder.clear();
2448 
2449  mOwner->updateMessageMenu();
2450 
2451  bool out_folder = kmkernel->folderIsDraftOrOutbox( mFolder );
2452  bool tem_folder = kmkernel->folderIsTemplates( mFolder );
2453  if ( tem_folder ) {
2454  mOwner->useAction()->plug( menu );
2455  } else {
2456  // show most used actions
2457  mOwner->messageActions()->replyMenu()->plug( menu );
2458  mOwner->forwardMenu()->plug( menu );
2459  if( mOwner->sendAgainAction()->isEnabled() ) {
2460  mOwner->sendAgainAction()->plug( menu );
2461  } else {
2462  mOwner->editAction()->plug( menu );
2463  }
2464  }
2465  menu->insertSeparator();
2466 
2467  TQPopupMenu *msgCopyMenu = new TQPopupMenu(menu);
2468  mOwner->folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage, TQT_TQOBJECT(this),
2469  &mMenuToFolder, msgCopyMenu );
2470  menu->insertItem(i18n("&Copy To"), msgCopyMenu);
2471 
2472  if ( !mFolder->canDeleteMessages() ) {
2473  int id = menu->insertItem( i18n("&Move To") );
2474  menu->setItemEnabled( id, false );
2475  } else {
2476  TQPopupMenu *msgMoveMenu = new TQPopupMenu(menu);
2477  mOwner->folderTree()->folderToPopupMenu( KMFolderTree::MoveMessage, TQT_TQOBJECT(this),
2478  &mMenuToFolder, msgMoveMenu );
2479  menu->insertItem(i18n("&Move To"), msgMoveMenu);
2480  }
2481  menu->insertSeparator();
2482  mOwner->statusMenu()->plug( menu ); // Mark Message menu
2483  if ( mOwner->threadStatusMenu()->isEnabled() ) {
2484  mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu
2485  }
2486 
2487  if ( !out_folder && !tem_folder ) {
2488  menu->insertSeparator();
2489  mOwner->filterMenu()->plug( menu ); // Create Filter menu
2490  mOwner->action( "apply_filter_actions" )->plug( menu );
2491  }
2492 
2493  menu->insertSeparator();
2494  mOwner->printAction()->plug(menu);
2495  mOwner->saveAsAction()->plug(menu);
2496  mOwner->saveAttachmentsAction()->plug(menu);
2497  menu->insertSeparator();
2498  if ( mFolder->isTrash() ) {
2499  mOwner->deleteAction()->plug(menu);
2500  if ( mOwner->trashThreadAction()->isEnabled() )
2501  mOwner->deleteThreadAction()->plug(menu);
2502  } else {
2503  mOwner->trashAction()->plug(menu);
2504  if ( mOwner->trashThreadAction()->isEnabled() )
2505  mOwner->trashThreadAction()->plug(menu);
2506  }
2507  menu->insertSeparator();
2508  mOwner->messageActions()->createTodoAction()->plug( menu );
2509 
2510  KAcceleratorManager::manage(menu);
2511  kmkernel->setContextMenuShown( true );
2512  menu->exec(TQCursor::pos(), 0);
2513  kmkernel->setContextMenuShown( false );
2514  delete menu;
2515 }
2516 
2517 //-----------------------------------------------------------------------------
2519 {
2520  HeaderItem *hi = currentHeaderItem();
2521  if (!hi)
2522  return 0;
2523  else
2524  return mFolder->getMsg(hi->msgId());
2525 }
2526 
2527 //-----------------------------------------------------------------------------
2529 {
2530  return static_cast<HeaderItem*>(currentItem());
2531 }
2532 
2533 //-----------------------------------------------------------------------------
2535 {
2536  HeaderItem* item = currentHeaderItem();
2537  if (item)
2538  return item->msgId();
2539  else
2540  return -1;
2541 }
2542 
2543 //-----------------------------------------------------------------------------
2545 {
2546  if (!mFolder->isOpened()) setFolder(mFolder);
2547 
2548  if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) {
2549  clearSelection();
2550  bool unchanged = (currentItem() == mItems[msgIdx]);
2551  setCurrentItem( mItems[msgIdx] );
2552  setSelected( mItems[msgIdx], true );
2553  setSelectionAnchor( currentItem() );
2554  if (unchanged)
2555  highlightMessage( mItems[msgIdx], false);
2557  }
2558 }
2559 
2560 //-----------------------------------------------------------------------------
2562 {
2563  HeaderItem *item = static_cast<HeaderItem*>( itemAt( TQPoint( 1, 1 ) ) );
2564  if ( item )
2565  return item->msgId();
2566  else
2567  return -1;
2568 }
2569 
2570 //-----------------------------------------------------------------------------
2572 {
2573  if ( aMsgIdx < 0 || static_cast<unsigned int>( aMsgIdx ) >= mItems.size() )
2574  return;
2575  const TQListViewItem * const item = mItems[aMsgIdx];
2576  if ( item )
2577  setContentsPos( 0, itemPos( item ) );
2578 }
2579 
2580 //-----------------------------------------------------------------------------
2581 void KMHeaders::setNestedOverride( bool override )
2582 {
2583  mSortInfo.dirty = true;
2584  mNestedOverride = override;
2585  setRootIsDecorated( nestingPolicy != AlwaysOpen
2586  && isThreaded() );
2587  TQString sortFile = mFolder->indexLocation() + ".sorted";
2588  unlink(TQFile::encodeName(sortFile));
2589  reset();
2590 }
2591 
2592 //-----------------------------------------------------------------------------
2593 void KMHeaders::setSubjectThreading( bool aSubjThreading )
2594 {
2595  mSortInfo.dirty = true;
2596  mSubjThreading = aSubjThreading;
2597  TQString sortFile = mFolder->indexLocation() + ".sorted";
2598  unlink(TQFile::encodeName(sortFile));
2599  reset();
2600 }
2601 
2602 //-----------------------------------------------------------------------------
2603 void KMHeaders::setOpen( TQListViewItem *item, bool open )
2604 {
2605  if ((nestingPolicy != AlwaysOpen)|| open)
2606  ((HeaderItem*)item)->setOpenRecursive( open );
2607 }
2608 
2609 //-----------------------------------------------------------------------------
2610 const KMMsgBase* KMHeaders::getMsgBaseForItem( const TQListViewItem *item ) const
2611 {
2612  const HeaderItem *hi = static_cast<const HeaderItem *> ( item );
2613  return mFolder->getMsgBase( hi->msgId() );
2614 }
2615 
2616 //-----------------------------------------------------------------------------
2617 void KMHeaders::setSorting( int column, bool ascending )
2618 {
2619  if ( mIgnoreSortOrderChanges )
2620  return;
2621 
2622  if (column != -1) {
2623  // carsten: really needed?
2624 // if (column != mSortCol)
2625 // setColumnText( mSortCol, TQIconSet( TQPixmap()), columnText( mSortCol ));
2626  if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied
2627  TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
2628  mSortInfo.dirty = true;
2629  }
2630 
2631  assert(column >= 0);
2632  mSortCol = column;
2633  mSortDescending = !ascending;
2634 
2635  if (!ascending && (column == mPaintInfo.dateCol))
2636  mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival;
2637 
2638  if (!ascending && (column == mPaintInfo.subCol))
2639  mPaintInfo.status = !mPaintInfo.status;
2640 
2641  TQString colText = i18n( "Date" );
2642  if (mPaintInfo.orderOfArrival)
2643  colText = i18n( "Order of Arrival" );
2644  setColumnText( mPaintInfo.dateCol, colText);
2645 
2646  colText = i18n( "Subject" );
2647  if (mPaintInfo.status)
2648  colText = colText + i18n( " (Status)" );
2649  setColumnText( mPaintInfo.subCol, colText);
2650  }
2651  KListView::setSorting( column, ascending );
2652  ensureCurrentItemVisible();
2653  // Make sure the config and .sorted file are updated, otherwise stale info
2654  // is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ).
2655  if ( mFolder ) {
2657  writeSortOrder();
2658  }
2659 }
2660 
2661 //Flatten the list and write it to disk
2662 static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid,
2663  int parent_id, TQString key,
2664  bool update_discover=true)
2665 {
2666  unsigned long msgSerNum;
2667  unsigned long parentSerNum;
2668  msgSerNum = KMMsgDict::instance()->getMsgSerNum( folder, msgid );
2669  if (parent_id >= 0)
2670  parentSerNum = KMMsgDict::instance()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED;
2671  else
2672  parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED);
2673 
2674  fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream);
2675  fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream);
2676  TQ_INT32 len = key.length() * sizeof(TQChar);
2677  fwrite(&len, sizeof(len), 1, sortStream);
2678  if (len)
2679  fwrite(key.unicode(), TQMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream);
2680 
2681  if (update_discover) {
2682  //update the discovered change count
2683  TQ_INT32 discovered_count = 0;
2684  fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
2685  fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
2686  discovered_count++;
2687  fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
2688  fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
2689  }
2690 }
2691 
2693 {
2694  mSortCacheItems.clear(); //autoDelete is true
2695  mSubjectLists.clear();
2696  mImperfectlyThreadedList.clear();
2697  mPrevCurrent = 0;
2698  emit selected(0);
2699 }
2700 
2701 
2703 {
2704  if ( mFolder->open( "kmheaders" ) == 0 )
2705  updateMessageList();
2706  else
2707  folderCleared();
2708 }
2709 
2710 bool KMHeaders::writeSortOrder()
2711 {
2712  TQString sortFile = KMAIL_SORT_FILE(mFolder);
2713 
2714  if (!mSortInfo.dirty) {
2715  struct stat stat_tmp;
2716  if(stat(TQFile::encodeName(sortFile), &stat_tmp) == -1) {
2717  mSortInfo.dirty = true;
2718  }
2719  }
2720  if (mSortInfo.dirty) {
2721  if (!mFolder->count()) {
2722  // Folder is empty now, remove the sort file.
2723  unlink(TQFile::encodeName(sortFile));
2724  return true;
2725  }
2726  TQString tempName = sortFile + ".temp";
2727  unlink(TQFile::encodeName(tempName));
2728  FILE *sortStream = fopen(TQFile::encodeName(tempName), "w");
2729  if (!sortStream)
2730  return false;
2731 
2732  mSortInfo.ascending = !mSortDescending;
2733  mSortInfo.dirty = false;
2734  mSortInfo.column = mSortCol;
2735  fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION);
2736  //magic header information
2737  TQ_INT32 byteOrder = 0x12345678;
2738  TQ_INT32 column = mSortCol;
2739  TQ_INT32 ascending= !mSortDescending;
2740  TQ_INT32 threaded = isThreaded();
2741  TQ_INT32 appended=0;
2742  TQ_INT32 discovered_count = 0;
2743  TQ_INT32 sorted_count=0;
2744  fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
2745  fwrite(&column, sizeof(column), 1, sortStream);
2746  fwrite(&ascending, sizeof(ascending), 1, sortStream);
2747  fwrite(&threaded, sizeof(threaded), 1, sortStream);
2748  fwrite(&appended, sizeof(appended), 1, sortStream);
2749  fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
2750  fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
2751 
2752  TQPtrStack<HeaderItem> items;
2753  {
2754  TQPtrStack<TQListViewItem> s;
2755  for (TQListViewItem * i = firstChild(); i; ) {
2756  items.push((HeaderItem *)i);
2757  if ( i->firstChild() ) {
2758  s.push( i );
2759  i = i->firstChild();
2760  } else if( i->nextSibling()) {
2761  i = i->nextSibling();
2762  } else {
2763  for(i=0; !i && s.count(); i = s.pop()->nextSibling())
2764  ;
2765  }
2766  }
2767  }
2768 
2769  KMMsgBase *kmb;
2770  while(HeaderItem *i = items.pop()) {
2771  int parent_id = -1; //no parent, top level
2772  if (threaded) {
2773  kmb = mFolder->getMsgBase( i->msgId() );
2774  assert(kmb); // I have seen 0L come out of this, called from
2775  // KMHeaders::setFolder(0xgoodpointer, false);
2776  // I see this crash too. after rebuilding a broken index on a dimap folder. always
2777  TQString replymd5 = kmb->replyToIdMD5();
2778  TQString replyToAuxId = kmb->replyToAuxIdMD5();
2779  SortCacheItem *p = NULL;
2780  if(!replymd5.isEmpty())
2781  p = mSortCacheItems[replymd5];
2782 
2783  if (p)
2784  parent_id = p->id();
2785  // We now have either found a parent, or set it to -1, which means that
2786  // it will be reevaluated when a message is added, for example. If there
2787  // is no replyToId and no replyToAuxId and the message is not prefixed,
2788  // this message is top level, and will always be, so no need to
2789  // reevaluate it.
2790  if (replymd5.isEmpty()
2791  && replyToAuxId.isEmpty()
2792  && !kmb->subjectIsPrefixed() )
2793  parent_id = -2;
2794  // FIXME also mark messages with -1 as -2 a certain amount of time after
2795  // their arrival, since it becomes very unlikely that a new parent for
2796  // them will show up. (Ingo suggests a month.) -till
2797  }
2798  internalWriteItem(sortStream, mFolder, i->msgId(), parent_id,
2799  i->key(mSortCol, !mSortDescending), false);
2800  //double check for magic headers
2801  sorted_count++;
2802  }
2803 
2804  //magic header twice, case they've changed
2805  fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET);
2806  fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
2807  fwrite(&column, sizeof(column), 1, sortStream);
2808  fwrite(&ascending, sizeof(ascending), 1, sortStream);
2809  fwrite(&threaded, sizeof(threaded), 1, sortStream);
2810  fwrite(&appended, sizeof(appended), 1, sortStream);
2811  fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
2812  fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
2813  if (sortStream && ferror(sortStream)) {
2814  fclose(sortStream);
2815  unlink(TQFile::encodeName(sortFile));
2816  kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
2817  kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
2818  kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
2819  }
2820  fclose(sortStream);
2821  ::rename(TQFile::encodeName(tempName), TQFile::encodeName(sortFile));
2822  }
2823 
2824  return true;
2825 }
2826 
2827 void KMHeaders::appendItemToSortFile(HeaderItem *khi)
2828 {
2829  TQString sortFile = KMAIL_SORT_FILE(mFolder);
2830  if(FILE *sortStream = fopen(TQFile::encodeName(sortFile), "r+")) {
2831  int parent_id = -1; //no parent, top level
2832 
2833  if (isThreaded()) {
2834  SortCacheItem *sci = khi->sortCacheItem();
2835  KMMsgBase *kmb = mFolder->getMsgBase( khi->msgId() );
2836  if(sci->parent() && !sci->isImperfectlyThreaded())
2837  parent_id = sci->parent()->id();
2838  else if(kmb->replyToIdMD5().isEmpty()
2839  && kmb->replyToAuxIdMD5().isEmpty()
2840  && !kmb->subjectIsPrefixed())
2841  parent_id = -2;
2842  }
2843 
2844  internalWriteItem(sortStream, mFolder, khi->msgId(), parent_id,
2845  khi->key(mSortCol, !mSortDescending), false);
2846 
2847  //update the appended flag FIXME obsolete?
2848  TQ_INT32 appended = 1;
2849  fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
2850  fwrite(&appended, sizeof(appended), 1, sortStream);
2851  fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
2852 
2853  if (sortStream && ferror(sortStream)) {
2854  fclose(sortStream);
2855  unlink(TQFile::encodeName(sortFile));
2856  kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
2857  kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
2858  kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
2859  }
2860  fclose(sortStream);
2861  } else {
2862  mSortInfo.dirty = true;
2863  }
2864 }
2865 
2867 {
2868  mSortInfo.dirty = true;
2869  TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
2870  setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true);
2871 }
2872 
2873 // -----------------
2874 void SortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder,
2875  bool waiting_for_parent, bool update_discover)
2876 {
2877  if(mSortOffset == -1) {
2878  fseek(sortStream, 0, SEEK_END);
2879  mSortOffset = ftell(sortStream);
2880  } else {
2881  fseek(sortStream, mSortOffset, SEEK_SET);
2882  }
2883 
2884  int parent_id = -1;
2885  if(!waiting_for_parent) {
2886  if(mParent && !isImperfectlyThreaded())
2887  parent_id = mParent->id();
2888  }
2889  internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover);
2890 }
2891 
2892 static bool compare_ascending = false;
2893 static bool compare_toplevel = true;
2894 static int compare_SortCacheItem(const void *s1, const void *s2)
2895 {
2896  if ( !s1 || !s2 )
2897  return 0;
2898  SortCacheItem **b1 = (SortCacheItem **)s1;
2899  SortCacheItem **b2 = (SortCacheItem **)s2;
2900  int ret = (*b1)->key().compare((*b2)->key());
2901  if(compare_ascending || !compare_toplevel)
2902  ret = -ret;
2903  return ret;
2904 }
2905 
2906 // Debugging helpers
2907 void KMHeaders::printSubjectThreadingTree()
2908 {
2909  TQDictIterator< TQPtrList< SortCacheItem > > it ( mSubjectLists );
2910  kdDebug(5006) << "SubjectThreading tree: " << endl;
2911  for( ; it.current(); ++it ) {
2912  TQPtrList<SortCacheItem> list = *( it.current() );
2913  TQPtrListIterator<SortCacheItem> it2( list ) ;
2914  kdDebug(5006) << "Subject MD5: " << it.currentKey() << " list: " << endl;
2915  for( ; it2.current(); ++it2 ) {
2916  SortCacheItem *sci = it2.current();
2917  kdDebug(5006) << " item:" << sci << " sci id: " << sci->id() << endl;
2918  }
2919  }
2920  kdDebug(5006) << endl;
2921 }
2922 
2923 void KMHeaders::printThreadingTree()
2924 {
2925  kdDebug(5006) << "Threading tree: " << endl;
2926  TQDictIterator<SortCacheItem> it( mSortCacheItems );
2927  kdDebug(5006) << endl;
2928  for( ; it.current(); ++it ) {
2929  SortCacheItem *sci = it.current();
2930  kdDebug(5006) << "MsgId MD5: " << it.currentKey() << " message id: " << sci->id() << endl;
2931  }
2932  for (int i = 0; i < (int)mItems.size(); ++i) {
2933  HeaderItem *item = mItems[i];
2934  int parentCacheId = item->sortCacheItem()->parent()?item->sortCacheItem()->parent()->id():0;
2935  kdDebug( 5006 ) << "SortCacheItem: " << item->sortCacheItem()->id() << " parent: " << parentCacheId << endl;
2936  kdDebug( 5006 ) << "Item: " << item << " sortCache: " << item->sortCacheItem() << " parent: " << item->sortCacheItem()->parent() << endl;
2937  }
2938  kdDebug(5006) << endl;
2939 }
2940 
2941 // -------------------------------------
2942 
2943 void KMHeaders::buildThreadingTree( TQMemArray<SortCacheItem *> sortCache )
2944 {
2945  mSortCacheItems.clear();
2946  mSortCacheItems.resize( mFolder->count() * 2 );
2947 
2948  // build a dict of all message id's
2949  for(int x = 0; x < mFolder->count(); x++) {
2950  KMMsgBase *mi = mFolder->getMsgBase(x);
2951  TQString md5 = mi->msgIdMD5();
2952  if(!md5.isEmpty())
2953  mSortCacheItems.replace(md5, sortCache[x]);
2954  }
2955 }
2956 
2957 
2958 void KMHeaders::buildSubjectThreadingTree( TQMemArray<SortCacheItem *> sortCache )
2959 {
2960  mSubjectLists.clear(); // autoDelete is true
2961  mSubjectLists.resize( mFolder->count() * 2 );
2962 
2963  for(int x = 0; x < mFolder->count(); x++) {
2964  // Only a lot items that are now toplevel
2965  if ( sortCache[x]->parent()
2966  && sortCache[x]->parent()->id() != -666 ) continue;
2967  KMMsgBase *mi = mFolder->getMsgBase(x);
2968  TQString subjMD5 = mi->strippedSubjectMD5();
2969  if (subjMD5.isEmpty()) {
2970  mFolder->getMsgBase(x)->initStrippedSubjectMD5();
2971  subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
2972  }
2973  if( subjMD5.isEmpty() ) continue;
2974 
2975  /* For each subject, keep a list of items with that subject
2976  * (stripped of prefixes) sorted by date. */
2977  if (!mSubjectLists.find(subjMD5))
2978  mSubjectLists.insert(subjMD5, new TQPtrList<SortCacheItem>());
2979  /* Insertion sort by date. These lists are expected to be very small.
2980  * Also, since the messages are roughly ordered by date in the store,
2981  * they should mostly be prepended at the very start, so insertion is
2982  * cheap. */
2983  int p=0;
2984  for (TQPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
2985  it.current(); ++it) {
2986  KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
2987  if ( mb->date() < mi->date())
2988  break;
2989  p++;
2990  }
2991  mSubjectLists[subjMD5]->insert( p, sortCache[x]);
2992  sortCache[x]->setSubjectThreadingList( mSubjectLists[subjMD5] );
2993  }
2994 }
2995 
2996 
2997 SortCacheItem* KMHeaders::findParent(SortCacheItem *item)
2998 {
2999  SortCacheItem *parent = NULL;
3000  if (!item) return parent;
3001  KMMsgBase *msg = mFolder->getMsgBase(item->id());
3002  TQString replyToIdMD5 = msg->replyToIdMD5();
3003  item->setImperfectlyThreaded(true);
3004  /* First, try if the message our Reply-To header points to
3005  * is available to thread below. */
3006  if(!replyToIdMD5.isEmpty()) {
3007  parent = mSortCacheItems[replyToIdMD5];
3008  if (parent)
3009  item->setImperfectlyThreaded(false);
3010  }
3011  if (!parent) {
3012  // If we dont have a replyToId, or if we have one and the
3013  // corresponding message is not in this folder, as happens
3014  // if you keep your outgoing messages in an OUTBOX, for
3015  // example, try the list of references, because the second
3016  // to last will likely be in this folder. replyToAuxIdMD5
3017  // contains the second to last one.
3018  TQString ref = msg->replyToAuxIdMD5();
3019  if (!ref.isEmpty())
3020  parent = mSortCacheItems[ref];
3021  }
3022  return parent;
3023 }
3024 
3025 SortCacheItem* KMHeaders::findParentBySubject(SortCacheItem *item)
3026 {
3027  SortCacheItem *parent = NULL;
3028  if (!item) return parent;
3029 
3030  KMMsgBase *msg = mFolder->getMsgBase(item->id());
3031 
3032  // Let's try by subject, but only if the subject is prefixed.
3033  // This is necessary to make for example cvs commit mailing lists
3034  // work as expected without having to turn threading off alltogether.
3035  if (!msg->subjectIsPrefixed())
3036  return parent;
3037 
3038  TQString replyToIdMD5 = msg->replyToIdMD5();
3039  TQString subjMD5 = msg->strippedSubjectMD5();
3040  if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) {
3041  /* Iterate over the list of potential parents with the same
3042  * subject, and take the closest one by date. */
3043  for (TQPtrListIterator<SortCacheItem> it2(*mSubjectLists[subjMD5]);
3044  it2.current(); ++it2) {
3045  KMMsgBase *mb = mFolder->getMsgBase((*it2)->id());
3046  if ( !mb ) return parent;
3047  // make sure it's not ourselves
3048  if ( item == (*it2) ) continue;
3049  int delta = msg->date() - mb->date();
3050  // delta == 0 is not allowed, to avoid circular threading
3051  // with duplicates.
3052  if (delta > 0 ) {
3053  // Don't use parents more than 6 weeks older than us.
3054  if (delta < 3628899)
3055  parent = (*it2);
3056  break;
3057  }
3058  }
3059  }
3060  return parent;
3061 }
3062 
3063 bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread )
3064 {
3065  if (!mFolder->isOpened()) mFolder->open("kmheaders");
3066 
3067  //all cases
3068  TQ_INT32 column, ascending, threaded, discovered_count, sorted_count, appended;
3069  TQ_INT32 deleted_count = 0;
3070  bool unread_exists = false;
3071  bool jumpToUnread = (GlobalSettings::self()->actionEnterFolder() ==
3072  GlobalSettings::EnumActionEnterFolder::SelectFirstUnreadNew) ||
3073  forceJumpToUnread;
3074  HeaderItem *oldestItem = 0;
3075  HeaderItem *newestItem = 0;
3076  TQMemArray<SortCacheItem *> sortCache(mFolder->count());
3077  bool error = false;
3078 
3079  //threaded cases
3080  TQPtrList<SortCacheItem> unparented;
3081  mImperfectlyThreadedList.clear();
3082 
3083  //cleanup
3084  mItems.fill( 0, mFolder->count() );
3085  sortCache.fill( 0 );
3086 
3087  mRoot->clearChildren();
3088 
3089  TQString sortFile = KMAIL_SORT_FILE(mFolder);
3090  FILE *sortStream = fopen(TQFile::encodeName(sortFile), "r+");
3091  mSortInfo.fakeSort = 0;
3092 
3093  if(sortStream) {
3094  mSortInfo.fakeSort = 1;
3095  int version = 0;
3096  if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1)
3097  version = -1;
3098  if(version == KMAIL_SORT_VERSION) {
3099  TQ_INT32 byteOrder = 0;
3100  fread(&byteOrder, sizeof(byteOrder), 1, sortStream);
3101  if (byteOrder == 0x12345678)
3102  {
3103  fread(&column, sizeof(column), 1, sortStream);
3104  fread(&ascending, sizeof(ascending), 1, sortStream);
3105  fread(&threaded, sizeof(threaded), 1, sortStream);
3106  fread(&appended, sizeof(appended), 1, sortStream);
3107  fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
3108  fread(&sorted_count, sizeof(sorted_count), 1, sortStream);
3109 
3110  //Hackyness to work around qlistview problems
3111  KListView::setSorting(-1);
3112  header()->setSortIndicator(column, ascending);
3113  TQObject::connect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
3114  //setup mSortInfo here now, as above may change it
3115  mSortInfo.dirty = false;
3116  mSortInfo.column = (short)column;
3117  mSortInfo.ascending = (compare_ascending = ascending);
3118 
3119  SortCacheItem *item;
3120  unsigned long serNum, parentSerNum;
3121  int id, len, parent, x;
3122  TQChar *tmp_qchar = 0;
3123  int tmp_qchar_len = 0;
3124  const int mFolderCount = mFolder->count();
3125  TQString key;
3126 
3127  CREATE_TIMER(parse);
3128  START_TIMER(parse);
3129  for(x = 0; !feof(sortStream) && !error; x++) {
3130  off_t offset = ftell(sortStream);
3131  KMFolder *folder;
3132  //parse
3133  if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end
3134  !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) ||
3135  !fread(&len, sizeof(len), 1, sortStream)) {
3136  break;
3137  }
3138  if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) {
3139  kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl;
3140  error = true;
3141  continue;
3142  }
3143  if(len) {
3144  if(len > tmp_qchar_len) {
3145  tmp_qchar = (TQChar *)realloc(tmp_qchar, len);
3146  tmp_qchar_len = len;
3147  }
3148  if(!fread(tmp_qchar, len, 1, sortStream))
3149  break;
3150  key = TQString(tmp_qchar, len / 2);
3151  } else {
3152  key = TQString(""); //yuck
3153  }
3154 
3155  KMMsgDict::instance()->getLocation(serNum, &folder, &id);
3156  if (folder != mFolder) {
3157  ++deleted_count;
3158  continue;
3159  }
3160  if (parentSerNum < KMAIL_RESERVED) {
3161  parent = (int)parentSerNum - KMAIL_RESERVED;
3162  } else {
3163  KMMsgDict::instance()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent);
3164  if (folder != mFolder)
3165  parent = -1;
3166  }
3167  if ((id < 0) || (id >= mFolderCount) ||
3168  (parent < -2) || (parent >= mFolderCount)) { // sanity checking
3169  kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl;
3170  error = true;
3171  continue;
3172  }
3173 
3174  if ((item=sortCache[id])) {
3175  if (item->id() != -1) {
3176  kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl;
3177  error = true;
3178  continue;
3179  }
3180  item->setKey(key);
3181  item->setId(id);
3182  item->setOffset(offset);
3183  } else {
3184  item = sortCache[id] = new SortCacheItem(id, key, offset);
3185  }
3186  if (threaded && parent != -2) {
3187  if(parent == -1) {
3188  unparented.append(item);
3189  mRoot->addUnsortedChild(item);
3190  } else {
3191  if( ! sortCache[parent] ) {
3192  sortCache[parent] = new SortCacheItem;
3193  }
3194  sortCache[parent]->addUnsortedChild(item);
3195  }
3196  } else {
3197  if(x < sorted_count )
3198  mRoot->addSortedChild(item);
3199  else {
3200  mRoot->addUnsortedChild(item);
3201  }
3202  }
3203  }
3204  if (error || (x != sorted_count + discovered_count)) {// sanity check
3205  kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl;
3206  fclose(sortStream);
3207  sortStream = 0;
3208  }
3209 
3210  if(tmp_qchar)
3211  free(tmp_qchar);
3212  END_TIMER(parse);
3213  SHOW_TIMER(parse);
3214  }
3215  else {
3216  fclose(sortStream);
3217  sortStream = 0;
3218  }
3219  } else {
3220  fclose(sortStream);
3221  sortStream = 0;
3222  }
3223  }
3224 
3225  if (!sortStream) {
3226  mSortInfo.dirty = true;
3227  mSortInfo.column = column = mSortCol;
3228  mSortInfo.ascending = ascending = !mSortDescending;
3229  threaded = (isThreaded());
3230  sorted_count = discovered_count = appended = 0;
3231  KListView::setSorting( mSortCol, !mSortDescending );
3232  }
3233  //fill in empty holes
3234  if((sorted_count + discovered_count - deleted_count) < mFolder->count()) {
3235  CREATE_TIMER(holes);
3236  START_TIMER(holes);
3237  KMMsgBase *msg = 0;
3238  for(int x = 0; x < mFolder->count(); x++) {
3239  if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) {
3240  int sortOrder = column;
3241  if (mPaintInfo.orderOfArrival)
3242  sortOrder |= (1 << 6);
3243  if (mPaintInfo.status)
3244  sortOrder |= (1 << 5);
3245  sortCache[x] = new SortCacheItem(
3246  x, HeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder ));
3247  if(threaded)
3248  unparented.append(sortCache[x]);
3249  else
3250  mRoot->addUnsortedChild(sortCache[x]);
3251  if(sortStream)
3252  sortCache[x]->updateSortFile(sortStream, mFolder, true, true);
3253  discovered_count++;
3254  appended = 1;
3255  }
3256  }
3257  END_TIMER(holes);
3258  SHOW_TIMER(holes);
3259  }
3260 
3261  // Make sure we've placed everything in parent/child relationship. All
3262  // messages with a parent id of -1 in the sort file are reevaluated here.
3263  if (threaded) buildThreadingTree( sortCache );
3264  TQPtrList<SortCacheItem> toBeSubjThreaded;
3265 
3266  if (threaded && !unparented.isEmpty()) {
3267  CREATE_TIMER(reparent);
3268  START_TIMER(reparent);
3269 
3270  for(TQPtrListIterator<SortCacheItem> it(unparented); it.current(); ++it) {
3271  SortCacheItem *item = (*it);
3272  SortCacheItem *parent = findParent( item );
3273  // If we have a parent, make sure it's not ourselves
3274  if ( parent && (parent != (*it)) ) {
3275  parent->addUnsortedChild((*it));
3276  if(sortStream)
3277  (*it)->updateSortFile(sortStream, mFolder);
3278  } else {
3279  // if we will attempt subject threading, add to the list,
3280  // otherwise to the root with them
3281  if (mSubjThreading)
3282  toBeSubjThreaded.append((*it));
3283  else
3284  mRoot->addUnsortedChild((*it));
3285  }
3286  }
3287 
3288  if (mSubjThreading) {
3289  buildSubjectThreadingTree( sortCache );
3290  for(TQPtrListIterator<SortCacheItem> it(toBeSubjThreaded); it.current(); ++it) {
3291  SortCacheItem *item = (*it);
3292  SortCacheItem *parent = findParentBySubject( item );
3293 
3294  if ( parent ) {
3295  parent->addUnsortedChild((*it));
3296  if(sortStream)
3297  (*it)->updateSortFile(sortStream, mFolder);
3298  } else {
3299  //oh well we tried, to the root with you!
3300  mRoot->addUnsortedChild((*it));
3301  }
3302  }
3303  }
3304  END_TIMER(reparent);
3305  SHOW_TIMER(reparent);
3306  }
3307  //create headeritems
3308  CREATE_TIMER(header_creation);
3309  START_TIMER(header_creation);
3310  HeaderItem *khi;
3311  SortCacheItem *i, *new_kci;
3312  TQPtrQueue<SortCacheItem> s;
3313  s.enqueue(mRoot);
3314  compare_toplevel = true;
3315  do {
3316  i = s.dequeue();
3317  const TQPtrList<SortCacheItem> *sorted = i->sortedChildren();
3318  int unsorted_count, unsorted_off=0;
3319  SortCacheItem **unsorted = i->unsortedChildren(unsorted_count);
3320  if(unsorted)
3321  qsort(unsorted, unsorted_count, sizeof(SortCacheItem *), //sort
3322  compare_SortCacheItem);
3323 
3324  /* The sorted list now contains all sorted children of this item, while
3325  * the (aptly named) unsorted array contains all as of yet unsorted
3326  * ones. It has just been qsorted, so it is in itself sorted. These two
3327  * sorted lists are now merged into one. */
3328  for(TQPtrListIterator<SortCacheItem> it(*sorted);
3329  (unsorted && unsorted_off < unsorted_count) || it.current(); ) {
3330  /* As long as we have something in the sorted list and there is
3331  nothing unsorted left, use the item from the sorted list. Also
3332  if we are sorting descendingly and the sorted item is supposed
3333  to be sorted before the unsorted one do so. In the ascending
3334  case we invert the logic for non top level items. */
3335  if( it.current() &&
3336  ( !unsorted || unsorted_off >= unsorted_count
3337  ||
3338  ( ( !ascending || (ascending && !compare_toplevel) )
3339  && (*it)->key() < unsorted[unsorted_off]->key() )
3340  ||
3341  ( ascending && (*it)->key() >= unsorted[unsorted_off]->key() )
3342  )
3343  )
3344  {
3345  new_kci = (*it);
3346  ++it;
3347  } else {
3348  /* Otherwise use the next item of the unsorted list */
3349  new_kci = unsorted[unsorted_off++];
3350  }
3351  if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent
3352  continue;
3353 
3354  if(threaded && i->item()) {
3355  // If the parent is watched or ignored, propagate that to it's
3356  // children
3357  if (mFolder->getMsgBase(i->id())->isWatched())
3358  mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusWatched);
3359  if (mFolder->getMsgBase(i->id())->isIgnored())
3360  mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusIgnored);
3361  khi = new HeaderItem(i->item(), new_kci->id(), new_kci->key());
3362  } else {
3363  khi = new HeaderItem(this, new_kci->id(), new_kci->key());
3364  }
3365  new_kci->setItem(mItems[new_kci->id()] = khi);
3366  if(new_kci->hasChildren())
3367  s.enqueue(new_kci);
3368  // we always jump to new messages, but we only jump to
3369  // unread messages if we are told to do so
3370  if ( ( mFolder->getMsgBase(new_kci->id())->isNew() &&
3371  GlobalSettings::self()->actionEnterFolder() ==
3372  GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
3373  ( ( mFolder->getMsgBase(new_kci->id())->isNew() ||
3374  mFolder->getMsgBase(new_kci->id())->isUnread() ) &&
3375  jumpToUnread ) )
3376  {
3377  unread_exists = true;
3378  }
3379 
3380  if ( !oldestItem || mFolder->getMsgBase( oldestItem->msgId() )->date() >
3381  mFolder->getMsgBase( new_kci->id() )->date() ) {
3382  oldestItem = khi;
3383  }
3384 
3385  if ( !newestItem || mFolder->getMsgBase( newestItem->msgId() )->date() <
3386  mFolder->getMsgBase( new_kci->id() )->date() ) {
3387  newestItem = khi;
3388  }
3389  }
3390  // If we are sorting by date and ascending the top level items are sorted
3391  // ascending and the threads themselves are sorted descending. One wants
3392  // to have new threads on top but the threads themselves top down.
3393  if (mSortCol == paintInfo()->dateCol)
3394  compare_toplevel = false;
3395  } while(!s.isEmpty());
3396 
3397  for(int x = 0; x < mFolder->count(); x++) { //cleanup
3398  if (!sortCache[x]) { // not yet there?
3399  continue;
3400  }
3401 
3402  if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
3403  kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. "
3404  << endl << "Please talk to your threading counselor asap. " << endl;
3405  khi = new HeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
3406  sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
3407  }
3408  // Add all imperfectly threaded items to a list, so they can be
3409  // reevaluated when a new message arrives which might be a better parent.
3410  // Important for messages arriving out of order.
3411  if (threaded && sortCache[x]->isImperfectlyThreaded()) {
3412  mImperfectlyThreadedList.append(sortCache[x]->item());
3413  }
3414  // Set the reverse mapping HeaderItem -> SortCacheItem. Needed for
3415  // keeping the data structures up to date on removal, for example.
3416  sortCache[x]->item()->setSortCacheItem(sortCache[x]);
3417  }
3418 
3419  if (getNestingPolicy()<2)
3420  for (HeaderItem *khi=static_cast<HeaderItem*>(firstChild()); khi!=0;khi=static_cast<HeaderItem*>(khi->nextSibling()))
3421  khi->setOpen(true);
3422 
3423  END_TIMER(header_creation);
3424  SHOW_TIMER(header_creation);
3425 
3426  if(sortStream) { //update the .sorted file now
3427  // heuristic for when it's time to rewrite the .sorted file
3428  if( discovered_count * discovered_count > sorted_count - deleted_count ) {
3429  mSortInfo.dirty = true;
3430  } else {
3431  //update the appended flag
3432  appended = 0;
3433  fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
3434  fwrite(&appended, sizeof(appended), 1, sortStream);
3435  }
3436  }
3437 
3438  // Select a message, depending on the "When entering a folder:" setting
3439  CREATE_TIMER(selection);
3440  START_TIMER(selection);
3441  if(set_selection) {
3442 
3443  // Search for the id of the first unread/new item, should there be any
3444  int first_unread = -1;
3445  if (unread_exists) {
3446  HeaderItem *item = static_cast<HeaderItem*>(firstChild());
3447  while (item) {
3448  if ( ( mFolder->getMsgBase(item->msgId())->isNew() &&
3449  GlobalSettings::self()->actionEnterFolder() ==
3450  GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
3451  ( ( mFolder->getMsgBase(item->msgId())->isNew() ||
3452  mFolder->getMsgBase(item->msgId())->isUnread() ) &&
3453  jumpToUnread ) )
3454  {
3455  first_unread = item->msgId();
3456  break;
3457  }
3458  item = static_cast<HeaderItem*>(item->itemBelow());
3459  }
3460  }
3461 
3462  // No unread message to select, so either select the newest, oldest or lastest selected
3463  if(first_unread == -1 ) {
3464  setTopItemByIndex( mTopItem );
3465 
3466  if ( GlobalSettings::self()->actionEnterFolder() ==
3467  GlobalSettings::EnumActionEnterFolder::SelectNewest && newestItem != 0 ) {
3468  setCurrentItemByIndex( newestItem->msgId() );
3469  }
3470  else if ( GlobalSettings::self()->actionEnterFolder() ==
3471  GlobalSettings::EnumActionEnterFolder::SelectOldest && oldestItem != 0 ) {
3472  setCurrentItemByIndex( oldestItem->msgId() );
3473  }
3474  else if ( mCurrentItem >= 0 )
3475  setCurrentItemByIndex( mCurrentItem );
3476  else if ( mCurrentItemSerNum > 0 )
3477  setCurrentItemBySerialNum( mCurrentItemSerNum );
3478  else
3479  setCurrentItemByIndex( 0 );
3480 
3481  // There is an unread item to select, so select it
3482  } else {
3483  setCurrentItemByIndex(first_unread);
3484  makeHeaderVisible();
3485  center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 );
3486  }
3487 
3488  // we are told to not change the selection
3489  } else {
3490  // only reset the selection if we have no current item
3491  if (mCurrentItem <= 0) {
3492  setTopItemByIndex(mTopItem);
3493  setCurrentItemByIndex(0);
3494  }
3495  }
3496  END_TIMER(selection);
3497  SHOW_TIMER(selection);
3498  if (error || (sortStream && ferror(sortStream))) {
3499  if ( sortStream )
3500  fclose(sortStream);
3501  unlink(TQFile::encodeName(sortFile));
3502  kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
3503  kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
3504 
3505  return true;
3506  }
3507  if(sortStream)
3508  fclose(sortStream);
3509 
3510  return true;
3511 }
3512 
3513 //-----------------------------------------------------------------------------
3514 void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum )
3515 {
3516  // Linear search == slow. Don't overuse this method.
3517  // It's currently only used for finding the current item again
3518  // after expiry deleted mails (so the index got invalidated).
3519  for (int i = 0; i < (int)mItems.size() - 1; ++i) {
3520  KMMsgBase *mMsgBase = mFolder->getMsgBase( i );
3521  if ( mMsgBase->getMsgSerNum() == serialNum ) {
3522  bool unchanged = (currentItem() == mItems[i]);
3523  setCurrentItem( mItems[i] );
3524  setSelected( mItems[i], true );
3525  setSelectionAnchor( currentItem() );
3526  if ( unchanged )
3527  highlightMessage( currentItem(), false );
3528  ensureCurrentItemVisible();
3529  return;
3530  }
3531  }
3532  // Not found. Maybe we should select the last item instead?
3533  kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl;
3534 }
3535 
3536 void KMHeaders::copyMessages()
3537 {
3538  mCopiedMessages.clear();
3539  KMMessageList* list = selectedMsgs();
3540  for ( uint i = 0; i < list->count(); ++ i )
3541  mCopiedMessages << list->at( i )->getMsgSerNum();
3542  mMoveMessages = false;
3543  updateActions();
3544  triggerUpdate();
3545 }
3546 
3547 void KMHeaders::cutMessages()
3548 {
3549  mCopiedMessages.clear();
3550  KMMessageList* list = selectedMsgs();
3551  for ( uint i = 0; i < list->count(); ++ i )
3552  mCopiedMessages << list->at( i )->getMsgSerNum();
3553  mMoveMessages = true;
3554  updateActions();
3555  triggerUpdate();
3556 }
3557 
3558 void KMHeaders::pasteMessages()
3559 {
3560  new MessageCopyHelper( mCopiedMessages, folder(), mMoveMessages, TQT_TQOBJECT(this) );
3561  if ( mMoveMessages ) {
3562  mCopiedMessages.clear();
3563  updateActions();
3564  }
3565 }
3566 
3567 void KMHeaders::updateActions()
3568 {
3569  KAction *copy = owner()->action( "copy_messages" );
3570  KAction *cut = owner()->action( "cut_messages" );
3571  KAction *paste = owner()->action( "paste_messages" );
3572 
3573  if ( selectedItems().isEmpty() ) {
3574  copy->setEnabled( false );
3575  cut->setEnabled( false );
3576  } else {
3577  copy->setEnabled( true );
3578  if ( folder() && !folder()->canDeleteMessages() )
3579  cut->setEnabled( false );
3580  else
3581  cut->setEnabled( true );
3582  }
3583 
3584  if ( mCopiedMessages.isEmpty() || !folder() || folder()->isReadOnly() )
3585  paste->setEnabled( false );
3586  else
3587  paste->setEnabled( true );
3588 }
3589 
3590 void KMHeaders::setCopiedMessages(const TQValueList< TQ_UINT32 > & msgs, bool move)
3591 {
3592  mCopiedMessages = msgs;
3593  mMoveMessages = move;
3594  updateActions();
3595 }
3596 
3597 bool KMHeaders::isMessageCut(TQ_UINT32 serNum) const
3598 {
3599  return mMoveMessages && mCopiedMessages.contains( serNum );
3600 }
3601 
3602 TQValueList< TQ_UINT32 > KMHeaders::selectedSernums()
3603 {
3604  TQValueList<TQ_UINT32> list;
3605  for ( TQListViewItemIterator it(this); it.current(); it++ ) {
3606  if ( it.current()->isSelected() && it.current()->isVisible() ) {
3607  HeaderItem* item = static_cast<HeaderItem*>( it.current() );
3608  KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
3609  if ( msgBase ) {
3610  list.append( msgBase->getMsgSerNum() );
3611  }
3612  }
3613  }
3614  return list;
3615 }
3616 
3617 TQValueList< TQ_UINT32 > KMHeaders::selectedVisibleSernums()
3618 {
3619  TQValueList<TQ_UINT32> list;
3620  TQListViewItemIterator it(this, TQListViewItemIterator::Selected|TQListViewItemIterator::Visible);
3621  while( it.current() ) {
3622  if ( it.current()->isSelected() && it.current()->isVisible() ) {
3623  if ( it.current()->parent() && ( !it.current()->parent()->isOpen() ) ) {
3624  // the item's parent is closed, don't traverse any more of this subtree
3625  TQListViewItem * lastAncestorWithSiblings = it.current()->parent();
3626  // travel towards the root until we find an ancestor with siblings
3627  while ( ( lastAncestorWithSiblings->depth() > 0 ) && !lastAncestorWithSiblings->nextSibling() )
3628  lastAncestorWithSiblings = lastAncestorWithSiblings->parent();
3629  // move the iterator to that ancestor's next sibling
3630  it = TQListViewItemIterator( lastAncestorWithSiblings->nextSibling() );
3631  continue;
3632  }
3633  HeaderItem *item = static_cast<HeaderItem*>(it.current());
3634  KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
3635  if ( msgBase ) {
3636  list.append( msgBase->getMsgSerNum() );
3637  }
3638  }
3639  ++it;
3640  }
3641 
3642  return list;
3643 }
3644 
3645 #include "kmheaders.moc"