Skip to main content
? Knighthawks

FOOLING AROUND WITH WINDOWS - A GFA STORY   By Stefan Posthuma

First of all,  I want to wish all readers of ST NEWS a very happy 
newyear in this first issue of ST NEWS in 1988.  May your ST  run 
programs that you never dreamed of running!

When I first played with an Atari ST,  I was really impressed  by 
the windows.  I was used to the simple prompt presented to me  by 
my  good old Commodore 64 (whenever that name comes up,  I  think 
back of the good old days of programming with one-line assemblers 
and  playing the most crazy videogames).  I loved resizing  them, 
moving  them,  playing  with the scroll bars  and  watching  them 
redraw  themselves.  I never really thought about how  it  really 
worked. They just were there.
Then I started programming the ST.  First in GfA,  now in C. When 
programming  in  C,   GEM  is  inevitable.  I  just  finished  an 
experiment  in C which involved full window handling,  and I  now 
want to explain to you how to use windows and stuff in GfA.
The problem with GfA is that there is not a ready-to-use  library 
with all routines present. You have to do all of it yourself with 
your own routines,  POKEing into Intin,  Intout ect.  and calling 
GEMSYS a lot.
I  assume that you have a good working knowledge of GfA  and  are 
familiar with terms like pointers and local and global variables. 
If you are not,  you can still use the routines,  but it will  be 
very difficult to understand.

Fortunes.....
If you are reading this, you are probably a programmer. I work on 
an  UNIX  system  which has a  program  called  'fortunes'  which 
creates  all  sorts  of  funny,  philosophical  and  other  weird 
messages. This is one of them:

A programmer is a person who passes as an exacting expert on  the 
basis of being able to turn out, after innuberable iterations, an 
infinite  series  of  incomprehensive  answers  calculated   with 
micrometric precisions from vague assumptions based on  debatable 
figures  taken  from inconclusive documents and  carried  out  on 
instruments  of  problematical  accuracy by  persons  of  dubious 
reliability and questionable mentality for the avowed purpose  of 
annoying and confounding a hopelessly defenseless department that 
was  unfortunate enough to ask for the information in  the  first 
place.

But let's get to the programming.

Throughout this article,  I will try to use all the official GEM-
names for the routines. But GfA has some nice built-in statements 
for event handling (the On Menu statements) so I will use them as 
well.
When we start a 'real' GEM program, the first thing we have to do 
is to restore the GEM-desktop that was deleted by GfA.  You might 
have encountered this desktop struggling to get out whenever  you 
worked with a menu-bar and called an accessory.  When you  close 
this accessory,  an irritating grey spot will appear on the  spot 
which the accessory covered.  This is GEM redrawing the  desktop. 
You really cannot do anything against this.
The statements to redraw the entire desktop are:

@Wind_get(0,4,*X,*Y,*W,*H)
@Form_dial(3,X,Y,W,H,X,Y,W,H)

Now this aren't real statements,  they are routines that are part 
of the GEM-library that will be built-up during this article.
The  first routine is a routine that allows you to get all  sorts 
of information about the windows to be found on the desktop.
'Windows?'  you  might wonder.  That's right,  there  aren't  any 
windows open, but GEM treats the desktop as a window with window-
handle 0.  Other windows get other handles (1..8) There are  four 
user definable windows and four windows reserved for accessories.
The first parameter of wind_get is the window handle.
The second parameter is the type of information you want to get.
The  four  other  parameters  are  coordinates  returned  by  the 
routine.

The second parameter can have the following values:
4  = WF_WORKXYWH -  Returns  the coordinates of the window  work
                    space without sliders, titles ect.
5  = WF_CURRXYWH -  Returns  the full coordinates of  the  window 
                    including sliders, titles ect.
6  = WF_PREVXYWH -  Returns the previous size of the window.
7  = WF_FULLXYWH -  Returns the  maximum  coordinates  of  the 
                    window.
8  = WF_HSLIDE   -  Returns    the  relative  position   of   the 
                    horizontal slider. 1 = left, 1000 = right
9  = WF_VSLIDE   -  Idem for the vertical slider.
10 = WF_TOP      -  Returns  the  handle of  the  top  (=active) 
                    window. First parameter whould be 0
11 = WF_FIRSTXYW -  The coordinates of the first retangle in  the 
                    retangle list are returned.
12 = WF_NEXTXYWH -  The  coordinates of the next retangle in  the 
                    retangle list are returned
15 = WF_HSLSIZE  -  The  relative size of the  horizontal  slider 
                    will be returned. 
                    -1 : minimum size (as big as window itself)
                    1-1000 relative size to window size
16 = WF_VSLSIZE  -  Idem for the vertical slider.
This  routine  and all other routines can be found  in  the  GfA-
source 'mywindow.bas' in the programs folder on the ST NEWS disk.

The  form_dial  routine is less complex and  has  four  functions 
determined by the first parameter:
0 :  mark the screen area defined by the next four parameters  as 
     'dirty'  GEM will redraw this part of the screen when it  is 
     released  with  another form_dial call with 4 as  the  first 
     parameter.
1 :  draw  an expaning box.  The next four parameters define  the 
     small box and the last four determine the large box.
2 :  draw a shrinking box
3 :  release a part of the screen so GEM will redraw that part.

In this case,  we first determined the coordinates of the desktop 
and  after  that we marked that entire part of the screen  to  be 
redrawn. It was already 'dirty' because GfA deleted the desktop.
Getting  coordinates with wind_get has the advantage that you  do 
not have to worry about screen-resolution.  And maybe, just maybe 
if  you  port your program to another computer  which  also  runs 
under  GEM and has different screen dimensions,  everything  will 
still be OK.
Phew! This is a lot of explaining for two lines of GfA text! If I 
go on this way, this will be a very big article.
Here is another fortune for you:

Keep grandma off the streets; legalize bingo

Next  thing we have to do is to set up a menu-bar and define  the 
event  routines.  The  routine that handles  menu-events  can  be 
specified with the statement:

On Menu Gosub Menu_chosen

The routine that handles other messages like window redraws  ect. 
can be specified with:

On Menu Gosub Gem_message

Now  we  enter a loop which contains the On  Menu  statement  and 
waits  for the user to do something like selecting the  menu-item 
to  open  a  window.   Now  we  can  de  some  real  GEM-program
ming...opening a window!

First,  we have to determine which attributes we want our  window 
to have.  Attributes are things like scrollbars,  title and  info 
bars  ect.  All these attributes are bits in a word that  can  be 
ORed together. The following values are possible:
1    - NAME    title bar with window name
2    - CLOSER  close-box
4    - FULLER  fuller
8    - MOVER   move-box
16   - INFO    info-bar under the title bar
32   - SIZER   size-box allows user to resize the window
64   - UPARROW    
128  - DNARROW
256  - VSLIDE  vertical slider
512  - LFARROW arrow-left
1024 - RTARROW arrow-right
2048 - HSLIDE  horizontal slider

So if you want a window with a name,  a close-box and a  vertical 
slider, you will have a statement like this:
Attr%=1 Or 2 Or 256

After  that,  we want to determine the size of the window  to  be 
opened.  We  can use the wind_get call for this  again.  We  just 
determine the size of the entire desktop. You can do other things 
like dividing it by two or subtracting sizes of other windows  to 
get the size you want.

@Wind_get(0,4,*X%,*Y%,*W%,*H%)

After that,  we create a window. With this call, GEM will reserve 
space for the window, and do other housekeeping to accomodate the 
window and will return a window handle in Gintout.  A value of -1 
means  that something went wrong (GEM is out of  windows,  memory 
fault  etc.)  and you cannot open another  window.  The  call  to 
create a window is:

@Wind_create(Attr%,X%,Y%,W%,H%)
W_handle%=Dpeek(Gintout)

If you want to give your window a title,  you can do this with  a 
wind_set call. With wind_set, you can set all sorts of attributes 
of a window.

When  setting  the  name of a  window,  one  important  thing  to 
remember  is  that  the variable holding the  name  should  be  a 
global;  GEM only stores the pointer and the second thing is that 
GEM in mainly written in C and strings in C are terminated with a 
null-character so you should append a Chr$(0) to your windowname.

@Wind_set(W_handle%,2,Varptr(W_name$),0,0,0)

This  windset  routine  is  somewhat  similar  to  the   wind_get 
routine,  but in this case you set the window attributes  instead 
of retrieving them.  Again,  the second parameter determines what 
is set and the next four usually contain coordinates, but in this 
case  only  the first of them is used to pass a  pointer  to  the 
window  name.  The other three are unused,  but should be  passed 
anyway, so just pass zeroes.

The second parameter can have the following values:

1  - WF_KIND        Changes the window-attributes like scrollbars 
                    etc. as defined in the wind_set call.
2  - WF_NAME        Sets the window name.
3  - WF_INFO        Sets the window info bar under the name.
5  - WF_CURRXYWH    Set the window coordinates
8  - WF_HSLIDE      Sets the relative position of the  horizontal 
                    slider.
9  - WF_VLSIDE      Idem for the vertical slider.
10 - WF_TOP         The  window  is  laid on  top  of  all  other 
                    windows thus making it active.
14 - WM_NEWDESK     The first parameter should be  zero,  because 
                    with  this  call,   you  can  install  a  new 
                    desktop.  The  third  parameter should  be  a 
                    pointer  to the tree-structure of the  object 
                    that  defines the new desktop and the  fourth 
                    parameter  should be the index of  the  first 
                    object to be drawn.
15 - WF_HSLSIZE     Set  the  relative  size  of  the  horizontal 
                    slider.
16 - WF_VSLSIZE     Idem for the vertical slider.
Now we can open the actual window with a wind_open call:

@Wind_open(W_handle%,X%,Y%,W%,H%)

If  all's correct,  there should be a neat window on our  desktop 
ready to be resized, redrawn, closed, fulled or whatever the all-
mighty  user wants to do with it in his limitless superiority  to 
me,  a humble computer willing to satisfy the needs of any  Great 
User! 
(sorry for this,  I got a bit carried away when my stereo started 
to  play  the fantastic 'War of the Worlds' tune by  Jeff  Wayne. 
Thanks Richard!)

Time for another fortune:

Sex is like a bridge game, if you have a good hand, no partner is 
needed.

Now  we  sould  start  our event loop that  just  waits  for  the 
guy/girl   with  the  mouse  in  his/her  hand  to  do  something 
interesting. It will somewhat look like this:

Do
   On Menu
Loop

Suppose  you  have called On Menu Message  routine  'Gem_message' 
with the statement:

On Menu Message Gosub Gem_message

Each  time  the  user does something like resizing  a  window  or 
closing  it,  or anything else happens regarding windows  on  the 
screen,  GEM will pass a message to your program. This message is 
caught  by  the  On  Menu  statement  and  GfA  will  enter   the 
Gem_message  routine.  The  global array Menu()  will  be  filled 
with  all kinds of interesting values concerning  window-handles, 
coordinates ect.  You need not define this array,  because it  is 
always there.

The  first element of the Menu array contains the  event  number, 
and  the  fourth  element  contains  the  handle  of  the  window 
involved. The event number can be one of the following:

20 WM_REDRAW   Parts  of  the  window  should  be  redrawn.  This 
               because  these parts were revealed  after  another 
               window was moved, or when the handling of a dialog 
               box  has ended and the underlying surfaces  should 
               be restored. More on this later.
21 WM_TOPPED   The window was topped by the user.
22 WM_CLOSED   The window was closed.
23 WM_FULLED   The window was fulled.  It should be full-sized or 
               returned  to its previous size if it  already  was 
               full-sized.  A routine will be presented for  this 
               later.
24 WM_ARROWED  One of the arrows of the sliders was selected.
25 WM_HSLID    The horizontal slider was moved.
26 WM_VSLID    The vertical slider was moved.
27 WM_SIZED    The window was resized.
28 WM_MOVED    The window was moved.
29 WM_NEWTOP   The window was topped. The difference between this 
               and number 21, WM_TOPPED is not clear to me.
The  WM_ARROWED message returnes the kind of action in the  fifth 
element of the Menu() array:

0 - Page up
1 - Page down
2 - Line up
3 - Line down
4 - Page left
5 - Page right
6 - Character left
7 - Character right

Before  we  start  topping,  moving or doing  other  things  with 
windows,  we should tell GEM about it.  After a Wind_update call, 
GEM  will know that we are going to mess around and it will  lock 
the  menubar  and  other active  objects  on  the  desktop.  This 
prevents  drop-down menus being dropped over parts of the  screen 
that are being redrawn.

@Wind_update(1)

A wind_update(0) call will tell GEM that we are ready.
The Gem-message routine might look like this:

Procedure Gem_message
   Local Event%          ! good thing to keep variables local
   Hidem                 ! hide mouse
   @Wind_update(1)       ! inform GEM about things
   Event%=Menu(1)        ! get event number
   If Event%=20          ! WM_REDRAW?
      @Redraw(Menu(4),Menu(5),Menu(6),Menu(7),Menu(8))
   Endif
   If Event%=21          ! WM_TOPPED?
      @Wind_set(Menu(4),10,0,0,0,0) 
   Endif
   If Event%=22          ! WM_CLOSED?
      @Close_window(Menu(4))
   Endif
   If Event%=23          ! WM_FULLED?
      @Fulled(Menu(4))
   Endif
   If Event%=24          ! WM_ARROWED?
      @Arrowed(Menu(4),Menu(5))
   Endif
   If Event%=25          ! WM_HSLID?
      @Hslid(Menu(4),Menu(5))
   Endif
   If Event%=26          ! WM_VSLID?
      @Vslid(Menu(4),Menu(5))
   Endif
   If Event%=27 Or Event%=28 ! WM_SIZED? or WM_MOVED?
      @Wind_set(Menu(4),5,Menu(5),Menu(6),Menu(7),Menu(8))
   Endif
   If Event%=30        ! WM_NEWTOP? if another window is closed
      @Wind_set(Menu(4),10,0,0,0,0)
   Endif
   @Wind_update(0)     ! tell GEW we are ready
   Showm               ! get mouse back
Return

Some of the routines called are GEM-calls like wind_set but there 
are six routines that must be explained:
Redraw, Close_window, Fulled, Arrowed, Hslid and Vslid.

The Redraw routine

As  mentioned above,  window redrawing must occur when part of  a 
window  are revealed when another window that laid on top  of  it 
has  been  moved or closed.  Also,  when a  dialog-box  has  been 
completed, he window parts underneath the box must be redrawn.
Now  you can't just redraw the entire window because there  might 
be other windows on top of it. GEM will store all retangles to be 
redrawn in a buffer, and with wind_get calls, those retangles can 
be obtained. 

The routine can be found on the next page.

Procedure Redraw(W_handle%,X1%,Y1%,W1%,H1%)
   Local X0%,Y0%,W0%,H0%,Ok%
   @Wind_get(W_handle%,11,*X0%,*Y0%,*W0%,*H0%)
   ' this call gets the first retangle from the list
   While(W0%<>0 And H0%<>0)
      @Rc_intersect(X1%,Y1%,W1%,H1%,*X0%,*Y0%,*W0%,*H0%,*Ok%)
      ' calculate intersection between two retangles
      ' and put it in X0%,Y0%,W0%,H0%
      If Ok% ! is there an intersection?
         @Draw(X0%,Y0%,W0%,H0%) ! yes, redraw it
      Endif
      @Wind_get(W_handle%,12,*X0%,*Y0%,*W0%,*H0%)
      ' get the next retangle from the list
   Wend
Return

This routine uses the same Draw routine for every window.  But in 
your  application,  different  windows  might  contain  different 
information.  In  this  case,  use a different Draw  routine  for 
different window handles.

In this case, the draw routine looks like this:

Procedure Draw(X%,Y%,W%,H%)
   Deffill 1,0,0    ' empty fill pattern
   Pbox X%,Y%,W%,H% ' draw an empty box
   Deffill 1,4,0    ' atari-signs fill pattern
   Pellipse X%+W%/2,Y%+H%/2,W%/2,H%/2
   ' Draw a filled ellipse in the retangle
Return

The Close_window routine
This  routine  is  very  simple and  the  actual  window  closing 
consists of two GEM-calls,  but to make things nice, we will also 
include a shrinkbox before the window is closed.

Procedure Close_window(W_handle%)
   Local X0%,Y0%,W0%,H0%,X1%,Y1%,W1%,H1%
   @Wind_get(0,5,*X0%,*Y0%,*W0%,*H0%)
   ' get desktop dimensions
   @Wind_get(W_handle%,5,*X1%,*Y1%,*W1%,*H1%)
   ' get window dimensions
   @Graf_shrinkbox((X0%+W0%)/2,(Y0%+H0%)/2,X%,Y%,W%,H%)
   ' department of special FX
   @Wind_close(W_handle%)
   ' close the window. The handle is still available to open it
   ' again
   @Wind_delete(W_handle%)
   ' delete the window from GEM-memory.
   ' the handle is cleared and can be used for another window
Return

The  Fulled  routine full-sizes a window if it  was  not  already 
full-sized  and reduces it to its orginal size if it was  already 
full-sized. (nice sentence huh?)

Procedure Fulled(W_handle%)
   Local Cx%,Cy%,Cw%,Ch%   ! to hold current window size
   Local Px%,Py%,Pw%,Ph%   ! to hold previous window size
   Local Fx%,Fy%,Fw%,Fh%   ! to hold full window size
   Local Ok%               ! nice flag
   @Wind_get(W_handle%,5,*Cx%,*Cy%,*Cw%,*Ch%) ! get current size
   @Wind_get(W_handle%,6,*Px%,*Py%,*Pw%,*Ph%) ! get prev. size
   @Wind_get(W_handle%,7,*Fx%,*Fy%,*Fw%,*Fh%) ! get full size
   @Rc_equal(Cx%,Cy%,Cw%,Ch%,Fx%,Fy%,Fw%,Fh%,*Ok%)
   ' check if retangles are equal and put result in Ok%
   If Ok% ! window already full-sized, reduce to previous size
      @Graf_shrinkbox(Px%,Py%,Pw%,Py%,Fx%,Fy%,Fw%,Fh%) ! nice
      @Wind_set(W_handle%,5,Px%,Py%,Pw%,Ph%)
   Else   ! full-size the window
      @Graf_growbox(Px%,Py%,Pw%,Ph%,Fx%,Fy%,Fw%,Fh%)   ! effects
      @Wind_set(W_handle%,5,Fx%,Fy%,Fw%,Fh%)
   Endif
Return   
The  next three event routines belong to the slider part  of  the 
windows.  Sliders  are a wonderful thing,  and you can do  a  lot 
with them,  like dragging and arrowing. But they also change size 
when  a  window has been resized,  or when the  contents  of  the 
window change in size.  In the following examples,  I will use  a 
situation in which a text has been displayed in a window.

First of all,  when you open a window,  the sliders must be  set. 
After you have created the window and opened it,  set the sliders 
with  an overall-routine that can be used when a window has  been 
opened, redrawed, arrowed, fulled ect.
In  our example,  we will need some way to store two  values  for 
each window:  the line number displayed at the top of the  window 
and the character number displayed at the left.  (This refers  to 
the text example).  We will use an array of eight by two to  hold 
the values for each window. Again, eight array elements for eight 
possible window handles.  (Accessories might occupy handles  that 
fall between the values of our four possible windows)
The first element of the second dimension will hold the top  line 
number  and  the  second will hold the  number  of  the  leftmost 
character. Dimension it like this:

Dim Wind%(7,1)

When you open a window,  the values must be set to initial values 
of 0:

Wind%(W_handle%,0)=0
Wind%(W_handle%,1)=0

For the following routines, you must know the size of a character 
on the screen for calculating the number of lines and  characters 
seen in a window. You can do this with the Get_textsize routine:

@Get_textsize(*Chrbw%,*Chrbh%)

Call this routine when you initialize your program.

Now you can call the Slid_calc routine as I have called it:

Procedure Slid_calc(W_handle%,Vert%,Hor%)
   Local Lns%,Chrs%,Vert%,Hor%
   @Wind_seen(W_handle%,*Lns%,*Chrs%)
   ' calculate number of lines and characters in window
   '
   If Vert%=1 ! recalc vertical slider?
      Total%=Txtsize%  
      ' Txtsize% holds total number of lines in text   
      Size%=Min(1000,1000*Lns%/Total%)
      ' standard formula to calculate size of slider
      If Total%<=Lns% ! avoid division by zero
         Pos%=0
      Else
         Pos%=1000*Wind%(W_handle%,0)/(Total%-Lns%)
      Endif
      ' calculate position of slider.
      ' Wind%(W_handle%,0) holds line number of top line in 
      ' window.
      @Wind_set(W_handle%,16,Size%,0,0,0) ! tell GEM about
      @Wind_set(W_handle%,9,Pos%,0,0,0)   ! new slider values
   Endif
   '
   '
   '
   If Hor%=1 ! recalc horizontal slider?
      Total%=Txtwidth%
      ' Txtwidth% holds max number of characters per line
      Size%=Min(1000,1000*Chrs%/Total%) ! standard formula again
      If Total%<=Chrs% ! avoid division by zero
         Pos%=0
      Else
         Pos%=1000*Wind%(W_handle%,1)/(Total%-Chrs%)
      Endif
      @Wind_set(W_handle%,15,Size%,0,0,0)
      @Wind_set(W_handle%,8,Pos%,0,0,0)
   Endif
   ' Phew...sliders are set
Return

After all this, we still need the slider messages routines:

Procedure Arrowed(W_handle%,Kind%)
  Local Lns%,Chrs%,Vert%,Hor%
  @Wind_seen(W_handle%,*Lns%,*Chrs%)
  If Kind%=0  ! page up
    Wind%(W_handle%,0)=Max(0,Wind%(W_handle%,0)-Lns%)
    Vert%=1
  Endif
  If Kind%=1  ! page down
    Wind%(W_handle%,0)=Min(Txtsize%,Wind%(W_handle%,0)+Lns%)
    Vert%=1
  Endif
  If Kind%=2  ! line up
    Wind%(W_handle%,0)=Max(0,Wind%(W_handle%,0)-1)
    Vert%=1
  Endif
  If Kind%=3  ! line down
    Wind%(W_handle%,0)=Min(Txtsize%,Wind%(W_handle%,0)+1)
    Vert%=1
  Endif
  '
  If Kind%=4  ! page left
    Wind%(W_handle%,1)=Max(0,Wind%(W_handle%,1)-Chrs%)
    Hor%=1
  Endif
  If Kind%=5  ! page right
    Wind%(W_handle%,1)=Min(Txtwidth%,Wind%(W_handle%,1)+Chrs%)
    Hor%=1
  Endif
  If Kind%=6  ! char left
    Wind%(W_handle%,1)=Max(0,Wind%(W_handle%,1)-1)
    Hor%=1
  Endif
  If Kind%=7  ! char right
    Wind%(W_handle%,1)=Min(Txtwidth%,Wind%(W_handle%,1)+1)
    Hor%=1
  Endif
  @Slid_calc(W_handle%,Vert%,Hor%)
Return

Now the routines when the sliders have been moved by the user:

Procedure Hslid(W_handle%,Pos%)
  Local Lns%,Chrs%
  @Wind_seen(W_handle%,*Lns%,*Chrs%)
  Wind%(W_handle%,1)=Pos%*(Txtwidth%-Chrs%)/1000
  @Slid_calc(W_handle%,0,1)
Return
'
Procedure Vslid(W_handle%,Pos%)
  Local Lns%,Chrs%
  @Wind_seen(W_handle%,*Lns%,*Chrs%)
  Wind%(W_handle%,0)=Pos%*(Txtsize%-Lns%)/1000
  @Slid_calc(W_handle%,1,0)
Return

That's  it  for the sliders,  but I still have to  give  you  one 
routine:

Proc Wind_seen(W_handle%,W_lines%,W_chars%)
   Local X%,Y%,W%,H%
   @Wind_get(W_handle%,5,*X%,*Y%,*W%,*H%)
   *W_lines%=H%/Chrbh% ! lines in window
   *W_chars%=W%/Chrbw% ! chars in window
Return

That's  it!  I  think  this is the biggest article  I  have  ever 
written  for  ST NEWS.  And I still have to tell  you  about  the 
sample program!

In  the  folder 'programs' on the disk you will  find  a  program 
called 'MYWIND.BAS'. This is a GfA-BASIC source that uses all the 
routines  and techniques described above.  It is a long  program, 
and it doesn't do anything sensible.  It just lets you play  with 
windows.  This  proves  that GEM is OK,  but you need  a  lot  of 
programming  to get it al working.  The program uses an array  to 
store  window  handles,  so  it  can  prevent  you  from  closing 
accessory windows, because accessories do not like it at all when 
their  windows  are closed by your program,  and  the  system  is 
likely  to  crash.  Also I trap errors and the break  routine  by 
closing and deleting all open windows when they should occur.  It 
is  important  that  you close all windows  before  exiting  your 
program  because 'ghost' windows will keep roaming around if  you 
do not close them, and funny things might occur.

I also use a little trick to keep track of menu-items. You can of 
course  check  out  the menu-numbers  and  call  the  appropriate 
routines,  but  when  you instert a new menu-item,  you  have  to 
change all numbers.  You can also check the names,  but this will 
cost a lot of memory.  So I use an array in which the numbers are 
stored  of the active menu-items.  Items that are not  used  like 
titles and dividers have number zero.  Now I can use a simple  On 
..  Gosub without any overhead. It sounds a little difficult, but 
check out the source and it will become clear.  A pity this can't 
be used with Menu X,Y statements to disable or check  menu-items. 
Now a preprocessor would come in handy.....

I guess this is the end of the article. I hope it has been useful 
to  you,  because it took me a lot of keyboard-tapping to  create 
it.  Now I did not dream all of this up myself, and I think it is 
a good idea to tell you the sources of my information:

- ATARI-ST GEM from Data Becker. ISBN 3-8901-251-X
a  little expensive but very comprehensive GEM-reference  manual. 
With VDI-listing and some sample programs in both GfA and C.
- GFA-BASIC by Frank Ostrowski.
Very good with loads of programs. Comes with a disk.
- 'Professional GEM' by Tim Ohren.
PD-stuff  by GEM co-designer and programmer Tim  Ohren.  Contains 
lots of information. Very nice.

  A word from the Public Relations Manager of Digital Insanity
After  the  release of 'The  Professional  ArtiST',  things  have 
calmed down a bit at the offices of Digital Insanity due to heavy 
involvment   of   Senior  Programmer  Stefan  Posthuma   in   the 
professional software business.  (in simple terms:  I have a very 
busy   but   very   nice  and  rewarding  job   as   a   software 
designer/programmer).  Also  I  have been contacted by  a  German 
Softwarehouse  concerning coorperating on a  certain  program.  I 
found  out  that there is already a similar  program  to  'DISH'. 
Digital  Insanity  Shell  was scheduled to  be  the  next  Digtal 
Insanity  release,  but I kind of lost  interest.  Also,  Digital 
Insanity Word,  the first Insane Wordprocessor has been scheduled 
to the lowest priority.  
I  will keep myself busy with watching  TV  (Moonlighting,  Miami 
Vice,  Pin-up Club,  Gary Shandling,  Family Ties,  Golden Girls, 
Who's  the  Boss etc etc),  taking nice girls to the  cinema  and 
disco (Kings Cross in Den Bosch is my favorite), writing articles 
for  ST NEWS,  hanging  around Den Bosch city  with  Richard  and 
sometimes Frank on Saturdays.  Also,  the Army has expressed  its 
interest  in  me,  and I will be looking like a fool in  a  funny 
green suit in a month or two...

My filmtips this time are 'Innerspace' and 'Stakeout'; Both  very 
humorous and exciting.  'Innerspace' contains  some  breathtaking 
special  effects.  Also if you like to sit for three hours  in  a 
cinema chair, The Last Emperor is very beautiful.

I also want to express my great fondness of a certain girl  named 
Hilde (sigh...my mind starts to wonder off into the pink mists of 
love...)
This is the end of a very long story!

See you next time in ST NEWS!

P.S.  If  you hear about the tragic death of one ST-freak  called 
Stefan Posthuma, it can have two causes:
1 - My father has made his own Sambal
2 - I am going to Austria to do some skiing.

I leave you with a rather naughty fortune:

     "God  built a compeling sex drive into  every  creature,  no 
matter  what  style  of  f.cking  it  practised.   He  made   sex 
irresistibly pleasurable, wildly joyous, free from fears. He made 
it innocent merriment."
     "Needless  to  say,  f.cking  was an  immediate  smash  hit. 
Everyone agreed,  from aardvarks to zebras. All the jolly animals 
--  lions and lambs,  rhinoceroses and  brazelles,  skylarks  and 
lobsters,  even insects, though most of them only f.ck once in  a 
lifetime  -- f.cked along innocently and merrily for hundreds  of 
millions of years.  Maybe they were dumb animals, but they knew a 
good thing when they had one."
     -- Alan Sherman, "The Rape of the A*P*E*"

Listen to Wally Jump Jr....Radical!

Disclaimer
The text of the articles is identical to the originals like they appeared in old ST NEWS issues. Please take into consideration that the author(s) was (were) a lot younger and less responsible back then. So bad jokes, bad English, youthful arrogance, insults, bravura, over-crediting and tastelessness should be taken with at least a grain of salt. Any contact and/or payment information, as well as deadlines/release dates of any kind should be regarded as outdated. Due to the fact that these pages are not actually contained in an Atari executable here, references to scroll texts, featured demo screens and hidden articles may also be irrelevant.