Creating a New Generalized 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.
How an IDLitOperation Works
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:
- Retrieving the currently selected items and determining which items the operation should be applied to.
- Creating an IDLitCommandSet object to contain undo/redo information.
- Recording the initial values of the selected objects in the IDLitCommandSet object, if necessary.
- Performing the actions associated with the operation.
- Recording the final values of the selected objects in the IDLitCommandSet object, if necessary.
- Returning the IDLitCommandSet object.
Creating an IDLitOperation
The process of creating an IDLitDataOperation is outlined in the following sections:
Creating the Class Structure Definition
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 (Object Programming).
Note
The class structure definition is generally the last routine in the .pro file that defines an object class.
Subclassing from the IDLitOperation 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" (IDL Reference Guide) for details on the methods and properties available to classes that subclass from IDLitOperation.
Example Class Structure Definition
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.
Discussion
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.
Creating an Init Method
The operation class Init method handles any initialization required by the operation object, and should do the following:
- define the Init function method, using the keyword inheritance mechanism to handle "extra" keywords
- call the Init methods of any superclasses, using the keyword inheritance mechanism to pass "extra" keywords
- register any properties of the operation, and set property attributes as necessary
- perform other initialization steps as necessary
- return the value 1 if the initialization steps are successful, or 0 otherwise
Definition of the Init Function
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:
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 (Application Programming) for details on IDL's keyword inheritance mechanism.)
Superclass Initialization
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:
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.
Error Checking
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:
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.
Keywords to the Init Method
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" (IDL Reference Guide).
Note
Always use keyword inheritance (the _EXTRA keyword) to pass keyword parameters through to the superclass. (See Keyword Inheritance (Application Programming) for details on IDL's keyword inheritance mechanism.)
Standard Base Class
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:
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.
Return Value
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.
Registering Properties
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:
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.
Setting Property Attributes
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:
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.
Example Init Method
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
Discussion
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:
- Calls the Init method of the superclass, IDLitOperation. We use the TYPES keyword to specify that our operation works on data that has the iTool data type
'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 theExampleOpInit method is called. - Returns the integer 1, indicating successful initialization.
Creating a Cleanup Method
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" (IDL Reference Guide) for additional details.
Example Cleanup Method
The following example code shows a very simple Cleanup method for the ExampleOp operation:
Discussion
Since our operation does not have any instance data of its own, the Cleanup method simply calls the superclass Cleanup method.
Creating a DoAction 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:
- determining which objects the operation should be applied to (generally, but not always, the objects that are selected when the operation is invoked)
- retrieving the data from the selected objects
- creating an IDLitCommandSet object that will contain undo/redo data
- saving the state of the selected objects before the actions associated with the operation are performed in the command set object
- performing the requested actions on the selected objects
- saving the state of the selected objects after the actions associated with the operation are performed in the command set object
- returning the command set object
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.
Example DoAction Method
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
Discussion
The ExampleOp operation DoAction method does the following things:
- Checks the validity of the iTool object passed to the DoAction method.
- Retrieves the list of selected objects from the iTool object.
- Filters out any selected objects that are not IDLitVisSurface objects.
- Calls the superclass DoAction method to create an IDLitCommandSet object.
- Calls the RecordInitialValues method to record the relevant values in the command set object before the operation is performed.
- Loops through the list of IDLitVisSurface objects and increments the STYLE property of each by 1.
- Calls the RecordFinalValues method to record the relevant values in the command set object after the operation has been performed.
- Returns the command set object.
Creating a RecordInitialValues Method
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.
Example RecordInitialValues Method
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
Discussion
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.
Creating a RecordFinalValues Method
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.
Example RecordFinalValues Method
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
Discussion
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.
Creating a GetProperty Method
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" (IDL Reference Guide) for additional details.
Example GetProperty Method
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
Discussion
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.
Creating a SetProperty Method
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" (IDL Reference Guide) for additional details.
Example SetProperty Method
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
Discussion
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.
Creating an UndoOperation Method
The operation class UndoOperation method is called when the user undoes the operation by selecting "Undo" from a menu or toolbar.
Example UndoOperation Method
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
Discussion
The UndoOperation method does the following things:
- Retrieves an array of IDLitCommand objects from the supplied IDLitCommandSet object
- Gets a reference to the iTool object.
- For each command object, retrieve the identifier string for the target object. Use the identifier string to retrieve a reference to the target object itself.
- Retrieve the OLD_STYLE item from the command object and use its value to set the STYLE property on the target object.
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.
Creating a RedoOperation Method
The operation class RedoOperation method is called when the user redoes the operation by selecting "Redo" from a menu or toolbar.
Example RedoOperation Method
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
Discussion
The RedoOperation method does the following things:
- Retrieves an array of IDLitCommand objects from the supplied IDLitCommandSet object
- Gets a reference to the iTool object.
- For each command object, retrieve the identifier string for the target object. Use the identifier string to retrieve a reference to the target object itself.
- Retrieve the NEW_STYLE Item from the command object and use its value to set the STYLE property on the target object.
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.