Chapter 4 - Building Plugin Interfaces (UI)

Defining UI for the user

The gadgets available to the plugin writer are a subset of the ones seen in the regular Chalice package. These gadgets provide a value at each frame where an image might be requested, so that the plugin routine can create the image. Currently, the types of input available to the plugin programmer are:

Integer

Float

Integer Vector

Float Vector

Toggle

Channel Mask

Button

Menu

String

File

Picture File

Inputs are usually grouped into tabs, like this:

The Info tab is standard to all nodes, and appears by default - you don't have to define it. Any other tabs need to be added before the parameters that appear in them. There must be at least one tab. So the minimum code for a node that has only one parameter would be:

{
    add a tab
    add the parameter
}
To do multiple tabs, the code would be structured as:
{
    add tab 1
    add parameter 1
    add parameter 2
    add parameter 3
    add tab 2
    add parameters
    add tab 3
    add parameters
}
The routine used to define the node UI is
CPIDSOEXPORT void upiCreateParameters(void)
{
}
This routine is called whenever a node is created and placed on the work area. The routines used to add the given parameters are as follows:
Integer

Usage:   cpiAddInteger(char *label, int default, int min_val,
                       int max_val, int flags);
Example: cpiAddInteger("Integer", 1, 0, 10, P_ANIMATE);
Result:

Float

Usage:   cpiAddFloat(char *label, float default, float min_val,
                       float max_val, int flags);
Example: cpiAddFloat("Float", 1.0, 0.0, 10.0, P_ANIMATE);
Result:

Integer Vector

Usage:   cpiAddIntVector(char *label, int size, int *default, 
                         int min_val, int max_val, int flags);
Example: int idef[] = {1, 150}; cpiAddIntVector("Int Vector", 2, idef, 1, 100, 0);
Result:

Float Vector

Usage:   cpiAddFloatVector(char *label, int size, float *default, 
                          float min_val, float max_val, int flags);
Example: float fdef[] = {-0.5, 3.1415}; cpiAddFloatVector("Float Vector", 2, fdef, 0.0, 1.0, 0);
Result:

Toggle

Usage:   cpiAddToggle(char *label, int default, int flags );
Example: cpiAddToggle( "Toggle", 0, 0);
Result:

Menu

Usage:   cpiAddMenu(char *label, int default, char *menu[], int flags);
Example: char *mdef[] = {"a choice", "another choice", "default choice", NULL}; cpiAddMenu( "Menu", 2, mdef, 0);
Result:

Channel Mask

Usage:   cpiAddChannelMask(char *label, int default, int flags );
Example: cpiAddChannelMask("Channel Mask", 0x0002, 0);
Result:

Button

Usage:   cpiAddButton(char *label);
Example: cpiAddButton("Button");
Result:

String

Usage:   cpiAddString(char *label, char *default, int flags );
Example: cpiAddString("String", "default value", 0);
Result:

File Chooser

Usage:   cpiAddFile(char *label, char *default, int flags );
Example: cpiAddFile("File", "default file", 0);
Result:

Picture File

Usage:   cpiAddPicFile(char *label, char *default, int flags );
Example: char sdef[] = {"/a/picture/file.pic"}; cpiAddPicFile("Picture File", sdef, 0);
Result:

Common Parameters

The routines have common parameters. The 'label' parameter is the name of the field - this name must be unique to the node. This will appear next to the field as a label for the user, and this string will also be used by the software to determine which field of a given type is being referred to. For example, say you define the following:
    cpiAddTab( "Window" );
    cpiAddInteger( "Center", 1, 0, 150, P_MIN | P_ANIMATE );
    cpiAddInteger( "Width", 5, 0, 150, P_MIN | P_ANIMATE );
Then when you want to get the value of the "Center" field, you will do this
    cpiGetInteger( "Center", result->time, &center )
This tells the software to get the value associated with the field you called "Center" at the current time. We will cover this in depth in a moment.

The default parameter is just that, a value to initialize the gadget with when it first appears.
min_val and max_val are values to put at the ends of the slider associated with the gadget. If the appropriate flag is set (see below), then these values are enforced - if not, they just appear as a guide to the user of a reasonable range of values.
flags may be any of the following:

P_MIN       enforce the minimum value as a hard limit
P_MAX       enforce the maximum value as a hard limit
P_MINMAX    enforce both min and max values as hard limits
P_ANIMATE   this parameter may be animated. If this flag is not set,
            the grey STED box will not appear next to this parameter,
            and its value will apply across all frames.
P_HIDDEN    this parameter will not appear in the node interface, but it
            will be available internally to the node, and its value(s) 
            will be saved to the .grail file when it is written. This 
            can be usefull for allocating extra parameters which users
            will not be changing; the track node, for example, has 
            hidden flags set up for internal use, which you can see if 
            you allocate a track node and then open up the STED on its 
            parameters.
Flags may be combined with a logical 'OR' (|) to set more than one flag.

Retrieving values from the user

To get a value back from a UI field, use the name of the field and the appropriate routine. These are:
   Type                     Routine
Integer                 int cpiGetInteger( char  *param,
                                           float  time,
                                           int   *value );

Float                   int cpiGetFloat( char  *param,
                                         float  time,
                                         float *value );

Integer Vector          int cpiGetIntVector( char        *param,
                                             float        time,
                                             unsigned int element,
                                             int         *value );

Float Vector            int cpiGetFloatVector( char      *param,
                                             float        time,
                                             unsigned int element,
                                             float       *value );

Toggle                  use cpiGetInteger() - a return value of
                        0 means the toggle is not set, 1 means it is.

Menu                    use cpiGetInteger() - the result is the index 
                        into the menu array that was passed when the
                        menu was defined. Indices begin at 0.

Channel Mask            use cpiGetInteger() - bits are set depending on
                        whether the corresponding channel button was
                        depressed. Bits start at the low end, so
                            (value & 0x01)  = red
                            (value & 0x02)  = green
                            (value & 0x04)  = blue
                            (value & 0x08)  = alpha
                            etc

Button                  Buttons don't return a value, they cause 
                        upiParameterChanged() to be executed, with the
                        name of the button passed in as an argument.

String                  int cpiGetString( char  *param,
                                          float  time,
                                          char  *buffer );

File                    use cpiGetString()

Picture File            use cpiGetString()

UI examples

The following code illustrates the setup and return values of the various UI gadgets. You can also find this code in UItester.C; you are encouraged to compile and load this as a plugin. Change some of it, to see the result.
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <memory.h>
#include <string.h>
#include "cpi.h"

// some constants to make the code more readable
#define FIRST_ELEM        0
#define SECND_ELEM        1

#define MAX_STRING        256

// define the user interface tabs and gadgets
CPIDSOEXPORT void upiCreateParameters( void )
{
    int        idef[] = {1, 150};
    float    fdef[] = {-0.5, 3.14159};
    char    *mdef[] = {"a choice", 
                       "another choice", 
                       "default choice", NULL};
    char    sdef[] = {"/a/picture/file.pic"};

    // define first tab
    cpiAddTab( "some stuff" );
    cpiAddInteger( "Integer", 1, 0, 10, P_ANIMATE );
    cpiAddFloat( "Float", 1.0F, 0.0F, 10.0F, P_ANIMATE );
    cpiAddIntVector( "Int Vector", 2, idef, 1, 100, 0);
    cpiAddFloatVector( "Float Vector", 2, fdef, 0.0, 1.0, 0);
    cpiAddToggle( "Toggle", 0, 0);
    cpiAddButton( "Button" );

    // define second tab
    cpiAddTab( "more stuff" );
    cpiAddString( "String", "default value", 0);
    cpiAddMenu( "Menu", 2, mdef, 0);
    cpiAddChannelMask( "Channel Mask", 0x0002, 0);
    cpiAddFile( "File", "default file", 0);
    cpiAddPicFile( "Picture File", sdef, 0);
}

// this routine is called by Chalice whenever a node parameter is
// changed - it comes in with the name of the parameter that changed
CPIDSOEXPORT void upiParameterChanged(char *name)
{
    int      ival, ivec[2];
    float    fval, fvec[2];
    char     string[MAX_STRING];

    // you might just get all the values from the node, or you can
    // check which was changed and only get that/those
    if (!strcmp(name, "Integer"))
        cpiGetInteger("Integer", 0, &ival);
    else if (!strcmp(name, "Float"))
        cpiGetFloat("Float", 0, &fval);
    else if (!strcmp(name, "Int Vector"))
    {
        cpiGetIntVector("Int Vector", 0, FIRST_ELEM, &ivec[0]);
        cpiGetIntVector("Int Vector", 0, SECND_ELEM, &ivec[1]);
    }
    else if (!strcmp(name, "Float Vector"))
    {
        cpiGetFloatVector("Float Vector", 0, FIRST_ELEM, &fvec[0]);
        cpiGetFloatVector("Float Vector", 0, SECND_ELEM, &fvec[1]);
    }
    else if (!strcmp(name, "Toggle"))
        cpiGetInteger("Toggle", 0, &ival); // ival == 1 if toggle set
    else if (!strcmp(name, "String"))
        cpiGetString("String", 0, string);
    else if (!strcmp(name, "Menu"))
        // return an index into the menu array, starting at 0
        cpiGetInteger("Menu", 0, &ival); 
    else if (!strcmp(name, "File"))
        cpiGetString("File", 0, string);
    else if (!strcmp(name, "Picture File"))
        cpiGetString("Picture File", 0, string);
    else if (!strcmp(name, "Button"))
        do_button_function();    // get here if button pushed
}
The result of the above is the following dialog in Chalice:

Error Handling

The Chalice UPI provides 2 mechanisms for reporting errors to the user. These are
void cpiWarning(char *format, ...)

void cpiError(char *format, ...)
cpiWarning() returns from the call and continues to process the image; cpiError() does not return. A warning turns the info button on the node to yellow; an error turns it to red.
The message passed into the routine is made available to the user when he/she holds the left mouse button down on the node's info button.
The message is in the form of a character string followed by some optional arguments in exactly the same way that printf() works in C. For example:
    cpiError("Must have alpha channel to proceed");
    cpiWarning("Negative value (%d) not allowed", ival);
    cpiError("Can't open file %s", filename);
cpiWarning() and cpiError() can only be called during the cooking of an image. Thus if you want to issue an error for bad input values, you will have to store the error and retrieve it during cook time, or postpone checking inputs until the image is cooked.

Example 1: Say you want to ask the user for a lookup table. Then you might have the following code fragments:

// user routine to load a lookup table
void loadfile(char *filename)
{
    FILE    *fp;

    if (!strcmp(Curfile, filename)) /* if different, load new table */
        return;

    if ((fp = fopen(filename, "r")) == NULL)
    {
        lookuptable.error = E_NOFILE;    /* file missing - save error */
        return;
    }

    if (readheader(fp))
    {
        lookuptable.error = E_BADHEAD;    /* bad header - save error */
        return;
    }

    if (readluts(fp))
    {
        lookuptable.error = E_BADLUT;    /* bad table - save error */
        return;
    }

    fclose(fp);
    strcpy(Curfile, filename);
    lookuptable.error = E_NONE;            /* everything went fine */
}

// define the UI to ask for a lookup table
CPIDSOEXPORT void upiCreateParameters( void )
{
    char def[] = {"/usr/grail/chalice/support/luts/cineview.dlut"};

    cpiAddTab( "Lookup Table" );
    cpiAddFile( "Use File", def, 0);
}

// when input changes, try to load the lookup table - if we fail, save
// that fact for later
CPIDSOEXPORT void upiInputChanged( void )
{
    char    filename[MAXSTR];

    cpiGetString("Use File", 0, filename);
    loadfile(filename);
}

// now we are going to process the image, but first, check to see if
// there are any errors to report. Another way of doing this would be
// to wait until now to get the file name, and try to read it now. In
// that case, cpiError() could be called directly from loadfile()
CPIDSOEXPORT int upiProcessImage( CPI_Image *result )
{
    if (lookuptable.error == E_NOFILE)
        cpiError("Could not open lookup table");
    else if (lookuptable.error == E_BADHEAD)
        cpiError("Bad header in lookup table");
    else if (lookuptable.error == E_BADLUT)
        cpiError("Bad or missing table data");
  
    ...process image...
}
Example 2: toggle the warning/error behavior - this example is provided as UIfeedback.C
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <memory.h>
#include <string.h>
#include "cpi.h"

// some constants to make the code more readable
#define TAB_NAME        "Parameter Area"
#define HOW_MANY        "How Many"
#define SEE_ERR            "Show Error"
#define LANGUAGE        "Language"

#define MAX_STRING        256

char    *menu[] = { "Dutch",
                    "English", 
                    "French", 
                    "German", 
                    NULL};

// define the user interface tabs and gadgets
CPIDSOEXPORT void upiCreateParameters( void )
{
    // define parameters
    cpiAddTab( TAB_NAME );
    cpiAddInteger( HOW_MANY, 1, 0, 10, P_ANIMATE );
    cpiAddMenu( LANGUAGE, 1, menu, 0);
    cpiAddToggle( SEE_ERR, 0, 0);
}

// warning/error messages can only be sent during cooking, so
// we check for errors in upiProcessImage()
CPIDSOEXPORT int upiProcessImage(CPI_Image *result)
{
    int        count, choice, state;

    // get the parameter values
    cpiGetInteger(HOW_MANY, result->info.time, &count);
    cpiGetInteger(SEE_ERR, result->info.time, &state);
    cpiGetInteger(LANGUAGE, result->info.time, &choice);

    if (state)
        cpiError("Language is %s", menu[choice]);
    else
        cpiWarning("%d Sample(s)", count);

    return 0;
}

[Previous Page] [Next Page]
[Table of Contents] [Index]

Copyright © 1998 Silicon Grail Inc.
710 Seward Street, Hollywood, CA 90038