#
Using Gadgets from within Oberon

[ Text | Contents | Index | Master index]

# Objective

You have most probably read the tutorial Using Gadgets to learn how to create user interfaces interactively and to learn how to define simple behaviors for these interfaces. Having mastered this first and simplest step in using the Oberon System 3 , we invite you to tackle the next and slightly more demanding step, namely that of placing the behavior of the user interface under program control. This entails writing fairly simple procedures in Oberon. To illustrate how to achieve that, code fragments of at most 20 lines are presented and explained in great details. You should regard these fragments as sound theoretical building elements susceptible of integration in workable procedures that you might fancy to develop. To help you to reach your goal even better and faster, all fragments have been implemented in the modules Examples.Mod which will be used throughout this tutorial for live demonstrations.

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 Edit.Print LPT1 Examples.Mod ~ on your local printer.

# How to control gadgets with commands

An important feature of the Oberon system is the capability to invoke commands from ordinary text. The Gadgets module exports a number of commands with which you can directly interrogate or change gadgets. These commands are described in the Gadgets module and in Attributes.Several gadgets, such as a button, have a Cmd attribute, which may contain a command. This command is executed when the gadget is activated (e.g. when a button is clicked). This device enables you to delegate the control of a gadget to yet another gadget.

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.

## Where to obtain information on the environment

The Gadgets module exports four variables Gadgets.context, Gadgets.executorObj, Gadgets.senderObj and Gadgets.receiverObj supplying the commands with the necessary information on the environment or context.

### Gadgets.context

This global variable is set when a gadget executes a command and contains the context or parent of the gadget executing the command. This value may then be used to retrieve gadgets (visual or model gadgets) in that context. Setting this value under program control changes the context.

Example: the procedure Gadgets.FindObj(context, name) may be used to look for a named gadget in a given context.

### Gadgets.executorObj

This global variable is set when a gadget executes a command and contains the gadget executing the command. This value is also stored in the global variable Oberon.Par.obj.

### Gadgets.senderObj

This variable only contains a valid value when a consume operation (drag and drop) is executed. It then contains the gadget being either consumed (e.g. a picture gadget consumed by a button) or dropped onto another gadget (e.g. a button onto a panel).

### Gadgets.receiverObj

This variable only contains a valid value when a consume operation (drag and drop) is executed. It then contains the gadget consuming another gadget (e.g. a button consuming a caption).

## Alternative environment information sources

The Oberon module exports the variables Oberon.Par.obj and Oberon.Par.frame supplying similar environment information to commands. Also the Objects module supplies an information.

### Oberon.Par.obj

This global variable is set when a gadget executes a command and contains the same value as Gadgets.executorObj.

### Oberon.Par.frame

This global variable is set when a gadget executes a command and contains (a pointer to) the outermost frame in which the command is executed, i.e. that of the desktop.

### Objects.NewObj

This global variable is set when a new object is created with either the procedure CreateObject or the procedure CreateViewModel. It then contains the new object. This variable allows other commands or procedures to process the new object, the simplest process being the insertion of the new gadget at the caret position in the display space.

# How to control gadgets with messages

The frame messages in the Display module derived from Display.FrameMsg play a central role in inter-frame communication. These build a communication protocol allowing frames to communicate with each other without knowing too much about each other. The latter is crucial if foreign or yet unknown objects are to be integrated into the system and applications need to exchange objects with each other. The FrameMsg is defined as follows: DEFINITION Display; (* excerpt only! *)
FrameMsg = RECORD (Objects.ObjMsg)
F: Frame; (* target frame *)
x, y, res: INTEGER
END
END Display.

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.

## Accessing gadgets - Display.SelectMsg

A client module can interrogate and control the display space by broadcasting a SelectMsg to the display space or to a given frame. A list of visual gadgets can be addressed on condition that they all have the same ancestor.

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 Examples.GetSelection (uses id=get).

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 Examples.GetSelection (uses id=get).

3) Deselect that gadget again with Examples.DeselectGadget (uses id=reset). If you attempted to deselect the button 'Test' with a right mouse key click, it would fail: the panel is locked!

## Removing gadgets - Display.ControlMsg

A client module can control the display space by broadcasting a ControlMsg to a given frame, specified in the destination frame field F of the base FrameMsg. A list of visual gadgets may be addressed on condition that they all have the same ancestor.

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 Gadgets.Insert Panels.NewPanel.
3) Select that gadget.
4) Remove it with Examples.RemoveSelection (uses id=remove).
5) Again place the caret in a free area of your desktop.
6) Re-insert the gadget at the caret with Examples.RestoreRemoved.

## Resizing / Moving a gadget - Display.ModifyMsg

A client module can resize or move a visual gadget in the display space by broadcasting a ModifyMsg to a given frame, specified in the destination frame field F of the base FrameMsg.

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 Gadgets.Insert Panels.NewPanel.
2) Select that gadget.
3) Determine the gadget coordinates and size with Examples.LocateGadget (uses SelectMsg).
4) Resize the gadget with Examples.Resize 120 45. The size values W=125 and H=45 are not editable in this viewer.

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 Gadgets.Insert Panels.NewPanel.
2) Select that gadget.
3) Determine the gadget coordinates and size with Examples.LocateGadget (uses SelectMsg).
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.

## Locating a frame - Display.LocateMsg

A client module can interrogate the display space by broadcasting a LocateMsg to the display space.

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 Examples.Locate and observe where the pointer is located relative to the top left corner of the frame. Explain.
3) Repeat this with other gadgets and also with the pointer placed in a free area of your desktop.

## (Re)drawing a gadget - Display.DisplayMsg

A client module can request the redrawing of gadgets by broadcasting a DisplayMsg.

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;

## Attribute management - Objects.AttrMsg

Each gadget has a set of attributes representing the state, configuration or behaviour of the gadget. An attribute is a (Name, Value) pair, where the value is of type boolean, integer, real, character or string. An attribute is used to store the name, the value, a command string, an editing characteristic or other information of a gadget. For instance an attribute named "Cmd" may contain an Oberon command (string) which is executed when the gadget is activated (clicking the middle mouse key).

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.

### Retrieving an attribute

Use id=Objects.get to retrieve an attribute value of a given object. The result is stored in one of the i, x, y, c, b, s fields.

(*-- 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 Examples.ShowValue.
3) Move the slider and repeat the query.

### Setting an attribute

Use id=Objects.set to set an attribute value.

(*-- 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;

### Enumerating the attributes

Use id=Objects.enum to retrieve all attribute values.

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;

# How to control gadgets with procedures

Preliminary remark: The command procedures exported by the Gadgets module are explained in the Using Gadgets tutorial.The gadgets module exports a whole palette of procedures, only some of which are described and commented here. Among these, some are provided to simplify programming only, that is the same functionality can be obtained using by sending some of the messages described in the previous chapter. Notes scattered in the text will draw your attention on these cases.

Procedures which are of special interest for programmers of new gadgets are explained in the Programming new Gadgets tutorial.

## Creating an object - CreateObject

Gadgets.CreateObject(newproc: ARRAY OF CHAR): Objects.Object returns a new object from a new procedure. The newly created object is stored in the global variable NewObj in the Objects module and it can be referenced by Objects.NewObj. Note that the new gadget instance, a visual gadget or a model gadget, exists only IN the system. You can let a visual gadget appear in the display space by calling the Gadgets.Integrate procedure described below.

See how this is implemented in the example below.

## Inserting a gadget at the caret location - Integrate

Gadgets.Integrate(obj: Objects.Object) integrates (i.e. inserts) the object O in the display space at the caret position.

(*-- 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 Examples.InsertAtCaret.

## Creating a view/model pair - CreateViewModel

Gadgets.CreateViewModel(viewnewproc, modelnewproc: ARRAY OF CHAR): Display.Frame creates a view/model gadget pair, i.e. a visual gadget associated to a model gadget, for example a text field and a string. The newly created visual gadget is stored in the global variable Objects.NewObj and the newly created model gadget can be referenced by Objects.NewObj(Gadgets.Frame).obj.

(*-- 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 Examples.InsertPair.

## Naming an object - NameObj

Gadgets.NameObj(obj: Objects.Object; name: ARRAY OF CHAR) changes the name of any object (Note: ANY object, i.e. needs not to be a gadget at all).

See how this is implemented in the example above.

## Retrieving an object's name - GetObjName

Gadgets.GetObjName(obj: Objects.Object; VAR name: ARRAY OF CHAR) retrieves the name of any object (Note: ANY object, i.e. needs not to be a gadget at all).

(*-- 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 Examples.ShowNames.
3) Deselect the slider and select the text field.
4) Execute Examples.ShowNames again and explain the result.

Exercise 2:

Repeat the exercise with other gadgets on your desktop (using Examples.Shownames) and note that some have no model gadget. Explain..

## Retrieving a public object - FindPublicObj

Gadgets.FindPublicObj(name: ARRAY OF CHAR): Objects.Object searches for a public object, the name of which is specified as "L.O". Where L is the name of a public library (e.g. Icons.Lib, a PublicPanel, ...) and O the name of the object. If no object of the given name exists NIL is returned.

## Retrieving an object - FindObj

Gadgets.FindObj(context: Objects.Object; name: ARRAY OF CHAR): Objects.Object searches for a named object in the specified context. If no object of the given name exists NIL is returned.

(* 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.

## Updating a gadget - Update

Gadgets.Update(obj: Objects.Object) if obj is a frame, a Display.DisplayMsg is broadcasted, else a Gadgets.UpdateMsg is broadcasted. This is needed if the data of a visual or model gadget is changed directly.

## Retrieving a frame - ThisFrame

Gadgets.ThisFrame(X, Y: INTEGER; VAR F: Display.Frame; VAR u, v: INTEGER) retrieves the frame F located at the absolute coordinates X, Y in the display space. u, v contain the relative coordinates of that point in the frame. These coordinates are determined relatively to the top left corner of the frame.

(*-- 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 Examples.LocateP and observe where the pointer is located relative to the top left corner of the frame. Explain.
3) Repeat this with other gadgets and also with the pointer placed in a free area of your desktop.

## Executing a command - Execute

Gadgets.Execute(cmd: ARRAY OF CHAR; executor, dlink, sender, receiver: Objects.Object) executes the command string cmd. Sender and receiver only have a value for consume operations. Dlink is copied to Gadgets.context; Oberon.Par.obj is set to the executor, and Oberon.Par.frame is set to the outermost frame in which the command is executed.

## Executing a command - ExecuteAttr

Gadgets.ExecuteAttr(F: Frame; attr: ARRAY OF CHAR; dlink, sender, receiver: Objects.Object) searches for a certain attribute attr of F to be executed as command.

## Changing a gadget value directly - SetValue

BasicGadgets.SetValue(obj: Objects.Object) changes the "Value" attribute of a gadget of a type defined in the BasicGadgets module, after a direct change of its "val" field. Visual gadgets are automatically updated.

(*-- 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.

## Comparison between commands and procedures

The Gadgets module provides a number of commands which may be executed by the user or by gadgets to control gadgets and the display space. The same actions may be performed under program control using procedures (middle column). Objects and gadgets attributes can also be retrieved or changed by sending messages, defined in the Objects module, directly to objects. The following table summarizes that:

# How to control documents

You may write your own procedures and command procedures to control the presentation of new or existing documents. A number of standard procedures will help you in doing that.

## Loading an existing document - Documents.Open

Documents.Open(name: ARRAY OF CHAR): Document loads a named document from disk and returns the document. If a file of that name is not found NIL is returned. Note that the external document is internalized but not presented in the display space. To present it to the user you must use the next procedure.

For an example see the statement (1) in the procedure below.

## Presenting a document - Desktops.ShowDoc

Desktops.ShowDoc(D: Documents.Document) opens a document in the desktop or in a viewer depending on the context.

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 Examples.OpenDoc which loads the document "Tutorials.Book" (1) and presents it (2).
2) Close the tutorial.

## Creating a new document

Included in this release you will find several document types. With each document type a so-called document New procedure is associated. Calling this procedure causes (a new instance of) a document of the associated type to be created. The name of the New procedure is also recorded in the document itself. In this manner, a filed document can be recreated later on.

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.

## Initializing a document - Documents.Init

Documents.Init(F: Document, main: Gadgets.Frame) initializes a document with a 'container' gadget.
(*-- Insert a new text document in the display space --*)
(*-- and give it a name, e.g. "MyDoc.Text". --*)
PROCEDURE InsertDoc(name: ARRAY OF CHAR);
VAR D: Documents.Document;
obj: Objects.Object;
BEGIN
TextDocs.NewDoc; (1)
D := Objects.NewObj(Documents.Document);
COPY(name, D.name); (1a)
obj := Gadgets.CreateObject("TextGadgets.New"); (2)
Documents.Init(D, obj(Gadgets.Frame); (3)
Desktops.ShowDoc(D, FALSE)
END InsertDoc;

## Overview of commands and procedures

When executed by the user or by a gadget, Gadgets.Insert causes a new visual (container) gadget to be presented in the display space whereas Desktops.OpenDoc causes a document gadget to be presented. The New procedure selected determines the gadget type or document type respectively.

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.

# A final example

As a final example we have a panel witch simply adds two real numbers. Entering a number in any of the three textfields will keep the equation correct.

Use the Inspector to see the attributes of selected gadgets. Note that the text fields have real model gadgets linked to them and a command attribute set to "Examples.Add".

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).

# What's next?

If you had liked to develop your own gadgets learn all about it in Programming new Gadgets.

Revised, afi 03 Mar 1995
Installed on 14 Feb 1997