A GUI can be ultimately thought of as a tree of nodes. Nodes are either visible GUI elements ( buttons, sliders, textfields etc. ), or holders of further GUI elements as their children ( layout nodes like grids, vertical boxes, horizontal boxes etc. ). If you want to describe this you need a data structure where nodes are hashes of properties ( the width of a textfield, the caption of a button etc. ) and their children are array of nodes. The natural problem in GUI scripting is how to describe this nested structure of hashes and arrays as a single string so that it can be later reconstructed from this string - a method called 'serialization'.
Java has a powerful method for serializing data structures, but the problem is that it produces binary output which is not human readable. There are human readable serialization formats like XML and Json. Why not use those? Well, they are an overkill: they are way too complicated. I need something that I can write on a Notepad by hand.
I developed an own serialization format for arbitrary nested structure of hashes and arrays ( it can contain arrays of hashes, hashes of arrays in any combination and depth ) with terminal scalars ( ultimate hash values or array values ) being strings ( this is not a real constraint, for it is very easy to store integers, Booleans, Doubles etc. as strings and parse them back when needed ).
This format is suitable to describe an arbitrary GUI structure, but still this is too rigid and abstract. The script language can take into to account the specialities of a GUI and make further simplifications and be more human readable.
So learning the lessons of my first rough shot at GUI srcipting I decided to have a 4 step process.
1. Describe the GUI as a script ( human readable ).
2. Translate this script into hash/array serialization format ( still human readable ).
3. Translate ( 'deserialize' ) this serialized format into Java data structures ( not yet GUI structures! ).
4. Ultimately when everything is in Java data structures, parse this structure and build the actual GUI elements and their children in the process.
The advantage of this among others that where the scripting language has not enough expressive power due to its simplicity, you can always insert native serialized parts in it as escape sequences. So you have to be complicated only when it is really necessary and can be simple most of the time. For example the scripting language is only prepared to store simple key-value pairs as node properties. But a combo's value is more complicated than a single key-value pair. It consists of the selected option and also the list of possible options ( which can also change, since the options of combos selecting editable profiles are edited by the app during the run ). Since the application stores any change to the value of GUI elements on disk ( and for this purpose it looks for the 'value' property in the property hash of the node ), this value has to be under a single key 'value' which is a hash of a string and an array in this case. If you want to give a default value to a combo in the script, you have to describe this structure somehow and now you can fall back to the serialization langauge and insert an escaped serialization sequence with the appropriate curly brackets and square brackets ( which are to be avoided in the script otherwise, for it has to use as little number of special characters as possible ).
Besides the inablity to store nested values, the other shortcoming of the first version was that with editable profiles you had to explicitly list all the elements the belong to the profile which was an ad hoc solution.
In the new version you can define a profile parent node and all children of that node that can have editable values will be detected during the run and their change in value will be reflected on disk. So to add a new option to an option list you just add a new element in the script within scope of the root node and all other things are taken care of by the app. The same is true for special controls of profiles ( add button, delete button, select combo ): whenever any of these are clicked, the profile that has to be edited is automatically detected whith traversing back the tree to the root of the profile and detecting the id of the profile that needs to be edited.
The script describing the chess board:
The way the values are stored on disk:
The way how the whole thing looks when run:
The following post will be of zero interest to the wider public, it will be too technical. Why do it anyway? Well, I found that having to write them down publicly helps me to better organize my thoughts.
Now, down to Java programming.
Some actual shortcomings of Java widgets.
For example suppose that you have a slider. If this slider is moved and you want to know about this, you just add a change listener. Sounds easy. But what happens when you have a dozen sliders? You are tempted to create an array of them, and have only one event handler, which is indexed by the array index. But unfortunately in Java the source of the event for a slider cannot be established within the change listener. It can only be done for widgets which have action events ( since the action event carries information as to its source ). You are left with writing change listeners for every single slider by hand.
MyGuiBuilder solves this problem by having a MySlider class which has an identifier (id) as its member variables. This MySlider class has an actual Slider as its Java node element and within the change listener added to this actual Slider, the id is visible. So whenever the slider is moved, the event handler is aware of the id of the slider that was moved.
An other actual problem is that a Java ComboBox turns out to be buggy when you want to delete an element from its list ( this is an actual bug reported on StackExchange ). The workaround is to delete the widget from its parent's children list and recreate it from scratch with the new list. MyComboBox class implements this behaviour, so it creates a flawless CombBox which works as expected.
But not only that. The main point of having derived widgets is that they can have identity and built in behaviours. In the script all MyWidgets have a human readable id. The value of the widget ( text in a texfield, selected element of a combo, true or false state of a checkbox etc. ) is automatically stored in a hash table where the key is this id. On startup the hash table is loaded from disk, on application shut down it is saved back ( in the overridden application start and stop method respectively ). So the state of all widgets is automatically backed up and is available during the run as a hash.
The fact that widgets now are not stored in Java global variables but are accessible through string id-s is very useful.
MyGuiBuilder has a concept of a unified event handler. All MyWidgets send there events to one single function. You have to override this event handler in the actual application. Suppose that you want to handle events which change the color of some elemt of the board ( piece color, square color etc ). Now that id-s are string, you don't have to write down and endless if statement to pick out the events which try to set a color. You can simply pattern match the id, and it it contains the literal "_color" then you know that you have to redraw the board with this new color. You can write something like:
if(ev.id.contains("_color")) { draw board... }
No declaration of variables, no configuration of widgets in separate statements, no writing of event handlers by hand for every single widget and this is just a beginning.
You can define complex behaviours which requires the interaction of widgets.
For example I have a MyProfile widget, which orchestrates together the behaviour of a combo box, a textfield and two buttons and arbitrary additional settables widgets. Together these form an editable profile. When you write something in the profile name ( which is the user name in the above example ) and press the Add/modify button, the profile having the values of all the additional setting elements will be saved under this name and will be listed in the respective profile combo ( so that you can later select it ). The delete button deletes the current profile from the list.
All this behaviour is automatic, so adding a new setting profile or elements to an existing profile is a matter of simple scripting. The same is a pain when you have to do it in plain Java code.
Also the sript allows you to have macros.
The fact that the main window and the settings modal window have the same rounded border is achieved by a macro bvbox ( bordered vbox ).
This complex behaviour is then simply invoked by the macro name in the actual script as !bvbox wherever it is needed:
The script can refer to hash values by id-s ( in the form $id ) so the widgets are automatically configured to the latest settings when they are built.
As you can notice the script language is as simple as possible, all small cap letters, not quotation marks, no commas, semicolons, brackets, etc.
This is intentional. It has to be as simple as possible, so that you can write new code quickly and easily.