The Super Widget Package

Unleashing and extending the power of the GUIKit

David Bailey

http://www.dbaileyconsultancy.co.uk

Abstract

This talk will describe the Super Widget Package (SWP), which offers an extremely high level interface to the GUIKit - making it easy for beginners and students to construct GUI applications in Mathematica. In particular, the SWP requires no knowledge of Java, J/Link, or GUIKit to access the bulk of its facilities.

First, one of the basic super widgets, the SuperWidgetRealBox, which creates a GUI box specialised for machine precision real numbers and binds a Mathematica variable to it, is described. The compactness and simplicity of the super widget notation compared to raw GUIKit notation is emphasised. The talk will illustrate the fact that the SWP uses very traditional Mathematica notation to great advantage. Thus, for example, colours are specified using traditional syntax (RGBColor, etc.) rather than introducing Java equivalents.

It will also be demonstrated how the simple idea of associating each super widget with a Mathematica variable, can streamline GUI construction in many different ways. In particular, no code is required to move data between a widget and its associated variable. This variable also forms a handle that can be used, for example, to specify the enabled/disabled status of a widget even before it has been created.

The talk will then move on to some of the more advanced super widgets, such as SuperWidgetGraphicsPanel. Using this, it is possible to write 2-d interactive graphics applications using traditional Mathematica graphics. Feedback from the mouse uses the normal graphics coordinate system rather than pixels. This opens up a wealth of applications, and will be illustrated by a graph in which the data points can be adjusted using the mouse.

Another super widget, SuperWidgetLiveGraphics3D, embeds a LiveGraphics3D applet in a GUI so that it can be controlled using the other widgets. The talk will illustrate how a 3-dimensional graphic can be rotated to a particular position using a button, or the independent variables (introduced at version 1.8 of LiveGraphics3D) can be controlled from a SuperWidgetRealBox widget.

If time permits, there will also be an opportunity to go under the hood of the SWP to discover some of the secrets of its implementation.

The problem

At first site the GUIKit looks as though it might be easy to program, however in reality, it exposes the raw Java GUI, which still requires considerable code to operate properly. Consider as an example a TextField widget that is to be used to display and amend an integer variable:

var1=2005;
ref = GUIRunModal[
Widget["TextField", {"text"->ToString[var1]}]
];
Print[var1];

This is about the simplest possible GUI, but in addition to layout considerations (which we will deal with shortly) it still needs code added to:
•	Set the cursor position correctly in the field.
•	Pick up user edits and transmit these changes to the variable var1.
•	 Reject invalid input.
•	Possibly interpret ↵ as dismissing the box.
•	Possibly call user code each time the data is changed and/or impose limits on the range of acceptable values.

While some of these concerns may sound trivial, the whole value of a GUI interface is that it should behave like other similar software. A realistic GUI interface may have scores of boxes like this, and programming all this for each instance would be extremely time consuming and error-prone.

The "TextField" widget is very simple. Other widgets require even more programming to work in useful ways. For example, you can construct an interactive graphics application using a "MathPanel" widget, but it involves a lot of work and a fair knowledge of Java.

SuperWidgetIntegerBox

The following code is slightly simpler than the above GUIKit code, but is much more powerful:

Needs["SuperWidgetPackage`"];

var1=2005;
SuperGUIRunModal[
SuperWidgetIntegerBox[var1]
];
Print[var1]

If you execute this and experiment with the resultant GUI, you will see that the cursor starts positioned correctly at the right of the number field and that any unsuitable input is rejected as soon as it is entered. Furthermore, the variable var1 is continuously updated to reflect the contents of the box. Thus the above code can be used without further elaboration other than layout.

Because the SWP uses symbolic option names (rather than strings), it is easy to explore the functionality available in the SuperWidgetIntegerBox:

Options[SuperWidgetIntegerBox]

Let us explore the ChangeFunction option. This is designed for the case where something should happen each time the number is changed by the user, rather than waiting until the window closes:

SetAttributes[f,HoldFirst];
f[x_]:=Print[HoldForm[x],"=",x];
var1=2005;
ref = SuperGUIRunModal[
SuperWidgetIntegerBox[var1,ChangeFunction->f]
];

Each time the user changes the contents of the input field, the contents of var1 are updated appropriately and the expression f[var1] is executed. As shown, by defining the function f with attribute HoldFirst, it is possible to recover the name of the variable as well as its new value. As you may have already guessed, SuperWidgetIntegerBox, and indeed all the super widgets, have the HoldFirst attribute.

The variable var1 is known as the associated variable of the super widget, and every super widget has one.

It is often useful to use SuperWidgetIntegerBox with a variable that does not have an initial value. In this case the corresponding field starts off completely blank – which is desirable if you don't have a sensible default value to offer.

The above additional features are available easily using the SWP, but would add considerable extra complexity to a program written directly using the GUIKit.

The SWP also contains super widgets for reading real numbers and strings – SuperWidgetRealBox, and SuperWidgetStringBox. These are exactly analogous to SuperWidgetIntegerBox.

Because the SWP is based on GUIKit (with some additional raw J/Link calls and Java code), it is possible to display the GUIKit widget definitions that the SWP has used using the ShowWidgets option:

SetAttributes[f,HoldFirst];
f[x_]:=Print[HoldForm[x],"=",x];
var1=2005;
SuperGUIRunModal[
SuperWidgetIntegerBox[var1,ChangeFunction->f],ShowWidgets->True
];

More super widgets

All the super widgets are designed for ease and consistency of use. The first argument is always the control variable, and any additional arguments supply essential additional information. Further options are controlled by named options that can be accessed via the Options command. Here is a typical selection:

Slider

Horizontal and vertical sliders manipulate a real number between user-defined limits.

x=500.0;
SuperWidgetHorizontalSlider[x,0,1000.0]//SuperGUIRunModal;
Print[x]

Options[SuperWidgetHorizontalSlider]

Combo box

A combo box displays a drop-down list of choices, and updates its control variable with the value selected. Using the Editable->True option lets the user type in his own value if appropriate.

SetAttributes[myFunc,HoldFirst];
myFunc[x_]:=Print[Unevaluated[x],"=",x];
x=.;
SuperWidgetComboBox[x,{"Alpha","Beta","Gamma"},(* Editable->True ,*)ChangeFunction->myFunc]//SuperGUIRunModal;
Print[x]

Options[SuperWidgetComboBox]

Buttons

SuperWidgetButton[Null,"OK",1]//SuperGUIRunModal

SetAttributes[f,HoldFirst];

f[x_]:=Print[HoldForm[x]];
SuperWidgetButton[xxx,"Test",f]//SuperGUIRunModal
Null

Tree structure widget

Here is a super widget that is not in 6.0!

tree˘fn[x_]:=Print[Cases[x,Tree˘Node[_,_,1],∞]];
build˘tree[prefix_String,items_List]:=Module[{s},
If[Length[items]==0,
Tree˘Node[prefix,0,0],
Tree˘Node[prefix<>"...",0,0,Map[build˘tree[prefix<>ToString[#],DeleteCases[items,#]]&,items]
]
]
];
x=build˘tree["",{1,2,3,4,5}];
SuperWidgetTree[x,SelectionFunction->tree˘fn,Pixel˘Width->300,Pixel˘Height->300]//SuperGUIRunModal

Clear[x];

Layout

One of the attractive features of the GUIKit is the easy to use layout mechanism. By default, a list of widgets is displayed vertically, sub-lists are displayed horizontally, sub-sub lists are displayed vertically, etc. Spacing is introduced with the WidgetSpace[n], WidgetFill[], and WidgetAlign[] primitives.

The SWP retains this mechanism. However, because a well laid out window needs a lot of space elements, an alternative  shorter notation is supplied to make GUI definitions easier to read.

Using these simple concepts it is easy to create a window that looks more appealing:

age=14.123;
{
(Subscript[¶, 10]),
{(Subscript[¶, 0]),Image˘File["c:/maths/conference_2005/galaxy.jpg"],(Subscript[¶, 0])},
(Subscript[¶, 10]),
{(Subscript[¶, 10]),"Enter the age of the universe (in billions of years)",(Subscript[¶, 10])},
(Subscript[¶, 10]),
{(Subscript[¶, 30]),SuperWidgetRealBox[age],(Subscript[¶, 30])},
(Subscript[¶, 10]),
{(Subscript[¶, 0]),SuperWidgetButton[Null,"OK",1],(Subscript[¶, 0])},
(Subscript[¶, 10])
}//SuperGUIRunModal

Notice how text strings and images are automatically interpreted as labels.

Here is a slightly larger example, which also introduces the SuperWidgetFrame – representing the entire window, and several other super widgets, whose meaning should be obvious.

board˘weight=3.0;
board˘length=1.2;
human˘weight=140.;
human˘height=70.0;
human˘sex=1;
SuperWidgetFrame[Null,{
{(Subscript[¶, 0]),Image˘File["c:/maths/conference_2005/bart.gif"],(Subscript[¶, 0])},
(Subscript[¶, 10]),
{(Subscript[¶, 10]),"Enter parameters for skate board simulation",(Subscript[¶, 10])},
(Subscript[¶, 10]),
{(Subscript[¶, 10]),SuperWidgetLabelledBox[Null,"Board data",{
{(Subscript[¶, 10]),"Weight(lbs):",SuperWidgetRealBox[board˘weight],(Subscript[¶, 10])},
{(Subscript[¶, 10]),"Length(feet):",SuperWidgetRealBox[board˘length],(Subscript[¶, 10])},
(Subscript[¶, 10])
}],(Subscript[¶, 10])},
{(Subscript[¶, 10]),SuperWidgetLabelledBox[Null,"Human data",{
{(Subscript[¶, 10]),"Weight(lbs):",⇥,SuperWidgetRealBox[human˘weight],(Subscript[¶, 10])},
{(Subscript[¶, 10]),"Height(inches):",⇥,SuperWidgetRealBox[human˘height],(Subscript[¶, 10])},
{(Subscript[¶, 10]),"Sex:",⇥,SuperWidgetComboBox[human˘sex,{"Male","Female"}],(Subscript[¶, 10])},
(Subscript[¶, 10])
}],(Subscript[¶, 10])},
{(Subscript[¶, 0]),SuperWidgetButton[Null,"OK",1],"   ",SuperWidgetButton[Null,"Cancel",2],(Subscript[¶, 0])},
(Subscript[¶, 10])
},Title->"Skate board dynamics"]//SuperGUIRunModal

• Comparatively little code is required to construct a useful (and fully functional) GUI interface.

• The two buttons will each close the window returning 1 or 2 to indicate what to do next.

• Animated GIF's can be used with no extra effort to add interest to a window.

Using images

Mathematica can use images from several different sources, but the code to convert them can be quite messy. The SWP uses a concise notation for images derived from the following sources:

These constructions – like the super widgets themselves – are inert until a complete window definition is interpreted by SuperGUIRunModal or SuperGUIRun.

For example, here is a button with a calculus expression on it's face:

Clear[f];
Clear[x];

{(Subscript[¶, 10]),
{(Subscript[¶, 10]),"Plain text, but fancy button",(Subscript[¶, 10])},
(Subscript[¶, 10]),
{(Subscript[¶, 0]),SuperWidgetButton[Null,Image˘Expression[Integrate[f[x],{x,0,∞}]],1],(Subscript[¶, 0])},
(Subscript[¶, 10])}//SuperGUIRunModal

Control variables

Much of the simplicity of super widgets comes from the concept of control variables. Every super widget has a control variable which passes data in and out of the super widget and acts as a handle to reference the widget from within code. Consider the super widget that creates a simple integer box:

SuperWidgetIntegerBox[myVar]

The control variable, myVar has a variety of functions:

• It can supply an initial value to the widget (or blank if the variable has no prior value).

• Its value will be updated as the contents of the widget are edited by the user.

• Its value can be changed by the program and that change passed to the widget using

UpdateWidgetValue[myVar]

• The widget can be enabled/disabled by reference to the control variable:

Set˘Enabled˘Status[myVar,False]

• The status of a control variable can be tested to determine if the window containing its widget is still visible.

Active˘WidgetQ[myVar]

The above functions work for all super widget types where they make sense. Some super widgets can be controlled in further ways using their control variables to identify them.

If a control variable is not required (e.g. in SuperWidgetLabel), it may be coded as Null, in which case a variable is created internally using Unique.

Interactive graphics(1)

The SuperWidgetGraphicsPanel displays graphics and can feed back mouse coordinate information in Mathematica graphics coordinates  (not pixel coordinates). This opens up a host of interesting possibilities – here is a very simple example:

Needs["SuperWidgetPackage`"];

drawing={};
drawing˘history={};
style=1;
Set˘Line˘Style[]:=(Set˘Mouse˘Mode[panel1,gr0,Line˘Drag˘Action];style=1);
Set˘Box˘Style[]:=(Set˘Mouse˘Mode[panel1,gr0,Rectangle˘Drag˘Action];style=2);
myExit[]:=Close˘Frame[fr1];

do˘drawing[]:=Module[{},
Graphics[{drawing,Graphics˘Region[gr0,{{0,0},{0,10},{10,10},{10,0}},Mouse˘Mode->Line˘Drag˘Action,Action˘Function->updateDrawing]},PlotRange->{{0,10},{0,10}},AspectRatio->1,ImageSize->{600,600}]
];

SetAttributes[newLine,HoldFirst];
updateDrawing[pvar_,x1_,y1_,x2_,y2_]:=Module[{},
drawing˘history=Append[drawing˘history,drawing];
Switch[style,
1,
drawing=Append[drawing,Line[{{x1,y1},{x2,y2}}]],

2,
drawing=Append[drawing,Line[{{x1,y1},{x1,y2},{x2,y2},{x2,y1},{x1,y1}}]]
];
panel1=do˘drawing[];
UpdateWidgetValue[panel1];
];

undo˘update[]:=
If[drawing˘history=!={},
drawing=Last[drawing˘history];
drawing˘history=Drop[drawing˘history,-1];
panel1=do˘drawing[];
UpdateWidgetValue[panel1];
];

SuperGUIRunModal[
SuperWidgetFrame[fr1,{
panel1=do˘drawing[];
SuperWidgetGraphicsPanel[panel1],
(Subscript[¶, 10]),
{(Subscript[¶, 0]),SuperWidgetButton[bb1,"OK",1],(Subscript[¶, 0])},
(Subscript[¶, 10])
},Title->"Example drawing program",
Menu->{
{"File",{{"Exit",myExit}}},
{"Edit",{{"Undo",undo˘update}}},{"Objects",{{"Line",Set˘Line˘Style},{"Box",Set˘Box˘Style}}}
}
](* ,ConcealNotebooks->True *)
]

• Note that permanent changes to the graphics are made by re-drawing the entire graph. The SWP causes the wait cursor to be displayed while this update takes place, which makes the brief delay seem more acceptable.

• The temporary lines or boxes displayed while the mouse is being dragged require very fast XOR drawing, and are generated directly by the SWP when the appropriate Mouse˘Mode is selected.

Interactive graphics(2)

Here is a much more elaborate example that illustrates the use of multiple Graphics˘Region constructs to represent the entire graphics surface and a small region round each data point. Each Graphics˘Region construct consists of:

• A control variable

• A list of 2-D vertices describing the region as a polygon.

• A set of options describing the mouse behaviour in the region:

The Graphics˘Region round each point is set up to display a context menu (right click on Windows), to highlight on mouse-over, and to be draggable. If a point is dragged, the best-fit curve is re-drawn when the drag is complete. The context menu for a point lets you delete the point, or to edit the coordinates of the point as numbers.

Options[Graphics˘Region]

Needs["SuperWidgetPackage`"];

(* Suppress a few spelling warnings  *)
ToExpression["{x˘min,x˘max,y˘min,y˘max,pp1,file}"];

(************************************)
(*                                  *)
(*     Various global variables     *)
(*                                  *)
(************************************)
ToExpression["{x˘min,x˘max,y˘min,y˘max,pp1,file}"];
(* Two arbitrary points just to avoid an initial empty screen! Each data point is associated with a unique variable which is used as a handle to manipulate it *)
myData={{Unique["data"],1,1},{Unique["data"],4,4}};
x˘min=-10;
x˘max=10;
y˘min=-20;
y˘max=20;
poly˘order=1;
location="";

(*******************************************************)
(*                                                     *)
(*       Various menu functions – most adjust          *) 
(*          a variable and re-draw the plot            *)
(*                                                     *)
(*******************************************************)
linear˘func[]:=(poly˘order=1;myPlot[]);
quadratic˘func[]:=(poly˘order=2;myPlot[]);
cubic˘func[]:=(poly˘order=3;myPlot[]);
quartic˘func[]:=(poly˘order=4;myPlot[]);
exit˘func[]:=Close˘Frame[fr1];
save˘func[]:=Module[{file,str},
file=File˘Save˘Dialog[];
Print[file];
If[file=!=Null,
str=OpenWrite[file];
Write[str,{x˘min,x˘max,y˘min,x˘max,poly˘order,Map[Drop[#,1]&,myData]}];
Close[str];
]
];

open˘func[]:=Module[{file},
file=File˘Open˘Dialog[];
If[file=!=Null,
{x˘min,x˘max,y˘min,x˘max,poly˘order,myData}=Get[file];
myData=Map[Prepend[#,Unique["data"]]&,myData];
myPlot[];
]
];

aboutText="<html><body>"<>
"<h1 align=\"center\">Simple interactive graph</h1>"<>
"<p>An example program desigend to illustrate the Super Widget Package. You can add or delete points using the mouse, and the graph will update to show the best fit. Use the menu to select the order of polynomial to use for the fit.</p>"<>
"<p>Although this program is extremely simple, it can be used to demonstrate the pitfalls of fitting a high order polynomial to data.</p>"<>
"<p align=\"right\"><a href=\"http://www.dbaileyconsultancy.co.uk\">http://www.dbaileyconsultancy.co.uk</a></p></body></html>";

aboutBox[]:=SuperGUIRunModal[
SuperWidgetFrame[fr2,{
SuperWidgetHTMLPanel[aboutText,Background->RGBColor[1,1,0]]
},
Title->"About this example"
]
];
showResults[]:=Module[{},
SuperWidgetFrame[Null,{
(Subscript[¶, 10]),
{(Subscript[¶, 10]),Image˘Expression[Fit[Map[Drop[#,1]&,myData],Table[x^i,{i,0,poly˘order}],x]],(Subscript[¶, 10])},
(Subscript[¶, 10]),
{(Subscript[¶, 0]),SuperWidgetButton[Null,"OK",1],(Subscript[¶, 0])},
(Subscript[¶, 10])
},Title->"Best fit polynomial"]//SuperGUIRunModal
];
pt˘r=0.3;
makePoint[{gr_,x_,y_}]:=Module[{k},
{Point[{x,y}],Graphics˘Region[gr,Table[{x+pt˘r Cos[k],y+pt˘r Sin[k]},{k,0,2 π,π/8}],
Mouse˘Cursor->"HAND_CURSOR",
Leave˘Dragged˘Image->True,Mouse˘Mode->Region˘Drag˘Action,
Action˘Function->point˘drag˘function,
Mouse˘Over˘Action->Red˘Border,
Menu->{{"Delete point",deletePoint},{"Edit point",editPoint}}]}
];

SetAttributes[motionFunc,HoldFirst];
motionFunc[v_,x_,y_]:=Module[{},
If[Active˘WidgetQ[v],
location="X: "<>ToString[x]<>", Y: "<>ToString[y],
location=""
];
UpdateWidgetValue[location];
];

SetAttributes[point˘drag˘function,HoldFirst];
point˘drag˘function[v_,x1_,y1_,x2_,y2_]:=Module[{pt},
myData=myData/.{v,___}->{v,x2-pt˘r,y2};
myPlot[];
];
(**********************************************************)
(*                                                        *)
(*       This function is called whenever                 *)
(*       a new plot is required.                          *)
(*       It combines a ListPlot of the data               *)
(*       points with a Plot of the best-fit function      *)
(*       using the current values of the various          *)
(*       global variables.                                *)
(*       It uses DisplayFunction->Identity to              *)
(*       prevent a plot being generated in the            *)
(*       notebook itself – simply returning its           *)
(*       result as a Graphics object.                     *)
(*       *Note* The plots use a fixed PlotRage – this     *)
(*       is important because successive plots will       *)
(*       be written over the same canvas and we           *)
(*         don't want to re-calibrate every time          *)
(*                                                        *)
(**********************************************************)

myPlot[]:=Module[{m,c,fitting=False,tmp,p1,p2,x},
fitting=True;
tmp=Fit[Map[Drop[#,1]&,myData],Table[x^i,{i,0,poly˘order}],x];
pp1=Plot[tmp,{x,x˘min,x˘max},
PlotRange->{{x˘min,x˘max},{y˘min,y˘max}},
PlotStyle->{{RGBColor[1,0,0]}},
DisplayFunction->Identity,
Epilog->{Graphics˘Region[gr0,{{x˘min,y˘min},{x˘max,y˘min},{x˘max,y˘max},{x˘min,y˘max}},
Action˘Function->addPoint,Mouse˘Cursor->"CROSSHAIR_CURSOR"],
{PointSize[0.01],Map[makePoint,myData]}},
ImageSize->{600,600},
DisplayFunction->Identity,
AspectRatio->1
];
UpdateWidgetValue[pp1];
];

(********************************************)
(*                                          *)
(*       Function to respond to a mouse     *)
(*       click.                             *)
(*       If you would like to see some      *)
(*       print-out in the notebook when     *)
(*       this happens, just un-comment      *)
(*       the Print statements.              *)
(*       Note how right and/or shifted      *)
(*       clicks are rejected                *)
(*                                          *)
(********************************************)
addPoint[gr_,x_,y_]:=Module[{pt},
If[Mouse˘Button˘Info[gr]==={Left˘Button},
(* Print["Adding data point: ",{x,y}]; *)
myData=Append[myData,{Unique["data"],x,y}];
myPlot[];
]
];

(**************************************************)
(*                                                *)
(*       Function to respond to the               *)
(*       "Delete point" context menu.             *)
(*       If you would like to see some print-out  *)
(*       in the notebook when this happens,       *)
(*       just un-comment the Print statements.    *)
(*                                                *)
(**************************************************)

deletePoint[x_]:=Module[{pt},
pt=Cases[myData,{x,___}];
If[pt=!={},
(* Print["Deleting data point: ",{pt[[1,2]],pt[[1,3]]}]; *)
myData=DeleteCases[myData,{x,___}];
myPlot[];
]
];

(****************************************************)
(*                                                  *)
(*       Function to respond to the "Edit point"    *)
(*       context menu                               *)
(*                                                  *)
(****************************************************)
editPoint[v_]:=Module[{s,x,y,ans},
s=Cases[myData,{v,___}];
x=s[[1,2]];
y=s[[1,3]];
ans=SuperWidgetFrame[Null,{
(Subscript[¶, 10]),
{(Subscript[¶, 10]),"X: ",SuperWidgetRealBox[x],(Subscript[¶, 10])},
(Subscript[¶, 10]),
{(Subscript[¶, 10]),"Y: ",SuperWidgetRealBox[y],(Subscript[¶, 10])},
(Subscript[¶, 10]),
{(Subscript[¶, 0]),SuperWidgetButton[Null,"OK",1],"   ",SuperWidgetButton[Null,"Cancel",2],(Subscript[¶, 0])},
(Subscript[¶, 10])
},Title->"Edit point"
]//SuperGUIRunModal;
If[ans==1,
myData=myData/.{v,___}->{v,x,y};
myPlot[];
]
];

(******************************************************)
(*                                                    *)
(*      This defines the menu as a series of          *)
(*      images of expressions and the corresponding   *)
(*      functions to call.                            *)
(*                                                    *)
(******************************************************)
myMenu={
{
"File",
{
{"Open",open˘func},
{"Save",save˘func},
Menu˘Separator,
{"Exit",exit˘func}
}
},
{
Image˘Expression[f[x]],
{
{Image˘Expression[HoldForm[f[x]=a x+b]],linear˘func},
{Image˘Expression[HoldForm[f[x]=a (x^2)+b x+c]],quadratic˘func},

{Image˘Expression[HoldForm[f[x]=a (x^3)+b (x^2)+c x+d]],cubic˘func},
{Image˘Expression[HoldForm[f[x]=a (x^4)+b (x^3)+c (x^2)+d x+e]],quartic˘func}}
},
{
"Options",
{
{"Show results",showResults}
}
},
{"Help",
{
{"About this example",aboutBox}
}
}
};

ShowGraph[opt_]:=(
myPlot[];
SuperGUIRunModal[
SuperWidgetFrame[fr1,{
SuperWidgetGraphicsPanel[pp1,MouseMotionFunction->motionFunc],
SuperWidgetStringBox[location,Font˘Colour->Blue]
},
Title->"Simple interactive graph",Menu->myMenu],
 ConcealNotebooks->opt 
];
);

This displays the graph. If you set the argument to True, the notebook will be concealed while the graph is on display – thus avoiding any confusion if the Java window is obscured by the notebook.

ShowGraph[False]

Interactive graphics(3)

In this example, the option Mouse˘Mode->Region˘Drag˘Action is used to enable the electronic components to be dragged onto the main surface and subsequently moved about. The Graphics˘Region round each component also supports a context menu with options that just print their name and the name of the corresponding component.

The circuit area (graph-paper area) is also contained in a Graphics˘Region which supports line drawing by drag to provide for 'wiring up' the components.

Note that Graphics˘Regions can be geometrically nested. In this case the iner-most region will dominate. Thus, when the mouse is placed over a data point, it will relate to the Graphics˘Region associated with that point – not the large region covering the entire circuit area.

Needs["SuperWidgetPackage`"];

Clear[cached˘image];
grid˘size=20;
lh˘margin=7*grid˘size;

Image˘Insert[file_,x_,y_,xscale_,yscale_,drag˘function_,name_]:=Module[{image,dims,sx,sy},
If[(cached˘image[file]//Head)===cached˘image,
cached˘image[file]=Import[file,ConversionOptions->{"ColormapOutput"->False}]];
image=cached˘image[file];
dims=Dimensions[image[[1,1]]];
sx=dims[[2]]*xscale;
sy=dims[[1]]*yscale;
{Rectangle[{x,y},{x+sx-1,y+sy-1},image],
Graphics˘Region[name,{{x,y},{x+sx-1,y},{x+sx-1,y+sy-1},{x,y+sy-1}},Mouse˘Cursor->"HAND_CURSOR",Action˘Function->drag˘function,Menu->{{"alpha",a1},{"Test",{{"Delta",d1}}},{"beta",b1}},
Leave˘Dragged˘Image->True,Mouse˘Mode->Region˘Drag˘Action]}
];
(* Note that none of these do anything at present – they could be configured as required *)
a1[x_]:=Print["Alpha ",x];
b1[x_]:=Print["Beta ",x];
d1[x_]:=Print["Delta ",x];
ToExpression["{clip}"];
draw˘component[resistor[name_,x_,y_]]:=Module[{},
Image˘Insert[SuperWidgetPackagePath[]<>"/ExampleFiles/resistor.gif",x,y,1,1,resistorDrag,name]
];
draw˘component[pnp˘transistor[name_,x_,y_]]:=Module[{},
Image˘Insert[SuperWidgetPackagePath[]<>"/ExampleFiles/pnp_transistor.gif",x,y,1,1,pnp˘trDrag,name]
];
draw˘component[npn˘transistor[name_,x_,y_]]:=Module[{},
Image˘Insert[SuperWidgetPackagePath[]<>"/ExampleFiles/npn_transistor.gif",x,y,1,1,npn˘trDrag,name]
];

draw˘component[wire[x1_,y1_,x2_,y2_]]:=Module[{},
{AbsoluteThickness[2],Line[{{x1,y1},{x2,y2}}]}
];

draw˘component[capacitor[name_,x_,y_]]:=Module[{},
Image˘Insert[SuperWidgetPackagePath[]<>"/ExampleFiles/capacitor.gif",x,y,1,1,capacitorDrag,name]
];

draw˘component[electrolytic[name_,x_,y_]]:=Module[{},
Image˘Insert[SuperWidgetPackagePath[]<>"/ExampleFiles/electrolytic.gif",x,y,1,1,electrolyticDrag,name]
];


clip[x_]:=grid˘size*Round[x/grid˘size]
ToExpression["{tr0,trx0}"];

SetAttributes[trDrag,HoldFirst];
circuit={};
pnp˘trDrag[v_,x1_,y1_,x2_,y2_]:=Module[{tr˘name},
tr˘name=Unique["tr"];
If[x1>lh˘margin,
circuit=DeleteCases[circuit,pnp˘transistor[v,_,_],∞]];
If[x2>lh˘margin,circuit=Append[circuit,pnp˘transistor[tr˘name,clip[x2],clip[y2]]]];
vv=Circuit˘Diagram[];
UpdateWidgetValue[vv];
];

npn˘trDrag[v_,x1_,y1_,x2_,y2_]:=Module[{tr˘name},
tr˘name=Unique["tr"];
If[x1>lh˘margin,
circuit=DeleteCases[circuit,npn˘transistor[v,_,_],∞]];
If[x2>lh˘margin,circuit=Append[circuit,npn˘transistor[tr˘name,clip[x2],clip[y2]]]];
vv=Circuit˘Diagram[];
UpdateWidgetValue[vv];
];

resistorDrag[v_,x1_,y1_,x2_,y2_]:=Module[{tr˘name},
res˘name=Unique["R"];
If[x1>lh˘margin,
circuit=DeleteCases[circuit,resistor[v,_,_],∞]];
If[x2>lh˘margin,circuit=Append[circuit,resistor[res˘name,clip[x2],clip[y2]]]];
vv=Circuit˘Diagram[];
UpdateWidgetValue[vv];
];
capacitorDrag[v_,x1_,y1_,x2_,y2_]:=Module[{tr˘name},
res˘name=Unique["C"];
If[x1>lh˘margin,
circuit=DeleteCases[circuit,capacitor[v,_,_],∞]];
If[x2>lh˘margin,circuit=Append[circuit,capacitor[res˘name,clip[x2],clip[y2]]]];
vv=Circuit˘Diagram[];
UpdateWidgetValue[vv];
];

electrolyticDrag[v_,x1_,y1_,x2_,y2_]:=Module[{tr˘name},
res˘name=Unique["V"];
If[x1>lh˘margin,
circuit=DeleteCases[circuit,electrolytic[v,_,_],∞]];
If[x2>lh˘margin,circuit=Append[circuit,electrolytic[res˘name,clip[x2],clip[y2]]]];
vv=Circuit˘Diagram[];
UpdateWidgetValue[vv];
];
circuit˘resize[_,w_,h_]:=Module[{},
circuit˘width=w;
circuit˘height=h;
vv=Circuit˘Diagram[];
UpdateWidgetValue[vv,ReCalibrate->True];
];

draw˘wire[var_,x1_,y1_,x2_,y2_]:=Module[{},
If[x2>lh˘margin,circuit=Append[circuit,wire[x1//clip,y1//clip,x2//clip,y2//clip]]];
vv=Circuit˘Diagram[];
UpdateWidgetValue[vv];
]

circuit˘width=800;
circuit˘height=600;
Circuit˘Diagram[]:=Module[{width=circuit˘width},
Graphics[{{RGBColor[0.8,0.8,0.8],Rectangle[{0,0},{lh˘margin,circuit˘height}]},{RGBColor[0.7,0.7,0.7],Table[Line[{{lh˘margin,k},{width,k}}],{k,0,circuit˘height,grid˘size}],Table[Line[{{k,0},{k,circuit˘height}}],{k,lh˘margin,circuit˘width,grid˘size}]},Map[draw˘component,Join[circuit,{pnp˘transistor[tr0,0,0], npn˘transistor[trx0,0,100],resistor[R0,0,200],capacitor[C0,0,300],electrolytic[V0,0,400]}]],
Graphics˘Region[surface,{{lh˘margin,0},{lh˘margin,circuit˘height-1},{circuit˘width-1,circuit˘height-1},{circuit˘width-1,0}},Mouse˘Mode->Line˘Drag˘Action,Action˘Function->draw˘wire,Leave˘Dragged˘Image->True]},ImageSize->{circuit˘width,circuit˘height},PlotRange->{{0,circuit˘width},{0,circuit˘height}},AspectRatio->Automatic]
];

vv=Circuit˘Diagram[];
SuperWidgetFrame[fr1,{SuperWidgetGraphicsPanel[vv,ResizeFunction->circuit˘resize ]},Window˘Position->{10,10}]//SuperGUIRunModal;

Null

LiveGraphics3D(1)

There is well known Java applet, written by Martin Kraus, which is used to display animated Mathematica graphics on websites. Using the SWP it is possible to embed one of these applets in a window with other controls. Here is a simple example:

Clear[v];
v=Graphics3D[Plot3D[Sin[x y],{x,-2,2},{y,-2,2},DisplayFunction->Identity],ViewPoint->{1,1,1},ViewVertical->{0,0,1}];

{SuperWidgetLiveGraphics3D[v],(Subscript[¶, 10]),{(Subscript[¶, 0]),SuperWidgetButton[Null,"OK",1],(Subscript[¶, 0])},(Subscript[¶, 10])}//SuperGUIRunModal

If the (free) Live3D Java applet is not already installed, the SuperWidgetLiveGraphics3D super widget will offer to install it from Martin Kraus' website.

LiveGraphics3D(2)

A Live Graphics3D applet can be controlled by parameters. These parameters are normally set up by the HTML, and it is possible to arrange for them to vary in time to produce an animation. When run from within the SWP, it is also possible to control these parameters directly:

Circle3D[{px_,py_,pz_},r_]:=With[{eps=0.1},Line[Table[{px+r Cos[t],py+r Sin[t],pz},{t,0,2.0π+eps,eps}]]];

Text3D[x_,pos_]:=Text[StyleForm[x,FontSize->35,FontWeight->"Bold"],pos*1.095];
xxx=1/Sqrt[2.0];
yyy=1/Sqrt[2.0];
gg=Graphics3D[{
Thickness[0.003],
Text3D["A",{-1,0,0}],Text3D["B",{1,0,0}],Text3D["C",{xx,yy,0}],
Circle3D[{0,0,0},1],Line[{{-1,0,0},{1,0,0}}],
PointSize[0.015],Point[{xx,yy,zz}],
Line[{{-1,0,0},{xx,yy,0},{1,0,0}}]
},ViewPoint->{0,0,4},ViewVertical->{0,1,0},Boxed->False];

indepvar={xx->xxx,yy->yyy};
depvar={zz->0,theta->ArcTan[xx,yy],xx->Cos[theta],yy->Sin[theta]};

f[_]:=Module[{s},
s=LiveGraphics3DVariables[gg];
{xxx,yyy}={xx,yy}/.s;
AC=Norm[{xxx,yyy}-{-1,0}];
BC=Norm[{xxx,yyy}-{1,0}];
res=Sqrt[AC^2+BC^2];
UpdateWidgetValue[AC];
UpdateWidgetValue[BC];
UpdateWidgetValue[res];
];

SuperWidgetFrame[Null,{
{(Subscript[¶, 10]),
{
{"AC",⇥,"=",SuperWidgetRealBox[AC]},
{"BC",⇥,"=",SuperWidgetRealBox[BC]},
{Image˘Expression[HoldForm[Sqrt[AC^2+BC^2]]],⇥,"=",SuperWidgetRealBox[res]}
},
SuperWidgetLiveGraphics3D[gg,Pixel˘Width->600,Pixel˘Height->450,
Mouse˘Dragable->False,
Background->RGBColor[0.8,0.8,1],
Independent˘Variables->indepvar,
Dependent˘Variables->depvar,
FullChangeFunction->f]},(Subscript[¶, 10]),
{(Subscript[¶, 0]),SuperWidgetButton[Null,"OK",1],(Subscript[¶, 0])},(Subscript[¶, 10])},Title->"Euclidean Geometry"
]//SuperGUIRun;

Some other Super Widgets and functions

There are more super widgets, such as:

• SuperWidgetTree
• SuperWidgetTable
• SuperWidgetHTMLPanel
• SuperWidgetTextEditor
• SuperWidgetTabPanel
• SuperWidgetCheckBox

The SWP also defines a variety of other easy-to-use functions such as:

• File˘Open˘Dialog
• File˘Save˘Dialog
• Get˘Screen˘Size
• Message˘Beep
• ShowMessageBox
• Java˘Console˘Print

Conclusions

• The SWP greatly simplifies Mathematica GUI construction, and gives it the 'look and feel' of ordinary Mathematica coding.

• No knowledge of Java, GUIKit, J/Link, or MathLink is required to use the SWP.

• The SWP builds on the strengths of Mathematica graphics – adding interactive animation controlled by the Graphics˘Region graphics primitive.

• LiveGraphics3D animations can be embedded in a SWP GUI and controlled by the other widgets.

• A SWP GUI can contain conventional GUIKit widgets embedded within it.

• Super widgets are identified by their unique control variable. This makes it easy to make changes to a live GUI – such as graying out a control in some circumstances, or updating one field in response to changes in another.

• Above all, SWP notation in succinct - the less you have to code, the easier it is to get it working!