Groovy SWT tutorial  Version 0.1

Author:  Will Woodman

Date 19 12 2012

 

Introduction

Groovy swt is factory DSL builder based solution for building SWT/Jface applications from within groovy projects.  You can still update elements outside the builder itself. 

I have made small improvement that allows you construct your end solution in parts which I’ll describe also but this is not in the main groovy code tree and I’m trying to find the owner to proffer these proposed improvements to.  So far just posted into the nabble forums, and will probably have to post as change request to groovy build environment and see what happens.

I wrote this tutorial as the information on how to use the builder is sparse and only hints at how it can be used.  I’ve tried to do some samples that are representative of things you’ll want to know if introducing the Swt/Jface builders into your developer’s kit bag.

References

I’ve attached some other references you can review. The Best book I have found for details on SWT and JFace is the first reference in the list.

1.      SWT/JFace In Action,
Manning, 2005
authors:  Scarpino, Holder, Ng, Mihalkovic 

2.      Very useful SWT background PDF
http://pollosky.files.wordpress.com/2007/12/swt-guide.pdf

3.      SWT Tutorial

http://www.vogella.com/articles/SWT/article.html

4.      IBM introduction to SWT and JFace  http://www.ibm.com/developerworks/views/opensource/libraryview.jsp?search_by=gentle+introduction+swt+jface

5.      Understanding Layouts in SWT

http://www.eclipse.org/articles/article.php?file=Article-Understanding-Layouts/index.html

Building the Groovy SWT project

SWT and Jface are eclipse based capabilities offered by IBM into the Eclipse domain.  Eclipse itself uses both of these technologies when providing the Eclipse tooling platform.  However you can build user interfaces from them without full Eclipse being available.

SWT comes as a standalone jar file from eclipse see http://www.eclipse.org/swt/ where the latest jar can be sourced from.

JFace is an additional set of libraries but you can’t get them as a standalone jar build – they come as part of the eclipse complete download – so you have to get a complete eclipse build and copy out the relevant jars from the /plugin subdirectory into your own project space. 

I’ve deconstructed the latest list subset of required libraries required to build the groovy swt library for a Juno 4.2 eclipse build.

To get the project code tree for groovy swt you have get the current SVN library build for groovy swt from xirces svn libraries at http://svn.groovy.codehaus.org/browse/groovy/trunk/groovy/modules/groovy-swt/ .  You have register for a xirces user account from codehaus here http://xircles.codehaus.org/ .

You can download the groovy swt code , unzip and load into groovy project in eclipse.  This way you get a build that’s built against your eclipse build libraries, and can study the code  – or just try and use the build delivered with groovy.

I’ve attached a picture of the project in eclipse with the eclipse Jface libraries required to meet the build for the project

Once you complete a working build – you can include the eclipse project into your own projects – or export it as a new jar.

The src/examples directory includes 70 odd sample examples you can study and run across SWT and Jface

SWT and JFace basics

The Intent of this tutorial is not to be detailed programming tutorial for SWT or JFace, but rather how the SWT and Jface builders support generating the equivalent code through using the builder.

SWT is a basic widget library and requires you get involved in the details of the UI.  Jface provides a layer of MVC abstraction for certain elements but without removing access to the  underlying SWT elements if you have to go there.

Basic SWT program

 

The following is a very basic SWT program as a groovy script

demoSwtScript.groovy

 

import org.eclipse.swt.SWT

import org.eclipse.swt.widgets.*

import org.eclipse.swt.layout.RowLayout as Layout

 

def display = new Display()

def shell = new Shell(display)

 

shell.layout = new Layout(SWT.VERTICAL)

 

shell.text = 'Groovy / SWT Test'

 

def label = new Label(shell, SWT.NONE)

label.text = 'Simple demo of Groovy and SWT'

shell.defaultButton = new Button(shell, SWT.PUSH)

shell.defaultButton.text = '  Push Me  '

 

shell.pack()

shell.open()

 

while (!shell.disposed) {

    if (!shell.display.readAndDispatch()) shell.display.sleep()

}

display.dispose()

 

Using SWT the Shell and Display classes are the cornerstone for communicating with the underlying OS elements.  The displays readAndDispatch () method is reading the systems event queue and processing the events generated by a user’s action. 

By default SWT assumes a zero size for widgets and controls – so they are invisible by default if you don’t give them a size.  This can be confusing first time whilst you look at a an empty window , so remember to explicitly set the size or use a layout.  The easiest way to achieve this is use of the many layout managers which will calculate and  sizes this for you automatically.  You call pack() method on the shell after you have finished building up the components .  The open() command opens the window and the while loop processes the event queue until the window close is performed. 

It’s the users responsibility to dispose () of UI resources especially images, and fonts under SWT.  Most the work is handled by the recursive call to dispose () on the widget tree.  But fonts and images require especial attention by the developer to avoid memory leaks.

This generates a UI like the above.

Let’s now do the same now using the SwtBuilder.  First it’s a lot shorter and alot of the ‘noise’ /boilerplate is removed from the code which allows you to see the structure more clearly

 

 

demoSwtBuilderScript.groovy

 

import groovy.swt.SwtBuilder

 

import org.eclipse.swt.SWT

import org.eclipse.swt.widgets.Button

 

 

def swt = new SwtBuilder ()

def ibutton

def shell  = swt.shell (text : 'Groovy / SWT Test' ){

              rowLayout()

              label ( 'Simple demo of Groovy and SWT' )

              ibutton = button ( "Push Me" ){

        onEvent(type: 'Selection' ) { println "Hello" }

       }

}

 

 

shell. doMainloop ( true )

you’ll notice that you can mix groovy logic and code amongst the builder code allowing for iterative generation of widgets within the closures, or holding references to generated nodes in the tree

The Builder actually returns a ShellImpl class that extends Shell but adds the open() and event loop processing all within the doMainloop method.

Other points to note.  The layout, and other widgets/controls etc  use a naming convention for  nodes in the DSL which is to use the name of the SWT class counterparts but starting with a lower case.

The parameters to each node in (params..) can be either in map form, or you can present a basic string attribute.  The string attribute is presumed to screen ‘name/label’ of the item – so button (‘Push me’) will use this string as the label on your button. 

Equivalently you can use the map form such as

shell (text : 'Groovy / SWT Test' )

which will use the data binding features in groovy and the underlying SWT data binding to map the values into the control attributes with the same name

within a widget you can attach  event listeners within the attached closure for the DSL build node, e.g.

button (‘name’) {onEvent(type: 'Selection' ){ do stuff..} }

this snippit adds an eventListener for Selection events on the associated button control.

If you hold a reference to the returned widget, like this

ibutton = button ( "Push Me" )

you can then add additional SWT features directly on that reference as you would if you where programing SWT directly.  This allows you to construct the basic structure in the builder and where necessary and still have full SWT programmatic access to the tree when the builder has generated the tree for you .  e.g with the reference ibutton you can write code such as

// extra code after the builder DSL
def  extraButton = new Button (shell, SWT.PUSH)
extraButton.setText ( ‘another extra button’)

from outside the builder,  which when you run the doMainloop() method will appear in your display 

Basic Jface  program

 

Jface has a different basic program structure.  Jface applications provide an abstraction on top of SWT which can make life easier for day to day programming.

Jface applications extend the ApplicationWindow class.  Internally this generates the display and a shell for you. 

In addition you can provide a createContents ()  method – which is called internally to provide the basic controls you want to appear  in the display like this

DemoSWT_Jface.groovy script

 

import org.eclipse.jface.window.ApplicationWindow

import org.eclipse.swt.SWT

import org.eclipse.swt.layout.RowLayout

import org.eclipse.swt.widgets.*

 

class HelloSWT_Jface extends ApplicationWindow

{

              public HelloSWT_Jface ()

              {

                            super ( null );

              }

             

              protected Control createContents (Composite parent)

              {

                            def layout = new RowLayout ()

                            parent.setLayout(layout)

                            def text = new Text (parent, SWT. CENTER )

                            text.setText ( 'Simple demo of Groovy and SWT and Jface ' )

                            def button = new Button(parent, SWT. PUSH )

                            button.setText ( 'Push Me' )

                            parent.pack()

                            return parent

              }

             

              void run ()

              {

                            this .setBlockOnOpen( true )

                            this .open ()

                            Display. getCurrent ().dispose()

              }

}

 

def app = new HelloSWT_Jface()

app.run()

 

this paints a window like this

 

There is another builder in the groovy swt  package that extends the SwtBuilder and registers factories for the Jface viewer and other support classes.  Re writing the above using the Jface builder looks like this

 

import groovy.jface.JFaceBuilder

 

import org.eclipse.swt.SWT

import org.eclipse.swt.layout.RowLayout

import org.eclipse.swt.widgets.Button

 

def jface = new JFaceBuilder()

 

def app = jface. applicationWindow () {

    rowLayout ()

    label ( 'Simple demo of Groovy and SWT' )

    button ( '  Push Me  ' ){

        onEvent (type: 'Selection' ) { println "Hello" }

    }

             

}

 

app. open ()

 

A couple of things to point at – standard download doesn’t seem to trigger initial sizing.  Adding menus seems to do that, but a button control on its own will show a blank window until you do the first resize on the window,  then the layout seems to get applied (I’ve added a fix for this to my local copy in the WindowFactory class by adding window.getShell().pack() to the onNodeCompleted method).

In edition to building as above you can add additional elements in the DSL by calling the builder again within an instance of the builder like this

 

import groovy.jface.JFaceBuilder

 

import org.eclipse.swt.SWT

import org.eclipse.swt.layout.RowLayout

import org.eclipse.swt.widgets.Button

 

def jface = new JFaceBuilder()

 

def app = jface. applicationWindow () {

    rowLayout ()

    label ( 'Simple demo of Groovy and SWT' )

    button ( '  Push Me  ' ){

        onEvent (type: 'Selection' ) { println "Hello" }

    }

              jface. composite ( it ) {

                            gridLayout ()

                            button ( 'hello' )

              }

              jface. button ( it , text: 'third button' )

}

 

 

app. open ()

as you can see we have invoked the builder with another builder call to create a composite passing the current context node of the closure – it , as the  parent for the composite.  In the next example, we have created a button and set the text using the map format parameter array.

Event handling in the builder

The easiest place to start is with the SWT onEvent builder node.  In the builder the onEvent node is registered with the  ListenerFactory class and passes in a Listener class to build.  The Factory node creates an instance of ListenerImpl and calls the parent widget’s addListener method with the matched EventType.

 

The ListenerImpl class has a closure and in the handleEvent() method calls that closure with the Event instance so you can write

    …

    button('  Push Me  '){

        onEvent(type:'Selection') { e -> println "Hello"; do something with e }

    }

 

The type attribute in the onEvent node uses the SWT standard type values for the event class

Values for type

Activate

FocusIn

KeyUp

Move

Arm

FocusOut

MenuDetect

None

Close

Expand

Modify

Paint

Collapse

HardKeyDown

MouseDoubleClick

Resize

Deactivate

HardKeyUp

MouseEnter

Selection

DefaultSelection

Help

MouseExit

Show

Deiconify

Hide

MouseHover

Traverse

Dispose

Iconify

MouseMove

Verify

DragDetect

KeyDown

MouseUp

 

In Jface there is an alternative event processing paradigm.  Jface separates the event handling capability from the UI components that generate the event.  Jface provides this separation with its Action and ActionContributionItem classes.. Simply put  the ActionContributionItem combines the UI widget and ite attached listener class.  When a user interacts with the UI element it triggers the associated Action class which handles the event .

To make this work Jface assumes a simplifying strategy for event handling

1.      Users actions will only involve buttons, toolbars, and menus

2.      Each component will have only one associated event

3.      Each event will have only one event handler

The first assumption means contributions only need take one of three forms, whilst the second separates the contributions from the actions, ie each contributing component triggers only one event.  The ApplicationWindow creates an Action class and sends it to the contribution that generated the event.  The contribution then invokes the run() method of the application class

ActionContributionItem is one of the key concrete classes that implements ContributionManager.  This class is created in an ApplicationWindow  to connect an action to the UI.  It has no set appearance, but instead takes the form of a button, menu bar item, or toolbar item depending on the fill() method used. 

  void

fill ( Composite   parent)
                    Fills the given composite control with controls representing this contribution item.

  void

fill ( CoolBar   parent, int   index)
                    Fills the given cool bar with controls representing this contribution item.

  void

fill ( Menu   parent, int   index)
                    Fills the given menu with controls representing this contribution item.

  void

fill ( ToolBar   parent, int   index)
                    Fills the given tool bar with controls representing this contribution item.

 

In a tool bar a contribution item is a tool bar button or a separator; in a menu bar a contribution item is a menu, and in a menu a contribution item is a menu item or separator.

Under the Jface builder the application node is registered to ContributionManagerFactory, setting ActionImpl as the beanClass for the builder.  What this means is that in the DSL the action node should only be used as a child of toolBarManger or menuManager nodes, as they both implement the IContributionManager interface.

 

import groovy.jface.JFaceBuilder

import groovy.jface.impl.ActionImpl

import org.eclipse.jface.action.Action

 

def jface = new JFaceBuilder()

 

def bRes = Action.isAssignableFrom(ActionImpl.class)

println "action impls is assignable from action" + bRes

 

def app = jface.applicationWindow() {              

                                         

menuManager( "File" ) {

                                     action ( "Very Nice", closure:{ println "Very Nice !!!" } )

                                     separator()

                                     action ( "Check me", checked:true, closure:{ println "I've been checked" } )

              }

              fillLayout ( type:"vertical" )

             

              label("A big red label", background:[204, 0, 0] )

              label("I can barely read this", foreground:[0,200,0] ) 

              label("It sure looks like the dutch flag", foreground:[204,0,150], background:[0, 0, 153] )              

}

app.open()

 

This generates a UI like this and when you open the menu

 

Another example from the samples

 

import groovy.jface.JFaceBuilder

import org.eclipse.swt.graphics.Color

import org.eclipse.swt.widgets.Display;

 

/**

* @author Alexander Becher

*/

class MenuManagerDemo {

 

              def mainapp

              def jface = new JFaceBuilder()

              def act1, label1

             

              public static void main(String[] args) {

                            def demo = new MenuManagerDemo()

                            demo.run()

              }

 

                void run(){

                             

                              mainapp = jface.applicationWindow() {

                                           

                       menuManager( "&File" ) {

                                     action ( "&Open file", closure:{ println "Open file pressed" } )

                                     separator()

                                     action ( "E&xit", closure:{ mainapp.close() } )

                       }

 

                       menuManager( "&Color" ) {

                                     act1 = action ( "Enable blue", checked:false, closure:{if (act1.checked)label1.background=new Color(Display.getDefault( ), 0,0,255); else label1.background=new Color(Display.getDefault( ), 255,0,0)} )

                       }

       

                                          fillLayout ( type:"vertical" )

 

                                          label1 = label( "A label", background:[255, 0, 0] ) {

                                                        image(src:"./resources/icon.gif")

                                          }

                            }

 

                              mainapp.open()

                           

                }

               

}

 

This will display the following app where you can set the colour from red to blue and back, using the color menu option.  Underneath the covers the builder is handling the image dispose activities

Widget map style attributes

The widgets in the builder can accept an array of map style when invoked.  Typically the name of the attribute is the same as the under lying property of each of the widgets which you can find on google for each of the SWT widget types.

I’ve included some of the obvious ones by widget type and some sample attributes

applicationWindow

size : [300, 200]

 

location: [100, 100]

 

title : ‘Name of window’

Shell

style:”CLOSE, BORDER, TITLE”

text :’name of window’

Composite

Style:”none”

Button

style: “Radio” or “Push” or “

 

 

text

Style:”BORDER”

label

background: [204,0,0]

 

Foreground:[0,200,0]

 

Text:’this label text’

Style: ‘left’ or ‘right’ or ‘center’ etc

table

linesVisible:true

headerVisible:true

style: ‘SINGLE, BORDER, H_SCROLL, V_SCROLL, FULL_SELECTION

image

src:”icon.gif”

messageBox

text:’ name of box’

style: “ICON_WARNING, YES, NO, CANCEL”
message:’some user message’

tableColumn

 

text: ‘name’

width:80

height:20

fillLayout

rowLayout

 

type: ‘vertical’, or type:’horizontal’

wrap:true

marginLeft:5

marginRight:5

marginTop:5

marginBottom:5

spacing:10

justify:false

pack:false (all widgets are same size)

rowData

size: (50, 20)

gridlayout

numColumns:2

makeColumnsEqualWidth:true

margeinWidth:5

marginHeight:5

horizontalSpacing:10

verticalSpacing:5

 

gridData

horizontalSpan:2

widthHint:200

style:”FILL_HORIZONTAL”,or “fill_horizontal”, or “HORIZONTAL_ALIGN_BEGINNING”

Action

text: “actionname”

accelerator: SWT. 

Closure: {do stuff}

 

 

 

Opening dialogue boxes

SWT dialogue

Quite often you need to open a dialogue box in your app and get the user to select from either a simple messagebox or more complex dialogue.

Starting in its simplest form you can create a MessageBox in the closure for an onEvent node in the DSL like this

   … but = button( '  Push Me  ' ){

        onEvent(type: 'Selection' ) {                            

                                          MessageBox box = new MessageBox(but.getShell(), SWT. ICON_WARNING | SWT. YES | SWT. NO | SWT. CANCEL )

                                          box.setText(but.getShell(). getText ())

                                          box.setMessage( "Close or Save" )

             

                                          int choice = box.open();

                                          if (choice == SWT. CANCEL ) {

                                                        return false

                                          } else if (choice == SWT. YES ) {

                                                        println "yes"

                                                        return true

                                          }

                            }

    }

 

you need to get set the correct shell parent for the box, and I’ve done that by referencing the button and getting is shell

Jface  dialogue

Jface adds some more flexible support for dialogues, and provides static methods to open commonly used dialogs

For example the MessageDialog also provides static methods to open commonly used dialog , e.g. an info or a warning dialog. To show this working example you can try this

    …but = button ( '  Push Me  ' ){

        onEvent (type: 'Selection' ) {

                                          def shell = but. getShell ()

                                          MessageDialog box = new MessageDialog(but. getShell (), "myDialog" , null /*image*/ , "wills message of day" , MessageDialog. ERROR , [ "first" , "second" , "third" ] as String[], 0 )

                                          int res = box.open()

                                          println res

                                         

                                          //couple of standard dialogs

                                          boolean bRes

                                          bRes = MessageDialog. openConfirm (shell, "Confirm" , "Please confirm" );

                                          bRes = MessageDialog. openError (shell, "Error" , "Error occured" );

                                          bRes = MessageDialog. openInformation (shell, "Info" , "Info for you" );

                                          bRes = MessageDialog. openQuestion (shell, "Question" , "Really, really?" );

                                          bRes = MessageDialog. openWarning (shell, "Warning" , "I warn you" );

                                          println bRes

                                         

                            }

    }

Which generates the first dialog like this – which returns int value starting at 0 which is the index of the button pressed

The static confirm dialog like this – which returns boolean

Jface Selection Dialogs

Eclipse provides several standard selection Dialogs in Eclipse 3.x. In Eclipse 3.x you can directly use them, in Eclipse 4.x you may have to copy them into your plug-in and modify them slightly. I still list them here, so that you can use their coding as examples.

A release after Eclipse 4.2 might make the directly available.

Here is a list of these Dialogs .

        ElementListSelectionDialog

        ListDialog

        ElementTreeSelectionDialog

        CheckTreeSelectionDialog

For example ElementListSelectionDialog allows you to select elements from a list where the dialog provides a filter for the elements.

ElementListSelectionDialog dialog =

  new ElementListSelectionDialog(shell, new LabelProvider());

dialog.setElements(new String[] { "Linux" , "Mac" , "Windows" });

dialog.setTitle( "Which operating system are you using" );

// User pressed cancel

if (dialog.open() != Window.OK) {

    return false;

}

Object[] result = dialog.getResult();

 

Note :  under Juno 4.2 this generates a runtime error looking for.  I cant find the jar file in the 4.2 build

org.eclipse.ui.testing.TestableObject -

also needed org.eclipse.core.jobs as this was referenced – but this is in the 4.2 build

you can also generate a number of standard dialogs such as for file dialog

    but = button ( '  Push Me  ' ){

        onEvent (type: 'Selection' ) {

                                          def shell = but. getShell ()

 

 

                                          FileDialog dialog =

                                          new FileDialog(shell)

                              dialog.setText( "select file" )

                              dialog.setFilterExtensions( "*.*" , "*.ppt" )

                              dialog.setFilterNames( "all (*.*)" , "powerpoint (*.ppt)" )

                              // User pressed cancel

                              def result = dialog.open()

                              println result

 

                            }

    }

Which generates the following

 

And there are other dialogs for

DirectoryDialog


FontDialog


ColorDialog


 

 

InputDialog – simple single values only

PrintDialog

ProgressDialog

 

There is also a PreferencesDialog.  If you look at the sample code in examples like this

 

import groovy.jface.JFaceBuilder

 

class PreferencesDemo {

    

    def jface

    def mainapp

    def pd

    def p1

    

    def run() {

        jface = new JFaceBuilder()

                           

        mainapp = jface . applicationWindow () {                      

                                          pd = preferenceDialog () {

                                                        p1 = preferencePage ( title: "General settings" , filename: "settings.props" ) {

                                                                      booleanFieldEditor (propertyName: "var1" , title: "It's boolean" )

                                                                      colorFieldEditor ( propertyName: "var2" , title: "MainColor" )

                                                                      directoryFieldEditor (propertyName: "var3" , title: "Directory"               )

                                                                      fileFieldEditor ( propertyName: "var4" , title: "File" )

                                                                      fontFieldEditor ( propertyName: "var5" , title: "Font" )

                                                                      integerFieldEditor ( propertyName: "var6" , title: "Integer" )

                                                                      stringFieldEditor ( propertyName: "var7" , title: "String" )

                                                                      radioGroupFieldEditor ( propertyName: "Burger with group" , title: "Burger size:" , numColumns: 3 , useGroup: true ,

                                                                                        labelAndValues: [[ "Small" , "0" ], [ "Big" , "1" ], [ "HUGE" , "2" ]])

                                                            radioGroupFieldEditor ( propertyName: "Cola without group" , title: "Cola size:" , numColumns: 3 , useGroup: false ,

                                                                              labelAndValues: [[ "Small" , "0" ], [ "Big" , "1" ], [ "HUGE" , "2" ]])

                                                  radioGroupFieldEditor ( propertyName: "shake without group" , title: "Shake size:" , numColumns: 2 , useGroup: true ,

                                                                    labelAndValues: [[ "Small" , "0" ], [ "Big" , "1" ], [ "XL" , "2" ], [ "HUGE" , "3" ]])

                                                        }                                                                                                                

                                                        p1 . noDefaultAndApplyButton ()

 

                                                        preferencePage ( title: "Personal settings" , filename: "settings.props" ) {

                                                                      booleanFieldEditor ( propertyName: "var8" , title: "It's boolean" )

                                                                      colorFieldEditor ( propertyName: "var2" , title: "MainColor" )

                                                                      directoryFieldEditor ( propertyName: "var9" , title: "Directory" )

                                                                      fileFieldEditor ( propertyName: "var10" , title: "File" )

                                                                      fontFieldEditor ( propertyName: "var11" , title: "Font" )

                                                                      integerFieldEditor ( propertyName: "var12" , title: "Integer" )

                                                                      stringFieldEditor ( propertyName: "var13" , title: "String" )

                                                        }

                                          }

                            }

                           

                              pd . open ()

              }

 

    public static void main (String[] args) {

                  new PreferencesDemo().run();

    }

    

    

    

}

 

This generates a dialogue like this

If you want to control and place the dialog in the model of the screen you can generate a new shell for the dialogue using the following

/**
* All dialogs and messages will be passed with the centered shell.
* @return Shell
*/
public static Shell getScreenCentredShell() {
   Display display = Display.getCurrent();
   Shell centreShell = new Shell(display);
   Point size = centreShell.computeSize(-1, -1);
  Rectangle screen = display.getMonitors()[0].getBounds();
  centreShell.setBounds((screen.width-size.x)/2, (screen.height-size.y)/2, size.x,   size.y);
  return centreShell;
}
 

Probably needs improvement  to compute bounds of the parent window first – and then fallback to monitor if no parent shell is defined

Toolbars

The following example shows you how to generate a application with a toolbar

import groovy.jface.JFaceBuilder;

 

/**

* @author Alexander Becher

*/

class ToolBarManagerDemo {

 

              def jface = new JFaceBuilder()

             

              public static void main (String[] args) {

                            def demo = new ToolBarManagerDemo()

                            demo.run()

              }

             

              void run() {

                            def mainapp = jface . applicationWindow ( size :[ 300 , 200 ], location:[ 100 , 100 ]){

                                          fillLayout ()

                                          def tb = toolBarManager () {

                                                        action ( "&Open file" , closure:{ println "Open file pressed" } )

                                                        action ( "&C" , closure:{ println "C pressed" } )

                                                        action ( "&A" , closure:{ println "A pressed" } )

                                                        separator ()

                                                        action ( "&Z" , closure:{ println "Z pressed" } )

                                                        action ( "&Y" , closure:{ println "Y pressed" } )

                                                        separator ()

                                                        def act1 = action ( closure:{ println "last one" } )

                                                        {

                                                                      image (src: "./resources/icon.gif" )

                                                        }

                                          }

                            }

                            mainapp. getShell (). layout ()

                            mainapp. open ()

              }

}

 

Which if you run it generates a window like this

 

Wizard pages

The builder also helps you generate SWT wizard pages that a user can step through using the WizardPage classes

 

import groovy.jface.JFaceBuilder

import org.eclipse.swt.layout.GridData

 

/**

* @author Alexander Becher

*/

class AnotherWizardDemo {

 

              def jface = new JFaceBuilder()

              def wizardPage2

             

  public static void main(String[] args) {

    def demo = new AnotherWizardDemo()

    demo.run()

  }

 

              void run() {

                            def dates =[ "1", "2", "3", "4", "5", "6", "7", "8", "9",

                                                             "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",

                                                             "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"]

                                    

              def  months= ["January", "February", "March", "April", "May",

"June", "July", "August", "September", "October", "November", "December" ]

                            // lets create a window

              def mainapp = jface.window() {

              // and now the wizard dialog

              def wizardDialog = wizardDialog("Groovy Wizard Demo",

                performFinish:{ println "Finished was pressed"; return true},

                performCancel:{ println "Cancel method"; return true} )

              {

                            // lets add a picture to the wizard's title

                            image(src:"./resources/groovy.gif")

                            // first page for the wizard

              def wizardPage1 = wizardPage( title:"Step 1",   description:"This is the first page",

                            nextPressed:{ println "Button Next on first Page" }, closure:

                            {

                                                                     

                                jface.composite(it) {

                                          gridLayout( numColumns:4 )

                                                                          

                                 // Date of travel

                                          def Calendar cal = Calendar.getInstance()

                                                                                      cal.setTime(new Date())

                                          def day = cal.get(Calendar.DAY_OF_MONTH)

                                                                                    label ("Travel on:", layoutData:gridData(horizontalAlignment:GridData.BEGINNING))

                                          def travelDate = combo( style:"READ_ONLY", items:dates, visibleItemCount:10, layoutData:gridData(horizontalAlignment:GridData.FILL, widthHint:25))

                                          travelDate.text=day

                                          def travelMonth = combo(  style:"READ_ONLY", items: months, layoutData:gridData(horizontalAlignment:GridData.FILL))

                                          travelMonth.text = travelMonth.items[cal.get(Calendar.MONTH)]

                                          // Calculate years              

                                          def startyear = cal.get(Calendar.YEAR);

                                          def years = []

                                          years.add(startyear)

                                          (1..<3).each {

                                                        years.add(startyear + it)

                                          }

                                                                                   

                                          def travelYear = combo( style:"READ_ONLY", items: years, layoutData:gridData(horizontalAlignment:GridData.FILL))

                                          travelYear.text = startyear

                                                                                   

                                          // Date of return                            

                                          label (style:"NONE", "Return on:", layoutData:gridData(horizontalAlignment:GridData.FILL))

 

                                          combo(style:"READ_ONLY", items: dates, layoutData:gridData(horizontalAlignment:GridData.FILL, widthHint:25))

                                          combo( style:"READ_ONLY", items:months, layoutData:gridData(horizontalAlignment:GridData.FILL))

                                          combo(style:"READ_ONLY", items:years, layoutData:gridData(horizontalAlignment:GridData.FILL))

                                                                                   

                                          // Departure                                                        

                                          label (style:"NONE", "From:", layoutData:gridData(horizontalAlignment:GridData.FILL))                                          

                                          text( style:"BORDER", layoutData:gridData(horizontalAlignment:GridData.FILL, horizontalSpan:3));

 

                                          // Destination

                                          label (style:"NONE", "To:", layoutData:gridData(horizontalAlignment:GridData.FILL))                                                        

                                          text( style:"BORDER", layoutData:gridData(horizontalAlignment:GridData.FILL, horizontalSpan:3))

                                                                                   

                                          radioButton("Take a plane", layoutData:gridData(horizontalAlignment:GridData.FILL, horizontalSpan:3))

                                          radioButton("Take the car", layoutData:gridData(horizontalAlignment:GridData.FILL, horizontalSpan:3))

                                          line (layoutData:gridData(horizontalAlignment:GridData.FILL, horizontalSpan:3))

                            }

                })

                // second page for the wizard

                wizardPage2 = wizardPage( title:"Step 2",

description:"This is the second page",

                              backPressed:{println "back on 2nd page"},

                              closure: {

                                          jface.composite( it ) {

                                          gridLayout( numColumns:"2" )

                                          label( "Set PageComplete to true/false: " )

                                          def button2 = button("Change it") {

                                                  onEvent('Selection'){

                                                                                                                        wizardPage2.setPageComplete(!wizardPage2.isPageComplete() )

                                                        }

                                          }

                            }

              })

def wizardPage3 = wizardPage( title:"Step 3",

    description:"This is the third page",

    closure: {

                            jface.composite( it ) {

                                          gridLayout( numColumns:"2" )

                                          label("Label 3" )

                                          button("Do nothing" )

                                          }

                            })

                                                                     

}

wizardPage2.setPageComplete(false)

wizardDialog.open()

                            }                            

}

 

}

 

This generates pages like this where you can step through the pages or cancel

 

 

Layouts

Layouts are an essential part of layout out the window unless you want to hand calculate all positions.  Standard layouts include

1.      FillLayout – equal sized widgets in a single row or column

2.      RowLayout – lays out in row or rows, with fill, wrap and spacing options

3.      GridLayout – lays out widgets in a grid – and widgets can provide GridData as child of a widget to help the GridLayout process

4.      FormLayout – lays out widgets by creating attachments for each widgets sides

5.      StackLayout

A number of the layouts use <Layout>Data classes to provide the additional detail for each component as hints to the layout manager

Incremental UI builder approach

Lastly I’ve made a change to the standard library that allows you build small sections and build them up into a bigger whole through incremental addition.  This is what started me in the first place as I wanted to build something small – which you could test and understand what it was doing and then move onto another section – without having a single builder DSL that was so rich and complex you couldn’t see the wood for the trees.  This is best illustrated by a simple example like this

import groovy.swt.SwtBuilder

 

import org.eclipse.swt.SWT

 

def swt = new SwtBuilder()

 

 

def button = swt.button ( "Push Me" ){

              onEvent ( 'Selection' ) { println "hello world" }

}

 

def app = swt. shell ( 'my app window' ) {

              widget (button)

}

 

app. doMainloop ()

 

in the example – I define a control – in this case button by itself with no containing top level shell required.

Later I can then build the main app using the builder shell node it will include the button into the larger tree.  When you run the app it looks like this

This has not been rigorously tested yet – but is I feel a useful addition to the DSL as it allows you build elements slowly and bring them together later, without having your head explode looking at a large complex full app DSL builder script.

To achieve this I have a registered a new WidgetFactory into the SwtBuilder.  It expects a control as its parameter.  The WidgetFactory class then resets the parent shell for the widget and links it into the parent DSLs shell