Toward a Theory of Widget Geometry
QUESTION: Say, uh, is it just me, or is widget geometry, like, totally playing with your head, man? I mean, I'm not that anal. But the man told me to get a couple of widgets lined up and it's, like, totally not happening. You have any good ideas for me, bro?
ANSWER: Uh, no. It confuses me, too. But Jean-Paul Davis (God bless him!) recently ran into this problem, and he actually took the time to write down some of what he learned about it for the benefit of the rest of us. Here is a short synopsis of what he learned about creating widgets in IDL 6.4 that “look nice” under both Windows XP and Red Hat LINUX 4.
When he talks about “information returned from WIDGET_INFO”, he is talking about fields in the geometry strucuture that is returned with this command.
geometry = Widget_Info(widgetID, /GEOMETRY)
For example, here is the geometry structure for a base widget in one of my programs.
** Structure WIDGET_GEOMETRY, 12 tags, length=48, data length=48: XOFFSET FLOAT 27.0000 YOFFSET FLOAT 222.000 XSIZE FLOAT 345.000 YSIZE FLOAT 64.0000 SCR_XSIZE FLOAT 345.000 SCR_YSIZE FLOAT 64.0000 DRAW_XSIZE FLOAT 0.000000 DRAW_YSIZE FLOAT 0.000000 MARGIN FLOAT 0.000000 XPAD FLOAT 3.00000 YPAD FLOAT 3.00000 SPACE FLOAT 2.00000
Many widgets (draw widgets, in particular) have a margin of 2-3 pixels around them, so that their screen size is 4-6 pixels larger than their reported size. Some widgets (notoriously base widgets in Windows XP) have padding around the widget so that you can't put a widget completely against the edge of these widgets. And space refers to the space between widgets. For example, the space between buttons in a base widget containing button widgets. The fields xsize and ysize normally refer to the "natural" or "current" size of the widget, while the fields scr_xsize and scr_ysize refer to the widget's phyiscal size on the display (i.e., the actual number of pixels required to render the widget).
- In all cases, the xsize and ysize tags returned by WIDGET_INFO does not include the widget's margin.
- Under Windows, using WIDGET_CONTROL to set ysize for a base defined with /column, or to set xsize for a base defined with /row, instead changes the scr_ysize or scr_xsize tag, respectively, so that it corresponds to the desired ysize or xsize. Effectively, ysize for column bases and xsize for row bases automatically keep track of the base's natural size, with the base's actual current size given by scr_ysize and scr_xsize, which does include the margins. (UNIX is different, see the next item.)
- Under UNIX, scr_xsize and scr_ysize are equal to xsize and ysize for all widgets except the top-level base (TLB), which has window dressing, or widgets using character units instead of pixels for xsize and ysize. Determining the natural size of these latter widgets requires adding up sizes of the individual elements of the widgets.
- Under UNIX, the first level of widget bases down from the TLB always size themselves to the TLB; the value of the xsize or ysize tag for such a base does not change unless WIDGET_CONTROL passes a value larger than the xsize or ysize of the widest/tallest child of TLB, or, if operating on the widest/tallest child widget, larger than the xsize or ysize of the next- widest/tallest child widget.
- Under UNIX, using WIDGET_CONTROL to change the value of an unrealized label widget will change that widget's xsize or ysize accordingly; thus, if you want a label widget to start blank, you have to define it with text, get its size, change the text to null string, then explicitly set it to the saved original size before realizing it.
- Under UNIX, using WIDGET_CONTROL to set xsize or ysize for an unrealized widget actually sets the xsize or ysize tag equal to the keyword value plus twice the current xpad or ypad value. In other words, the xsize or ysize value passed to WIDGET_CONTROL excludes the padding.
- Under UNIX, once a widget hierarchy is realized, the above behavior changes to the expected behavior (xsize or ysize values passed to WIDGET_CONTROL includes the padding), and (get this!) the first time xsize or ysize is changed for any widget, all other widgets that had xsize or ysize specified before the hierarchy was realized suddenly change their values of xsize or ysize to equal the values originally passed to them by WIDGET_CONTROL (instead of passed value + padding).
For a demonstration of this Item 7 (very strange) behavior, try the following code:
wtest = widget_base(/row) wcol1 = widget_base(wtest, /column) wcol2 = widget_base(wtest, /column) wsub11 = widget_base(wcol1, /column, /frame) wsub12 = widget_base(wcol1, /column, /frame) wsub21 = widget_base(wcol2, /column, /frame) wsub22 = widget_base(wcol2, /column, /frame) w111 = widget_text(wsub11, xsize=15) w112 = widget_button(wsub11, value='TESTING') w121 = widget_label(wsub12, value='TESTING') w122 = widget_text(wsub12, xsize=15) w211 = widget_button(wsub21, value='TESTING') w212 = widget_button(wsub21, value='TESTING') w221 = widget_text(wsub22, xsize=15) w222 = widget_text(wsub22, xsize=15) gsub11 = widget_info(wsub11, /geometry) print, 'NATURAL PRE-REALIZED WSUB11: ysize = ', gsub11.ysize, 'ypad =', $ gsub11.ypad, 'margin = ', gsub11.margin, format='(A0,I0,3X,A0,I0,3X,A0,I0)' gsub21 = widget_info(wsub21, /geometry) print, 'NATURAL PRE-REALIZED WSUB21: ysize = ', gsub21.ysize, 'ypad =', $ gsub21.ypad, 'margin = ', gsub21.margin, format='(A0,I0,3X,A0,I0,3X,A0,I0)' widget_control, wsub11, ysize = gsub11.ysize + 20 widget_control, wsub21, ysize = gsub21.ysize + 40 gsub11 = widget_info(wsub11, /geometry) print, 'MODIFIED PRE-REALIZED WSUB11: ysize = ', gsub11.ysize, 'ypad =', $ gsub11.ypad, 'margin = ', gsub11.margin, format='(A0,I0,3X,A0,I0,3X,A0,I0)' gsub21 = widget_info(wsub21, /geometry) print, 'MODIFIED PRE-REALIZED WSUB21: ysize = ', gsub21.ysize, 'ypad =', $ gsub21.ypad, 'margin = ', gsub21.margin, format='(A0,I0,3X,A0,I0,3X,A0,I0)' widget_control, wtest, /realize widget_control, wsub11, ysize = gsub11.ysize - 10 gsub11 = widget_info(wsub11, /geometry) print, 'REMODIFIED POST-REALIZED WSUB11: ysize = ', gsub11.ysize,'ypad = ', $ gsub11.ypad, 'margin = ', gsub11.margin, format='(A0,I0,3X,A0,I0,3X,A0,I0)' gsub21 = widget_info(wsub21, /geometry) print, 'REMODIFIED POST-REALIZED WSUB21: ysize = ', gsub21.ysize,'ypad = ', $ gsub21.ypad, 'margin = ', gsub21.margin, format='(A0,I0,3X,A0,I0,3X,A0,I0)'
On my Linux system this produces the following output:
NATURAL PRE-REALIZED WSUB11: ysize = 65 ypad = 3 margin = 2 NATURAL PRE-REALIZED WSUB21: ysize = 59 ypad = 3 margin = 2 MODIFIED PRE-REALIZED WSUB11: ysize = 91 ypad = 3 margin = 2 MODIFIED PRE-REALIZED WSUB21: ysize = 105 ypad = 3 margin = 2 REMODIFIED POST-REALIZED WSUB11: ysize = 81 ypad = 3 margin = 2 REMODIFIED POST-REALIZED WSUB21: ysize = 99 ypad = 3 margin = 2
Note how setting ysize to 85 and 99 for the unrealized widgets actually gave ysize of 91 and 105, respectively (2*ypad was added to ysize, which should normally include the padding). After realizing the widgets, setting the ysize of wsub11 to 81 actually produces 81, but also changes the ysize of wsub21 to 99. To verify that ypad is the relevant parameter, change its value in the widget creation calls. I would be curious to know if this behavior does not occur under someone else's UNIX system.
For my application, I determined natural sizes of the widgets by adding up components (no references to scr_xsize or scr_ysize), and only had to include system-dependent code (based on !VERSION.OS_FAMILY system variable) for sizing of non- realized widgets.
Many thanks to Jean-Paul for his wonderful start on this important work. If you have other insights, please let us know. We have an on-going project to document this behavior so we can (1) write better looking code, and (2) report it to ITTVIS so they can fix it in future versions of IDL.
Copyright © 2007 David W. Fanning
Last Updated 30 August 2007