Form follows function

[Work in Progress]

Declarative GUI Programming Tutorial (for Swing Programmers)

Hello World: Your First F3 Program

It is traditional to provide the "Hello World" program as the first exposure to a new language, so here it is in F3:

        Frame {

            title: "Hello World F3"

            width: 200

            content: Label {

                text: "Hello World"

            }

            visible: true
        }


Running this program displays the following on the screen:

As you can see F3 provides a declarative syntax for expressing the structure and content of user interface components. To help you understand what's happening, let's rewrite the above program in a purely procedural fashion, similar to the way it's normally done in Swing:

        var win = new Frame();
        win.title = "Hello World F3";
        win.width = 200;
        var label = new Label();
        label.text = "Hello World";
        win.content = label;
        win.visible = true;

This program is also a valid F3 program and it has exactly the same effect as the first one.

So what's happening (in both cases) is basically the following:
I call the Frame class constructor to create a new Frame
I assign values to its title, width, visible, and content attributes
In the process of assigning the content attribute, I call the Label class constructor to create a new Label, and I assign its text attribute


However, even in an extremely simple example like this one, the meaning of the program in the declarative syntax is tangibly easier to grasp.

Thus declarative programming consists of creating a program from a single expression. As in the first example above the root of that expression is often an object allocation expression (constructor) that generates the object graph that makes up your program.

Dynamic Behavior

The "Hello World" program has no dynamic behavior. To create a graphical user interface with dynamic behavior in F3, you create graphical user interface components whose properties depend on the attribute values of other objects. Those other objects can be anything you like - whatever representation of the state of your application you feel is suitable. Since the GUI component's properties depend on the properties of another object, whenever you modify that other object the GUI component will automatically reflect your changes. In this context the GUI component is typically referred to as the View and the "other" object as the Model. Here is a Model/View version of the "Hello World" program.

        class HelloWorldModel {
            attribute saying: String;
        }

        var model = HelloWorldModel {
            saying: "Hello World"
        };

        var win = Frame {

            title: "Hello World F3"

            width: 200

            content: Label {

                text: bind model.saying

            }

            visible: true
        };

Running this program displays the following:

If I programmatically change the model object's saying attribute:

        model.saying = "Goodbye Cruel World!";

The view automatically changes:

Notice that this example initializes the text attribute of the label by applying the F3 bind operator to the saying attribute of model. In this context, the bind operator indicates incremental update. This means that whenever the value of model.saying changes the text attribute of the label will be updated to the same value.

In the case of input widgets, such as Buttons, CheckBoxes, TextFields, etc., the linkage between model attributes and GUI component attributes can be bidirectional.

Consider this example:

        class HelloWorldModel {
            attribute saying: String;
        }

        var model = HelloWorldModel {
            saying: "Hello World"
        };

        var win = Frame {

            title: bind "{model.saying} F3"

            width: 200

            content: TextField {

                value: bind model.saying

            }

            visible: true
        };

Running this program displays the following:

If I type something else into the text field and press Enter, the title of the window changes accordingly:

In this case the text field's value attribute is updated as a result of user input (by the implementation of the TextField class). When that happens the saying attribute of model is updated to the same value. Since the expression assigned to the title attribute of the window depends on the saying attribute of model the expression is reevaluated and the window's title attribute updated to the result.

You can bind any property of any component to an incrementally evaluated expression. Such expressions can use conditional logic, iteration, selection, etc. to produce dynamic content of any complexity, yet the expression of such content remains declarative.

Borders and Layout Managers

In F3, using Borders and Layout Managers is also declarative. Each Swing/AWT layout manager is encapsulated in an F3 class that instantiates a JPanel with the specified layout manager. Components are added to the (underlying) JPanel in a declarative way using the attributes provided by the F3 class. Each Swing border type is also encapsulated by an F3 class that has attributes corresponding to the configuration options of that border. Here is a simple example using EmptyBorder and GridPanel. As you might expect, EmptyBorder corresponds to javax.swing.border.EmptyBorder and GridPanel corresponds to java.awt.GridLayout.

        class ButtonClickModel {
            attribute numClicks: Number;
        }

        var model = new ButtonClickModel();

        var win = Frame {
            width: 200
            content: GridPanel {
                border: EmptyBorder {
                   top: 30
                   left: 30
                   bottom: 30
                   right: 30
                }
                rows: 2
                columns: 1
                vgap: 10
                cells:
                [Button {
                     text: "I'm a button!"
                     mnemonic: I
                     action: operation() {
                         model.numClicks++;
                    }
                },
                Label {
                    text: bind "Number of button clicks: {model.numClicks}"
                }]
            }
            visible: true
        };

Running this program displays the following:

And after clicking the button a few times:

Note: see below for an explanation of the Button's action and mnemonic attributes.

In this case I've configured the GridPanel to have one column, two rows, and a vertical gap of 10 pixels between the rows by assigning values to its columns, rows, and vgap attributes (GridPanel also has an hgap attribute if you want to create a gap between its columns). I've also given it an empty border 30 pixels on all sides.

I added the button and label by assigning them to its cells attribute. The GridPanel implementation responds to insertions and deletions to/from its cells attribute by adding or removing components from its underlying JPanel.

Other layout managers are supported in F3 in a similar fashion. Here is a summary:

Layout Managers:

F3 Widget Layout Manager
GridPanel GridLayout
GridBagPanel GridBagLayout
FlowPanel FlowLayout
BorderPanel BorderLayout
Box BoxLayout
StackPanel Romain Guy's StackLayout
CardPanel CardLayout
GroupPanel org.jdesktop.layout.GroupLayout

And here's a summary of the F3 Border classes:

Borders:

F3 Border Swing Border
EmptyBorder EmptyBorder
LineBorder LineBorder
BevelBorder BevelBorder
SoftBevelBorder SoftBevelBorder
MatteBorder MatteBorder
TitledBorder TitledBorder

Menus

Let's add a simple menubar to the above example. The new code is shown in bold:

        import java.lang.System;

        class ButtonClickModel {
            attribute numClicks: Number;
        }

        var model = new ButtonClickModel();

        Frame {
            width: 200
            menubar: MenuBar {
                 menus: Menu {
                     text: "File"
                     mnemonic: F
                     items: MenuItem {
                         text: "Exit"
                         mnemonic: X
                         accelerator: {
                             modifier: ALT
                             keyStroke: F4
                         }
                         action: operation() {
                             System.exit(0);
                         }
                     }
                 }
            }
            content: GridPanel {
                border: EmptyBorder {
                   top: 30
                   left: 30
                   bottom: 30
                   right: 30
                }
                rows: 2
                columns: 1
                vgap: 10
                cells:
                [Button {
                     text: "I'm a button!"
                     mnemonic: I
                     action: operation() {
                         model.numClicks++;
                    }
                },
                Label {
                    text: bind "Number of button clicks: {model.numClicks}"
                }]
            }
            visible: true
        }

After running this program and pressing ALT+F the following is displayed:

As you can see I create a menubar by assigning a new instance of the MenuBar class to the menubar attribute of the window. I add menus to the menubar by assigning them to the menubar's menus attribute. In this case I've only added one menu, but any expression returning a list of Menu objects could be used.

To define the menu I assigned values to its text, mnemonic, and items attributes.

As you can probably tell the type of the text attribute is String. The mnemonic attribute, however, is of type KeyStroke. Its value F is an enumerated instance of the KeyStroke class. In F3, in the context of an attribute initializer the enumerated values of the attribute's static type (and static fields in the case of Java classes) can be accessed without being qualified by the type name (elsewhere I would have to refer to F as F:KeyStroke).

I've created one menu item, a MenuItem whose text is "Exit" and whose mnemonic is X. I've also assigned a value to its accelerator attribute. Note that I actually omitted the type name Accelerator in the declaration of the value. This is permitted. If the type name isn't present, the static type of the attribute is used, in this case Accelerator. In addition, I've initialized the accelerator's modifier and keyStroke attributes using enumerations.

Finally, as you can probably tell, the action attribute of the MenuItem has a function type (i.e its value is a function, not an object). In this case I've written an inline operation that simply exits the application by calling some Java code.

Labels

The F3 Label class supports HTML content. Using Labels you can create styled text and images with HTML and CSS, very much like you would in a typical Web application. In addition, by using F3 embedded expressions you can create dynamic HTML content in a Swing application as easily as Web page authors can using tools like JSTL or Velocity.

Consider this example of a hypothetical shopping cart:

        class Item {
            attribute id: String;
            attribute productId: String;
            attribute description: String;
            attribute inStock: Boolean;
            attribute quantity: Number;
            attribute listPrice: Number;
            attribute totalCost: Number;
        }

        attribute Item.totalCost = bind quantity*listPrice;

        class Cart {
            attribute items: Item*;
            attribute subTotal: Number;
        }

        operation sumItems(itemList:Item*) {
            var result = 0.00;
            for (item in itemList) {
                result += item.totalCost;
            }
            return result;
        }

        attribute Cart.subTotal = bind sumItems(items);

        var cart = Cart {
            items:
            [Item {
                id: "UGLY"
                productId: "D100"
                description: "BullDog"
                inStock: true
                quantity: 1
                listPrice: 97.50
            },
            Item {
                id: "BITES"
                productId: "D101"
                description: "Pit Bull"
                inStock: true
                quantity: 1
                listPrice: 127.50
            }]
        };

        Frame {

             content: Label {

                text: bind

                   "<html>
                       <h2 align='center'>Shopping Cart</h2>
                       <table align='center' border='0' bgcolor='#008800' cellspacing='2' cellpadding='5'>
                          <tr bgcolor='#cccccc'>
                             <td><b>Item ID</b></td>
                             <td><b>Product ID</b></td>
                             <td><b>Description</b></td>
                             <td><b>In Stock?</b></td>
                             <td><b>Quantity</b></td>
                             <td><b>List Price</b></td>
                             <td><b>Total Cost</b></td>
                             <td> </td>
                           </tr>

                           {
                             if (sizeof cart.items == 0)
                             then "<tr bgcolor='#FFFF88'><td colspan='8'><b>Your cart is empty.</b></td></tr>"
                             else foreach (item in cart.items)
                                 "<tr bgcolor='#FFFF88'>
                                  <td>{item.id}</td>
                                  <td>{item.productId}</td>
                                  <td>{item.description}</td>
                                  <td>{if item.inStock then "Yes" else "No"}</td>
                                  <td>{item.quantity}</td>
                                  <td align='right'>{item.listPrice}</td>
                                  <td align='right'>{item.totalCost}</td>
                                  <td> </td>
                                  </tr>"
                           }

                           <tr bgcolor='#FFFF88'>
                                <td colspan='7' align='right'>
                                   <b>Sub Total: ${cart.subTotal}</b>
                               </td>
                               <td> </td>
                            </tr>
                         </table>
                      </html>"
                }

                visible: true

        }

Running the above program displays the following:

If I programmatically delete the cart items:

       delete cart.items;

I see the following:

In the above example, embedded F3 expressions (shown in bold) dynamically create the rows of an HTML table and the content of the table cells. When the objects those expressions depend on change, the HTML content of the Label is automatically updated.

The above example is also interesting because it demonstrates the use of expressions to define attribute values. The totalCost attribute of the Item class and the subTotal attribute of the Cart class are bound to expressions that compute their values. Whenever the objects those expressions depend on change, the attribute value is automatically recomputed and updated. Think of a spreadsheet where some cells contain formulas that refer to other cells. When you enter data into those other cells the values of the cells containing formulas that depend on them are automatically updated.

Images in HTML

The F3 Label class actually encapsulates a specialized JEditorPane which uses a shared image cache that supports loading images from JAR files using the Java class loader. Thus you can use HTML <img> elements that reference image resources packaged with your application as if they were normal file URL's.

Hyperlinking

The Label class also supports HTML hyperlinking by embedding a special URL as the href attribute of an HTML <a> element.

Such URL's are created with the F3 # operator. This operator generates a stringified object reference that refers to its operand which can later be dereferenced by the F3 ? operator. For example,

        var a = 20;
        var b = #a;
        assert b instanceof String; // passes
        var c = (Number) ?b;
        assert a == c;  // passes

The Label class's HTML renderer recognizes such URLs in HTML <a href=url> contexts and handles mouse clicks on such elements by deferencing the URL and if its value refers to a function or operation it calls that function or operation.

For example, here's the previous Button-Click example rewritten to use a Label with a hyperlink instead of a Button:

        class ButtonClickModel {
            attribute numClicks: Number;
        }

        var model = new ButtonClickModel();

        Frame {
            width: 200
            content: GridPanel {
                border: EmptyBorder {
                   top: 30
                   left: 30
                   bottom: 30
                   right: 30
                }
                rows: 2
                columns: 1
                vgap: 10
                cells:
                [Label {
                     text: bind
                             "<html>
                                <a href='{#(operation() {model.numClicks++;})}'>
                                    I'm a hyperlink!
                                </a>
                             </html>"
                },
                Label {
                    text: bind "Number of clicks: {model.numClicks}"
                }]

            }
            visible: true
        };

The expression in bold in the above example creates a new operation that will increment the model's numClicks attribute. Applying the # operator to that generates a URL that refers to the operation which is then embedded into the HTML markup.

Running this program displays:

And after clicking the hyperlink twice:

GroupPanel, SimpleLabel, and TextField

This section will use a very simple example to introduce you to the F3 GroupPanel, SimpleLabel, and TextField classes.

The F3 GroupPanel class encapsulates the Java.net GroupLayout class. GroupLayout is a powerful layout manager that represents the contents of a panel as sets of parallel horizontal and vertical groups. In F3 these parallel groups are simply referred to as Rows and Columns. When you declare a GroupPanel, you also declare Row and Column objects for each horizontal or vertical grouping of components. Then when you add components, you assign the appropriate Row and Column objects to the component's row and column attributes. GroupPanel automatically inserts gaps between components as defined by the current look-and-feel's style guidelines. By declaring values for the alignment and resizable attributes of a Row or Column object you can control the alignment of components within that row or column, and whether the row or column can be resized.

The F3 TextField class encapsulates the Swing JFormattedTextField class. It has a value attribute that is updated whenever the user presses Enter while the focus is on the text field or when the focus moves to another component. You can control its width by assigning a numeric value to its columns attribute, and its horizontal alignment by assigning LEADING, CENTER, or TRAILING to its horizontalAligment attribute. Finally, two function-valued attributes are provided that allow you to perform actions based on user interaction: action and onChange. If you assign a function or operation to the action attribute, your function will be called whenever the user presses the Enter key. If you assign a function or operation to the onChange attribute, your function or operation will be called whenever the text field's value changes.

The F3 SimpleLabel class encapsulates the Swing JLabel class. SimpleLabel differs from Label in that it doesn't support hyperlinking and its preferred size is calculated differently.

Here what the example will look like:

And here's the code to create it:

        class Model {
            attribute firstName: String;
            attribute lastName: String;
        }

        var model = Model {
            firstName: "Joe"
            lastName: "Smith"
        };

        Frame {
            content: GroupPanel {
                var firstNameRow = Row { alignment: BASELINE }
                var lastNameRow = Row { alignment: BASELINE }
                var labelsColumn = Column {
                    alignment: TRAILING
                }
                var fieldsColumn = Column {
                    alignment: LEADING
                    resizable: true
                }
                rows: [firstNameRow, lastNameRow]
                columns: [labelsColumn, fieldsColumn]
                content:
                [SimpleLabel {
                    row: firstNameRow
                    column: labelsColumn
                    text: "First Name:"
                },
                TextField {
                    row: firstNameRow
                    column: fieldsColumn

                    columns: 25
                    value: bind model.firstName
                },
                SimpleLabel {
                    row: lastNameRow
                    column: labelsColumn
                    text: "Last Name:"
                },
                TextField {
                    row: lastNameRow
                    column: fieldsColumn
                    columns: 25
                    value: bind model.lastName
                }]
            }
            visible: true
        };

In the above example the code related to layout is in blue. This example consists of two rows (one for the first name and one for the last name) and two columns (one for the labels and one for the text fields). In the declaration of the GroupPanel I've declared four variables (firstNameRow, lastNameRow, labelsColumn, and fieldsColumn) for the rows and columns. Then I assigned the two Row's to the GroupPanel's rows attribute and the two Column's to its columns attribute. Finally, as you can see, I added the labels and text fields by assigning them to the GroupPanel's elements attribute, and, as I declared the labels and text fields, I assigned their row and column attributes appropriately.

Buttons

The F3 Button class encapsulates the Swing JButton component. To introduce you to the use of Buttons, I'll recreate a simple example from the Swing tutorial, which looks like this

        class ButtonDemoModel {
            attribute buttonEnabled: Boolean;
        }

        var model = ButtonDemoModel {
            buttonEnabled: true
        };

        Frame {
            title: "ButtonDemo"
            content: FlowPanel {
                content:
                [Button {
                    text: "Disable middle button"
                    verticalTextPosition: CENTER
                    horizontalTextPosition: LEADING
                    icon: Image {
                        url: "http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/right.gif"
                    }
                    mnemonic: D
                    toolTipText: "Click this button to disable the middle button"
                    enabled: bind model.buttonEnabled
                    action: operation() {
                         model.buttonEnabled = false;
                    }
                },
                Button {
                    text: "Middle button"
                    icon: Image {
                        url: "http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/middle.gif"
                    }
                    verticalTextPosition: BOTTOM
                    horizontalTextPosition: CENTER
                    mnemonic: M
                    toolTipText: "This middle button does nothing when you click it."
                    enabled: bind model.buttonEnabled
                },
                Button {
                    text: "Enable middle button"
                    icon: Image {
                        url: "http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/left.gif"
                    }
                    mnemonic: E
                    toolTipText: "Click this button to enable the middle button"
                    action: operation() {
                         model.buttonEnabled = true;
                    }
                    enabled: bind not model.buttonEnabled
                }]
            }
            visible: true
        }

After clicking the left button, the example looks like this:

The enabled attribute of each of the three buttons is bound to the buttonEnabled attribute of the model object. So when I modify this attribute in the actions of the left and right buttons they all reflect the change.

I've added images to the buttons by assigning Image objects to their icon attributes.

F3 Image objects have a url attribute to which you can assign a String containing a URL that points at the desired image resource. F3 has an internal image cache that supports loading images from JAR files using the Java class loader. As a result image resources packaged in JAR files accessible to the class loader can be accessed by simply using file: URLs.

TabbedPanes

To demonstrate the use of TabbedPane's, I'll define a model class that has attributes corresponding to those of the TabbedPane widget:

        class Model {
            attribute tabPlacement: TabPlacement;
            attribute tabLayout: TabLayout;
            attribute tabCount: Integer;
            attribute selectedTab: Integer;
        }

and then I'll project an instance of of TabbedPane from an instance of this model:

        var model = Model {
            tabPlacement: TOP
            tabLayout: WRAP
            selectedTab: 3
            tabCount: 5
        };

        Frame {
            height: 300
            width: 400
            content: TabbedPane {
                tabPlacement: bind model.tabPlacement
                tabLayout: bind model.tabLayout
                tabs: bind foreach (i in [1..model.tabCount])
                    Tab {
                        title: "Tab {i}"
                        toolTipText: "Tooltip {i}"
                    }
                selectedIndex: bind model.selectedTab

            }
            visible: true
        }

The code in bold shows the dependencies between the TabbedPane and the model. Having done this I can now change the appearance of the TabbedPane by modifying the model.

Tabs are added to a TabbedPane by assigning a list of Tab objects to its tabs attribute. The TabPlacement and TabLayout classes define enumerations (TOP, LEFT, BOTTOM, RIGHT and WRAP or SCROLL) that allow you to control the location and layout of the tabs by assigning appropriate values to the TabbedPane's tabPlacement and tabLayout attributes. The TabbedPane's selectedIndex attribute determines which tab's content is visible.

Running the above program displays the following:

Notice that the forth tab is selected. This is due to the fact that I initialized the model's selectedTab attribute to the (zero-based) index 3. Because in this example the TabbedPane's selectedIndex attribute is bound to the value of selectedTab, it is also updated, causing that tab to be selected.

If I programmatically change the model:

        model.tabPlacement = BOTTOM;

the tabs move to the bottom:

Executing:

        model.selectedTab = 0;

causes the first tab to become the selected tab:

Executing:

        model.tabCount = 20;

causes 15 new tabs to be created and added to the tabbed pane:

Executing:

        model.tabLayout = SCROLL;

shows:

And executing:

        model.tabCount = 2;

causes all but the first two tabs to be removed:

ListBoxes

The F3 ListBox class provides the functionality of the Swing JList component but with a declarative interface.

As a demonstration, I'll recreate a simple example from the Swing tutorial (ListDemo) that initially looks like this:

In this example the ListBox contains a list of employee names. If I click the "Fire" button the selected employee is removed from the list. If I enter a new name into the TextField below the list, the "Hire" button becomes enabled, and if I click it, that name is added to the list. This example also demonstrates the use of BorderPanel and FlowPanel. A BorderPanel contains up to 5 components which can be placed at the top, left, bottom, right, or center of the panel. It stretches the left and right components vertically, the top and bottom components horizontally, and the center component in both directions. A FlowPanel contains a list of components which it lays out in a left to right flow, much like text in a paragraph. This example also shows the use of RigidArea, an invisible filler component which can be used to create space between other components.

        class EmployeeModel {
            attribute employees: String*;
            attribute selectedEmployee: Number;
            attribute newHireName: String;
        }

        var model = EmployeeModel {
            employees:
            ["Alan Sommerer",
             "Alison Huml",
             "Kathy Walrath",
             "Lisa Friendly",
             "Mary Campione",
             "Sharon Zakhour"]
        };

        Frame {
            title: "ListBox Example"
            content: BorderPanel {
                center: ListBox {
                    selection: bind model.selectedEmployee
                    cells: bind foreach (emp in model.employees)
                        ListCell {
                           text: emp
                        }
                }
                bottom: FlowPanel {
                    content:
                    [Button {
                       text: "Fire"
                       action: operation() {
                           delete model.employees[model.selectedEmployee];
                       }
                    },
                    RigidArea {
                        width: 5
                    },
                    TextField {
                        columns: 30
                        value: bind model.newHireName
                    },
                    RigidArea {
                        width: 5
                    },
                    Button {
                        text: "Hire"
                        enabled: bind model.newHireName.length() > 0
                        action: operation() {
                            insert model.newHireName
                               after model.employees[model.selectedEmployee];
                            model.newHireName = "";
                            if (sizeof model.employees == 1) {
                                model.selectedEmployee = 0;
                            } else {
                                model.selectedEmployee++;
                            }
                        }
                    }]
                }
            }
            visible: true
        }

The code to create the ListBox is in bold. As you can see, I've created the ListBox by assigning a list of ListCell objects to its cells attribute. In this case the list of cells is projected from the list of employees in the model. So as employees are added to or deleted from the model the corresponding cells will be added to or deleted from the ListBox. Whatever value you assign to a ListCell's text attribute will be displayed when the cell is rendered. Although not required in this example, you can also assign HTML markup to a ListCell's text attribute to create styled text and/or images as the content of the cell.

The ListBox's selection attribute contains the indices of the list's selected cells. In this case I've bound it to the model's selectedEmployee attribute, so as I move the selection in the list, the model's selectedEmployee will be updated. At the same time, if I update the selectedEmployee attribute (as I do above in the "Hire" button's action) the list's selection will reflect my change.

After clicking "Fire" twice I see:

If I enter a new name in the text field:

And then click "Hire":

SplitPanes

The F3 SplitPane is based on a custom Java component rather than the Swing JSplitPane class. Unlike JSplitPane it can contain multiple components. Like JSplitPane you can control its orientation and the amount of space assigned to each of its contained components. Let's look at an example, which will look like this:

This example consists of a horizontal split pane with two components. The left component is a ListBox which occupies 30% of the available space. The right component which occupies the remaining 70% of the space is actually a ScrollPane containing a CenterPanel (a panel that contains one component which it centers in its content area). The CenterPanel, in turn, contains an ImagePanel that displays the image associated with the selected item in the list box.

        class ExampleModel {
            attribute imageFiles: String*;
            attribute selectedImageIndex: Number;
            attribute selectedImageUrl: String;
        }


        var model = ExampleModel {
            var: self
            imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif",
                         "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif",
                         "kathyCosmo.gif", "lainesTongue.gif",
                         "left.gif", "middle.gif", "right.gif",
                         "stickerface.gif"]

            selectedImageUrl: bind "http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/{self.imageFiles[self.selectedImageIndex]}"
        };

        Frame {
            title: "SplitPane Example"
            height: 400
            width: 500
            content: SplitPane {
                orientation: HORIZONTAL
                content:
                [SplitView {
                    weight: 0.30
                    content: ListBox {
                         selection: bind model.selectedImageIndex
                         cells: bind foreach (file in model.imageFiles)
                            ListCell {
                               text: bind file
                           }
                    }
                },
                SplitView {
                    weight: 0.70
                    content: ScrollPane {
                        view: CenterPanel {
                            background: white
                            content: ImagePanel {
                                url: bind model.selectedImageUrl
                            }
                        }
                    }
                }]
            }

            visible: true
        }

The code related to the split pane is in bold. As you can see I've made this a horizontal split pane by assigning HORIZONTAL to its orientation attribute. I've added components to the SplitPane by assigning a list of SplitView objects to its content attribute. Each SplitView has two attributes, weight and content. The weight attribute determines how much of the available space is given to that panel when the overall split pane is resized. Whatever component you assign to its content attribute will be displayed inside that section of the split pane.

RadioButton, RadioButtonMenuItem, ToggleButton, and ButtonGroup

The F3 RadioButton class encapsulates the Swing JRadioButton component. The F3 RadioButtonMenuItem class encapsulates the Swing JRadioButtonMenuItem component. The F3 ToggleButton class encapsulates the Swing JToggleButton component.

These components have a strong similarity with each other, and with single selection ListBox's, ComboBox's, TabbedPane's, and CardPanel's, namely that all of these components allow you to "pick" one item from a list of items.

RadioButtons, RadioButtonMenuItems, and ToggleButtons are associated with a list of items by means of the F3 ButtonGroup class (which as you might expect corresponds to the Swing ButtonGroup). Unlike the Swing class however, the F3 ButtonGroup also provides a selection model similar to a (single-selection) ListBox. ButtonGroup's selection attribute holds a numeric index that controls which button among the buttons it contains is the selected one. If you assign a value to this attribute, the button at that index will become selected, and any other buttons will be deselected. Likewise, if the user selects a particular button, the index of that button will be implicitly assigned to the ButtonGroup's selection.

To demonstrate this I'll extend the example from the previous section to include three button groups. The first will be associated with a list of RadioButtonMenuItems in a menu. The second will be associated with a list of RadioButtons placed into a four column GridPanel. Finally, the third will be associated with a list of ToggleButtons placed into a single column GridPanel. Each group of buttons will be projected from the same model objects as were the ListBox's cells in the original example, and the selection attribute of their ButtonGroup's will be bound to the same model attribute as the ListBox's selection was in the original. The result is that whatever selection you make in the ListBox, the menu, the radio buttons, or the toggle buttons will be reflected in all the others.

Here's what it looks like initially:

        class ExampleModel {
            attribute imageFiles: String*;
            attribute selectedImageIndex: Number;
            attribute selectedImageUrl: String;
        }

        var model = ExampleModel {
            var: self
            imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif",
                         "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif",
                         "kathyCosmo.gif", "lainesTongue.gif",
                         "left.gif", "middle.gif", "right.gif",
                         "stickerface.gif"]

            selectedImageUrl: bind
                "http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/{self.imageFiles[self.selectedImageIndex]}"
        };

        Frame {
            menubar: MenuBar {
                menus: Menu {
                   text: "File"
                   mnemonic: F
                   var buttonGroup = ButtonGroup {
                       selection: bind model.selectedImageIndex
                   }
                   items: foreach (imageName in model.imageFiles)
                        RadioButtonMenuItem {
                           buttonGroup: buttonGroup
                           text: imageName
                        }
                }
            }
            title: "RadioButton/ToggleButton Example"
            height: 400
            width: 500
            content: BorderPanel {
                top: GridPanel {
                        rows:  sizeof model.imageFiles / 4
                        columns: sizeof model.imageFiles % 4
                        var buttonGroup = ButtonGroup {
                            selection: bind model.selectedImageIndex
                        }
                        cells: foreach (imageName in model.imageFiles)
                             RadioButton {
                                 buttonGroup: buttonGroup

                                 text: imageName
                             }
                }
                right: GridPanel {
                        rows: sizeof model.imageFiles
                        columns: 1
                        var buttonGroup = ButtonGroup {
                             selection: bind model.selectedImageIndex
                        }
                        cells: foreach (imageName in model.imageFiles)
                             ToggleButton {
                                 buttonGroup: buttonGroup
                                 text: imageName
                             }
                }
                center: SplitPane {
                    orientation: HORIZONTAL
                    panels:
                    [SplitPanel {
                        weight: 0.30
                        content: ListBox {
                             selection: bind model.selectedImageIndex
                             cells: bind foreach (imageName in model.imageFiles)
                                 ListCell {
                                    text: bind imageName
                                 }
                         }
                    },
                    SplitPanel {
                         weight: 0.70
                         content: ScrollPane {
                             view: CenterPanel {
                                 background: white
                                 content: ImagePanel {
                                     url: bind model.selectedImageUrl
                                 }
                             }
                         }
                    }]
                }
            }
            visible: true
        }

The code related to the ButtonGroups is in orange. As you can see you add buttons to a button group by setting the button's buttonGroup attribute to the desired ButtonGroup.

If I click on "Pig.gif" in the list box (or on the "Pig.gif" radio button, or on the "Pig.gif" toggle button) I see the following:

If I open the menu you can see that it also reflects the same selection:

ComboBoxes

The F3 ComboBox corresponds to the Swing JComboBox component. To demonstrate the use of ComboBoxes I'll add two of them to the previous example. The example will now look like this:

        class ExampleModel {
            attribute imageFiles: String*;
            attribute selectedImageIndex: Number;
            attribute selectedImageUrl: String;
        }


        var model = ExampleModel {
            var: self
            imageFiles: ["Bird.gif", "Cat.gif", "Dog.gif",
                     "Rabbit.gif", "Pig.gif", "dukeWaveRed.gif",
                     "kathyCosmo.gif", "lainesTongue.gif",
                     "left.gif", "middle.gif", "right.gif",
                     "stickerface.gif"]
            selectedImageUrl: bind "http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/{self.imageFiles[self.selectedImageIndex]}"
        };

        Frame {
            menubar: MenuBar {
                menus: Menu {
                   text: "File"
                   mnemonic: F
                   var buttonGroup = ButtonGroup {
                       selection: bind model.selectedImageIndex
                   }
                   function makeRadioButton(buttonGroup, imageName) {
                        return RadioButtonMenuItem {
                           buttonGroup: buttonGroup
                           text: imageName
                        };
                   }
                   items: foreach (imageName in model.imageFiles)
                        makeRadioButton(buttonGroup, imageName)

                }
            }
            title: "RadioButton/ToggleButton/ComboBox Example"
            height: 400
            width: 500
            content: BorderPanel {
                top: GridPanel {
                        rows:  sizeof model.imageFiles / 4
                        columns: sizeof model.imageFiles % 4
                        var buttonGroup = ButtonGroup {
                            selection: bind model.selectedImageIndex
                        }
                        cells: foreach (imageName in model.imageFiles)
                            RadioButton {
                                buttonGroup: buttonGroup
                                text: imageName
                            }
                }
                right: GridPanel {
                        rows: sizeof model.imageFiles
                        columns: 1
                        var buttonGroup = ButtonGroup {
                             selection: bind model.selectedImageIndex
                        }
                        cells: foreach (imageName in model.imageFiles)
                             ToggleButton {
                                 buttonGroup: buttonGroup
                                 text: imageName
                             }
                }
                center: SplitPane {
                    orientation: HORIZONTAL
                    panels:
                    [SplitPanel {
                        weight: 0.30
                        content: ListBox {
                            selection: bind model.selectedImageIndex
                            cells: bind foreach (imageName in model.imageFiles)
                                ListCell {
                                    text: bind imageName
                                }
                        }
                    },
                    SplitPanel {
                         weight: 0.70
                         content: BorderPanel {
                            top: ComboBox {
                                selection: bind model.selectedImageIndex
                                cells: bind foreach (imageName in model.imageFiles)
                                    ComboBoxCell {
                                        text: bind imageName
                                    }
                            } 
                            center: ScrollPane {
                                view: CenterPanel {
                                    background: white
                                    content: ImagePanel {
                                        url: bind model.selectedImageUrl
                                    }
                                }
                            }
                        }
                    }]
                }
                bottom: FlowPanel {
                     alignment: LEADING
                     content: ComboBox {
                         selection: bind model.selectedImageIndex
                         cells: bind foreach (imageName in model.imageFiles)
                              ComboBoxCell {
                                 text: bind "<html>
                                           <table>
                                              <tr>
                                                <td>
                                                   <img src='http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/{imageName}' height='32' width='32'></img>
                                                </td>
                                                <td>
                                                    {imageName}
                                                </td>
                                              </tr>
                                           </table>
                                         </html>"
                              }
                    }
                }
            }
            visible: true
        }

The code related to the ComboBoxes is in bold. You specify the content of a ComboBox's drop-down list by assigning a list of ComboBoxCell's to its cells attribute. The ComboBoxCell's text attribute determines the appearance of the cell. You can create styled text and/or images as the content of a cell by assigning HTML content to its text attribute (as in the ComboBox in the lower left corner of the window in this example). The ComboBox's numeric selection attribute determines the selected cell. Programmatically assigning a (zero-based) integer index to this attribute causes the cell at that position to be selected. At the same time when the user selects a cell, the index of the cell will be implicitly assigned to selection. In this example I've bound the selection attribute of both ComboBoxes to the same model attribute as the button groups and listbox in the original example, and I've also projected the list of ComboBox cells from the same model attribute as the button groups and the listbox. As a result, you can select the image to be displayed through either ComboBox as well as through the listbox or button groups.

If I open the second ComboBox, the example looks like this:

Trees

The F3 Tree class provides a declarative interface encapsulating the Swing JTree component. To introduce you to the use of Trees, let's first create a very simple example tree with no dynamic behavior:

        Frame {
            height: 400
            width: 300
            content: Tree {
                root: TreeCell {
                    text: "Tree"
                    cells:
                    [TreeCell {
                        text: "colors"
                        cells:
                        [TreeCell {
                            text: "<html><font color='blue'>blue</font></html>"
                        },
                        TreeCell {
                            text: "<html><font color='red'>red</font></html>"
                        },
                        TreeCell {
                            text: "<html><font color='green'>green</font></html>"
                        }]
                    },
                    TreeCell {
                        text: "food"
                        cells:
                        [TreeCell {
                            text: "hot dogs"
                        },
                        TreeCell {
                            text: "pizza"
                        },
                        TreeCell {
                            text: "ravioli"
                        }]
                    }]
                }
            }
            visible: true
        }

Here's what it looks like when I run it:

To populate a Tree I assign an expression returning a TreeCell object to its root attribute. A TreeCell represents one row in the tree. You specify the child cells of a TreeCell by assigning a list of TreeCell objects to its cells attribute. In addition, a TreeCell has a text attribute which determines its visual appearance. As you can see, you can assign HTML content to text to create styled text and/or images as the content of a tree cell.

Next I'll recreate one of the Swing tutorial demos (called GenealogyExample) that displays the descendants or ancestors of a particular person.

When you run it, this example progam initially looks like this:

If I select a person in the tree, and then click one of the radio buttons, the selected person becomes the root of the tree, and depending on my selection, the ancestors or descendants of the person are shown as sub-nodes in the tree.

Below is the code for this example. The code related to the tree itself is in bold. TreeCell has a Boolean selected attribute that is set by the tree implementation as the user selects cells in the tree. At the same time if you programatically assign a value to this attribute, the corresponding tree cell will be selected or unselected depending on the value.

In this case the geneology of a person is a recursive data structure, so I've defined the TreeCell's cells using an expression that makes recursive function calls. Notice that I've used the bind lazy operator rather than just the bind operator in the initialization of the cells attribute, which indicates lazy evaluation. This means that its right hand side is not evaluated until its left hand side is accessed the first time. As a result the recursive calls to the descendantTree() and ancestorTree() functions are not actually performed until you expand a node in the tree and the tree requires access to the node's child cells.

        class GeneologyModel {
            attribute people: Person*;
            attribute selectedPerson: Person;
            attribute showDescendants: Boolean;
        }

        class Person {
            attribute selected: Boolean;
            attribute father: Person;
            attribute mother: Person;
            attribute children: Person*;
            attribute name: String;
        }

        // By defining these triggers I can populate the model
        // by just assigning the mother and father attributes of a Person

        trigger on Person.father = father {
            insert this into father.children;
        }

        trigger on Person.mother = mother {
            insert this into mother.children;
        }

        // Create and populate the model
        var model = GeneologyModel {

            var jack = Person {
                selected: true
                name: "Jack (great-granddaddy)"
            }
            var jean = Person {
                name: "Jean (great-granny)"
            }
            var albert = Person {
                name: "Albert (great-granddaddy)"
            }
            var rae = Person {
                name: "Rae (great-granny)"
            }
            var paul = Person {
                name: "Paul (great-granddaddy)"
            }
            var josie = Person {
                name: "Josie (great-granny)"
            }
            var peter = Person {
                father: jack
                mother: jean
                name: "Peter (grandpa)"
            }
            var zoe = Person {
                father: jack
                mother: jean
                name: "Zoe (grandma)"
            }
            var simon = Person {
                father: jack
                mother: jean
                name: "Simon (grandpa)"
            }
            var james = Person {
                father: jack
                mother: jean
                name: "James (grandpa)"
            }
            var bertha = Person {
                father: albert
                mother: rae
                name: "Bertha (grandma)"
            }
            var veronica = Person {
                father: albert
                mother: rae
                name: "Veronica (grandma)"
            }
            var anne = Person {
                father: albert
                mother: rae
                name: "Anne (grandma)"
            }
            var renee = Person {
                father: albert
                mother: rae
                name: "Renee (grandma)"
            }
            var joseph = Person {
                father: paul
                mother: josie
                name: "Joseph (grandpa)"
            }
            var isabelle = Person {
                father: simon
                mother: veronica
                name: "Isabelle (mom)"
            }
            var frank = Person {
                father: simon
                mother: veronica
                name: "Frank (dad)"
            }
            var louis = Person {
                father: simon
                mother: veronica
                name: "Louis (dad)"
            }
            var laurence = Person {
                father: james
                mother: bertha
                name: "Laurence (dad)"
            }
            var valerie = Person {
                father: james
                mother: bertha
                name: "Valerie (mom)"
            }
            var marie = Person {
                father: james
                mother: bertha
                name: "Marie (mom)"
            }
            var helen = Person {
                father: joseph
                mother: renee
                name: "Helen (mom)"
            }
            var mark = Person {
                father: joseph
                mother: renee
                name: "Mark (dad)"
            }
            var oliver = Person {
                father: joseph
                mother: renee
                name: "Oliver (dad)"
            }
            var clement = Person {
                father: laurence
                mother: helen
                name: "Clement (boy)"
            }
            var colin = Person {
                father: laurence
                mother: helen
                name: "Colin (boy)"
            }

            people: [jack, jean, albert, rae, paul, josie,
                  peter, zoe, simon, james, bertha, anne,
                  renee, joseph, frank, louis, laurence,
                  valerie, marie, helen, mark, oliver,
                  clement, colin]

            selectedPerson: jack
            showDescendants: true
        };

        // Tree generation functions:
        operation geneologyTree(p:Person, showDescendants:Boolean) {
            if (showDescendants) {
                return descendantTree(p);
            } else {
                return ancestorTree(p);
            }
        }

        function descendantTree(p:Person) {
            return TreeCell {
                selected: bind p.selected
                text: bind p.name
                cells:
                    bind lazy
                        foreach (c in p.children)
                            descendantTree(c)
            };
        }

        function ancestorTree(p:Person) {
            return TreeCell {
                selected: bind p.selected
                text: bind p.name
                cells:
                    bind lazy
                        foreach (a in [p.father, p.mother])
                            ancestorTree(a)
            };
        }

        Frame {
            title: "Genology Example"
            height: 300
            width: 300
            content: BorderPanel {
                top: FlowPanel {
                    var buttonGroup = new ButtonGroup()
                    content:
                    [RadioButton {
                        buttonGroup: buttonGroup
                        text: "Show Descendants"
                        selected: model.showDescendants
                        onChange: operation(newValue:Boolean) {
                             if (newValue) {
                                 var selectedPerson = model.people[selected];
                                 if (selectedPerson <> null) {
                                     model.selectedPerson = selectedPerson;
                                 }
                                 model.showDescendants = true;
                             }
                        }
                    },
                    RadioButton {
                        buttonGroup: buttonGroup
                        text: "Show Ancestors"
                        onChange: operation(newValue:Boolean) {
                             if (newValue) {
                                 var selectedPerson = model.people[selected];
                                 if (selectedPerson <> null) {
                                     model.selectedPerson = selectedPerson;
                                 }
                                 model.showDescendants = false;
                             }
                        }
                    }]
                }
                center: Tree {
                        showRootHandles: true
                        root: bind geneologyTree(model.selectedPerson,
                                                   model.showDescendants)
                }
            }
            visible: true
        }

If I expand all the nodes and select "Clement" the tree looks like this:

After clicking on "Show Ancestors", Clement becomes the root, and his parents are shown beneath him:

Tables

The F3 Table class encapsulates the Swing JTable component. To demonstrate the use of Tables, I'll create a slight variation of one of the Swing tutorial examples (SimpleTableDemo) that looks like this:

        class Person {
            attribute firstName: String;
            attribute lastName: String;
            attribute sport: String;
            attribute numYears: Number;
            attribute vegetarian: Boolean;
            attribute selected: Boolean;
        }

        class TableDemoModel {
            attribute people: Person*;
        }

        var model = TableDemoModel {
            people:
            [Person {
                firstName: "Mary"
                lastName: "Campione"
                sport: "Snowboarding"
                numYears: 5
                vegetarian: false
            },
            Person {
                firstName: "Alison"
                lastName: "Huml"
                sport: "Rowing"
                numYears: 3
                vegetarian: true
            },
            Person {
                firstName: "Kathy"
                lastName: "Walrath"
                sport: "Knitting"
                numYears: 2
                vegetarian: false
            },
            Person {
                firstName: "Sharon"
                lastName: "Zakhour"
                sport: "Speed reading"
                numYears: 20
                vegetarian: true
            },
            Person {
                firstName: "Philip"
                lastName: "Milne"
                sport: "Pool"
                numYears: 10
                vegetarian: false
            }]
        };

        Frame {
            height: 120
            width: 500
            title: "SimpleTableDemo"
            content: Table {
                columns:
                [TableColumn {
                    text: "First Name"
                },
                TableColumn {
                    text: "Last Name"
                },
                TableColumn {
                    text: "Sport"
                    width: 100

                },
                TableColumn {
                    text: "# of Years"
                    alignment: TRAILING
                },
                TableColumn {
                    text: "Vegetarian"
                    alignment: CENTER
                }]

                cells: bind foreach (p in model.people)

                    [TableCell {
                        text:bind p.firstName
                        selected: bind p.selected
                    },
                    TableCell {
                        text:bind p.lastName
                    },
                    TableCell {
                        text: bind p.sport

                    },
                    TableCell {
                        text: bind "{p.numYears}"
                    },
                    TableCell {
                        text: bind if p.vegetarian then "Yes" else "No"
                        toolTipText: bind "{p.firstName} {p.lastName} {if not p.vegetarian then "eats" else "does not eat"} meat"
                    }]
            }
            visible: true
        }


The code related to the table is in bold. To create the Table I've assigned a list of TableColumn objects to its columns attribute, and a list of TableCell objects to its cells attribute. Since in this case I assigned five TableColumn objects, this table will have five columns. Since I also assigned five TableCell objects for each person, there will be one row for each person. The TableColumn's text attribute determines the content of the column's header cell. Its width and alignment attributes determine the column's preferred width and horizontal alignment.

The F3 Table is a ScrollableWidget so you don't have to explicitly add it to a scroll pane.

Text Components

To demonstrate the use of text components in F3, I'll create a slight variation of the text sampler demo from the Swing tutorial, which looks like this:

The F3 text components in this example correspond to Swing components as follows:

F3 Widget Swing Component
TextField JFormattedTextField
PasswordField JPasswordField
TextArea JTextArea
EditorPane JEditorPane
TextPane JTextPane

        class TextSamplerModel {
            attribute textFieldInput: String?;
        }

        var model = TextSamplerModel {
        };

        Frame {
            title: "Text Sampler"
            visible: true
            content: SplitPane {
                orientation: HORIZONTAL
                panels:
                [SplitPanel {
                    weight: 0.5
                    content:
                    BorderPanel {
                        top: GridBagPanel {
                             border: CompoundBorder {
                                 borders:
                                 [TitledBorder {
                                     title: "Text Fields"
                                 },
                                 EmptyBorder {
                                     top: 5
                                     left: 5
                                     bottom: 5
                                     right: 5
                                 }]
                             }
                             cells:
                             [GridCell {
                                  anchor: EAST
                                  gridx: 0
                                  gridy: 0
                                  content: SimpleLabel {
                                       text: "TextField: "
                                  }
                              },
                              GridCell {
                                  anchor: WEST
                                  fill: HORIZONTAL
                                  weightx: 1
                                  gridx: 1
                                  gridy: 0
                                  content: TextField {
                                      action: operation(value:String) {
                                          model.textFieldInput = value;
                                      }
                                  }
                              },
                              GridCell {
                                  anchor: EAST
                                  gridx: 0
                                  gridy: 1
                                  insets: {top: 2}
                                  content: SimpleLabel {
                                       text: "PasswordField: "
                                  }
                              },
                              GridCell {
                                  gridx: 1
                                  gridy: 1
                                  fill: HORIZONTAL
                                  weightx: 1
                                  insets: {top: 2}
                                  content: PasswordField {
                                      action: operation(value:String) {
                                          model.textFieldInput = value;
                                      }
                                  }
                              },
                              GridCell {
                                  anchor: WEST
                                  weightx: 1.0
                                  gridx: 0
                                  gridy: 2
                                  gridwidth: 2
                                  fill: HORIZONTAL
                                  content: SimpleLabel {
                                      border: EmptyBorder {
                                          top: 10
                                      }
                                      text: bind if model.textFieldInput == null then  "Type text and then Return in a field" else "You typed \"{model.textFieldInput}\""

                                  }
                              }]
                         }
                   center: BorderPanel {
                             border: CompoundBorder {
                                 borders:
                                 [TitledBorder {
                                     title: "Plain Text"
                                  },
                                  EmptyBorder {
                                      top: 5
                                      left: 5
                                      bottom: 5
                                      right: 5
                                  }]
                             }
                             center: TextArea {
                                 font: new Font("Serif", Font.ITALIC, 16)
                                 lineWrap: true
                                 wrapStyleWord: true
                                 text: "This is an editable TextArea that has been initialized with its text attribute. A text area is a \"plain\" text component, which means that although it can display text in any font, all of the text is in the same font"
                             }
                        }
                    }
                },
                SplitPanel {
                    weight: 0.5
                    content: SplitPane {
                       border: CompoundBorder {
                           borders:
                           [TitledBorder {
                               title: "Styled Text"
                            },
                            EmptyBorder {
                                top: 5
                                left: 5
                                bottom: 5
                                right: 5
                            }]
                       }
                       orientation: VERTICAL
                       panels:
                       [SplitPanel {
                           weight: 0.5
                           content: EditorPane {
                                opaque: true
                                preferredSize: {height: 250 width: 250}
                                contentType: HTML
                                editable: false
                                text: "<html>
        <body>
        <img src='http://java.sun.com/docs/books/tutorial/uiswing/components/example-1dot4/images/dukeWaveRed.gif' width='64' height='64'>
        This is an uneditable <code>EditorPane</code>,
        which was <em>initialized</em>
        with <strong>HTML</strong> text <font size='-2'>but not from</font> a
        <font size='+2'>URL</font>.

        <p>
        An editor pane uses specialized editor kits
        to read, write, display, and edit text of
        different formats.
        </p>
        <p>
        The Swing text package includes editor kits
        for plain text, HTML, and RTF.
        </p>
        <p>
        You can also develop
        custom editor kits for other formats.
        </p>
        </body></html>"
                           }
                       },
                       SplitPanel {
                           weight: 0.5
                           content: TextPane {
                               preferredSize: {height: 250 width: 250}
                               editable: true
                               content:
                               ["This is an editable TextPane, another styled text component, which supports embedded icons...\n",
                                Image {url: "http://java.sun.com/docs/books/tutorial/uiswing/components/example-swing/images/Pig.gif"},
                                "\n...and embedded components...\n",
                                Button {
                                    contentAreaFilled: false
                                    icon: Image {url: "http://java.sun.com/docs/books/tutorial/uiswing/components/example-swing/images/sound.gif"}
                                },
                                "\nTextPane is a subclass of EditorPane that uses a StyledEditorKit and StyledDocument,\n and provides cover methods for interacting with those objects."]
                          }
                       }]
                  }
                }]
           }
        }

Spinners and Sliders

The F3 Spinner and Slider classes correspond to Swing components as follows:

F3 Widget Swing Component
Spinner JSpinner
Slider JSlider

To demonstrate their use I'll create a simple application that shows the temperature on both celsius and farenheit scales.

When I run it the example initially looks like this:

        class Temp {
            attribute celsius: Number;
            attribute farenheit: Number;
            attribute showCelsius: Boolean;
            attribute showFarenheit: Boolean;
        }


        trigger on Temp.celsius = value {
            farenheit = (9/5 * celsius + 32);
        }

        trigger on Temp.farenheit = value {
            celsius = ((farenheit - 32) * 5/9);
        }


        Frame {

            var temp = Temp {
                farenheit: 32
                showFarenheit: true
                showCelsius: true
            }

            height: 300

            width: 400


            title: "Temperature"

            content: Box {
                orientation: VERTICAL
                content:
                [FlowPanel {
                    content:
                    [CheckBox {
                        text: "Show Celsius"
                        selected: bind temp.showCelsius
                    },
                    RigidArea {
                        width: 20
                    },
                    CheckBox {
                        text: "Show Farenheit"
                        selected: bind temp.showFarenheit
                    }]
                },
                Slider {
                    visible: bind temp.showCelsius
                    min: -100
                    max: 100
                    border: TitledBorder {title: "Celsius"}
                    value: bind temp.celsius
                    minorTickSpacing: 5
                    majorTickSpacing: 10
                    paintTicks: true
                    paintLabels: true
                    labels:
                    [SliderLabel {
                         value: 0
                         label: SimpleLabel {
                             text: "0"
                         }
                    },
                    SliderLabel {
                         value: 100
                         label: SimpleLabel {
                             text: "100"
                         }
                    }]
                },
                Slider {
                    visible: bind temp.showFarenheit
                    border: TitledBorder {title: "Farenheit"}
                    min: -148
                    max: 212
                    paintTicks: true
                    minorTickSpacing: 5
                    majorTickSpacing: 10
                    value: bind temp.farenheit
                    paintLabels: true
                    labels:
                    [SliderLabel {
                         value: 0
                         label: SimpleLabel {
                            text: "0"
                         }
                    },
                    SliderLabel {
                         value: 32
                         label: SimpleLabel {
                             text: "32"
                         }
                    },
                    SliderLabel {
                         value: 212
                         label: SimpleLabel {
                             text: "212"
                         }
                    }]
                },
                FlowPanel {
                     alignment: LEADING
                     content:
                     [SimpleLabel {
                          visible: bind temp.showCelsius
                          alignmentX: 1
                          text: "Celsius:"
                      },
                      Spinner {
                          visible: bind temp.showCelsius
                          min: -100
                          max: 100
                          value: bind temp.celsius
                      },
                      RigidArea {
                          width: 20
                      },
                      SimpleLabel {
                          visible: bind temp.showFarenheit
                          alignmentX: 1
                          text: "Farenheit:"
                      },
                      Spinner {
                          visible: bind temp.showFarenheit
                          min: -148
                          max: 212
                          value: bind temp.farenheit
                      }]
                 }]
            }

            visible: true

        }

The code related to the Spinners and Sliders is in bold. Both Spinners and Sliders have min and max attributes that determine their ranges and a value attribute that determines the current value.

In this example I've bound the value attributes of the celsius Spinner and Slider to the model's celsius attribute, and I've bound the value attributes of the farenheit Spinner and Slider to the model's farenheit attribute. I've also defined triggers on the model's celsius and farenheit attributes to update the other attribute's value whenever one of them changes. As a result if I move either slider or change either spinner, all of the others will reflect this change.

For example, here's what it looks like if I set the temperature to 88 degrees farenheit:

Sliders also have several attributes that determine if and how tick lines should be displayed. In addition, by assigning a list of SliderLabels to a Slider's labels attribute you can attach labels to particular values. In this example I've done that for the freezing and boiling points and for 0 degrees farenheit.