Tuesday, May 23, 2006

After a few hours of work this weekend, scrolled lists are working now under Gtk+ and .NET, ported from Cocoa.

I originally thought that this would be it; finishing scrolled lists, it would be time to put this thing up on sourceforge and see how it took off. But I felt Monday that I still needed to add two widgets; radio buttons, and checkboxes. So, tonight I fleshed out both of these widgets under Cocoa.

As it turns out, both of these widgets are just versions of NSButton, with an appropriate style assigned to them at creation time (the default style gives the familiar push button widget).

Checkboxes were easy to implement; all I needed to do was clone CocoaButtomImpl and set the appropriate style. Radio buttons were nearly the same, except for one major snafu. As many of you who have used radio buttons in various toolkits are aware, a radio button usually is associated somehow with a "radio group". Radio buttons that belong in a radio group are related by state -- that is, when one of the radio buttons becomes selected, the remaining radio buttons in the group become deselected. generally, a radio group is identified by some number or name. In my markup, I simply have a group="name" attribute that can be assigned to a radiobutton element. To place radiobuttons in the same group, one only needs to give each radiobutton the same group attribute, like this:

<box orient="vertical">
<spacer/>
<radiobutton group="radiogroup1" label="Radio Button 1" id="RadioButton1" checked="true"/>
<spacer/>
<radiobutton group="radiogroup1" label="Radio Button 2" id="RadioButton2"/>
<spacer/>
<radiobutton group="radiogroup1" label="Radio Button 3" id="RadioButton3"/>
<spacer/>
<radiobutton group="radiogroup1" label="Radio Button 4" id="RadioButton4"/>
<spacer/>
</box>

The above results in a vertically stacked set of 4 radio buttons, all belonging to the group "radiogroup1".

In some markup-based toolkits, one might see an additional tag, e.g., radiogroup, that wraps the buttons that belong to that group. I found that not to be necessary, and to incorrectly imply layout -- there is no reason that radio buttons related by group need to be physically adjacent in the UI (though it is clearly the case that visual proximity lends greatly to one's knowledge of the relationship that exists among members of the group.)

So, what was the snafu I mentioned? In Cocoa, to relate buttons, you are forced to use a view-based widget, NSMatrix, and make the radio buttons children of that widget. NSMatrix not only enforces layout semantics on the radio buttons, but it also implements the semantics I described earlier of ensuring that only one of the child radio buttons is active at any time.

while that is fine and dandy for the majority of Cocoa users, it makes things tough if you are creating your own layout engine atop of Cocoa. The problem for me is that NSMatrix is incompatible with my layout scheme -- one critical problem (perhaps *the* critical problem) is that with NSMatrix in the way, users could not place spacer elements between adjacent radio buttons, nor could I compute my own layout of the radio buttons housed by the NSMatrix container.

My only solution, then, is not to use NSMatrix, but, instead, to implement the "only one active" semantics associated with radio groups myself. To do this, I need two things: a callback that can handle when a radio button changes state, and a class to manage the radio groups that exist in a document. This class is called RadioGroupManager, and it maintains a map of all radiogroup names in the document to a list of all radio button elements that belong to that group. As radio buttons are created, the call into RadioGroupManager and add themselves to the map. When a given button changes its state (either programmatically or via user input) to active, it lets RadioGroupManager know about this state change. In response, RadioGroupManager iterates its list of all radio buttons that belong to the same group, and programmatically makes them inactive. This is essentially what NSMatrix must do each time as well.

Had the designers of Cocoa decoupled the GUI aspect of NSMatrix from its radio button state management aspect, I could have used something (NSMatrixController??) provided by Cocoa. The coupling, as it stands, is a design flaw in my opinion (and my need to work around it is a clear example of why this is so).