Action Interfaces

Action interfaces only contain UI Action Functions that provide a programmatic method of "pressing" buttons and keys in the UI for a plug-in. As mentioned in the opening section of this doc, these action functions have certain special characteristics and possess additional descriptive metadata, relative to the functions we've described so far. Here's an annotated Action Interface example for the EdMesh system we looked at above.

The public interface (in a public header file)

#include"iFnPub.h"
#define EM_ACT_INTERFACE Interface_ID(0x65678, 0x123)
#define GetEMActionsInterface(cd) \
   (EMActions*)(cd)->GetFPInterface(EM_ACT_INTERFACE)

enum {
   ema_create, ema_create_enabled, ema_create_checked,
   ema_delete, ema_delete_enabled,
};

class EMActions: public FPInterface
{
   public:
   virtualFPStatus Create()=0;
   virtualFPStatus Delete()=0;
   virtualBOOL IsCreateEnabled()=0; 
   virtualBOOL IsCreateChecked()=0; 
   virtualBOOL IsDeleteEnabled()=0; 
};

As with normal function interfaces, we we have an Interface_ID defined and interface accessor helpers. There is also a function ID enum, but in this case there are more IDs than published actions. Each action function can have associated with it up to 3 predicate functions that users of the interface can call to determine status for the action. These are the isEnabled, isChecked, and isVisible predicates and they are used mostly by the UI elements that can be bound to an interface action to determine things like whether a button or menu item should greyed or a checkbutton should be highlighted or whether the action should be added to an about-to-be-displayed pop-up menu. The isEnabled & isVisible predicates are optional and default to always TRUE. The isChecked predicate is for actions that toggle a state, for example starting & stopping a mouse command mode. This predicate is also optional and defaults to FALSE, but it is required if the action is bound to TYPE_CHECKBUTTON via the ParamMap2 system.

Declarations for action functions along with any associated predicates are supplied in the virtual interface class. Action functions always take zero arguments and return an FPStatus result which indicates whether the action was succesfully performed or not (success if FPS_OK, but it may return values such as FPS_ACTION_DISABLED if the action was not enabled at the time). Predicate functions always take zero arguments and return a BOOL.

Here are some action function call examples:

EMActions* emai = GetEMActionsInterface(edmeshCD);
//...
if (emai->IsCreateEnabled()) // direct calls
   emai->Create();

Indirect Calls:

if (emai->IsEnabled(ema_delete))
   emai->Invoke(ema_delete);

This indirect example uses one of the predicate helper functions to call the isEnabled predicate in the interface for a given function, specified by FunctionID.

The implementation class (in the .cpp implementation file)

class EMActionsImp : publicEMActions
{
   public:
   BOOLcreating;

   DECLARE_DESCRIPTOR(EMActionsImp)
   BEGIN_FUNCTION_MAP
     FN_ACT(ema_create, Create)
     FN_PRED(ema_create_enabled, IsCreateEnabled)
     FN_PRED(ema_create_checked, IsCreateChecked)
     FN_ACT(ema_delete, Delete)
     FN_PRED(ema_delete_enabled, IsDeleteEnabled)
   END_FUNCTION_MAP

   voidInit()
   {
     // initialize any state data
     creating = FALSE;
   }
   FPStatus Create()
   {
     //... start or stop the create mode
     //...
     creating = !creating; // toggle creating state
     //...
     return FPS_OK;
   }
   BOOLIsCreateEnabled()
   {
     //... determine if create enable, perhaps
     // vertex subobjlevel test, etc.
   }
   BOOLIsCreateChecked()
   {
     return creating; // the current create state
   }
   //...
};

The action interface implementation class contains implementations for the actions and predicates, as required by the virtual interface. It also contains a FUNCTION_MAP, but in this case the actions and predicates entries in the map are defined with their own special FN_ macros, FN_ACT for actions and FN_PRED for predicates.

The Create action in this example corresponds to a vertex create mode start button and so it provides an isChecked predicate to tell what state the action is in, in this case checked => in create mode, not checked => not in create mode. The implementation interface contains a data element 'creating' to help keep track of this state. Note that it is intialized in a method called Init(). Since the varargs constructor is the normal way the interface instance is created, it guarantees to call this (virtual) init method for you; alternatively, you could make it a static data member and statically initialize it.

The interface definition (in the .cpp implementation file)

static EMActionsImp emai
(
   EM_ACT_INTERFACE, _T("Actions"), IDS_EMAI, &edmeshCD, FP_ACTIONS,
   ema_create, _T("create"), IDS_CREATE, 0,
   f_predicates, ema_create_enabled, ema_create_checked,

   FP_NO_FUNCTION,
     f_category, _T("creation"), 0,
     f_iconRes, IDI_CREATE_ICON,
     f_toolTip, IDS_VERTEX_CREATE_BY_CLICKIN,
     f_ui, em_params, 0, TYPE_CHECKBUTTON, IDC_CREATE,
     GREEN_WASH,
   end,
     ema_delete, _T("delete"), IDS_DELETE, 0,
     f_isEnabled, ema_delete_enabled,
     f_ui, em_params, 0, BUTTON, IDC_DELETE,
   end,
   end
);

The constructor for an Action Interface follows a slightly different syntax to a normal function interface. The header parameters are the same (Interface_ID, internal name, descriptor, CD, flags), the FP_ACTIONS flag must be specified to mark this as an action interface.

Each action function is specified by header parameters giving FunctionID, internal name, descriptor resID and flag bits. This is followed by one or more tagged action function options, in the same manner as parameter options in a ParamBlockDesc2 constructor. Each option defines a separate item of metadata for the function In the case of ema_create, the following are specified:

The possible options include:

f_category category name, as internal TCHAR* and localizable string resID, defaults to interface name

f_predicates supply 3 functionIDs for isEnabled, isChecked, isVisible

predicates

f_isEnabled isEnabled predicate functionID

f_isChecked isChecked predicate functionID

f_isVisible isVisible predicate functionID

f_iconRes icon as resource ID

f_icon icon as UI .bmp filename, index pair, as per CUI icon specifications

f_buttonText button text string resID, defaults to function description

f_toolTip tooltip string resID, defaults to function description

f_menuText menu item text string resID, defaults to buttonText or function description

f_ui UI spec if paramMap2-implemented UI (pmap blockID, mapID, control type, button or checkbutton resID, hilight col if chkbtn)

f_shortcut default keyboard shortcuts for action functions

f_keyArgDefault marks a parameter as optional and supplies a default value. Optional parameters must come after the positional parameters. Example:

Meshop::buildMapFaces, _T("buildMapFaces"), 0, TYPE_VOID, 0, 3,
   _T("source"),  0, TYPE_FPVALUE_BR,
   _T("keep"),     0, TYPE_BOOL, f_keyArgDefault, FALSE,
   _T("channel"),  0, TYPE_INT, f_keyArgDefault, 0,

f_inOut specifies whether \_BR parameter is just for input, just for output or both via FPP_OUT_PARAM, FPP_IN_PARAM, or FPP_IN_OUT_PARAM argument. Default is FP_IN_OUT_PARAM.

f_macroEmitter Supply a pointer to FPMacroEmitter subclass instance to customize macro-recorder emission. The Emit() method is called on this instance with interface and function def pointers for the particular action function to be emitted.

The ActionTable system 'table ID' DWORD for Action interfaces is generated automatically from the Interface_ID for the Action interface (by xor-ing the two DWORDS in the interface ID). If this generates an ID that clashes with built-in table IDS (in the range 0-64), an error message is generated and you should choose a more random Interface_ID.

You define the UI context for the interface as a whole, rather than for each individual action function. The context ID should be specified immediately after the flag word in an Action interface descriptor constructor. For example:

static IKChainActionsImp sIKChainActionImp(
   IKCHAIN_FP_INTERFACE_ID, _T("Action"), IDS_IKCHAIN_ACT,
   &theIKChainControlDesc, FP_ACTIONS, kActionMainUIContext,
   IKChainActionsImp::kSnap, _T("snap"), IDS_IKCHAIN_SNAP, 0,
   //...

The constant 'kActionMainUIContext' is one of the built-in ActionTable system contexts that you can use (defined in \\MAXSDK\include\ActionTable.h), and is most likely the one to be used. If you create an entirely new context and context ID, you will need to register and control its activation yourself (see the IActionManager in \\MAXSDK\include\ActioNTable.h for details).

The automatically-generated ActionTable for the Action interface can be accessed via a new FPInterface method: virtual``ActionTable* GetActionTable();

By default, the action table is activated immediately and stays active throughout the 3ds Max session. You can dynamically control this activation via a new FPInterface method: virtual void EnableActions(BOOL onOff);

This enables or disables the entire set of actions in the interface. You might do this if the actions are only to be active during certain times in the running of 3ds Max. Usually, this control is achieved via ActionTable contexts.