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.
3218 lines
101 KiB
3218 lines
101 KiB
// kmheaders.cpp |
|
|
|
#include <config.h> |
|
|
|
#include "kmheaders.h" |
|
|
|
#include "kcursorsaver.h" |
|
#include "kmcommands.h" |
|
#include "kmfolderimap.h" |
|
#include "kmmainwidget.h" |
|
#include "kmcomposewin.h" |
|
#include "kmfiltermgr.h" |
|
#include "undostack.h" |
|
#include "kmmsgdict.h" |
|
#include "kmkernel.h" |
|
using KMail::FolderJob; |
|
#include "kmbroadcaststatus.h" |
|
|
|
#include <kapplication.h> |
|
#include <kglobalsettings.h> |
|
#include <kmessagebox.h> |
|
#include <kiconloader.h> |
|
#include <kimageio.h> |
|
#include <kconfig.h> |
|
#include <klocale.h> |
|
#include <kdebug.h> |
|
|
|
#include <qbuffer.h> |
|
#include <qfile.h> |
|
#include <qheader.h> |
|
#include <qptrstack.h> |
|
#include <qptrqueue.h> |
|
#include <qpainter.h> |
|
#include <qtextcodec.h> |
|
#include <qbitmap.h> |
|
|
|
#include <mimelib/enum.h> |
|
#include <mimelib/field.h> |
|
#include <mimelib/mimepp.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#if 0 //timing utilities |
|
#include <qdatetime.h> |
|
#define CREATE_TIMER(x) int x=0, x ## _tmp=0; QTime x ## _tmp2 |
|
#define START_TIMER(x) x ## _tmp2 = QTime::currentTime() |
|
#define GRAB_TIMER(x) x ## _tmp2.msecsTo(QTime::currentTime()) |
|
#define END_TIMER(x) x += GRAB_TIMER(x); x ## _tmp++ |
|
#define SHOW_TIMER(x) qDebug(#x " == %d (%d)", x, x ## _tmp) |
|
#else |
|
#define CREATE_TIMER(x) |
|
#define START_TIMER(x) |
|
#define GRAB_TIMER(x) |
|
#define END_TIMER(x) |
|
#define SHOW_TIMER(x) |
|
#endif |
|
|
|
QPixmap* KMHeaders::pixNew = 0; |
|
QPixmap* KMHeaders::pixUns = 0; |
|
QPixmap* KMHeaders::pixDel = 0; |
|
QPixmap* KMHeaders::pixRead = 0; |
|
QPixmap* KMHeaders::pixRep = 0; |
|
QPixmap* KMHeaders::pixQueued = 0; |
|
QPixmap* KMHeaders::pixSent = 0; |
|
QPixmap* KMHeaders::pixFwd = 0; |
|
QPixmap* KMHeaders::pixFlag = 0; |
|
QPixmap* KMHeaders::pixWatched = 0; |
|
QPixmap* KMHeaders::pixIgnored = 0; |
|
QPixmap* KMHeaders::pixFullySigned = 0; |
|
QPixmap* KMHeaders::pixPartiallySigned = 0; |
|
QPixmap* KMHeaders::pixUndefinedSigned = 0; |
|
QPixmap* KMHeaders::pixFullyEncrypted = 0; |
|
QPixmap* KMHeaders::pixPartiallyEncrypted = 0; |
|
QPixmap* KMHeaders::pixUndefinedEncrypted = 0; |
|
QPixmap* KMHeaders::pixFiller = 0; |
|
QPixmap* KMHeaders::pixEncryptionProblematic = 0; |
|
QPixmap* KMHeaders::pixSignatureProblematic = 0; |
|
|
|
bool KMHeaders::mTrue = true; |
|
bool KMHeaders::mFalse = false; |
|
|
|
//----------------------------------------------------------------------------- |
|
// KMHeaderItem method definitions |
|
|
|
class KMHeaderItem : public KListViewItem |
|
{ |
|
|
|
public: |
|
int mMsgId; |
|
QString mKey; |
|
// WARNING: Do not add new member variables to the class |
|
|
|
// Constuction a new list view item with the given colors and pixmap |
|
KMHeaderItem( QListView* parent, int msgId, QString key = QString::null) |
|
: KListViewItem( parent ), |
|
mMsgId( msgId ), |
|
mKey( key ), |
|
mAboutToBeDeleted( false ) |
|
{ |
|
irefresh(); |
|
} |
|
|
|
// Constuction a new list view item with the given parent, colors, & pixmap |
|
KMHeaderItem( QListViewItem* parent, int msgId, QString key = QString::null) |
|
: KListViewItem( parent ), |
|
mMsgId( msgId ), |
|
mKey( key ), |
|
mAboutToBeDeleted( false ) |
|
{ |
|
irefresh(); |
|
} |
|
|
|
// Update the msgId this item corresponds to. |
|
void setMsgId( int aMsgId ) |
|
{ |
|
mMsgId = aMsgId; |
|
} |
|
|
|
// Profiling note: About 30% of the time taken to initialize the |
|
// listview is spent in this function. About 60% is spent in operator |
|
// new and QListViewItem::QListViewItem. |
|
void irefresh() |
|
{ |
|
KMHeaders *headers = static_cast<KMHeaders*>(listView()); |
|
NestingPolicy threadingPolicy = headers->getNestingPolicy(); |
|
if ((threadingPolicy == AlwaysOpen) || |
|
(threadingPolicy == DefaultOpen)) { |
|
//Avoid opening items as QListView is currently slow to do so. |
|
if (parent()) |
|
parent()->setOpen(true); |
|
return; |
|
} |
|
if (threadingPolicy == DefaultClosed) |
|
return; //default to closed |
|
|
|
// otherwise threadingPolicy == OpenUnread |
|
if (parent() && parent()->isOpen()) { |
|
setOpen(true); |
|
return; |
|
} |
|
|
|
KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); |
|
if (mMsgBase->isNew() || mMsgBase->isUnread() |
|
|| mMsgBase->isFlag() || mMsgBase->isWatched() ) { |
|
setOpen(true); |
|
KMHeaderItem * topOfThread = this; |
|
while(topOfThread->parent()) |
|
topOfThread = (KMHeaderItem*)topOfThread->parent(); |
|
topOfThread->setOpenRecursive(true); |
|
} |
|
} |
|
|
|
// Return the msgId of the message associated with this item |
|
int msgId() |
|
{ |
|
return mMsgId; |
|
} |
|
|
|
// Update this item to summarise a new folder and message |
|
void reset( int aMsgId ) |
|
{ |
|
mMsgId = aMsgId; |
|
irefresh(); |
|
} |
|
|
|
//Opens all children in the thread |
|
void setOpenRecursive( bool open ) |
|
{ |
|
if (open){ |
|
QListViewItem * lvchild; |
|
lvchild = firstChild(); |
|
while (lvchild){ |
|
((KMHeaderItem*)lvchild)->setOpenRecursive( true ); |
|
lvchild = lvchild->nextSibling(); |
|
} |
|
setOpen( true ); |
|
} else { |
|
setOpen( false ); |
|
} |
|
} |
|
|
|
QString text( int col) const |
|
{ |
|
KMHeaders *headers = static_cast<KMHeaders*>(listView()); |
|
KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); |
|
QString tmp; |
|
|
|
if(col == headers->paintInfo()->flagCol) { |
|
if (headers->paintInfo()->flagCol >= 0) |
|
tmp = QString( QChar( (char)mMsgBase->status() )); |
|
|
|
} else if(col == headers->paintInfo()->senderCol) { |
|
if (headers->folder()->whoField().lower() == "to") |
|
tmp = mMsgBase->toStrip(); |
|
else |
|
tmp = mMsgBase->fromStrip(); |
|
if (tmp.isEmpty()) |
|
tmp = i18n("Unknown"); |
|
else |
|
tmp = tmp.simplifyWhiteSpace(); |
|
|
|
} else if(col == headers->paintInfo()->subCol) { |
|
tmp = mMsgBase->subject(); |
|
if (tmp.isEmpty()) |
|
tmp = i18n("No Subject"); |
|
else |
|
tmp = tmp.simplifyWhiteSpace(); |
|
|
|
} else if(col == headers->paintInfo()->dateCol) { |
|
tmp = headers->mDate.dateString( mMsgBase->date() ); |
|
} else if(col == headers->paintInfo()->sizeCol |
|
&& headers->paintInfo()->showSize) { |
|
if ( mMsgBase->parent()->folderType() == KMFolderTypeImap ) |
|
{ |
|
QCString cstr; |
|
headers->folder()->getMsgString(mMsgId, cstr); |
|
int a = cstr.find("\nX-Length: "); |
|
if(a != -1) { |
|
int b = cstr.find('\n', a+11); |
|
tmp = KIO::convertSize(cstr.mid(a+11, b-a-11).toULong()); |
|
} |
|
} else tmp = KIO::convertSize(mMsgBase->msgSize()); |
|
} |
|
return tmp; |
|
} |
|
|
|
void setup() |
|
{ |
|
widthChanged(); |
|
const int ph = KMHeaders::pixNew->height(); |
|
QListView *v = listView(); |
|
int h = QMAX( v->fontMetrics().height(), ph ) + 2*v->itemMargin(); |
|
h = QMAX( h, QApplication::globalStrut().height()); |
|
if ( h % 2 > 0 ) |
|
h++; |
|
setHeight( h ); |
|
} |
|
|
|
typedef QValueList<QPixmap> PixmapList; |
|
|
|
QPixmap pixmapMerge( PixmapList pixmaps ) const { |
|
int width = 0; |
|
int height = 0; |
|
for ( PixmapList::ConstIterator it = pixmaps.begin(); |
|
it != pixmaps.end(); ++it ) { |
|
width += (*it).width(); |
|
height = QMAX( height, (*it).height() ); |
|
} |
|
|
|
QPixmap res( width, height ); |
|
QBitmap mask( width, height ); |
|
|
|
int x = 0; |
|
for ( PixmapList::ConstIterator it = pixmaps.begin(); |
|
it != pixmaps.end(); ++it ) { |
|
bitBlt( &res, x, 0, &(*it) ); |
|
bitBlt( &mask, x, 0, (*it).mask() ); |
|
x += (*it).width(); |
|
} |
|
|
|
res.setMask( mask ); |
|
return res; |
|
} |
|
|
|
|
|
const QPixmap * pixmap( int col) const |
|
{ |
|
if(!col) { |
|
KMHeaders *headers = static_cast<KMHeaders*>(listView()); |
|
KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); |
|
|
|
PixmapList pixmaps; |
|
|
|
// Have the watched/ignored icons first, I guess. |
|
if(mMsgBase->isIgnored()) pixmaps << *KMHeaders::pixIgnored; |
|
if(mMsgBase->isWatched()) pixmaps << *KMHeaders::pixWatched; |
|
|
|
if(mMsgBase->isNew()) pixmaps << *KMHeaders::pixNew; |
|
if(mMsgBase->isRead() || mMsgBase->isOld()) pixmaps << *KMHeaders::pixRead; |
|
if(mMsgBase->isUnread()) pixmaps << *KMHeaders::pixUns; |
|
if(mMsgBase->isDeleted()) pixmaps << *KMHeaders::pixDel; |
|
if(mMsgBase->isFlag()) pixmaps << *KMHeaders::pixFlag; |
|
if(mMsgBase->isReplied()) pixmaps << *KMHeaders::pixRep; |
|
if(mMsgBase->isForwarded()) pixmaps << *KMHeaders::pixFwd; |
|
if(mMsgBase->isQueued()) pixmaps << *KMHeaders::pixQueued; |
|
if(mMsgBase->isSent()) pixmaps << *KMHeaders::pixSent; |
|
|
|
// Only merge the crypto icons in if that is configured. |
|
if( headers->paintInfo()->showCryptoIcons ) { |
|
if( mMsgBase->encryptionState() == KMMsgFullyEncrypted ) |
|
pixmaps << *KMHeaders::pixFullyEncrypted; |
|
else if( mMsgBase->encryptionState() == KMMsgPartiallyEncrypted ) |
|
pixmaps << *KMHeaders::pixPartiallyEncrypted; |
|
else if( mMsgBase->encryptionState() == KMMsgEncryptionStateUnknown ) |
|
pixmaps << *KMHeaders::pixUndefinedEncrypted; |
|
else if( mMsgBase->encryptionState() == KMMsgEncryptionProblematic ) |
|
pixmaps << *KMHeaders::pixEncryptionProblematic; |
|
else |
|
pixmaps << *KMHeaders::pixFiller; |
|
|
|
if( mMsgBase->signatureState() == KMMsgFullySigned ) |
|
pixmaps << *KMHeaders::pixFullySigned; |
|
else if( mMsgBase->signatureState() == KMMsgPartiallySigned ) |
|
pixmaps << *KMHeaders::pixPartiallySigned; |
|
else if( mMsgBase->signatureState() == KMMsgSignatureStateUnknown ) |
|
pixmaps << *KMHeaders::pixUndefinedSigned; |
|
else if( mMsgBase->signatureState() == KMMsgSignatureProblematic ) |
|
pixmaps << *KMHeaders::pixSignatureProblematic; |
|
else |
|
pixmaps << *KMHeaders::pixFiller; |
|
} |
|
|
|
static QPixmap mergedpix; |
|
mergedpix = pixmapMerge( pixmaps ); |
|
return &mergedpix; |
|
} |
|
return 0; |
|
} |
|
|
|
void paintCell( QPainter * p, const QColorGroup & cg, |
|
int column, int width, int align ) |
|
{ |
|
KMHeaders *headers = static_cast<KMHeaders*>(listView()); |
|
if (headers->noRepaint) return; |
|
if (!headers->folder()) return; |
|
QColorGroup _cg( cg ); |
|
QColor c = _cg.text(); |
|
QColor *color; |
|
|
|
KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); |
|
if (!mMsgBase) return; |
|
|
|
color = (QColor *)(&headers->paintInfo()->colFore); |
|
// new overrides unread, and flagged overrides new. |
|
if (mMsgBase->isUnread()) color = (QColor*)(&headers->paintInfo()->colUnread); |
|
if (mMsgBase->isNew()) color = (QColor*)(&headers->paintInfo()->colNew); |
|
if (mMsgBase->isFlag()) color = (QColor*)(&headers->paintInfo()->colFlag); |
|
|
|
_cg.setColor( QColorGroup::Text, *color ); |
|
|
|
if( column == headers->paintInfo()->dateCol ) |
|
p->setFont(headers->dateFont); |
|
|
|
KListViewItem::paintCell( p, _cg, column, width, align ); |
|
|
|
_cg.setColor( QColorGroup::Text, c ); |
|
} |
|
|
|
static QString generate_key( int id, KMHeaders *headers, KMMsgBase *msg, const KPaintInfo *paintInfo, int sortOrder) |
|
{ |
|
// It appears, that QListView in Qt-3.0 asks for the key |
|
// in QListView::clear(), which is called from |
|
// readSortOrder() |
|
if (!msg) return QString::null; |
|
|
|
int column = sortOrder & ((1 << 5) - 1); |
|
QString ret = QChar( (char)sortOrder ); |
|
QString sortArrival = QString( "%1" ) |
|
.arg( kernel->msgDict()->getMsgSerNum(headers->folder(), id), 0, 36 ); |
|
while (sortArrival.length() < 7) sortArrival = '0' + sortArrival; |
|
|
|
if (column == paintInfo->dateCol) { |
|
if (paintInfo->orderOfArrival) |
|
return ret + sortArrival; |
|
else { |
|
static const int dateLength = 30; |
|
char cDate[dateLength + 1]; |
|
const time_t date = msg->date(); |
|
strftime( cDate, dateLength, "%Y:%j:%H:%M:%S", gmtime( &date )); |
|
return ret + cDate + sortArrival; |
|
} |
|
} else if (column == paintInfo->senderCol) { |
|
QString tmp; |
|
if (headers->folder()->whoField().lower() == "to") |
|
tmp = msg->toStrip(); |
|
else |
|
tmp = msg->fromStrip(); |
|
return ret + tmp.lower() + ' ' + sortArrival; |
|
} else if (column == paintInfo->subCol) { |
|
if (paintInfo->status) |
|
return ret + ' ' + msg->statusToSortRank() + ' ' + sortArrival; |
|
return ret + KMMessage::stripOffPrefixes( msg->subject().lower() ) + ' ' + sortArrival; |
|
} |
|
else if (column == paintInfo->sizeCol) { |
|
QString len; |
|
if ( msg->parent()->folderType() == KMFolderTypeImap ) |
|
{ |
|
QCString cstr; |
|
headers->folder()->getMsgString(id, cstr); |
|
int a = cstr.find("\nX-Length: "); |
|
int b = cstr.find('\n', a+1); |
|
len = QString( "%1" ).arg( cstr.mid(a+11, b-a-11) ); |
|
} else { |
|
len = QString( "%1" ).arg( msg->msgSize() ); |
|
} |
|
while (len.length() < 9) len = '0' + len; |
|
return ret + len; |
|
} |
|
return ret + "missing key"; //you forgot something!! |
|
} |
|
|
|
virtual QString key( int column, bool /*ascending*/ ) const |
|
{ |
|
KMHeaders *headers = static_cast<KMHeaders*>(listView()); |
|
int sortOrder = column; |
|
if (headers->mPaintInfo.orderOfArrival) |
|
sortOrder |= (1 << 6); |
|
if (headers->mPaintInfo.status) |
|
sortOrder |= (1 << 5); |
|
//This code should stay pretty much like this, if you are adding new |
|
//columns put them in generate_key |
|
if(mKey.isEmpty() || mKey[0] != (char)sortOrder) { |
|
KMHeaders *headers = static_cast<KMHeaders*>(listView()); |
|
return ((KMHeaderItem *)this)->mKey = |
|
generate_key(mMsgId, headers, headers->folder()->getMsgBase( mMsgId ), |
|
headers->paintInfo(), sortOrder); |
|
} |
|
return mKey; |
|
} |
|
|
|
void setTempKey( QString key ) { |
|
mKey = key; |
|
} |
|
|
|
QListViewItem* firstChildNonConst() /* Non const! */ { |
|
enforceSortOrder(); // Try not to rely on QListView implementation details |
|
return firstChild(); |
|
} |
|
|
|
bool mAboutToBeDeleted; |
|
bool aboutToBeDeleted() const { return mAboutToBeDeleted; } |
|
void setAboutToBeDeleted( bool val ) { mAboutToBeDeleted = val; } |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
KMHeaders::KMHeaders(KMMainWidget *aOwner, QWidget *parent, |
|
const char *name) : |
|
KMHeadersInherited(parent, name) |
|
{ |
|
static bool pixmapsLoaded = FALSE; |
|
//qInitImageIO(); |
|
KImageIO::registerFormats(); |
|
mOwner = aOwner; |
|
mFolder = 0; |
|
noRepaint = FALSE; |
|
getMsgIndex = -1; |
|
mTopItem = 0; |
|
setMultiSelection( TRUE ); |
|
setAllColumnsShowFocus( TRUE ); |
|
mNested = false; |
|
nestingPolicy = OpenUnread; |
|
mNestedOverride = false; |
|
mSubjThreading = true; |
|
mousePressed = FALSE; |
|
mSortInfo.dirty = TRUE; |
|
mSortInfo.fakeSort = 0; |
|
mSortInfo.removed = 0; |
|
mSortInfo.column = 0; |
|
mSortInfo.ascending = false; |
|
mJumpToUnread = false; |
|
setLineWidth(0); |
|
// popup-menu |
|
header()->setClickEnabled(true); |
|
header()->installEventFilter(this); |
|
mPopup = new KPopupMenu(this); |
|
mPopup->insertTitle(i18n("View Columns")); |
|
mPopup->setCheckable(true); |
|
mSizeColumn = mPopup->insertItem(i18n("Size Column"), this, SLOT(slotToggleSizeColumn())); |
|
|
|
mPaintInfo.flagCol = -1; |
|
mPaintInfo.subCol = mPaintInfo.flagCol + 1; |
|
mPaintInfo.senderCol = mPaintInfo.subCol + 1; |
|
mPaintInfo.dateCol = mPaintInfo.senderCol + 1; |
|
mPaintInfo.sizeCol = mPaintInfo.dateCol + 1; |
|
mPaintInfo.orderOfArrival = false; |
|
mPaintInfo.status = false; |
|
mSortCol = KMMsgList::sfDate; |
|
mSortDescending = FALSE; |
|
|
|
readConfig(); |
|
restoreLayout(KMKernel::config(), "Header-Geometry"); |
|
setShowSortIndicator(true); |
|
setFocusPolicy( WheelFocus ); |
|
|
|
addColumn( i18n("Subject"), 310 ); |
|
addColumn( i18n("Sender"), 170 ); |
|
addColumn( i18n("Date"), 170 ); |
|
|
|
if (mPaintInfo.showSize) { |
|
addColumn( i18n("Size"), 80 ); |
|
setColumnAlignment( mPaintInfo.sizeCol, AlignRight ); |
|
showingSize = true; |
|
} else { |
|
showingSize = false; |
|
} |
|
|
|
if (!pixmapsLoaded) |
|
{ |
|
pixmapsLoaded = TRUE; |
|
pixNew = new QPixmap( UserIcon("kmmsgnew") ); |
|
pixUns = new QPixmap( UserIcon("kmmsgunseen") ); |
|
pixDel = new QPixmap( UserIcon("kmmsgdel") ); |
|
pixRead = new QPixmap( UserIcon("kmmsgread") ); |
|
pixRep = new QPixmap( UserIcon("kmmsgreplied") ); |
|
pixQueued= new QPixmap( UserIcon("kmmsgqueued") ); |
|
pixSent = new QPixmap( UserIcon("kmmsgsent") ); |
|
pixFwd = new QPixmap( UserIcon("kmmsgforwarded") ); |
|
pixFlag = new QPixmap( UserIcon("kmmsgflag") ); |
|
pixWatched = new QPixmap( UserIcon("kmmsgwatched") ); |
|
pixIgnored = new QPixmap( UserIcon("kmmsgignored") ); |
|
pixFullySigned = new QPixmap( UserIcon( "kmmsgfullysigned" ) ); |
|
pixPartiallySigned = new QPixmap( UserIcon( "kmmsgpartiallysigned" ) ); |
|
pixUndefinedSigned = new QPixmap( UserIcon( "kmmsgundefinedsigned" ) ); |
|
pixFullyEncrypted = new QPixmap( UserIcon( "kmmsgfullyencrypted" ) ); |
|
pixPartiallyEncrypted = new QPixmap( UserIcon( "kmmsgpartiallyencrypted" ) ); |
|
pixUndefinedEncrypted = new QPixmap( UserIcon( "kmmsgundefinedencrypted" ) ); |
|
pixFiller = new QPixmap( UserIcon( "kmmsgfiller" ) ); |
|
pixEncryptionProblematic = new QPixmap( UserIcon( "kmmsgencryptionproblematic" ) ); |
|
pixSignatureProblematic = new QPixmap( UserIcon( "kmmsgsignatureproblematic" ) ); |
|
} |
|
|
|
connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int )), |
|
this, SLOT( rightButtonPressed( QListViewItem*, const QPoint &, int ))); |
|
connect(this, SIGNAL(doubleClicked(QListViewItem*)), |
|
this,SLOT(selectMessage(QListViewItem*))); |
|
connect(this,SIGNAL(currentChanged(QListViewItem*)), |
|
this,SLOT(highlightMessage(QListViewItem*))); |
|
resetCurrentTime(); |
|
|
|
beginSelection = 0; |
|
endSelection = 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
KMHeaders::~KMHeaders () |
|
{ |
|
if (mFolder) |
|
{ |
|
writeFolderConfig(); |
|
writeSortOrder(); |
|
mFolder->close(); |
|
} |
|
writeConfig(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool KMHeaders::eventFilter ( QObject *o, QEvent *e ) |
|
{ |
|
if ( e->type() == QEvent::MouseButtonPress && |
|
static_cast<QMouseEvent*>(e)->button() == RightButton && |
|
o->isA("QHeader") ) |
|
{ |
|
mPopup->popup( static_cast<QMouseEvent*>(e)->globalPos() ); |
|
return true; |
|
} |
|
return KListView::eventFilter(o, e); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::slotToggleSizeColumn () |
|
{ |
|
mPaintInfo.showSize = !mPaintInfo.showSize; |
|
mPopup->setItemChecked(mSizeColumn, mPaintInfo.showSize); |
|
|
|
// we need to write it back so that |
|
// the configure-dialog knows the correct status |
|
KConfig* config = KMKernel::config(); |
|
KConfigGroupSaver saver(config, "General"); |
|
config->writeEntry("showMessageSize", mPaintInfo.showSize); |
|
|
|
setFolder(mFolder); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Support for backing pixmap |
|
void KMHeaders::paintEmptyArea( QPainter * p, const QRect & 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, colorGroup().base() ); |
|
} |
|
|
|
bool KMHeaders::event(QEvent *e) |
|
{ |
|
bool result = KMHeadersInherited::event(e); |
|
if (e->type() == QEvent::ApplicationPaletteChange) |
|
{ |
|
readColorConfig(); |
|
} |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::readColorConfig (void) |
|
{ |
|
KConfig* config = KMKernel::config(); |
|
// Custom/System colors |
|
KConfigGroupSaver saver(config, "Reader"); |
|
QColor c1=QColor(kapp->palette().active().text()); |
|
QColor c2=QColor("red"); |
|
QColor c3=QColor("blue"); |
|
QColor c4=QColor(kapp->palette().active().base()); |
|
QColor c5=QColor(0,0x7F,0); |
|
QColor c6=KGlobalSettings::alternateBackgroundColor(); |
|
|
|
if (!config->readBoolEntry("defaultColors",TRUE)) { |
|
mPaintInfo.colFore = config->readColorEntry("ForegroundColor",&c1); |
|
mPaintInfo.colBack = config->readColorEntry("BackgroundColor",&c4); |
|
QPalette newPal = kapp->palette(); |
|
newPal.setColor( QColorGroup::Base, mPaintInfo.colBack ); |
|
newPal.setColor( QColorGroup::Text, mPaintInfo.colFore ); |
|
setPalette( newPal ); |
|
mPaintInfo.colNew = config->readColorEntry("NewMessage",&c2); |
|
mPaintInfo.colUnread = config->readColorEntry("UnreadMessage",&c3); |
|
mPaintInfo.colFlag = config->readColorEntry("FlagMessage",&c5); |
|
c6 = config->readColorEntry("AltBackgroundColor",&c6); |
|
} |
|
else { |
|
mPaintInfo.colFore = c1; |
|
mPaintInfo.colBack = c4; |
|
QPalette newPal = kapp->palette(); |
|
newPal.setColor( QColorGroup::Base, c4 ); |
|
newPal.setColor( QColorGroup::Text, c1 ); |
|
setPalette( newPal ); |
|
mPaintInfo.colNew = c2; |
|
mPaintInfo.colUnread = c3; |
|
mPaintInfo.colFlag = c5; |
|
} |
|
setAlternateBackground(c6); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::readConfig (void) |
|
{ |
|
KConfig* config = KMKernel::config(); |
|
|
|
// Backing pixmap support |
|
{ // area for config group "Pixmaps" |
|
KConfigGroupSaver saver(config, "Pixmaps"); |
|
QString pixmapFile = config->readEntry("Headers"); |
|
mPaintInfo.pixmapOn = FALSE; |
|
if (!pixmapFile.isEmpty()) { |
|
mPaintInfo.pixmapOn = TRUE; |
|
mPaintInfo.pixmap = QPixmap( pixmapFile ); |
|
} |
|
} |
|
|
|
{ // area for config group "General" |
|
KConfigGroupSaver saver(config, "General"); |
|
mPaintInfo.showSize = config->readBoolEntry("showMessageSize"); |
|
mPopup->setItemChecked(mSizeColumn, mPaintInfo.showSize); |
|
mPaintInfo.showCryptoIcons = config->readBoolEntry( "showCryptoIcons", 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))) |
|
{ |
|
QFont listFont( KGlobalSettings::generalFont() ); |
|
setFont(config->readFontEntry("list-font", &listFont)); |
|
dateFont = KGlobalSettings::fixedFont(); |
|
dateFont = config->readFontEntry("list-date-font", &dateFont); |
|
} else { |
|
dateFont = KGlobalSettings::generalFont(); |
|
setFont(dateFont); |
|
} |
|
} |
|
|
|
// Behavior |
|
{ |
|
KConfigGroupSaver saver(config, "Behaviour"); |
|
mLoopOnGotoUnread = (LoopOnGotoUnreadValue)config->readNumEntry( |
|
"LoopOnGotoUnread", LoopInCurrentFolder ); |
|
mJumpToUnread = config->readBoolEntry( "JumpToUnread", false ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::reset(void) |
|
{ |
|
int top = topItemIndex(); |
|
int id = currentItemIndex(); |
|
noRepaint = TRUE; |
|
clear(); |
|
noRepaint = FALSE; |
|
mItems.resize(0); |
|
updateMessageList(); |
|
setCurrentMsg(id); |
|
setTopItemByIndex(top); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::refreshNestedState(void) |
|
{ |
|
bool oldState = mNested != mNestedOverride; |
|
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 != (mNested != mNestedOverride))) |
|
{ |
|
setRootIsDecorated( nestingPolicy != AlwaysOpen && mNested != mNestedOverride ); |
|
reset(); |
|
} |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::readFolderConfig (void) |
|
{ |
|
KConfig* config = KMKernel::config(); |
|
assert(mFolder!=0); |
|
|
|
KConfigGroupSaver saver(config, "Folder-" + mFolder->idString()); |
|
mNestedOverride = config->readBoolEntry( "threadMessagesOverride", false ); |
|
mSortCol = config->readNumEntry("SortColumn", (int)KMMsgList::sfDate); |
|
mSortDescending = (mSortCol < 0); |
|
mSortCol = abs(mSortCol) - 1; |
|
|
|
mTopItem = config->readNumEntry("Top", 0); |
|
mCurrentItem = config->readNumEntry("Current", 0); |
|
|
|
mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", TRUE ); |
|
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 && mNested != mNestedOverride ); |
|
mSubjThreading = config->readBoolEntry( "threadMessagesBySubject", true ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::writeFolderConfig (void) |
|
{ |
|
KConfig* config = KMKernel::config(); |
|
int mSortColAdj = mSortCol + 1; |
|
|
|
assert(mFolder!=0); |
|
|
|
KConfigGroupSaver saver(config, "Folder-" + mFolder->idString()); |
|
config->writeEntry("SortColumn", (mSortDescending ? -mSortColAdj : mSortColAdj)); |
|
config->writeEntry("Top", topItemIndex()); |
|
config->writeEntry("Current", currentItemIndex()); |
|
config->writeEntry("OrderOfArrival", mPaintInfo.orderOfArrival); |
|
config->writeEntry("Status", mPaintInfo.status); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::writeConfig (void) |
|
{ |
|
saveLayout(KMKernel::config(), "Header-Geometry"); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setFolder (KMFolder *aFolder, bool jumpToFirst) |
|
{ |
|
CREATE_TIMER(set_folder); |
|
START_TIMER(set_folder); |
|
|
|
int id; |
|
QString str; |
|
|
|
mSortInfo.fakeSort = 0; |
|
setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol )); |
|
if (mFolder && mFolder==aFolder) { |
|
int top = topItemIndex(); |
|
id = currentItemIndex(); |
|
writeFolderConfig(); |
|
readFolderConfig(); |
|
updateMessageList(); |
|
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, SIGNAL(numUnreadMsgsChanged(KMFolder*)), |
|
this, SLOT(setFolderInfoStatus())); |
|
|
|
mFolder->markNewAsUnread(); |
|
writeFolderConfig(); |
|
disconnect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)), |
|
this, SLOT(msgHeaderChanged(KMFolder*,int))); |
|
disconnect(mFolder, SIGNAL(msgAdded(int)), |
|
this, SLOT(msgAdded(int))); |
|
disconnect(mFolder, SIGNAL(msgRemoved(int,QString, QString)), |
|
this, SLOT(msgRemoved(int,QString, QString))); |
|
disconnect(mFolder, SIGNAL(changed()), |
|
this, SLOT(msgChanged())); |
|
disconnect(mFolder, SIGNAL(cleared()), |
|
this, SLOT(folderCleared())); |
|
disconnect(mFolder, SIGNAL(statusMsg(const QString&)), |
|
mOwner, SLOT(statusMsg(const QString&))); |
|
writeSortOrder(); |
|
mFolder->close(); |
|
// 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->editAction->setEnabled(mFolder ? (kernel->folderIsDraftOrOutbox(mFolder)): false ); |
|
mOwner->replyListAction()->setEnabled(mFolder ? mFolder->isMailingList() : |
|
false); |
|
|
|
if (mFolder) |
|
{ |
|
connect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)), |
|
this, SLOT(msgHeaderChanged(KMFolder*,int))); |
|
connect(mFolder, SIGNAL(msgAdded(int)), |
|
this, SLOT(msgAdded(int))); |
|
connect(mFolder, SIGNAL(msgRemoved(int,QString, QString)), |
|
this, SLOT(msgRemoved(int,QString, QString))); |
|
connect(mFolder, SIGNAL(changed()), |
|
this, SLOT(msgChanged())); |
|
connect(mFolder, SIGNAL(cleared()), |
|
this, SLOT(folderCleared())); |
|
connect(mFolder, SIGNAL(statusMsg(const QString&)), |
|
mOwner, SLOT(statusMsg(const QString&))); |
|
connect(aFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)), |
|
this, SLOT(setFolderInfoStatus())); |
|
|
|
// 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 (mNested != mNestedOverride) { |
|
noRepaint = TRUE; |
|
clear(); |
|
noRepaint = FALSE; |
|
mItems.resize( 0 ); |
|
} |
|
|
|
readFolderConfig(); |
|
|
|
CREATE_TIMER(kmfolder_open); |
|
START_TIMER(kmfolder_open); |
|
mFolder->open(); |
|
END_TIMER(kmfolder_open); |
|
SHOW_TIMER(kmfolder_open); |
|
|
|
if (mNested != mNestedOverride) { |
|
noRepaint = TRUE; |
|
clear(); |
|
noRepaint = FALSE; |
|
mItems.resize( 0 ); |
|
} |
|
} |
|
} |
|
|
|
CREATE_TIMER(updateMsg); |
|
START_TIMER(updateMsg); |
|
updateMessageList(!jumpToFirst); // jumpToFirst seem inverted - don |
|
END_TIMER(updateMsg); |
|
SHOW_TIMER(updateMsg); |
|
makeHeaderVisible(); |
|
|
|
if (mFolder) |
|
setFolderInfoStatus(); |
|
|
|
QString colText = i18n( "Sender" ); |
|
if (mFolder && (mFolder->whoField().lower() == "to")) |
|
colText = i18n("Receiver"); |
|
setColumnText( mPaintInfo.senderCol, colText); |
|
|
|
colText = i18n( "Date" ); |
|
if (mPaintInfo.orderOfArrival) |
|
colText = i18n( "Date (Order of Arrival)" ); |
|
setColumnText( mPaintInfo.dateCol, colText); |
|
|
|
colText = i18n( "Subject" ); |
|
if (mPaintInfo.status) |
|
colText = colText + i18n( " (Status)" ); |
|
setColumnText( mPaintInfo.subCol, colText); |
|
|
|
|
|
if (mFolder) { |
|
if (mPaintInfo.showSize) { |
|
colText = i18n( "Size" ); |
|
if (showingSize) { |
|
setColumnText( mPaintInfo.sizeCol, colText); |
|
} else { |
|
// add in the size field |
|
addColumn(colText); |
|
|
|
setColumnAlignment( mPaintInfo.sizeCol, AlignRight ); |
|
} |
|
showingSize = true; |
|
} else { |
|
if (showingSize) { |
|
// remove the size field |
|
removeColumn(mPaintInfo.sizeCol); |
|
} |
|
showingSize = false; |
|
} |
|
} |
|
END_TIMER(set_folder); |
|
SHOW_TIMER(set_folder); |
|
} |
|
|
|
// QListView::setContentsPos doesn't seem to work |
|
// until after the list view has been shown at least |
|
// once. |
|
void KMHeaders::workAroundQListViewLimitation() |
|
{ |
|
setTopItemByIndex(mTopItem); |
|
setCurrentItemByIndex(mCurrentItem); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::msgChanged() |
|
{ |
|
emit maybeDeleting(); |
|
if (mFolder->count() == 0) { // Folder cleared |
|
clear(); |
|
return; |
|
} |
|
int i = topItemIndex(); |
|
int cur = currentItemIndex(); |
|
if (!isUpdatesEnabled()) return; |
|
QString msgIdMD5; |
|
QListViewItem *item = currentItem(); |
|
KMHeaderItem *hi = dynamic_cast<KMHeaderItem*>(item); |
|
if (item && hi) { |
|
KMMsgBase *mb = mFolder->getMsgBase(hi->msgId()); |
|
if (mb) |
|
msgIdMD5 = mb->msgIdMD5(); |
|
} |
|
if (!isUpdatesEnabled()) return; |
|
// prevent IMAP messages from scrolling to top |
|
disconnect(this,SIGNAL(currentChanged(QListViewItem*)), |
|
this,SLOT(highlightMessage(QListViewItem*))); |
|
updateMessageList(); |
|
setTopItemByIndex( i ); |
|
setCurrentMsg(cur); |
|
setSelected( currentItem(), TRUE ); |
|
connect(this,SIGNAL(currentChanged(QListViewItem*)), |
|
this,SLOT(highlightMessage(QListViewItem*))); |
|
|
|
// 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<KMHeaderItem*>(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); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
KMHeaderItem * KMHeaders::findParent(int id, bool *perfectParent) |
|
{ |
|
// This is the same logic as in readSortOrder, where it is also explained. |
|
// TODO: merge the two |
|
if (mIdTree.isEmpty()) return 0; |
|
KMHeaderItem *parent = NULL; |
|
QString replyToId = mFolder->getMsgBase(id)->replyToIdMD5(); |
|
|
|
if (!replyToId.isEmpty()) { |
|
parent = mIdTree[replyToId]; |
|
if ( parent && perfectParent!=NULL ) |
|
*perfectParent = true; |
|
} |
|
if (!parent) { |
|
// Try by replyToAuxId. |
|
QString replyToAuxId = mFolder->getMsgBase(id)->replyToAuxIdMD5(); |
|
if (!replyToAuxId.isEmpty()) |
|
parent = mIdTree[replyToAuxId]; |
|
} |
|
if (!parent && mFolder->getMsgBase(id)->subjectIsPrefixed() && mSubjThreading) { |
|
// Still no parent, try by subject. |
|
QString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5(); |
|
if (!subjMD5.isEmpty()) |
|
parent = mMsgSubjects[subjMD5]; |
|
} |
|
return parent; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::msgAdded(int id) |
|
{ |
|
KMHeaderItem* hi = 0; |
|
if (!isUpdatesEnabled()) return; |
|
|
|
CREATE_TIMER(msgAdded); |
|
START_TIMER(msgAdded); |
|
|
|
assert( mFolder->getMsgBase( id ) ); // otherwise using count() above is wrong |
|
|
|
if (mNested != mNestedOverride) { |
|
// make sure the id and subject dicts grow, if necessary |
|
if (mIdTree.count() == (uint)mFolder->count()) { |
|
kdDebug (5006) << "KMHeaders::msgAdded: Resizing id and subject trees." << endl; |
|
mIdTree.resize(mFolder->count()*2); |
|
mMsgSubjects.resize(mFolder->count()*2); |
|
} |
|
QString msgId = mFolder->getMsgBase(id)->msgIdMD5(); |
|
if (msgId.isNull()) |
|
msgId = ""; |
|
QString replyToId = mFolder->getMsgBase(id)->replyToIdMD5(); |
|
|
|
bool perfectParent = false; |
|
KMHeaderItem *parent = findParent(id, &perfectParent); |
|
if (parent && !perfectParent) { |
|
// 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->msgId())->replyToIdMD5() |
|
|| msgId == mFolder->getMsgBase(parent->msgId())->replyToAuxIdMD5()) |
|
parent = NULL; |
|
} |
|
if (parent) |
|
hi = new KMHeaderItem( parent, id ); |
|
else |
|
hi = new KMHeaderItem( this, id ); |
|
|
|
if (parent && mFolder->getMsgBase(parent->msgId())->isWatched()) |
|
mFolder->getMsgBase(id)->setStatus( KMMsgStatusWatched ); |
|
else if (parent && mFolder->getMsgBase(parent->msgId())->isIgnored()) { |
|
mFolder->getMsgBase(id)->setStatus( KMMsgStatusIgnored ); |
|
mFolder->setStatus( id, KMMsgStatusRead ); |
|
} |
|
|
|
// Update and resize the id trees. |
|
mItems.resize( mFolder->count() ); |
|
mItems[id] = hi; |
|
|
|
mIdTree.replace( msgId, hi ); |
|
|
|
if (mSubjThreading) { |
|
QString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5(); |
|
if (subjMD5.isEmpty()) { |
|
mFolder->getMsgBase(id)->initStrippedSubjectMD5(); |
|
subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5(); |
|
} |
|
if( !subjMD5.isEmpty() && !mMsgSubjects.find(subjMD5) ) { |
|
QString replyToIdMD5 = mFolder->getMsgBase(id)->replyToIdMD5(); |
|
QString replyToAuxIdMD5 = mFolder->getMsgBase(id)->replyToAuxIdMD5(); |
|
if ( (replyToIdMD5.isEmpty() || !mIdTree.find(replyToIdMD5)) |
|
&& (replyToAuxIdMD5.isEmpty() || !mIdTree.find(replyToAuxIdMD5))) |
|
mMsgSubjects.replace(subjMD5, hi ); |
|
} |
|
} |
|
// The message we just added might be a better parent for one of the as of |
|
// yet imperfectly threaded messages. Let's find out. |
|
for(QPtrListIterator<KMHeaderItem> it(mImperfectlyThreadedList); it.current(); ++it) { |
|
int tryMe = (*it)->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; |
|
QString otherId = mFolder->getMsgBase(tryMe)->replyToIdMD5(); |
|
if (msgId != otherId) { |
|
if (msgId != mFolder->getMsgBase(tryMe)->replyToAuxIdMD5()) |
|
continue; |
|
else { |
|
if (!otherId.isEmpty() && mIdTree.find(otherId)) |
|
continue; |
|
else |
|
// Thread below us by aux id, but keep on the list of |
|
// imperfectly threaded messages. |
|
perfectParent = false; |
|
} |
|
} |
|
QListViewItem *newParent = mItems[id]; |
|
QListViewItem *msg = mItems[tryMe]; |
|
|
|
if (msg->parent()) |
|
msg->parent()->takeItem(msg); |
|
else |
|
takeItem(msg); |
|
newParent->insertItem(msg); |
|
if (mSubjThreading) { |
|
// Check if this message was a potential parent for threading by |
|
// subject. If so, replace it in the dict, we are better. |
|
QString mySubjMD5 = mFolder->getMsgBase(tryMe)->strippedSubjectMD5(); |
|
if (mMsgSubjects[mySubjMD5] == msg ) |
|
mMsgSubjects.replace(mySubjMD5, hi); |
|
} |
|
makeHeaderVisible(); |
|
|
|
if (perfectParent) |
|
mImperfectlyThreadedList.remove ((*it)); |
|
} |
|
// Add ourselves only now, to avoid circularity above. |
|
if (hi && !perfectParent) |
|
mImperfectlyThreadedList.append(hi); |
|
} else { |
|
// non-threaded case |
|
hi = new KMHeaderItem( this, id ); |
|
mItems.resize( mFolder->count() ); |
|
mItems[id] = hi; |
|
} |
|
|
|
appendUnsortedItem(hi); //inserted into sorted list |
|
if (mSortInfo.fakeSort) { |
|
QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); |
|
KMHeadersInherited::setSorting(mSortCol, !mSortDescending ); |
|
mSortInfo.fakeSort = 0; |
|
} |
|
|
|
msgHeaderChanged(mFolder,id); |
|
|
|
if ((childCount() == 1) && hi) { |
|
setSelected( hi, true ); |
|
setCurrentItem( firstChild() ); |
|
// ### workaround the fact that Qt 3.0.1's QListView doesn't emit |
|
// currentChanged() on setCurrentItem(): |
|
/*own slot*/highlightMessage( firstChild() ); |
|
} |
|
|
|
END_TIMER(msgAdded); |
|
SHOW_TIMER(msgAdded); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::msgRemoved(int id, QString msgId, QString strippedSubjMD5) |
|
{ |
|
if (!isUpdatesEnabled()) return; |
|
|
|
if ((id < 0) || (id >= (int)mItems.size())) |
|
return; |
|
CREATE_TIMER(msgRemoved); |
|
START_TIMER(msgRemoved); |
|
|
|
KMHeaderItem *removedItem = mItems[id]; |
|
QListViewItem *next = removedItem->itemBelow(); |
|
for (int i = id; i < (int)mItems.size() - 1; ++i) { |
|
mItems[i] = mItems[i+1]; |
|
mItems[i]->setMsgId( i ); |
|
} |
|
mItems.resize( mItems.size() - 1 ); |
|
if (mNested != mNestedOverride && mFolder->count()) { |
|
if (mIdTree[msgId] == removedItem) |
|
mIdTree.remove(msgId); |
|
if (mSubjThreading) { |
|
// Remove the message from the list of potential parents for threading by |
|
// subject. If we have a child, that one is the next best parent for |
|
// threading by subject, so replace with that. Since the subjects can |
|
// differ, check for that. |
|
if (mMsgSubjects[strippedSubjMD5] == removedItem) { |
|
mMsgSubjects.remove(strippedSubjMD5); |
|
if (removedItem->firstChild()) { |
|
KMHeaderItem *kiddo = |
|
static_cast<KMHeaderItem*> (removedItem->firstChild()); |
|
|
|
int id = kiddo->msgId(); |
|
QString newSubj = mFolder->getMsgBase(id)->strippedSubjectMD5(); |
|
if (newSubj.isEmpty()) { |
|
mFolder->getMsgBase(id)->initStrippedSubjectMD5(); |
|
newSubj = mFolder->getMsgBase(id)->strippedSubjectMD5(); |
|
} |
|
if( !newSubj.isEmpty() && (newSubj == strippedSubjMD5)) |
|
mMsgSubjects.replace(newSubj, kiddo); |
|
} |
|
} |
|
} |
|
// Reparent children of item. |
|
QListViewItem *myParent = removedItem; |
|
QListViewItem *myChild = myParent->firstChild(); |
|
QListViewItem *threadRoot = myParent; |
|
while (threadRoot->parent()) |
|
threadRoot = threadRoot->parent(); |
|
QString key = static_cast<KMHeaderItem*>(threadRoot)->key(mSortCol, !mSortDescending); |
|
|
|
QPtrList<QListViewItem> childList; |
|
while (myChild) { |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(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 ); |
|
} |
|
item->setTempKey( key + item->key( mSortCol, !mSortDescending )); |
|
if (mSortInfo.fakeSort) { |
|
QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); |
|
KMHeadersInherited::setSorting(mSortCol, !mSortDescending ); |
|
mSortInfo.fakeSort = 0; |
|
} |
|
} |
|
for (QPtrListIterator<QListViewItem> it(childList); it.current() ; ++it ) { |
|
QListViewItem *lvi = *it; |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi); |
|
bool perfectParent = false; |
|
KMHeaderItem *parent = NULL; |
|
parent = findParent(item->msgId(), &perfectParent); |
|
myParent->takeItem(lvi); |
|
if (parent && parent != item) |
|
parent->insertItem(lvi); |
|
else |
|
insertItem(lvi); |
|
|
|
if (!parent || (!perfectParent && !mImperfectlyThreadedList.containsRef(item))) |
|
mImperfectlyThreadedList.append(item); |
|
if (parent && perfectParent && mImperfectlyThreadedList.containsRef(item)) |
|
mImperfectlyThreadedList.removeRef(item); |
|
} |
|
} |
|
// Make sure our data structures are cleared. |
|
if (!mFolder->count()) |
|
folderCleared(); |
|
|
|
// Housekeeping. |
|
if (currentItem() == removedItem) |
|
mPrevCurrent = 0; |
|
|
|
if (next && removedItem == static_cast<KMHeaderItem*>(currentItem()) ) { |
|
setCurrentItem( next ); |
|
setSelected( next, TRUE ); |
|
} else { |
|
emit selected(0); |
|
} |
|
|
|
mImperfectlyThreadedList.removeRef(removedItem); |
|
delete removedItem; |
|
END_TIMER(msgRemoved); |
|
SHOW_TIMER(msgRemoved); |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::msgHeaderChanged(KMFolder*, int msgId) |
|
{ |
|
if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return; |
|
KMHeaderItem *item = mItems[msgId]; |
|
if (item) { |
|
item->irefresh(); |
|
item->repaint(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setMsgStatus (KMMsgStatus status, bool toggle) |
|
{ |
|
SerNumList serNums; |
|
for (QListViewItemIterator it(this); it.current(); ++it) |
|
if (it.current()->isSelected()) { |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); |
|
KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); |
|
serNums.append( msgBase->getMsgSerNum() ); |
|
} |
|
if (serNums.empty()) |
|
return; |
|
|
|
KMCommand *command = new KMSetStatusCommand( status, serNums, toggle ); |
|
command->start(); |
|
} |
|
|
|
|
|
QPtrList<QListViewItem> KMHeaders::currentThread() const |
|
{ |
|
if (!mFolder) return QPtrList<QListViewItem>(); |
|
|
|
// starting with the current item... |
|
QListViewItem *curItem = currentItem(); |
|
if (!curItem) return QPtrList<QListViewItem>(); |
|
|
|
// ...find the top-level item: |
|
QListViewItem *topOfThread = curItem; |
|
while ( topOfThread->parent() ) |
|
topOfThread = topOfThread->parent(); |
|
|
|
// collect the items in this thread: |
|
QPtrList<QListViewItem> list; |
|
QListViewItem *topOfNextThread = topOfThread->nextSibling(); |
|
for ( QListViewItemIterator it( topOfThread ) ; |
|
it.current() && it.current() != topOfNextThread ; ++it ) |
|
list.append( it.current() ); |
|
return list; |
|
} |
|
|
|
void KMHeaders::setThreadStatus(KMMsgStatus status, bool toggle) |
|
{ |
|
QPtrList<QListViewItem> curThread = currentThread(); |
|
QPtrListIterator<QListViewItem> it( curThread ); |
|
SerNumList serNums; |
|
|
|
for ( it.toFirst() ; it.current() ; ++it ) { |
|
int id = static_cast<KMHeaderItem*>(*it)->msgId(); |
|
KMMsgBase *msgBase = mFolder->getMsgBase( id ); |
|
serNums.append( msgBase->getMsgSerNum() ); |
|
} |
|
|
|
if (serNums.empty()) |
|
return; |
|
|
|
KMCommand *command = new KMSetStatusCommand( status, serNums, toggle ); |
|
command->start(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int KMHeaders::slotFilterMsg(KMMessage *msg) |
|
{ |
|
msg->setTransferInProgress(false); |
|
int filterResult = kernel->filterMgr()->process(msg,KMFilterMgr::Explicit); |
|
if (filterResult == 2) { |
|
// something went horribly wrong (out of space?) |
|
kernel->emergencyExit( i18n("Unable to process messages (out of space?)" )); |
|
return 2; |
|
} |
|
if (msg->parent()) { // unGet this msg |
|
int idx = -1; |
|
KMFolder * p = 0; |
|
kernel->msgDict()->getLocation( msg, &p, &idx ); |
|
assert( p == msg->parent() ); assert( idx >= 0 ); |
|
p->unGetMsg( idx ); |
|
p->close(); |
|
} |
|
|
|
return filterResult; |
|
} |
|
|
|
|
|
void KMHeaders::slotExpandOrCollapseThread( bool expand ) |
|
{ |
|
if ( !isThreaded() ) return; |
|
// find top-level parent of currentItem(). |
|
QListViewItem *item = currentItem(); |
|
if ( !item ) return; |
|
clearSelection(); |
|
item->setSelected( true ); |
|
while ( item->parent() ) |
|
item = item->parent(); |
|
KMHeaderItem * hdrItem = static_cast<KMHeaderItem*>(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; |
|
|
|
QListViewItem * item = currentItem(); |
|
if( item ) { |
|
clearSelection(); |
|
item->setSelected( true ); |
|
} |
|
|
|
for ( QListViewItem *item = firstChild() ; |
|
item ; item = item->nextSibling() ) |
|
static_cast<KMHeaderItem*>(item)->setOpenRecursive( expand ); |
|
if ( !expand ) { // collapse can hide the current item: |
|
QListViewItem * item = currentItem(); |
|
if( item ) { |
|
while ( item->parent() ) |
|
item = item->parent(); |
|
setCurrentMsg( static_cast<KMHeaderItem*>(item)->msgId() ); |
|
} |
|
} |
|
ensureItemVisible( currentItem() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setFolderInfoStatus () |
|
{ |
|
QString str = i18n("%n message, %1.", "%n messages, %1.", mFolder->count()) |
|
.arg(i18n("%n unread", "%n unread", mFolder->countUnread())); |
|
if (mFolder->isReadOnly()) str += i18n("Folder is read-only."); |
|
KMBroadcastStatus::instance()->setStatusMsg(str); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::applyFiltersOnMsg() |
|
{ |
|
emit maybeDeleting(); |
|
|
|
disconnect(this,SIGNAL(currentChanged(QListViewItem*)), |
|
this,SLOT(highlightMessage(QListViewItem*))); |
|
KMMessageList* msgList = selectedMsgs(); |
|
int topX = contentsX(); |
|
int topY = contentsY(); |
|
|
|
if (msgList->isEmpty()) |
|
return; |
|
QListViewItem *qlvi = currentItem(); |
|
QListViewItem *next = qlvi; |
|
while (next && next->isSelected()) |
|
next = next->itemBelow(); |
|
if (!next || (next && next->isSelected())) { |
|
next = qlvi; |
|
while (next && next->isSelected()) |
|
next = next->itemAbove(); |
|
} |
|
|
|
clearSelection(); |
|
|
|
for (KMMsgBase* msgBase=msgList->first(); msgBase; msgBase=msgList->next()) { |
|
int idx = msgBase->parent()->find(msgBase); |
|
assert(idx != -1); |
|
KMMessage * msg = mFolder->getMsg(idx); |
|
if (msg->transferInProgress()) continue; |
|
msg->setTransferInProgress(true); |
|
mFolder->open(); |
|
if ( !msg->isComplete() ) |
|
{ |
|
FolderJob *job = mFolder->createJob(msg); |
|
connect(job, SIGNAL(messageRetrieved(KMMessage*)), |
|
SLOT(slotFilterMsg(KMMessage*))); |
|
job->start(); |
|
} else { |
|
if (slotFilterMsg(msg) == 2) break; |
|
} |
|
} |
|
kernel->filterMgr()->cleanup(); |
|
|
|
setContentsPos( topX, topY ); |
|
emit selected( 0 ); |
|
if (next) { |
|
setCurrentItem( next ); |
|
setSelected( next, TRUE ); |
|
highlightMessage( next, true); |
|
} |
|
else if (currentItem()) { |
|
setSelected( currentItem(), TRUE ); |
|
highlightMessage( currentItem(), true); |
|
} |
|
else |
|
emit selected( 0 ); |
|
|
|
makeHeaderVisible(); |
|
connect(this,SIGNAL(currentChanged(QListViewItem*)), |
|
this,SLOT(highlightMessage(QListViewItem*))); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
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 KMSetStatusCommand( KMMsgStatusRead, serNums ); |
|
command->start(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::deleteMsg () |
|
{ |
|
//make sure we have an associated folder (root of folder tree does not). |
|
if (!mFolder) |
|
return; |
|
|
|
KMMessageList msgList = *selectedMsgs(true); |
|
KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList, this ); |
|
command->start(); |
|
|
|
KMBroadcastStatus::instance()->setStatusMsg(""); |
|
// triggerUpdate(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::resendMsg () |
|
{ |
|
KMComposeWin *win; |
|
KMMessage *newMsg, *msg = currentMsg(); |
|
if (!msg || !msg->codec()) return; |
|
|
|
KCursorSaver busy(KBusyPtr::busy()); |
|
newMsg = new KMMessage; |
|
newMsg->fromString(msg->asString()); |
|
newMsg->setCharset(msg->codec()->mimeName()); |
|
// the message needs a new Message-Id |
|
newMsg->removeHeaderField( "Message-Id" ); |
|
|
|
win = new KMComposeWin(); |
|
win->setMsg(newMsg, FALSE, true); |
|
win->show(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::moveSelectedToFolder( int menuId ) |
|
{ |
|
if (mMenuToFolder[menuId]) |
|
moveMsgToFolder( mMenuToFolder[menuId] ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::prepareMove( int *id, int *contentX, int *contentY ) |
|
{ |
|
emit maybeDeleting(); |
|
|
|
disconnect( this, SIGNAL(currentChanged(QListViewItem*)), |
|
this, SLOT(highlightMessage(QListViewItem*))); |
|
|
|
QListViewItem *curItem; |
|
KMHeaderItem *item; |
|
curItem = currentItem(); |
|
while (curItem && curItem->isSelected() && curItem->itemBelow()) |
|
curItem = curItem->itemBelow(); |
|
while (curItem && curItem->isSelected() && curItem->itemAbove()) |
|
curItem = curItem->itemAbove(); |
|
item = static_cast<KMHeaderItem*>(curItem); |
|
if (item && !item->isSelected()) |
|
*id = item->msgId(); |
|
*contentX = contentsX(); |
|
*contentY = contentsY(); |
|
|
|
// The following is a rather delicate process. We can't allow getMsg |
|
// to be called on messages in msgList as then we will try to operate on a |
|
// dangling pointer below (assuming a KMMsgInfo* is deleted and a KMMessage* |
|
// is created when getMsg is called). |
|
// |
|
// But KMMainWidget was modified recently so that exactly that happened |
|
// (a slot was connected to the selectionChanged signal. |
|
// |
|
// So we block all signals for awhile to avoid this. |
|
|
|
blockSignals( true ); // don't emit signals when the current message is |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::finalizeMove( int id, int contentX, int contentY ) |
|
{ |
|
blockSignals( false ); |
|
emit selected( 0 ); |
|
|
|
if ( id > -1) { |
|
setCurrentMsg( id ); |
|
highlightMessage( currentItem(), false); |
|
} |
|
|
|
setContentsPos( contentX, contentY ); |
|
makeHeaderVisible(); |
|
connect( this, SIGNAL(currentChanged(QListViewItem*)), |
|
this, SLOT(highlightMessage(QListViewItem*))); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::moveMsgToFolder (KMFolder* destFolder) |
|
{ |
|
KMMessageList msgList = *selectedMsgs(true); |
|
if ( !destFolder && // 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() ), |
|
i18n("Delete Messages"), i18n("De&lete"), "NoConfirmDelete") == KMessageBox::Cancel ) |
|
return; // user cancelled the action |
|
|
|
KMCommand *command = new KMMoveCommand( destFolder, msgList, this ); |
|
command->start(); |
|
} |
|
|
|
bool KMHeaders::canUndo() const |
|
{ |
|
return ( kernel->undoStack()->size() > 0 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::undo() |
|
{ |
|
kernel->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 ); |
|
} |
|
makeHeaderVisible(); |
|
setFolderInfoStatus(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setSelected( QListViewItem *item, bool selected ) |
|
{ |
|
if ( !item || item->isSelected() == selected ) |
|
return; |
|
|
|
KMHeadersInherited::setSelected( item, selected ); |
|
// If the item is the parent of a closed thread recursively select |
|
// children . |
|
if ( mNested != mNestedOverride && !item->isOpen() && item->firstChild() ) { |
|
QListViewItem *nextRoot = item->itemBelow(); |
|
QListViewItemIterator it( item->firstChild() ); |
|
for( ; (*it) != nextRoot; ++it ) |
|
(*it)->setSelected( true ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted) |
|
{ |
|
mSelMsgBaseList.clear(); |
|
for (QListViewItemIterator it(this); it.current(); it++) { |
|
if (it.current()->isSelected()) { |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); |
|
if (toBeDeleted) item->setAboutToBeDeleted ( true ); |
|
KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); |
|
mSelMsgBaseList.append(msgBase); |
|
} |
|
} |
|
return &mSelMsgBaseList; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int KMHeaders::firstSelectedMsg() const |
|
{ |
|
int selectedMsg = -1; |
|
QListViewItem *item; |
|
for (item = firstChild(); item; item = item->itemBelow()) |
|
if (item->isSelected()) { |
|
selectedMsg = (static_cast<KMHeaderItem*>(item))->msgId(); |
|
break; |
|
} |
|
return selectedMsg; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::nextMessage() |
|
{ |
|
QListViewItem *lvi = currentItem(); |
|
if (lvi && lvi->itemBelow()) { |
|
clearSelection(); |
|
setSelected( lvi, FALSE ); |
|
selectNextMessage(); |
|
ensureCurrentItemVisible(); |
|
} |
|
} |
|
|
|
void KMHeaders::selectNextMessage() |
|
{ |
|
QListViewItem *lvi = currentItem(); |
|
if( lvi ) { |
|
QListViewItem *below = lvi->itemBelow(); |
|
QListViewItem *temp = lvi; |
|
if (lvi && below ) { |
|
while (temp) { |
|
temp->firstChild(); |
|
temp = temp->parent(); |
|
} |
|
lvi->repaint(); |
|
/* 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() |
|
{ |
|
QListViewItem *lvi = currentItem(); |
|
if (lvi && lvi->itemAbove()) { |
|
clearSelection(); |
|
setSelected( lvi, FALSE ); |
|
selectPrevMessage(); |
|
ensureCurrentItemVisible(); |
|
} |
|
} |
|
|
|
void KMHeaders::selectPrevMessage() |
|
{ |
|
QListViewItem *lvi = currentItem(); |
|
if( lvi ) { |
|
QListViewItem *above = lvi->itemAbove(); |
|
QListViewItem *temp = lvi; |
|
|
|
if (lvi && above) { |
|
while (temp) { |
|
temp->firstChild(); |
|
temp = temp->parent(); |
|
} |
|
lvi->repaint(); |
|
/* 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::findUnreadAux( KMHeaderItem*& item, |
|
bool & foundUnreadMessage, |
|
bool onlyNew, |
|
bool aDirNext ) |
|
{ |
|
KMMsgBase* msgBase = 0; |
|
KMHeaderItem *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<KMHeaderItem*>(item->itemBelow()); |
|
} |
|
} else { |
|
KMHeaderItem *newItem = static_cast<KMHeaderItem*>(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<KMHeaderItem*>(newItem->itemBelow()); |
|
} |
|
item = lastUnread; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent) |
|
{ |
|
KMHeaderItem *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<KMHeaderItem*>(firstChild()); |
|
else |
|
item = static_cast<KMHeaderItem*>(lastChild()); |
|
} |
|
if (!item) |
|
return -1; |
|
|
|
if ( !acceptCurrent ) |
|
if (aDirNext) |
|
item = static_cast<KMHeaderItem*>(item->itemBelow()); |
|
else |
|
item = static_cast<KMHeaderItem*>(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) { |
|
QListViewItem *next = item; |
|
while (next->parent()) |
|
next = next->parent(); |
|
next = static_cast<KMHeaderItem*>(next)->firstChildNonConst(); |
|
while (next && (next != item)) |
|
if (static_cast<KMHeaderItem*>(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 && mLoopOnGotoUnread != DontLoop ) |
|
{ |
|
KMHeaderItem * first = static_cast<KMHeaderItem*>(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 && mLoopOnGotoUnread != DontLoop ) { |
|
KMHeaderItem * last = static_cast<KMHeaderItem*>(lastItem()); |
|
if ( last ) |
|
i = findUnread(FALSE, last->msgId() ); // from bottom |
|
} |
|
if ( i < 0 ) |
|
return false; |
|
setCurrentMsg(i); |
|
ensureCurrentItemVisible(); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::slotNoDrag() |
|
{ |
|
mousePressed = FALSE; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::makeHeaderVisible() |
|
{ |
|
if (currentItem()) |
|
ensureItemVisible( currentItem() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::highlightMessage(QListViewItem* lvi, bool markitread) |
|
{ |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi); |
|
if (lvi != mPrevCurrent) { |
|
if (mPrevCurrent) |
|
{ |
|
KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId()); |
|
if (prevMsg) |
|
{ |
|
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 (!msg || msg->transferInProgress()) |
|
{ |
|
emit selected( 0 ); |
|
mPrevCurrent = 0; |
|
return; |
|
} |
|
|
|
KMBroadcastStatus::instance()->setStatusMsg(""); |
|
if (markitread && idx >= 0) setMsgRead(idx); |
|
mItems[idx]->irefresh(); |
|
mItems[idx]->repaint(); |
|
emit selected(mFolder->getMsg(idx)); |
|
setFolderInfoStatus(); |
|
} |
|
|
|
void KMHeaders::resetCurrentTime() |
|
{ |
|
mDate.reset(); |
|
QTimer::singleShot( 1000, this, SLOT( resetCurrentTime() ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::selectMessage(QListViewItem* lvi) |
|
{ |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(lvi); |
|
if (!item) |
|
return; |
|
|
|
int idx = item->msgId(); |
|
KMMessage *msg = mFolder->getMsg(idx); |
|
if (!msg->transferInProgress()) |
|
{ |
|
emit activated(mFolder->getMsg(idx)); |
|
} |
|
|
|
// if (kernel->folderIsDraftOrOutbox(mFolder)) |
|
// setOpen(lvi, !lvi->isOpen()); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::updateMessageList(bool set_selection) |
|
{ |
|
mPrevCurrent = 0; |
|
KMHeadersInherited::setSorting( mSortCol, !mSortDescending ); |
|
if (!mFolder) { |
|
noRepaint = TRUE; |
|
clear(); |
|
noRepaint = FALSE; |
|
mItems.resize(0); |
|
repaint(); |
|
return; |
|
} |
|
readSortOrder(set_selection); |
|
if (mNested != mNestedOverride) { |
|
buildIdTrees(); |
|
mImperfectlyThreadedList.clear(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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. |
|
// |
|
// See contentsMousePressEvent below for a description of mouse selection |
|
// behaviour. |
|
// |
|
// 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( QKeyEvent * e ) |
|
{ |
|
bool cntrl = (e->state() & ControlButton ); |
|
bool shft = (e->state() & ShiftButton ); |
|
QListViewItem *cur = currentItem(); |
|
|
|
if (!e || !firstChild()) |
|
return; |
|
|
|
// If no current item, make some first item current when a key is pressed |
|
if (!cur) { |
|
setCurrentItem( firstChild() ); |
|
return; |
|
} |
|
|
|
// Handle space key press |
|
if (cur->isSelectable() && e->ascii() == ' ' ) { |
|
setSelected( cur, !cur->isSelected() ); |
|
highlightMessage( cur, false); |
|
return; |
|
} |
|
|
|
if (cntrl) { |
|
if (!shft) |
|
disconnect(this,SIGNAL(currentChanged(QListViewItem*)), |
|
this,SLOT(highlightMessage(QListViewItem*))); |
|
switch (e->key()) { |
|
case Key_Down: |
|
case Key_Up: |
|
case Key_Home: |
|
case Key_End: |
|
case Key_Next: |
|
case Key_Prior: |
|
case Key_Escape: |
|
KMHeadersInherited::keyPressEvent( e ); |
|
} |
|
if (!shft) |
|
connect(this,SIGNAL(currentChanged(QListViewItem*)), |
|
this,SLOT(highlightMessage(QListViewItem*))); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Handle RMB press, show pop up menu |
|
void KMHeaders::rightButtonPressed( QListViewItem *lvi, const QPoint &, int ) |
|
{ |
|
if (!lvi) |
|
return; |
|
|
|
if (!(lvi->isSelected())) { |
|
clearSelection(); |
|
} |
|
setSelected( lvi, TRUE ); |
|
slotRMB(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// KMail mouse selection - simple description |
|
// Normal click - select and make current just this item unselect all others |
|
// Shift click - select all items from current item to clicked item |
|
// can be used multiple times |
|
// Cntrl click - select this item in addition to current selection make this |
|
// item the current item. |
|
void KMHeaders::contentsMousePressEvent(QMouseEvent* e) |
|
{ |
|
// This slot isn't called anymore if the RMB is pressed (Qt 3.0.1) |
|
//kdDebug(5006) << "MB pressed: " << e->button() << endl; |
|
beginSelection = currentItem(); |
|
presspos = e->pos(); |
|
QListViewItem *lvi = itemAt( contentsToViewport( e->pos() )); |
|
|
|
if (!lvi) { |
|
KMHeadersInherited::contentsMousePressEvent(e); |
|
return; |
|
} |
|
// 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 (e->button() == LeftButton && !lvi->isOpen()) { |
|
bool rootDecoClicked = |
|
( presspos.x() <= header()->cellPos( header()->mapToActual( 0 ) ) + |
|
treeStepSize() * ( lvi->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin() ) |
|
&& ( presspos.x() >= header()->cellPos( header()->mapToActual( 0 ) ) ); |
|
if (rootDecoClicked) { |
|
clearSelection(); |
|
lvi->setSelected( true ); |
|
} |
|
} |
|
|
|
setCurrentItem( lvi ); |
|
if ((e->button() == LeftButton) && |
|
!(e->state() & ControlButton) && |
|
!(e->state() & ShiftButton)) { |
|
mousePressed = TRUE; |
|
if (!(lvi->isSelected())) { |
|
clearSelection(); |
|
KMHeadersInherited::contentsMousePressEvent(e); |
|
} else { |
|
KMHeadersInherited::contentsMousePressEvent(e); |
|
setSelected(lvi, TRUE); |
|
} |
|
} |
|
else if ((e->button() == LeftButton) && (e->state() & ShiftButton)) { |
|
if (!shiftSelection( beginSelection, lvi )) |
|
shiftSelection( lvi, beginSelection ); |
|
mousePressed = TRUE; |
|
} |
|
else if ((e->button() == LeftButton) && (e->state() & ControlButton)) { |
|
setSelected( lvi, !lvi->isSelected() ); |
|
mousePressed = TRUE; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::contentsMouseReleaseEvent(QMouseEvent* e) |
|
{ |
|
QListViewItem *endSelection = itemAt( contentsToViewport( e->pos() )); |
|
|
|
if ((e->button() == LeftButton) |
|
&& !(e->state() & ControlButton) |
|
&& !(e->state() & ShiftButton)) { |
|
clearSelectionExcept( endSelection ); |
|
} |
|
|
|
if (e->button() != RightButton) |
|
KMHeadersInherited::contentsMouseReleaseEvent(e); |
|
|
|
beginSelection = 0; |
|
endSelection = 0; |
|
mousePressed = FALSE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::contentsMouseMoveEvent( QMouseEvent* e ) |
|
{ |
|
if (mousePressed && |
|
(e->pos() - presspos).manhattanLength() > KGlobalSettings::dndEventDelay()) { |
|
mousePressed = FALSE; |
|
QListViewItem *item = itemAt( contentsToViewport(presspos) ); |
|
if ( item ) { |
|
QStoredDrag *d = new QStoredDrag("x-kmail-drag/message", viewport()); |
|
|
|
// Set the drag data to be list of serial numbers selected |
|
QByteArray serNumArray; |
|
QBuffer serNumBuffer( serNumArray ); |
|
serNumBuffer.open( IO_WriteOnly ); |
|
QDataStream serNumStream( &serNumBuffer ); |
|
unsigned int count = 0; |
|
for( QListViewItemIterator it(this); it.current(); it++ ) |
|
if( it.current()->isSelected() ) { |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(it.current()); |
|
KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); |
|
KMFolder *pFolder = msgBase->parent(); |
|
serNumStream << kernel->msgDict()->getMsgSerNum(pFolder, pFolder->find( msgBase ) ); |
|
count++; |
|
} |
|
serNumBuffer.close(); |
|
d->setEncodedData( serNumArray ); |
|
|
|
// Set pixmap |
|
QPixmap pixmap; |
|
if( count == 1 ) |
|
pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) ); |
|
else |
|
pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) ); |
|
|
|
// Calculate hotspot (as in Konqueror) |
|
if( !pixmap.isNull() ) { |
|
QPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 ); |
|
d->setPixmap( pixmap, hotspot ); |
|
} |
|
d->drag(); |
|
} |
|
} |
|
} |
|
|
|
void KMHeaders::highlightMessage(QListViewItem* i) |
|
{ |
|
highlightMessage( i, false ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::clearSelectionExcept( QListViewItem *exception ) |
|
{ |
|
selectAll( false ); |
|
setSelected( exception, true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool KMHeaders::shiftSelection( QListViewItem *begin, QListViewItem *end ) |
|
{ |
|
QListViewItem *search = begin; |
|
while (search && search->itemBelow() && (search != end)) |
|
search = search->itemBelow(); |
|
if (search && (search == end)) { |
|
while (search && (search != begin)) { |
|
setSelected( search, TRUE ); |
|
search = search->itemAbove(); |
|
} |
|
setSelected( search, TRUE ); |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::slotRMB() |
|
{ |
|
if (!topLevelWidget()) return; // safe bet |
|
|
|
if (currentMsg()->transferInProgress()) |
|
return; |
|
|
|
QPopupMenu *menu = new QPopupMenu(this); |
|
|
|
mMenuToFolder.clear(); |
|
|
|
mOwner->updateMessageMenu(); |
|
|
|
QPopupMenu *msgMoveMenu = new QPopupMenu(menu); |
|
KMMoveCommand::folderToPopupMenu( TRUE, this, &mMenuToFolder, msgMoveMenu ); |
|
QPopupMenu *msgCopyMenu = new QPopupMenu(menu); |
|
KMCopyCommand::folderToPopupMenu( FALSE, this, &mMenuToFolder, msgCopyMenu ); |
|
|
|
bool out_folder = kernel->folderIsDraftOrOutbox(mFolder); |
|
if ( out_folder ) |
|
mOwner->editAction->plug(menu); |
|
else { |
|
// show most used actions |
|
mOwner->replyAction()->plug(menu); |
|
mOwner->replyAllAction()->plug(menu); |
|
mOwner->replyListAction()->plug(menu); |
|
mOwner->forwardMenu()->plug(menu); |
|
mOwner->bounceAction()->plug(menu); |
|
mOwner->sendAgainAction->plug(menu); |
|
} |
|
menu->insertSeparator(); |
|
|
|
menu->insertItem(i18n("&Copy To"), msgCopyMenu); |
|
menu->insertItem(i18n("&Move To"), msgMoveMenu); |
|
|
|
if ( !out_folder ) { |
|
mOwner->statusMenu->plug( menu ); // Mark Message menu |
|
if ( mOwner->threadStatusMenu->isEnabled() ) { |
|
mOwner->threadStatusMenu->plug( menu ); // Mark Thread menu |
|
} |
|
} |
|
|
|
if (mOwner->watchThreadAction->isEnabled() ) { |
|
menu->insertSeparator(); |
|
mOwner->watchThreadAction->plug(menu); |
|
mOwner->ignoreThreadAction->plug(menu); |
|
} |
|
menu->insertSeparator(); |
|
mOwner->trashAction->plug(menu); |
|
mOwner->deleteAction->plug(menu); |
|
|
|
menu->insertSeparator(); |
|
mOwner->saveAsAction->plug(menu); |
|
mOwner->saveAttachments->plug(menu); |
|
mOwner->printAction()->plug(menu); |
|
|
|
if ( !out_folder ) { |
|
menu->insertSeparator(); |
|
mOwner->action("apply_filters")->plug(menu); |
|
mOwner->filterMenu()->plug( menu ); // Create Filter menu |
|
} |
|
|
|
mOwner->action("apply_filter_actions")->plug(menu); |
|
|
|
menu->exec (QCursor::pos(), 0); |
|
delete menu; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
KMMessage* KMHeaders::currentMsg() |
|
{ |
|
KMHeaderItem *hi = currentHeaderItem(); |
|
if (!hi) |
|
return 0; |
|
else |
|
return mFolder->getMsg(hi->msgId()); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
KMHeaderItem* KMHeaders::currentHeaderItem() |
|
{ |
|
return static_cast<KMHeaderItem*>(currentItem()); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int KMHeaders::currentItemIndex() |
|
{ |
|
KMHeaderItem* item = currentHeaderItem(); |
|
if (item) |
|
return item->msgId(); |
|
else |
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setCurrentItemByIndex(int msgIdx) |
|
{ |
|
if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) { |
|
clearSelection(); |
|
bool unchanged = (currentItem() == mItems[msgIdx]); |
|
setCurrentItem( mItems[msgIdx] ); |
|
setSelected( mItems[msgIdx], TRUE ); |
|
if (unchanged) |
|
highlightMessage( mItems[msgIdx], false); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int KMHeaders::topItemIndex() |
|
{ |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(itemAt(QPoint(1,1))); |
|
if (item) |
|
return item->msgId(); |
|
else |
|
return -1; |
|
} |
|
|
|
// If sorting ascending by date/ooa then try to scroll list when new mail |
|
// arrives to show it, but don't scroll current item out of view. |
|
void KMHeaders::showNewMail() |
|
{ |
|
if (mSortCol != mPaintInfo.dateCol) |
|
return; |
|
for( int i = 0; i < (int)mItems.size(); ++i) |
|
if (mFolder->getMsgBase(i)->isNew()) { |
|
if (!mSortDescending) |
|
setTopItemByIndex( currentItemIndex() ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setTopItemByIndex( int aMsgIdx) |
|
{ |
|
int msgIdx = aMsgIdx; |
|
if (msgIdx < 0) |
|
msgIdx = 0; |
|
else if (msgIdx >= (int)mItems.size()) |
|
msgIdx = mItems.size() - 1; |
|
if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) |
|
setContentsPos( 0, itemPos( mItems[msgIdx] )); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setNestedOverride( bool override ) |
|
{ |
|
mSortInfo.dirty = TRUE; |
|
mNestedOverride = override; |
|
setRootIsDecorated( nestingPolicy != AlwaysOpen |
|
&& mNested != mNestedOverride ); |
|
QString sortFile = mFolder->indexLocation() + ".sorted"; |
|
unlink(QFile::encodeName(sortFile)); |
|
reset(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setSubjectThreading( bool aSubjThreading ) |
|
{ |
|
mSortInfo.dirty = TRUE; |
|
mSubjThreading = aSubjThreading; |
|
QString sortFile = mFolder->indexLocation() + ".sorted"; |
|
unlink(QFile::encodeName(sortFile)); |
|
reset(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setOpen( QListViewItem *item, bool open ) |
|
{ |
|
if ((nestingPolicy != AlwaysOpen)|| open) |
|
((KMHeaderItem*)item)->setOpenRecursive( open ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMHeaders::setSorting( int column, bool ascending ) |
|
{ |
|
if (column != -1) { |
|
if (column != mSortCol) |
|
setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol )); |
|
if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied |
|
QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); |
|
mSortInfo.dirty = TRUE; |
|
} |
|
|
|
mSortCol = column; |
|
mSortDescending = !ascending; |
|
|
|
if (!ascending && (column == mPaintInfo.dateCol)) |
|
mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival; |
|
|
|
if (!ascending && (column == mPaintInfo.subCol)) |
|
mPaintInfo.status = !mPaintInfo.status; |
|
|
|
QString colText = i18n( "Date" ); |
|
if (mPaintInfo.orderOfArrival) |
|
colText = i18n( "Date (Order of Arrival)" ); |
|
setColumnText( mPaintInfo.dateCol, colText); |
|
|
|
colText = i18n( "Subject" ); |
|
if (mPaintInfo.status) |
|
colText = colText + i18n( " (Status)" ); |
|
setColumnText( mPaintInfo.subCol, colText); |
|
} |
|
KMHeadersInherited::setSorting( column, ascending ); |
|
ensureCurrentItemVisible(); |
|
} |
|
|
|
//Flatten the list and write it to disk |
|
#define KMAIL_SORT_VERSION 1012 |
|
#define KMAIL_SORT_FILE(x) x->indexLocation() + ".sorted" |
|
#define KMAIL_SORT_HEADER "## KMail Sort V%04d\n\t" |
|
#define KMAIL_MAGIC_HEADER_OFFSET 21 //strlen(KMAIL_SORT_HEADER) |
|
#define KMAIL_MAX_KEY_LEN 16384 |
|
#define KMAIL_RESERVED 3 |
|
static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid, |
|
int parent_id, QString key, |
|
bool update_discover=TRUE) |
|
{ |
|
unsigned long msgSerNum; |
|
unsigned long parentSerNum; |
|
msgSerNum = kernel->msgDict()->getMsgSerNum( folder, msgid ); |
|
if (parent_id >= 0) |
|
parentSerNum = kernel->msgDict()->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); |
|
Q_INT32 len = key.length() * sizeof(QChar); |
|
fwrite(&len, sizeof(len), 1, sortStream); |
|
if (len) |
|
fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream); |
|
|
|
if (update_discover) { |
|
//update the discovered change count |
|
Q_INT32 discovered_count = 0; |
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); |
|
fread(&discovered_count, sizeof(discovered_count), 1, sortStream); |
|
discovered_count++; |
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); |
|
fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream); |
|
} |
|
} |
|
|
|
void KMHeaders::folderCleared() |
|
{ |
|
mIdTree.clear(); |
|
if (mSubjThreading) |
|
mMsgSubjects.clear(); |
|
mImperfectlyThreadedList.clear(); |
|
} |
|
|
|
void KMHeaders::buildIdTrees () |
|
{ |
|
int count = mFolder->count(); |
|
if (!count) return; |
|
CREATE_TIMER(buildIdTrees); |
|
START_TIMER(buildIdTrees); |
|
|
|
mIdTree.resize(count * 2); |
|
mIdTree.clear(); |
|
if (mSubjThreading) { |
|
mMsgSubjects.resize(count * 2); |
|
mMsgSubjects.clear(); |
|
} |
|
|
|
for(int x = 0; x < count; x++) { |
|
QString md5; |
|
if(!mItems[x]) |
|
continue; |
|
md5 = mFolder->getMsgBase(x)->msgIdMD5(); |
|
if (!md5.isEmpty() && !mIdTree[md5]) |
|
mIdTree.insert(md5, mItems[x]); |
|
} |
|
if (mSubjThreading) { |
|
for(int x = 0; x < count; x++) { |
|
QString subjMD5; |
|
if(!mItems[x]) |
|
continue; |
|
subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5(); |
|
if (subjMD5.isEmpty()) { |
|
mFolder->getMsgBase(x)->initStrippedSubjectMD5(); |
|
subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5(); |
|
} |
|
if( !subjMD5.isEmpty() && !mMsgSubjects.find(subjMD5) ) { |
|
QString replyToIdMD5 = mFolder->getMsgBase(x)->replyToIdMD5(); |
|
QString replyToAuxIdMD5 = mFolder->getMsgBase(x)->replyToAuxIdMD5(); |
|
if ( (replyToIdMD5.isEmpty() || !mIdTree.find(replyToIdMD5)) |
|
&& (replyToAuxIdMD5.isEmpty() || !mIdTree.find(replyToAuxIdMD5))) |
|
mMsgSubjects.replace(subjMD5, mItems[x]); |
|
} |
|
} |
|
} |
|
END_TIMER(buildIdTrees); |
|
SHOW_TIMER(buildIdTrees); |
|
} |
|
|
|
bool KMHeaders::writeSortOrder() |
|
{ |
|
QString sortFile = KMAIL_SORT_FILE(mFolder); |
|
|
|
if (!mSortInfo.dirty) { |
|
struct stat stat_tmp; |
|
if(stat(QFile::encodeName(sortFile), &stat_tmp) == -1) { |
|
mSortInfo.dirty = TRUE; |
|
} |
|
} |
|
if (mSortInfo.dirty) { |
|
if (!mFolder->count()) { |
|
// Folder is empty now, remove the sort file. |
|
unlink(QFile::encodeName(sortFile)); |
|
return TRUE; |
|
} |
|
QString tempName = sortFile + ".temp"; |
|
unlink(QFile::encodeName(tempName)); |
|
FILE *sortStream = fopen(QFile::encodeName(tempName), "w"); |
|
if (!sortStream) |
|
return FALSE; |
|
mSortInfo.dirty = FALSE; |
|
fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION); |
|
//magic header information |
|
Q_INT32 byteOrder = 0x12345678; |
|
Q_INT32 column = mSortCol; |
|
Q_INT32 ascending= !mSortDescending; |
|
Q_INT32 threaded = (mNested != mNestedOverride); |
|
Q_INT32 appended=0; |
|
Q_INT32 discovered_count = 0; |
|
Q_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); |
|
|
|
QPtrStack<KMHeaderItem> items; |
|
{ |
|
QPtrStack<QListViewItem> s; |
|
for (QListViewItem * i = firstChild(); i; ) { |
|
items.push((KMHeaderItem *)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(KMHeaderItem *i = items.pop()) { |
|
kmb = mFolder->getMsgBase( i->mMsgId ); |
|
|
|
QString replymd5 = kmb->replyToIdMD5(); |
|
QString replyToAuxId = kmb->replyToAuxIdMD5(); |
|
int parent_id = -2; //no parent, top level |
|
KMHeaderItem *p = NULL; |
|
if(!replymd5.isEmpty()) |
|
p = mIdTree[replymd5]; |
|
|
|
if (p) |
|
parent_id = p->mMsgId; |
|
else |
|
parent_id = -1; |
|
// 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->mMsgId, 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(QFile::encodeName(sortFile)); |
|
kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; |
|
kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; |
|
kernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); |
|
} |
|
fclose(sortStream); |
|
::rename(QFile::encodeName(tempName), QFile::encodeName(sortFile)); |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
void KMHeaders::appendUnsortedItem(KMHeaderItem *khi) |
|
{ |
|
QString sortFile = KMAIL_SORT_FILE(mFolder); |
|
if(FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+")) { |
|
KMMsgBase *kmb = mFolder->getMsgBase( khi->mMsgId ); |
|
int parent_id = -2; //no parent, top level |
|
if(khi->parent()) |
|
parent_id = ((KMHeaderItem *)khi->parent())->mMsgId; |
|
else if(!kmb->replyToIdMD5().isEmpty() |
|
|| !kmb->replyToAuxIdMD5().isEmpty() |
|
|| kmb->subjectIsPrefixed()) |
|
parent_id = -1; |
|
internalWriteItem(sortStream, mFolder, khi->mMsgId, parent_id, |
|
khi->key(mSortCol, !mSortDescending)); |
|
|
|
//update the appended flag |
|
Q_INT32 appended = 1; |
|
fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); |
|
fwrite(&appended, sizeof(appended), 1, sortStream); |
|
|
|
if (sortStream && ferror(sortStream)) { |
|
fclose(sortStream); |
|
unlink(QFile::encodeName(sortFile)); |
|
kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; |
|
kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; |
|
kernel->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; |
|
QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); |
|
setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : TRUE); |
|
} |
|
|
|
class KMSortCacheItem { |
|
KMHeaderItem *mItem; |
|
KMSortCacheItem *mParent; |
|
int mId, mSortOffset; |
|
QString mKey; |
|
|
|
QPtrList<KMSortCacheItem> mSortedChildren; |
|
int mUnsortedCount, mUnsortedSize; |
|
KMSortCacheItem **mUnsortedChildren; |
|
bool mImperfectlyThreaded; |
|
|
|
public: |
|
KMSortCacheItem() : mItem(0), mParent(0), mId(-1), mSortOffset(-1), |
|
mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0), |
|
mImperfectlyThreaded (false) { } |
|
KMSortCacheItem(int i, QString k, int o=-1) |
|
: mItem(0), mParent(0), mId(i), mSortOffset(o), mKey(k), |
|
mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0), |
|
mImperfectlyThreaded (false) { } |
|
~KMSortCacheItem() { if(mUnsortedChildren) free(mUnsortedChildren); } |
|
|
|
KMSortCacheItem *parent() const { return mParent; } //can't be set, only by the parent |
|
bool isImperfectlyThreaded() const |
|
{ return mImperfectlyThreaded; } |
|
void setImperfectlyThreaded (bool val) |
|
{ mImperfectlyThreaded = val; } |
|
bool hasChildren() const |
|
{ return mSortedChildren.count() || mUnsortedCount; } |
|
const QPtrList<KMSortCacheItem> *sortedChildren() const |
|
{ return &mSortedChildren; } |
|
KMSortCacheItem **unsortedChildren(int &count) const |
|
{ count = mUnsortedCount; return mUnsortedChildren; } |
|
void addSortedChild(KMSortCacheItem *i) { |
|
i->mParent = this; |
|
mSortedChildren.append(i); |
|
} |
|
void addUnsortedChild(KMSortCacheItem *i) { |
|
i->mParent = this; |
|
if(!mUnsortedChildren) |
|
mUnsortedChildren = (KMSortCacheItem **)malloc((mUnsortedSize = 25) * sizeof(KMSortCacheItem *)); |
|
else if(mUnsortedCount >= mUnsortedSize) |
|
mUnsortedChildren = (KMSortCacheItem **)realloc(mUnsortedChildren, |
|
(mUnsortedSize *= 2) * sizeof(KMSortCacheItem *)); |
|
mUnsortedChildren[mUnsortedCount++] = i; |
|
} |
|
|
|
KMHeaderItem *item() const { return mItem; } |
|
void setItem(KMHeaderItem *i) { Q_ASSERT(!mItem); mItem = i; } |
|
|
|
const QString &key() const { return mKey; } |
|
void setKey(const QString &key) { mKey = key; } |
|
|
|
int id() const { return mId; } |
|
void setId(int id) { mId = id; } |
|
|
|
int offset() const { return mSortOffset; } |
|
void setOffset(int x) { mSortOffset = x; } |
|
|
|
void updateSortFile(FILE *, KMFolder *folder, bool =FALSE); |
|
}; |
|
|
|
void KMSortCacheItem::updateSortFile(FILE *sortStream, KMFolder *folder, bool waiting_for_parent) |
|
{ |
|
if(mSortOffset == -1) { |
|
fseek(sortStream, 0, SEEK_END); |
|
mSortOffset = ftell(sortStream); |
|
} else { |
|
fseek(sortStream, mSortOffset, SEEK_SET); |
|
} |
|
|
|
int parent_id = -2; |
|
if(!waiting_for_parent) { |
|
if(mParent) |
|
parent_id = mParent->id(); |
|
else |
|
parent_id = -1; |
|
} |
|
internalWriteItem(sortStream, folder, mId, parent_id, mKey); |
|
} |
|
|
|
static bool compare_ascending = FALSE; |
|
static int compare_KMSortCacheItem(const void *s1, const void *s2) |
|
{ |
|
if ( !s1 || !s2 ) |
|
return 0; |
|
KMSortCacheItem **b1 = (KMSortCacheItem **)s1; |
|
KMSortCacheItem **b2 = (KMSortCacheItem **)s2; |
|
int ret = (*b1)->key().compare((*b2)->key()); |
|
if(compare_ascending) |
|
ret = -ret; |
|
return ret; |
|
} |
|
|
|
|
|
bool KMHeaders::readSortOrder(bool set_selection) |
|
{ |
|
//all cases |
|
Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended; |
|
Q_INT32 deleted_count = 0; |
|
bool unread_exists = false; |
|
QMemArray<KMSortCacheItem *> sortCache(mFolder->count()); |
|
KMSortCacheItem root; |
|
QString replyToIdMD5; |
|
root.setId(-666); //mark of the root! |
|
bool error = false; |
|
|
|
//threaded cases |
|
QPtrList<KMSortCacheItem> unparented; |
|
|
|
//cleanup |
|
noRepaint = TRUE; |
|
clear(); |
|
noRepaint = FALSE; |
|
mItems.resize( mFolder->count() ); |
|
for (int i=0; i<mFolder->count(); i++) { |
|
sortCache[i] = 0; |
|
mItems[i] = 0; |
|
} |
|
|
|
QString sortFile = KMAIL_SORT_FILE(mFolder); |
|
FILE *sortStream = fopen(QFile::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) { |
|
Q_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 |
|
KMHeadersInherited::setSorting(-1); |
|
header()->setSortIndicator(column, ascending); |
|
QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); |
|
//setup mSortInfo here now, as above may change it |
|
mSortInfo.dirty = FALSE; |
|
mSortInfo.column = (short)column; |
|
mSortInfo.ascending = (compare_ascending = ascending); |
|
|
|
KMSortCacheItem *item; |
|
unsigned long serNum, parentSerNum; |
|
int id, len, parent, x; |
|
QChar *tmp_qchar = 0; |
|
int tmp_qchar_len = 0; |
|
const int mFolderCount = mFolder->count(); |
|
QString 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 = (QChar *)realloc(tmp_qchar, len); |
|
tmp_qchar_len = len; |
|
} |
|
if(!fread(tmp_qchar, len, 1, sortStream)) |
|
break; |
|
key = QString(tmp_qchar, len / 2); |
|
} else { |
|
key = QString(""); //yuck |
|
} |
|
|
|
kernel->msgDict()->getLocation(serNum, &folder, &id); |
|
if (folder != mFolder) { |
|
++deleted_count; |
|
continue; |
|
} |
|
if (parentSerNum < KMAIL_RESERVED) { |
|
parent = (int)parentSerNum - KMAIL_RESERVED; |
|
} else { |
|
kernel->msgDict()->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 KMSortCacheItem(id, key, offset); |
|
} |
|
if (threaded && parent != -2) { |
|
if(parent == -1) { |
|
unparented.append(item); |
|
root.addUnsortedChild(item); |
|
// Abuse appended for signaling that messages need to |
|
// threaded. |
|
appended = 1; |
|
} else { |
|
if( ! sortCache[parent] ) |
|
sortCache[parent] = new KMSortCacheItem; |
|
sortCache[parent]->addUnsortedChild(item); |
|
} |
|
} else { |
|
if(x < sorted_count ) |
|
root.addSortedChild(item); |
|
else { |
|
root.addUnsortedChild(item); |
|
} |
|
} |
|
} |
|
if (error || (x != sorted_count + discovered_count)) {// sanity check |
|
kdDebug(5006) << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << 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 = (mNested != mNestedOverride); |
|
sorted_count = discovered_count = appended = 0; |
|
KMHeadersInherited::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 KMSortCacheItem( |
|
x, KMHeaderItem::generate_key(x, this, msg, &mPaintInfo, sortOrder)); |
|
if(threaded) |
|
unparented.append(sortCache[x]); |
|
else |
|
root.addUnsortedChild(sortCache[x]); |
|
if(sortStream) |
|
sortCache[x]->updateSortFile(sortStream, mFolder, 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 (appended && threaded && !unparented.isEmpty()) { |
|
CREATE_TIMER(reparent); |
|
START_TIMER(reparent); |
|
// Build two dictionaries, one with all messages and their ids, and |
|
// one with the md5 hashes of the subject stripped of prefixes such as |
|
// Re: or similar. |
|
QDict<KMSortCacheItem> msgs(mFolder->count() * 2); |
|
QDict<KMSortCacheItem> msgSubjects(mFolder->count() * 2); |
|
for(int x = 0; x < mFolder->count(); x++) { |
|
KMMsgBase *mi = mFolder->getMsgBase(x); |
|
QString md5 = mi->msgIdMD5(); |
|
if(!md5.isEmpty()) |
|
msgs.insert(md5, sortCache[x]); |
|
} |
|
if (mSubjThreading) { |
|
for(int x = 0; x < mFolder->count(); x++) { |
|
KMMsgBase *mi = mFolder->getMsgBase(x); |
|
QString subjMD5 = mi->strippedSubjectMD5(); |
|
if (subjMD5.isEmpty()) { |
|
mFolder->getMsgBase(x)->initStrippedSubjectMD5(); |
|
subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5(); |
|
} |
|
// The first message with a certain subject is where we want to |
|
// thread the other messages with the same suject below. Only keep |
|
// that in the dict. Also only accept messages which would not |
|
// otherwise be threaded by IDs as top level messages to avoid |
|
// circular threading. |
|
if( !subjMD5.isEmpty() && !msgSubjects.find(subjMD5) ) { |
|
QString replyToIdMD5 = mi->replyToIdMD5(); |
|
QString replyToAuxIdMD5 = mi->replyToAuxIdMD5(); |
|
if ( (replyToIdMD5.isEmpty() || !msgs.find(replyToIdMD5)) |
|
&& (replyToAuxIdMD5.isEmpty() || !msgs.find(replyToAuxIdMD5)) ) |
|
msgSubjects.insert(subjMD5, sortCache[x]); |
|
} |
|
} |
|
} |
|
for(QPtrListIterator<KMSortCacheItem> it(unparented); it.current(); ++it) { |
|
KMSortCacheItem *parent=NULL; |
|
KMMsgBase *msg = mFolder->getMsgBase((*it)->id()); |
|
QString replyToIdMD5 = msg->replyToIdMD5(); |
|
if(!replyToIdMD5.isEmpty()) |
|
parent = msgs[replyToIdMD5]; |
|
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 |
|
// contains the second to last one. |
|
QString ref = msg->replyToAuxIdMD5(); |
|
if (!ref.isEmpty()) { |
|
parent = msgs[ref]; |
|
(*it)->setImperfectlyThreaded(true); |
|
} |
|
} |
|
if (!parent && msg->subjectIsPrefixed() && mSubjThreading) { |
|
// Still no parent. 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. |
|
QString subjMD5 = msg->strippedSubjectMD5(); |
|
if (!subjMD5.isEmpty()) { |
|
parent = msgSubjects[subjMD5]; |
|
(*it)->setImperfectlyThreaded(true); |
|
} |
|
} |
|
// If we have a parent, make sure it's not ourselves |
|
if ( parent && (parent != (*it)) ) { |
|
parent->addUnsortedChild((*it)); |
|
if(sortStream) |
|
(*it)->updateSortFile(sortStream, mFolder); |
|
// If the parent is watched or ignored, propagate that to it's |
|
// children |
|
if (mFolder->getMsgBase(parent->id())->isWatched()) |
|
msg->setStatus(KMMsgStatusWatched); |
|
if (mFolder->getMsgBase(parent->id())->isIgnored()) { |
|
msg->setStatus(KMMsgStatusIgnored); |
|
mFolder->setStatus((*it)->id(), KMMsgStatusRead); |
|
} |
|
} else { |
|
//oh well we tried, to the root with you! |
|
root.addUnsortedChild((*it)); |
|
} |
|
} |
|
END_TIMER(reparent); |
|
SHOW_TIMER(reparent); |
|
} |
|
|
|
//create headeritems |
|
int first_unread = -1; |
|
CREATE_TIMER(header_creation); |
|
START_TIMER(header_creation); |
|
KMHeaderItem *khi; |
|
KMSortCacheItem *i, *new_kci; |
|
QPtrQueue<KMSortCacheItem> s; |
|
s.enqueue(&root); |
|
do { |
|
i = s.dequeue(); |
|
const QPtrList<KMSortCacheItem> *sorted = i->sortedChildren(); |
|
int unsorted_count, unsorted_off=0; |
|
KMSortCacheItem **unsorted = i->unsortedChildren(unsorted_count); |
|
if(unsorted) |
|
qsort(unsorted, unsorted_count, sizeof(KMSortCacheItem *), //sort |
|
compare_KMSortCacheItem); |
|
//merge two sorted lists of siblings |
|
for(QPtrListIterator<KMSortCacheItem> it(*sorted); |
|
(unsorted && unsorted_off < unsorted_count) || it.current(); ) { |
|
if(it.current() && |
|
(!unsorted || unsorted_off >= unsorted_count || |
|
(ascending && (*it)->key() >= unsorted[unsorted_off]->key()) || |
|
(!ascending && (*it)->key() < unsorted[unsorted_off]->key()))) { |
|
new_kci = (*it); |
|
++it; |
|
} else { |
|
new_kci = unsorted[unsorted_off++]; |
|
} |
|
if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent |
|
continue; |
|
|
|
if(threaded && i->item()) |
|
khi = new KMHeaderItem(i->item(), new_kci->id(), new_kci->key()); |
|
else |
|
khi = new KMHeaderItem(this, new_kci->id(), new_kci->key()); |
|
new_kci->setItem(mItems[new_kci->id()] = khi); |
|
if(new_kci->hasChildren()) |
|
s.enqueue(new_kci); |
|
if(set_selection && mFolder->getMsgBase(new_kci->id())->isNew() || |
|
set_selection && mFolder->getMsgBase(new_kci->id())->isUnread() ) |
|
unread_exists = true; |
|
} |
|
} while(!s.isEmpty()); |
|
|
|
for(int x = 0; x < mFolder->count(); x++) { //cleanup |
|
if (!sortCache[x]->item()) { // we missed a message, how did that happen ? |
|
khi = new KMHeaderItem(this, sortCache[x]->id(), sortCache[x]->key()); |
|
sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi); |
|
// by definition ;) |
|
sortCache[x]->setImperfectlyThreaded(true); |
|
} |
|
// 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()); |
|
} |
|
delete sortCache[x]; |
|
sortCache[x] = 0; |
|
} |
|
if (getNestingPolicy()<2) |
|
for (KMHeaderItem *khi=static_cast<KMHeaderItem*>(firstChild()); khi!=0;khi=static_cast<KMHeaderItem*>(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); |
|
} |
|
} |
|
|
|
//show a message |
|
CREATE_TIMER(selection); |
|
START_TIMER(selection); |
|
if(set_selection) { |
|
if (unread_exists) { |
|
KMHeaderItem *item = static_cast<KMHeaderItem*>(firstChild()); |
|
while (item) { |
|
bool isUnread = false; |
|
if (mJumpToUnread) // search unread messages |
|
if (mFolder->getMsgBase(item->msgId())->isUnread()) |
|
isUnread = true; |
|
|
|
if (mFolder->getMsgBase(item->msgId())->isNew() || isUnread) { |
|
first_unread = item->msgId(); |
|
break; |
|
} |
|
item = static_cast<KMHeaderItem*>(item->itemBelow()); |
|
} |
|
} |
|
|
|
if(first_unread == -1 ) { |
|
setTopItemByIndex(mTopItem); |
|
setCurrentItemByIndex((mCurrentItem >= 0) ? mCurrentItem : 0); |
|
} else { |
|
setCurrentItemByIndex(first_unread); |
|
makeHeaderVisible(); |
|
center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 ); |
|
} |
|
} else { |
|
// only reset the selection if we have no current item |
|
if (mCurrentItem <= 0) |
|
{ |
|
setTopItemByIndex(mTopItem); |
|
setCurrentItemByIndex((mCurrentItem >= 0) ? mCurrentItem : 0); |
|
} |
|
} |
|
END_TIMER(selection); |
|
SHOW_TIMER(selection); |
|
if (error || (sortStream && ferror(sortStream))) { |
|
if ( sortStream ) |
|
fclose(sortStream); |
|
unlink(QFile::encodeName(sortFile)); |
|
kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; |
|
kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; |
|
kernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); |
|
} |
|
if(sortStream) |
|
fclose(sortStream); |
|
|
|
return TRUE; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
#include "kmheaders.moc" |
|
|
|
|
|
|
|
|