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:

  1. Retrieving the currently selected items and determining which items the operation should be applied to.
  2. Creating an IDLitCommandSet object to contain undo/redo information.
  3. Recording the initial values of the selected objects in the IDLitCommandSet object, if necessary.
  4. Performing the actions associated with the operation.
  5. Recording the final values of the selected objects in the IDLitCommandSet object, if necessary.
  6. 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.

PRO ExampleOp__Define 
 
   struct = { ExampleOp, INHERITS IDLitOperation} 
 
END 

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:

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:

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 (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:

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.

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:

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.

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:

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.

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:

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.

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:

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.

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:

  1. 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 the ExampleOp Init method is called.
  2. 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:

PRO ExampleOp::Cleanup 
 
   ; Clean up superclass 
   self->IDLitOperation::Cleanup 
 
END 

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:

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:

  1. Checks the validity of the iTool object passed to the DoAction method.
  2. Retrieves the list of selected objects from the iTool object.
  3. Filters out any selected objects that are not IDLitVisSurface objects.
  4. Calls the superclass DoAction method to create an IDLitCommandSet object.
  5. Calls the RecordInitialValues method to record the relevant values in the command set object before the operation is performed.
  6. Loops through the list of IDLitVisSurface objects and increments the STYLE property of each by 1.
  7. Calls the RecordFinalValues method to record the relevant values in the command set object after the operation has been performed.
  8. 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:

  1. Retrieves an array of IDLitCommand objects from the supplied IDLitCommandSet object
  2. Gets a reference to the iTool object.
  3. 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.
  4. Retrieve the OLD_STYLE item from the command object and use its value to set the STYLE property on the target object.
  5. 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:

  1. Retrieves an array of IDLitCommand objects from the supplied IDLitCommandSet object
  2. Gets a reference to the iTool object.
  3. 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.
  4. Retrieve the NEW_STYLE Item from the command object and use its value to set the STYLE property on the target object.
  5. 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.