Build a swing app from the command line
This example shows how to create a simple swing unitConverter application using the command line editor.Create a layer
To learn StrataCode start by writing a unit converter application which converts numbers from one unit to another, e.g. celcius to fahrenheit. Change to your /home/StrataCode directory and run scc with no arguments:[/home/StrataCode]% scc
No current layers.
Create a new layer?: [y]
Enter new layer directory: /home/StrataCode/layers/
Enter new layer directory: /home/StrataCode/layers/unitDemo/model
Types dynamic by default? y/n: [y]
Members public by default? y/n: [y]
Package: []
Package: [] unitDemo
Layers to extend (optional): []
Layers to extend (optional): [] util
About to create layer definition file: /home/StrataCode/layers/unitDemo/model/model.sc:
dynamic public unitDemo.model extends util {
}
Create? [y]:
Adding layer: unitDemo.model extends: [util(sc.util)]
Compiling util(sc.util) into: /home/StrataCode/layers/util/build using:
No files to compile
Compiling into: /home/StrataCode/layers/unitDemo/model/build layers: [util(sc.util)] dynamic: [unitDemo.model(unitDemo)]
No files to compile
(unitDemo.model:unitDemo) ->
Defining the Domain Model
Start by defining the domain model for the unit converter. You'll need two floating point numbers to store the values to convert between, titles for each unit, and a title for the conversion itself. The class Converter will be used to store each type of converter. Adding a new conversion will require configuration of a new instance of this class.Place the code below into the file unitDemo/model/UnitConverter.sc, or just paste this text directly into the command line. In this case, StrataCode creates unitDemo/model/UnitConverter.sc automatically.
object UnitConverter { static class Converter { double value1, value2; String unit1, unit2; String title; public String toString() { return title; } } object converters extends ComponentList<converter> { object temperature extends Converter { value1 = 0; value2 :=: value1 * 9.0 / 5.0 + 32; unit1 = "Celcius"; unit2 = "Farenheit"; title = "Temperature"; } object distance extends Converter { value1 = 0; value2 :=: value1 * 0.62137119; unit1 = "Kilometers"; unit2 = "Miles"; title = "Distance"; } object speed extends Converter { title = "Speed"; value1 = 0; value2 :=: value1 * 1.609344; unit1 = "Miles per hour"; unit2 = "Kilometers per hour"; } } }StrataCode does not yet listen for file system changes automatically so use:
cmd.refresh();
In addition to the Converter domain model class, there are instances of that class for temperature, distance and speed. Notice the use of the object operator where you'd normally expect to see a class. The object tag lets you define a type and an instance in one definition. You reference objects using the same naming rules as classes (imports, inner objects, etc.) but you can use object references in both situations where Java expects types and values.
Let's look at the definition of the converters object in more detail. It extends ComponentList from the util layer with elements of the newly defined type Converter. Inner objects are added to the List.
Note: In the sample, notice that you can initialize variables in either a class or an instance with a top-level variable=value statement. For example, with the two files:
class Bar { int baz; }
class Foo extends Bar { baz = 3; }In Java, you have to surround these statements with { baz=3; } but StrataCode allows property assignments at the top-level. Arbitrary statements and expressions are disallowed at this level but you can put in an "a.b" expression into the left-hand side. So you can say: a.b = 3 if there is an inner type "a" with a property "b". Let's also look at this statement in more detail:
value2 :=: value1 * 9.0 / 5.0 + 32;
Notice the use of a new assignment-like operator :=:. In StrataCode this defines a forward and reverse data binding expression. When value1 changes, the expression is applied and value2 is updated. And similarly when value2 is modified, the inverse expression is evaluated and value1 is updated. Not all expressions are invertible of course. StrataCode will give a compile time error if it cannot use a reverse binding with a given expression. In this case because there is only a single variable in an arithmetic expression, you can invert the expression so the bi-directional binding is allowed. In StrataCode, a forward-only binding is specified with := and a reverse-only binding is expressed with =:. StrataCode supports most Java expressions with forward-only bindings. For a given method, you can define an inverseMethod to use that method call in a reverse binding.
Because UnitConverter is an object, you can now test this code:
(unitDemo.model:unitDemo) -> UnitConverter {
(unitDemo.model:object unitDemo.UCTest) -> converters {
(unitDemo.model:object unitDemo.UCTest.converters) -> temperature {
(unitDemo.model:object unitDemo.UCTest.converters.temperature) -> value1;
0
(unitDemo.model:object unitDemo.UCTest.converters.temperature) -> value2;
32.0
(unitDemo.model:object unitDemo.UCTest.converters.temperature) -> value1 = 100;
(unitDemo.model:object unitDemo.UCTest.converters.temperature) -> value2;
212.0
(unitDemo.model:object unitDemo.UCTest.converters.temperature) -> }
(unitDemo.model:object unitDemo.UCTest.converters) -> }
(unitDemo.model:object unitDemo.UCTest) -> }
Extending the Model Object
Extend the unitDemo application to add more conversions algorithms, but do this in a new layer:(unitDemo.model:unitDemo) -> cmd.createLayer();
Enter new layer directory: /home/StrataCode/layers/unitDemo/extendedModel
Types dynamic by default? y/n: [y]
Members public by default? y/n: [y]
Package: [unitDemo]
Layers to extend (optional): [] unitDemo/model
Make layer transparent? y/n: [n] unitDemo/model
About to create layer definition file: /home/StrataCode/layers/unitDemo/extendedModel/extendedModel.sc:
dynamic public unitDemo.extendedModel extends unitDemo.model {
}
Create? [y]:
Now place this code in unitDemo/extendedModel/UnitConverter.sc (or just paste this into the command line again):
UnitConverter { converters { object volume extends Converter { value1 = 0; value2 :=: value1 * 0.94635295; unit1 = "Quarts"; unit2 = "Liters"; title = "Volume"; } object weight extends Converter { value1 :=: value2 * 0.035273962; value2 = 0; unit1 = "Ounces (US)"; unit2 = "Grams"; title = "Weight"; } object area extends Converter { value1 = 0; value2 :=: value1 * 2.5899881; unit1 = "Square Miles"; unit2 = "Square Kilometers"; title = "Area"; } } }Here you omit the class/object keyword to modify the UnitConverter object in the base layer. If you had specified instead "object UnitConverter" here, it would replace the previous version with a new object. The modify operation works much like extends but updates the existing type instead of creating a new one.
In this case you are modifying two objects one inside the other. First you modify the UnitConverter object, then you modify the converters subobject. This layer is a simple declarative layer similar to what a business analyst would manipulate. Though it's java-like, the code expresses just the business logic.
Adding the User Interface
Now let's create a new layer to add the swing user interface.Note: Using a new layer follows a nice design pattern to keep dependencies on framework code like swing out of the domain model. This not only lets you reuse the domain model as widely as possible, it also makes the domain model code easier to read and modify. More than any other part of your system, the domain model reflects the underlying business processes you are helping to manage. A flexible and nimble domain model promotes flexible and nimble business processes. Through layering of designs, programmers can easily expose simple but powerful declarative models which expose logic and configuration directly to the people who need them. And with layers, you do not compromise performance or lose static typing, and the option to bind at compile time by putting this information into runtime-only formats like XML.
Create the swing layer:
(unitDemo.extendedModel:unitDemo) -> cmd.createLayer();
Enter new layer directory: /home/StrataCode/layers/unitDemo/swingui
Types dynamic by default? y/n: [y]
Members public by default? y/n: [y]
Package: [unitDemo]
Layers to extend (optional): [] swing/core unitDemo/extendedModel
Make layer transparent? y/n: [n]
UnitConverter { @MainInit object UI extends AppFrame { object numberConverter extends sc.util.NumberConverter { } object converterChoice extends JComboBox { int alignx = 20; items = converters; location := SwingUtil.point(xpad+alignx, ypad); size := SwingUtil.dimension(windowWidth - 2 * (xpad+alignx), preferredSize.height); }StrataCode's swing/core layer is a thin wrapper on the swing component set to add data binding. Class names are the same and other than adding sc data binding events, the behavior of these classes will be the same as with Swing. Because Swing does not have simple row/column layout, this sample works around that limitation by using data binding for location and size properties. Fixable with custom layout managers but for now this shows off the power of StrataCode's data binding. The SwingUtil.point and dimension methods do necessary casts and wrapping.
Properties like location and size in swing are implemented with getX/setX methods which take Point and Dimension instances. StrataCode handles that for you as well as generating getX/setX methods as needed to make properties bindable.
At this point you should see a window. Objects in StrataCode are by default lazily instantiated. The @MainInit annotation is added by the swing layer and causes the component to be initialized as part of the default swing main method. While you are still inside of UnitConverter.UI, define a currentConverter property using a forward binding:
Converter currentConverter := converters.get(converterChoice.selectedIndex);
Since converters is a list, you bind to the List.get method based on the currently selected item in the combo box. As the combo box selection changes, currentConverter's value is automatically updated to point to the selected instance: temperature, area, etc.
Now the rest of the class which defines the two labels and text fields, bound to the currentConverter's values:
int row1y := (int)(ypad + converterChoice.location.y + converterChoice.size.height); object unit1Label extends JLabel { text := currentConverter.unit1; // Display's converter's "unit 1" labelFor = unit1Field; location := SwingUtil.point(xpad, row1y + baseline); size := preferredSize; } object unit1Field extends JTextField { location := SwingUtil.point(xpad + unit1Label.preferredSize.width + gap, row1y); size := SwingUtil.dimension(windowWidth - unit1Label.size.width - xpad - 2*gap, preferredSize.height); // Bind's text property to current converter's value1 after converter to/from string text :=: numberConverter.numberToString(currentConverter.value1); } int row2y := (int)(unit1Field.location.y + unit1Field.size.height + ypad); object unit2Label extends JLabel { text := currentConverter.unit2; labelFor = unit2Field; size := preferredSize; location := SwingUtil.point(unit1Label.location.x, row2y + baseline); } object unit2Field extends JTextField { location := SwingUtil.point(xpad + unit2Label.preferredSize.width + gap, row2y); size := SwingUtil.dimension(windowWidth - unit2Label.size.width - xpad - 2*gap, preferredSize.height); text :=: numberConverter.numberToString(currentConverter.value2); } object errorLabel extends JLabel { // The number converter provides an error when an invalid number is supplied text := numberConverter.error; location := SwingUtil.point(xpad, unit2Field.location.y + unit2Field.size.height + ypad + baseline); size := preferredSize; }As each widget is defined, it is immediately added to the display because your swing layer is dynamic. You now have a working swing version of the UnitConverter. Notice that components robustly respond to resize events because of the data binding expressions. You can interactively adjust the xpad, ypad, baseline, and color variables to adjust the display. Do this in a new layer and you've just created a layer which applies a specific style, something suitable to hand off to a designer. Before you exit, make sure to close the UI and UnitConverter objects so UnitConverter gets saved with your changes:
}
}
Adding a Style Layer
Now that our app is built, we want to create a layer for designers that gives them control over select properties they can use to customize the app. Use the command: cmd.createLayer(); create a layer called unitDemo.style. This becomes the current layer. Type at the command line:UnitConverter { xpad = 5; ypad = 5; gap = 10; foreground = Color.WHITE; background = Color.BLACK; errorLabel { foreground = Color.RED; } }
Compiled Layers
These layers use dynamic mode which for StrataCode means interpreted code, incremental updates of types and instances in most cases, keeping the safety of strong type checking.When you need performance, simply remove the "dynamic" keyword from the class or layer, and StrataCode compiles, rather than interprets, the definition. With no dynamic layers, your code is not dependent on the StrataCode language engine. A standard jar file is produced with only a few simple utilities required from StrataCode. When dynamic layers are present, you need the dynamic layers in the "layer path" of the runtime, plus the small/portable StrataCode language engine.
Edit the files: unitDemo/model/model.sc, unitDemo/extendedModel/extendedModel.sc, unitDemo/swingui/swingui.sc and remove the dynamic keyword from these layer definition files. Run:
scc unitDemo/swingui
Your application runs now in compiled mode. Look at the generated java files in unitDemo/swingui/build. Run the command unitDemo/swingui/build/swingmain to run the application without the sc command.
Note: making a layer dynamic implies that all layers which extend that layer are themselves dynamic.
Command Interpreter
A few more commands that might be helpful at this point:
cmd.down();
cmd.up();
Move down or up to the next layer down in the list. When inside a type, you move to the next layer which modifies that type. When at the root level, you move to the next layer in the global stack of layers. The # characters before and after the type name indicate the number of layers above and below the current layer in the stack.
Use these commands:
cmd.print(); // prints the current type
cmd.listObjects(); // lists sub-objects of this type
cmd.list(); // lists all members of this type
cmd.print("xx"); // prints details of the named member
cmd.edit(); // Valid only when a type is opened. Edits the file, saves, and refreshes if the file changed.
To see all commands, simply use cmd. followed by the TAB character. Command-line completion works in many, not all contexts. Also use up-arrow/down-arrow to navigate through your command history.
Next, add a UnitConverter web UI with the [wicket framework](wicket.html) mobile using [android](gettingStartedAndroid.html), or javascript application using [gwt](gwt.html).