Draw widgets are graphics windows that appear as part of a widget hierarchy rather than appearing as an independent window. Like other graphics windows, draw widgets can be created to use either Direct or Object graphics. (See "Graphics" in the Using IDL manual for a discussion of IDL's two graphics modes.) Draw widgets allow designers of IDL graphical user interfaces to take advantage of the full power of IDL graphics in their displays. See WIDGET_DRAW for a complete description of the function used to create draw widgets.
This section discusses the following topics:
By default, draw widgets use IDL Direct graphics. (To create a draw widget that uses Object graphics, set the GRAPHICS_LEVEL keyword to WIDGET_DRAW equal to two; see Using Object Graphics in Draw Widgets.) Once created, draw widgets using Direct graphics are used in the same way as standard Direct graphics windows created using the WINDOW procedure.
All IDL Direct graphics windows are referred to by a window number. Unlike windows created by the WINDOW procedure, the window number of a Direct graphics draw widget cannot be assigned by the user. In addition, the window number of a draw widget is not assigned until the draw widget is actually realized, and thus cannot be returned by WIDGET_DRAW when the widget is created. Instead, you must use the WIDGET_CONTROL procedure to retrieve the window number, which is stored in the value of the draw widget, after the widget has been realized.
Unlike normal graphics windows, creating a draw widget does not cause the current graphics window to change to the new widget. You must use the WSET procedure to explicitly make the draw widget the current graphics window. The following IDL statements demonstrate the required steps:
;Create a base widget. base = WIDGET_BASE() ;Attach a 256 x 256 draw widget. draw = WIDGET_DRAW(base, XSIZE = 256, YSIZE = 256) ;Realize the widgets. WIDGET_CONTROL, /REALIZE, base ;Obtain the window index. WIDGET_CONTROL, draw, GET_VALUE = index ;Set the new widget to be the current graphics window WSET, index
If you attempt to get the value of a draw widget before the widget has been realized, WIDGET_CONTROL returns the value -1, which is not a valid index.
To create a draw widget that uses Object graphics, set the GRAPHICS_LEVEL keyword to WIDGET_DRAW equal to two. Once created, draw widgets using Object graphics are used in the same way as standard IDLgrWindow objects.
All IDL Object graphics windows (that is, IDLgrWindow objects) are referred to by an object reference. Since you do not explicitly create the IDLgrWindow object used in a draw widget, you must retrieve the object reference by using the WIDGET_CONTROL procedure to get the value of the draw widget. As with Direct graphics draw widgets, the window object is not created-and thus the object reference cannot be retrieved-until after the draw widget is realized. If you attempt to retrieve the object reference for a draw widget's IDLgrWindow object before the draw widget is realized, IDL returns a null object.
Another difference between a draw widget and either a graphics window created with the WINDOW procedure or an IDLgrWindow object is that draw widgets can include scroll bars. Setting the APP_SCROLL keyword or the SCROLL keyword to the WIDGET_DRAW function causes scrollbars to be attached to the drawing widget, which allows the user to view images or graphics larger than the visible area.
The amount of memory used by a draw widget is directly related to the size of the drawable area of the widget. If a draw widget does not have scroll bars, the entire drawable area is viewable. In this case, the size of the drawable area is controlled by the XSIZE and YSIZE keywords to WIDGET_DRAW.
With the addition of scroll bars, it is possible to display an image that is larger than the viewable area (the viewport) of the draw widget. IDL provides two options for dealing with images larger than the viewport:
The concept of a virtual drawable area allows you to display portions of very large images in a draw widget without the need for enough memory to display the entire image. The price for this facility is the need to manually handle display of the correct portion of the image in an event-handling routine.
The following code creates a simple scrollable draw widget and displays an image.
| Note |
draw_scroll.pro in the examples/widgets subdirectory of the IDL distribution. You can either open the file in an IDL editor window and compile and run the code using items on the Run menu, or simply enter draw_scroll
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected. You may need to enter DEVICE, RETAIN=2 at the IDL command prompt before running this example.
; Event-handler routine. Does nothing in this example.
PRO draw_scroll_event, ev
END
; Widget creation routine.
PRO draw_scroll
; Read an image for use in the example.
READ_JPEG, FILEPATH('muscle.jpg', $
SUBDIR=['examples', 'data']), image
; Create the base widget.
base = WIDGET_BASE()
; Create the draw widget. The size of the viewport is set to
; 200x200 pixels, but the size of the drawable area is
; set equal to the dimensions of the image array using the
; XSIZE and YSIZE keywords.
draw = WIDGET_DRAW(base, X_SCROLL_SIZE=200, Y_SCROLL_SIZE=200, $
XSIZE=(SIZE(image))[1], YSIZE=(SIZE(image))[2], /SCROLL)
; Realize the widgets.
WIDGET_CONTROL, base, /REALIZE
; Retrieve the window ID from the draw widget.
WIDGET_CONTROL, draw, GET_VALUE=drawID
; Set the draw widget as the current drawable area.
WSET, drawID
; Load the image.
TVSCL, image
; Call XMANAGER to manage the widgets.
XMANAGER, 'draw_scroll', base, /NO_BLOCK
END
In this example, the drawable area created for the draw widget is the full size of the displayed image. Since IDL handles the display of the image as the scroll bars are adjusted, no event-handling is necessary to update the display.
We can easily rework the previous example to use the APP_SCROLL keyword rather than the SCROLL keyword. Using APP_SCROLL has the following consequences:
; Event-handler routine. PRO draw_app_scroll_event, ev COMMON app_scr_ex, image IF (ev.TYPE EQ 3) THEN TVSCL, image, 0-ev.X, 0-ev.Y ENDFirst, notice that since we need access to the image array in both the widget creation routine and the event handler, we place the array in a COMMON block. This is appropriate since the image data itself is not altered by the widget application.
Second, we check the
TYPEfield of the event structure to see if it is equal to3, which is the code for a viewport event. If it is, we use the values of theXandYfields of the event structure as the Position arguments to the TVSCL routine to display the appropriate portion of the image array.
| Note |
draw_app_scroll.pro in the examples/widgets subdirectory of the IDL distribution.
On the surface the two examples appear identical. The difference is that the example using APP_SCROLL uses only the memory necessary to create the smaller drawable area described by the size of the viewport, whereas the example using SCROLL uses the memory necessary to create the full drawable area described by the XSIZE and YSIZE keywords. While the example image is not so large that this makes much difference, if the image contained several hundred million pixels rather than a few hundred thousand, the memory saving could be significant.
The WIDGET_DRAW function does not have a CONTEXT_EVENTS keyword to specify that context menu events be generated when the user clicks the right mouse button over a drawable area. Instead, the event structure generated by draw widgets when the BUTTON_EVENTS keyword is set includes the PRESS and RELEASE fields, both of which contain information regarding which mouse button was pressed.
See Context-Sensitive Menus for techniques used to simulate the generation of context menu events with draw widgets.
The following example program creates a small widget application consisting of a draw widget and a droplist menu. One of three plots is displayed in the draw widget depending on the selection made from the droplist. To add to dynamic behavior, we will use timer events to change the color table used in the draw window every three seconds.
| Note |
draw_widget_example.pro in the examples/widgets subdirectory of the IDL distribution. You can either open the file in an IDL editor window and compile and run the code using items on the Run menu, or simply enter draw_widget_example
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected.
; Event-handler routine PRO draw_widget_example_event, ev ; We need to save the value of the seed variable for the random ; number generator between calls to the event-handling routine. ; We do this using a COMMON block. COMMON dwe, seed ; Retrieve the anonymous structure contained in the user value of ; the top-level base widget. This structure contains the ; following fields: ; drawID: the widget ID of the draw widget ; labelID: the widget ID of the label widget that will hold ; the color table name. ; sel_index: the index of the current selection in the ; droplist ; ctable: the index of the current color table. WIDGET_CONTROL, ev.TOP, GET_UVALUE=stash ; Set the draw widget as the current IDL drawable. WSET, stash.drawID ; Check the type of event structure returned. If it is a timer ; event, change the color table index to a random number between ; 0 and 40, then set the value of the label widget to the name of ; the new color table. ; (See Identifying Widget Type from an Event for ; more on identifying widget types from returned event ; structures.) IF (TAG_NAMES(ev, /STRUCTURE_NAME) EQ 'WIDGET_TIMER') THEN BEGIN LOADCT, GET_NAMES=ctnames stash.ctable = FIX(RANDOMU(seed)*41) LOADCT, stash.ctable, /SILENT WIDGET_CONTROL, stash.labelID, $ SET_VALUE='Color Table: ' + ctnames[stash.ctable] WIDGET_CONTROL, ev.ID, TIMER=3.0 ENDIF ; If the event is a droplist event, change the value of the ; variable 'selection' to the new index value. IF (TAG_NAMES(ev, /STRUCTURE_NAME) EQ 'WIDGET_DROPLIST') $ THEN BEGIN stash.sel_index=ev.index ENDIF ; Reset the user value of the top-level base widget to the ; modified stash structure. WIDGET_CONTROL, ev.TOP, SET_UVALUE=stash ; Display a plot, surface, or shaded surface, or destroy the ; widget application, depending on the value of the 'selection' ; variable. CASE stash.sel_index OF 0: PLOT, DIST(150) 1: SURFACE, DIST(150) 2: SHADE_SURF, DIST(150) 3: WIDGET_CONTROL, ev.TOP, /DESTROY ENDCASE END PRO draw_widget_example ; Define the values for the droplist widget and define the ; initially selected index to show a shaded surface. select = ['Plot', 'Surface', 'Shaded Surface', 'Done'] sel_index = 2 ; Create a base widget containing a draw widget and a sub-base ; containing a droplist menu and a label widget. base = WIDGET_BASE(/COLUMN) draw = WIDGET_DRAW(base, XSIZE=350, YSIZE=350) base2 = WIDGET_BASE(base, /ROW) dlist = WIDGET_DROPLIST(base2, VALUE=select) label = WIDGET_LABEL(base2, XSIZE=200) ; Realize the widget hierarchy, then retrieve the widget ID of ; the draw widget. WIDGET_CONTROL, base, /REALIZE WIDGET_CONTROL, draw, GET_VALUE=drawID ; Set the timer value of the draw widget. WIDGET_CONTROL, draw, TIMER=0.0 ; Set the droplist to display the proper selection index. WIDGET_CONTROL, dlist, SET_DROPLIST_SELECT=sel_index ; Store the widget ID of the draw widget, the widget ID of ; the label widget, the droplist selection index, and the ; initial color table index in an anonymous structure, and ; set the user value of the top-level base widget to this ; structure. stash = { drawID:drawID, labelID:label, $ sel_index:sel_index, ctable:0} WIDGET_CONTROL, base, SET_UVALUE=stash ; Register the widget with the XMANAGER. XMANAGER, 'draw_widget_example', base, /NO_BLOCK ; Set some display device parameters. DEVICE, RETAIN=2, DECOMPOSED=0 END
The intent of this example is to demonstrate the use of draw widgets, menus, and timer events with a minimum of other complicating issues. However, it is easy to imagine applications wherein a graphics window containing a plot or some other information is updated periodically by a timer. The method used here can be easily applied to more realistic situations.
To go beyond merely displaying an image in a draw widget and allow the user to interact in some way with the displayed image, you must configure the draw widget to generate either button, motion, or keyboard events:
The following example uses motion events to update the values of several label widgets as the mouse cursor moves over an image in a draw widget. This and several other features are discussed in the section following the code.
| Note |
draw_widget_data.pro in the examples/widgets subdirectory of the IDL distribution. You can either open the file in an IDL editor window and compile and run the code using items on the Run menu, or simply enter draw_widget_data
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected. You may need to enter DEVICE, DECOMPOSED=1 at the IDL command prompt before running this example.
; Event-handling procedure.
PRO draw_widget_data_event, ev
; Retrieve the anonymous structure contained in the user value of
; the top-level base widget.
WIDGET_CONTROL, ev.TOP, GET_UVALUE=stash
; If the event is generated in the draw widget, update the
; label values with the current cursor position and the value
; of the data point under the cursor. Note that since we have
; passed a pointer to the image array rather than the array
; itself, we must dereference the pointer in the 'image' field
; of the stash structure before getting the subscripted value.
IF (TAG_NAMES(ev, /STRUCTURE_NAME) eq 'WIDGET_DRAW') THEN BEGIN
WIDGET_CONTROL, stash.label1, $
SET_VALUE='X position: ' + STRING(ev.X)
WIDGET_CONTROL, stash.label2, $
SET_VALUE='Y position: ' + STRING(ev.Y)
WIDGET_CONTROL, stash.label3, $
SET_VALUE='Hex Value: ' + $
STRING((*stash.imagePtr)[ev.X, ev.Y], FORMAT='(Z12)')
ENDIF
; If the event is generated in a button, destroy the widget
; hierarchy. We know we can use this simple test because there
; is only one button in the application.
IF (TAG_NAMES(ev, /STRUCTURE_NAME) eq 'WIDGET_BUTTON') THEN BEGIN
WIDGET_CONTROL, ev.TOP, /DESTROY
ENDIF
END
; Widget creation routine.
PRO draw_widget_data
; Define a monochrome image array for use in the application.
READ_PNG, FILEPATH('mineral.png', $
SUBDIR=['examples', 'data']), image
; Place the image array in a pointer heap variable, so we can
; pass the pointer to the event routine rather than passing the
; entire image array.
imagePtr=PTR_NEW(image, /NO_COPY)
; Retrieve the size information from the image array.
im_size=SIZE(*imagePtr)
; Create a base widget to hold the application.
base = WIDGET_BASE(/COLUMN)
; Create a draw widget based on the size of the image, and
; set the MOTION_EVENTS keyword so that events are generated
; as the cursor moves across the image. Setting the BUTTON_EVENTS
; keyword rather than MOTION_EVENTS would require the user to
; click on the image before an event is generated.
draw = WIDGET_DRAW(base, XSIZE=im_size[1], YSIZE=im_size[2], $
/MOTION_EVENTS)
; Create 'Done' button.
button = WIDGET_BUTTON(base, VALUE='Done')
; Create label widgets to hold the cursor position and
; Hexadecimal value of the pixel under the cursor.
label1 = WIDGET_LABEL(base, XSIZE=im_size[1]*.9, $
VALUE='X position:')
label2 = WIDGET_LABEL(base, XSIZE=im_size[1]*.9, $
VALUE='Y position:')
label3 = WIDGET_LABEL(base, XSIZE=im_size[1]*.9, $
VALUE='Hex Value:')
; Realize the widget hierarchy.
WIDGET_CONTROL, base, /REALIZE
; Retrieve the widget ID of the draw widget. Note that the widget
; hierarchy must be realized before you can retrieve this value.
WIDGET_CONTROL, draw, GET_VALUE=drawID
; Create an anonymous array to hold the image data and widget IDs
; of the label widgets.
stash = { imagePtr:imagePtr, label1:label1, label2:label2, $
label3:label3 }
; Set the user value of the top-level base widget equal to the
; 'stash' array.
WIDGET_CONTROL, base, SET_UVALUE=stash
; Make the draw widget the current IDL drawable area.
WSET, drawID
; Draw the image into the draw widget.
TVSCL, *imagePtr
; Call XMANAGER to manage the widgets.
XMANAGER, 'draw_widget_data', base, /NO_BLOCK
END
The following things about this example are worth noting:
stash structure, but since the image could be large, we choose to pass a pointer to the image instead. This means we must dereference the pointer variable every time we need to use the image data. For more information on pointers and how to dereference them, see Pointers.