1 /** \mainpage KMail architectural overview
3 \section KMail design principles
5 This file is intended to guide the reader's way through the KMail
6 codebase. It should esp. be handy for people not hacking full-time on
7 KMail as well as people that want to trace bugs in parts of KMail
8 which they don't know well.
21 TODO: reader, composer, messages, accounts, ...
23 \section kernel Kernel
25 Files: kmkernel.h, kmkernel.cpp
27 Contact Zack Rusin <zack@kde.org> with questions...
29 The first thing you'll notice about KMail is the extensive use of
30 kmkernel->xxx() constructs. The "kmkernel" is a define in kmkernel.h
32 #define kmkernel KMKernel::self()
33 KMKernel is the central object in KMail. It's always created before
34 any other class, therefore you are _guaranteed_ that KMKernel::self()
35 (and therefore "kmkernel" construct) won't return 0 (null).
37 KMKernel implements the KMailIface (our DCOP interface) and gives
38 access to all the core KMail functionality.
41 \section identity Identity
43 FIXME this has moved to libkpimidentities, right?
45 Files: identity*, kmidentity.{h,cpp}, configuredialog.cpp,
46 signatureconfigurator.{h,cpp}
48 Contact Marc Mutz <mutz@kde.org> on questions...
50 Identities consists of various fields represented by
51 QStrings. Currently, those fields are hardcoded, but feel free to
52 implement KPIMIdentities::Identity as a map from strings to QVariants or somesuch.
54 One part of identities are signatures. They can represent four modes
55 (Signature::Type) of operation (disabled, text from file or command
56 and inline text), which correspond to the combo box in the
59 Identities are designed to be used through the KPIMIdentities::IdentityManager:
60 const KPIM::Identity & ident =
61 kmkernel->identityManager()->identityForUoidOrDefault(...)
62 Make sure you assign to a _const_ reference, since the identityForFoo
63 methods are overloaded with non-const methods that access a different
64 list of identities in the manager that is used while configuring. That
65 is known source of errors when you use identityForFoo() as a parameter
66 to a method taking const KPIMIdentities::Identity &.
68 WARNING: Don't hold the reference longer than the current functions
69 scope or next return to the event loop. That's b/c the config dialog
70 is not modal and the user may hit apply/ok anytime between calls to
71 function that want to use the identity reference. Store the UOID
72 instead if you need to keep track of the identity. You may also want
73 to connect to one of the KPIMIdentities::IdentityManager::changed() or ::deleted()
74 signals, if you want to do special processing in case the identity
77 Thus, in the ConfigureDialog, you will see non-const KPIMIdentities::Identity
78 references being used, while everywhere else (KMMessage,
79 IdentityCombo) const references are used.
81 The KPIMIdentities::IdentityCombo is what you see in the composer. It's a
82 self-updating combo box of KPIMIdentities::Identity's. Use this if you want the user
83 to choose an identity, e.g. in the folder dialog.
85 Ihe IdentityListView is what you see in the config dialog's identity
86 management page. It's not meant to be used elsewhere, but is DnD
87 enabled (well, at the time of this writing, only drag-enabled). This
88 is going to be used to dnd identities around between KNode and KMail,
91 The SignatureConfigurator is the third tab in the identity
92 dialog. It's separate since it is used by the identity manager to
93 request a new file/command if the current value somehow fails.
97 \section filter Filter
99 Contact Marc Mutz <mutz@kde.org> on questions...
101 Filters consist of a search pattern and a list of actions plus a few
102 flags to indicate when they are to be applied (kmfilter.h).
103 They are managed in a QPtrList<KMFilter>, called KMFilterMgr. This
104 filter magnager is responsible for loading and storing filters
105 (read/writeConfig) and for executing them (process). The unique
106 instance of the filter manager is held by the kernel
107 (KMKernel::filterMgr()).
109 The search pattern is a QPtrList of search rules (kmsearchpattern.h) and a
110 boolean operator that defines their relation (and/or).
112 A search rule consists of a field-QString, a "function"-enum and a
113 "contents" or "value" QString. The first gives the header (or
114 pseudoheader) to match against, the second says how to match (equals,
115 consists, is less than,...) and the third holds the pattern to match
117 Currently, there are two types of search rules, which are mixed
118 together into a single class: String-valued and int-valued. The latter
119 is a hack to enable \verbatim<size>\endverbatim and
120 \verbatim<age in days>\endverbatim pseudo-header matching.
121 KMSearchRules should better be organized like KMFilterActions are.
123 A filter action (kmfilteraction.h) inherits from KMFilterAction or one
124 of it's convenience sub-classes. They have three sub-interfaces: (1)
125 argument handling, (2) processing and (3) parameter widget handling.
126 Interface (1) consists of args{From,As}String(), name() and
127 isEmpty() and is used to read and write the arguments (if any) from/to
129 Interface (2) is used by the filter manager to execute the action
130 (process() / ReturnCode).
131 Interface (3) is used by the filter dialog to allow editing of
132 actions and consists of name(), label() and the
133 *ParamWidget*(). Complex parameter widgets are collected in
136 A typical call for applying filters is
139 KMKernel::filterMgr()
141 KMFilterMgr::process():
146 \section configuration Configure Dialog
148 Files: configuredialog*.{h,cpp} ( identitylistview.{h,cpp} )
150 Contact Marc Mutz <mutz@kde.org> on questions...
152 The configuredialog is made up of pages that in turn may consist of a
153 number of tabs. The genral rule of thumb is that each page and tab is
154 responsible for reading and writing the config options presented on
155 it, although in the future, that may be reduced to interacting with
156 the corresponding config manager instead. But that won't change the
159 Thus, there is an abstract base class ConfigurePage (defined in
160 configuredialog_p.h), which derives from QWidget. It has four methods
161 of which you have to reimplement at least the first two:
164 Re-read the config (from the config file or the manager) and update
165 the widgets correspondingly. That is, you should only create the
166 widgets in the ctor, not set the options yet. The reason for that is
167 that the config dialog, once created, is simply hidden and shown
168 subsequently, so we need a reset-like method anyway.
171 Read the config from the widgets and write it into the config file
172 or the corresponding config manager.
174 - void installProfile()
175 This is called when the user selected a profile and hit apply. A
176 profile is just another KConfig object. Therefore, this method
177 should be the same as setup(), except that you should only alter
178 widgets for configs that really exist in the profile.
180 For tabbed config pages, there exists a convenience class called
181 TabbedConfigurationPage, which (as of this writing only offers the
182 addTab() convenience method. It is planned to also provide
183 reimplementations of setup, dismiss, apply and installProfile that just
184 call the same functions for each tab.
188 Files: libkdenetwork/kmime_mdn.{h,cpp} and kmmessage.{h,cpp}, mostly
190 Contact Marc Mutz <mutz@kde.org> on questions...
192 MDNs (Message Disposition Notifications; RFC 2298) are a way to send
193 back information regarding received messages back to their
194 sender. Examples include "message read/deleted/forwarded/processed".
196 The code in kmime_mdn.{h,cpp} is responsible for creating the
197 message/disposition-notification body part (2nd child of
198 multipart/report that makes the MDN) and for providing the template
199 for human-readable text that goes into the text/plain part (1st child
200 of the multipart/report).
202 The code in KMMessage::createMDN() actually constructs a message
203 containing a MDN for this message, using the kmime_mdn helper
204 functions. It starts by checking the index for an already sent MDN,
205 since the RFC demands that MDNs be sent only once for every
206 message. If that test succeeds, it goes on to check various other
207 constraints as per RFC and if all goes well the message containing the
208 multipart/report is created.
210 If you need to use this functionality, see KMReaderWin::touchMsg() and
211 KMFilterAction::sendMDN() for examples. The touchMsg() code is invoked
212 on display of a message and sends a "displayed" MDN back (if so
213 configured), whereas the KMFilterAction method is a convenience helper
214 for the various filter actions that can provoke a MDN (move to trash,
215 redirect, forward, ...).
218 \section folders Folders
220 Files: kmfolder*.{h,cpp}, folderstorage.{h,cpp} and *job.{h,cpp}
222 Contact Zack Rusin <zack@kde.org> with questions...
224 The collaboration among KMail folder classes looks
242 ---< actual folder types: KMFolderImap, KMFolderMbox... >--
245 At the base KMail's folder design starts with KMFolderNode which
246 inherits QObject. KMFolderNode is the base class encapsulating
247 common folder properties such as the name and a boolean signifying whether
248 the folder is a folder holding mail directly or a KMFolderDir.
249 KMFolderNode's often do not have an on-disk representation, they are
250 entities existing only within KMail's design.
252 KMFolder acts as the runtime representation of a folder with the physical
253 storage part being represented by a member of type FolderStorage.
254 KMFolder and FolderStorage have many functions with the same names and
255 signatures, but there is no inheritance.
256 KMFolderIndex contains some common indexing functionality for physical folders.
257 Subclasses of KMFolderIndex finally interact directly with physical storage
258 or with storage providers over the network.
260 KMFolderDir is a directory abstraction which holds KMFolderNode's.
261 It inherits KMFolderNode and KMFolderNodeList which is a QPtrList<KMFolderNode>.
262 A special case of a KMFolderDir is KMFolderRootDir; it represents
263 the toplevel KMFolderDir in KMail's folder hierarchy.
265 KMFolderDir's contents are managed by KMFolderMgr's.
266 KMail contains four main KMFolderMgr's. They can be
267 accessed via KMKernel ( the "kmkernel" construct ). Those methods are :
268 1) KMFolderMgr *folderMgr() - which returns the folder manager for
269 the folders stored locally.
270 2) KMFolderMgr *imapFolderMgr() - which returns the folder manager
271 for all imap folders. They're handled a little differently because
272 for all imap messages only headers are cached locally while the
273 main contents of all messages is kept on the server.
274 3) KMFolderMgr *dimapFolderMgr() - which returns disconnected IMAP (dimap)
275 folder manager. In dimap, both the headers and a copy of the full message
277 4) KMFolderMgr *searchFolderMgr() - which returns the folder manager
278 for search folders (folders created by using the "find
279 messages" tool). Other email clients call this type of folder
282 FolderJob classes - These classes allow asynchronous operations on
283 KMFolder's. You create a Job on the heap, connect to one of its
284 signals and wait for the job to finish. Folders serve as FolderJob
285 factories. For example, to retrieve the full message from a folder
289 FolderJob *job = folderParent->createJob( aMsg, tGetMessage );
290 connect( job, SIGNAL(messageRetrieved(KMMessage*)),
291 SLOT(msgWasRetrieved(KMMessage*)) );
296 \section folderindex Index (old)
298 Files: kmfolderindex.{h,cpp} and kmmsg{base,info}.{h,cpp}
300 Contact Marc Mutz <mutz@kde.org> or
301 Till Adam <adam@kde.org> or
302 Don Sanders <sanders@kde.org>
305 index := header *entry
308 header := magic LF NUL header-length byte-order-marker sizeof-long
310 magic := "# KMail-Index V" 1*DIGITS
312 header-length := quint32
314 byte-order-marker := quint32( 0x12345678 )
316 sizeof-long := quint32( 4 / 8 )
319 entry := tag length value
321 tag := quint32 ; little endian (native?)
323 length := quint16 ; little endian (native?)
325 value := unicode-string-value / ulong-value
327 unicode-string-value := 0*256QChar ; network-byte-order
329 ulong-value := unsigned_long ; little endian
331 Currently defined tag values are:
334 Msg*Part num. val type obtained by:
337 From 1 u fromStrip().trimmed()
338 Subject 2 u subject().trimmed()
339 To 3 u toStrip().trimmed()
340 ReplyToIdMD5 4 u replyToIdMD5().trimmed()
341 IdMD5 5 u msgIdMD5().trimmed()
342 XMark 6 u xmark().trimmed()
343 Offset 7 l folderOffset() (not only mbox!)
344 LegacyStatus 8 l mLegacyStatus
347 File 11 u fileName() (not only maildir!)
348 CryptoState 12 l (signatureState() << 16) | encryptionState())
349 MDNSent 13 l mdnSentState()
350 ReplyToAuxIdMD5 14 u replyToAuxIdMD5()
351 StrippedSubject 15 u strippedSubjectMD5().trimmed()
355 u: unicode-string-value; l: ulong-value
357 Proposed new (KDE 3.2 / KMail 1.6) entry format:
359 index := header *entry
361 entry := sync 1*( tag type content ) crc-tag crc-value
363 sync := quint16 (32?) ; resync mark, some magic bit pattern
364 ; (together with preceding crc-tag provides
365 ; 24(40)bits to resync on in case of index
372 content := variable-length-content / fixed-length-content
374 crc-tag := tag type ; tag=CRC16, type=CRC16
376 crc-value := quint16 ; the CRC16 sum is calculated over all of
377 ; 1*( tag type content )
379 variable-length-content := length *512byte padding
381 padding := *3NUL ; make the string a multiple of 4 octets in length
383 fixed-length-content := 1*byte
385 length := quint16 (quint8?)
389 The type field is pseudo-structured:
393 +-----+-----+-----+-----+-----+-----+-----+-----+
394 | uniq | chunk | len |
395 +-----+-----+-----+-----+-----+-----+-----+-----+
398 uniq: 3 bits = max. 8 different types with same chunk size:
401 for chunk = (0)00 (LSB(base)=0: octets):
407 for chunk = (1)00 (LSB(base)=1: 16-octet blocks):
413 for chunk = 01 (shorts):
418 for chunk = 10 (int32):
419 000 Utf32String (4; not to be used yet)
424 101 Color (QRgb: (a,r,g,b))
428 for chunk = 11 (int64):
436 len: length in chunks
437 000 (variable width -> quint16 with the real width follows)
438 001..111: fixed-width data of length 2^len (2^1=2..2^6=128)
440 You find all defined values for the type field in indexentrybase.cpp
442 Currently defined tags are:
447 DateSent DateTime Date:
448 DateReceived DateTime last Received:'s date-time
449 FromDisplayName String decoded display-name of first From: addr
450 ToDisplayName String dto. for To:
451 CcDisplayName String dto. for Cc:
452 FromAddrSpecs String possibly IMAA-encoded, comma-separated addr-spec's of From:
453 ToAddrSpecs String dto. for To:
454 CcAddrSpecs String dto. for Cc:
455 Subject String decoded Subject:
456 BaseSubjectMD5 String md5(utf8(stripOffPrefixes(subject())))
457 BodyPeek String body preview
458 MaildirFile String Filename of maildir file for this messagea
459 MBoxOffset Offset(64) Offset in mbox file (pointing to From_)
460 MBoxLength Size(64) Length of message in mbox file (incl. From_)
461 Size Size(64) rfc2822-size of message (in mbox: excl. From_)
462 Status BitField (see below)
463 MessageIdMD5 MD5Hash MD5Hash of _normalized_ Message-Id:
464 MDNLink SerialNumber SerNum of MDN received for this message
465 DNSLink SerialNumber SerNUm of DSN received for this message
466 ThreadHeads SerialNumberList MD5Hash's of all (so far discovered)
467 _top-level thread parents_
468 ThreadParents SerialNumberList MD5Hash's of all (so far discovered)
473 "String" is either Utf8String or (Utf16String or Latin1String),
476 Currently allocated bits for the Status BitField are:
479 Bit Value: on(/off) (\\imapflag)
484 2 Answered (\\Answered)
485 3 Deleted (\\Deleted)
486 4 Flagged (\\Flagged)
491 # message properties:
494 10..11 00: unspecified, 01: Low, 10: Normal, 11: High Priority
495 12..15 0001: Queued, 0010: Sent, 0011: DeliveryDelayed,
496 0100: Delivered, 0101: DeliveryFailed,
497 0110: DisplayedToReceiver, 0111: DeletedByReceiver,
498 1001: ProcessedByReceiver, 1010: ForwardedByReceiver,
502 # signature / encryption state:
503 16..19 0001: FullyEncrypted, 0010: PartiallyEncrypted, 1xxx: Problem
504 20..23 0001: FullySigned, 0010: PartiallySigned, 1xxx: Problem
507 24..25 01: Important, 10: ToDo, 11: Later (from Moz/Evo)
508 26..27 01: Work, 10: Personal, 11: reserved (dto.)
509 28..29 01: ThreadWatched, 10: ThreadIgnored, 11: reserved
513 All bits and combinations marked as reserved MUST NOT be altered if
514 set and MUST be set to zero (0) when creating the bitfield.
517 \section headers Headers (Threading and Sorting)
519 Contact Till Adam <adam@kde.org> or
520 Don Sanders <sanders@kde.org>
523 Threading and sorting is implemented in kmheaders.[cpp|h] and headeritem.[cpp|h]
524 and involves a handfull of players, namely:
527 this is the listview that contains the subject, date etc of each mail.
528 It's a singleton, which means there is only one, per mainwidget headers
529 list. It is actually a member of KMMainwidget and accessible there.
531 class KMail::HeaderItem:
532 these are the [Q|K]ListViewItem descendend items the KMHeaders listview
533 consists of. There's one for each message.
535 class KMail::SortCacheItem:
536 these are what the threading and sorting as well as the caching of those
537 operate on. Each is paired with a HeaderItem, such that each holds a
538 pointer to one, and vice versa, each header item holds a pointer to it's
539 associated sort cache item.
542 The order of the sorted and threaded (if threading is turned on for this
543 folder) messages is cached on disk in a file named .$FOLDER.index.sorted
544 so if, for example the name of a folder is foo, the associated sorting
545 cache file would be called ".foo.index.sorted".
546 For each message, its serial number, that of its parent, the length of its
547 sorting key, and the key itself are written to this file. At the start of
548 the file several per folder counts and flags are cached additionally,
549 immediately after a short file headers. The entries from the start of the
551 - "## KMail Sort V%04d\n\t" (magic header and version string)
552 - byteOrder flag containing 0x12345678 for byte order testing
553 - column, the sort column used for the folder
554 - ascending, the sort direction
555 - threaded, is the view threaded or is it not?
556 - appended, have there been items appended to the file (obsolete?)
557 - discovered_count, number of new mail discovered since the last sort file
559 - sorted_count, number of sorted messages in the header list
561 What is used for figuring out threading?
562 - messages can have an In-Reply-To header that contains the message id of
563 another message. This is called a perfect parent.
564 - additionally there is the References header which, if present, holds a
565 list of message ids that the current message is a follow up to. We
566 currently use the second to last entry in that list only. See further
567 down for the reasoning behind that.
568 - If the above two fail and the message is prefixed (Re: foo, Fwd: foo etc.)
569 an attempt is made to find a parent by looking for messages with the same
570 subject. How that is done is explained below as well.
572 For all these comparisons of header contents, the md5 hashes of the headers
573 are used for speed reasons. They are stored in the index entry for each
574 message. All data structures described below use md5 hash strings unless
578 When a folder is opened, updateMessageList is called, which in turn calls
579 readSortOrder where all the fun happens. If there is a .sorted file at the
580 expected location, it is openend and parsed. The header flags are read in
581 order to figure out the state in which this .sorted file was written. This
582 means the sort direction, whether the folder is threaded or not etc.
583 FIXME: is there currently sanity checking going on?
584 Now the file is parsed and for each message in the file a SortCacheItem is
585 created, which holds the offset in the .sorted file for this message as well
586 as it's sort key as read from the file. That sort cache item is entered into
587 the global mSortCache structure (member of the KMHeaders instance), which is
588 a QMemArray<SortCacheItem *> of the size mFolder->count(). Note that this
589 is the size reported by the folder, not as read from the .sorted file. The
590 mSortCache (along with some other structures) is updated when messages are
591 added or removed. More on that further down.
592 As soon as the serial number is read from the file, that number is looked up
593 in the message dict, to ensure it is still in the current folder. If not, it
594 has been moved away in the meantime (possibly by outside forces such as
595 other clients) and a deleted counter is incremented and all further
596 processing stopped for this message.
597 The messages parent serial number, as read from the sorted file is then
598 used to look up the parent and reset it to -1 should it not be in the
599 current folder anymore. -1 and -2 are special values that can show up
600 as parent serial numbers and are used to encode the following:
601 -1 means this message has no perfect parent, a parent for it needs to
602 be found from among the other messages, if there is a suitable one
603 -2 means this message is top level and will forever stay so, no need
604 to even try to find a parent. This is also used for the non-threaded
605 case. These are messages that have neither an In-Reply-To header nor
606 a References header and have a subject that is not prefixed.
607 In case there is a perfect parent, the current sort cache item is
608 appended to the parents list of unsorted children, or to that of
609 root, if there is not. A sort cache item is created in the mSortCache
610 for the parent, if it is not already there. Messages with a parent of
611 -1 are appended to the "unparented" list, which is later traversed and
612 its elements threaded. Messages with -2 as the parent are children of
613 root as well, as noted above, and will remain so.
615 Once the end of the file is reached, we should have a nicely filled
616 mSortCache, containing a sort cache item for each message that was in the
617 sorted file. Messages with perfect parents know about them, top level
618 messages know about that as well, all others are on a list and will be
621 Now, what happens when messages have been added to the store since the last
622 update of the .sorted file? Right, they don't have a sort cache item yet,
623 and would be overlooked. Consequently all message ids in the folder from 0
624 to mFolder->count() are looked at and a SortCacheItem is created for the
625 ones that do not have one yet. This is where all sort cache items are created
626 if there was no sorted file. The items created here are by definition un-
627 sorted as well as unparented. On creation their sort key is figured out as
630 The next step is finding parents for those messages that are either new, or
631 had a parent of -1 in the .sorted file. To that end, a dict of all sort
632 cache items indexed by the md5 hash of their messsage id headers is created,
633 that will be used for looking up sort cache items by message id. The list of
634 yet unparented messages is then traversed and findParent() called for each
635 element wihch checks In-Reply-To and References headers and looks up the
636 sort cache item of those parents in the above mentioned dict. Should none be
637 found, the item is added to a second list the items of which will be subject
640 How does findParent() work, well, it tries to find the message pointed to by
641 the In-Reply-To header and if that fails looks for the one pointed to by the
642 second to last entry of the References header list. Why the second to last?
643 Imagine the following situation in Bob's kmail:
644 - Bob get's mail from Alice
645 - Bob replies to Alice's mail, but his mail is stored in the Outbox, not the
647 - Alice replies again, so Bob now has two mails from Alice which are part of
648 the same thread. The mail in the middle is somewhere else. We want that to
651 Bob <- In-Reply-To: Alice1
654 |_Alice <- In-Reply-To: Bob (not here), References: Alice, Bob
657 - since the above is a common special case, it is worth trying. I think. ;)
659 If the parent found is perfect (In-Reply-To), the sort cache items is marked
660 as such. mIsImperfectlyThreaded is used for that purposer, we will soon see
661 why that flag is needed.
663 On this first pass, no subject threading is attempted yet. Once it is done,
664 the messages that are now top-level, the current thread heads, so to speak,
665 are collected into a second dict ( QDict< QPtrList< SortCacheItem > > )
666 that contains for each different subject an entry holding a list of (so far
667 top level) messages with that subject, that are potential parents for
668 threading by subjects. These lists are sorted by date, so the parent closest
669 by date can be chosen. Sorting of these lists happens via insertion sort
670 while they are built because not only are they expected to be short (apart
671 from hard corner cases such as cvs commit lists, for which subject threading
672 makes little sense anyhow and where it should be turned off), but also since
673 the messages should be roughly pre sorted by date in the store already.
674 Some cursory benchmarking supports that assumption.
675 If now a parent is needed for a message with a certain subject, the list of
676 top level messages with that subject is traversed and the first one that is
677 older than our message is chosen as it's parent. Parents more than six weeks
678 older than the message are not accepted. The reasoning being that if a new
679 message with the same subject turns up after such a long time, the chances
680 that it is still part of the same thread are slim. The value of six weeks
681 is chosen as a result of a poll conducted on #kde-devel, so it's probably
682 bogus. :) All of this happens in the aptly named: findParentBySubject().
684 Everthing that still has no parent after this ends up at toplevel, no further
685 attemp is made at finding one. If you are reading this because you want to
686 implement threading by body search or somesuch, please go away, I don't like
689 Ok, so far we have only operated on sort cache items, nothing ui wise has
690 happened yet. Now that we have established the parent/child relations of all
691 messages, it's time to create HeaderItems for them for use in the header
692 list. But wait, you say, what about sorting? Wouldn't it make sense to do
693 that first? Exactly, you're a clever bugger, ey? Here, have a cookie. ;)
694 Both creation of header items and sorting of the as of yet unsorted sort
695 cache items happen at the same time.
697 As previously mentioned (or not) each sort cache item holds a list of its
698 sorted and one of its unsorted children. Starting with the root node the
699 unsorted list is first qsorted, and then merged with the list of already
700 sorted children. To achieve that, the heads of both lists are compared and
701 the one with the "better" key is added to the list view next by creating a
702 KMHeaderListItem for it. That header item receives both its sort key as well
703 as its id from the sort cache item. Should the current sort cache item have
704 children, it is added to the end of a queue of nodes to repeat these steps
705 on after the current level is sorted. This way, a breadth first merge sort
706 is performed on the sort cache items and header items are created at each
709 What follows is another iteration over all message ids in the folder, to
710 make sure that there are HeaderItems for each now. Should that not be the
711 case, top level emergency items are created. This loop is also used to add
712 all header items that are imperfectly threaded to a list so they can be
713 reevalutated when a new message arrives. Here the reverse mapping from
714 header items to sort cache items are made as well. Those are also necessary
715 so the sort cache item based data structures can be updated when a message
718 The rest of readSortOrder should make sense on itself, I guess, if not, drop
721 What happens when a message arrives in the folder?
722 Among other things, the msgAdded slot is called, which creates the necessary
723 sort cache item and header item for the new message and makes sure the data
724 structures described above are updated accordingly. If threading is enabled,
725 the new message is threaded using the same findParent and findParentBySubject
726 methods used on folder open. If the message ends up in a watched or ignored
727 thread, those status bits are inherited from the parent. The message is also
728 added to the dict of header items, the index of messages by message id and,
729 if applicable and if the message is threaded at top level, to the list of
730 potential parents for subject threading.
732 After those house keeping tasks are performed, the list of as of yet imper-
733 fectly threaded messages is traversed and our newly arrived message is
734 considered as a new parent for each item on it. This is especially important
735 to ensure that parents arriving out of order after their children still end
736 up as parents. If necessary, the entries in the .sorted file of rethreaded
737 messages are updated. An entry for the new message itself is appended to the
738 .sorted file as well.
740 Note that as an optimization newly arriving mail as part of a mailcheck in
741 an imap folder is not added via msgAdded, but rather via complete reload of
742 the folder via readSortOrder(). That's because only the headers are gotten
743 for each mail on imap and that is so fast, that adding them individually to
744 the list view is very slow by comparison. Thus we need to make sure that
745 writeSortOrder() is called whenever something related to sorting changes,
746 otherwise we read stale info from the .sorted file. The reload is triggered
747 by the folderComplete() signal of imap folders.
749 What happens when a message is removed from the folder?
750 In this case the msgRemoved slot kicks in and updates the headers list. First
751 the sort cache item and header item representing our message are removed from
752 the data structures and the ids of all items after it in the store decre-
753 mented. Then a list of children of the message is assembled containing those
754 children that have to be reparented now that our message has gone away. If
755 one of those children has been marked as toBeDeleted, it is simply added to
756 root at top level, because there is no need to find a parent for it if it is
757 to go away as well. This is an optimization to avoid rethreading all
758 messages in a thread when deleting several messages in a thread, or even the
759 whole thread. The KMMoveCommand marks all messages that will be moved out of
760 the folder as such so that can be detected here and the useless reparenting
761 can be avoided. Note that that does not work when moving messages via filter
764 That list of children is then traversed and a new parent found for each one
765 using, again, findParent and findParentBySubject. When a message becomes
766 imperfectly threaded in the process, it is added to the corresponding list.
768 The message itself is removed from the list of imperfectly threaded messages
769 and its header item is finally deleted. The HeaderItem destructor destroys
770 the SortCacheItem as well, which is hopefully no longer referenced anywhere
775 \section display Display (reader window - new)
777 Contact Marc Mutz <mutz@kde.org> with questions...
779 What happens when a message is to displayed in the reader window?
780 First, since KMMessagePart and KMMessage don't work with nested body
781 parts, a hierarchical tree of MIME body parts is constructed with
782 partNode's (being wrappers around KMMessagePart and
783 DwBodyPart). This is done in KMReaderWin::parseMsg(KMMessage*).
784 After some legacy handling, an ObjectTreeParser is instantiated and
785 it's parseObjectTree() method called on the root
786 partNode. ObjectTreeParser is the result of an ongoing refactoring
787 to enhance the design of the message display code. It's an
788 implementation of the Method Object pattern, used to break down a
789 huuuge method into many smaller ones without the need to pass around
790 a whole lot of paramters (those are made members
791 instead). parseObjectTree() recurses into the partNode tree and
792 generates an HTML representation of each node in the tree (although
793 some can be hidden by setting them to processed beforehand or - the
794 new way - by using AttachmentStrategy::Hidden). The HTML generation
795 is delegated to BodyPartFormatters, while the HTML is written to
796 HTMLWriters, which abstract away HTML sinks. One of those is
797 KHTMLPartHTMLWriter, which writes to the KHTMLPart in the
798 readerwindow. This is the current state of the code. The goal of the
799 ongoing refactoring is to make the HTML generation blissfully
800 ignorant of the readerwindow and to allow display plugins that can
801 operate against stable interfaces even though the internal KMail
802 classes change dramatically.
804 To this end, we designed a new set of interfaces that allows plugins
805 to be written that need or want to asynchronously update their HTML
806 representation. This set of interfaces consists of the following:
808 - @em BodyPartFormatterPlugin
809 the plugin base class. Allows the BodyPartFormatterFactory to
810 query it for an arbitray number of BodyPartFormatters and
811 their associated metadata and url handlers.
813 - @em BodyPartFormatter
814 the formatter interface. Contains a single method format()
815 that takes a BodyPart to process and a HTMLWriter to write the
816 generated HTML to and returns a result code with which it can
817 request more information or delegate the formatting back to
818 some default processor. BodyPartFormatters are meant to be
819 Flyweights, implying that the format() method is not allowed
820 to maintain state between calls, but see Mememto below.
823 body part interface. Contains methods to retrieve the content
824 of a message part and some of the more interesting header
825 information. This interface decouples the bodypart formatters
826 from the implementation used in KMail (or KNode, for that
827 matter). Also contains methods to set and retrieve a Memento,
828 which is used by BodyPartFormatters to maintain state between
831 - @em BodyPartMemento
832 interface for opaque state storage and event handling.
833 Contains only a virtual destructor to enable the backend
834 processing code to destroy the object without the help of the
838 During the design phase we identified a need for BodyPartFormatters to
839 request their being called on some form of events, e.g. a dcop
840 signal. Thus, the Memento interface also includes the IObserver and
841 ISubject interfaces. If a BodyPartFormatter needs to react to a signal
842 (Qt or DCOP), it implements the Memento interface using a QObject,
843 connects the signal to a slot on the Memento and (as an ISubject)
844 notifies it's IObservers when the slot is called. If a Memento is
845 created, the reader window registers itself as an observer of the
846 Memento and will magically invoke the corresponding BodyPartFormatter,
847 passing along the Memento to be retrieved from the BodyPart interface.
849 An example should make this clearer. Suppose, we want to update our
850 display after 10 seconds. Initially, we just write out an icon, and
851 after 10 seconds, we want to replace the icon by a "Hello world!"
852 line. The following code does exactly this:
855 class DelayedHelloWorldBodyPartFormatter
856 : public KMail::BodyPartFormatter {
858 Result format( KMail::BodyPart * bodyPart,
859 KMail::HTMLWriter * htmlWriter ) {
860 if ( !bodyPart->memento() ) {
861 bodyPart->registerMemento( new DelayedHelloWorldBodyPartMemento() );
864 htmlWriter->write( "Hello, world!" );
870 class DelayedHelloWorldBodyPartMemento
871 : public QObject, public KMail::BodyPartMemento {
873 DelayedHelloWorldBodyPartMemento()
874 : QObject( 0, "DelayedHelloWorldBodyPartMemento" ),
875 KMail::BodyPartMemento()
877 QTimer::singleShot( 10*1000, this, SLOT(slotTimeout()) );
881 void slotTimeout() { notify(): }
884 // need to reimplement this abstract method...
885 bool update() { return true; }
891 // DOXYGEN_REFERENCES = kdecore kdeui kio kparts kutils kde3support sonnet kresources kabc gpgme++ qgpgme kleo libkdepim
892 // DOXYGEN_SET_IGNORE_PREFIX = KM K