Estimated time: 60 minutes.
How to prepare your environment}
Click on the "Log" icon in your desktop to open an "Oberon.Log". Move that viewer to the top right corner of your desktop and remember to keep an eye on the Log viewer whilst doing the exercises. Now move this tutorial viewer to the top left corner of the desktop and make sure there is some free space in your desktop. You will use that free space later on as playground. If the playground becomes crowded, you may take the initiative to remove superfluous visual gadgets.
Exercises are not at all confined to those suggested in the tutorial, you would do well to apply the exercises to other objects found on your desktop.
You are now invited to print the source text "Examples.Mod" on paper for consultation, with for example
The following panel demonstrates such a situation.
On the right you have a color picker and on the left a rectangle named "Color", i.e. "Color" is the value of its Name attribute. The color picker's Cmd attribute has the value:
Gadgets.Set Color.Color #Col ~.
Now, press the middle mouse key when on the color picker, position the arrow pointer on a color of your choice and release the key. The color of the rectangle is changed. In this case one of the commands exported by the Gadgets module is used, but you are not limited to these.
If the construction of new commands is a declared objective of this tutorial and of yourself, one programming problem is that of supplying the commands with information about the environment in which they are executed.
Example: the procedure Gadgets.FindObj(context, name) may be used to look for a named gadget in a given context.
F plays a central role in the FrameMsg. It determines the destination or target frame of a message. Often the destination frame of a message is unknown. This happens for example when model update messages are broadcast. In this case the F field is set to NIL.
Caution: It is very important to initialize the message fields correctly. The res field common to all messages, is used to store a result value (>= 0). M.res therefore always should be initialized to -1.
DEFINITION Display; (* excerpt only! *)
CONST
get = 0; set = 1; reset = 2;
TYPE
SelectMsg = RECORD (FrameMsg)
id: INTEGER;
time: LONGINT;
sel: Frame;
obj: Objects.Object
END
END Display.
where:
- id is the message identifier determining which action to perform on a gadget. The following actions are defined:
get: When interrogating which gadget or gadgets are selected in the display space, the destination of the message is unknown. Therefore the field F must be set to NIL. On return the fields obj and sel are set.
set: Select the visual gadget specified in the destination frame field F.
reset: Deselect the visual gadget specified in the destination frame field F.
When id=set or id=reset the visual gadget is NOT redrawn. The client module must request a redraw by broadcasting a DisplayMsg.
- time contains, on return, the time of the selection when id=get. This field is unaltered if no object is selected. It is therefore important to initialize it with a negative value (time:=-1).
- sel contains, on return, the ancestor of the selected object(s) when id=get.
- obj contains, on return, the selected object (or the first object of a list) when id=get.
(*-- Access a list of selected gadget(s) one by one. --*)
PROCEDURE EnumSelection;
VAR S: Display.SelectMsg; obj: Objects.Object;
BEGIN
S.id := Display.get; S.F := NIL; S.time := -1;
Display.Broadcast(S);
IF (S.time > 0) & (S.obj # NIL) THEN
obj := S.obj; (* Access the first object *)
WHILE obj # NIL DO
(*-- Process this object --*)
obj := obj.slink (* Access the next object *)
END
END
END EnumSelection;
Exercise:
1) Select any number of gadgets in your display space and interrogate them with
2) Deselect all gadgets and repeat the question.
(*-- Select a gadget --*)
PROCEDURE SelectGadget*;
VAR S: Display.SelectMsg; obj: Objects.Object;
BEGIN
obj := Gadgets.FindObj(Gadgets.context, "Test");
IF (obj # NIL) THEN
S.id := Display.set; S.F := obj(Display.Frame); S.obj := NIL; S.sel := NIL;
Display.Broadcast(S);
(* Use the information collected *)
Info(S.obj);
Info(S.sel);
(* Redraw the gadget *)
...
END
END SelectGadget;
Exercise:
1) Click the left button in this panel, to execute the command Examples.SelectGadget (uses id=set):
2) Display the information on that gadget named "Test" which has just been selected
3) Deselect that gadget again with
DEFINITION Display; (* excerpt only! *)
CONST
remove = 0; suspend = 1; restore = 2;
TYPE
ControlMsg = RECORD (FrameMsg)
id: INTEGER
END
END Display.
where:
- id is the message identifier which determines which action to perform in the display space. The following actions are defined:
remove: Remove the destination frame(s) from the display space. Only the frame is (or the frames are) removed from the display space. The object(s) still exist(s) and may be restored to the display space at the caret location using Gadgets.Integrate(obj) for example.
This is either to delete it or to insert it into a new parent.
suspend: Temporarily remove a frame from the destination downwards from the display space.
restore: Restore a frame from the destination downwards to the display space. Frames which may have missed messages while suspended will then update their internal data structures.
Model program for removing a gadget from the display space:
PROCEDURE RemoveGadget(obj: Objects.Object);
VAR C: Display.ControlMsg;
BEGIN
C.id := Display.remove; C.F := obj(Gadgets.Frame); C.res := -1;
Display.Broadcast(C)
END RemoveGadget;
Exercise:
1) Place the caret in a free area of your desktop (outside of this viewer!).
2) Insert a panel gadget at that point with
3) Select that gadget.
4) Remove it with
5) Again place the caret in a free area of your desktop.
6) Re-insert the gadget at the caret with
DEFINITION Display; (* excerpt only! *)
CONST
reduce = 0; extend = 1; move = 2;
display = 0; state = 1;
TYPE
ModifyMsg = RECORD (FrameMsg)
id: INTEGER; (* reduce, extend, move *)
mode: INTEGER; (* display, state *)
dX, dY, dW, dH: INTEGER;
X, Y, W, H: INTEGER
END
END Display.
where:
- id is the message identifier which determines which action to perform in the display space. The following actions are defined:
reduce or extend: Resize frame to the specified Width and Height.
move: Move the frame to the new coordinates X and Y.
- mode is set to state, the frame should not display itself immediately but only update its coordinates or size. This message must never be invalidated and the dX, dY, dW, dH change coordinates should always be set.
(*-- Resize a gadget to (W, H) --*)
PROCEDURE ResizeTo(F: Gadgets.Frame; W, H: INTEGER);
VAR M: Display.ModifyMsg;
BEGIN
M.id := Display.extend; (* or M.id := Display.reduce *)
M.mode := Display.display;
M.F := F;
M.X := F.X; M.Y := F.Y; (* Same coordinates *)
M.dX := 0; M.Y := 0;
M.W := W; M.H := H; (* New size *)
M.dW := W-F.W; M.dH := H-F.H;
Display.Broadcast(M)
END ResizeTo;
Exercise:
1) Place the caret in a free area of your desktop (outside of this viewer!).
2) Insert a panel gadget at that point with
2) Select that gadget.
3) Determine the gadget coordinates and size with
4) Resize the gadget with
If you copy these two Examples commands in an editable viewer, say Oberon.Log, you may exercise with other gadgets and sizes on your desktop. Adequate size values can be chosen using the values produced in step 3).
(*-- Move a gadget to (X, Y) --*)
PROCEDURE MoveToXY(F: Gadgets.Frame; X, Y: INTEGER);
VAR M: Display.ModifyMsg;
BEGIN
M.id := Display.move;
M.mode := Display.display;
M.F := F;
M.X := X; M.Y := Y; (* New coordinates *)
M.dX := X-F.X; M.dY := Y-F.Y;
M.W := F.W; M.H := F.H; (* Same size *)
M.dW := 0; M.dH := 0;
Display.Broadcast(M)
END MoveToXY;
Exercise:
1) Place the caret in a free area of your desktop (outside of this viewer!).
2) Insert a panel at that point with
2) Select that gadget.
3) Determine the gadget coordinates and size with
4) Copy this Examples.MoveGadget X Y command to an editable viewer, say Oberon.Log, and replace the X and Y by reasonable coordinates values infered from the data obtained in the previous step.
You may also exercise with other gadgets and coordinates values on your desktop.
DEFINITION Display; (* excerpt only! *)
TYPE
LocateMsg = RECORD (FrameMsg)
loc: Frame;
X, Y, u, v: INTEGER
END
END Display.
where:
- loc contains, on return, the frame containing the point at the absolute coordinates X, Y. If no frame is found it contains NIL.
- X, Y are the absolute coordinates of the point where the display space is interrogated.
- u, v contain, on return, the relative coordinates of that point in the frame (relative to the frame's top left corner).
(*-- Locate gadget at screen coordinates X, Y --*)
PROCEDURE Locate*;
VAR L: Display.LocateMsg; X, Y: INTEGER;
BEGIN
...
(* Set X and Y *)
L.X := X; L.Y := Y; L.res := -1; L.F := NIL; L.loc := NIL;
Display.Broadcast(L);
(* Use the information collected *)
Info(L.loc);
Out.String("u="); Out.Int(L.u, 5);
Out.String(" v="); Out.Int(L.v, 5); Out.Ln
...
END Locate;
Exercise:
1) Move the mouse pointer and place the star-shaped pointer within the limits of a frame on your desktop.
2) Determine which gadget is located at that point with
3) Repeat this with other gadgets and also with the pointer placed in a free area of your desktop.
DEFINITION Display; (* excerpt only! *)
CONST
frame = 0; area = 1;
TYPE
DisplayMsg = RECORD (FrameMsg)
id: INTEGER;
u, v, w, h: INTEGER
END
END Display.
where:
- id is the message identifier which determines which action to perform in the display space. The following actions are defined:
frame: Redraw the destination frame.
area: Redraw the area defined by u, v, w, h inside the destination frame.
- u, v, w, h define the relative coordinates u, v of an area of width h and of height w inside the destination frame, when id=area. These coordinates are determined relatively to the top left corner of the frame.
(*-- Select gadget --*)
PROCEDURE SelectGadget*;
VAR S: Display.SelectMsg; obj: Objects.Object; D: Display.DisplayMsg;
BEGIN
obj := Gadgets.FindObj(Gadgets.context, "Test");
IF (obj # NIL) THEN
(* Select the gadget *)
...
(* Redraw the gadget *)
D.id := Display.frame; D.F := obj(Display.Frame);
Display.Broadcast(D);
...
END
END SelectGadget;
In the gadget system of Oberon System 3 attribute management is strictly and exclusively handled by sending messages, that is gadget attributes are retrieved and changed individually or enumerated by sending a message of type AttrMsg (defined in the module Objects) directly to a gadget, by calling it's handler with:
obj.handle(obj, message)
Note that object attributes can also be visualized and edited using a universal editor name "Inspector".
DEFINITION Objects; (* excerpt only! *)
CONST
enum = 0; get = 1; set = 2;
Inval = 0; String = 2; Int = 3; Real = 4;
LongReal = 5; Char = 6; Bool = 7;
TYPE
AttrMsg = RECORD (ObjMsg)
id: INTEGER;
Enum: PROCEDURE (name: ARRAY OF CHAR);
name: Name;
res, class: INTEGER;
i: LONGINT; x: REAL;
y: LONGREAL; c: CHAR;
b: BOOLEAN; s: ARRAY 64 OF CHAR
END
END Objects.
where:
- id is the message identifier which determines which action to perform on the attribute. The following values are defined:
get: Retrieve the value of the attribute name.
set: Set the value of the attribute name.
enum: Enumerate all the attributes of an object. The procedure enum is called repeatedly, once for each attribute
- Enum is an enumerator procedure that must be supplied by the sender when id=enum.
- name is the name of the attribute. It must be specified when id=get or id=set.
- res reports about the result: < 0 message not handled, >= 0 message handled.
- class is the type of attribute. The predefined types are listed above: Inval, String, ...
- i, x, y, c, b, s: only just one of these fields is used to store the attribute value. The field used is the one matching the type specified by the class value. When id=get the attribute value is returned in one of the fields. When id=set the attribute value to assign must be stored in one of the fields before sending the message.
(*-- Retrieve a named attribute of an object --*)
PROCEDURE GetAttr(obj: Objects.Object; name: ARRAY OF CHAR);
VAR A: Objects.AttrMsg;
BEGIN
A.id := Objects.get;
COPY(name, A.name);
A.res := -1;
obj.handle(obj, A);
IF A.res >= 0 THEN (* Attribute exists *)
IF A.class = Objects.String THEN (* value A.s *)
ELSIF A.class = Objects.Int THEN (* value A.i *)
ELSIF A.class = Objects.Real THEN (* value A.x *)
ELSIF A.class = Objects.LongReal THEN(* value A.y *)
ELSIF A.class = Objects.Char THEN (* value A.c *)
ELSIF A.class = Objects.Bool THEN (* value A.b *)
ELSE
(* unknown class *)
END
ELSE (* Attribute does not exist *)
END
END GetAttr;
Exercise:
1) Select this object
2) Display that object's attributes with
3) Move the slider and repeat the query.
(*-- Store a LONGINT value in a named object --*)
PROCEDURE SetAttr(obj: Objects.Object; name: ARRAY OF CHAR; i: LONGINT);
VAR A: Objects.AttrMsg;
BEGIN
A.id := Objects.set;
COPY(name, A.name);
A.class := Objects.Int;
A.i := i;
A.res := -1;
obj.handle(obj, A)
END SetAttr;
VAR tmp: Objects.Object;
(*-- Retrieve a named attribute --*)
PROCEDURE RetrObjAttr(name: ARRAY OF CHAR);
VAR A: Objects.AttrMsg;
BEGIN
A.id := Objects.get;
COPY(name, A.name);
A.res := -1;
tmp.handle(tmp, A);
IF A.res >= 0 THEN (* Attribute exists *)
(* A.class as in GetAttr *)
ELSE (* Attribute does not exist *)
END
END RetrObjAttr;
PROCEDURE EnumAttr*(obj: Objects.Object);
VAR A: Objects.AttrMsg;
BEGIN
A.id := Objects.enum;
A.Enum := RetrObjAttr;
A.res := -1;
tmp := obj;
obj.handle(obj, A)
END EnumAttrs;
Procedures which are of special interest for programmers of new gadgets are explained in the Programming new Gadgets tutorial.
See how this is implemented in the example below.
(*-- Create a slider gadget and insert it at the caret position --*)
PROCEDURE InsertAtCaret*;
VAR obj: Objects.Object;
BEGIN
...
obj := Gadgets.CreateObject("BasicGadgets.NewSlider");
Gadgets.Integrate(obj)
END InsertAtCaret;
Exercise:
1) Place the caret in a free area of your desktop (outside of this viewer!).
2) Insert a slider gadget at that point with
(*-- Create a text field linked to an integer and insert it at the caret position --*)
PROCEDURE InsertPair*;
VAR F: Display.Frame; obj: Objects.Object;
BEGIN
F := Gadgets.CreateViewModel("TextFields.NewTextField",
"BasicGadgets.NewInteger");
Gadgets.Integrate(F);
(* Name the model "Volts" *)
Gadgets.NameObj(F(Gadgets.Frame).obj, "Volts");
(* Create a slider named "Slider" and link it to the integer --*)
obj := Gadgets.CreateObject("BasicGadgets.NewSlider");
Gadgets.Integrate(obj);
Gadgets.NameObj(obj, "Slider");
obj(Gadgets.Frame).obj := F(Gadgets.Frame).obj
END InsertPair;
Exercise:
1) Place the caret in a free area of your desktop (not in this viewer!).
2) Insert a slider gadget at that point with
See how this is implemented in the example above.
(*-- Display names assigned in previous example--*)
PROCEDURE ShowNames*;
VAR S: Display.SelectMsg; ObjName: ARRAY 64 OF CHAR;
BEGIN
.....
Out.String("Visual gadget name: ");
Gadgets.GetObjName(S.obj, ObjName);
Out.String(ObjName); Out.Ln;
(*-- This visual gadget may have no model --*)
Out.String("Model gadget name: ");
Gadgets.GetObjName(S.obj(Gadgets.Frame).obj, ObjName);
Out.String(ObjName); Out.Ln
END ShowNames;
Exercise 1:
1) Select the slider created before.
2) Prove that the names assigned are correct with
3) Deselect the slider and select the text field.
4) Execute
Exercise 2:
Repeat the exercise with other gadgets on your desktop (using
(* Find a named gadget in a specified context. *)
PROCEDURE FindObj*;
VAR obj: Objects.Object;
BEGIN
(* Note that if this command is executed from a gadget
the context is already set at execution time, before
reaching this point. *)
obj := Gadgets.FindObj(Gadgets.context, "Test");
IF (obj # NIL) & (obj IS BasicGadgets.Button) THEN
...
END
END FindObj;
Exercise:
1) Click the left button in this panel, to execute the command Examples.FindObj:
2) Read the new information in Oberon.Log and explain.
(*-- Locate gadget at screen coordinates X, Y --*)
PROCEDURE LocateP*;
VAR F: Display.Frame; X, Y: INTEGER; u, v: INTEGER;
BEGIN
....
(* Set X and Y *)
Gadgets.ThisFrame(X, Y, F, u, v);
(* Use the information collected *)
Info(F);
Out.String("u="); Out.Int(u, 5);
Out.String(" v="); Out.Int(v, 5); Out.Ln
....
END LocateP;
Exercise:
1) Move the mouse pointer and place the star-shaped pointer within the limits of a frame on your desktop.
2) Determine which gadget is located at that point with
3) Repeat this with other gadgets and also with the pointer placed in a free area of your desktop.
(*-- Look for an integer model gadget called "Test" in the current
context and increment its val field. The model is visualized by
a text field. --*)
(*-- This command must be executed in a given context. --*)
PROCEDURE Inc*;
VAR obj: Objects.Object;
BEGIN
obj := Gadgets.FindObj(Gadgets.context, "Test");
IF (obj # NIL) & (obj IS BasicGadgets.Integer) THEN
WITH obj: BasicGadgets.Integer DO
INC(obj.val);
BasicGadgets.SetValue(obj)
END
END
END Inc;
Exercise:
1) Click on the "Inc" button at the top right corner of the panel and watch the Counter text field (starts at 0) and the slider (starts at 50) changing value or position. The "Inc" button's "Cmd" field contains "Examples.Inc". Executing the command causes a direct increment of two gadget values and the two views are updated. No Objects.AttrMsg is required.
For an example see the statement (1) in the procedure below.
PROCEDURE OpenDoc(name: ARRAY OF CHAR);
VAR D: Documents.Document;
BEGIN
D := Documents.Open(name); (1)
IF D # NIL THEN
Desktops.ShowDoc(D) (2)
END
END OpenDoc;
Exercise:
1) Start the tutorial named "Tutorial User's Guide", a Book document, with
2) Close the tutorial.
The New procedures currently available are listed in a table appearing in a subsequent chapter.
The procedure "InsertDoc" below shows how a new text document is created (1) and how it is given a name (1a).
Reminder: the command Desktops.OpenDoc uses the same new procedures to create and present new documents in the display space.
The same result can be obtained under program control using standard procedures. Refer to the InsertDoc procedure above to see the three steps (1), (2) and (3) required. The following table summarizes what is needed for each document type.
The third step is to initialize the document with the appropriate gadget (3).
The system is also delivered with a number of special purpose documents.
Use the
The program behind this panel is rather simple:
MODULE Examples;
IMPORT Objects, Gadgets, BasicGadgets, Out;
PROCEDURE Add*;
VAR x, a, b: BasicGadgets.Real;
PROCEDURE GetReal(name: ARRAY OF CHAR): BasicGadgets.Real;
VAR obj: Objects.Object;
BEGIN
obj := Gadgets.FindObj(Gadgets.context, name);
IF (obj # NIL) & (obj IS BasicGadgets.Real) THEN
RETURN obj(BasicGadgets.Real)
ELSE
RETURN NIL
END
END GetReal;
BEGIN
(* 1. get the real gadgets *)
x := GetReal("xx");
a := GetReal("aa");
b := GetReal("bb");
IF (x = NIL) OR (a = NIL) OR (b = NIL) THEN
RETURN
END;
(* 2. solve the equation *)
IF Gadgets.executorObj(Gadgets.Frame).obj # x THEN
(* command executed from text field aa or bb *)
x.val := b.val -a.val
END;
(* 3. notify clients of model x that x.val has changed *)
BasicGadgets.SetValue(x)
END Add
END Examples.
The Examples.Add command can be divided into three parts:
(1) Retrieve the objects: Gadgets.FindObj(context, name) is used to find a named gadget in a context. Because the command is executed from a gadget, the context (the panel containing the text fields) is stored in the variable Gadgets.context.
(2) Solve the equation.
(3) Notify clients of the changed object: In the case of real gadgets this can be done by calling BasicGadgets.SetValue(obj) (alternatively Gadgets.Update(obj) could have been used).