VTKWidgets: Difference between revisions

From KitwarePublic
Jump to navigationJump to search
Line 16: Line 16:
== Basic Concepts ==
== Basic Concepts ==
The basic idea is to create a separation between VTK events and widget events (see figure). An event translator is used to translate a VTK event into a widget event. In turn, the widget event is mapped into a method invocation.
The basic idea is to create a separation between VTK events and widget events (see figure). An event translator is used to translate a VTK event into a widget event. In turn, the widget event is mapped into a method invocation.
[[Image:WidgetMap.jpg]]
 


In practice this approach is overly simplified. The problem is that an event is really more than a VTK EventId, it includes various modifiers that go along with it (e.g., we'd like to  bind Shift-LeftButtonPress to a particular callback). The current VTK implementation is based around only the EventId, and then leaves the callback to check for the appropriate modifiers. This poses difficulties if we want configurable event bindings, since the source code must be modified to process the modifiers separately. Hence the right way to do this is to create separate, simple callback methods that are independent of modifiers, and then insist that widget events include modifiers as part of their definition. In order to do this, the vtkWidgetEventTranslator map shown above is modified as follows. The key to the map remains the VTK event id, but the value is now a list of vtkEvents and associated widget events (see figure below). When a VTK event is received, the event id is mapped into the list, and then the list is traversed to find the matching event (the comparison takes into account the VTK event qualifiers). If found, the associated widget event is invoked. Note that the modifiers must include the notion of "ignore any modifiers" in case the user does not care whether or not the shift key is pressed at the same time the LeftButtonPress is.
In practice this approach is overly simplified. The problem is that an event is really more than a VTK EventId, it includes various modifiers that go along with it (e.g., we'd like to  bind Shift-LeftButtonPress to a particular callback). The current VTK implementation is based around only the EventId, and then leaves the callback to check for the appropriate modifiers. This poses difficulties if we want configurable event bindings, since the source code must be modified to process the modifiers separately. Hence the right way to do this is to create separate, simple callback methods that are independent of modifiers, and then insist that widget events include modifiers as part of their definition. In order to do this, the vtkWidgetEventTranslator map shown above is modified as follows. The key to the map remains the VTK event id, but the value is now a list of vtkEvents and associated widget events (see figure below). When a VTK event is received, the event id is mapped into the list, and then the list is traversed to find the matching event (the comparison takes into account the VTK event qualifiers). If found, the associated widget event is invoked. Note that the modifiers must include the notion of "ignore any modifiers" in case the user does not care whether or not the shift key is pressed at the same time the LeftButtonPress is.

Revision as of 13:44, 24 August 2005

Overview

The VTK widgets are undergoing a redesign. This is mainly to add functionality and clean up some inconsistencies with the existing implementation. We also have limited funding (see acknowledgements) to add new widgets in support of medical image processing (i.e., segmentation and registration).

There are four major goals of this work:

  1. Support the ability to customize event bindings
  2. Decouple the widget representation (geometry) from the event processing
  3. Reduce redundant code; centralize common operations
  4. Add new widgets in support of medical image processing

The following sections discuss eash of these goals and the proposed implementation.

Customized Event Binding

The current VTK widgets have hardwired event bindings; e.g., a "LeftButtonPressEvent" is hardwired to execute a particular action, and this action/event binding cannot be changed. This is a problem when users desire different bindings due to personal preference, use of different interaction devices, etc. A prototype design has been created that addresses this issue and is described in the following.

Basic Concepts

The basic idea is to create a separation between VTK events and widget events (see figure). An event translator is used to translate a VTK event into a widget event. In turn, the widget event is mapped into a method invocation.


In practice this approach is overly simplified. The problem is that an event is really more than a VTK EventId, it includes various modifiers that go along with it (e.g., we'd like to bind Shift-LeftButtonPress to a particular callback). The current VTK implementation is based around only the EventId, and then leaves the callback to check for the appropriate modifiers. This poses difficulties if we want configurable event bindings, since the source code must be modified to process the modifiers separately. Hence the right way to do this is to create separate, simple callback methods that are independent of modifiers, and then insist that widget events include modifiers as part of their definition. In order to do this, the vtkWidgetEventTranslator map shown above is modified as follows. The key to the map remains the VTK event id, but the value is now a list of vtkEvents and associated widget events (see figure below). When a VTK event is received, the event id is mapped into the list, and then the list is traversed to find the matching event (the comparison takes into account the VTK event qualifiers). If found, the associated widget event is invoked. Note that the modifiers must include the notion of "ignore any modifiers" in case the user does not care whether or not the shift key is pressed at the same time the LeftButtonPress is. WidgetMap2.jpg

Implementation Details

Here are some code snippets showing the major features of the design. Refer to the vtkSliderWidget code. Thanks to Brad for helping out here.

In the constructor of a widget, instances of vtkWidgetEventTranslator and vtkWidgetCallbackMapper are created. (In actual implementation, the vtkWidgetEventTranslator would be instantiated in a superclass (vtkInteractorObserver ?) Once these classes are implemented, then the default event bindings are set up.

 this->EventTranslator = vtkWidgetEventTranslator::New(); //should be in superclass
 this->CallbackMapper = new vtkWidgetCallbackMapper<vtkSliderWidget>(this->EventTranslator);
 this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent,
                                         vtkWidgetEvents::Select,
                                         this, vtkSliderWidget::SelectAction);

This version of SetCallbackMethod() sets things up so that modifiers to the event are ignored. Alternatively, the following method allows specification of all of the modifiers:

 this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent,
                                         modifiers, keyCode, repeatCount, keySym,
                                         vtkWidgetEvents::Select,
                                         this, vtkSliderWidget::SelectAction);

Note that the vtkWidgeCallbackMapper is a templated class (templated on the widget class). This is because the callback mapper eventually invokes a slider instance method (the callback mapper is a friend to the vtkSliderWidget). The SetCallbackMethod() is a convenience method that sets up the relationship between the two maps (VTK event->widget event and widget event -> method callback invocation).

Once the widget is constructed, the user can change the VTK event bindings as shown from TestSliderWidget.cxx:

 sliderWidget->GetEventTranslator()->SetTranslation(vtkCommand::RightButtonPressEvent, 
                                                    modifier, keyCode, repeatCount, keySym,
                                                    vtkWidgetEvents::Select);

To set up the callbacks in the widget (i.e., add observers in vtkSliderWidget::SetEnabled()) the following code is executed:

   vtkRenderWindowInteractor *i = this->Interactor;
   this->EventTranslator->AddEventsToInteractor(i,this->EventCallbackCommand,this->Priority);

which is a very nice reduction in the code that was there previously.

(Removing the callbacks does not change:)

   // don't listen for events any more
   this->Interactor->RemoveObserver(this->EventCallbackCommand);

Finally, the ProcessEvents code also simplifies dramatically:

 void vtkSliderWidget::ProcessEvents(vtkObject* object, unsigned long vtkEvent,
                                     void* clientdata, void* calldata)
 {
 vtkSliderWidget* self = reinterpret_cast<vtkSliderWidget *>( clientdata );
 
 unsigned long widgetEvent = 
   self->EventTranslator->GetTranslation(vtkEvent,
                                         vtkEvent::GetModifier(self->Interactor),
                                         self->Interactor->GetKeyCode(),
                                         self->Interactor->GetRepeatCount(),
                                         self->Interactor->GetKeySym());
 
 if ( widgetEvent != vtkWidgetEvent::NoEvent)
   {
   self->CallbackMapper->InvokeCallback(widgetEvent);
   }
 }

That's really all there is to it, with the exception that the callback methods should be renamed (i.e., rather than OnLeftButtonDown() the better name would be SelectAction(), etc.).

Issues

There are several issues with this design that need addressing.

  1. What should the names of the widget events be? It you look at vtkWidgetEvents.h you will see one possibility.
  2. Currently the implementation is implemented in two principle classes: vtkWidgetEventTranslator and vtkWidgetCallbackMapper. vtkWidgetCallbackMapper is templated over the widget class type, this is to allow simple declaration and invocation of the callback widget methods. Brad tells me there is are ways around this, but the C++ tricks are not pretty.

Decouple Event Processing from Widget Geometry

Code Refactoring

New Widgets