Creating a New Data-Centric Operation

iTool operations that act primarily on data are based on the IDLitDataOperation class. The class definition file for an IDLitDataOperation object must (at the least) provide methods to initialize the operation class, get and set property values, execute the operation, and define the operation class structure. Complex operations will likely provide additional methods.

How an IDLitDataOperation Works

When an IDLitDataOperation is requested by a user, the following things occur:

  1. As with any operation, execution commences when the DoAction method is called. When called, the IDLitDataOperation retrieves the currently-selected items. If nothing is selected, the operation returns.
  2. For each selected item, the data objects of the parameters registered as "operation targets" are retrieved.
  3. The data objects are queried for iTool data types that match the types supported by the IDLitDataOperation.

For each data object that includes data of an iTool data type supported by the IDLitDataOperation, the following things occur:

  1. The data from the data object is retrieved.
  2. If the IDLitDataOperation does not have the REVERSIBLE_OPERATION property set, a copy of the data is created and placed into the undo-redo command set.
  3. The Execute method is called, with the retrieved data as its argument.
  4. If the Execute method succeeds and the IDLitDataOperation has the EXPENSIVE_COMPUTATION property set, a copy of the results is placed into the undo-redo command set.
  5. The result of the IDLitDataOperation is placed in the data object. This action will cause all visualization items that use the data object to update when the operation is completed.

Once all selected data items have been processed, the undo-redo command set is placed into the system undo-redo buffer for later use.

Creating an IDLitDataOperation

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 be 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 IDLitDataOperation Class

The IDLitDataOperation class simplifies the creation of operations that act only on data (as opposed to acting on the visual representation of that data) by providing methods that automate much of the process of execution and storing undo/redo data. If your operation class modifies data, you will almost certainly subclass from IDLitDataOperation, or from another operation that subclasses from IDLitDataOperation. See "IDLitDataOperation" (IDL Reference Guide) for details on the methods and properties available to classes that subclass from IDLitDataOperation.

Example Class Structure Definition

The following is the class structure definition for the ExampleDataOp operation class. This procedure should be the last procedure in a file named exampledataop__define.pro.

PRO ExampleDataOp__Define 
 
   struct = { ExampleDataOp,       $ 
      INHERITS IDLitDataOperation, $ 
     _byteTop: 0B                  $ 
   } 
 
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, ExampleDataOp.

Like many iTool operations that act on data, ExampleDataOp is created as a subclass of the IDLitDataOperation class. Operation classes that subclass from IDLitDataOperation class inherit methods and properties that make it easy to perform operations that affect data in an iTool.

The ExampleDataOp Operation class instance data includes a single property; a byte value that is stored in the _byteTop class structure field.

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. We strongly suggest 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, data-centric operation classes you create will be subclassed directly from the base class IDLitDataOperation:

IF (self->IDLitDataOperation::Init(_EXTRA = _extra) EQ 0) $ 
   THEN RETURN, 0 

The IDLitDataOperation class provides the base iTool functionality used in the data-centric operation classes created by ITT Visual Information Solutions. See Subclassing from the IDLitDataOperation 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 ExampleDataOp. This function would be included (along with the class structure definition routine and any other methods defined by the class) in a file named exampledataop__define.pro.

FUNCTION ExampleDataOp::Init, _REF_EXTRA = _extra 
 
; Initialize the superclass. 
IF (self->IDLitDataOperation::Init(TYPES=['IDLIMAGE'], $ 
   NAME='Example Data Operation', ICON='sum', $ 
   _EXTRA = _extra) NE 1) THEN $ 
      RETURN, 0 
 
; Register a property that holds a byte value. 
self->RegisterProperty, 'ByteTop', $ 
   DESCRIPTION='An example property', $ 
   NAME='Byte Threshold', SENSITIVE = 1 
 
; Unhide the SHOW_EXECUTION_UI property. 
self->SetPropertyAttribute, 'SHOW_EXECUTION_UI', HIDE=0 
 
; Return success 
RETURN, 1 
 
END 

Discussion

The ExampleDataOp class is based on the IDLitDataOperation class (discussed in Subclassing from the IDLitDataOperation Class). As a result, all of the standard features of an iTool data 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 ExampleDataOp Init method does the following things:

  1. Calls the Init method of the superclass, IDLitDataOperation. We use the TYPES keyword to specify that our operation works on data that has the iTool data type 'IDLIMAGE', 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 ExampleDataOp Init method is called.
  2. Registers a property that holds a byte value.
  3. 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 IDLitDataOperation 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 "IDLitDataOperation::Cleanup" (IDL Reference Guide) for additional details.

Example Cleanup Method

The following example code shows a very simple Cleanup method for the ExampleDataOp operation:

PRO ExampleDataOp::Cleanup 
 
   ; Clean up superclass 
   self->IDLitDataOperation::Cleanup 
 
END 

Discussion

Since our operation's instance data does not include any pointers or object references, the Cleanup method simply calls the superclass Cleanup method.

Creating an Execute Method

The operation class Execute method does the computational work of a data-centric operation; it is called automatically when the iTool user requests an operation based on the IDLitDataOperation class. The Execute method must accept a single argument that contains the raw data associated with an item selected by the user.

The fact that the raw data is passed to the execute method means that the Execute method itself does not need to "unpack" a data object before performing the operations, allowing rapid and simple operation execution. For example, if the operation expects data of the iTools data type IDLARRAY2D, the iTool system will include the selected two-dimensional array as the Data argument.

The actual processing performed by the Execute method depends entirely on the operation.

Example Execute Method

The following example code shows a simple Execute method for the ExampleDataOp operation, which will invert the values of the supplied data. Since our ExampleDataOp operation works on image data, this means the operation has the effect of producing the negative image.

FUNCTION ExampleDataOp::Execute, data 
 
   ; If byte data then offsets are 0 and 255, otherwise  
   ; use data minimum and maximum. 
   offsetMax = (SIZE(data, /TYPE) eq 1) ? 255b : MAX(data) 
   offsetMin = (SIZE(data, /TYPE) eq 1) ? 0b : MIN(data) 
   data = offsetMax - TEMPORARY(data) + offsetMin 
   RETURN, 1 
 
END 

Discussion

When our ExampleDataOp operation is invoked by a user, the iTool system automatically checks to see which items are selected in the visualization window. For each selection, the iTool system extracts any data of type IDLIMAGE and passes that data to the Execute method as an IDL array. Our Execute method then finds the minimum and maximum values, and inverts the data values.

Creating a DoExecuteUI Method

Suppose we want to collect some information from the user before executing our operation. If the operation class sets the SHOW_EXECUTION_UI property, the iTool system will call the DoExecuteUI method before calling the Execute method. The DoExecuteUI method is responsible for displaying a user interface that collects the appropriate information and storing that information in properties of the operation object.

Note
iTools provided with IDL that need to collect user input in this manner use the UI service mechanism, described in iTool User Interface Architecture. While it is possible for the DoExecuteUI method to perform all the necessary functions directly, using a UI service is the preferred method.

Two predefined user interface services are provided for use in DoExecuteUI methods:

See Predefined iTool UI Services for additional details.

Example DoExecuteUI Method

The following example code shows a simple DoExecuteUI method for the ExampleDataOp operation. This method relies on a UI service named 'ExampleDataOp' being registered with the current iTool.

FUNCTION ExampleDataOp::DoExecuteUI 
 
   oTool = self->GetTool() 
   IF (oTool EQ OBJ_NEW()) THEN RETURN, 0 
 
   RETURN, oTool->DoUIService('ExampleDataOp', self) 
 
END 

Discussion

If the SHOW_EXECUTION_UI property is set on our ExampleDataOp operation object, the DoExecuteUI method is called automatically when the user invokes the operation. This method does the following:

  1. Retrieve a reference to the current iTool object using the GetTool method of the IDLitIMessaging class. (IDLitIMessaging is a superclass of IDLitOperation, and thus of IDLitDataOperation.)
  2. If the retrieved iTool object reference is a null object reference, no data about the current tool is available, so we return immediately without calling the UI service.
  3. Call the ExampleDataOp UI service. Since our ExampleDataOp operation has only one property of its own (ByteTop), the ExampleDataOp UI presumably allows the user to set this value. See Creating a User Interface Service for discussion of UI services.

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 "IDLitDataOperation::GetProperty" (IDL Reference Guide) for additional details.

Example GetProperty Method

The following example code shows a very simple GetProperty method for the ExampleDataOp operation:

PRO ExampleDataOp::GetProperty, $ 
   BYTETOP = byteTop, _REF_EXTRA = _extra 
 
   IF ARG_PRESENT(byteTop) THEN BEGIN 
      byteTop = self._byteTop 
   ENDIF 
 
   ; get superclass properties 
   IF (N_ELEMENTS(_extra) GT 0) THEN $ 
      self->IDLitDataOperation::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 ExampleDataOp class' superclasses without knowing the names of the properties.

Using the ARG_PRESENT function, we check for the presence of keywords in the call to the GetProperty method. If a keyword is detected, we retrieve the value of the associated property. In this example, only one property (ByteTop) is specific to the ExampleDataOp object. We retrieve the value of the ByteTop property directly from the ExampleDataOp object's instance data.

Finally, we 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 "IDLitDataOperation::SetProperty" (IDL Reference Guide) for additional details.

Example SetProperty Method

The following example code shows a very simple SetProperty method for the ExampleDataOp operation:

PRO ExampleDataOp::SetProperty, BYTETOP = byteTop, $ 
   _REF_EXTRA = _extra 
 
   If (N_ELEMENTS(byteTop) GT 0) THEN BEGIN 
      self._byteTop = byteTop 
   ENDIF 
 
   IF (N_ELEMENTS(_extra) GT 0) THEN $ 
      self->IDLitDataOperation::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 ExampleDataOp class' superclasses without knowing the names of the properties.

Using the N_ELEMENTS function, we check to see whether a value was specified for each keyword. If a value is detected, we set the value of the associated property. In this example, only one property (ByteTop) is specific to the ExampleDataOp object. We set the value of the ExampleProperty directly in the ExampleDataOp object's instance data.

Finally, we call the superclass' SetProperty method, passing in all of the keywords stored in the _extra structure.

Creating an UndoExecute Method

The operation class' UndoExecute method is called when the user undoes an invocation of the operation and the REVERSIBLE_OPERATION property is set on the operation object. (See Operations and the Undo/Redo System for details on how undo and redo are handled in different situations.) The UndoExecute method must reverse the effect of the Execute method.

The actual processing performed by the UndoExecute method depends entirely on the operation.

Example UndoExecute Method

The following example code shows a simple UndoExecute method for the ExampleDataOp operation, which reverses the operation of the Execute method.

FUNCTION ExampleDataOp::UndoExecute, data 
 
   ; If byte data then offsets are 0 and 255, otherwise  
   ; use data minimum and maximum. 
   offsetMax = (SIZE(data, /TYPE) eq 1) ? 255b : MAX(data) 
   offsetMin = (SIZE(data, /TYPE) eq 1) ? 0b : MIN(data) 
   data = offsetMax - TEMPORARY(data) + offsetMin 
   RETURN, 1 
 
END 

Discussion

When the user undoes an invocation of our ExampleDataOp operation, the iTool system supplies the data that were computed by the Execute method when the operation was invoked. Our UndoExecute method then reverses the original operation.