Here's the evaluation for each of the issues listed above:
1) First a quick background on how the preferred size of JButtons are calculated under
the Synth/GTK L&Fs. Take for example a simple JButton containing only the text "Test".
There are a few things that are typically taken into account when SynthButtonUI is
calculating the preferred size of a button:
a) the insets
b) the margins
c) the icon rect
d) the text rect
The total preferred size of the button will be the union of the icon and text rects,
plus the margins, plus the insets. In this example, we can forget about the icon
rect. The text rect is being calculated properly (in this case, we get a nice tight
bounding box of 27x17, which is consistent with native). The problem areas are in
(a) and (b). In the case of (b), we are erroneously taking into account the default
Button.margin value, defined in BasicLookAndFeel as (2,14,2,14). This helps explain
why buttons are so much wider than expected by default. In the case of (a), our
calculations in GTKStyle.getButtonInsets() are nearly correct, except that we're
not taking into account the CHILD_SPACING constant value that is used in the
native gtk_button_size_allocate() method.
The fix here is to override the Button.margin value in GTKLookAndFeel to use
a zero insets value instead. This takes the margin value out of the equation.
Now the only other thing we need to do is to fix GTKStyle.getButtonInsets() to
add in the CHILD_SPACING constant, and voila, our buttons are now pixel-for-pixel
accurate with native GtkButtons.
It is worth noting that fixing this issue resolves a number of the problems we were
seeing in 6479305 w.r.t. toolbar buttons being rectangular by default. It was
really this bogus margins value that was causing those toolbar buttons to be
non-square. With this fix in place, toolbar buttons (especially those that contain
only a square icon) will now be laid out correctly. So that resolves the first
part of 6479305, although there is still more work to be done under that bugid to
get toolbars looking perfect.
2,3) These issues are closely related to what I've described above for (1). The
extra padding is again caused by the fact that we're picking up the margins defined
in BasicLookAndFeel for CheckBox.margin and RadioButton.margin, both of which are
defined as (2,2,2,2). The first fix here is to override CheckBox.margin and
RadioButton.margin with zero insets in GTKLookAndFeel.
The remaining padding issue is caused by the fact that we use
GTKStyle.getButtonInsets() to calculate the insets for JCheckBox and JRadioButton.
There are subtle differences between how GtkButtons and GtkCheckButtons are laid out,
so we really should have a separate method, GTKStyle.getRadioInsets(), that is tuned
to laying out JCheckBoxes and JRadioButtons. These two widgets are made up of the
same four regions as I described above, but with some minor variation:
a) the insets
b) the margins
c) the icon rect (for the indicator)
d) the text rect
e) the icon-text spacing (the padding between the indicator and the text)
The difficulty here is that native GtkCheckButtons (this includes GtkRadioButtons,
which are a subclass) paint their focus around only the text, not the
indicator. This means we can't just use the same calculation that is used in
GTKStyle.getButtonInsets(), since that will figure the focus around both the icon
and the text (also, GtkCheckButtons do not have that CHILD_SPACING constant that
I described earlier, which is another difference). So, the top/bottom insets are
easy: that's just "focus-line-width" plus "focus-padding" (call it "totalFocus").
The left/right insets are more tricky, and depend on the LTR/RTL orientation of the
component. In the LTR case, there should be no padding on the left side of the
component (where the indicator lies; any padding is provided by the indicator icon
itself); on the right side of the component, we need to include "totalFocus". For
the RTL case, we simply reverse these values.
Finally, we need to be smarter about how we handle CheckBox/RadioButton.iconTextGap
values. Currently, we use GtkCheckButton's "indicator-spacing" value, but that
isn't enough. It might help to visualize the horizontal layout of the
contents of a JCheckBox; from left to right, the important elements are:
a) the left inset
b) the width of the indicator icon
c) the iconTextGap
d) the left part of the focus indicator
e) the width of the text
f) the right part of the focus indicator
g) the right inset
It's also worth describing how those parts map to the way GtkCheckButton
lays its elements out horizontally. In gtkcheckbutton.c
(gtk_check_button_size_allocate() method), it appears to lay out horizontally
e) focus-padding + focus-line-width
f) the width of the text
g) focus-padding + focus-line-width
Note that GTKIconFactory.DelegateIcon actually adds "indicator-spacing"
as a padding to each side of the icon, so if "indicator-size" is 13 and
"indicator-spacing" is 2, the actual SynthIcon will end up being 17x17
To bridge these two worlds, we need to do the following for JCheckBox
- the left inset should be zero
- the DelegateIcon already includes
"indicator-spacing" PLUS "indicator-size" PLUS "indicator-spacing"
(so nothing needs to change in DelegateIcon)
- the value of iconTextGap needs to include another
"indicator-spacing" PLUS "focus-padding" PLUS "focus-line-width"
(this needs to be calculated in GTKStyle.get())
- the width of the text is okay, no changes needed
- the right inset should be
"focus-padding" PLUS "focus-line-width"
With those changes in place, Swing's JCheckBox and JRadioButton are now
pixel-for-pixel identical to their native counterparts.