views:

415

answers:

2

Greetings,

I've been writing some nasty code to support the undo/redo of deletion of an arbitrary set of objects from my model. I feel like I'm going about this correctly, as all the other mutators (adding/copy-pasting) are subsets of this functionality.

The code is nastier than it needs to me, mostly because the only way to mutate the model involves calling beginInsertRows/beginRemoveRows and removing the rows in a range (just doing 1 row at a time, no need to optimize "neighbors" into a single call yet)

The problem with beginInsertRows/beginRemoveRows is that removal of a row could affect another QModelIndex (say, one cached in a list). For instance:

ParentObj
   ->ChildObj1
   ->ChildObj2
   ->ChildObj3

Say I select ChildObj1 and ChildObj3 and delete them, if I remove ChildObj1 first I've changed ChildObj3's QModelIndex (row is now different). Similar issues occur if I delete a parent object (but I've fixed this by "pruning" children from the list of objects).

Here are the ways I've thought of working around this interface limitation, but I thought I'd ask for a better one before forging ahead:

  1. Move "backwards", assuming a provided list of QModelIndices is orderered from top to bottom just go from bottom up. This really requires sorting to be reliable, and the sort would probably be something naive and slow (maybe there's a smart way of sorting a collection of QModelIndexes? Or does QItemSelectionModel provide good (ordered) lists?)

  2. Update other QModelIndeces each time an object is removed/added (can't think of a non-naive solution, search the list, get new QModelIndeces where needed)

  3. Since updating the actual data is easy, just update the data and rebuild the model. This seems grotesque, and I can imagine it getting quite slow with large sets of data.

Those are the ideas I've got currently. I'm working on option 1 right now.

Regards, Dan O

+2  A: 

I would consider using a "all-data" model and a filter proxy model with the data model. Data would only be added to the all-data model, and never removed. That way, you could store your undo/redo information with references to that model. (May I suggest QPersistentModelIndex?). Your data model could also track what should be shown, somehow. The filter model would then only return information for the items that should be shown at a given time.

Caleb Huitt - cjhuitt
I'm not an app developer (this is just a hobby project) but never freeing objects in my model seems evil. Efficiently indexing such an (eventually) large and deeply nested set of data also seems unlikely.The QPersistentModelIndex seems very interesting, I will look into making use of this class.
Dan O
@Dan O: You are going to need to store the data somewhere, either in your undo/redo tree or in another spot, such as the data model. I should clarify that the other model shouldn't never delete data, but only in certain, well-specified times (which could then interact with your undo/redo code).
Caleb Huitt - cjhuitt
Yes, I'm storing the data in my undo/redo commands. Management of the command lifetime is quite clean right now, as they are freed by their Undo/Redo command stack.
Dan O
+3  A: 

Think of beginRemoveRows/endRemoveRows, etc. as methods to ask the QAbstractItemModel base class to fix up your persistent model indexes for you instead of just a way of updating views, and try not to confuse the QAbstractItemModel base class in its work on those indexes. Check out http://labs.trolltech.com/page/Projects/Itemview/Modeltest to exercise your model and see if you are keeping the QAbstractItemModel base class happy.

Where QPersistentModelIndex does not help is if you want to keep your undo/redo data outside of the model. I built a model that is heavily edited, and I did not want to try keeping everything in the model. I store the undo/redo data on the undo stack. The problem is that if you edit a column, storing the persistent index of that column on the undo stack, and then delete the row holding that column, the column's persistent index becomes invalid.

What I do is keep both a persistent model index, and a "historical" regular QModelIndex. When it's time to undo/redo, I check if the persistent index has become invalid. If it has, I pass the historical QModelIndex to a special method of my model to ask it to recreate the index, based on the row, column, and internalPointer. Since all of my edits are on the undo stack, by the time I've backed up to that column edit on the undo stack, the row is sure to be there in the model. I keep enough state in the internalPointer to recreate the original index.

Jim Flood
Is using QPersistantModelIndex the only sane way? The Qt MVC classes feel really outdated to me…
Georg