WidgetFX Calendar Tutorial

27 01 2009

Update 1: This sample is now Java 1.5 compatible.

Update 2: The Calendar Tutorial has been updated for the WidgetFX 1.2 beta!  Updates are highlighted in red.

WidgetFX makes it simple to deploy JavaFX widgets to the desktop.  This tutorial that Keith Combs and I developed will show you how to create simple Calendar Widget that shows the current year, month, and day.  For some visual splash, we will also make the widget appear like a stack of calendar pages complete with a spiral binding.

Calendar Widget

Calendar Widget

The first step is to download the latest SDK from the WidgetFX site.  You can download the SDK from the following link: http://code.google.com/p/widgetfx/downloads/list

The SDK includes javadoc, samples (including this one), and the required jars.  The only files you need to complete this tutorial are widgetfx-api.jar and JFXtras-0.5.jar which are located in the lib directory.  Make sure to add these to your application classpath when building this sample.

The main widget file always needs to return an object of type Widget.  The following code sample creates a new widget that has an initial width and height of 180 and 200, respectively. It is constrained to keep the same aspect ratio when resizing, and uses config and group variables that you will define later:

def defaultWidth = 180.0;
def defaultHeight = 200.0;

def widget:Widget = Widget {
    width: defaultWidth
    height: defaultHeight
    aspectRatio: defaultWidth / defaultHeight
    configuration: config
    content: group
}
return widget;

Next you need to create the content group.  In this case you can use a Group that scales to fit the window and has a sequence of content to generate the pages, spiral, and calendar text.

def group:Group = Group {
    def arcHeight = defaultHeight / 20;
    def offset = defaultWidth / 25;

    transforms: bind Transform.scale(
        widget.width / defaultWidth,
        widget.height / defaultHeight);
    // scale down by 1% to leave room for stroke width
    scaleX: .99
    scaleY: .99
    content: [
        for (i in reverse [0..3]) {
            createPage(offset*i/3, offset*i/3 + arcHeight,
                       defaultWidth - offset,
                       defaultHeight - offset - arcHeight)
        },
        createSpiral(defaultWidth - offset, arcHeight),
        createPageContents(0, arcHeight * 2,
                           defaultWidth - offset,
                           defaultHeight-offset-arcHeight*2)
    ]
}

The pages are created using a simple for loop that iterates over a fixed sequence of 4 elements.  Each page is created by passing in different offsets to a common createPage function.

To build the createPage function you can return a sequence of 3 Rectangle objects layered on top of each other for the fill, footer, and border of the page.

function createPage(x:Number, y:Number,
                    width:Number, height:Number) {
    [
        Rectangle { // Fill
            translateX: x
            translateY: y
            width: width
            height: height
            fill: Color.WHITE
        },
        Rectangle { // Footer
            translateX: x
            translateY: y + height * 6/7
            width: width
            height: height / 7
            fill: Color.MIDNIGHTBLUE
        },
        Rectangle { // Border
            translateX: x
            translateY: y
            width: width
            height: height
            fill: null
            stroke: Color.BLACK
        }
    ]
}

Because Widget extends Panel, you can run your application by simply passing your CalendarWidget class as the main class to the javafx command.  Running the program so far produces the following stacked deck of pages:

Calendar Widget Page Deck

Calendar Widget Page Deck

Next you need to implement the createSpiral method to add in the spiral binding, which can be created with a sequence of Arc objects.  The following code sample shows how to create 20 arcs that are evenly spaced out across the top of the page using another for loop:

function createSpiral(width:Number, arcHeight:Number) {
    def numArcs = 20;
    for (i in [1..numArcs]) {
        var arcSpacing = width / (numArcs + 2);
        Arc {
            centerX: arcSpacing * (i + 1)
            centerY: arcHeight
            radiusX: arcHeight * 2 / 3
            radiusY: arcHeight
            startAngle: 0
            length: 230
            stroke: Color.BLACK
            fill: null
        }
    }
}

Adding the spiral to the page stack produces the following output:

Calendar Widget with Spiral

Calendar Widget with Spiral

Now it is time to add in the calendar contents.  To get the current year, month, and day you can call the java.util.Calendar class from JavaFX Script.  Rendering the text is simply a matter of binding Text instances to the corners and center of the page as shown in the following example:

var locale = Locale.getDefault();
function createPageContents(x:Number, y: Number,
                            width:Number, height:Number) {
    def calendar = Calendar.getInstance();
    def fontHeight = 20;
    def offset = 5;
    def dateSymbols = bind new DateFormatSymbols(locale);
    def date:Text = Text {
        translateX: bind (width-date.layoutBounds.width)/2
        translateY: bind (height-date.layoutBounds.height)/2
        content: "{calendar.get(Calendar.DAY_OF_MONTH)}"
        font: Font.font("Impact", height * 2 / 3)
        textOrigin: TextOrigin.TOP
    }
    def year:Text = Text {
        translateX: offset
        translateY: offset + fontHeight
        font: Font.font(null, fontHeight)
        content: "{calendar.get(Calendar.YEAR)}"
    }
    def month:Text = Text {
        translateX: bind width - month.layoutBounds.width -
                         offset
        translateY: offset + fontHeight
        font: Font.font(null, fontHeight)
        content: bind Arrays.asList(dateSymbols.getMonths())
            .get(calendar.get(Calendar.MONTH))
    }
    def dayOfWeek:Text = Text {
        translateX:
            bind (width - dayOfWeek.layoutBounds.width) / 2
        translateY: height - offset * 1.5
        font: Font.font(null, fontHeight)
        content: bind Arrays.asList(dateSymbols.getWeekdays())
            .get(calendar.get(Calendar.DAY_OF_WEEK))
        fill: Color.WHITESMOKE
    }
    Group {
        translateX: x
        translateY: y
        content: [date, year, month, dayOfWeek]
    }
}

The example uses a platform specific font for Windows, which will automatically map to a similar Font on other platforms, but you may want to change it based on your platform.

To run your application as a real Widget complete with transparency, resizing, and a toolbar, you can use the Widget Runner.  The Widget Runner is automatically invoked whenever you run a Widget class via Web Start.  Try using the javafxpackager tool to create a Web Start distribution for your widget, which will produce the following results when you launch your JNLP file:

Calendar Widget in Widget Runner

Calendar Widget in Widget Runner

Rather than simply hard coding the locale, you can make this dynamic by creating a Configuration object for the widget.  Configuration consists of properties that get saved to disk or loaded on startup and a Scene that defines the configuration dialog that pops up when you click on the wrench icon in the toolbar.  The following example persists the language, country, and variant as Strings, and pops up a dialog using the JFXtras Grid to align the Text and SwingComboBox.

var language:String = Locale.getDefault().getLanguage();
var country:String = Locale.getDefault().getCountry();
var variant:String = Locale.getDefault().getVariant();
var locale = bind new Locale(language, country, variant);

def config = Configuration {
    properties: [
        StringProperty {
            name: "language"
            value: bind language with inverse
        },
        StringProperty {
            name: "country"
            value: bind country with inverse
        },
        StringProperty {
            name: "variant"
            value: bind variant with inverse
        }
    ]
    var locales = Locale.getAvailableLocales();
    var localePicker = SwingComboBox {
        items: for (l in locales) {
            SwingComboBoxItem {
                selected: l == locale
                text: l.getDisplayName()
                value: l
            }
        }
    }
    scene: Scene {
        content: Grid {
            rows: row([
                Text {content: "Locale:"},
                localePicker
            ])
        }
    }
    onSave: function() {
        var l = localePicker.selectedItem.value as Locale;
        language = l.getLanguage();
        country = l.getCountry();
        variant = l.getVariant();
    }
    onLoad: function() {
        localePicker.selectedIndex =
            Sequences.indexOf(locales, locale);
    }
}

Now when you run the example in the Widget Runner and click on the wrench icon in the toolbar you will get the following dialog:

Locale Configuration Dialog

Locale Configuration Dialog

Note: Due to a defect in the JavaFX 1.2 release the combo box dropdown will not extend below the bottom of the window, so you will have to use keyboard navigation to change the locale.  A JavaFX native combo box is targeted for the next major JavaFX release, which should solve this problem.

Changing the locale and closing the dialog will allow you to see the dates in your language of choice, and will also be saved between sessions.  The following example shows the finished Calendar Widget configured for the Greek locale:

Finished Calendar Widget in Greek

Finished Calendar Widget in Greek

Deploying your fnished WidgetFX application on the web is as simple as creating a Web Start link to the WidgetFX application that references your widget’s jnlp file using the following format:

http://widgetfx.org/dock/launch.jnlp?arg=<widgetUrl&gt;

Note: Until the official WidgetFX 1.2 release, you will have to replace “dock” with “beta” in all your URLs (e.g. http://widgetfx.org/beta/launch.jnlp)

This will automatically install or launch WidgetFX and add your Widget to the dock.  Please feel free to use the following graphic when creating links to widgets so your users know that it runs in WidgetFX:

WidgetFX Launch Icon
WidgetFX Launch Icon

Your completed HTML link would look something like the following:

<a href="http://widgetfx.org/dock/launch.jnlp?arg=http://yoursite.com/CalendarWidget.jnlp">
<img src="http://widgetfx.googlecode.com/svn/site/images/WidgetFX-launch-icon.png" /></a>

Click to launch the finished demo:

Congratulations!

You have finished your first WidgetFX widget complete with dynamic resizing, configuration, and web deployment.  The sky is the limit in creating JavaFX widgets, so give your own widget ideas a try and upload them to the new Widget Library.

Advertisements

Actions

Information

5 responses

1 02 2009
Swing links of the week, February 2nd | Jonathan Giles

[…] Chin posts in his blog a tutorial on using the Calendar widget (which comes as part of […]

20 02 2009
JavaFX Links (3) « Java and more …

[…] Calendar […]

24 02 2009
World feelings WidgetFX « TareitasFX

[…] If you want to learn how to build a widget for widgetfx platform here is a basic tutorial calendar tutorial […]

18 06 2009
30 06 2009
WidgetFX 1.2 Release Announcement « Steve on Java

[…] (from left-to-right: Clock, WebFeed, Weather, SlideShow, Pac-Man, World Clock, Calendar) […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: