|
iTool Programming: Creating an Operation |
|
Generalized operations are iTool operations that are not limited to acting on data that underlies a visualization. Generalized operations are based on the IDLitOperation class. The class definition file for an IDLitOperation object must (at the least) provide methods to initialize the operation class, get and set property values, execute the operation, undo and redo the operation, and define the operation class structure. Complex operations will likely provide additional methods.
When an IDLitOperation is requested by a user, the operation's DoAction method (which must be provided by the operation class' developer) is called. The DoAction method is responsible for doing the following:
The process of creating an IDLitDataOperation is outlined in the following sections:
When any IDL object is created, IDL looks for an IDL class structure definition that specifies the instance data fields needed by an instance of the object, along with the data types of those fields. The object class structure must have been defined before any objects of the type are created. In practice, when the IDL OBJ_NEW function attempts to create an instance of a specified object class, it executes a procedure named ObjectClass__define (where ObjectClass is the name of the object), which is expected to define an IDL structure variable with the correct name and structure fields. For additional information on how IDL creates object instances, see The Object Lifecycle.
| Note The class structure definition is generally the last routine in the .pro file that defines an object class. |
The IDLitOperation class is the base class for all iTool operations. In almost all cases, new operations will be subclassed either from the IDLitOperation class or from a class that is a subclass of IDLitOperation.
| Note If your operation acts directly on data, rather than affecting the visual appearance of objects in the iTool, you may be able to subclass from IDLitDataContainer. See Creating a New Data-Centric Operation for details. |
See IDLitOperation for details on the methods and properties available to classes that subclass from IDLitOperation.
The following is the class structure definition for the ExampleOp operation class. This procedure should be the last procedure in a file named exampleop__define.pro.
PRO ExampleOp__Define
struct = { ExampleOp, INHERITS IDLitOperation}
END
The purpose of the structure definition routine is to define a named IDL structure with structure fields that will contain the operation object instance data. The structure name should be the same as the operation's class name — in this case, ExampleOp.
Like many iTool operations that act on data, ExampleOp is created as a subclass of the IDLitOperation class. The ExampleOp Operation class does not include any instance data of its own.
| Note This example is intended to demonstrate how simple it can be to create a new operation class definition. While the class definition for an operation class with significant extra functionality will likely define additional structure fields, and may inherit from other iTool classes, the basic principles are the same. |
The operation class Init method handles any initialization required by the operation object, and should do the following:
Begin by defining the argument and keyword list for your Init method. The argument and keyword list defines positional parameters (arguments) accepted by your method, defines any keywords that will be handled directly by your method, and specifies whether keywords not explicitly handled by your method will be passed through to other routines called by your method via IDL's keyword inheritance mechanism.
| Note Because iTool operations are invoked by the user's interactive choice of an item from a menu, they generally do not accept any keywords of their own. |
The function signature of an Init method for an operation generally looks something like this:
FUNCTION MyOperation::Init, _REF_EXTRA = _extra
where MyOperation is the name of your operation class.
| Note Always use keyword inheritance (the _REF_EXTRA keyword) to pass keyword parameters through to any called routines. (See Keyword Inheritance for details on IDL's keyword inheritance mechanism.) |
The operation class Init method should call the Init method of any required superclass. For example, if your operation class is based on an existing operation, you would call that operation's Init method:
success = self->SomeOperationClass::Init(_EXTRA = _extra)
where SomeOperationClass is the class definition file for the operation on which your new operation is based. The variable success contains a 1 if the initialization was successful.
| Note Your operation class may have multiple superclasses. In general, each superclass' Init method should be invoked by your class' Init method. |
Rather than simply calling the superclass Init method, it is a good idea to check whether the call to the superclass Init method succeeded. The following statement checks the value returned by the superclass Init method; if the returned value is 0 (indicating failure), the current Init method also immediately returns with a value of 0:
IF (self->SomeOperationClass::Init(_EXTRA = _extra) EQ 0) THEN $ RETURN, 0
This convention is used in all operation classes included with IDL. ITT Visual Information Solutions strongly suggests that you include similar checks in your own class definition files.
Properties of the operation class can be set in the Init method by specifying the property names and values as IDL keyword-value pairs. In addition to any keywords implemented directly in the Init method of the superclass on which you base your class, the properties of the IDLitOperation class and the IDLitComponent class are available to any operation class. See IDLitOperation Properties and IDLitComponent Properties.
| Note Always use keyword inheritance (the _EXTRA keyword) to pass keyword parameters through to the superclass. (See Keyword Inheritance for details on IDL's keyword inheritance mechanism.) |
While you can create your new operation class from any existing operation class, in many cases, operations that do not act directly on the data that underlies a visualization will be subclassed directly from the base class IDLitOperation:
IF (self->IDLitOperation::Init(_EXTRA = _extra) EQ 0) $ THEN RETURN, 0
The IDLitOperation class provides the base iTool functionality used in all operation classes created by ITT Visual Information Solutions. See Subclassing from the IDLitOperation Class for details.
If all of the routines and methods used in the Init method execute successfully, it should indicate successful initialization by returning 1. Other operation classes that subclass from your operation class may check this return value, as your routine should check the value returned by any superclass Init methods called.
Operations can register properties with the iTool. Registered properties show up in the property sheet interface, and can be modified interactively by users. The iTool property interface is described in detail in Property Management.
Register a property by calling the RegisterProperty method of the IDLitComponent class:
self->RegisterProperty, PropertyIdentifier [, TypeCode] $ [, ATTRIBUTE = value]
where PropertyIdentifier is a string that uniquely identifies the property, TypeCode is an integer between 0 and 9 specifying the property data type, and ATTRIBUTE is a property attribute. See Registering Properties for details.
If a property has already been registered, perhaps by a superclass of your operation class, you can change the registered attribute values using the SetPropertyAttribute method of the IDLitComponent class:
self->SetPropertyAttribute, Identifier
where Identifier is the name of the keyword to the GetProperty and SetProperty methods used to retrieve or change the value of this property. (The Identifier is specified in the call to RegisterProperty either via the PropertyName argument or the IDENTIFIER keyword.) See Property Attributes for additional details.
The following example code shows a very simple Init method for an operation named ExampleOp. This function would be included (along with the class structure definition routine and any other methods defined by the class) in a file named exampleop__define.pro.
FUNCTION ExampleOp::Init, _REF_EXTRA = _extra ; Initialize the superclass. IF (self->IDLitOperation::Init(TYPES=['IDLARRAY2D'], $ NAME='Example Operation', ICON='generic_op', $ _EXTRA = _extra) NE 1) THEN $ RETURN, 0 ; Unhide the SHOW_EXECUTION_UI property. self->SetPropertyAttribute, 'SHOW_EXECUTION_UI', HIDE=0 ; Return success RETURN, 1 END
The ExampleOp class is based on the IDLitOperation class (discussed in Subclassing from the IDLitOperation Class). As a result, all of the standard features of an iTool operation are already present. We don't define any keyword values to be handled explicitly in the Init method, but we do use the keyword inheritance mechanism to pass keyword values through to methods called within the Init method. The ExampleOp Init method does the following things:
'IDLARRAY2D', provide a Name for the object instance, and provide an icon. Finally, we use the _EXTRA keyword inheritance mechanism to pass through any keywords provided when the ExampleOp Init method is called.
The operation class Cleanup method handles any cleanup required by the operation object, and should do the following:
Calling the superclass' cleanup method will destroy any objects created when the superclass was initialized.
| Note If your operation class is based on the IDLitOperation class, and does not create any pointers or objects of its own, the Cleanup method is not strictly required. It is always safest, however, to create a Cleanup method that calls the superclass' Cleanup method. |
See IDLitOperation::Cleanup for additional details.
The following example code shows a very simple Cleanup method for the ExampleOp operation:
PRO ExampleOp::Cleanup ; Clean up superclass self->IDLitOperation::Cleanup END
Since our operation does not have any instance data of its own, the Cleanup method simply calls the superclass Cleanup method.
The operation class DoAction method is called by the iTool system when an operation is requested by the user. (Note that data-centric operations do not need to implement the DoAction method because it is implemented by the IDLitDataOperation class itself.) The DoAction method is responsible for the following:
| Note If your operation changes the values of its own registered properties (as the result of user interaction with a dialog or other interface element called by DoUIService, for example), be sure to call the RecordInitialValues and RecordFinalValues methods. This ensures that changes made through the dialog are placed in the undo-redo transaction buffer. |
The following example code shows a simple DoAction method for the ExampleOp operation. This operation retrieves the STYLE property of any selected IDLitVisSurface objects and increments its value by 1. Repeated invocations of this operation would cause the selected surfaces to loop through the seven available surface styles.
FUNCTION ExampleOp::DoAction, oTool ; Make sure we have a valid iTool object. IF ~ OBJ_VALID(oTool) THEN RETURN, OBJ_NEW() ; Get the selected objects oTargets = oTool->GetSelectedItems() ; Select only IDLitVisSurface objects. If there are ; no surface objects selected, return a null object. surfaces = OBJ_NEW() FOR i = 0, N_ELEMENTS(oTargets)-1 DO BEGIN IF (OBJ_ISA(oTargets[i], 'IDLitVisSurface')) THEN BEGIN surfaces = OBJ_VALID(surfaces[0]) ? $ [surfaces, oTargets[i]] : oTargets[i] ENDIF ENDFOR IF (~OBJ_VALID(surfaces[0])) THEN RETURN, OBJ_NEW() ; Create a command set: oCmdSet = self->IDLitOperation::DoAction(oTool) ; Record the initial values IF (~ self->RecordInitialValues(oCmdSet, surfaces, '')) THEN $ BEGIN OBJ_DESTROY, oCmdSet RETURN, OBJ_NEW() ENDIF ; Increment the style index for each surface. FOR i = 0, N_ELEMENTS(surfaces)-1 DO BEGIN ; Retrieve the current surface style and increment it surfaces[i]->GetProperty, STYLE = styleIndex IF styleIndex eq 6 THEN BEGIN styleIndex = 0 ENDIF ELSE BEGIN styleIndex += 1 ENDELSE ; Set the new surface style surfaces[i]->SetProperty, STYLE = styleIndex ENDFOR oTool->RefreshCurrentWindow ; Record the final values result = self->RecordFinalValues(oCmdSet, surfaces, '') RETURN, oCmdSet END
The ExampleOp operation DoAction method does the following things:
The operation class RecordInitialValues method is responsible for recording the appropriate "before" values from the specified objects in the provided IDLitCommandSet object. The values recorded depend entirely on the operation being performed.
The following example code shows a simple RecordInitialValues method for the ExampleOp operation. An IDLitCommand object is created for each of the target objects, and the value of the STYLE property of each object is recorded as an Item in the command object.
FUNCTION ExampleOp::RecordInitialValues, oCmdSet, oTargets, idProp
; Loop through the target objects and record the value of the
; STYLE property.
FOR i = 0, N_ELEMENTS(oTargets)-1 DO BEGIN
; Create a command object to store the values.
oCmd = OBJ_NEW('IDLitCommand', $
TARGET_IDENTIFIER = oTargets[i]->GetFullIdentifier())
; Get the value of the STYLE property
oTargets[i]->GetProperty, STYLE = styleIndex
; Add the value to the command object
void = oCmd->AddItem('OLD_STYLE', styleIndex)
; Add the command object to the command set
oCmdSet->Add, oCmd
ENDFOR
RETURN, 1
END
The ExampleOp operation RecordInitialValues method simply loops through the supplied list of target objects, creating a new IDLitCommand object for each. We set the TARGET_IDENTIFIER property for each command object. Next, we retrieve the value of the STYLE property for each target object and add it to the command object as an Item. Finally, we add each command object to the supplied IDLitCommandSet object.
The operation class RecordFinalValues method is responsible for recording the appropriate "after" values from the specified objects in the provided IDLitCommandSet object. The values recorded depend entirely on the operation being performed.
The following example code shows a simple RecordFinalValues method for the ExampleOp operation. The new value of the STYLE property of each target object is recorded in the appropriate IDLitCommand object retrieved from the command set.
FUNCTION ExampleOp::RecordFinalValues, oCmdSet, oTargets, idProp
; Loop through the target objects and record the value of the
; STYLE property.
FOR i = 0, N_ELEMENTS(oTargets)-1 DO BEGIN
; Retreive the appropriate command object from the
; command set.
oCmd = oCmdSet->Get(POSITION = i)
; Get the value of the STYLE property
oTargets[i]->GetProperty, STYLE = styleIndex
; Add the value to the command object
void = oCmd->AddItem('NEW_STYLE', styleIndex)
ENDFOR
RETURN, 1
END
The ExampleOp operation RecordFinalValues method simply loops through the supplied list of target objects, recording the new value for the STYLE property in the IDLitCommand object associated with each target.
The operation class GetProperty method retrieves property values from the operation object instance or from instance data of other associated objects. It should retrieve the requested property value, either from the operation object's instance data or by calling another class' GetProperty method.
| Note Any property registered with a call to the RegisterProperty method must be listed as a keyword to the GetProperty method either of the operation class or one of its superclasses. |
See IDLitOperation::GetProperty for additional details.
The following example code shows a very simple GetProperty method for the ExampleOp operation:
PRO ExampleOp::GetProperty, _REF_EXTRA = _extra ; get superclass properties IF (N_ELEMENTS(_extra) GT 0) THEN $ self->IDLitOperation::GetProperty, _EXTRA = _extra END
The GetProperty method first defines the keywords it will accept. There must be a keyword for each property of the operation type. The keyword inheritance mechanism allows properties to be retrieved from the ExampleOp class' superclasses without knowing the names of the properties.
In this example, there are no properties specific to the ExampleOp object, so we simply call the superclass' GetProperty method, passing in all of the keywords stored in the _extra structure.
The operation class SetProperty method stores property values in the operation object's instance data or in properties of associated objects. It should set the specified property value, either by storing the value directly in the operation object's instance data or by calling another class' SetProperty method.
| Note Any property registered with a call to the RegisterProperty method must be listed as a keyword to the SetProperty method either of the operation class or one of its superclasses. |
See IDLitOperation::SetProperty for additional details.
The following example code shows a very simple SetProperty method for the ExampleOp operation:
PRO ExampleOp::SetProperty, _REF_EXTRA = _extra IF (N_ELEMENTS(_extra) GT 0) THEN $ self->IDLitOperation::SetProperty, _EXTRA = _extra END
The SetProperty method first defines the keywords it will accept. There must be a keyword for each property of the operation. The keyword inheritance mechanism allows properties to be set on the ExampleOp class' superclasses without knowing the names of the properties.
In this example, there are no properties specific to the ExampleOp object, so we simply use the N_ELEMENTS function to check whether the _extra structure contains any elements. If it does, we call the superclass' SetProperty method, passing in all of the keywords stored in the _extra structure.
The operation class UndoOperation method is called when the user undoes the operation by selecting "Undo" from a menu or toolbar.
The following example code shows a very simple UndoOperation method for the ExampleOp operation:
FUNCTION ExampleOp::UndoOperation, oCommandSet
; Retrieve the IDLitCommand objects stored in the
; command set object.
oCmds = oCommandSet->Get(/ALL, COUNT = nObjs)
; Get a reference to the iTool object.
oTool = self->GetTool()
; Loop through the IDLitCommand objects and restore the
; original values.
FOR i = 0, nObjs-1 DO BEGIN
oCmds[i]->GetProperty, TARGET_IDENTIFIER = idTarget
oTarget = oTool->GetByIdentifier(idTarget)
; Get the old value
IF (oCmds[i]->GetItem('OLD_STYLE', styleIndex) EQ 1) THEN $
oTarget->SetProperty, STYLE = styleIndex
ENDFOR
RETURN, 1
END
The UndoOperation method does the following things:
| Note The UndoOperation method could also have been implemented without the use of the values stored in the command set object simply by decrementing the value of the STYLE property for each target. |
The operation class RedoOperation method is called when the user redoes the operation by selecting "Redo" from a menu or toolbar.
The following example code shows a very simple RedoOperation method for the ExampleOp operation:
FUNCTION ExampleOp::RedoOperation, oCommandSet
; Retrieve the IDLitCommand objects stored in the
; command set object.
oCmds = oCommandSet->Get(/ALL, COUNT = nObjs)
; Get a reference to the iTool object.
oTool = self->GetTool()
; Loop through the IDLitCommand objects and restore the
; new values.
FOR i = 0, nObjs-1 DO BEGIN
oCmds[i]->GetProperty, TARGET_IDENTIFIER = idTarget
oTarget = oTool->GetByIdentifier(idTarget)
; Get the new value
IF (oCmds[i]->GetItem('NEW_STYLE', styleIndex) EQ 1) THEN $
oTarget->SetProperty, STYLE = styleIndex
ENDFOR
RETURN, 1
END
The RedoOperation method does the following things:
| Note The RedoOperation method could also have been implemented without the use of the values stored in the command set object simply by incrementing the value of the STYLE property for each target. |
IDL Online Help (March 06, 2007)