DEFINITION MODULE DMMessages;
(*******************************************************************
Module DMMessages ('Dialog Machine' DM_V3.0)
Copyright (c) 1991-2006 by Andreas Fischlin and ETH Zurich.
Purpose Set of basic mechanisms to bring a short message
to the attention of the user.
Remarks The user is forced to aknowledge the message.
In some cases it might be even required, that the
user answers a question. Typically the running
client program asks the user for a clarification
or displays some information, warnings or even
error messages.
This module is functionally a replacement for the
following routines previously beeing part of the
Dialog Machine:
Old routine replaced by new routine
----------- -----------------------
DMAlerts.ShowAlert DMMessages.Inform
DMErrorMsgs.DispError DMMessages.DoInform
DMSubLaunch.Message DMMessages.Inform
DMQuestions.Ask DMMessages.Ask
DMSubLaunch.HaltMessage DMMessages.Abort
Note: For upward compatibility only DMAlerts from
above modules are still available. All other modules,
i.e. DMQuestions, DMSubLaunch, DMErrorMsgs, are now
removed from the Dialog Machine. DMMessages is now
part of the DM kernel.
This module belongs to the 'Dialog Machine'.
Programming
o Design
Andreas Fischlin 21/04/1991
o Implementation
Andreas Fischlin 29/04/1991
ETH Zurich
Systems Ecology
CHN E 35.1
Universitaetstrasse 16
8092 Zurich
SWITZERLAND
URLs:
<mailto:RAMSES@env.ethz.ch>
<http://www.sysecol.ethz.ch>
<http://www.sysecol.ethz.ch/SimSoftware/RAMSES>
Last revision of definition: 30/05/1991 AF
*******************************************************************)
(***************************************)
(*##### Core Message Routines #####*)
(***************************************)
CONST
LNBREAK = 15C; (* If encountered forces a message text to continue
on a new line (line break) *)
undefMsgNr = -1;
PROCEDURE Ask(question: ARRAY OF CHAR; butTexts: ARRAY OF CHAR;
butWidth: CARDINAL; VAR answer: INTEGER);
(*
Activates in the middle of the screen a question window with
a series of push buttons and requests from the user an
answer. The pushbuttons are placed in the lower portion of
the window starting with the default button as the leftmost,
and ending with the cancel button as the rightmost
push-button.
The question text, e.g. "Save changes?", is written at the top
of the window and will be wrapped onto as many lines as
needed. If questions contains the character LNBREAK = 15C (ASCII
Return), the current line will be terminated and subsequent
characters of the question are written to a new line.
Implementation restriction: Up to a maximum of 17 lines
(fits still onto any Mac screen) can be displayed.
String butTexts contains the labels of each button,
e.g. "Yes|No|Cancel", in the sequence the buttons will be
displayed from left to right. answer returns the answer
given by the user, i.e. the ordinal number of the button
pressed starting with 1 as the leftmost (default)
button.
The pushbuttons can also be pressed by means of keyboard
equivalents. If the user presses the key corresponding to
the first character of each button text, that button can also
be activated (case insensitive). Note that in case there are
several button texts starting with the same character, the
first (from left to right) button matching the key pressed
will be returned in the answer.
The first button (default button) can also be pushed via
the standard keyboard equivalents, i.e. by pressing the
return or enter key. Note that in case there is more than one
button present, that the standard cancel keyboard equivalents
(Command^'.', ESC) are always interpreted the same as
pressing the rightmost button. Hence, it is a good programing
practice to assign to butTexts actual values similar to
"...xyz...|Cancel".
*)
PROCEDURE DisplayBusyMessage( message: ARRAY OF CHAR );
(*
Displays a one line small message window showing the string
message. At a time there exists only one single busy message
window and the user can't move nor close the busy message
window. Typically the busy message window is used to inform
the user about an ogoing activity. This procedure can be
called repetitively, e.g. to change the displayed text while
the process is busy.
*)
PROCEDURE DiscardBusyMessage;
(*
Discards the busy message window. Call it once, after the
activity has been terminated.
*)
PROCEDURE Inform (paragraph1, paragraph2, paragraph3: ARRAY OF CHAR);
PROCEDURE DoInform (msgnr: INTEGER; modIdent, locDescr, insertions: ARRAY OF CHAR);
PROCEDURE Warn (paragraph1, paragraph2, paragraph3: ARRAY OF CHAR);
PROCEDURE DoWarn (msgnr: INTEGER; modIdent, locDescr, insertions: ARRAY OF CHAR);
PROCEDURE Abort (paragraph1, paragraph2, paragraph3: ARRAY OF CHAR);
PROCEDURE DoAbort (msgnr: INTEGER; modIdent, locDescr, insertions: ARRAY OF CHAR);
(*
By default any of these procedures display on the main screen
(Warn/DoWarn and Abort/DoAbort in the middle of the screen) a
message window with some text in them. In each case the user
is forced to acknowledge the message before the program may
resume execution.
The three pairs differ slightly in their behavior. They are
designed to be called for particular purposes depending whether
just a message shall be displayed to the user or whether a
minor or serious error has been encounterd. This is described
below under section "Basic Functioning".
The two procedures of each pair differ only in the method of
message text retrieval respectively construction. The
procedures Inform, Warn, and Abort use a simple method, where
any message text has to be fully passed at call time; the
procedures DoInform, DoWarn, and DoAbort are more luxurious and
offer a more sophisticated method of message text retrieval and
construction as described below under section "Methods of
message retrieval and construction".
*)
(****************************************************************
On Inform/DoInform, Warn/DoWarn, and Abort/DoAbort
==================================================
Basic Functioning:
*****************
Independent of the message retrieval respectively construction
method each of the three procedures differ in their meanings,
i.e. they are to be called under different conditions and for
different purposes. The main difference is in the number of push
buttons available to the user for aknowledging the message.
Depending on the actual push button the user clicks, subsequent
program action differs according to the following table:
Procedure pair Button(s) Meaning
-------------- --------- -------
o Inform/DoInform "OK" Resume program execution
Typical Use: Some particular information has to be brought
to the user's attention. Hence, the user must
click the button or press the return key to the
message before the program resumes further
execution.
o Warn/DoWarn 'Debug' Calls the debugger
"Continue" Resume program execution
(Default)
'Abort' Terminates (sub)program
execution
Typical Use: A non-fatal error has been encountered, still
allowing for further program execution, however
the user must be warned and informed on the
circumstances. For instance the user is warned
that the system is low on memory.
o Abort/DoAbort 'Debug' Calls the debugger
"Abort" Terminates (sub)program
execution (Default)
Typical Use: A fatal error has been encountered, and any
further program execution is unsafe. E.g. a
floating-point division by a small number has
lead to an overflow and thus making any future
computations with the resulting infinite real
operand impossible.
Any push button can be answered via keyboard equivalents:
default button Return, Enter, 1st char of label (case insensitive)
Debug 'D', 'd'
Abort 'A', 'a', Command^'.', Escape
Continue 'C', 'c'
Methods of message retrieval and construction:
*********************************************
1st Method: Inform, Warn, and Abort
------------------------------------
The procedures Inform, Warn, and Abort adopt the simple
method of text production only from the actual string
parameters. Each string corresponds to a paragraph of text,
which will be automatically wrapped onto as many lines as
needed. Calling Inform as follows:
Inform("Low on memory!","Please close some windows.","Thanks.");
results in a display similar to the following message
window:
------------------------------------
I I
I Low on memory! I
I Please close some windows. I
I Thanks. I
I I
I ========== I
I || OK || I
I ========== I
------------------------------------
A second example: Calling Abort as follows:
Abort("Fatal error occurred:"," ","Division by zero!");
results in a display similar to the following message
window:
---------------------------------------------
I ***** Halt in module "Modulexyz" I
I ******* Program counter at offset xyzH I
I ***** I
I I
I Fatal error occurred: I
I I
I Division by zero! I
I I
I ----------- ============ I
I | Debug | Execution || Abort || I
I ----------- stopped! ============ I
---------------------------------------------
2nd Method: DoInform, DoWarn, and DoAbort
-----------------------------------------------
The more elaborate method adopted by the procedures
DoInform, DoWarn, and DoAbort functions by default in
the way described below.
To explain these mechanisms, a typical example shall be
introduced: Let us assume that the user of a program is asked
to enter a number x, which ought to stay within a given
subrange, say [xmin..xmax] = [-1..+1] when used by procedure
'DoIt' from module 'MyModule'. The user now enters for the
number x a value, say 2. Then the procedure 'DoIt' is called,
which detects that x is out of range. The user should now be
informed about the situation and to achieve the desired effect
procedure DoInform shall be called with the following actual
arguments:
DoInform(100,"MyModule",'in procedure "DoIt"',"2|-1|+1");
The following steps will then be performed by procedure DoInform:
1) First a header message text common to all messages is
used and the identifiers of the module 'MyModule' and
the description of the error location as
"in procedure 'DoIt'" are inserted within this header.
The begin of the message will then look similar to:
--------------------------------------------------------
I Error in module "MyModule" in procedure "DoIt": I
2) Second the actual message text from a central, globally
accessible storage (by default this is module DMLanguage)
is retrieved via the number msgnr = 100. Say the message
text with msgnr=100 for the out of range error is:
100 : Number Δ is out of range [ Δ .. Δ ]! Please correct.
3) Third the retrieved text, which is not yet very meaningful,
is filtered, i.e. every character 'Δ' (306C), called a
place-holder, is replaced by one of the substrings
contained in the parameter 'insertions'. In our example
the actual value of this parameter is "2|-1|+1". This
means that the first occurence of 'Δ' (306C) in the
original string as retrieved in the preceeding step is
to be replaced with the first substring, i.e. '2', the
second with '-1', and the third with '+1'. After this
substitution the central section of the message looks
similar to:
I I
I Number 2 is out of range [ -1 .. +1 ]! Please I
I correct. I
I I
4) Fourth the message is displayed and depending on the particular
call, i.e. DoInform, DoWarn, or DoAbort, push buttons are
added (DoInform just adds on 'OK'-button). The resulting
message window similar to the one below will then be
brought to the user's attention:
--------------------------------------------------------
I Error in module "MyModule" in procedure "DoIt": I
I I
I Number 2 is out of range [ -1 .. +1 ]! Please I
I correct. I
I ========== I
I || OK || I
I ========== I
--------------------------------------------------------
Hints on how to customize messages:
**********************************
NOTE: The following information may be useful to understand
some more details of the described steps or to customize the
behavior.
to step 1) o In the procedures Inform resp. DoInform it is
possible to suppress any header. To achieve
this set both parameters 'modIdent' and 'locDescr'
to the empty string. Furthermore, in case
'modIdent' is the empty string, no header text
such as "Error in module..." is displayed;
instead just the text contained in 'locDescr' is
used as the header.
In the frst case ('modIdent' and 'locDescr'
both not empty) the header text can be
interpreted as the following string:
'Error in module "Δ" Δ:'
In the second case ('modIdent' empty, but 'locDescr'
not empty) the header text can be
interpreted as the following string:
'Δ:'
where 'modIdent' is substituted in place of the
first, and 'locDescr' in place of the second
placeholder.
All other procedures, i.e. Warn, DoWarn, Abort,
DoAbort, will always show a header. If both
parameters 'modIdent' and 'locDescr' are the
empty string, only automatic generation of the
header will take place. In this case the
identifier of the module in which execution is
halted is shown together with the current value
of the program counter as an offset (hexadecimal)
within the module code. An automatically
generated header looks similar to the following:
Halt in module "Modulexyz"
Program counter at offset xyzH
You may override the automatic generation of the
module ident by using an actual value of 'modIdent'
different from the empty string. Moreover,
optionally you may describe the halt location by
'locDescr'. The latter string is added after the
module ident. E.g. the following call to DoAbort
DoAbort(100,"MyModule (V0.2)",'in proc "DoIt"', "2|-1|+1");
results in the following message window:
---------------------------------------------
I ***** Halt in module "MyModule I
I ******* (V0.2)" in proc "DoIt" I
I ***** Program counter at offset xyzH I
I I
I Number 2 is out of range [ -1 .. +1 ]! |
| Please correct. I
I I
I ----------- ============ I
I | Debug | Execution || Abort || I
I ----------- stopped! ============ I
---------------------------------------------
The header text can be interpreted as the following
string:
'Halt in module "Δ" Δ
Program counter at offset ΔH'
where 'modIdent' is substituted in place of the
first, 'locDescr' in place of the second, and the
always automatically generated program counter offset
in place of the third placeholder.
to step 2) o The actual mechanism of message construction
is determined by the current settings, which may
be modified by the programmer by calling
SetMsgRetrieveProc. By default message texts are
retrieved by DMLanguage.GetMsgString. If the latter
fails to find a text associated with the msgnr, a
particular resource string (see DMStrings.GetRString)
is searched. It is also possible to use
DMMsgFile.GetMessage to retrieve the messages
from a text file containing the message texts.
Excerpt from a sample message file:
...
...
100 : Number Δ is out of range [ Δ .. Δ ]! Please correct.
101 : Out of memory. Requested block of size Δ too large.
...
to step 3) o Note that the placeholder replacement mechanism
is more important than it may first appear. In
our example the information on the value actually
entered by the user, as well as the information
on the currently legal range may be quite
crucial. It serves not only the convenience of
the user, but is also needed for any subsequent
correction of x. Since the user runs an
interactive program he or she needs to receive
some hints on currently legal ranges. Otherwise
he or she might be unable to enter a correct
value for x and hence may be unable to exit an
endless program loop.
o The substrings used in the parameter insertions
are separated by the delimiter "|" (vertical bar)
according to the following EBNF-Syntax:
insertions = substring {"|" substring }.
substring = { ' any char except "|" ' }.
The substrings are inserted in sequence of
their occurrence in insertions at the positions
denoted by the place holders 'Δ' (=306C) as
found in the original string from left to
right.
to step 4) o Running a program, e.g. a long simulation run,
on a machine in background, may lead to
undesirable program interruption, if a message
producing procedure is called inadvertently. To
have full control over the message production, a
mechanism to globally divert all messages to, for
instance a journaling file instead of the
screen, is provided (see below procedure
UseForMsgJournaling).
This divertion is available to all exported
message procedures regardless of their message
text retrieval and construction method. Moreover
it is possible to control this behavior
individually for each class of errors, i.e. asking,
informations, warnings, or abortions (see below
procedure SetMsgDevice).
Example: For instance in the situation in which
a computer runs unobserved simulations by solving
differential equations during several days:
Typically simple information messages are to be
recorded on the journaling file and program
execution may continue always. In the second
class, i.e. warnings, the messages may be treated
similarly, i.e. recorded and then execution is
resumed. But if a fatal error is encountered the
user may wish to be able to call the debugger
whithout having to repeat the lengthy
computations when he/she returns to the machine.
Hence, just abortions should not be diverted but
still be displayed on the screen.
****************************************************************)
(**********************************************************
The following objects are only used to customize message
production and behavior
**********************************************************)
(*********************************************************)
(*##### Customization of message text retrieval #####*)
(*********************************************************)
TYPE
MsgRetrieveProc = PROCEDURE ( INTEGER , VAR ARRAY OF CHAR );
PROCEDURE SetMsgRetrieveProc(rp: MsgRetrieveProc);
PROCEDURE GetMsgRetrieveProc(VAR rp: MsgRetrieveProc);
(*
Sets or gets the current message text retrieving procedure.
The following procedures retrieving a string associated with a
particular number are readily available in the Dialog Machine
and can be set as the current method:
- DMLanguage.GetMsgString (if not found, calls DMStrings.GetRString)
- DMStrings.GetRString
- MsgFile.GetMessage
- MsgFile.GetNumberedMessage
Initial default: DMLanguage.GetMsgString
*)
(*************************************************)
(*##### Customization of message output #####*)
(*************************************************)
CONST
toScreen = 0; toJournalFile = 1;
TYPE
MsgDevice = [toScreen..toJournalFile];
MsgWriteProc = PROCEDURE ( CHAR );
MsgWriteLnProc = PROC;
PROCEDURE UseForMsgJournaling(wp: MsgWriteProc; wlnp: MsgWriteLnProc);
(*
Use subsequently for the journaling of messages the procedures
wp and wlnp. For instance journaling on a file can be
accomplished by installing for wp the following sample
procedure:
PROCEDURE MyWrite( ch: CHAR );
BEGIN
DMFiles.WriteChar(myJournalFile,ch);
END MyWrite;
*)
PROCEDURE SetMaxMsgs (max: INTEGER);
(*
Set the maximum of messages which are allowed to be
redirected. If a total of more than max messages, regardless
of the class, should have been redirected, then the redirection
mechanism will be reset to its default, i.e. messages will
appear on the screen again. This avoids the filling up of a hard
disk with a journaling file full of the same message in case of an
endless loop. Default: max = 1000 per program level.
*)
PROCEDURE SetMsgDevice (forAsk,forInform,forWarn,forAbort: MsgDevice);
PROCEDURE GetMsgDevice (VAR forAsk,forInform,forWarn,forAbort: MsgDevice);
(*
Activates respectively returns current journaling, i.e. message
redirection individually for any message class. A flag value of
toJournalFile starts, toScreen stops the journaling, i.e.
resets the message to the default display on the screen. In
the latter case the program always awaits user acknowledgement
before any possible program continuation. Journaling is done
by writing the message text by means of the procedures
currently set by procedure UseForMsgJournaling. Note that if
journaling is active for a particular message class, that no
answers will be awaited by the executing program; instead the
Dialog Machine assumes always that the default answer has been
given. forAsk starts or stops the journaling mechanism for the
procedure Ask respectively AskPredefinedQuestion, forInform
does it for the procedures Inform respectively DoInform,
forWarn does it for the procedures Warn resp. DoWarn, and
forAbort for the procedures Abort resp. DoAbort. Hint: In case
of an active journaling mechanism, make sure that the default
answer can never cause the program to repeat a message
endlessly, but always exits any eventually present loop.
*)
(**************************************************)
(*##### Asking a resource based question #####*)
(**************************************************)
PROCEDURE AskPredefinedQuestion(fileName: ARRAY OF CHAR; alertID: INTEGER;
str1,str2,str3,str4: ARRAY OF CHAR;
VAR answer: INTEGER);
(*
Activates a predefined question window read from a Macintosh
resource of type "ALRT" with ID = alertID contained in the
resource fork of the file fileName. In case that the
fileName passed is empty, the default search strategy to find
the resource is followed. Up to four strings (str1, str2,
str3, str4) may be specified in order to insert them at the
place of the placeholders within the predefined text. In
order to perform correctly, the resource must contain as
first dialog item the default button (item 1) and as the
the second button the cancel button (item 2). (NOTE: this
behavior is different from the one of procedure Ask where the
cancel button must be the last, i.e. rightmost not the second
pushbutton).
*)
END DMMessages.