Separate tree handling code from Q*Model* code in IncidenceTreeModel
IncidenceTreeModel is quite complex and responsible for many things at once.
It handles all the QAbstractItemModel stuff, but also cares for the Todo nodes to form a tree structure, creates and deletes those nodes, sorts them topologically etc.
The implementation is complex enough to make several issues hard to spot:
- Lexicographical ordering is used in two places, and lexicographical sorting is in fact implemented twice. Both have quadratic running time, but should be at most amortized n log n. They also works fundamentally differently, although they could have simply used the same algorithm: Reversing an order in which parents are processed before children makes sure that children are processed before parents. The sorting is quite unreliable, as it depends on a depth attribute that can easily get out of sync, and it does not handle hierarchies with missing intermediates.
- IncidenceTreeModel relies on begin/endResetModel() too much, resulting in the selection getting lost every time something in the underlying model changes.
- UIDs are assumed to be unique, but Akonadi doesn't seem to make sure that they are. For example, IncidenceTreeModel can crash easily on iCal file resources with duplicate UIDs.
- Cycles in RELATED-TOs are not detected.
- Reparenting can break the view.
Several of these tasks can be extracted to (or implemented in) separate classes.
I'd like to do the following:
- Implement a class that stores items the UID of which is already known. This allows to greatly simplify the tree-ifying code by never exposing it to duplicate UIDs.
- Extract the code that handles the tree structure on the items to a class. The implementation has to be tailored towards QAbstractItemModels. For example, before reparenting an existing node of the tree, we first need to ask the tree handling class about for the row of the node after moving. That is because we have to announce such moves via begin*Rows().
- Come up with an efficient way to break cycles in the VTODOs' RELATED-TO hierarchy. This can be handled either by the tree-ifying class, or by a class on its own. The former would make the tree-ifying class quite complex again, but the latter might introduce some redundancies.
Steps 1. and 3. make the model more robust towards skewed source models.
I guess that breaking cycles is to much to ask Akonadi for; I have no clue if it is Akonadi's responsibility to ensure UIDs that are actually unique.
Step 2. is meant as a mere refactoring step, but I think it also prevents some of the crashes I encountered while toying around with simple iCal files.
Of course, IncidenceTreeModel should then make use of those classes and simplified in the process.
Separate classes for the above task should also improve testability.
I already have working code that does the above, except for the cycle-breaking part. It needs polishing, though. This task's task is to make sure that I don't waste any more time in case my patch wouldn't be accepted anyway (thanks Dan for the hint).