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.