Accept/Reset/Undo/Redo Blues
Clint asked this recently:
So I was coming across some limitations of the accept/reset stuff I was doing. Consider applying the threshold filter in ParaView. Changing the scalars affects the range of the upper and lower threshold. But the only way for the GUI to know that is to actually have the server manager set the scalar, right? What if our whole accept/reset mechanism is based on undo/redo? At the beginning of operations, we open an undo stack. Do things. If accept is pressed, close the undo stack and update the display. If reset is pressed, close the undo stack and undo it. Seems like less code overall.
This is a problem I have been thinking a lot on and off lately. There are so many similarities between reset and undo; why not merge them? Before I get into this, let me describe what we agreed on so far:
- Undo: Reverts all changes pushed to the server with Accept or changes to the display (for example, change from solid to wireframe representation). Will cause pipeline execution/re-render.
- Reset: Resets all changes that are not pushed to the server. Disable accept. Will not undo display changes or changes that were already pushed.
- Undo has to call reset also. We cannot undo changes to the server and still have pending changes.
- Reset does not interact with undo.
The GUI does not push it's values to the server manager until Accept is pushed. At that point, it pushes changes to the server manager and then tells the server manager to push it's changes to the server. If Reset if invoked, the GUI pulls all values from server manager and disables Accept. So far so good. Here is the tricky part:
Consider applying the threshold filter in ParaView. Changing the scalars affects the range of the upper and lower threshold. But the only way for the GUI to know that is to actually have the server manager set the scalar, right?
It seems like things would be easier if the GUI pushed all the changes to the server manager immediately and the server manager pushed it's changes to the server when Accept is pressed. This way, changing the scalars would be immediately reflected on the domain and this would be immediately reflected on the threshold GUI. This would work fine except it would impact undo/redo in a very negative way. Currently, the GUI opens an undo stack right before values are pushed in accept and closes the stack right after. If we were to change the code so that GUI pushed it's value right away to the SM, this stack would have to be opened right after Accept and closed before. This is bad because it would cause interleaving between real undo elements (like changes to the display) and things that should be grouped together (everything pushed with Accept). Also, we would have to differentiate between undo that should be pushed to the server and undo that shouldn't be. In summary, this approach has a lot of issues.
So what do we do to solve to change array/update threshold range problem? The SM has a mechanism to handle this: unchecked properties. Here is how it would work:
- User makes change to GUI
- The GUI set the unchecked values of the appropriate properties
- The GUI calls UpdateDependendentDomains on those properties
- The domains get modified and invoke events
- GUI catches these events and update ranges appropriately
Here is an example. vtkPVArrayMenu update unchecked elements when the user selects a new array:
//---------------------------------------------------------------------------- void vtkPVArrayMenu::UpdateProperty() { vtkSMStringVectorProperty* svp = vtkSMStringVectorProperty::SafeDownCast( this->GetSMProperty()); if (svp) { if (!(this->FieldMenu && svp->GetUncheckedElement(3))) { // Don't reset the unchecked element if it has already been set // by changing the value of the field menu on which this array menu // depends. (This is used by the Threshold filter.) svp->SetUncheckedElement(3, svp->GetElement(3)); } svp->SetUncheckedElement(0, this->InputAttributeIndex); svp->SetUncheckedElement(1, svp->GetElement(1)); svp->SetUncheckedElement(2, svp->GetElement(2)); svp->SetUncheckedElement(4, this->ArrayName); svp->UpdateDependentDomains(); } }
In ParaView, the mechanism that updates other widgets that depend on the properties that depend on this domain is clunky and is not done through the SM event. In ParaQ, this is handled much better. In this case, one of the domains that is dependent on the array selection property is vtkSMArrayRangeDomain. This domain will set a new range based on the unchecked element and invoke DomainModifiedEvent. The GUI should be listening to this event.