
                       GEM PROGRAMMING by Jonathan White

                                  ARTICLE III

     WINDOWS.

     Before we get into the article proper, I  will pass on a bit of news I
     heard today on the Internet. There  is  a  second edition of the Atari
     Compendium coming  out  soon.  Not  only  will  it  fix  all  the errr
     typographical undisclosed features (errors) in  the  first ed. It will
     also include details on some  things  left  out  of the first. Plus it
     will have a better  cover  (my  first  ed  is already looking somewhat
     distressed. The book world has a term  for it - badgered. I think mine
     is more sort  of  'rabid  enraged  polar  beared'..).  Also, wonder of
     wonders, it's going to be bound so it will lie flat on the desk.

     So if you were thinking of buying it,  wait a bit to see if the second
     ed comes available. Also, I have just had a chance to look at the book
     'Modern Atari System Software' by Hisoft. If you already have a set of
     GEM reference manuals - say the Compute! ones..

     a) Why are you reading these articles??
     b) It's a cheaper way to  get  new  info  than the AC. Your references
     won't be as integrated as the AC, but it'll save you 20 quid..

     In the third of the series, we  look  at  how  to use windows - how to
     create them, how to limit graphics to  within them, and how to process
     user changes to them. Thus we shall have our first introduction to GEM
     events and messages on the way.

     Before we actually  look  at  windows,  a  point  about  using them at
     startup. If a program starts with  no  command  line, do you display a
     window or not. Most Macintosh applications,  if started without a file
     being passed to them open a blank window to start a new document into.
     Some do not. The Atari interface guidelines give you two alternatives.
     One, do  as  a  Macintosh  and  open  a  blank  window  with  the name
     'untitled'. Two, start with a dialogue  box giving the user the option
     to load a file  (using  the  file  selector)  or  open a new document.
     Personally, I prefer this way.  There are few things as infuriating as
     watching an application open  a  blank  window,  knowing full well the
     first thing you are going to do is close it again.


     1) Creating Windows.

     Creating windows is pretty simple in itself, but it does require a bit
     of preparation.  The  call  to  create  a  window  is, unsurprisingly,
     wind_create(), which takes several parameters,  and returns the handle
     for the window. All functions which  affect  the window identify it by
     this handle. It is  the  window's  ID.  The  parameters  passed to the
     function define the maximum size the window can be set to by the user.
     Finding these limits if the first bit of preparation we must do.

     The maximum size the  window  can  be  isn't  usually  the size of the
     display, as passed back when the initialisation functions were called.
     It is actually that minus the size  of  the  menu bar. But to save you
     the effort, there is a useful command called wind_get() which can give
     you all sorts of information. One of  the  parameters it can take is a
     flag, and if that flag has a value of WF_WORKXYWH, when the window you
     are examining has the number 0 (the desktop has the handle 0), it will
     return X,Y width and height  of  the  desktop.  Therefore, if the four
     numbers returned are x,y,w and h, the  maximum size a window can be is
     these numbers too. Since they are passed and returned as pointers, all
     you have to do is use the  same  variables in both calls. For once GEM
     helps us along.

     The other thing you must  pass  to  wind_create  is  what parts of the
     window you will display. You can have scrollbars (or not), close boxes
     (or not). Anything from a full  GEM  window,  to a window which has no
     identifying objects at all, and is  therefore  just a frame. As it is,
     the objects  defined  also  define  what  messages  the  program  must
     process. You will never  receive  a  'window  resized'  message if the
     window doesn't have a sizer attached  to it. Most GEM compilers define
     the set of objects in one of  the include files. Therefore, to set the
     object, you logically  AND  these  pre-defined  names  into a variable
     passed to the function. For example,  a  window to display messages in
     might have a close box (to remove  it)  and  a title bar (to move it),
     but not a sizer box. So, you define the 'parts' variable to equal NAME
     | CLOSER.

     Once you have used wind_create(), and  got your window handle, you can
     actually display it. If you have  given  the  window a title bar or an
     info line, you have to  set  the  text  in  these  before you open the
     window.

     You do this with wind_set().  This  function  has  other uses (such as
     setting the size and position of the  scroll bars) but it can also set
     these text strings. Once  you  have  done  these,  you can display the
     window with wind_open(). This sets the initial size of the window. You
     can set it to the maximum size if you wish, or to a standard size. The
     appropriate size depends on the  exact  function  of your program. You
     can then go on to fill it.

     Obviously, a window can  have  ANYTHING  in  it.  To  go  into all the
     possibilities here would take too  long  and  would  be  too much of a
     digression (like digressing isn't something I do enough. Damn I did it
     again..). But, all graphic  operations  (that  includes text printing,
     when done in a GEM  window)  must  be  limited to the windows 'working
     area'. What's the point of  having  windows if programs splatter their
     output around willy nilly?

     The first thing we must do is  find  out the windows working area. The
     function to do this is one we have already come across, wind_get(). If
     you pass that function the WF_WORKXYWH  flag with your windows handle,
     it will return the x,y w and h  of  the windows work area - the window
     not including any scroll bars, gadgets and  the title / info lines. If
     you pass the flag as WF_CURRXYWH, it  will return the full size of the
     window. Alternatively, you can use the function wind_calc() to convert
     between  work  area  and  full  area.  The  functions  wind_set()  and
     wind_get() are used frequently when dealing with windows, and we shall
     come across other functions they serve soon.

     So, you now have the co-ordinates of  your work area. The next step is
     to pass these co-ordinates  to  the  VDI  (the  GEM output manager) to
     restrict output to our window - a process known as 'clipping'. Here we
     come across a small problem. While  the  AES prefers to define windows
     in terms of start corner, width  and  height,  the VDI prefers to have
     them as top  left  and  bottom  right  corners.  Why  this  is I can't
     possibly imagine, aside from the  usual  reason  (Atari messed up) but
     there you go. The VDI rectangle  form  actually has an official name -
     it's called a GRECT - and usually has a structure definition somewhere
     in the include files if you care to use it. Converting from AES to VDI
     is not a problem (x1=x,  y1=y,  x2=(x1+w-1),  y2=(y1+h-1)), but if you
     don't remember to do it,  your  program  will  fail, and will probably
     crash.

     Anyway, the call to clip  your  graphics  output is, logically, called
     vs_clip(), and takes the  VDI-form  rectangle  as   a parameter, along
     with the VDI handle (NOT the  window  handle).  Then, you can go on to
     draw your graphics or  text.  It  is,  usually,  wise  to put this re-
     drawing routine in a procedure  or  function, and pass the coordinates
     to re-draw into and the window handle as parameters, as this means you
     can use the same routine to re-draw  parts  of the window later on. If
     fact, the most elegant system I  saw  presumed  all graphics were as a
     result of a 'please redraw  your  window'  message.  The program I saw
     first displayed it's  graphics  by  sending  the  application itself a
     redraw message which is a useful  idea  I  shall  detail in a sec. And
     talking of messages..

     2) Handling Windows (or, messages part 1)

     The system communicates what is  happening  to  the user via messages.
     Every time the user performs  an  action  -  picking  an item from the
     menu, moving a window etc. etc - its called an event. Each event makes
     the system send you one or more messages. The big if-then-else loop is
     actually just processing messages.  Unfortunately,  there are an awful
     lot of possible messages you  can  get.  First  of  all, there are the
     messages sent by other applications. We  saw those last time. The next
     group we will look at are window messages.

     You check for messages via one of  several calls. The most general one
     is the infamous evnt_multi(), which  basically informs you if ANYTHING
     happens - keypresses, menu selections,  mouse clicks, messages etc etc
     etc- all in the one call. That's  the  one you would use for most FULL
     GEM programs. AS we are only looking at window messages (right now) we
     can restrict ourselves to the more simple evnt_mesag() function (no, I
     don't know why its spelt that way..).

     Before we look at it, I'd just  like to talk about multitasking again.
     In the first part, I spoke about how, under certain circumstances, the
     system leaves your program and goes  off  to  give other programs a go
     (cooperative multitasking, as used by  the  Geneva  system). It is the
     case that evnt_calls are actually when  it  does this. Until the event
     you wish to look out for happens, your program sits and waits. Thus if
     you wish your program to be  multi-tasking friendly, you should put an
     evnt_call in every long  processing  job,  even  if you don't actually
     wish to know what's  going  on.  Pre-emptive multitasking environments
     (MultiTOS, Mag!x ) don't  need  this  courtesy,  but since this method
     allows all multitasking systems  to  work,  it's  best  to use it. The
     standard call to use is evnt_timer,  which causes your program to wait
     a certain number  of  milliseconds.  Seeing  as  how  Atari have never
     actually guaranteed its accuracy to any  degree,  the call isn't a lot
     of use for its stated function.  However,  if you call an evnt_timer()
     with a time of 0, it allows multitasking to continue.

     The evnt_mesag() function waits for  messages  to come down the 'pipe'
     to your program. They  can  be  system  messages,  messages from other
     apps, or even from your own program  - sent using appl_write with your
     own app_id. Thus,  you  can  send  yourself  a  window  redraw message
     whenever you wish. You could also send yourself any other message, but
     since the event they are  attached  to  hasn't happened, there isn't a
     lot of point.

     There are various messages  you  might  receive,  depending on how you
     have set up the window. I shall  detail each one, with what you should
     do and the part of the window you must have made 'active', (the actual
     format of evnt_mesag is in the reference for this month)

     1)  WM_SIZED (sizer box). Use  wind_set  to  actually set the window's
     new size. Then use  wind_get  to  get  the  new  working size, set the
     sliders to their new values (a bit later..) and redraw the window.

     2)  WM_MOVED (title bar). Use wind_set to set the new sizes.

     3)  WM_FULLED (fuller). If  the  window  is  not  'fulled', record the
     current window size, then set the  window  to the maximum size allowed

     and redraw. It the window was previously 'fulled', recall the previous
     coordinates and set the window to these, then redraw.

     4)  WM_CLOSED (closer). Use  wind_delete()  to  remove the window from
     the screen. You could also incorporate a routine to query this  if the
     window contents haven't been saved.

     5)  WM_ARROWED (scroll bars and arrows). This message can mean several
     things. Firstly, it can mean the user has clicked on one of the scroll
     bar arrows. If so, move  the  'window  contents' one system characters
     width up, down, left or  right  (that's  actually  just a rough rule I
     use, you can actually use any distance you like) for graphic images or
     one line up/down or one character left/right for text. If the user has
     clicked in the free space  near  the  bar,  move the screen one screen
     MINUS ONE LINE  (according to  the  Atari  guidelines) in the relevant
     direction for text images, or (the  working  area - some small amount)
     for graphic images. Obviously,  a  screen  redraw  is required and you
     should also set the window scrollbars as appropriate.

     6)  WM_HSLID (horizontal slider). This passes  back a number between 0
     and 1000. This number defines how far  to the right the scroll bar has
     been placed IN IT'S  MOVEMENT  RANGE  (which  is actually window width
     -(scroll bar width+arrows  width+sizer  width).  Basically  0=start of
     document, 1000=end of document  and  ranges  in between are calculated
     depending upon the form your  window  is  displaying. Then, redraw the
     screen.

     7)  WM_VSLID (vertical slider).  As  previous,  except  in a different
     direction.

     There are also various  messages  you  will  only  receive  if you are
     dealing  with  AES  4.0+  systems,  i.e.  multitasking  systems.  They
     basically tell you if something done to some other window belonging to
     another application has had some effect on your windows. Most of these
     follow a logical pattern, and  they  are  in  fact pretty easy to deal
     with. Details of these are in both the new reference books.

     The last, and most important message you can receive when dealing with
     windows is the WM_REDRAW message.  Your  application will process this
     message many times when it is run, so  it  is vital that it is as bug-
     free and robust as possible. Please note  that it is possible you will
     need to redraw your window  contents  even  if  you have hard coded in
     that you only will have one  window  open  and you are running under a
     single tasking OS. Why? Desk  Accessories.  They  can open windows too
     (Hey, a slogan..  'Da's are programs  too'.  'I  am  not  a DA, I am a
     proper program', 'Can't multitask, won't  multitask' errrr sorry.) So,
     if your program allows for DA's  (i.e.  it  has a menu with spaces for
     them) you have to assume that at some  time one of them is going to be
     opened over your program. So, we  shall  look at the WM_REDRAW message
     in detail.

     All system generated messages are  the  same  size,  16 bytes or eight
     WORDS. evnt_mesag returns a  pointer  to  the  message  buffer. If you
     expect to get messages from  apps,  16  bytes  might  not be the right
     length. That's why I suggested if  you  are  going to use messages for
     interprocess communication, you keep  them  to  16  bytes too, to keep
     things simple. This structure is defined as follows..

     message[0]=message type (WM_REDRAW should be defined in one of
                     your include files, you can use that for an
                     'if')

     message[1]= the ap_id of the sending application (for above you
                     can ignore this)
     message[2]= the length of the message over 16 bytes (in this case, 0)
     message[3]= the window handle which needs to be redrawn
     message[4]= x   co-ordinate of dirtied triangle
     message[5]= y           "       "       "
     message[6]= width of            "       "
     message[7]= height of           "       "

     So, in theory, all  you need  to  do  is get the coordinates, and pass
     those co-ordinates  on  to  your  screen  redrawing  routine.  Lovely.
     Unfortunately ( you knew I was gonna say that didn't you..) things can
     get a little more complicated than  that. It is entirely possible that
     you will  get  a  redraw  message,  yet  your  active  window  will be
     partially obscured. How is this? Just to  use an example, suppose a DA
     ("free rights for.. " Oh Shut  up!)  in  a  window is moved across the
     surface of your active window. E,g,

     If you have a DA being opened across your window like this..



                             <put figure1.img here>




     and the user moves it like this...


                             <Put figure2.img here>



     then the part that had been dirtied is this...


                             <Put figure3.img here>




     but you can't just redraw the  whole  window because some of the space
     is being used by the desk accessory, like this..


                             <Put figure4.img here>




     In actual fact, part of the dirtied  rectangle might even be under the
     DA, so you can't really assume anything about the rectangle to redraw.

     GEM actually gives us the  answer.  The function wind_get() (said we'd
     see that one again) has a couple of pre-defined values it can look at.
     What the GEM  system  does  is  divide  up  your  window  into visible
     rectangles, which GEM is  sure  aren't  obscured. Basically, GEM takes
     the visible section (or sections) of  your  window and divides it into
     the minimum number  of  rectangles  that  will  cover  it.  Like those
     puzzles you did when you were a kid, when you got six weird shapes and
     you had to fit them together to make a square..

     What this does is give us a method to systematically check the damaged
     rectangle against each of the visible rectangles. If you spot the area
     each visible rectangle overlaps into the dirtied rectangle, and redraw
     that bit, then by the end  of  the  visible rectangle list, you should
     have correctly redrawn your window..


     Next, we'll present the  algorithm  to  do  this  in detail. Seriously
     folks, if you can do this  bit,  and  the  bit  later on in the series
     where we look at GEM dialog  box  trees,  you have GEM sussed. So here
     comes one of the more difficult bits..

     3) Redrawing your Window (in 7 easy steps..)

     Right, there is a  fairly  well  defined  process  for redrawing a GEM
     window, and it goes something like this...

     1)     Get your damaged rectangle. This  is  sent to you in the window
     redraw message.

     2)     Use wind_get with a flag parameter of WF_FIRSTXYWH. This gives
     you the size of the first rectangle in your 'visible rectangle'
            list.

     3)     If the width and height is 0, you have reached the end of the
            list and the redraw is complete.

     4)     If width or height of the list window is not 0, you have to
            calculate the overlap between the two, and redraw this overlap.
            The overlap is calculated by the following rules...

     If the damaged rectangle is  x1,y1,w1,h1  and the visible rectangle is
     x2,y2,w2,h2 and the rectangle to redraw is x3,y3,w3,h3 then :-

             x3= the bigger of x1 and x2
             y3= the bigger of y1 and y2
             w3= (the bigger of (x1+w1) and (x2+w2)) - x3
             h3= (the bigger of (y1+h1) and (y2+h2)) - y3

     Note that,  if  you  have  Lattice  C,  there  is  a  function  called
     rc_intersect which will give you  the  overlap. It's actually supposed
     to be used for drawing dialog boxes, but what the hell...

     5)      If w3 and h3 are both greater than zero, you have a window to
             redraw. The process for doing this is as follows..

             a) call graf_mouse(M_OFF,OxOL) to freeze the mouse

             b) call wind_update(BEG_UPDATE) to freeze the screen while
                     you redraw.

             c) call vs_clip to limit your drawing to the damage
                     rectangle

             d) redraw the rectangle

             e) call wind_update(END_UPDATE) to release the screen

             f) call graf_mouse(M_ON, 0x0L) to set the mouse free

     6)     Get the next visible rectangle, using WF_NEXTXYWH to get the
            next rectangle in the list

     7) Go back to step 3.

     And so on until you exit on  step  3.  You can, of course, put most of
     this in a C function call. I actually use two standard ones. The first
     one, redraw_wind() gets the  window  handle  and the damaged rectangle
     passed to it, and it does the  overlap  checking. If it finds it needs
     to redraw, it calls  a  function  named  draw_rect,  again passing the
     window handle and the rectangle to  be  redrawn.  Thus, I can use this
     function over and over again  just  by  changing draw_rect to draw the

     appropriate  thing..  That's  almost  Object-Orientated  Programming!!
     (it's as close as I'll ever get..).

     Note that, if you are REALLY  clever,  you might include a check about
     redoing the scrollbars in here too. And right on cue..

     4) Windows Scrollbars..

     One of the best parts  of  GEM  is  the  fact that it has proportional
     scrollbars. These show you not only where you are in the document, and
     also how much of the document you  can  see.  In fact, they are such a
     good idea that Microsoft (spit!!) has put them in windows 4 / Chicago.
     Ten years behind as usual. Just as an aside, not only does Windows 3.1
     not have these type scrollbars, the little buttons it uses instead are
     called 'thumbs'. So if you use windows, you actually do spend a lot of
     your time twiddling your thumbs.. But if  you use windows, you spend a
     lot of your time doing that anyway :-)...

     Anyway. Having these things is very useful,  but it does mean you have
     to keep an eye on  them.  Whenever  the  user  puts in some more data,
     resizes the window or clicks on  the  scrollbars / arrows etc you have
     to  redraw  them.  This  is  done  via  the  getting-to-be-overexposed
     wind_set call. Figuring out what to tell it is a two step thing.

     First of all, you have to figure out how big they should be. GEM rates
     them proportionally to your window,and  uses an arbitrary measure that
     the range is from 0 (nonexistent) to  1000  (the full length / width -
     the size of the  arrows).  So  you  don't  have  to  figure it out for
     yourself in pixels, just as a  percentage. Obviously, if you have less
     data than the window size, it's the  full 1000. If you are showing 50%
     of the data, it's 500 etc etc. It's actually considered proper to have
     a minimum of 2-3%, so the user has something to drag..

     Actually, there is a simple formula to get the size..

             size = 1000 * (amount seen / total amount).

     Position is a little  more  tricky.  Mainly  because  the 0-1000 range
     doesn't define the full width the scroll  bar can go (i.e. the size of
     the window - (the size of  the  arrows+ any gadgets)). Instead, the 0-
     1000 covers the possible movement range  of  the top of the scrollbar.
     Since your scrollbar has a finite width, the top line can never get to
     the bottom.

     An example will make things easier.

     Say you have a 100  line  document,  and  you are currently showing 20
     lines of it. The scrollbar therefore takes up 20% of space. So, our 0-
     1000 range is actually only 0-800. But  we give the number still in 0-
     1000 units. You see? No? The 0-1000 is the whole bar, but the range of
     movement is 0-800. therefore, if you want  the  bar to start at 48% of
     the way through, your bar is actually placed at..

     1000*48/(100-20) or 600 - 60% of the way down.

     It also means that, even if you  go  right  to the bottom of the file,
     your slider edge will only be 80% of  the way down the bar. This gives
     us a general formula for the start of the scrollbar..

     position = 1000* Start point / (total length - length shown)

     Therefore, to redraw the scroll bar you have 4 steps..

     1) Calculate the total size of the file in that direction -
             pixels, lines, characters..

     2) Calculate the amount of the image shown - in the same units

     3) Apply the above formulae to find the parameters

     4) Call wind_set to set the scrollbars.




     5) Window Cleanups.

     That's just about all. OK, so I  haven't  told you how to PUT anything
     in then yet, but that's coming soon (well, as soon as someone takes my
     shiny new Jaguar away..). All that's left is how to remove the window,
     and that's pretty simple,  in  fact  it's  two calls, wind_close() and
     wind_delete(). Wind_close removes the window from the screen (and will
     probably send you a redraw message if you have other windows) and then
     you use wind_delete to tell the system you've finished with it, so the
     handle can be used by another window.

     That's all for now. Next time  we'll  go  over  how to use a GEM menu.
     That'll be pretty straightforward after this.
