The following example incorporates ideas from the previous sections to show how you might approach the task of writing a compound widget. The widget is called CW_DICE, and it simulates a single six-sided die. The figure below shows the appearance of XDICE, an application that uses two instances of CW_DICE. XDICE is discussed in Using CW_DICE in a Widget Program.
The cw_dice.pro can be found in the lib subdirectory of the IDL distribution. xdice.pro can be found in the examples/doc/widgets subdirectory of the IDL distribution. Run this example procedure by entering cw_dice at the IDL command prompt or view the file in an IDL Editor window by entering .EDIT cw_dice.pro. You should examine these files for additional details and comments not included here. We present sections of the code here for didactic purposes—there is no need to re-create either of these files yourself.
The CW_DICE compound widget has the following features:
- It uses a button widget. The current value of the die is displayed as a bitmap label on the button itself. When the user presses the button, the die “rolls” itself by displaying a sequence of bitmaps and then settles on a final value. An event is generated that returns this final value.
- Timer events are used to create the rolling effect. This allows the dice to give the same appearance on machines of varying performance levels. (Timer events are discussed in Working with Widget Events.)
- The die can be set to a specific value via the SET_VALUE keyword to the WIDGET_CONTROL procedure. If the desired value is outside of the range 1 through 6, the die is rolled as if the user had pressed the button and a final value is selected randomly. Using WIDGET_CONTROL to set the value of the widget in this manner does not cause an event to be issued; IDL’s convention is that user actions cause events, while programmatic changes do not.
- The current value of the die can be obtained via the GET_VALUE keyword to the WIDGET_CONTROL procedure.
Almost any compound widget will have an associated state. The following information is used by an instantiation of the CW_DICE compound widget:
- The current value.
- The number of times the die should “tumble” before settling on a final value.
- The amount of time to take between tumbles.
- A count of how many tumbles are left before a final value is displayed, while a roll is in progress.
- The bitmaps to use for the 6 possible die values.
- The seed to use for the random number generator.
The first four items are stored in a per-widget structure kept in one of the child widget’s user values. Since the bitmaps never change, it makes sense to keep them in a COMMON block to be accessed freely by all the CW_DICE routines. It also makes sense to use a single random number seed for the entire CW_DICE class rather than one per instance to avoid the situation where multiple dice, having been created at the same time, have the same seed and thus display the same value on each roll.
Note: It is rare that using a COMMON block in a compound widget makes sense. Notice, however, that we are not storing widget state information, but read-only data (bitmaps) and data that can be overwritten at any time with no negative effects (random number generator seed). Using a COMMON block in this situation means that the read-only data can be created once and used by any number of instantiations of the CW_DICE widget. See Managing Application State for a discussion of techniques (including the per-widget structure used here) you can use to store and access widget-specific state information.
Given the above decisions, it is now possible to write the CW_DICE procedure.
The following sections discuss elements of the procedure’s source code, located in cw_dice.pro in the lib subdirectory of the IDL distribution. Run this example procedure by entering cw_dice at the IDL command prompt or view the file in an IDL Editor window by entering .EDIT cw_dice.pro.
In the CW_DICE function, beginning with function CW_DICE, parent, value, UVALUE=uvalue, notice that the code makes reference to two routines named CW_DICE_SET_VAL and CW_DICE_GET_VAL.
By using the FUNC_GET_VALUE and PRO_SET_VALUE keywords to WIDGET_BASE, WIDGET_CONTROL can call these routines whenever the user makes a WIDGET_CONTROL, SET_VALUE or GET_VALUE request. See the functions, cw_dice_set_val and cw_dice_get_val in the for details.
CW_DICE_SET_VALUE makes reference to a procedure named CW_DICE_ROLL that does the actual dice rolling. Rolling is implemented as follows:
- If this is the initial call to CW_DICE_ROLL, then pick the final value that will end up being displayed and enter this into the widget’s state. Hence, WIDGET_CONTROL, /GET_VALUE reports the final value instead of one of the intermediate “tumble” values no matter when it is called.
- If this is not the final tumble, pick a random intermediate value and display that. Then, make another timer event request for the next tumble.
- If this is the final tumble, use the saved final value.
- CW_DICE_ROLL works in cooperation with the event handler function for CW_DICE. Each timer event causes the event handler to be called and the event handler in turn calls CW_DICE_ROLL to process the next tumble.
The CW_DICE_ROLL procedure leads us to the event handler function, CW_DICE_EVENT. This event handler expects to see button press events generated from a user action as well as TIMER events from CW_DICE_ROLL. We only want to issue events for the button presses so if the tag name in the event structure is not WIDGET_TIMER, then create an event.
Using CW_DICE in a Widget Program
We can use CW_DICE to implement an application named XDICE. XDICE displays two dice as well as a “Roll” button. Pressing either die causes it to roll individually. Pressing the “Roll” button causes both dice to roll together. A text widget at the bottom displays the current value.
xdice.pro can be found in the examples/doc/widgets subdirectory of the IDL distribution. Run this example procedure by entering xdice at the IDL command prompt or view the file in an IDL Editor window by entering .EDIT xdice.pro.