You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3646 lines
126 KiB
3646 lines
126 KiB
// -*- mode: C++; c-file-style: "gnu" -*-
|
|
// kmheaders.cpp
|
|
|
|
#include <config.h>
|
|
|
|
#include "kmheaders.h"
|
|
#include "headeritem.h"
|
|
using KMail::HeaderItem;
|
|
|
|
#include "kcursorsaver.h"
|
|
#include "kmcommands.h"
|
|
#include "kmmainwidget.h"
|
|
#include "kmfiltermgr.h"
|
|
#include "undostack.h"
|
|
#include "kmmsgdict.h"
|
|
#include "kmdebug.h"
|
|
#include "kmfoldertree.h"
|
|
#include "folderjob.h"
|
|
using KMail::FolderJob;
|
|
#include "actionscheduler.h"
|
|
using KMail::ActionScheduler;
|
|
#include "messagecopyhelper.h"
|
|
using KMail::MessageCopyHelper;
|
|
#include "broadcaststatus.h"
|
|
using KPIM::BroadcasStatus;
|
|
#include "progressmanager.h"
|
|
using KPIM::ProgressManager;
|
|
using KPIM::ProgressItem;
|
|
#include <maillistdrag.h>
|
|
#include "globalsettings.h"
|
|
using namespace KPIM;
|
|
#include "messageactions.h"
|
|
|
|
#include <kapplication.h>
|
|
#include <kaccelmanager.h>
|
|
#include <kglobalsettings.h>
|
|
#include <kmessagebox.h>
|
|
#include <kiconloader.h>
|
|
#include <kpopupmenu.h>
|
|
#include <kimageio.h>
|
|
#include <kconfig.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
|
|
#include <tqbuffer.h>
|
|
#include <tqeventloop.h>
|
|
#include <tqfile.h>
|
|
#include <tqheader.h>
|
|
#include <tqptrstack.h>
|
|
#include <tqptrqueue.h>
|
|
#include <tqpainter.h>
|
|
#include <tqtextcodec.h>
|
|
#include <tqstyle.h>
|
|
#include <tqlistview.h>
|
|
|
|
#include <mimelib/enum.h>
|
|
#include <mimelib/field.h>
|
|
#include <mimelib/mimepp.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
|
|
#include "textsource.h"
|
|
|
|
TQPixmap* KMHeaders::pixNew = 0;
|
|
TQPixmap* KMHeaders::pixUns = 0;
|
|
TQPixmap* KMHeaders::pixDel = 0;
|
|
TQPixmap* KMHeaders::pixRead = 0;
|
|
TQPixmap* KMHeaders::pixRep = 0;
|
|
TQPixmap* KMHeaders::pixQueued = 0;
|
|
TQPixmap* KMHeaders::pixTodo = 0;
|
|
TQPixmap* KMHeaders::pixSent = 0;
|
|
TQPixmap* KMHeaders::pixFwd = 0;
|
|
TQPixmap* KMHeaders::pixFlag = 0;
|
|
TQPixmap* KMHeaders::pixWatched = 0;
|
|
TQPixmap* KMHeaders::pixIgnored = 0;
|
|
TQPixmap* KMHeaders::pixSpam = 0;
|
|
TQPixmap* KMHeaders::pixHam = 0;
|
|
TQPixmap* KMHeaders::pixFullySigned = 0;
|
|
TQPixmap* KMHeaders::pixPartiallySigned = 0;
|
|
TQPixmap* KMHeaders::pixUndefinedSigned = 0;
|
|
TQPixmap* KMHeaders::pixFullyEncrypted = 0;
|
|
TQPixmap* KMHeaders::pixPartiallyEncrypted = 0;
|
|
TQPixmap* KMHeaders::pixUndefinedEncrypted = 0;
|
|
TQPixmap* KMHeaders::pixEncryptionProblematic = 0;
|
|
TQPixmap* KMHeaders::pixSignatureProblematic = 0;
|
|
TQPixmap* KMHeaders::pixAttachment = 0;
|
|
TQPixmap* KMHeaders::pixInvitation = 0;
|
|
TQPixmap* KMHeaders::pixReadFwd = 0;
|
|
TQPixmap* KMHeaders::pixReadReplied = 0;
|
|
TQPixmap* KMHeaders::pixReadFwdReplied = 0;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMHeaders::KMHeaders(KMMainWidget *aOwner, TQWidget *parent,
|
|
const char *name) :
|
|
KListView( parent, name ),
|
|
mIgnoreSortOrderChanges( false )
|
|
{
|
|
static bool pixmapsLoaded = false;
|
|
//qInitImageIO();
|
|
KImageIO::registerFormats();
|
|
mOwner = aOwner;
|
|
mFolder = 0;
|
|
noRepaint = false;
|
|
getMsgIndex = -1;
|
|
mTopItem = 0;
|
|
setSelectionMode( TQListView::Extended );
|
|
setAllColumnsShowFocus( true );
|
|
mNested = false;
|
|
nestingPolicy = OpenUnread;
|
|
mNestedOverride = false;
|
|
mSubjThreading = true;
|
|
mMousePressed = false;
|
|
mSortInfo.dirty = true;
|
|
mSortInfo.fakeSort = 0;
|
|
mSortInfo.removed = 0;
|
|
mSortInfo.column = 0;
|
|
mSortCol = 2; // 2 == date
|
|
mSortDescending = false;
|
|
mSortInfo.ascending = false;
|
|
mReaderWindowActive = false;
|
|
mRoot = new SortCacheItem;
|
|
mRoot->setId(-666); //mark of the root!
|
|
setStyleDependantFrameWidth();
|
|
// popup-menu
|
|
header()->setClickEnabled(true);
|
|
header()->installEventFilter(this);
|
|
mPopup = new KPopupMenu(this);
|
|
mPopup->insertTitle(i18n("View Columns"));
|
|
mPopup->setCheckable(true);
|
|
mPopup->insertItem(i18n("Status"), KPaintInfo::COL_STATUS);
|
|
mPopup->insertItem(i18n("Important"), KPaintInfo::COL_IMPORTANT);
|
|
mPopup->insertItem(i18n("Action Item"), KPaintInfo::COL_TODO);
|
|
mPopup->insertItem(i18n("Attachment"), KPaintInfo::COL_ATTACHMENT);
|
|
mPopup->insertItem(i18n("Invitation"), KPaintInfo::COL_INVITATION);
|
|
mPopup->insertItem(i18n("Spam/Ham"), KPaintInfo::COL_SPAM_HAM);
|
|
mPopup->insertItem(i18n("Watched/Ignored"), KPaintInfo::COL_WATCHED_IGNORED);
|
|
mPopup->insertItem(i18n("Signature"), KPaintInfo::COL_SIGNED);
|
|
mPopup->insertItem(i18n("Encryption"), KPaintInfo::COL_CRYPTO);
|
|
mPopup->insertItem(i18n("Size"), KPaintInfo::COL_SIZE);
|
|
mPopup->insertItem(i18n("Receiver"), KPaintInfo::COL_RECEIVER);
|
|
|
|
connect(mPopup, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotToggleColumn(int)));
|
|
|
|
setShowSortIndicator(true);
|
|
setFocusPolicy( WheelFocus );
|
|
|
|
if (!pixmapsLoaded)
|
|
{
|
|
pixmapsLoaded = true;
|
|
pixNew = new TQPixmap( UserIcon( "kmmsgnew" ) );
|
|
pixUns = new TQPixmap( UserIcon( "kmmsgunseen" ) );
|
|
pixDel = new TQPixmap( UserIcon( "kmmsgdel" ) );
|
|
pixRead = new TQPixmap( UserIcon( "kmmsgread" ) );
|
|
pixRep = new TQPixmap( UserIcon( "kmmsgreplied" ) );
|
|
pixQueued = new TQPixmap( UserIcon( "kmmsgqueued" ) );
|
|
pixTodo = new TQPixmap( UserIcon( "kmmsgtodo" ) );
|
|
pixSent = new TQPixmap( UserIcon( "kmmsgsent" ) );
|
|
pixFwd = new TQPixmap( UserIcon( "kmmsgforwarded" ) );
|
|
pixFlag = new TQPixmap( UserIcon( "kmmsgflag" ) );
|
|
pixWatched = new TQPixmap( UserIcon( "kmmsgwatched" ) );
|
|
pixIgnored = new TQPixmap( UserIcon( "kmmsgignored" ) );
|
|
pixSpam = new TQPixmap( UserIcon( "kmmsgspam" ) );
|
|
pixHam = new TQPixmap( UserIcon( "kmmsgham" ) );
|
|
pixFullySigned = new TQPixmap( UserIcon( "kmmsgfullysigned" ) );
|
|
pixPartiallySigned = new TQPixmap( UserIcon( "kmmsgpartiallysigned" ) );
|
|
pixUndefinedSigned = new TQPixmap( UserIcon( "kmmsgundefinedsigned" ) );
|
|
pixFullyEncrypted = new TQPixmap( UserIcon( "kmmsgfullyencrypted" ) );
|
|
pixPartiallyEncrypted = new TQPixmap( UserIcon( "kmmsgpartiallyencrypted" ) );
|
|
pixUndefinedEncrypted = new TQPixmap( UserIcon( "kmmsgundefinedencrypted" ) );
|
|
pixEncryptionProblematic = new TQPixmap( UserIcon( "kmmsgencryptionproblematic" ) );
|
|
pixSignatureProblematic = new TQPixmap( UserIcon( "kmmsgsignatureproblematic" ) );
|
|
pixAttachment = new TQPixmap( UserIcon( "kmmsgattachment" ) );
|
|
pixInvitation = new TQPixmap( UserIcon( "kmmsginvitation" ) );
|
|
pixReadFwd = new TQPixmap( UserIcon( "kmmsgread_fwd" ) );
|
|
pixReadReplied = new TQPixmap( UserIcon( "kmmsgread_replied" ) );
|
|
pixReadFwdReplied = new TQPixmap( UserIcon( "kmmsgread_fwd_replied" ) );
|
|
}
|
|
|
|
header()->setStretchEnabled( false );
|
|
header()->setResizeEnabled( false );
|
|
|
|
mPaintInfo.subCol = addColumn( i18n("Subject"), 310 );
|
|
mPaintInfo.senderCol = addColumn( i18n("Sender"), 170 );
|
|
mPaintInfo.dateCol = addColumn( i18n("Date"), 170 );
|
|
mPaintInfo.sizeCol = addColumn( i18n("Size"), 0 );
|
|
mPaintInfo.receiverCol = addColumn( i18n("Receiver"), 0 );
|
|
|
|
mPaintInfo.statusCol = addColumn( *pixNew , "", 0 );
|
|
mPaintInfo.importantCol = addColumn( *pixFlag , "", 0 );
|
|
mPaintInfo.todoCol = addColumn( *pixTodo , "", 0 );
|
|
mPaintInfo.attachmentCol = addColumn( *pixAttachment , "", 0 );
|
|
mPaintInfo.invitationCol = addColumn( *pixInvitation , "", 0 );
|
|
mPaintInfo.spamHamCol = addColumn( *pixSpam , "", 0 );
|
|
mPaintInfo.watchedIgnoredCol = addColumn( *pixWatched , "", 0 );
|
|
mPaintInfo.signedCol = addColumn( *pixFullySigned , "", 0 );
|
|
mPaintInfo.cryptoCol = addColumn( *pixFullyEncrypted, "", 0 );
|
|
|
|
setResizeMode( TQListView::NoColumn );
|
|
|
|
// only the non-optional columns shall be resizeable
|
|
header()->setResizeEnabled( true, mPaintInfo.subCol );
|
|
header()->setResizeEnabled( true, mPaintInfo.senderCol );
|
|
header()->setResizeEnabled( true, mPaintInfo.dateCol );
|
|
|
|
connect( this, TQT_SIGNAL( contextMenuRequested( TQListViewItem*, const TQPoint &, int )),
|
|
this, TQT_SLOT( rightButtonPressed( TQListViewItem*, const TQPoint &, int )));
|
|
connect(this, TQT_SIGNAL(doubleClicked(TQListViewItem*)),
|
|
this,TQT_SLOT(selectMessage(TQListViewItem*)));
|
|
connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
resetCurrentTime();
|
|
|
|
mSubjectLists.setAutoDelete( true );
|
|
|
|
mMoveMessages = false;
|
|
connect( this, TQT_SIGNAL(selectionChanged()), TQT_SLOT(updateActions()) );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMHeaders::~KMHeaders ()
|
|
{
|
|
if (mFolder)
|
|
{
|
|
writeFolderConfig();
|
|
writeSortOrder();
|
|
mFolder->close("kmheaders");
|
|
}
|
|
writeConfig();
|
|
delete mRoot;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMHeaders::eventFilter ( TQObject *o, TQEvent *e )
|
|
{
|
|
if ( e->type() == TQEvent::MouseButtonPress &&
|
|
static_cast<TQMouseEvent*>(e)->button() == RightButton &&
|
|
o->isA(TQHEADER_OBJECT_NAME_STRING) )
|
|
{
|
|
// if we currently only show one of either sender/receiver column
|
|
// modify the popup text in the way, that it displays the text of the other of the two
|
|
if ( mPaintInfo.showReceiver )
|
|
mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
|
|
else
|
|
if ( mFolder && (mFolder->whoField().lower() == "to") )
|
|
mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Sender"));
|
|
else
|
|
mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
|
|
|
|
mPopup->popup( static_cast<TQMouseEvent*>(e)->globalPos() );
|
|
return true;
|
|
}
|
|
return KListView::eventFilter(o, e);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KMHeaders::slotToggleColumn(int id, int mode)
|
|
{
|
|
bool *show = 0;
|
|
int *col = 0;
|
|
int width = 0;
|
|
int moveToCol = -1;
|
|
|
|
switch ( static_cast<KPaintInfo::ColumnIds>(id) )
|
|
{
|
|
case KPaintInfo::COL_SIZE:
|
|
{
|
|
show = &mPaintInfo.showSize;
|
|
col = &mPaintInfo.sizeCol;
|
|
width = 80;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_ATTACHMENT:
|
|
{
|
|
show = &mPaintInfo.showAttachment;
|
|
col = &mPaintInfo.attachmentCol;
|
|
width = pixAttachment->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_INVITATION:
|
|
{
|
|
show = &mPaintInfo.showInvitation;
|
|
col = &mPaintInfo.invitationCol;
|
|
width = pixAttachment->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_IMPORTANT:
|
|
{
|
|
show = &mPaintInfo.showImportant;
|
|
col = &mPaintInfo.importantCol;
|
|
width = pixFlag->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_TODO:
|
|
{
|
|
show = &mPaintInfo.showTodo;
|
|
col = &mPaintInfo.todoCol;
|
|
width = pixTodo->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_SPAM_HAM:
|
|
{
|
|
show = &mPaintInfo.showSpamHam;
|
|
col = &mPaintInfo.spamHamCol;
|
|
width = pixSpam->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_WATCHED_IGNORED:
|
|
{
|
|
show = &mPaintInfo.showWatchedIgnored;
|
|
col = &mPaintInfo.watchedIgnoredCol;
|
|
width = pixWatched->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_STATUS:
|
|
{
|
|
show = &mPaintInfo.showStatus;
|
|
col = &mPaintInfo.statusCol;
|
|
width = pixNew->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_SIGNED:
|
|
{
|
|
show = &mPaintInfo.showSigned;
|
|
col = &mPaintInfo.signedCol;
|
|
width = pixFullySigned->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_CRYPTO:
|
|
{
|
|
show = &mPaintInfo.showCrypto;
|
|
col = &mPaintInfo.cryptoCol;
|
|
width = pixFullyEncrypted->width() + 8;
|
|
if ( *col == header()->mapToIndex( *col ) )
|
|
moveToCol = 0;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_RECEIVER:
|
|
{
|
|
show = &mPaintInfo.showReceiver;
|
|
col = &mPaintInfo.receiverCol;
|
|
width = 170;
|
|
break;
|
|
}
|
|
case KPaintInfo::COL_SCORE: ; // only used by KNode
|
|
// don't use default, so that the compiler tells us you forgot to code here for a new column
|
|
}
|
|
|
|
assert(show);
|
|
|
|
if (mode == -1)
|
|
*show = !*show;
|
|
else
|
|
*show = mode;
|
|
|
|
mPopup->setItemChecked(id, *show);
|
|
|
|
if (*show) {
|
|
header()->setResizeEnabled(true, *col);
|
|
setColumnWidth(*col, width);
|
|
if ( moveToCol >= 0 )
|
|
header()->moveSection( *col, moveToCol );
|
|
}
|
|
else {
|
|
header()->setResizeEnabled(false, *col);
|
|
header()->setStretchEnabled(false, *col);
|
|
hideColumn(*col);
|
|
}
|
|
|
|
// if we change the visibility of the receiver column,
|
|
// the sender column has to show either the sender or the receiver
|
|
if ( static_cast<KPaintInfo::ColumnIds>(id) == KPaintInfo::COL_RECEIVER ) {
|
|
TQString colText = i18n( "Sender" );
|
|
if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
|
|
colText = i18n( "Receiver" );
|
|
setColumnText( mPaintInfo.senderCol, colText );
|
|
}
|
|
|
|
if (mode == -1)
|
|
writeConfig();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Support for backing pixmap
|
|
void KMHeaders::paintEmptyArea( TQPainter * p, const TQRect & rect )
|
|
{
|
|
if (mPaintInfo.pixmapOn)
|
|
p->drawTiledPixmap( rect.left(), rect.top(), rect.width(), rect.height(),
|
|
mPaintInfo.pixmap,
|
|
rect.left() + contentsX(),
|
|
rect.top() + contentsY() );
|
|
else
|
|
p->fillRect( rect, tqcolorGroup().base() );
|
|
}
|
|
|
|
bool KMHeaders::event(TQEvent *e)
|
|
{
|
|
bool result = KListView::event(e);
|
|
if (e->type() == TQEvent::ApplicationPaletteChange)
|
|
{
|
|
readColorConfig();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::readColorConfig (void)
|
|
{
|
|
KConfig* config = KMKernel::config();
|
|
// Custom/System colors
|
|
KConfigGroupSaver saver(config, "Reader");
|
|
TQColor c1=TQColor(kapp->palette().active().text());
|
|
TQColor c2=TQColor("red");
|
|
TQColor c3=TQColor("blue");
|
|
TQColor c4=TQColor(kapp->palette().active().base());
|
|
TQColor c5=TQColor(0,0x7F,0);
|
|
TQColor c6=TQColor(0,0x98,0);
|
|
TQColor c7=KGlobalSettings::alternateBackgroundColor();
|
|
|
|
if (!config->readBoolEntry("defaultColors",true)) {
|
|
mPaintInfo.colFore = config->readColorEntry("ForegroundColor",&c1);
|
|
mPaintInfo.colBack = config->readColorEntry("BackgroundColor",&c4);
|
|
TQPalette newPal = kapp->palette();
|
|
newPal.setColor( TQColorGroup::Base, mPaintInfo.colBack );
|
|
newPal.setColor( TQColorGroup::Text, mPaintInfo.colFore );
|
|
setPalette( newPal );
|
|
mPaintInfo.colNew = config->readColorEntry("NewMessage",&c2);
|
|
mPaintInfo.colUnread = config->readColorEntry("UnreadMessage",&c3);
|
|
mPaintInfo.colFlag = config->readColorEntry("FlagMessage",&c5);
|
|
mPaintInfo.colTodo = config->readColorEntry("TodoMessage",&c6);
|
|
c7 = config->readColorEntry("AltBackgroundColor",&c7);
|
|
}
|
|
else {
|
|
mPaintInfo.colFore = c1;
|
|
mPaintInfo.colBack = c4;
|
|
TQPalette newPal = kapp->palette();
|
|
newPal.setColor( TQColorGroup::Base, c4 );
|
|
newPal.setColor( TQColorGroup::Text, c1 );
|
|
setPalette( newPal );
|
|
mPaintInfo.colNew = c2;
|
|
mPaintInfo.colUnread = c3;
|
|
mPaintInfo.colFlag = c5;
|
|
mPaintInfo.colTodo = c6;
|
|
}
|
|
setAlternateBackground(c7);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::readConfig (void)
|
|
{
|
|
KConfig* config = KMKernel::config();
|
|
|
|
// Backing pixmap support
|
|
{ // area for config group "Pixmaps"
|
|
KConfigGroupSaver saver(config, "Pixmaps");
|
|
TQString pixmapFile = config->readEntry("Headers");
|
|
mPaintInfo.pixmapOn = false;
|
|
if (!pixmapFile.isEmpty()) {
|
|
mPaintInfo.pixmapOn = true;
|
|
mPaintInfo.pixmap = TQPixmap( pixmapFile );
|
|
}
|
|
}
|
|
|
|
{ // area for config group "General"
|
|
KConfigGroupSaver saver(config, "General");
|
|
bool show = config->readBoolEntry("showMessageSize");
|
|
slotToggleColumn(KPaintInfo::COL_SIZE, show);
|
|
|
|
show = config->readBoolEntry("showAttachmentColumn");
|
|
slotToggleColumn(KPaintInfo::COL_ATTACHMENT, show);
|
|
|
|
show = config->readBoolEntry("showInvitationColumn");
|
|
slotToggleColumn(KPaintInfo::COL_INVITATION, show);
|
|
|
|
show = config->readBoolEntry("showImportantColumn");
|
|
slotToggleColumn(KPaintInfo::COL_IMPORTANT, show);
|
|
|
|
show = config->readBoolEntry("showTodoColumn");
|
|
slotToggleColumn(KPaintInfo::COL_TODO, show);
|
|
|
|
show = config->readBoolEntry("showSpamHamColumn");
|
|
slotToggleColumn(KPaintInfo::COL_SPAM_HAM, show);
|
|
|
|
show = config->readBoolEntry("showWatchedIgnoredColumn");
|
|
slotToggleColumn(KPaintInfo::COL_WATCHED_IGNORED, show);
|
|
|
|
show = config->readBoolEntry("showStatusColumn");
|
|
slotToggleColumn(KPaintInfo::COL_STATUS, show);
|
|
|
|
show = config->readBoolEntry("showSignedColumn");
|
|
slotToggleColumn(KPaintInfo::COL_SIGNED, show);
|
|
|
|
show = config->readBoolEntry("showCryptoColumn");
|
|
slotToggleColumn(KPaintInfo::COL_CRYPTO, show);
|
|
|
|
show = config->readBoolEntry("showReceiverColumn");
|
|
slotToggleColumn(KPaintInfo::COL_RECEIVER, show);
|
|
|
|
mPaintInfo.showCryptoIcons = config->readBoolEntry( "showCryptoIcons", false );
|
|
mPaintInfo.showAttachmentIcon = config->readBoolEntry( "showAttachmentIcon", true );
|
|
mPaintInfo.showInvitationIcon = config->readBoolEntry( "showInvitationIcon", false );
|
|
|
|
KMime::DateFormatter::FormatType t =
|
|
(KMime::DateFormatter::FormatType) config->readNumEntry("dateFormat", KMime::DateFormatter::Fancy ) ;
|
|
mDate.setCustomFormat( config->readEntry("customDateFormat") );
|
|
mDate.setFormat( t );
|
|
}
|
|
|
|
readColorConfig();
|
|
|
|
// Custom/System fonts
|
|
{ // area for config group "General"
|
|
KConfigGroupSaver saver(config, "Fonts");
|
|
if (!(config->readBoolEntry("defaultFonts",true)))
|
|
{
|
|
TQFont listFont( KGlobalSettings::generalFont() );
|
|
listFont = config->readFontEntry( "list-font", &listFont );
|
|
setFont( listFont );
|
|
mNewFont = config->readFontEntry( "list-new-font", &listFont );
|
|
mUnreadFont = config->readFontEntry( "list-unread-font", &listFont );
|
|
mImportantFont = config->readFontEntry( "list-important-font", &listFont );
|
|
mTodoFont = config->readFontEntry( "list-todo-font", &listFont );
|
|
mDateFont = KGlobalSettings::fixedFont();
|
|
mDateFont = config->readFontEntry( "list-date-font", &mDateFont );
|
|
} else {
|
|
mNewFont= mUnreadFont = mImportantFont = mDateFont = mTodoFont =
|
|
KGlobalSettings::generalFont();
|
|
setFont( mDateFont );
|
|
}
|
|
}
|
|
|
|
// Behavior
|
|
{
|
|
KConfigGroupSaver saver(config, "Geometry");
|
|
mReaderWindowActive = config->readEntry( "readerWindowMode", "below" ) != "hide";
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::restoreColumnLayout( KConfig *config, const TQString &group )
|
|
{
|
|
// KListView::restoreLayout() will call setSorting(), which is reimplemented by us.
|
|
// We don't want to change the sort order, so we set a flag here that is checked in
|
|
// setSorting().
|
|
mIgnoreSortOrderChanges = true;
|
|
restoreLayout( config, group );
|
|
mIgnoreSortOrderChanges = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::reset()
|
|
{
|
|
int top = topItemIndex();
|
|
int id = currentItemIndex();
|
|
noRepaint = true;
|
|
clear();
|
|
TQString colText = i18n( "Sender" );
|
|
if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
|
|
colText = i18n( "Receiver" );
|
|
setColumnText( mPaintInfo.senderCol, colText );
|
|
noRepaint = false;
|
|
mItems.resize(0);
|
|
updateMessageList();
|
|
setCurrentMsg(id);
|
|
setTopItemByIndex(top);
|
|
ensureCurrentItemVisible();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::refreshNestedState(void)
|
|
{
|
|
bool oldState = isThreaded();
|
|
NestingPolicy oldNestPolicy = nestingPolicy;
|
|
KConfig* config = KMKernel::config();
|
|
KConfigGroupSaver saver(config, "Geometry");
|
|
mNested = config->readBoolEntry( "nestedMessages", false );
|
|
|
|
nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
|
|
if ((nestingPolicy != oldNestPolicy) ||
|
|
(oldState != isThreaded()))
|
|
{
|
|
setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
|
|
reset();
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::readFolderConfig (void)
|
|
{
|
|
if (!mFolder) return;
|
|
KConfig* config = KMKernel::config();
|
|
|
|
KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
|
|
mNestedOverride = config->readBoolEntry( "threadMessagesOverride", false );
|
|
mSortCol = config->readNumEntry("SortColumn", mSortCol+1 /* inited to date column */);
|
|
mSortDescending = (mSortCol < 0);
|
|
mSortCol = abs(mSortCol) - 1;
|
|
|
|
mTopItem = config->readNumEntry("Top", 0);
|
|
mCurrentItem = config->readNumEntry("Current", 0);
|
|
mCurrentItemSerNum = config->readNumEntry("CurrentSerialNum", 0);
|
|
|
|
mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", false );
|
|
mPaintInfo.status = config->readBoolEntry( "Status", false );
|
|
|
|
{ //area for config group "Geometry"
|
|
KConfigGroupSaver saver(config, "Geometry");
|
|
mNested = config->readBoolEntry( "nestedMessages", false );
|
|
nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
|
|
}
|
|
|
|
setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
|
|
mSubjThreading = config->readBoolEntry( "threadMessagesBySubject", true );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::writeFolderConfig (void)
|
|
{
|
|
if (!mFolder) return;
|
|
KConfig* config = KMKernel::config();
|
|
int mSortColAdj = mSortCol + 1;
|
|
|
|
KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
|
|
config->writeEntry("SortColumn", (mSortDescending ? -mSortColAdj : mSortColAdj));
|
|
config->writeEntry("Top", topItemIndex());
|
|
config->writeEntry("Current", currentItemIndex());
|
|
HeaderItem* current = currentHeaderItem();
|
|
ulong sernum = 0;
|
|
if ( current && mFolder->getMsgBase( current->msgId() ) )
|
|
sernum = mFolder->getMsgBase( current->msgId() )->getMsgSerNum();
|
|
config->writeEntry("CurrentSerialNum", sernum);
|
|
|
|
config->writeEntry("OrderOfArrival", mPaintInfo.orderOfArrival);
|
|
config->writeEntry("Status", mPaintInfo.status);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::writeConfig (void)
|
|
{
|
|
KConfig* config = KMKernel::config();
|
|
saveLayout(config, "Header-Geometry");
|
|
KConfigGroupSaver saver(config, "General");
|
|
config->writeEntry("showMessageSize" , mPaintInfo.showSize);
|
|
config->writeEntry("showAttachmentColumn" , mPaintInfo.showAttachment);
|
|
config->writeEntry("showInvitationColumn" , mPaintInfo.showInvitation);
|
|
config->writeEntry("showImportantColumn" , mPaintInfo.showImportant);
|
|
config->writeEntry("showTodoColumn" , mPaintInfo.showTodo);
|
|
config->writeEntry("showSpamHamColumn" , mPaintInfo.showSpamHam);
|
|
config->writeEntry("showWatchedIgnoredColumn", mPaintInfo.showWatchedIgnored);
|
|
config->writeEntry("showStatusColumn" , mPaintInfo.showStatus);
|
|
config->writeEntry("showSignedColumn" , mPaintInfo.showSigned);
|
|
config->writeEntry("showCryptoColumn" , mPaintInfo.showCrypto);
|
|
config->writeEntry("showReceiverColumn" , mPaintInfo.showReceiver);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setFolder( KMFolder *aFolder, bool forceJumpToUnread )
|
|
{
|
|
CREATE_TIMER(set_folder);
|
|
START_TIMER(set_folder);
|
|
|
|
int id;
|
|
TQString str;
|
|
|
|
mSortInfo.fakeSort = 0;
|
|
if ( mFolder && static_cast<KMFolder*>(mFolder) == aFolder ) {
|
|
int top = topItemIndex();
|
|
id = currentItemIndex();
|
|
writeFolderConfig();
|
|
readFolderConfig();
|
|
updateMessageList(); // do not change the selection
|
|
setCurrentMsg(id);
|
|
setTopItemByIndex(top);
|
|
} else {
|
|
if (mFolder) {
|
|
// WABA: Make sure that no KMReaderWin is still using a msg
|
|
// from this folder, since it's msg's are about to be deleted.
|
|
highlightMessage(0, false);
|
|
|
|
disconnect(mFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder*)),
|
|
this, TQT_SLOT(setFolderInfoStatus()));
|
|
|
|
mFolder->markNewAsUnread();
|
|
writeFolderConfig();
|
|
disconnect(mFolder, TQT_SIGNAL(msgHeaderChanged(KMFolder*,int)),
|
|
this, TQT_SLOT(msgHeaderChanged(KMFolder*,int)));
|
|
disconnect(mFolder, TQT_SIGNAL(msgAdded(int)),
|
|
this, TQT_SLOT(msgAdded(int)));
|
|
disconnect(mFolder, TQT_SIGNAL( msgRemoved( int, TQString ) ),
|
|
this, TQT_SLOT( msgRemoved( int, TQString ) ) );
|
|
disconnect(mFolder, TQT_SIGNAL(changed()),
|
|
this, TQT_SLOT(msgChanged()));
|
|
disconnect(mFolder, TQT_SIGNAL(cleared()),
|
|
this, TQT_SLOT(folderCleared()));
|
|
disconnect(mFolder, TQT_SIGNAL(expunged( KMFolder* )),
|
|
this, TQT_SLOT(folderCleared()));
|
|
disconnect(mFolder, TQT_SIGNAL(closed()),
|
|
this, TQT_SLOT(folderClosed()));
|
|
disconnect( mFolder, TQT_SIGNAL( statusMsg( const TQString& ) ),
|
|
BroadcasStatus::instance(), TQT_SLOT( seStatusMsg( const TQString& ) ) );
|
|
disconnect(mFolder, TQT_SIGNAL(viewConfigChanged()), this, TQT_SLOT(reset()));
|
|
writeSortOrder();
|
|
mFolder->close("kmheaders");
|
|
// System folders remain open but we also should write the index from
|
|
// time to time
|
|
if (mFolder->dirty()) mFolder->writeIndex();
|
|
}
|
|
|
|
mSortInfo.removed = 0;
|
|
mFolder = aFolder;
|
|
mSortInfo.dirty = true;
|
|
|
|
mOwner->useAction()->setEnabled( mFolder ?
|
|
( kmkernel->folderIsTemplates( mFolder ) ) : false );
|
|
mOwner->messageActions()->replyListAction()->setEnabled( mFolder ?
|
|
mFolder->isMailingListEnabled() : false );
|
|
if ( mFolder ) {
|
|
connect(mFolder, TQT_SIGNAL(msgHeaderChanged(KMFolder*,int)),
|
|
this, TQT_SLOT(msgHeaderChanged(KMFolder*,int)));
|
|
connect(mFolder, TQT_SIGNAL(msgAdded(int)),
|
|
this, TQT_SLOT(msgAdded(int)));
|
|
connect(mFolder, TQT_SIGNAL(msgRemoved(int,TQString)),
|
|
this, TQT_SLOT(msgRemoved(int,TQString)));
|
|
connect(mFolder, TQT_SIGNAL(changed()),
|
|
this, TQT_SLOT(msgChanged()));
|
|
connect(mFolder, TQT_SIGNAL(cleared()),
|
|
this, TQT_SLOT(folderCleared()));
|
|
connect(mFolder, TQT_SIGNAL(expunged( KMFolder* )),
|
|
this, TQT_SLOT(folderCleared()));
|
|
connect(mFolder, TQT_SIGNAL(closed()),
|
|
this, TQT_SLOT(folderClosed()));
|
|
connect(mFolder, TQT_SIGNAL(statusMsg(const TQString&)),
|
|
BroadcasStatus::instance(), TQT_SLOT( seStatusMsg( const TQString& ) ) );
|
|
connect(mFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder*)),
|
|
this, TQT_SLOT(setFolderInfoStatus()));
|
|
connect(mFolder, TQT_SIGNAL(viewConfigChanged()), this, TQT_SLOT(reset()));
|
|
|
|
// Not very nice, but if we go from nested to non-nested
|
|
// in the folderConfig below then we need to do this otherwise
|
|
// updateMessageList would do something unspeakable
|
|
if (isThreaded()) {
|
|
noRepaint = true;
|
|
clear();
|
|
noRepaint = false;
|
|
mItems.resize( 0 );
|
|
}
|
|
|
|
readFolderConfig();
|
|
|
|
CREATE_TIMER(kmfolder_open);
|
|
START_TIMER(kmfolder_open);
|
|
mFolder->open("kmheaders");
|
|
END_TIMER(kmfolder_open);
|
|
SHOW_TIMER(kmfolder_open);
|
|
|
|
if (isThreaded()) {
|
|
noRepaint = true;
|
|
clear();
|
|
noRepaint = false;
|
|
mItems.resize( 0 );
|
|
}
|
|
}
|
|
|
|
CREATE_TIMER(updateMsg);
|
|
START_TIMER(updateMsg);
|
|
updateMessageList(true, forceJumpToUnread);
|
|
END_TIMER(updateMsg);
|
|
SHOW_TIMER(updateMsg);
|
|
makeHeaderVisible();
|
|
setFolderInfoStatus();
|
|
|
|
TQString colText = i18n( "Sender" );
|
|
if (mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
|
|
colText = i18n("Receiver");
|
|
setColumnText( mPaintInfo.senderCol, colText);
|
|
|
|
colText = i18n( "Date" );
|
|
if (mPaintInfo.orderOfArrival)
|
|
colText = i18n( "Order of Arrival" );
|
|
setColumnText( mPaintInfo.dateCol, colText);
|
|
|
|
colText = i18n( "Subject" );
|
|
if (mPaintInfo.status)
|
|
colText = colText + i18n( " (Status)" );
|
|
setColumnText( mPaintInfo.subCol, colText);
|
|
}
|
|
|
|
updateActions();
|
|
|
|
END_TIMER(set_folder);
|
|
SHOW_TIMER(set_folder);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::msgChanged()
|
|
{
|
|
if (mFolder->count() == 0) { // Folder cleared
|
|
mItems.resize(0);
|
|
clear();
|
|
return;
|
|
}
|
|
if (!isUpdatesEnabled()) return;
|
|
|
|
// Remember selected messages, current message and some scrollbar data, as we have to restore it
|
|
const TQValueList<int> oldSelectedItems = selectedItems();
|
|
const int oldCurrentItemIndex = currentItemIndex();
|
|
const bool scrollbarAtTop = verticalScrollBar() &&
|
|
verticalScrollBar()->value() == verticalScrollBar()->minValue();
|
|
const bool scrollbarAtBottom = verticalScrollBar() &&
|
|
verticalScrollBar()->value() == verticalScrollBar()->maxValue();
|
|
const HeaderItem * const oldFirstVisibleItem = dynamic_cast<HeaderItem*>( itemAt( TQPoint( 0, 0 ) ) );
|
|
const int oldOffsetOfFirstVisibleItem = tqitemRect( oldFirstVisibleItem ).y();
|
|
const uint oldSerNumOfFirstVisibleItem = oldFirstVisibleItem ? oldFirstVisibleItem->msgSerNum() : 0;
|
|
|
|
TQString msgIdMD5;
|
|
TQListViewItem *item = currentItem();
|
|
HeaderItem *hi = dynamic_cast<HeaderItem*>(item);
|
|
if (item && hi) {
|
|
// get the msgIdMD5 to compare it later
|
|
KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
|
|
if (mb)
|
|
msgIdMD5 = mb->msgIdMD5();
|
|
}
|
|
// if (!isUpdatesEnabled()) return;
|
|
// prevent IMAP messages from scrolling to top
|
|
disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
|
|
updateMessageList(); // do not change the selection
|
|
|
|
// Restore scrollbar state and selected and current messages
|
|
setCurrentMsg( oldCurrentItemIndex );
|
|
setSelectedByIndex( oldSelectedItems, true );
|
|
if ( scrollbarAtTop ) {
|
|
setContentsPos( 0, 0 );
|
|
} else if ( scrollbarAtBottom ) {
|
|
setContentsPos( 0, contentsHeight() );
|
|
} else if ( oldSerNumOfFirstVisibleItem > 0 ) {
|
|
for ( uint i = 0; i < mItems.size(); ++i ) {
|
|
const KMMsgBase * const mMsgBase = mFolder->getMsgBase( i );
|
|
if ( mMsgBase->getMsgSerNum() == oldSerNumOfFirstVisibleItem ) {
|
|
setContentsPos( 0, itemPos( mItems[i] ) - oldOffsetOfFirstVisibleItem );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
|
|
// if the current message has changed then emit
|
|
// the selected signal to force an update
|
|
|
|
// Normally the serial number of the message would be
|
|
// used to do this, but because we don't yet have
|
|
// guaranteed serial numbers for IMAP messages fall back
|
|
// to using the MD5 checksum of the msgId.
|
|
item = currentItem();
|
|
hi = dynamic_cast<HeaderItem*>(item);
|
|
if (item && hi) {
|
|
KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
|
|
if (mb) {
|
|
if (msgIdMD5.isEmpty() || (msgIdMD5 != mb->msgIdMD5()))
|
|
emit selected(mFolder->getMsg(hi->msgId()));
|
|
} else {
|
|
emit selected(0);
|
|
}
|
|
} else
|
|
emit selected(0);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::msgAdded(int id)
|
|
{
|
|
HeaderItem* hi = 0;
|
|
if (!isUpdatesEnabled()) return;
|
|
|
|
CREATE_TIMER(msgAdded);
|
|
START_TIMER(msgAdded);
|
|
|
|
assert( mFolder->getMsgBase( id ) ); // otherwise using count() is wrong
|
|
|
|
/* Create a new SortCacheItem to be used for threading. */
|
|
SortCacheItem *sci = new SortCacheItem;
|
|
sci->setId(id);
|
|
if (isThreaded()) {
|
|
// make sure the id and subject dicts grow, if necessary
|
|
if (mSortCacheItems.count() == (uint)mFolder->count()
|
|
|| mSortCacheItems.count() == 0) {
|
|
kdDebug (5006) << "KMHeaders::msgAdded - Resizing id and subject trees of " << mFolder->label()
|
|
<< ": before=" << mSortCacheItems.count() << " ,after=" << (mFolder->count()*2) << endl;
|
|
mSortCacheItems.resize(mFolder->count()*2);
|
|
mSubjectLists.resize(mFolder->count()*2);
|
|
}
|
|
TQString msgId = mFolder->getMsgBase(id)->msgIdMD5();
|
|
if (msgId.isNull())
|
|
msgId = "";
|
|
TQString replyToId = mFolder->getMsgBase(id)->replyToIdMD5();
|
|
|
|
SortCacheItem *parent = findParent( sci );
|
|
if (!parent && mSubjThreading) {
|
|
parent = findParentBySubject( sci );
|
|
if (parent && sci->isImperfectlyThreaded()) {
|
|
// The parent we found could be by subject, in which case it is
|
|
// possible, that it would be preferrable to thread it below us,
|
|
// not the other way around. Check that. This is not only
|
|
// cosmetic, as getting this wrong leads to circular threading.
|
|
if (msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToIdMD5()
|
|
|| msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToAuxIdMD5())
|
|
parent = NULL;
|
|
}
|
|
}
|
|
|
|
if (parent && mFolder->getMsgBase(parent->id())->isWatched())
|
|
mFolder->getMsgBase(id)->seStatus( KMMsgStatusWatched );
|
|
else if (parent && mFolder->getMsgBase(parent->id())->isIgnored())
|
|
mFolder->getMsgBase(id)->seStatus( KMMsgStatusIgnored );
|
|
if (parent)
|
|
hi = new HeaderItem( parent->item(), id );
|
|
else
|
|
hi = new HeaderItem( this, id );
|
|
|
|
// o/` ... my buddy and me .. o/`
|
|
hi->setSortCacheItem(sci);
|
|
sci->setItem(hi);
|
|
|
|
// Update and resize the id trees.
|
|
mItems.resize( mFolder->count() );
|
|
mItems[id] = hi;
|
|
|
|
if ( !msgId.isEmpty() )
|
|
mSortCacheItems.tqreplace(msgId, sci);
|
|
/* Add to the list of potential parents for subject threading. But only if
|
|
* we are top level. */
|
|
if (mSubjThreading && parent) {
|
|
TQString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
|
|
if (subjMD5.isEmpty()) {
|
|
mFolder->getMsgBase(id)->initStrippedSubjectMD5();
|
|
subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
|
|
}
|
|
if( !subjMD5.isEmpty()) {
|
|
if ( !mSubjectLists.find(subjMD5) )
|
|
mSubjectLists.insert(subjMD5, new TQPtrList<SortCacheItem>());
|
|
// insertion sort by date. See buildThreadTrees for details.
|
|
int p=0;
|
|
for (TQPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
|
|
it.current(); ++it) {
|
|
KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
|
|
if ( mb->date() < mFolder->getMsgBase(id)->date())
|
|
break;
|
|
p++;
|
|
}
|
|
mSubjectLists[subjMD5]->insert( p, sci);
|
|
sci->setSubjectThreadingList( mSubjectLists[subjMD5] );
|
|
}
|
|
}
|
|
// The message we just added might be a better parent for one of the as of
|
|
// yet imperfectly threaded messages. Let's find out.
|
|
|
|
/* In case the current item is taken during reparenting, prevent qlistview
|
|
* from selecting some unrelated item as a result of take() emitting
|
|
* currentChanged. */
|
|
disconnect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this, TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
|
|
if ( !msgId.isEmpty() ) {
|
|
TQPtrListIterator<HeaderItem> it(mImperfectlyThreadedList);
|
|
HeaderItem *cur;
|
|
while ( (cur = it.current()) ) {
|
|
++it;
|
|
int tryMe = cur->msgId();
|
|
// Check, whether our message is the replyToId or replyToAuxId of
|
|
// this one. If so, thread it below our message, unless it is already
|
|
// correctly threaded by replyToId.
|
|
bool perfectParent = true;
|
|
KMMsgBase *otherMsg = mFolder->getMsgBase(tryMe);
|
|
if ( !otherMsg ) {
|
|
kdDebug(5006) << "otherMsg is NULL !!! tryMe: " << tryMe << endl;
|
|
continue;
|
|
}
|
|
TQString otherId = otherMsg->replyToIdMD5();
|
|
if (msgId != otherId) {
|
|
if (msgId != otherMsg->replyToAuxIdMD5())
|
|
continue;
|
|
else {
|
|
if (!otherId.isEmpty() && mSortCacheItems.find(otherId))
|
|
continue;
|
|
else
|
|
// Thread below us by aux id, but keep on the list of
|
|
// imperfectly threaded messages.
|
|
perfectParent = false;
|
|
}
|
|
}
|
|
TQListViewItem *newParent = mItems[id];
|
|
TQListViewItem *msg = mItems[tryMe];
|
|
|
|
if (msg->parent())
|
|
msg->parent()->takeItem(msg);
|
|
else
|
|
takeItem(msg);
|
|
newParent->insertItem(msg);
|
|
HeaderItem *hi = static_cast<HeaderItem*>( newParent );
|
|
hi->sortCacheItem()->addSortedChild( cur->sortCacheItem() );
|
|
|
|
makeHeaderVisible();
|
|
|
|
if (perfectParent) {
|
|
mImperfectlyThreadedList.removeRef (mItems[tryMe]);
|
|
// The item was imperfectly thread before, now it's parent
|
|
// is there. Update the .sorted file accordingly.
|
|
TQString sortFile = KMAIL_SORT_FILE(mFolder);
|
|
FILE *sortStream = fopen(TQFile::encodeName(sortFile), "r+");
|
|
if (sortStream) {
|
|
mItems[tryMe]->sortCacheItem()->updateSortFile( sortStream, mFolder );
|
|
fclose (sortStream);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Add ourselves only now, to avoid circularity above.
|
|
if (hi && hi->sortCacheItem()->isImperfectlyThreaded())
|
|
mImperfectlyThreadedList.append(hi);
|
|
} else {
|
|
// non-threaded case
|
|
hi = new HeaderItem( this, id );
|
|
mItems.resize( mFolder->count() );
|
|
mItems[id] = hi;
|
|
// o/` ... my buddy and me .. o/`
|
|
hi->setSortCacheItem(sci);
|
|
sci->setItem(hi);
|
|
}
|
|
if (mSortInfo.fakeSort) {
|
|
TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
|
|
KListView::setSorting(mSortCol, !mSortDescending );
|
|
mSortInfo.fakeSort = 0;
|
|
}
|
|
appendItemToSortFile(hi); //inserted into sorted list
|
|
|
|
msgHeaderChanged(mFolder,id);
|
|
|
|
if ((childCount() == 1) && hi) {
|
|
setSelected( hi, true );
|
|
setCurrentItem( firstChild() );
|
|
setSelectionAnchor( currentItem() );
|
|
highlightMessage( currentItem() );
|
|
}
|
|
|
|
/* restore signal */
|
|
connect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this, TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
|
|
emit msgAddedToListView( hi );
|
|
END_TIMER(msgAdded);
|
|
SHOW_TIMER(msgAdded);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::msgRemoved(int id, TQString msgId )
|
|
{
|
|
if (!isUpdatesEnabled()) return;
|
|
|
|
if ((id < 0) || (id >= (int)mItems.size()))
|
|
return;
|
|
/*
|
|
* qlistview has its own ideas about what to select as the next
|
|
* item once this one is removed. Sine we have already selected
|
|
* something in prepare/finalizeMove that's counter productive
|
|
*/
|
|
disconnect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this, TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
|
|
HeaderItem *removedItem = mItems[id];
|
|
if (!removedItem) return;
|
|
HeaderItem *curItem = currentHeaderItem();
|
|
|
|
for (int i = id; i < (int)mItems.size() - 1; ++i) {
|
|
mItems[i] = mItems[i+1];
|
|
mItems[i]->setMsgId( i );
|
|
mItems[i]->sortCacheItem()->setId( i );
|
|
}
|
|
|
|
mItems.resize( mItems.size() - 1 );
|
|
|
|
if (isThreaded() && mFolder->count()) {
|
|
if ( !msgId.isEmpty() && mSortCacheItems[msgId] ) {
|
|
if (mSortCacheItems[msgId] == removedItem->sortCacheItem())
|
|
mSortCacheItems.remove(msgId);
|
|
}
|
|
// Remove the message from the list of potential parents for threading by
|
|
// subject.
|
|
if ( mSubjThreading && removedItem->sortCacheItem()->subjectThreadingList() )
|
|
removedItem->sortCacheItem()->subjectThreadingList()->removeRef( removedItem->sortCacheItem() );
|
|
|
|
// Reparent children of item.
|
|
TQListViewItem *myParent = removedItem;
|
|
TQListViewItem *myChild = myParent->firstChild();
|
|
TQListViewItem *threadRoot = myParent;
|
|
while (threadRoot->parent())
|
|
threadRoot = threadRoot->parent();
|
|
TQString key = static_cast<HeaderItem*>(threadRoot)->key(mSortCol, !mSortDescending);
|
|
|
|
TQPtrList<TQListViewItem> childList;
|
|
while (myChild) {
|
|
HeaderItem *item = static_cast<HeaderItem*>(myChild);
|
|
// Just keep the item at top level, if it will be deleted anyhow
|
|
if ( !item->aboutToBeDeleted() ) {
|
|
childList.append(myChild);
|
|
}
|
|
myChild = myChild->nextSibling();
|
|
if ( item->aboutToBeDeleted() ) {
|
|
myParent->takeItem( item );
|
|
insertItem( item );
|
|
mRoot->addSortedChild( item->sortCacheItem() );
|
|
}
|
|
item->setTempKey( key + item->key( mSortCol, !mSortDescending ));
|
|
if (mSortInfo.fakeSort) {
|
|
TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
|
|
KListView::setSorting(mSortCol, !mSortDescending );
|
|
mSortInfo.fakeSort = 0;
|
|
}
|
|
}
|
|
|
|
for (TQPtrListIterator<TQListViewItem> it(childList); it.current() ; ++it ) {
|
|
TQListViewItem *lvi = *it;
|
|
HeaderItem *item = static_cast<HeaderItem*>(lvi);
|
|
SortCacheItem *sci = item->sortCacheItem();
|
|
SortCacheItem *parent = findParent( sci );
|
|
if ( !parent && mSubjThreading )
|
|
parent = findParentBySubject( sci );
|
|
|
|
Q_ASSERT( !parent || parent->item() != removedItem );
|
|
myParent->takeItem(lvi);
|
|
if ( parent && parent->item() != item && parent->item() != removedItem ) {
|
|
parent->item()->insertItem(lvi);
|
|
parent->addSortedChild( sci );
|
|
} else {
|
|
insertItem(lvi);
|
|
mRoot->addSortedChild( sci );
|
|
}
|
|
|
|
if ((!parent || sci->isImperfectlyThreaded())
|
|
&& !mImperfectlyThreadedList.tqcontainsRef(item))
|
|
mImperfectlyThreadedList.append(item);
|
|
|
|
if (parent && !sci->isImperfectlyThreaded()
|
|
&& mImperfectlyThreadedList.tqcontainsRef(item))
|
|
mImperfectlyThreadedList.removeRef(item);
|
|
}
|
|
}
|
|
// Make sure our data structures are cleared.
|
|
if (!mFolder->count())
|
|
folderCleared();
|
|
|
|
mImperfectlyThreadedList.removeRef( removedItem );
|
|
#ifdef DEBUG
|
|
// This should never happen, in this case the folders are inconsistent.
|
|
while ( mImperfectlyThreadedList.findRef( removedItem ) != -1 ) {
|
|
mImperfectlyThreadedList.remove();
|
|
kdDebug(5006) << "Remove doubled item from mImperfectlyThreadedList: " << removedItem << endl;
|
|
}
|
|
#endif
|
|
delete removedItem;
|
|
// we might have rethreaded it, in which case its current state will be lost
|
|
if ( curItem ) {
|
|
if ( curItem != removedItem ) {
|
|
setCurrentItem( curItem );
|
|
setSelectionAnchor( currentItem() );
|
|
} else {
|
|
// We've removed the current item, which means it was removed from
|
|
// something other than a user move or copy, which would have selected
|
|
// the next logical mail. This can happen when the mail is deleted by
|
|
// a filter, or some other behind the scenes action. Select something
|
|
// sensible, then, and make sure the reader window is cleared.
|
|
emit maybeDeleting();
|
|
int contentX, contentY;
|
|
HeaderItem *nextItem = prepareMove( &contentX, &contentY );
|
|
finalizeMove( nextItem, contentX, contentY );
|
|
}
|
|
}
|
|
/* restore signal */
|
|
connect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this, TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::msgHeaderChanged(KMFolder*, int msgId)
|
|
{
|
|
if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return;
|
|
HeaderItem *item = mItems[msgId];
|
|
if (item) {
|
|
item->irefresh();
|
|
item->tqrepaint();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setMsgStatus (KMMsgStatus status, bool toggle)
|
|
{
|
|
// kdDebug() << k_funcinfo << endl;
|
|
SerNumList serNums = selectedVisibleSernums();
|
|
if (serNums.empty())
|
|
return;
|
|
|
|
KMCommand *command = new KMSeStatusCommand( status, serNums, toggle );
|
|
command->start();
|
|
}
|
|
|
|
|
|
TQPtrList<TQListViewItem> KMHeaders::currentThread() const
|
|
{
|
|
if (!mFolder) return TQPtrList<TQListViewItem>();
|
|
|
|
// starting with the current item...
|
|
TQListViewItem *curItem = currentItem();
|
|
if (!curItem) return TQPtrList<TQListViewItem>();
|
|
|
|
// ...find the top-level item:
|
|
TQListViewItem *topOfThread = curItem;
|
|
while ( topOfThread->parent() )
|
|
topOfThread = topOfThread->parent();
|
|
|
|
// collect the items in this thread:
|
|
TQPtrList<TQListViewItem> list;
|
|
TQListViewItem *topOfNextThread = topOfThread->nextSibling();
|
|
for ( TQListViewItemIterator it( topOfThread ) ;
|
|
it.current() && it.current() != topOfNextThread ; ++it )
|
|
list.append( it.current() );
|
|
return list;
|
|
}
|
|
|
|
void KMHeaders::setThreadStatus(KMMsgStatus status, bool toggle)
|
|
{
|
|
TQPtrList<TQListViewItem> curThread;
|
|
|
|
if (mFolder) {
|
|
TQPtrList<TQListViewItem> topOfThreads;
|
|
|
|
// for each selected item...
|
|
for (TQListViewItem *item = firstChild(); item; item = item->itemBelow())
|
|
if (item->isSelected() ) {
|
|
// ...find the top-level item:
|
|
TQListViewItem *top = item;
|
|
while ( top->parent() )
|
|
top = top->parent();
|
|
if (!topOfThreads.tqcontains(top)) {
|
|
topOfThreads.append(top);
|
|
}
|
|
}
|
|
|
|
// for each thread found...
|
|
for ( TQPtrListIterator<TQListViewItem> it( topOfThreads ) ;
|
|
it.current() ; ++ it ) {
|
|
TQListViewItem *top = *it;
|
|
|
|
// collect the items in this thread:
|
|
TQListViewItem *topOfNextThread = top->nextSibling();
|
|
for ( TQListViewItemIterator it( top ) ;
|
|
it.current() && it.current() != topOfNextThread ; ++it )
|
|
curThread.append( it.current() );
|
|
}
|
|
}
|
|
|
|
TQPtrListIterator<TQListViewItem> it( curThread );
|
|
SerNumList serNums;
|
|
|
|
for ( it.toFirst() ; it.current() ; ++it ) {
|
|
int id = static_cast<HeaderItem*>(*it)->msgId();
|
|
KMMsgBase *msgBase = mFolder->getMsgBase( id );
|
|
serNums.append( msgBase->getMsgSerNum() );
|
|
}
|
|
|
|
if (serNums.empty())
|
|
return;
|
|
|
|
KMCommand *command = new KMSeStatusCommand( status, serNums, toggle );
|
|
command->start();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMHeaders::slotFilterMsg(KMMessage *msg)
|
|
{
|
|
if ( !msg ) return 2; // messageRetrieve(0) is always possible
|
|
msg->setTransferInProgress(false);
|
|
int filterResult = kmkernel->filterMgr()->process(msg,KMFilterMgr::Explicit);
|
|
if (filterResult == 2) {
|
|
// something went horribly wrong (out of space?)
|
|
kmkernel->emergencyExit( i18n("Unable to process messages: " ) + TQString::fromLocal8Bit(strerror(errno)));
|
|
return 2;
|
|
}
|
|
if (msg->parent()) { // unGet this msg
|
|
int idx = -1;
|
|
KMFolder * p = 0;
|
|
KMMsgDict::instance()->getLocation( msg, &p, &idx );
|
|
assert( p == msg->parent() ); assert( idx >= 0 );
|
|
p->unGetMsg( idx );
|
|
}
|
|
|
|
return filterResult;
|
|
}
|
|
|
|
|
|
void KMHeaders::slotExpandOrCollapseThread( bool expand )
|
|
{
|
|
if ( !isThreaded() ) return;
|
|
// find top-level parent of currentItem().
|
|
TQListViewItem *item = currentItem();
|
|
if ( !item ) return;
|
|
clearSelection();
|
|
item->setSelected( true );
|
|
while ( item->parent() )
|
|
item = item->parent();
|
|
HeaderItem * hdrItem = static_cast<HeaderItem*>(item);
|
|
hdrItem->setOpenRecursive( expand );
|
|
if ( !expand ) // collapse can hide the current item:
|
|
setCurrentMsg( hdrItem->msgId() );
|
|
ensureItemVisible( currentItem() );
|
|
}
|
|
|
|
void KMHeaders::slotExpandOrCollapseAllThreads( bool expand )
|
|
{
|
|
if ( !isThreaded() ) return;
|
|
|
|
TQListViewItem * item = currentItem();
|
|
if( item ) {
|
|
clearSelection();
|
|
item->setSelected( true );
|
|
}
|
|
|
|
for ( TQListViewItem *item = firstChild() ;
|
|
item ; item = item->nextSibling() )
|
|
static_cast<HeaderItem*>(item)->setOpenRecursive( expand );
|
|
if ( !expand ) { // collapse can hide the current item:
|
|
TQListViewItem * item = currentItem();
|
|
if( item ) {
|
|
while ( item->parent() )
|
|
item = item->parent();
|
|
setCurrentMsg( static_cast<HeaderItem*>(item)->msgId() );
|
|
}
|
|
}
|
|
ensureItemVisible( currentItem() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setStyleDependantFrameWidth()
|
|
{
|
|
// set the width of the frame to a reasonable value for the current GUI style
|
|
int frameWidth;
|
|
if( style().isA("KeramikStyle") )
|
|
frameWidth = style().tqpixelMetric( TQStyle::PM_DefaultFrameWidth ) - 1;
|
|
else
|
|
frameWidth = style().tqpixelMetric( TQStyle::PM_DefaultFrameWidth );
|
|
if ( frameWidth < 0 )
|
|
frameWidth = 0;
|
|
if ( frameWidth != lineWidth() )
|
|
setLineWidth( frameWidth );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::styleChange( TQStyle& oldStyle )
|
|
{
|
|
setStyleDependantFrameWidth();
|
|
KListView::styleChange( oldStyle );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setFolderInfoStatus ()
|
|
{
|
|
if ( !mFolder ) return;
|
|
TQString str;
|
|
const int unread = mFolder->countUnread();
|
|
if ( static_cast<KMFolder*>(mFolder) == kmkernel->outboxFolder() )
|
|
str = unread ? i18n( "1 unsent", "%n unsent", unread ) : i18n( "0 unsent" );
|
|
else
|
|
str = unread ? i18n( "1 unread", "%n unread", unread ) : i18n( "0 unread" );
|
|
const int count = mFolder->count();
|
|
str = count ? i18n( "1 message, %1.", "%n messages, %1.", count ).arg( str )
|
|
: i18n( "0 messages" ); // no need for "0 unread" to be added here
|
|
if ( mFolder->isReadOnly() )
|
|
str = i18n("%1 = n messages, m unread.", "%1 Folder is read-only.").arg( str );
|
|
BroadcasStatus::instance()->seStatusMsg(str);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::applyFiltersOnMsg()
|
|
{
|
|
if (ActionScheduler::isEnabled() ||
|
|
kmkernel->filterMgr()->atLeastOneOnlineImapFolderTarget()) {
|
|
// uses action scheduler
|
|
KMFilterMgr::FilterSet set = KMFilterMgr::Explicit;
|
|
TQValueList<KMFilter*> filters = kmkernel->filterMgr()->filters();
|
|
ActionScheduler *scheduler = new ActionScheduler( set, filters, this );
|
|
scheduler->setAutoDestruct( true );
|
|
|
|
int contentX, contentY;
|
|
HeaderItem *nextItem = prepareMove( &contentX, &contentY );
|
|
TQPtrList<KMMsgBase> msgList = *selectedMsgs(true);
|
|
finalizeMove( nextItem, contentX, contentY );
|
|
|
|
for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
|
|
scheduler->execFilters( msg );
|
|
} else {
|
|
int contentX, contentY;
|
|
HeaderItem *nextItem = prepareMove( &contentX, &contentY );
|
|
|
|
//prevent issues with stale message pointers by using serial numbers instead
|
|
TQValueList<unsigned long> serNums = KMMsgDict::serNumList( *selectedMsgs() );
|
|
if ( serNums.isEmpty() )
|
|
return;
|
|
|
|
finalizeMove( nextItem, contentX, contentY );
|
|
CREATE_TIMER(filter);
|
|
START_TIMER(filter);
|
|
|
|
KCursorSaver busy( KBusyPtr::busy() );
|
|
int msgCount = 0;
|
|
int msgCountToFilter = serNums.count();
|
|
ProgressItem* progressItem =
|
|
ProgressManager::createProgressItem( "filter"+ProgressManager::getUniqueID(),
|
|
i18n( "Filtering messages" ) );
|
|
progressItem->setTotalItems( msgCountToFilter );
|
|
|
|
for ( TQValueList<unsigned long>::ConstIterator it = serNums.constBegin();
|
|
it != serNums.constEnd(); ++it ) {
|
|
msgCount++;
|
|
if ( msgCountToFilter - msgCount < 10 || !( msgCount % 20 ) || msgCount <= 10 ) {
|
|
progressItem->updateProgress();
|
|
TQString statusMsg = i18n("Filtering message %1 of %2");
|
|
statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter );
|
|
KPIM::BroadcasStatus::instance()->seStatusMsg( statusMsg );
|
|
KApplication::kApplication()->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput, 50 );
|
|
}
|
|
|
|
KMFolder *folder = 0;
|
|
int idx;
|
|
KMMsgDict::instance()->getLocation( *it, &folder, &idx );
|
|
KMMessage *msg = 0;
|
|
if (folder)
|
|
msg = folder->getMsg(idx);
|
|
if (msg) {
|
|
if (msg->transferInProgress())
|
|
continue;
|
|
msg->setTransferInProgress(true);
|
|
if (!msg->isComplete()) {
|
|
FolderJob *job = mFolder->createJob(msg);
|
|
connect(job, TQT_SIGNAL(messageRetrieved(KMMessage*)),
|
|
this, TQT_SLOT(slotFilterMsg(KMMessage*)));
|
|
job->start();
|
|
} else {
|
|
if (slotFilterMsg(msg) == 2)
|
|
break;
|
|
}
|
|
} else {
|
|
kdDebug (5006) << "####### KMHeaders::applyFiltersOnMsg -"
|
|
" A message went missing during filtering " << endl;
|
|
}
|
|
progressItem->incCompletedItems();
|
|
}
|
|
progressItem->setComplete();
|
|
progressItem = 0;
|
|
END_TIMER(filter);
|
|
SHOW_TIMER(filter);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setMsgRead (int msgId)
|
|
{
|
|
KMMsgBase *msgBase = mFolder->getMsgBase( msgId );
|
|
if (!msgBase)
|
|
return;
|
|
|
|
SerNumList serNums;
|
|
if (msgBase->isNew() || msgBase->isUnread()) {
|
|
serNums.append( msgBase->getMsgSerNum() );
|
|
}
|
|
|
|
KMCommand *command = new KMSeStatusCommand( KMMsgStatusRead, serNums );
|
|
command->start();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::deleteMsg ()
|
|
{
|
|
//make sure we have an associated folder (root of folder tree does not).
|
|
if (!mFolder)
|
|
return;
|
|
|
|
int contentX, contentY;
|
|
HeaderItem *nextItem = prepareMove( &contentX, &contentY );
|
|
KMMessageList msgList = *selectedMsgs(true);
|
|
finalizeMove( nextItem, contentX, contentY );
|
|
|
|
KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList );
|
|
connect( command, TQT_SIGNAL( completed( KMCommand * ) ),
|
|
this, TQT_SLOT( slotMoveCompleted( KMCommand * ) ) );
|
|
command->start();
|
|
|
|
BroadcasStatus::instance()->seStatusMsg("");
|
|
// triggerUpdate();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::moveSelectedToFolder( int menuId )
|
|
{
|
|
if (mMenuToFolder[menuId])
|
|
moveMsgToFolder( mMenuToFolder[menuId] );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
HeaderItem* KMHeaders::prepareMove( int *contentX, int *contentY )
|
|
{
|
|
HeaderItem *ret = 0;
|
|
emit maybeDeleting();
|
|
|
|
disconnect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this, TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
|
|
TQListViewItem *curItem;
|
|
HeaderItem *item;
|
|
curItem = currentItem();
|
|
while (curItem && curItem->isSelected() && curItem->itemBelow())
|
|
curItem = curItem->itemBelow();
|
|
while (curItem && curItem->isSelected() && curItem->itemAbove())
|
|
curItem = curItem->itemAbove();
|
|
item = static_cast<HeaderItem*>(curItem);
|
|
|
|
*contentX = contentsX();
|
|
*contentY = contentsY();
|
|
|
|
if (item && !item->isSelected())
|
|
ret = item;
|
|
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::finalizeMove( HeaderItem *item, int contentX, int contentY )
|
|
{
|
|
emit selected( 0 );
|
|
clearSelection();
|
|
|
|
if ( item ) {
|
|
setCurrentItem( item );
|
|
setSelected( item, true );
|
|
setSelectionAnchor( currentItem() );
|
|
mPrevCurrent = 0;
|
|
highlightMessage( item, false);
|
|
}
|
|
|
|
setContentsPos( contentX, contentY );
|
|
makeHeaderVisible();
|
|
connect( this, TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this, TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::moveMsgToFolder ( KMFolder* destFolder, bool askForConfirmation )
|
|
{
|
|
if ( destFolder == mFolder ) return; // Catch the noop case
|
|
if ( mFolder->isReadOnly() ) return;
|
|
|
|
KMMessageList msgList = *selectedMsgs();
|
|
if ( msgList.isEmpty() ) return;
|
|
if ( !destFolder && askForConfirmation && // messages shall be deleted
|
|
KMessageBox::warningContinueCancel(this,
|
|
i18n("<qt>Do you really want to delete the selected message?<br>"
|
|
"Once deleted, it cannot be restored.</qt>",
|
|
"<qt>Do you really want to delete the %n selected messages?<br>"
|
|
"Once deleted, they cannot be restored.</qt>", msgList.count() ),
|
|
msgList.count()>1 ? i18n("Delete Messages") : i18n("Delete Message"), KStdGuiItem::del(),
|
|
"NoConfirmDelete") == KMessageBox::Cancel )
|
|
return; // user canceled the action
|
|
|
|
// remember the message to select afterwards
|
|
int contentX, contentY;
|
|
HeaderItem *nextItem = prepareMove( &contentX, &contentY );
|
|
msgList = *selectedMsgs(true);
|
|
finalizeMove( nextItem, contentX, contentY );
|
|
|
|
KMCommand *command = new KMMoveCommand( destFolder, msgList );
|
|
connect( command, TQT_SIGNAL( completed( KMCommand * ) ),
|
|
this, TQT_SLOT( slotMoveCompleted( KMCommand * ) ) );
|
|
command->start();
|
|
}
|
|
|
|
void KMHeaders::slotMoveCompleted( KMCommand *command )
|
|
{
|
|
kdDebug(5006) << k_funcinfo << command->result() << endl;
|
|
bool deleted = static_cast<KMMoveCommand *>( command )->destFolder() == 0;
|
|
if ( command->result() == KMCommand::OK ) {
|
|
// make sure the current item is shown
|
|
makeHeaderVisible();
|
|
BroadcasStatus::instance()->seStatusMsg(
|
|
deleted ? i18n("Messages deleted successfully.") : i18n("Messages moved successfully") );
|
|
} else {
|
|
/* The move failed or the user canceled it; reset the state of all
|
|
* messages involved and tqrepaint.
|
|
*
|
|
* Note: This potentially resets too many items if there is more than one
|
|
* move going on. Oh well, I suppose no animals will be harmed.
|
|
* */
|
|
for (TQListViewItemIterator it(this); it.current(); it++) {
|
|
HeaderItem *item = static_cast<HeaderItem*>(it.current());
|
|
if ( item->aboutToBeDeleted() ) {
|
|
item->setAboutToBeDeleted ( false );
|
|
item->setSelectable ( true );
|
|
KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
|
|
if ( msgBase->isMessage() ) {
|
|
KMMessage *msg = static_cast<KMMessage *>(msgBase);
|
|
if ( msg ) msg->setTransferInProgress( false, true );
|
|
}
|
|
}
|
|
}
|
|
triggerUpdate();
|
|
if ( command->result() == KMCommand::Failed )
|
|
BroadcasStatus::instance()->seStatusMsg(
|
|
deleted ? i18n("Deleting messages failed.") : i18n("Moving messages failed.") );
|
|
else
|
|
BroadcasStatus::instance()->seStatusMsg(
|
|
deleted ? i18n("Deleting messages canceled.") : i18n("Moving messages canceled.") );
|
|
}
|
|
mOwner->updateMessageActions();
|
|
}
|
|
|
|
bool KMHeaders::canUndo() const
|
|
{
|
|
return ( kmkernel->undoStack()->size() > 0 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::undo()
|
|
{
|
|
kmkernel->undoStack()->undo();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::copySelectedToFolder(int menuId )
|
|
{
|
|
if (mMenuToFolder[menuId])
|
|
copyMsgToFolder( mMenuToFolder[menuId] );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::copyMsgToFolder(KMFolder* destFolder, KMMessage* aMsg)
|
|
{
|
|
if ( !destFolder )
|
|
return;
|
|
|
|
KMCommand * command = 0;
|
|
if (aMsg)
|
|
command = new KMCopyCommand( destFolder, aMsg );
|
|
else {
|
|
KMMessageList msgList = *selectedMsgs();
|
|
command = new KMCopyCommand( destFolder, msgList );
|
|
}
|
|
|
|
command->start();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setCurrentMsg(int cur)
|
|
{
|
|
if (!mFolder) return;
|
|
if (cur >= mFolder->count()) cur = mFolder->count() - 1;
|
|
if ((cur >= 0) && (cur < (int)mItems.size())) {
|
|
clearSelection();
|
|
setCurrentItem( mItems[cur] );
|
|
setSelected( mItems[cur], true );
|
|
setSelectionAnchor( currentItem() );
|
|
}
|
|
makeHeaderVisible();
|
|
setFolderInfoStatus();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setSelected( TQListViewItem *item, bool selected )
|
|
{
|
|
if ( !item )
|
|
return;
|
|
|
|
if ( item->isVisible() )
|
|
KListView::setSelected( item, selected );
|
|
|
|
// If the item is the parent of a closed thread recursively select
|
|
// children .
|
|
if ( isThreaded() && !item->isOpen() && item->firstChild() ) {
|
|
TQListViewItem *nextRoot = item->itemBelow();
|
|
TQListViewItemIterator it( item->firstChild() );
|
|
for( ; (*it) != nextRoot; ++it ) {
|
|
if ( (*it)->isVisible() )
|
|
(*it)->setSelected( selected );
|
|
}
|
|
}
|
|
}
|
|
|
|
void KMHeaders::setSelectedByIndex( TQValueList<int> items, bool selected )
|
|
{
|
|
for ( TQValueList<int>::Iterator it = items.begin(); it != items.end(); ++it )
|
|
{
|
|
if ( ((*it) >= 0) && ((*it) < (int)mItems.size()) )
|
|
{
|
|
setSelected( mItems[(*it)], selected );
|
|
}
|
|
}
|
|
}
|
|
|
|
void KMHeaders::clearSelectableAndAboutToBeDeleted( TQ_UINT32 serNum )
|
|
{
|
|
// fugly, but I see no way around it
|
|
for (TQListViewItemIterator it(this); it.current(); it++) {
|
|
HeaderItem *item = static_cast<HeaderItem*>(it.current());
|
|
if ( item->aboutToBeDeleted() ) {
|
|
KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
|
|
if ( serNum == msgBase->getMsgSerNum() ) {
|
|
item->setAboutToBeDeleted ( false );
|
|
item->setSelectable ( true );
|
|
}
|
|
}
|
|
}
|
|
triggerUpdate();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted)
|
|
{
|
|
mSelMsgBaseList.clear();
|
|
for (TQListViewItemIterator it(this); it.current(); it++) {
|
|
if ( it.current()->isSelected() && it.current()->isVisible() ) {
|
|
HeaderItem *item = static_cast<HeaderItem*>(it.current());
|
|
if ( !item->aboutToBeDeleted() ) { // we are already working on this one
|
|
if (toBeDeleted) {
|
|
// make sure the item is not uselessly rethreaded and not selectable
|
|
item->setAboutToBeDeleted ( true );
|
|
item->setSelectable ( false );
|
|
}
|
|
KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
|
|
mSelMsgBaseList.append(msgBase);
|
|
}
|
|
}
|
|
}
|
|
return &mSelMsgBaseList;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQValueList<int> KMHeaders::selectedItems()
|
|
{
|
|
TQValueList<int> items;
|
|
for ( TQListViewItemIterator it(this); it.current(); it++ )
|
|
{
|
|
if ( it.current()->isSelected() && it.current()->isVisible() )
|
|
{
|
|
HeaderItem* item = static_cast<HeaderItem*>( it.current() );
|
|
items.append( item->msgId() );
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMHeaders::firstSelectedMsg() const
|
|
{
|
|
int selectedMsg = -1;
|
|
TQListViewItem *item;
|
|
for (item = firstChild(); item; item = item->itemBelow())
|
|
if (item->isSelected()) {
|
|
selectedMsg = (static_cast<HeaderItem*>(item))->msgId();
|
|
break;
|
|
}
|
|
return selectedMsg;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::nextMessage()
|
|
{
|
|
TQListViewItem *lvi = currentItem();
|
|
if (lvi && lvi->itemBelow()) {
|
|
clearSelection();
|
|
setSelected( lvi, false );
|
|
selectNextMessage();
|
|
setSelectionAnchor( currentItem() );
|
|
ensureCurrentItemVisible();
|
|
}
|
|
}
|
|
|
|
void KMHeaders::selectNextMessage()
|
|
{
|
|
KMMessage *cm = currentMsg();
|
|
if ( cm && cm->isBeingParsed() )
|
|
return;
|
|
TQListViewItem *lvi = currentItem();
|
|
if( lvi ) {
|
|
TQListViewItem *below = lvi->itemBelow();
|
|
TQListViewItem *temp = lvi;
|
|
if (lvi && below ) {
|
|
while (temp) {
|
|
temp->firstChild();
|
|
temp = temp->parent();
|
|
}
|
|
lvi->tqrepaint();
|
|
/* test to see if we need to unselect messages on back track */
|
|
(below->isSelected() ? setSelected(lvi, false) : setSelected(below, true));
|
|
setCurrentItem(below);
|
|
makeHeaderVisible();
|
|
setFolderInfoStatus();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::prevMessage()
|
|
{
|
|
TQListViewItem *lvi = currentItem();
|
|
if (lvi && lvi->itemAbove()) {
|
|
clearSelection();
|
|
setSelected( lvi, false );
|
|
selectPrevMessage();
|
|
setSelectionAnchor( currentItem() );
|
|
ensureCurrentItemVisible();
|
|
}
|
|
}
|
|
|
|
void KMHeaders::selectPrevMessage()
|
|
{
|
|
KMMessage *cm = currentMsg();
|
|
if ( cm && cm->isBeingParsed() )
|
|
return;
|
|
TQListViewItem *lvi = currentItem();
|
|
if( lvi ) {
|
|
TQListViewItem *above = lvi->itemAbove();
|
|
TQListViewItem *temp = lvi;
|
|
|
|
if (lvi && above) {
|
|
while (temp) {
|
|
temp->firstChild();
|
|
temp = temp->parent();
|
|
}
|
|
lvi->tqrepaint();
|
|
/* test to see if we need to unselect messages on back track */
|
|
(above->isSelected() ? setSelected(lvi, false) : setSelected(above, true));
|
|
setCurrentItem(above);
|
|
makeHeaderVisible();
|
|
setFolderInfoStatus();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void KMHeaders::incCurrentMessage()
|
|
{
|
|
KMMessage *cm = currentMsg();
|
|
if ( cm && cm->isBeingParsed() )
|
|
return;
|
|
TQListViewItem *lvi = currentItem();
|
|
if ( lvi && lvi->itemBelow() ) {
|
|
|
|
disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
setCurrentItem( lvi->itemBelow() );
|
|
ensureCurrentItemVisible();
|
|
setFocus();
|
|
connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
}
|
|
}
|
|
|
|
void KMHeaders::decCurrentMessage()
|
|
{
|
|
KMMessage *cm = currentMsg();
|
|
if ( cm && cm->isBeingParsed() )
|
|
return;
|
|
TQListViewItem *lvi = currentItem();
|
|
if ( lvi && lvi->itemAbove() ) {
|
|
disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
setCurrentItem( lvi->itemAbove() );
|
|
ensureCurrentItemVisible();
|
|
setFocus();
|
|
connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
}
|
|
}
|
|
|
|
void KMHeaders::selectCurrentMessage()
|
|
{
|
|
setCurrentMsg( currentItemIndex() );
|
|
highlightMessage( currentItem() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::findUnreadAux( HeaderItem*& item,
|
|
bool & foundUnreadMessage,
|
|
bool onlyNew,
|
|
bool aDirNext )
|
|
{
|
|
KMMsgBase* msgBase = 0;
|
|
HeaderItem *lastUnread = 0;
|
|
/* itemAbove() is _slow_ */
|
|
if (aDirNext)
|
|
{
|
|
while (item) {
|
|
msgBase = mFolder->getMsgBase(item->msgId());
|
|
if (!msgBase) continue;
|
|
if (msgBase->isUnread() || msgBase->isNew())
|
|
foundUnreadMessage = true;
|
|
|
|
if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())) break;
|
|
if (onlyNew && msgBase->isNew()) break;
|
|
item = static_cast<HeaderItem*>(item->itemBelow());
|
|
}
|
|
} else {
|
|
HeaderItem *newItem = static_cast<HeaderItem*>(firstChild());
|
|
while (newItem)
|
|
{
|
|
msgBase = mFolder->getMsgBase(newItem->msgId());
|
|
if (!msgBase) continue;
|
|
if (msgBase->isUnread() || msgBase->isNew())
|
|
foundUnreadMessage = true;
|
|
if ( ( !onlyNew && (msgBase->isUnread() || msgBase->isNew()) )
|
|
|| ( onlyNew && msgBase->isNew() ) )
|
|
lastUnread = newItem;
|
|
if (newItem == item) break;
|
|
newItem = static_cast<HeaderItem*>(newItem->itemBelow());
|
|
}
|
|
item = lastUnread;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent)
|
|
{
|
|
HeaderItem *item, *pitem;
|
|
bool foundUnreadMessage = false;
|
|
|
|
if (!mFolder) return -1;
|
|
if (mFolder->count() <= 0) return -1;
|
|
|
|
if ((aStartAt >= 0) && (aStartAt < (int)mItems.size()))
|
|
item = mItems[aStartAt];
|
|
else {
|
|
item = currentHeaderItem();
|
|
if (!item) {
|
|
if (aDirNext)
|
|
item = static_cast<HeaderItem*>(firstChild());
|
|
else
|
|
item = static_cast<HeaderItem*>(lastChild());
|
|
}
|
|
if (!item)
|
|
return -1;
|
|
|
|
if ( !acceptCurrent ) {
|
|
if (aDirNext) {
|
|
item = static_cast<HeaderItem*>(item->itemBelow());
|
|
}
|
|
else {
|
|
item = static_cast<HeaderItem*>(item->itemAbove());
|
|
}
|
|
}
|
|
}
|
|
|
|
pitem = item;
|
|
|
|
findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
|
|
|
|
// We have found an unread item, but it is not necessary the
|
|
// first unread item.
|
|
//
|
|
// Find the ancestor of the unread item closest to the
|
|
// root and recursively sort all of that ancestors children.
|
|
if (item) {
|
|
TQListViewItem *next = item;
|
|
while (next->parent())
|
|
next = next->parent();
|
|
next = static_cast<HeaderItem*>(next)->firstChildNonConst();
|
|
while (next && (next != item))
|
|
if (static_cast<HeaderItem*>(next)->firstChildNonConst())
|
|
next = next->firstChild();
|
|
else if (next->nextSibling())
|
|
next = next->nextSibling();
|
|
else {
|
|
while (next && (next != item)) {
|
|
next = next->parent();
|
|
if (next == item)
|
|
break;
|
|
if (next && next->nextSibling()) {
|
|
next = next->nextSibling();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
item = pitem;
|
|
|
|
findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
|
|
if (item)
|
|
return item->msgId();
|
|
|
|
|
|
// A kludge to try to keep the number of unread messages in sync
|
|
int unread = mFolder->countUnread();
|
|
if (((unread == 0) && foundUnreadMessage) ||
|
|
((unread > 0) && !foundUnreadMessage)) {
|
|
mFolder->correctUnreadMsgsCount();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMHeaders::nextUnreadMessage(bool acceptCurrent)
|
|
{
|
|
if ( !mFolder || !mFolder->countUnread() ) return false;
|
|
int i = findUnread(true, -1, false, acceptCurrent);
|
|
if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
|
|
GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
|
|
{
|
|
HeaderItem * first = static_cast<HeaderItem*>(firstChild());
|
|
if ( first )
|
|
i = findUnread(true, first->msgId(), false, acceptCurrent); // from top
|
|
}
|
|
if ( i < 0 )
|
|
return false;
|
|
setCurrentMsg(i);
|
|
ensureCurrentItemVisible();
|
|
return true;
|
|
}
|
|
|
|
void KMHeaders::ensureCurrentItemVisible()
|
|
{
|
|
int i = currentItemIndex();
|
|
if ((i >= 0) && (i < (int)mItems.size()))
|
|
center( contentsX(), itemPos(mItems[i]), 0, 9.0 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMHeaders::prevUnreadMessage()
|
|
{
|
|
if ( !mFolder || !mFolder->countUnread() ) return false;
|
|
int i = findUnread(false);
|
|
if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
|
|
GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
|
|
{
|
|
HeaderItem * last = static_cast<HeaderItem*>(lastItem());
|
|
if ( last )
|
|
i = findUnread(false, last->msgId() ); // from bottom
|
|
}
|
|
if ( i < 0 )
|
|
return false;
|
|
setCurrentMsg(i);
|
|
ensureCurrentItemVisible();
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::slotNoDrag()
|
|
{
|
|
// This causes Kolab issue 1569 (encrypted mails sometimes not dragable)
|
|
// This was introduced in r73594 to fix interference between dnd and
|
|
// pinentry, which is no longer reproducable now. However, since the
|
|
// original problem was probably a race and might reappear, let's keep
|
|
// this workaround in for now and just disable it.
|
|
// mMousePressed = false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::makeHeaderVisible()
|
|
{
|
|
if (currentItem())
|
|
ensureItemVisible( currentItem() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::highlightMessage(TQListViewItem* lvi, bool markitread)
|
|
{
|
|
// shouldnt happen but will crash if it does
|
|
if (lvi && !lvi->isSelectable()) return;
|
|
|
|
HeaderItem *item = static_cast<HeaderItem*>(lvi);
|
|
if (lvi != mPrevCurrent) {
|
|
if (mPrevCurrent && mFolder)
|
|
{
|
|
KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId());
|
|
if (prevMsg && mReaderWindowActive)
|
|
{
|
|
mFolder->ignoreJobsForMessage(prevMsg);
|
|
if (!prevMsg->transferInProgress())
|
|
mFolder->unGetMsg(mPrevCurrent->msgId());
|
|
}
|
|
}
|
|
mPrevCurrent = item;
|
|
}
|
|
|
|
if (!item) {
|
|
emit selected( 0 ); return;
|
|
}
|
|
|
|
int idx = item->msgId();
|
|
KMMessage *msg = mFolder->getMsg(idx);
|
|
if (mReaderWindowActive && !msg) {
|
|
emit selected( 0 );
|
|
mPrevCurrent = 0;
|
|
return;
|
|
}
|
|
|
|
BroadcasStatus::instance()->seStatusMsg("");
|
|
if (markitread && idx >= 0) setMsgRead(idx);
|
|
mItems[idx]->irefresh();
|
|
mItems[idx]->tqrepaint();
|
|
emit selected( msg );
|
|
setFolderInfoStatus();
|
|
}
|
|
|
|
void KMHeaders::highlightCurrentThread()
|
|
{
|
|
TQPtrList<TQListViewItem> curThread = currentThread();
|
|
TQPtrListIterator<TQListViewItem> it( curThread );
|
|
|
|
for ( it.toFirst() ; it.current() ; ++it ) {
|
|
TQListViewItem *lvi = *it;
|
|
lvi->setSelected( true );
|
|
lvi->tqrepaint();
|
|
}
|
|
}
|
|
|
|
void KMHeaders::resetCurrentTime()
|
|
{
|
|
mDate.reset();
|
|
// only reset exactly during minute switch
|
|
TQTimer::singleShot( ( 60-TQTime::currentTime().second() ) * 1000,
|
|
this, TQT_SLOT( resetCurrentTime() ) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::selectMessage(TQListViewItem* lvi)
|
|
{
|
|
HeaderItem *item = static_cast<HeaderItem*>(lvi);
|
|
if (!item)
|
|
return;
|
|
|
|
int idx = item->msgId();
|
|
KMMessage *msg = mFolder->getMsg(idx);
|
|
if (msg && !msg->transferInProgress())
|
|
{
|
|
emit activated(mFolder->getMsg(idx));
|
|
}
|
|
|
|
// if (kmkernel->folderIsDraftOrOutbox(mFolder))
|
|
// setOpen(lvi, !lvi->isOpen());
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::updateMessageList( bool set_selection, bool forceJumpToUnread )
|
|
{
|
|
mPrevCurrent = 0;
|
|
noRepaint = true;
|
|
clear();
|
|
mItems.resize(0); // will contain deleted pointers
|
|
noRepaint = false;
|
|
KListView::setSorting( mSortCol, !mSortDescending );
|
|
if (!mFolder) {
|
|
tqrepaint();
|
|
return;
|
|
}
|
|
readSortOrder( set_selection, forceJumpToUnread );
|
|
emit messageListUpdated();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// KMail Header list selection/navigation description
|
|
//
|
|
// If the selection state changes the reader window is updated to show the
|
|
// current item.
|
|
//
|
|
// (The selection state of a message or messages can be changed by pressing
|
|
// space, or normal/shift/cntrl clicking).
|
|
//
|
|
// The following keyboard events are supported when the messages headers list
|
|
// has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End,
|
|
// Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do
|
|
// not change the selection state.
|
|
//
|
|
// Exception: When shift selecting either with mouse or key press the reader
|
|
// window is updated regardless of whether of not the selection has changed.
|
|
void KMHeaders::keyPressEvent( TQKeyEvent * e )
|
|
{
|
|
bool cntrl = (e->state() & ControlButton );
|
|
bool shft = (e->state() & ShiftButton );
|
|
TQListViewItem *cur = currentItem();
|
|
|
|
if (!e || !firstChild())
|
|
return;
|
|
|
|
// If no current item, make some first item current when a key is pressed
|
|
if (!cur) {
|
|
setCurrentItem( firstChild() );
|
|
setSelectionAnchor( currentItem() );
|
|
return;
|
|
}
|
|
|
|
// Handle space key press
|
|
if (cur->isSelectable() && e->ascii() == ' ' ) {
|
|
setSelected( cur, !cur->isSelected() );
|
|
highlightMessage( cur, false);
|
|
return;
|
|
}
|
|
|
|
if (cntrl) {
|
|
if (!shft)
|
|
disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
switch (e->key()) {
|
|
case Key_Down:
|
|
case Key_Up:
|
|
case Key_Home:
|
|
case Key_End:
|
|
case Key_Next:
|
|
case Key_Prior:
|
|
case Key_Escape:
|
|
KListView::keyPressEvent( e );
|
|
}
|
|
if (!shft)
|
|
connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)),
|
|
this,TQT_SLOT(highlightMessage(TQListViewItem*)));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Handle RMB press, show pop up menu
|
|
void KMHeaders::rightButtonPressed( TQListViewItem *lvi, const TQPoint &, int )
|
|
{
|
|
if (!lvi)
|
|
return;
|
|
|
|
if (!(lvi->isSelected())) {
|
|
clearSelection();
|
|
}
|
|
setSelected( lvi, true );
|
|
slotRMB();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::contentsMousePressEvent(TQMouseEvent* e)
|
|
{
|
|
mPressPos = e->pos();
|
|
TQListViewItem *lvi = itemAt( contentsToViewport( e->pos() ));
|
|
bool wasSelected = false;
|
|
bool rootDecoClicked = false;
|
|
if (lvi) {
|
|
wasSelected = lvi->isSelected();
|
|
rootDecoClicked =
|
|
( mPressPos.x() <= header()->cellPos( header()->mapToActual( 0 ) ) +
|
|
treeStepSize() * ( lvi->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() )
|
|
&& ( mPressPos.x() >= header()->cellPos( header()->mapToActual( 0 ) ) );
|
|
|
|
if ( rootDecoClicked ) {
|
|
// Check if our item is the parent of a closed thread and if so, if the root
|
|
// decoration of the item was clicked (i.e. the +/- sign) which would expand
|
|
// the thread. In that case, deselect all children, so opening the thread
|
|
// doesn't cause a flicker.
|
|
if ( !lvi->isOpen() && lvi->firstChild() ) {
|
|
TQListViewItem *nextRoot = lvi->itemBelow();
|
|
TQListViewItemIterator it( lvi->firstChild() );
|
|
for( ; (*it) != nextRoot; ++it )
|
|
(*it)->setSelected( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
// let klistview do it's thing, expanding/collapsing, selection/deselection
|
|
KListView::contentsMousePressEvent(e);
|
|
/* QListView's shift-select selects also invisible items. Until that is
|
|
fixed, we have to deselect hidden items here manually, so the quick
|
|
search doesn't mess things up. */
|
|
if ( e->state() & ShiftButton ) {
|
|
TQListViewItemIterator it( this, TQListViewItemIterator::Invisible );
|
|
while ( it.current() ) {
|
|
it.current()->setSelected( false );
|
|
++it;
|
|
}
|
|
}
|
|
|
|
if ( rootDecoClicked ) {
|
|
// select the thread's children after closing if the parent is selected
|
|
if ( lvi && !lvi->isOpen() && lvi->isSelected() )
|
|
setSelected( lvi, true );
|
|
}
|
|
|
|
if ( lvi && !rootDecoClicked ) {
|
|
if ( lvi != currentItem() )
|
|
highlightMessage( lvi );
|
|
/* Explicitely set selection state. This is necessary because we want to
|
|
* also select all children of closed threads when the parent is selected. */
|
|
|
|
// unless ctrl tqmask, set selected if it isn't already
|
|
if ( !( e->state() & ControlButton ) && !wasSelected )
|
|
setSelected( lvi, true );
|
|
// if ctrl tqmask, toggle selection
|
|
if ( e->state() & ControlButton )
|
|
setSelected( lvi, !wasSelected );
|
|
|
|
if ((e->button() == LeftButton) )
|
|
mMousePressed = true;
|
|
}
|
|
|
|
// check if we are on a status column and toggle it
|
|
if ( lvi && e->button() == LeftButton && !( e->state() & (ShiftButton | ControlButton | AltButton | MetaButton) ) ) {
|
|
bool flagsToggleable = GlobalSettings::self()->allowLocalFlags() || !(mFolder ? mFolder->isReadOnly() : true);
|
|
int section = header()->sectionAt( e->pos().x() );
|
|
HeaderItem *item = static_cast<HeaderItem*>( lvi );
|
|
KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
|
|
if ( section == mPaintInfo.flagCol && flagsToggleable ) {
|
|
setMsgStatus( KMMsgStatusFlag, true );
|
|
} else if ( section == mPaintInfo.importantCol && flagsToggleable ) {
|
|
setMsgStatus( KMMsgStatusFlag, true );
|
|
} else if ( section == mPaintInfo.todoCol && flagsToggleable ) {
|
|
setMsgStatus( KMMsgStatusTodo, true );
|
|
} else if ( section == mPaintInfo.watchedIgnoredCol && flagsToggleable ) {
|
|
if ( msg->isWatched() || msg->isIgnored() )
|
|
setMsgStatus( KMMsgStatusIgnored, true );
|
|
else
|
|
setMsgStatus( KMMsgStatusWatched, true );
|
|
} else if ( section == mPaintInfo.statusCol ) {
|
|
if ( msg->isUnread() || msg->isNew() )
|
|
setMsgStatus( KMMsgStatusRead );
|
|
else
|
|
setMsgStatus( KMMsgStatusUnread );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::contentsMouseReleaseEvent(TQMouseEvent* e)
|
|
{
|
|
if (e->button() != RightButton)
|
|
KListView::contentsMouseReleaseEvent(e);
|
|
|
|
mMousePressed = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::contentsMouseMoveEvent( TQMouseEvent* e )
|
|
{
|
|
if (mMousePressed &&
|
|
(e->pos() - mPressPos).manhattanLength() > KGlobalSettings::dndEventDelay()) {
|
|
mMousePressed = false;
|
|
TQListViewItem *item = itemAt( contentsToViewport(mPressPos) );
|
|
if ( item ) {
|
|
MailList mailList;
|
|
unsigned int count = 0;
|
|
for( TQListViewItemIterator it(this); it.current(); it++ )
|
|
if( it.current()->isSelected() ) {
|
|
HeaderItem *item = static_cast<HeaderItem*>(it.current());
|
|
KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
|
|
// FIXME: msg can be null here which crashes. I think it's a race
|
|
// because it's very hard to reproduce. (GS)
|
|
MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(),
|
|
msg->subject(), msg->fromStrip(),
|
|
msg->toStrip(), msg->date() );
|
|
mailList.append( mailSummary );
|
|
++count;
|
|
}
|
|
MailListDrag *d = new MailListDrag( mailList, viewport(), new KMTextSource );
|
|
|
|
// Set pixmap
|
|
TQPixmap pixmap;
|
|
if( count == 1 )
|
|
pixmap = TQPixmap( DesktopIcon("message", KIcon::SizeSmall) );
|
|
else
|
|
pixmap = TQPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) );
|
|
|
|
// Calculate hotspot (as in Konqueror)
|
|
if( !pixmap.isNull() ) {
|
|
TQPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 );
|
|
d->setPixmap( pixmap, hotspot );
|
|
}
|
|
if ( mFolder->isReadOnly() )
|
|
d->dragCopy();
|
|
else
|
|
d->drag();
|
|
}
|
|
}
|
|
}
|
|
|
|
void KMHeaders::highlightMessage(TQListViewItem* i)
|
|
{
|
|
highlightMessage( i, false );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::slotRMB()
|
|
{
|
|
if (!tqtopLevelWidget()) return; // safe bet
|
|
mOwner->updateMessageActions();
|
|
|
|
// check if the user clicked into a status column and only show the respective menues
|
|
TQListViewItem *item = itemAt( viewport()->mapFromGlobal( TQCursor::pos() ) );
|
|
if ( item ) {
|
|
int section = header()->sectionAt( viewportToContents( viewport()->mapFromGlobal( TQCursor::pos() ) ).x() );
|
|
if ( section == mPaintInfo.flagCol || section == mPaintInfo.importantCol
|
|
|| section == mPaintInfo.todoCol || section == mPaintInfo.statusCol ) {
|
|
mOwner->statusMenu()->popup( TQCursor::pos() );
|
|
return;
|
|
}
|
|
if ( section == mPaintInfo.watchedIgnoredCol ) {
|
|
mOwner->threadStatusMenu()->popup( TQCursor::pos() );
|
|
return;
|
|
}
|
|
}
|
|
|
|
TQPopupMenu *menu = new TQPopupMenu(this);
|
|
|
|
mMenuToFolder.clear();
|
|
|
|
mOwner->updateMessageMenu();
|
|
|
|
bool out_folder = kmkernel->folderIsDraftOrOutbox( mFolder );
|
|
bool tem_folder = kmkernel->folderIsTemplates( mFolder );
|
|
if ( tem_folder ) {
|
|
mOwner->useAction()->plug( menu );
|
|
} else {
|
|
// show most used actions
|
|
mOwner->messageActions()->replyMenu()->plug( menu );
|
|
mOwner->forwardMenu()->plug( menu );
|
|
if( mOwner->sendAgainAction()->isEnabled() ) {
|
|
mOwner->sendAgainAction()->plug( menu );
|
|
} else {
|
|
mOwner->editAction()->plug( menu );
|
|
}
|
|
}
|
|
menu->insertSeparator();
|
|
|
|
TQPopupMenu *msgCopyMenu = new TQPopupMenu(menu);
|
|
mOwner->folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage, this,
|
|
&mMenuToFolder, msgCopyMenu );
|
|
menu->insertItem(i18n("&Copy To"), msgCopyMenu);
|
|
|
|
if ( !mFolder->canDeleteMessages() ) {
|
|
int id = menu->insertItem( i18n("&Move To") );
|
|
menu->setItemEnabled( id, false );
|
|
} else {
|
|
TQPopupMenu *msgMoveMenu = new TQPopupMenu(menu);
|
|
mOwner->folderTree()->folderToPopupMenu( KMFolderTree::MoveMessage, this,
|
|
&mMenuToFolder, msgMoveMenu );
|
|
menu->insertItem(i18n("&Move To"), msgMoveMenu);
|
|
}
|
|
menu->insertSeparator();
|
|
mOwner->statusMenu()->plug( menu ); // Mark Message menu
|
|
if ( mOwner->threadStatusMenu()->isEnabled() ) {
|
|
mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu
|
|
}
|
|
|
|
if ( !out_folder && !tem_folder ) {
|
|
menu->insertSeparator();
|
|
mOwner->filterMenu()->plug( menu ); // Create Filter menu
|
|
mOwner->action( "apply_filter_actions" )->plug( menu );
|
|
}
|
|
|
|
menu->insertSeparator();
|
|
mOwner->printAction()->plug(menu);
|
|
mOwner->saveAsAction()->plug(menu);
|
|
mOwner->saveAttachmentsAction()->plug(menu);
|
|
menu->insertSeparator();
|
|
if ( mFolder->isTrash() ) {
|
|
mOwner->deleteAction()->plug(menu);
|
|
if ( mOwner->trashThreadAction()->isEnabled() )
|
|
mOwner->deleteThreadAction()->plug(menu);
|
|
} else {
|
|
mOwner->trashAction()->plug(menu);
|
|
if ( mOwner->trashThreadAction()->isEnabled() )
|
|
mOwner->trashThreadAction()->plug(menu);
|
|
}
|
|
menu->insertSeparator();
|
|
mOwner->messageActions()->createTodoAction()->plug( menu );
|
|
|
|
KAcceleratorManager::manage(menu);
|
|
kmkernel->setContextMenuShown( true );
|
|
menu->exec(TQCursor::pos(), 0);
|
|
kmkernel->setContextMenuShown( false );
|
|
delete menu;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage* KMHeaders::currentMsg()
|
|
{
|
|
HeaderItem *hi = currentHeaderItem();
|
|
if (!hi)
|
|
return 0;
|
|
else
|
|
return mFolder->getMsg(hi->msgId());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
HeaderItem* KMHeaders::currentHeaderItem()
|
|
{
|
|
return static_cast<HeaderItem*>(currentItem());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMHeaders::currentItemIndex()
|
|
{
|
|
HeaderItem* item = currentHeaderItem();
|
|
if (item)
|
|
return item->msgId();
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setCurrentItemByIndex(int msgIdx)
|
|
{
|
|
if (!mFolder->isOpened()) setFolder(mFolder);
|
|
|
|
if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) {
|
|
clearSelection();
|
|
bool unchanged = (currentItem() == mItems[msgIdx]);
|
|
setCurrentItem( mItems[msgIdx] );
|
|
setSelected( mItems[msgIdx], true );
|
|
setSelectionAnchor( currentItem() );
|
|
if (unchanged)
|
|
highlightMessage( mItems[msgIdx], false);
|
|
makeHeaderVisible();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMHeaders::topItemIndex()
|
|
{
|
|
HeaderItem *item = static_cast<HeaderItem*>( itemAt( TQPoint( 1, 1 ) ) );
|
|
if ( item )
|
|
return item->msgId();
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setTopItemByIndex( int aMsgIdx)
|
|
{
|
|
if ( aMsgIdx < 0 || static_cast<unsigned int>( aMsgIdx ) >= mItems.size() )
|
|
return;
|
|
const TQListViewItem * const item = mItems[aMsgIdx];
|
|
if ( item )
|
|
setContentsPos( 0, itemPos( item ) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setNestedOverride( bool override )
|
|
{
|
|
mSortInfo.dirty = true;
|
|
mNestedOverride = override;
|
|
setRootIsDecorated( nestingPolicy != AlwaysOpen
|
|
&& isThreaded() );
|
|
TQString sortFile = mFolder->indexLocation() + ".sorted";
|
|
unlink(TQFile::encodeName(sortFile));
|
|
reset();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setSubjectThreading( bool aSubjThreading )
|
|
{
|
|
mSortInfo.dirty = true;
|
|
mSubjThreading = aSubjThreading;
|
|
TQString sortFile = mFolder->indexLocation() + ".sorted";
|
|
unlink(TQFile::encodeName(sortFile));
|
|
reset();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setOpen( TQListViewItem *item, bool open )
|
|
{
|
|
if ((nestingPolicy != AlwaysOpen)|| open)
|
|
((HeaderItem*)item)->setOpenRecursive( open );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const KMMsgBase* KMHeaders::getMsgBaseForItem( const TQListViewItem *item ) const
|
|
{
|
|
const HeaderItem *hi = static_cast<const HeaderItem *> ( item );
|
|
return mFolder->getMsgBase( hi->msgId() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setSorting( int column, bool ascending )
|
|
{
|
|
if ( mIgnoreSortOrderChanges )
|
|
return;
|
|
|
|
if (column != -1) {
|
|
// carsten: really needed?
|
|
// if (column != mSortCol)
|
|
// setColumnText( mSortCol, TQIconSet( TQPixmap()), columnText( mSortCol ));
|
|
if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied
|
|
TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
|
|
mSortInfo.dirty = true;
|
|
}
|
|
|
|
assert(column >= 0);
|
|
mSortCol = column;
|
|
mSortDescending = !ascending;
|
|
|
|
if (!ascending && (column == mPaintInfo.dateCol))
|
|
mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival;
|
|
|
|
if (!ascending && (column == mPaintInfo.subCol))
|
|
mPaintInfo.status = !mPaintInfo.status;
|
|
|
|
TQString colText = i18n( "Date" );
|
|
if (mPaintInfo.orderOfArrival)
|
|
colText = i18n( "Order of Arrival" );
|
|
setColumnText( mPaintInfo.dateCol, colText);
|
|
|
|
colText = i18n( "Subject" );
|
|
if (mPaintInfo.status)
|
|
colText = colText + i18n( " (Status)" );
|
|
setColumnText( mPaintInfo.subCol, colText);
|
|
}
|
|
KListView::setSorting( column, ascending );
|
|
ensureCurrentItemVisible();
|
|
// Make sure the config and .sorted file are updated, otherwise stale info
|
|
// is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ).
|
|
if ( mFolder ) {
|
|
writeFolderConfig();
|
|
writeSortOrder();
|
|
}
|
|
}
|
|
|
|
//Flatten the list and write it to disk
|
|
static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid,
|
|
int parent_id, TQString key,
|
|
bool update_discover=true)
|
|
{
|
|
unsigned long msgSerNum;
|
|
unsigned long parentSerNum;
|
|
msgSerNum = KMMsgDict::instance()->getMsgSerNum( folder, msgid );
|
|
if (parent_id >= 0)
|
|
parentSerNum = KMMsgDict::instance()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED;
|
|
else
|
|
parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED);
|
|
|
|
fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream);
|
|
fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream);
|
|
TQ_INT32 len = key.length() * sizeof(TQChar);
|
|
fwrite(&len, sizeof(len), 1, sortStream);
|
|
if (len)
|
|
fwrite(key.tqunicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream);
|
|
|
|
if (update_discover) {
|
|
//update the discovered change count
|
|
TQ_INT32 discovered_count = 0;
|
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
|
|
fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
|
|
discovered_count++;
|
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
|
|
fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
|
|
}
|
|
}
|
|
|
|
void KMHeaders::folderCleared()
|
|
{
|
|
mSortCacheItems.clear(); //autoDelete is true
|
|
mSubjectLists.clear();
|
|
mImperfectlyThreadedList.clear();
|
|
mPrevCurrent = 0;
|
|
emit selected(0);
|
|
}
|
|
|
|
|
|
void KMHeaders::folderClosed()
|
|
{
|
|
if ( mFolder->open( "kmheaders" ) == 0 )
|
|
updateMessageList();
|
|
else
|
|
folderCleared();
|
|
}
|
|
|
|
bool KMHeaders::writeSortOrder()
|
|
{
|
|
TQString sortFile = KMAIL_SORT_FILE(mFolder);
|
|
|
|
if (!mSortInfo.dirty) {
|
|
struct stat stat_tmp;
|
|
if(stat(TQFile::encodeName(sortFile), &stat_tmp) == -1) {
|
|
mSortInfo.dirty = true;
|
|
}
|
|
}
|
|
if (mSortInfo.dirty) {
|
|
if (!mFolder->count()) {
|
|
// Folder is empty now, remove the sort file.
|
|
unlink(TQFile::encodeName(sortFile));
|
|
return true;
|
|
}
|
|
TQString tempName = sortFile + ".temp";
|
|
unlink(TQFile::encodeName(tempName));
|
|
FILE *sortStream = fopen(TQFile::encodeName(tempName), "w");
|
|
if (!sortStream)
|
|
return false;
|
|
|
|
mSortInfo.ascending = !mSortDescending;
|
|
mSortInfo.dirty = false;
|
|
mSortInfo.column = mSortCol;
|
|
fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION);
|
|
//magic header information
|
|
TQ_INT32 byteOrder = 0x12345678;
|
|
TQ_INT32 column = mSortCol;
|
|
TQ_INT32 ascending= !mSortDescending;
|
|
TQ_INT32 threaded = isThreaded();
|
|
TQ_INT32 appended=0;
|
|
TQ_INT32 discovered_count = 0;
|
|
TQ_INT32 sorted_count=0;
|
|
fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
|
|
fwrite(&column, sizeof(column), 1, sortStream);
|
|
fwrite(&ascending, sizeof(ascending), 1, sortStream);
|
|
fwrite(&threaded, sizeof(threaded), 1, sortStream);
|
|
fwrite(&appended, sizeof(appended), 1, sortStream);
|
|
fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
|
|
fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
|
|
|
|
TQPtrStack<HeaderItem> items;
|
|
{
|
|
TQPtrStack<TQListViewItem> s;
|
|
for (TQListViewItem * i = firstChild(); i; ) {
|
|
items.push((HeaderItem *)i);
|
|
if ( i->firstChild() ) {
|
|
s.push( i );
|
|
i = i->firstChild();
|
|
} else if( i->nextSibling()) {
|
|
i = i->nextSibling();
|
|
} else {
|
|
for(i=0; !i && s.count(); i = s.pop()->nextSibling())
|
|
;
|
|
}
|
|
}
|
|
}
|
|
|
|
KMMsgBase *kmb;
|
|
while(HeaderItem *i = items.pop()) {
|
|
int parent_id = -1; //no parent, top level
|
|
if (threaded) {
|
|
kmb = mFolder->getMsgBase( i->msgId() );
|
|
assert(kmb); // I have seen 0L come out of this, called from
|
|
// KMHeaders::setFolder(0xgoodpointer, false);
|
|
// I see this crash too. after rebuilding a broken index on a dimap folder. always
|
|
TQString replymd5 = kmb->replyToIdMD5();
|
|
TQString replyToAuxId = kmb->replyToAuxIdMD5();
|
|
SortCacheItem *p = NULL;
|
|
if(!replymd5.isEmpty())
|
|
p = mSortCacheItems[replymd5];
|
|
|
|
if (p)
|
|
parent_id = p->id();
|
|
// We now have either found a parent, or set it to -1, which means that
|
|
// it will be reevaluated when a message is added, for example. If there
|
|
// is no replyToId and no replyToAuxId and the message is not prefixed,
|
|
// this message is top level, and will always be, so no need to
|
|
// reevaluate it.
|
|
if (replymd5.isEmpty()
|
|
&& replyToAuxId.isEmpty()
|
|
&& !kmb->subjectIsPrefixed() )
|
|
parent_id = -2;
|
|
// FIXME also mark messages with -1 as -2 a certain amount of time after
|
|
// their arrival, since it becomes very unlikely that a new parent for
|
|
// them will show up. (Ingo suggests a month.) -till
|
|
}
|
|
internalWriteItem(sortStream, mFolder, i->msgId(), parent_id,
|
|
i->key(mSortCol, !mSortDescending), false);
|
|
//double check for magic headers
|
|
sorted_count++;
|
|
}
|
|
|
|
//magic header twice, case they've changed
|
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET);
|
|
fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
|
|
fwrite(&column, sizeof(column), 1, sortStream);
|
|
fwrite(&ascending, sizeof(ascending), 1, sortStream);
|
|
fwrite(&threaded, sizeof(threaded), 1, sortStream);
|
|
fwrite(&appended, sizeof(appended), 1, sortStream);
|
|
fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
|
|
fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
|
|
if (sortStream && ferror(sortStream)) {
|
|
fclose(sortStream);
|
|
unlink(TQFile::encodeName(sortFile));
|
|
kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
|
|
kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
|
|
kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
|
|
}
|
|
fclose(sortStream);
|
|
::rename(TQFile::encodeName(tempName), TQFile::encodeName(sortFile));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void KMHeaders::appendItemToSortFile(HeaderItem *khi)
|
|
{
|
|
TQString sortFile = KMAIL_SORT_FILE(mFolder);
|
|
if(FILE *sortStream = fopen(TQFile::encodeName(sortFile), "r+")) {
|
|
int parent_id = -1; //no parent, top level
|
|
|
|
if (isThreaded()) {
|
|
SortCacheItem *sci = khi->sortCacheItem();
|
|
KMMsgBase *kmb = mFolder->getMsgBase( khi->msgId() );
|
|
if(sci->parent() && !sci->isImperfectlyThreaded())
|
|
parent_id = sci->parent()->id();
|
|
else if(kmb->replyToIdMD5().isEmpty()
|
|
&& kmb->replyToAuxIdMD5().isEmpty()
|
|
&& !kmb->subjectIsPrefixed())
|
|
parent_id = -2;
|
|
}
|
|
|
|
internalWriteItem(sortStream, mFolder, khi->msgId(), parent_id,
|
|
khi->key(mSortCol, !mSortDescending), false);
|
|
|
|
//update the appended flag FIXME obsolete?
|
|
TQ_INT32 appended = 1;
|
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
|
|
fwrite(&appended, sizeof(appended), 1, sortStream);
|
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
|
|
|
|
if (sortStream && ferror(sortStream)) {
|
|
fclose(sortStream);
|
|
unlink(TQFile::encodeName(sortFile));
|
|
kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
|
|
kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
|
|
kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
|
|
}
|
|
fclose(sortStream);
|
|
} else {
|
|
mSortInfo.dirty = true;
|
|
}
|
|
}
|
|
|
|
void KMHeaders::dirtySortOrder(int column)
|
|
{
|
|
mSortInfo.dirty = true;
|
|
TQObject::disconnect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
|
|
setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true);
|
|
}
|
|
|
|
// -----------------
|
|
void SortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder,
|
|
bool waiting_for_parent, bool update_discover)
|
|
{
|
|
if(mSortOffset == -1) {
|
|
fseek(sortStream, 0, SEEK_END);
|
|
mSortOffset = ftell(sortStream);
|
|
} else {
|
|
fseek(sortStream, mSortOffset, SEEK_SET);
|
|
}
|
|
|
|
int parent_id = -1;
|
|
if(!waiting_for_parent) {
|
|
if(mParent && !isImperfectlyThreaded())
|
|
parent_id = mParent->id();
|
|
}
|
|
internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover);
|
|
}
|
|
|
|
static bool compare_ascending = false;
|
|
static bool compare_toplevel = true;
|
|
static int compare_SortCacheItem(const void *s1, const void *s2)
|
|
{
|
|
if ( !s1 || !s2 )
|
|
return 0;
|
|
SortCacheItem **b1 = (SortCacheItem **)s1;
|
|
SortCacheItem **b2 = (SortCacheItem **)s2;
|
|
int ret = (*b1)->key().compare((*b2)->key());
|
|
if(compare_ascending || !compare_toplevel)
|
|
ret = -ret;
|
|
return ret;
|
|
}
|
|
|
|
// Debugging helpers
|
|
void KMHeaders::printSubjectThreadingTree()
|
|
{
|
|
TQDictIterator< TQPtrList< SortCacheItem > > it ( mSubjectLists );
|
|
kdDebug(5006) << "SubjectThreading tree: " << endl;
|
|
for( ; it.current(); ++it ) {
|
|
TQPtrList<SortCacheItem> list = *( it.current() );
|
|
TQPtrListIterator<SortCacheItem> it2( list ) ;
|
|
kdDebug(5006) << "Subject MD5: " << it.currentKey() << " list: " << endl;
|
|
for( ; it2.current(); ++it2 ) {
|
|
SortCacheItem *sci = it2.current();
|
|
kdDebug(5006) << " item:" << sci << " sci id: " << sci->id() << endl;
|
|
}
|
|
}
|
|
kdDebug(5006) << endl;
|
|
}
|
|
|
|
void KMHeaders::printThreadingTree()
|
|
{
|
|
kdDebug(5006) << "Threading tree: " << endl;
|
|
TQDictIterator<SortCacheItem> it( mSortCacheItems );
|
|
kdDebug(5006) << endl;
|
|
for( ; it.current(); ++it ) {
|
|
SortCacheItem *sci = it.current();
|
|
kdDebug(5006) << "MsgId MD5: " << it.currentKey() << " message id: " << sci->id() << endl;
|
|
}
|
|
for (int i = 0; i < (int)mItems.size(); ++i) {
|
|
HeaderItem *item = mItems[i];
|
|
int parentCacheId = item->sortCacheItem()->parent()?item->sortCacheItem()->parent()->id():0;
|
|
kdDebug( 5006 ) << "SortCacheItem: " << item->sortCacheItem()->id() << " parent: " << parentCacheId << endl;
|
|
kdDebug( 5006 ) << "Item: " << item << " sortCache: " << item->sortCacheItem() << " parent: " << item->sortCacheItem()->parent() << endl;
|
|
}
|
|
kdDebug(5006) << endl;
|
|
}
|
|
|
|
// -------------------------------------
|
|
|
|
void KMHeaders::buildThreadingTree( TQMemArray<SortCacheItem *> sortCache )
|
|
{
|
|
mSortCacheItems.clear();
|
|
mSortCacheItems.resize( mFolder->count() * 2 );
|
|
|
|
// build a dict of all message id's
|
|
for(int x = 0; x < mFolder->count(); x++) {
|
|
KMMsgBase *mi = mFolder->getMsgBase(x);
|
|
TQString md5 = mi->msgIdMD5();
|
|
if(!md5.isEmpty())
|
|
mSortCacheItems.tqreplace(md5, sortCache[x]);
|
|
}
|
|
}
|
|
|
|
|
|
void KMHeaders::buildSubjectThreadingTree( TQMemArray<SortCacheItem *> sortCache )
|
|
{
|
|
mSubjectLists.clear(); // autoDelete is true
|
|
mSubjectLists.resize( mFolder->count() * 2 );
|
|
|
|
for(int x = 0; x < mFolder->count(); x++) {
|
|
// Only a lot items that are now toplevel
|
|
if ( sortCache[x]->parent()
|
|
&& sortCache[x]->parent()->id() != -666 ) continue;
|
|
KMMsgBase *mi = mFolder->getMsgBase(x);
|
|
TQString subjMD5 = mi->strippedSubjectMD5();
|
|
if (subjMD5.isEmpty()) {
|
|
mFolder->getMsgBase(x)->initStrippedSubjectMD5();
|
|
subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
|
|
}
|
|
if( subjMD5.isEmpty() ) continue;
|
|
|
|
/* For each subject, keep a list of items with that subject
|
|
* (stripped of prefixes) sorted by date. */
|
|
if (!mSubjectLists.find(subjMD5))
|
|
mSubjectLists.insert(subjMD5, new TQPtrList<SortCacheItem>());
|
|
/* Insertion sort by date. These lists are expected to be very small.
|
|
* Also, since the messages are roughly ordered by date in the store,
|
|
* they should mostly be prepended at the very start, so insertion is
|
|
* cheap. */
|
|
int p=0;
|
|
for (TQPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
|
|
it.current(); ++it) {
|
|
KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
|
|
if ( mb->date() < mi->date())
|
|
break;
|
|
p++;
|
|
}
|
|
mSubjectLists[subjMD5]->insert( p, sortCache[x]);
|
|
sortCache[x]->setSubjectThreadingList( mSubjectLists[subjMD5] );
|
|
}
|
|
}
|
|
|
|
|
|
SortCacheItem* KMHeaders::findParent(SortCacheItem *item)
|
|
{
|
|
SortCacheItem *parent = NULL;
|
|
if (!item) return parent;
|
|
KMMsgBase *msg = mFolder->getMsgBase(item->id());
|
|
TQString replyToIdMD5 = msg->replyToIdMD5();
|
|
item->setImperfectlyThreaded(true);
|
|
/* First, try if the message our Reply-To header points to
|
|
* is available to thread below. */
|
|
if(!replyToIdMD5.isEmpty()) {
|
|
parent = mSortCacheItems[replyToIdMD5];
|
|
if (parent)
|
|
item->setImperfectlyThreaded(false);
|
|
}
|
|
if (!parent) {
|
|
// If we dont have a replyToId, or if we have one and the
|
|
// corresponding message is not in this folder, as happens
|
|
// if you keep your outgoing messages in an OUTBOX, for
|
|
// example, try the list of references, because the second
|
|
// to last will likely be in this folder. replyToAuxIdMD5
|
|
// tqcontains the second to last one.
|
|
TQString ref = msg->replyToAuxIdMD5();
|
|
if (!ref.isEmpty())
|
|
parent = mSortCacheItems[ref];
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
SortCacheItem* KMHeaders::findParentBySubject(SortCacheItem *item)
|
|
{
|
|
SortCacheItem *parent = NULL;
|
|
if (!item) return parent;
|
|
|
|
KMMsgBase *msg = mFolder->getMsgBase(item->id());
|
|
|
|
// Let's try by subject, but only if the subject is prefixed.
|
|
// This is necessary to make for example cvs commit mailing lists
|
|
// work as expected without having to turn threading off alltogether.
|
|
if (!msg->subjectIsPrefixed())
|
|
return parent;
|
|
|
|
TQString replyToIdMD5 = msg->replyToIdMD5();
|
|
TQString subjMD5 = msg->strippedSubjectMD5();
|
|
if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) {
|
|
/* Iterate over the list of potential parents with the same
|
|
* subject, and take the closest one by date. */
|
|
for (TQPtrListIterator<SortCacheItem> it2(*mSubjectLists[subjMD5]);
|
|
it2.current(); ++it2) {
|
|
KMMsgBase *mb = mFolder->getMsgBase((*it2)->id());
|
|
if ( !mb ) return parent;
|
|
// make sure it's not ourselves
|
|
if ( item == (*it2) ) continue;
|
|
int delta = msg->date() - mb->date();
|
|
// delta == 0 is not allowed, to avoid circular threading
|
|
// with duplicates.
|
|
if (delta > 0 ) {
|
|
// Don't use parents more than 6 weeks older than us.
|
|
if (delta < 3628899)
|
|
parent = (*it2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread )
|
|
{
|
|
if (!mFolder->isOpened()) mFolder->open("kmheaders");
|
|
|
|
//all cases
|
|
TQ_INT32 column, ascending, threaded, discovered_count, sorted_count, appended;
|
|
TQ_INT32 deleted_count = 0;
|
|
bool unread_exists = false;
|
|
bool jumpToUnread = (GlobalSettings::self()->actionEnterFolder() ==
|
|
GlobalSettings::EnumActionEnterFolder::SelectFirstUnreadNew) ||
|
|
forceJumpToUnread;
|
|
HeaderItem *oldestItem = 0;
|
|
HeaderItem *newestItem = 0;
|
|
TQMemArray<SortCacheItem *> sortCache(mFolder->count());
|
|
bool error = false;
|
|
|
|
//threaded cases
|
|
TQPtrList<SortCacheItem> unparented;
|
|
mImperfectlyThreadedList.clear();
|
|
|
|
//cleanup
|
|
mItems.fill( 0, mFolder->count() );
|
|
sortCache.fill( 0 );
|
|
|
|
mRoot->clearChildren();
|
|
|
|
TQString sortFile = KMAIL_SORT_FILE(mFolder);
|
|
FILE *sortStream = fopen(TQFile::encodeName(sortFile), "r+");
|
|
mSortInfo.fakeSort = 0;
|
|
|
|
if(sortStream) {
|
|
mSortInfo.fakeSort = 1;
|
|
int version = 0;
|
|
if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1)
|
|
version = -1;
|
|
if(version == KMAIL_SORT_VERSION) {
|
|
TQ_INT32 byteOrder = 0;
|
|
fread(&byteOrder, sizeof(byteOrder), 1, sortStream);
|
|
if (byteOrder == 0x12345678)
|
|
{
|
|
fread(&column, sizeof(column), 1, sortStream);
|
|
fread(&ascending, sizeof(ascending), 1, sortStream);
|
|
fread(&threaded, sizeof(threaded), 1, sortStream);
|
|
fread(&appended, sizeof(appended), 1, sortStream);
|
|
fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
|
|
fread(&sorted_count, sizeof(sorted_count), 1, sortStream);
|
|
|
|
//Hackyness to work around qlistview problems
|
|
KListView::setSorting(-1);
|
|
header()->setSortIndicator(column, ascending);
|
|
TQObject::connect(header(), TQT_SIGNAL(clicked(int)), this, TQT_SLOT(dirtySortOrder(int)));
|
|
//setup mSortInfo here now, as above may change it
|
|
mSortInfo.dirty = false;
|
|
mSortInfo.column = (short)column;
|
|
mSortInfo.ascending = (compare_ascending = ascending);
|
|
|
|
SortCacheItem *item;
|
|
unsigned long serNum, parentSerNum;
|
|
int id, len, parent, x;
|
|
TQChar *tmp_qchar = 0;
|
|
int tmp_qchar_len = 0;
|
|
const int mFolderCount = mFolder->count();
|
|
TQString key;
|
|
|
|
CREATE_TIMER(parse);
|
|
START_TIMER(parse);
|
|
for(x = 0; !feof(sortStream) && !error; x++) {
|
|
off_t offset = ftell(sortStream);
|
|
KMFolder *folder;
|
|
//parse
|
|
if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end
|
|
!fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) ||
|
|
!fread(&len, sizeof(len), 1, sortStream)) {
|
|
break;
|
|
}
|
|
if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) {
|
|
kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl;
|
|
error = true;
|
|
continue;
|
|
}
|
|
if(len) {
|
|
if(len > tmp_qchar_len) {
|
|
tmp_qchar = (TQChar *)realloc(tmp_qchar, len);
|
|
tmp_qchar_len = len;
|
|
}
|
|
if(!fread(tmp_qchar, len, 1, sortStream))
|
|
break;
|
|
key = TQString(tmp_qchar, len / 2);
|
|
} else {
|
|
key = TQString(""); //yuck
|
|
}
|
|
|
|
KMMsgDict::instance()->getLocation(serNum, &folder, &id);
|
|
if (folder != mFolder) {
|
|
++deleted_count;
|
|
continue;
|
|
}
|
|
if (parentSerNum < KMAIL_RESERVED) {
|
|
parent = (int)parentSerNum - KMAIL_RESERVED;
|
|
} else {
|
|
KMMsgDict::instance()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent);
|
|
if (folder != mFolder)
|
|
parent = -1;
|
|
}
|
|
if ((id < 0) || (id >= mFolderCount) ||
|
|
(parent < -2) || (parent >= mFolderCount)) { // sanity checking
|
|
kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl;
|
|
error = true;
|
|
continue;
|
|
}
|
|
|
|
if ((item=sortCache[id])) {
|
|
if (item->id() != -1) {
|
|
kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl;
|
|
error = true;
|
|
continue;
|
|
}
|
|
item->setKey(key);
|
|
item->setId(id);
|
|
item->setOffset(offset);
|
|
} else {
|
|
item = sortCache[id] = new SortCacheItem(id, key, offset);
|
|
}
|
|
if (threaded && parent != -2) {
|
|
if(parent == -1) {
|
|
unparented.append(item);
|
|
mRoot->addUnsortedChild(item);
|
|
} else {
|
|
if( ! sortCache[parent] ) {
|
|
sortCache[parent] = new SortCacheItem;
|
|
}
|
|
sortCache[parent]->addUnsortedChild(item);
|
|
}
|
|
} else {
|
|
if(x < sorted_count )
|
|
mRoot->addSortedChild(item);
|
|
else {
|
|
mRoot->addUnsortedChild(item);
|
|
}
|
|
}
|
|
}
|
|
if (error || (x != sorted_count + discovered_count)) {// sanity check
|
|
kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl;
|
|
fclose(sortStream);
|
|
sortStream = 0;
|
|
}
|
|
|
|
if(tmp_qchar)
|
|
free(tmp_qchar);
|
|
END_TIMER(parse);
|
|
SHOW_TIMER(parse);
|
|
}
|
|
else {
|
|
fclose(sortStream);
|
|
sortStream = 0;
|
|
}
|
|
} else {
|
|
fclose(sortStream);
|
|
sortStream = 0;
|
|
}
|
|
}
|
|
|
|
if (!sortStream) {
|
|
mSortInfo.dirty = true;
|
|
mSortInfo.column = column = mSortCol;
|
|
mSortInfo.ascending = ascending = !mSortDescending;
|
|
threaded = (isThreaded());
|
|
sorted_count = discovered_count = appended = 0;
|
|
KListView::setSorting( mSortCol, !mSortDescending );
|
|
}
|
|
//fill in empty holes
|
|
if((sorted_count + discovered_count - deleted_count) < mFolder->count()) {
|
|
CREATE_TIMER(holes);
|
|
START_TIMER(holes);
|
|
KMMsgBase *msg = 0;
|
|
for(int x = 0; x < mFolder->count(); x++) {
|
|
if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) {
|
|
int sortOrder = column;
|
|
if (mPaintInfo.orderOfArrival)
|
|
sortOrder |= (1 << 6);
|
|
if (mPaintInfo.status)
|
|
sortOrder |= (1 << 5);
|
|
sortCache[x] = new SortCacheItem(
|
|
x, HeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder ));
|
|
if(threaded)
|
|
unparented.append(sortCache[x]);
|
|
else
|
|
mRoot->addUnsortedChild(sortCache[x]);
|
|
if(sortStream)
|
|
sortCache[x]->updateSortFile(sortStream, mFolder, true, true);
|
|
discovered_count++;
|
|
appended = 1;
|
|
}
|
|
}
|
|
END_TIMER(holes);
|
|
SHOW_TIMER(holes);
|
|
}
|
|
|
|
// Make sure we've placed everything in parent/child relationship. All
|
|
// messages with a parent id of -1 in the sort file are reevaluated here.
|
|
if (threaded) buildThreadingTree( sortCache );
|
|
TQPtrList<SortCacheItem> toBeSubjThreaded;
|
|
|
|
if (threaded && !unparented.isEmpty()) {
|
|
CREATE_TIMER(reparent);
|
|
START_TIMER(reparent);
|
|
|
|
for(TQPtrListIterator<SortCacheItem> it(unparented); it.current(); ++it) {
|
|
SortCacheItem *item = (*it);
|
|
SortCacheItem *parent = findParent( item );
|
|
// If we have a parent, make sure it's not ourselves
|
|
if ( parent && (parent != (*it)) ) {
|
|
parent->addUnsortedChild((*it));
|
|
if(sortStream)
|
|
(*it)->updateSortFile(sortStream, mFolder);
|
|
} else {
|
|
// if we will attempt subject threading, add to the list,
|
|
// otherwise to the root with them
|
|
if (mSubjThreading)
|
|
toBeSubjThreaded.append((*it));
|
|
else
|
|
mRoot->addUnsortedChild((*it));
|
|
}
|
|
}
|
|
|
|
if (mSubjThreading) {
|
|
buildSubjectThreadingTree( sortCache );
|
|
for(TQPtrListIterator<SortCacheItem> it(toBeSubjThreaded); it.current(); ++it) {
|
|
SortCacheItem *item = (*it);
|
|
SortCacheItem *parent = findParentBySubject( item );
|
|
|
|
if ( parent ) {
|
|
parent->addUnsortedChild((*it));
|
|
if(sortStream)
|
|
(*it)->updateSortFile(sortStream, mFolder);
|
|
} else {
|
|
//oh well we tried, to the root with you!
|
|
mRoot->addUnsortedChild((*it));
|
|
}
|
|
}
|
|
}
|
|
END_TIMER(reparent);
|
|
SHOW_TIMER(reparent);
|
|
}
|
|
//create headeritems
|
|
CREATE_TIMER(header_creation);
|
|
START_TIMER(header_creation);
|
|
HeaderItem *khi;
|
|
SortCacheItem *i, *new_kci;
|
|
TQPtrQueue<SortCacheItem> s;
|
|
s.enqueue(mRoot);
|
|
compare_toplevel = true;
|
|
do {
|
|
i = s.dequeue();
|
|
const TQPtrList<SortCacheItem> *sorted = i->sortedChildren();
|
|
int unsorted_count, unsorted_off=0;
|
|
SortCacheItem **unsorted = i->unsortedChildren(unsorted_count);
|
|
if(unsorted)
|
|
qsort(unsorted, unsorted_count, sizeof(SortCacheItem *), //sort
|
|
compare_SortCacheItem);
|
|
|
|
/* The sorted list now tqcontains all sorted children of this item, while
|
|
* the (aptly named) unsorted array tqcontains all as of yet unsorted
|
|
* ones. It has just been qsorted, so it is in itself sorted. These two
|
|
* sorted lists are now merged into one. */
|
|
for(TQPtrListIterator<SortCacheItem> it(*sorted);
|
|
(unsorted && unsorted_off < unsorted_count) || it.current(); ) {
|
|
/* As long as we have something in the sorted list and there is
|
|
nothing unsorted left, use the item from the sorted list. Also
|
|
if we are sorting descendingly and the sorted item is supposed
|
|
to be sorted before the unsorted one do so. In the ascending
|
|
case we invert the logic for non top level items. */
|
|
if( it.current() &&
|
|
( !unsorted || unsorted_off >= unsorted_count
|
|
||
|
|
( ( !ascending || (ascending && !compare_toplevel) )
|
|
&& (*it)->key() < unsorted[unsorted_off]->key() )
|
|
||
|
|
( ascending && (*it)->key() >= unsorted[unsorted_off]->key() )
|
|
)
|
|
)
|
|
{
|
|
new_kci = (*it);
|
|
++it;
|
|
} else {
|
|
/* Otherwise use the next item of the unsorted list */
|
|
new_kci = unsorted[unsorted_off++];
|
|
}
|
|
if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent
|
|
continue;
|
|
|
|
if(threaded && i->item()) {
|
|
// If the parent is watched or ignored, propagate that to it's
|
|
// children
|
|
if (mFolder->getMsgBase(i->id())->isWatched())
|
|
mFolder->getMsgBase(new_kci->id())->seStatus(KMMsgStatusWatched);
|
|
if (mFolder->getMsgBase(i->id())->isIgnored())
|
|
mFolder->getMsgBase(new_kci->id())->seStatus(KMMsgStatusIgnored);
|
|
khi = new HeaderItem(i->item(), new_kci->id(), new_kci->key());
|
|
} else {
|
|
khi = new HeaderItem(this, new_kci->id(), new_kci->key());
|
|
}
|
|
new_kci->setItem(mItems[new_kci->id()] = khi);
|
|
if(new_kci->hasChildren())
|
|
s.enqueue(new_kci);
|
|
// we always jump to new messages, but we only jump to
|
|
// unread messages if we are told to do so
|
|
if ( ( mFolder->getMsgBase(new_kci->id())->isNew() &&
|
|
GlobalSettings::self()->actionEnterFolder() ==
|
|
GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
|
|
( ( mFolder->getMsgBase(new_kci->id())->isNew() ||
|
|
mFolder->getMsgBase(new_kci->id())->isUnread() ) &&
|
|
jumpToUnread ) )
|
|
{
|
|
unread_exists = true;
|
|
}
|
|
|
|
if ( !oldestItem || mFolder->getMsgBase( oldestItem->msgId() )->date() >
|
|
mFolder->getMsgBase( new_kci->id() )->date() ) {
|
|
oldestItem = khi;
|
|
}
|
|
|
|
if ( !newestItem || mFolder->getMsgBase( newestItem->msgId() )->date() <
|
|
mFolder->getMsgBase( new_kci->id() )->date() ) {
|
|
newestItem = khi;
|
|
}
|
|
}
|
|
// If we are sorting by date and ascending the top level items are sorted
|
|
// ascending and the threads themselves are sorted descending. One wants
|
|
// to have new threads on top but the threads themselves top down.
|
|
if (mSortCol == paintInfo()->dateCol)
|
|
compare_toplevel = false;
|
|
} while(!s.isEmpty());
|
|
|
|
for(int x = 0; x < mFolder->count(); x++) { //cleanup
|
|
if (!sortCache[x]) { // not yet there?
|
|
continue;
|
|
}
|
|
|
|
if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
|
|
kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. "
|
|
<< endl << "Please talk to your threading counselor asap. " << endl;
|
|
khi = new HeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
|
|
sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
|
|
}
|
|
// Add all imperfectly threaded items to a list, so they can be
|
|
// reevaluated when a new message arrives which might be a better parent.
|
|
// Important for messages arriving out of order.
|
|
if (threaded && sortCache[x]->isImperfectlyThreaded()) {
|
|
mImperfectlyThreadedList.append(sortCache[x]->item());
|
|
}
|
|
// Set the reverse mapping HeaderItem -> SortCacheItem. Needed for
|
|
// keeping the data structures up to date on removal, for example.
|
|
sortCache[x]->item()->setSortCacheItem(sortCache[x]);
|
|
}
|
|
|
|
if (getNestingPolicy()<2)
|
|
for (HeaderItem *khi=static_cast<HeaderItem*>(firstChild()); khi!=0;khi=static_cast<HeaderItem*>(khi->nextSibling()))
|
|
khi->setOpen(true);
|
|
|
|
END_TIMER(header_creation);
|
|
SHOW_TIMER(header_creation);
|
|
|
|
if(sortStream) { //update the .sorted file now
|
|
// heuristic for when it's time to rewrite the .sorted file
|
|
if( discovered_count * discovered_count > sorted_count - deleted_count ) {
|
|
mSortInfo.dirty = true;
|
|
} else {
|
|
//update the appended flag
|
|
appended = 0;
|
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
|
|
fwrite(&appended, sizeof(appended), 1, sortStream);
|
|
}
|
|
}
|
|
|
|
// Select a message, depending on the "When entering a folder:" setting
|
|
CREATE_TIMER(selection);
|
|
START_TIMER(selection);
|
|
if(set_selection) {
|
|
|
|
// Search for the id of the first unread/new item, should there be any
|
|
int first_unread = -1;
|
|
if (unread_exists) {
|
|
HeaderItem *item = static_cast<HeaderItem*>(firstChild());
|
|
while (item) {
|
|
if ( ( mFolder->getMsgBase(item->msgId())->isNew() &&
|
|
GlobalSettings::self()->actionEnterFolder() ==
|
|
GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
|
|
( ( mFolder->getMsgBase(item->msgId())->isNew() ||
|
|
mFolder->getMsgBase(item->msgId())->isUnread() ) &&
|
|
jumpToUnread ) )
|
|
{
|
|
first_unread = item->msgId();
|
|
break;
|
|
}
|
|
item = static_cast<HeaderItem*>(item->itemBelow());
|
|
}
|
|
}
|
|
|
|
// No unread message to select, so either select the newest, oldest or lastest selected
|
|
if(first_unread == -1 ) {
|
|
setTopItemByIndex( mTopItem );
|
|
|
|
if ( GlobalSettings::self()->actionEnterFolder() ==
|
|
GlobalSettings::EnumActionEnterFolder::SelectNewest && newestItem != 0 ) {
|
|
setCurrentItemByIndex( newestItem->msgId() );
|
|
}
|
|
else if ( GlobalSettings::self()->actionEnterFolder() ==
|
|
GlobalSettings::EnumActionEnterFolder::SelectOldest && oldestItem != 0 ) {
|
|
setCurrentItemByIndex( oldestItem->msgId() );
|
|
}
|
|
else if ( mCurrentItem >= 0 )
|
|
setCurrentItemByIndex( mCurrentItem );
|
|
else if ( mCurrentItemSerNum > 0 )
|
|
setCurrentItemBySerialNum( mCurrentItemSerNum );
|
|
else
|
|
setCurrentItemByIndex( 0 );
|
|
|
|
// There is an unread item to select, so select it
|
|
} else {
|
|
setCurrentItemByIndex(first_unread);
|
|
makeHeaderVisible();
|
|
center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 );
|
|
}
|
|
|
|
// we are told to not change the selection
|
|
} else {
|
|
// only reset the selection if we have no current item
|
|
if (mCurrentItem <= 0) {
|
|
setTopItemByIndex(mTopItem);
|
|
setCurrentItemByIndex(0);
|
|
}
|
|
}
|
|
END_TIMER(selection);
|
|
SHOW_TIMER(selection);
|
|
if (error || (sortStream && ferror(sortStream))) {
|
|
if ( sortStream )
|
|
fclose(sortStream);
|
|
unlink(TQFile::encodeName(sortFile));
|
|
kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
|
|
kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
|
|
|
|
return true;
|
|
}
|
|
if(sortStream)
|
|
fclose(sortStream);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum )
|
|
{
|
|
// Linear search == slow. Don't overuse this method.
|
|
// It's currently only used for finding the current item again
|
|
// after expiry deleted mails (so the index got tqinvalidated).
|
|
for (int i = 0; i < (int)mItems.size() - 1; ++i) {
|
|
KMMsgBase *mMsgBase = mFolder->getMsgBase( i );
|
|
if ( mMsgBase->getMsgSerNum() == serialNum ) {
|
|
bool unchanged = (currentItem() == mItems[i]);
|
|
setCurrentItem( mItems[i] );
|
|
setSelected( mItems[i], true );
|
|
setSelectionAnchor( currentItem() );
|
|
if ( unchanged )
|
|
highlightMessage( currentItem(), false );
|
|
ensureCurrentItemVisible();
|
|
return;
|
|
}
|
|
}
|
|
// Not found. Maybe we should select the last item instead?
|
|
kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl;
|
|
}
|
|
|
|
void KMHeaders::copyMessages()
|
|
{
|
|
mCopiedMessages.clear();
|
|
KMMessageList* list = selectedMsgs();
|
|
for ( uint i = 0; i < list->count(); ++ i )
|
|
mCopiedMessages << list->at( i )->getMsgSerNum();
|
|
mMoveMessages = false;
|
|
updateActions();
|
|
triggerUpdate();
|
|
}
|
|
|
|
void KMHeaders::cutMessages()
|
|
{
|
|
mCopiedMessages.clear();
|
|
KMMessageList* list = selectedMsgs();
|
|
for ( uint i = 0; i < list->count(); ++ i )
|
|
mCopiedMessages << list->at( i )->getMsgSerNum();
|
|
mMoveMessages = true;
|
|
updateActions();
|
|
triggerUpdate();
|
|
}
|
|
|
|
void KMHeaders::pasteMessages()
|
|
{
|
|
new MessageCopyHelper( mCopiedMessages, folder(), mMoveMessages, this );
|
|
if ( mMoveMessages ) {
|
|
mCopiedMessages.clear();
|
|
updateActions();
|
|
}
|
|
}
|
|
|
|
void KMHeaders::updateActions()
|
|
{
|
|
KAction *copy = owner()->action( "copy_messages" );
|
|
KAction *cut = owner()->action( "cut_messages" );
|
|
KAction *paste = owner()->action( "paste_messages" );
|
|
|
|
if ( selectedItems().isEmpty() ) {
|
|
copy->setEnabled( false );
|
|
cut->setEnabled( false );
|
|
} else {
|
|
copy->setEnabled( true );
|
|
if ( folder() && !folder()->canDeleteMessages() )
|
|
cut->setEnabled( false );
|
|
else
|
|
cut->setEnabled( true );
|
|
}
|
|
|
|
if ( mCopiedMessages.isEmpty() || !folder() || folder()->isReadOnly() )
|
|
paste->setEnabled( false );
|
|
else
|
|
paste->setEnabled( true );
|
|
}
|
|
|
|
void KMHeaders::setCopiedMessages(const TQValueList< TQ_UINT32 > & msgs, bool move)
|
|
{
|
|
mCopiedMessages = msgs;
|
|
mMoveMessages = move;
|
|
updateActions();
|
|
}
|
|
|
|
bool KMHeaders::isMessageCut(TQ_UINT32 serNum) const
|
|
{
|
|
return mMoveMessages && mCopiedMessages.tqcontains( serNum );
|
|
}
|
|
|
|
TQValueList< TQ_UINT32 > KMHeaders::selectedSernums()
|
|
{
|
|
TQValueList<TQ_UINT32> list;
|
|
for ( TQListViewItemIterator it(this); it.current(); it++ ) {
|
|
if ( it.current()->isSelected() && it.current()->isVisible() ) {
|
|
HeaderItem* item = static_cast<HeaderItem*>( it.current() );
|
|
KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
|
|
if ( msgBase ) {
|
|
list.append( msgBase->getMsgSerNum() );
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
TQValueList< TQ_UINT32 > KMHeaders::selectedVisibleSernums()
|
|
{
|
|
TQValueList<TQ_UINT32> list;
|
|
TQListViewItemIterator it(this, TQListViewItemIterator::Selected|TQListViewItemIterator::Visible);
|
|
while( it.current() ) {
|
|
if ( it.current()->isSelected() && it.current()->isVisible() ) {
|
|
if ( it.current()->parent() && ( !it.current()->parent()->isOpen() ) ) {
|
|
// the item's parent is closed, don't traverse any more of this subtree
|
|
TQListViewItem * lastAncestorWithSiblings = it.current()->parent();
|
|
// travel towards the root until we find an ancestor with siblings
|
|
while ( ( lastAncestorWithSiblings->depth() > 0 ) && !lastAncestorWithSiblings->nextSibling() )
|
|
lastAncestorWithSiblings = lastAncestorWithSiblings->parent();
|
|
// move the iterator to that ancestor's next sibling
|
|
it = TQListViewItemIterator( lastAncestorWithSiblings->nextSibling() );
|
|
continue;
|
|
}
|
|
HeaderItem *item = static_cast<HeaderItem*>(it.current());
|
|
KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
|
|
if ( msgBase ) {
|
|
list.append( msgBase->getMsgSerNum() );
|
|
}
|
|
}
|
|
++it;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
#include "kmheaders.moc"
|