Here are the tools required to create and build the application:
• Java 1.6
• Apache Maven
• NetBeans
• Leiningen
Creating the GUI
We use Leiningen as our Clojure build tool and it plays nicely with Maven, so we create a Maven project in NetBeans for the GUI. The project consists of Swing components, in our example just one JFrame. We do not write any Java code and we just use the NetBeans GUI builder to create components without any logic. The logic will be in the Clojure code. This Maven project will be a dependency of our Clojure project.
First we make a new NetBeans project by clicking on File/New Project.../Maven/Maven Project and selects Maven Quickstart Archetype which is nearly an empty Maven project. We name our project henleyuicomponents. Clojars forces you to have lower-case JAR names, so we choose a lower-case name for our project, useful if we want to publish it later. We set the Group Id to henley, the version to 1.0.0-SNAPSHOT and the package to henley.uicomponents.
We create a new JFrame called MainFrame by right-clicking on the package name in the Projects tree, and selecting "JFrame Form...". We then design the application with the GUI builder and make it looks like this:
For each Swing components (JButton, JText etc.) that we need to access to from our Clojure code, we right-click on it in the GUI builder, select "Customize code", rename it with a meaningful name and set its Access to public. When we are finished designing the GUI we can build it by right-clicking on the project name and choosing "build". This will invoke Maven with the command "mvn install". Alternatively you can go in the project directory and invokes this command with a shell. Once build, the Maven artifact is installed in your local Maven repository (in ~/.m2 on Linux for instance).
We now need to connect the Swing components to our Clojure code.
Wiring the components
We create an "henley" project with Leiningen with "lein new henley" with the following content for the project.clj:
(defproject henley "1.0.0-SNAPSHOT" :description "A GUI for a text generator similar to the one in the book Ansi Common Lisp, Paul Graham" :dependencies [[org.clojure/clojure "1.2.0"] [org.clojure/clojure-contrib "1.2.0"] [henley/henleyuicomponents "1.0.0-SNAPSHOT"]] :dev-dependencies [[swank-clojure "1.2.0"] [lein-run "1.0.0-SNAPSHOT"]] :main henley.core )
The Maven component is listed as a dependency. The :main option specify where the main function is located. This is necessary to create a self-executable JAR.
We follow a MVC pattern where the data of the model are pushed from the controller to the view. The view consists of the Swing components in the Maven component; for our simple example it is only the JFrame. Let us see how our project is organized, here is the project tree:
- The model directory contains the logic of the application, in our case one namespace and its functions to generate some text.
- The view directory contains all the code necessary to use the Swing components. We define two protocols, View and SwingUI: they are used by the controller and allow to abstract the details of the particular graphical implementation. The View protocols is for all functions that are totally independent of Swing from the point of view of the controller. The SwingUI is for all functions that are relative to Swing, like attaching a listener. They are defined in the files view.clj and swingui.clj. The Swing components inside the Maven artifact are referenced in the uicomponents.clj file. Application.clj provides an implementation for the two protocols.
- The controller directory contains the register.clj function which uses the SwingUI interface to register Swing listeners defined in swing_listeners.clj. These listeners can use the SwingUI interface to get some information from the Swing components, for instance the number of words defined by the user in the JText field. This is indeed their purpose: extract the information and calls the function defined in handlers.clj. The functions in handlers.clj are callbacks and do not have access to the SwingUI interface, all the relevant information has been extracted by the swing_listeners and they only access the interface through the View protocol. The function in the handlers uses the model and informs the View of any changes.
Developing the application logic
In our case the logic of the application is just the text generation, a functional translation in Clojure of the Henley program available in the book ANSI Common LISP.
Building and using the application
We build and install the Swing components with the command "mvn install" launched from the henleyuicomponents directory. We then go in the henley directory and call "lein deps" to resolve all dependencies. If we want to build a self-executable we call "lein uberjar" and go outside for a walk; when we are back we should have a standalone JAR. If not, we may have more success by installing cake and do "cake uberjar". The JAR can be executed with "java -jar jarname".
If you have a problem building the JAR you can comment the :main option in the project.clj file and type the command "lein run henley.core -main" from the henley directory to launch the application.
How to use the application? We can for instance generate a french "poem" by using the baudelaire.txt file in the test directory as an input file:
Abstraction levels
We have a lot of files and two protocols just for a simple project. What kind of abstraction do they defined?
- uicomponents.clj allows us to access the Swing components independently of the way they are defined. They could be defined with code manually, with an Eclipse project etc. We choose the NetBeans GUI builder because it is the best free Swing builder available (to the extend of our knowledge).
- the SwingUI protocol allows the controller to access the Swing components in an implementation-independent way. If for instance the Number of words JText field becomes later a JSpinner this will not affect the controller: the details of the implementation are already abstracted by the protocol.
- The callbacks defined in handlers.clj, which are the heart of the controller, are independent of the GUI. The GUI could be written in SWT: this will not affect them. At this level, the View protocol abstracts the GUI implementation.
- By pushing data from the controller to the View, the GUI is independent of the model data. It does not matter in a simple example as ours, but it will on a bigger project.
Conclusion
We have a clean and very flexible design but with one constraint: a lot of functions are defined just to do a few operations. What do you think of this design? Do you see a way to simplify it without losing flexibility?
Links
The self-executable JAR can be download here
The code is available on Git: https://github.com/kototama/henley
No comments:
Post a Comment