Open source Java projects: JFXtras

Utilities and add-ons for the JavaFX Script programming language

1 2 3 4 Page 3
Page 3 of 4

Listing 5. The Help class from a TextSrch3 project's Main.fx file

class Help
{
    function showHelp (owner: Stage): Void
    {
        def helpText = "<html>"
                       "Welcome to Text Search!<br><br>"
                       "Enter the search text in the text field.<br>"
                       "Click <b>Search</b> to initiate the search.<br>"
                       "Click <b>Stop</b> to interrupt a search.<br>"
                       "Click <b>Help</b> to display this help.<br>"
                       "<br>"
                       "The paths of all files that contain<br>"
                       "the search text appear in the list."
                       "</html>";

        var dialogRef: JFXDialog;
        dialogRef = JFXDialog
        {
            title: "Text Search Help"

            owner: owner
            modal: true
            packed: true
            resizable: false

            scene: Scene
            {
                content: Grid
                {
                    border: 15

                    rows:
                    [
                        Row
                        {
                            cells: SwingLabel
                            {
                                text: helpText
                            }
                        }
                        Row
                        {
                            cells: Cell
                            {
                                content: SwingButton
                                {
                                    action: function (): Void
                                    {
                                        dialogRef.close ()
                                    }

                                    text: "OK"
                                }

                                horizontalAlignment: GridConstraints.CENTER
                            }
                        }
                    ]

                    vgap: 10
                }
            }
        }
    }
}

Listing 5 initializes Grid's border attribute to specify an empty border around the dialog box's content. It also initializes Grid's vgap attribute to place some empty space between the text and the button. The rows attribute is populated with two Rows, with the second row consisting of a single Cell that centers its wrapped SwingButton.

Figure 5 shows the visually improved dialog box.

The grid simplifies component centering, provides a border, and provides a vertical gap between the text and the button.
Figure 5. The grid simplifies component centering, provides a border, and provides a vertical gap between the text and the button. (Click to enlarge.)

Additional layouts

The org.jfxtras.scene.layout package provides two other layout classes: Deck and EmptyBorder. By stacking nodes on top of each other, Deck is reminiscent of the java.awt.CardLayout class (which stacks components) in the Abstract Window Toolkit (AWT). EmptyBorder subclasses Deck, letting you surround the stacked nodes with empty space.

Language-oriented classes

The org.jfxtras.lang package provides JFXObject and JFXException classes for (respectively) providing base functionality that is generally useful to all JavaFX objects (such as returning an object's class type), and for supporting the declarative construction of runtime exceptions (that is, classes extending java.lang.RuntimeException). Listing 6 demonstrates these classes.

Listing 6. Main.fx for a LangDemo project

/*
 * Main.fx
 */
package langdemo;

import org.jfxtras.lang.JFXException;
import org.jfxtras.lang.JFXObject;

class NegativeBalanceException extends JFXException
{
}

class Account extends JFXObject
{
    var balance: Number on replace oldValue = newValue
    {
        if (newValue < 0.0)
        {
            balance = oldValue;
            throw NegativeBalanceException
            {
                message: "attempt to set balance to {newValue}"
            }
        }
    }

    function deposit (amount: Number): Void
    {
        balance += amount;
    }

    function withdraw (amount: Number): Void
    {
        balance -= amount;
    }

    override function toString (): String
    {
        "balance = {balance}"
    }
}

def acct = Account { balance: 1000.0 }
println (acct);
println (acct.getJFXClass ());
acct.deposit (1000.0);
println (acct);
acct.withdraw (100.0);
println (acct);
acct.withdraw (1901.0)

Listing 6 invokes JFXObject's public getJFXClass(): javafx.reflect.FXClassType function to return the acct object's class type, which subsequently outputs. This listing also subclasses JFXException to provide a suitably named exception class, and declaratively initializes an instance of this class prior to throwing the exception when an attempt is made to withdraw too much money.

This script generates the following output:

balance = 1000.0
class langdemo.Main.Account
balance = 2000.0
balance = 1900.0
Exception in trigger:
langdemo.Main$NegativeBalanceException: attempt to set balance to -1.0
        at langdemo.Main$Account$1.onChange(Main.fx:21)

Native menus

JFXtras provides, via org.jfxtras.menu's six classes, declarative access to AWT-based menus for integrating pop-up menus into your applications. You can use the classes below to create pop-up menus for use with the system tray, to create and show context-sensitive pop-up menus for different nodes, and so on:

  • NativePopupMenu provides a JavaFX wrapper for the java.awt.PopupMenu class. This class provides a parent attribute (of type java.awt.Component) that identifies the pop-up menu's AWT component. Invoke this class's public show(origin: java.awt.Component, x: Integer, y: Integer): Void function to show the pop-up menu at the specified coordinates relative to the origin component.
  • NativeMenu provides a JavaFX wrapper for the java.awt.Menu class and serves as NativePopupMenu's parent. This class provides an items attribute (of type NativeMenuEntry[]) that identifies the menu entries associated with this menu.
  • NativeMenuEntry serves as the base class for NativeMenuItem.
  • NativeMenuItem provides a JavaFX wrapper for the java.awt.MenuItem class and serves as the parent of the NativeMenu class (and also the NativeCheckboxMenuItem class). This class provides an action attribute (of type function(): Void) for specifying the function that's executed when this menu item is clicked. The class also provides a text attribute (of type String) that specifies the menu item's displayed text.
  • NativeCheckboxMenuItem provides a JavaFX wrapper for the java.awt.CheckboxMenuItem class. This class provides a selected attribute (of type Boolean) that specifies this menu item's checked state.

I've created a TextSrch4 NetBeans project (as an extension to the previous TextSrch3 project) that demonstrates NativePopupMenu and NativeMenuItem. Listing 7 excerpts this project's createMenu() function.

Listing 7. The createMenu() function from a TextSrch4 project's Main.fx file

function createMenu (parent: Component): NativePopupMenu
{
    NativePopupMenu
    {
        items:
        [
            NativeMenuItem
            {
                action: function (): Void
                {
                    Help {}.showHelp (stageRef)
                }

                text: "Help..."
            }
        ]

        parent: parent
    }
}

Listing 7's createMenu() function constructs and returns a pop-up menu with a single Help... menu item (as an alternative to a Help button) for activating the help dialog box. Whenever this menu item is clicked, its action function instantiates the Help class and invokes that class's showHelp() function to present the help dialog box shown previously in Figure 5.

This menu is created via def menu = createMenu (stageRef.getWindow ());, which appears in the script after the stage's declaration. This positioning is necessary to ensure that the stage (an instance of JFXStage) is visible so that its public getWindow(): java.awt.Window function can return a non-null AWT parent for the native pop-up menu.

To ensure that the pop-up menu can be triggered from anywhere on the scene, I inserted a transparent rectangle behind all other scene content. As Listing 8 shows, I then assigned functions to this Rectangle object's onMousePressed and onMouseReleased attributes to show the pop-up menu whenever they detect a menu-trigger.

Listing 8. The menu-trigger detection and display logic from a TextSrch4 project's Main.fx file

onMousePressed: function (me: MouseEvent): Void
{
    if (me.popupTrigger)
        menu.show (stageRef.getWindow (), me.x, me.y)
}

onMouseReleased: function (me: MouseEvent): Void
{
    if (me.popupTrigger)
        menu.show (stageRef.getWindow (), me.x, me.y)
}

Because pop-up menus are triggered differently on different platforms, isPopupTrigger is checked in both of the functions assigned to onMousePressed and mouseReleased, to ensure proper cross-platform behavior.

Figure 6 reveals the pop-up menu for activating the help dialog box.

Right-click (on Windows platforms, if the left and right mouse buttons haven't been reversed) to activate the pop-up menu.
Figure 6. Right-click (on Windows platforms, if the left and right mouse buttons haven't been reversed) to activate the pop-up menu. (Click to enlarge.)

More advanced menu example

Stephen Chin demonstrates the native menu classes in his  DockDialog.fx source code, which is part of WidgetFX.

Unit testing

Recognizing the importance of unit testing, JFXtras provides a declarative unit-testing framework that lets you write JavaFX tests in JavaFX. This testing framework is modeled after best-of-class, behavior-driven, and fluent testing patterns to make tests easier to read and maintain. It consists of the following classes, located in the org.jfxtras.test package:

  • Test is the base class for creating declarative, fluent, behavior-driven tests.
  • Assumption describes a condition that must be met before a test can be run.
  • Expectation specifies a declarative assertion that causes a test to fail if it's not true.
  • Expect provides several convenience functions (such as public equalTo(expected: java.lang.Object): Expectation) that return standard Expectations.
  • ExpectationException is thrown when an expectation is not met.
  • TestResults records the number of failed, passed, and skipped tests.

I've created a BasicTests NetBeans project that demonstrates this unit-testing framework in terms of its Test and Expect classes. Listing 9 presents this project's Main.fx script.

Listing 9. Main.fx for a BasicTests project

/*
 * Main.fx
 */

package basictests;

import org.jfxtras.test.Expect;
import org.jfxtras.test.Test;

Test
{
     say: "factorialGood should return 24 when passed 4"
     do: function () { factorialGood (4) }
     expect: Expect.equalTo (24)
}
.perform ();

println (" ");

Test
{
     say: "factorialBad should return 24 when passed 4"
     do: function () { factorialBad (4) }
     expect: Expect.equalTo (24)
}
.perform ();

function factorialGood (n: Integer): Integer
{
    if (n == 0) 1 else n*factorialGood (n-1)
}

function factorialBad (n: Integer): Integer
{
    if (n < 0) 1 else n*factorialBad (n-1)
}

Listing 9 tests two functions that return factorials. Each test is established by creating a Test object, assigning appropriate values to Test's say (of type String), do (of type function(): Object), and expect (of type Expectation[]) attributes, and invoking Test's public perform(): Test function to run the test. This function outputs test results after running the test.

The String assigned to say describes what the test should accomplish. The function assigned to do contains the code to be tested. It should return a value that can be checked by the expect clause, which consists of a sequence of Expectations; the test fails if any Expectation is not true. Any exceptions thrown from the function are counted towards a test failure unless defined via Test's expectException attribute.

This script generates the following output:

test: factorialGood should return 24 when passed 4.
Test Results: 1 passed, 0 failed, 0 skipped.
Test run was successful!

test: factorialBad should return 24 when passed 4.
TEST FAILURE:
Expected: equal to "24"
  Actual: 0
Test Results: 0 passed, 1 failed, 0 skipped.
TEST RUN FAILED WITH ERRORS.  See above for cause of failures.

Utilities classes

Finally, JFXtras provides a pair of utilities classes in its org.jfxtras.util package. The GeometryUtil class provides functions for converting between JavaFX and Java versions of the Point2D and Rectangle2D classes, whereas the SequenceUtil class provides the following sequence-oriented functions:

  • public characterSequence(start: java.lang.String, end: java.lang.String): <any>[] generates and returns a sequence of one-character strings, starting with the first character of the start string and ending with the first character of the end string.
  • public concat(seq: java.lang.String[]): java.lang.String concatenates all elements in a sequence of Strings into a single String, which is returned.
  • public fold(ident: Number, seq: Number[], func: com.sun.javafx.functions.Function2): Number performs what is known in functional languages as a left fold operation. This operation consists of passing ident's value and seq's first element to reduce function func, which combines these two inputs into a single output. This output value and seq's second element are passed to func, resulting in a new output value. This output value and seq's third value are passed to func, and so on until there are no elements left in seq. The fold() function returns the final output value. Check out Wikipedia's Fold (higher-order function) entry to learn more about this operation.
  • public fold(ident: Integer, seq: Integer[], func: com.sun.javafx.functions.Function2): Integer is equivalent to the former function, but is typed for Integers.
  • public fold(ident: java.lang.Object, seq: java.lang.Object[], func: com.sun.javafx.functions.Function2): java.lang.Object is equivalent to the former function, but is typed for java.lang.Objects.
  • public join(seq: java.lang.String[], delimiter: java.lang.String): java.lang.String joins all of seq's String elements into a single String, with the specified delimiter appearing between successive Strings. The resulting String is returned.
  • public sum(seq: Number[]): Number adds all elements of a Numbers sequence and returns the total.
  • public sum(seq: Integer[]): Integer adds all elements of an Integers sequence and returns the total.

Listing 1 presented an example of SequenceUtil's characterSequence() function. Listing 10 provides another example of this function, and also demonstrates most of the other functions.

Listing 10. Main.fx for a UtilDemo project

/*
 * Main.fx
 */

package utildemo;

import org.jfxtras.util.SequenceUtil;

def letters = SequenceUtil.characterSequence ("dog", "bone");
println (letters);
println (SequenceUtil.concat (letters));
println (SequenceUtil.join (letters, ", "));

def grades = [ 69, 23, 46, 58 ];
println ("Average = {SequenceUtil.sum (grades)/sizeof grades}");

// What is the sum of the series 1+1/2+1/4+1/8+1/16+... (forever)?

var numbers: Number[];
for (i in [0..100])
     insert 1.0/java.lang.Math.pow (2, i) into numbers;
println ("Sum = {SequenceUtil.sum (numbers)}");

// Use a fold function to calculate 5! (factorial).

println (SequenceUtil.fold (1, [2, 3, 4, 5], multiply));

function multiply (a: Integer, b: Integer): Integer
{
    a*b
}

This script generates the following output:

[ d, c, b ]
dcb
d, c, b
Average = 49
Sum = 2.0
120
1 2 3 4 Page 3
Page 3 of 4