Global Variables

Introduction


To significantly expand the expressive power of text2gui, objects can be put and retrieved from a global namespace shared by the model (Java code) and the view/controller (resource bundle). This has the following benefits:
Putting Globals

Via Resource Bundle Key

To put an object converted from a resource bundle key into the globals, just set its globalName subkey like this:

textArea.globalName=ta
textArea.text=Mary had a little indigestion.

which maps ta to the text area in the global namespace. The created object doesn't have to be a composite for this technique to work.

Via String

If you are describing an entire object with a single string, just prefix the string with an ampersand ('&'), the name of the global, followed by a colon (':'):

panel1.contents=[{&fchooser:jfilechooser approveButtonText="Nuke" multiSelectionEnabled=true}]

maps fchooser to the file chooser in the global namespace.

Via Script or Java Code

Inside a BeanShell script, a global can be put by calling putGlobal().

angleModePanel.preInit={
  putGlobal("abg", new ButtonGroup(), argMap);   
}    

The static utility class com.taco.text.GlobalUtilities is statically imported to any BeanShell script (assuming you are using BeanShell 2.0 or above), so you can refer to getGlobal() and putGlobal() without any qualifiers. Also recall that the argMap variable is automatically set to the argument map in any BeanShell script used by the tex2gui library. It needs to be passed to putGlobal() since the globals are stored in the argument map.

A model (written in ordinary Java) can put a global like this:

import com.taco.text.GlobalUtilities;

...


GlobalUtilities.putGlobal("model", this, argMap);

puts this instance as the value of the global variable model. Although, strictly speaking this violates the MVC principle, this mechanism can be useful if you want to allow the view / controller to call methods of the model directly. For example, a "Save" button might be defined as follows:

saveButton.text=Save
saveButton.actionListeners.0={
  new ActionListener() {
    public void actionPerformed(ActionEvent event) {
      getGlobal("model", argMap).save();
    }
  }
}

putGlobal() never throws an exception. putGlobal() returns true iff the global was put successfully.

Retrieving Globals

Global variable values are stored in the argument map, so they can be retrieved by both the model and the view/controller. If a global has not been defined, retrieving the global will result in getting null.

Via String

To reference a global, just precede the name of the global by '&':

angleRadioButton.buttonGroup=&abg

sets the button group of the angle radio button to the global named abg.

panel.contents=[%okButton, {strut length=&len}, %cancelButton]

creates a panel with a horizontal strut between the OK and cancel buttons. The strut has length given by the global len.

Via Script or Java code

The syntax may differ slightly since BeanShell is loosely typed, but the basic mechanism for retrieving a global is the same: calling getGlobal() in the class com.taco.text.GlobalUtilities. In a script inside a resource bundle describing the view, you would write:

clearButton.action={
   new AbstractAction() {
     public void actionPerformed(ActionEvent e) {
       textArea = getGlobal("ta", argMap)       
       textArea.setText("");
      }
   }
}

because all methods of GlobalUtilities are statically imported into the BeanShell environment.

 A model (written in ordinary Java) can retrieve a global like this:

import com.taco.text.GlobalUtilities;

...


argMap.putNoReturn("clearAction", new AbstractAction() {
  public void actionPerformed(ActionEvent e) {
    JTextArea textArea = (JTextArea)  GlobalUtilities.getGlobal("ta", argMap);
    textArea.setText("");
  }
});

which puts an action into the argument map, which the clear button would use. Note that this violates the MVC principle since the model now assumes implementation details of the view/controller.

Retrieving globals can be a useful technique when the text2gui library is only used for the construction of GUI's, not for the control. See The Use of Globals for Non-MVC Implementations below.

Execution Order of Global Creation

Globals created via strings or resource bundle keys are created only when then the string or resource bundle key is converted to an object. This means you cannot assume that a global has been set unless you know that the associated string or resource bundle key has already been read. For example, you might be tempted to write something like this:

commonBorder.globalName=cb
commonBorder=etched type=raised highlight=0xffff shadow=0x808080

panel1.border=&cb
panel1.contents=[%panel2, {jlabel text="Age:"}, {strut length=20}, %spinner}]

spinner.value=65

panel2.border=&cb
panel2.contents=[{jtextfield text="Yo"}]

then in your Java code, convert the resource bundle key panel1 to an instance of JPanel:

JPanel panel = (JPanel) DispatchingComponentConverter.toComponent(bundle, "panel1",
  argMap);


However, the border of the returned panel won't be set to an etched border. It will be set to null instead, because the global cb was never explicitly set; the resource bundle key commonBorder was never read.

To fix the example, we need to change the border line for panel1:

panel1.border=%commonBorder

Then when panel1 is read, the border will be created from the resource bundle key. At that point, cb will be set to reference to the border.

Recall the order of composite creation is preorder. This means in the above example, panel1 is created first. According to the Component Converters table, the border of a JPanel is created before its contents. So the border is created, then panel2, then the text field, then the age label, then the strut, and finally the spinner. That means the line

panel2.border=&cb

is safe because cb will have already been set to the etched border by the time the creation process for panel2 begins.

In practice, this problem is not as complicated as it seems. Just be sure that a global is set in the container of a child that uses it. Setting the global in the instance or preInit subkeys ensures that the global is available before the child is created (see Special Properties of Composites).

Global Composites

Recall the steps involved in constructing a composite object:
  1. If baseKey is assigned to a value in bundle, set val to that value.
  2. Otherwise, construct an instance of the composite object. (This may need to read subkeys as in step 3; the properties corresponding to these subkeys are called creation properties.)
  3. For each ordinary property with name prop:
    1. Determine the converter associated with prop.
    2. Using the associated converter, convert the subkey baseKey.prop to an object. If an error occurs, ignore it -- this allows the user to omit property assignments.
    3. Set the property of the composite object with the value determined in step ii.
  4. If no properties were set in step 3), throw a MissingResourceException
If a composite is assigned to a global variable, the global variable referencing the composite will be set after step 2. This means that the global variable will be available when the composite's ordinary properties are being created.

Avoiding Confusion

An easy way to eliminate all confusion as to when globals are ready to be used is to set all globals before the main component is created. This can be done with the instance collection converter. Let's say you want to share a font and a border throughout your GUI. You don't want to worry about creation order, so you create a resource bundle as follows:

globals.0=b:{
  return new LineBorder(Color.GREEN);
}
globals.1=f:{
  return new Font("Monospaced", Font.PLAIN, 15);
}

textArea.border=&b
textArea.font=&f
...

Because globals key will be read with an instance collection converter, each element must conform to the syntax for instances, not for borders or fonts. Now to create the text area in the Java code for the model, you need two steps:

CollectionConverter.INSTANCE_COLLECTION_CONVERTER.toObject(bundle, "globals", argMap, null);
JTextArea = (JTextArea) DispatchingComponentConverter.toComponent(bundle, "textArea",
  argMap);


The first line reads the globals key and creates all the instances. Since the instances both have their globalName property set, the globals b and f will be available by the time the first line returns. The return value is not needed. The second line can then create a text area that has access to b and f, with no worries.

The Use of Globals for Non-MVC Implementations

One technique for constructing a GUI using text2gui is to put only initial values in the argument map. All of the components you wish to have control over in your application can be put into the global namespace during conversion. Then your application can retrieve all of these components and manipulate them as desired. Here is an example resource bundle:

panel.layout=border
panel.contents=[%scrollPane, {%buttonPanel, {south}}]

scrollPane.viewportView=%list

list.globalName=names
list.listData=["Jeff", "Jim", "Lily", "Helen", "James"]

buttonPanel.layout=box.axis=x
buttonPanel.contents=[%okButton, {strut}, %cancelButton]

okButton.globalName=ok
okButton.text=OK

cancelButton.globalName=cancel
cancelButton.text=Cancel

An application could create the panel, then retrieve the components it needs from the global namespace with the following code:
// An argument map is required to store globals.
JPanel panel = (JPanel) DispatchingComponentConverter.toComponent(bundle,
"panel", argMap);
JList list = (JList) GlobalUtilities.getGlobal("names", argMap);
JButton okButton = (JButton) GlobalUtilities.getGlobal("ok", argMap);
JButton cancelButton = (JButton) GlobalUtilities.getGlobal("cancel", argMap);

list.setSelectedIndex(2);
cancelButton.setEnabled(false);

// other manipulation of the list and buttons here
// you may want to store them in class fields for use by other methods
...
This is frequently the easiest way to convert existing GUI's to use the text2gui library. However, this violates the MVC principle and doesn't make the code much better -- this method only eliminates the GUI construction code and makes the GUI ready for localization. See The MVC Architecture and Argument Maps for information on how to do better.