      
         [42m/*                                     */[40m
         [42m/*   A Closer Look at the OpenWindow   */[40m
         [42m/*   Function in 'rexxarplib.library'  */[40m   [33;3mby John Collett[0m
         [42m/*                                     */[40m
                      
    
    The subjects of the CreateHost() and OpenWindow() functions in
    'rexxarplib' have featured in some of my earlier contributions, and
    one or two of you may think I'm beginning to get a bit boring on the
    subject.  It's time to come clean, and to confess that there are some
    aspects of the OpenWindow function which I still cannot get to behave
    according to the docs, and one or two details in the docs which at the
    moment leave me baffled.
    
    My most recent exploration was intended to be no more than that, but
    the framework which I set up soon started to look as though it might be
    a suitable base for *another ARexx tutorial*.  It may help some readers
    who are having difficulties with ARexx in general, and/or are finding
    'rexxarplib' in particular a bit of a tough nut to crack.  Other
    readers may come to our aid and be able to throw some light on the
    remaining mysteries.
    
     * Unless and until someone asks me to stop, we all run the risk of *
     * the writing of these tutorials becoming an irrepressible habit.  * 
    
    [42m  The OpenWindow Function : arguments  [40m
    
                     1     2     3     4      5       6      7      8
    [32mcall OpenWindow(port, left, top, width, height, idcmp, flags, title)[31m
    
    [42m 1 [40m  Port : The 'controlport' as opened by the CreateHost function :
    
                            i           ii        iii
        [32mcall CreateHost(controlport,notifyport,publicscreen)[31m
    
      i Name of the message port the window should listen to.
     ii Name of the message port to be notified when an IDCMP event occurs
        (mouse move, key press, menu choice, etc.).
    iii Optional, instead of WorkBench, and if included it must have been
        previously opened with the OpenScreen function.

    [42m 2, 3, 4, 5  [40m  Dimensions of the window to be opened.

    [42m 6 [40m  IDCMP :  The classes of message which can be passed - any
    combination of the following :
    
        CLOSEWINDOW    MENUPICK     ACTIVEWINDOW     NEWPREFS      
        MOUSEBUTTONS   NEWSIZE      INACTIVEWINDOW   REFRESHWINDOW 
        GADGETUP       VANILLAKEY   DISKINSERTED     MOUSEMOVE     
        GADGETDOWN     RAWKEY       DISKREMOVED      DELTAMOVE     
  
    [42m 7 [40m  Flags : To specify which gadgets will be available in the window -
      WINDOWCLOSE, WINDOWSIZING, etc.

    [42m 8 [40m  Title : To appear in the title bar of the window.
    
    
    [42m  Defining contents of messages  [40m
    
    The ModifyHost function can be used to determine what data will be
    contained in any message which is sent for a particular kind of IDCMP
    event.  For example, if you want the IDCMP message which is generated
    by a mouse click to contain the x and y mouse position, then :
    
     [32mcall ModifyHost(controlport,'MOUSEBUTTONS','%x %y')[31m
    
    Within an IDCMP message, a two-character sequence starting with '%'
    will be interpreted as shown in the following list :

       [32m%a Alphanumeric keys - ASCII character[31m
       [32m%b Buttons - SELECTDOWN, MENUDOWN, etc.[31m    
       [32m%c Non-alphanumeric keys - ASCII character[31m
       [32m%d Gadget number[31m
       [32m%e Top of window[31m
       [32m%f Left of window[31m                  Here are listed only those 
       [32m%g Gadget string[31m                   elements which are used      
       [32m%h Window height[31m                   in the program below. 
       [32m%i Menu item number[31m                A fuller list can be found
       [32m%l Name of IDCMP class[31m             in 'rexxarplib' docs.     
       [32m%m Menu number[31m                    
       [32m%w Window width[31m                    I omitted items which I 
       [32m%x Mouse x[31m                         did not understand or
       [32m%y Mouse y[31m                         could not get to work.
  
  
    [42m  The usual reminders  [40m

    The easiest way to run the following program is to make a copy of this
    article, and delete everything from it except the patches of ARexx
    code and an initial obligatory comment line.  Before you run it :

     - You must have ARexx.
     - 'rexxmast' must be waiting in the background.
     - You must have the required libraries in LIBS:

    /* --------------------------------------------------------- */
    /*                    Start of program                       */
    /*                                                           */
     signal on syntax      /* A minimal error trap, just in case */
    
    /* Make the required libraries active        */    
    if ~show('l', "rexxarplib.library") then do
     check = addlib('rexxsupport.library',0,-30,0) 
     check = addlib('rexxarplib.library',0,-30,0)   
     end
   
    /* Enable window opening, and name the ports */
    address AREXX '"call CreateHost(HOST, PORT)"'  
    
    /* Wait until the ports are really open      */  
    if ~show('Ports','HOST') then address command 'WaitForPort HOST' 
   
    /* 
    Assign flags. BACKFILL is not a standard Intuition flag. It implies a
    subsequent call to SetReqColor() to specify a background colour, and
    then to BackFill() to colour-fill the window.  Colour 1 is used thus
    below, but the black background then serves only as a frame for two
    rectangles obtained by RectFill() and filled with colour 0.  
    */
    flags = 'WINDOWCLOSE+WINDOWDEPTH+WINDOWSIZING+WINDOWDRAG+BACKFILL'

    /* 
    The ARexx equivalent of an array is set up to contain the IDCMP class
    names, and at the same time messages are defined for each class as
    required. 'Group' is predefined for convenience.  All members of the
    'a.' array are initialised as '%l', the class name. Actually this 
    achieves nothing, since the 'rexxarplib' default presets '%l' for all
    classes except gadgets and menus, which set their own messages in their
    respective calls.
    */

    group = '%l %e %f %w %h %x %y' ; i. = '' ; a. = '%l'  
    i.1 = 'CLOSEWINDOW'                         /* Ends the program   */
    i.2 = 'MOUSEBUTTONS'   ;  a.2 = '%l %b %x %y'
    i.3 = 'GADGETUP'                            /* These three are    */
    i.4 = 'GADGETDOWN'                          /* set in their own   */
    i.5 = 'MENUPICK'                            /* respective calls   */
    i.6 = 'NEWSIZE'        ;  a.6 = group                           
    i.7 = 'VANILLAKEY'     ;  a.7 = '%l %c %a'
    i.8 = 'RAWKEY'         ;  a.8 = '%l %c %a'
    i.9 = 'ACTIVEWINDOW'   ;  a.9 = group
    i.10= 'INACTIVEWINDOW' ; a.10 = group
    i.11= 'DISKINSERTED'                        /* Only the class name */
    i.12= 'DISKREMOVED'                         /* to be displayed for */
    i.13= 'NEWPREFS'                            /* these three items   */
    i.14= 'REFRESHWINDOW'  ; a.14 = group
    i.15= 'MOUSEMOVE'                           /* These two remain    */
    i.16= 'DELTAMOVE'                           /* a total mystery     */
    
    /* 
    Build an IDCMP string for the OpenWindow command. Eagle-eyed readers
    may spot that I have concatenated the 'flags' using '+' between each
    element, and the IDCMP string using just spaces. Either method will
    work, because of the marvellous type-less nature of ARexx - you rarely
    have to worry about whether variables are strings, integers, or
    whatever. 
    */
    idcmp = ''
    do j = 1 to 16 ; idcmp = idcmp || i.j || ' ' ; end

    /* --------------------------------------------------------- */
    /*                    Open the window                        */
    
    left = 20 ; top = 30 ; width = 600 ; height = 190
    title = "   What messages do your actions send to the IDCMP Port?"    
    call OpenWindow(HOST,left,top,width,height,idcmp,flags,title) 
    call Openport(PORT) 
    call SetReqColor(HOST,BACKGROUNDPEN,1) ; call BackFill(HOST) ;  
  
    /* 
    Now those predefined arrays are really useful. 
    ModifyHost() can be called for all 16 elements in a simple loop. 
    */
    do j = 1 to 16 ; call ModifyHost(HOST,i.j,a.j) ; end
    
    /* 
    The next three lines of code are particularly interesting.  They
    contain a single, small, specific application of the method which is
    going to be used later on for obtaining all messages.  If you
    understand this little patch, you will find it easier to follow what
    goes on in the main program loop. 

    The opened window is going to hold two lists, one for prompting you,
    and one for displaying program output as you go, its contents
    depending on what you do.  It seemed like a good idea to have the basic
    window and pointer parameters displayed from the outset, as a sample of
    program output before you actually does anything, but those parameters
    have to be obtained before they can be displayed.

    The window is made active by a call to ActivateWindow().  That is an
    IDCMP event.  It will send back a message, and the type of message to
    be passed for that class of IDCMP event has already been defined in
    'group' above.  A little subroutine called RHost is called, waits for a
    message to arrive at the port, and returns its contents.  We can then
    parse those contents into whatever 'group' has specified that they
    should contain : %l %e %f %w %h %x %y; that is, the class name
    (ACTIVEWINDOW), window top and left, window width and height, and
    pointer x and y.  We parse them by a looping construction which removes
    the first element of whatever was returned, assigns it to an array
    (arg.), gives the same name as the original to whatever is left, and
    repeats the process until there is nothing left.  The array of 7 items
    can then be held until needed.    
    */

    call ActivateWindow(HOST) ; call RHost(group) ; t = 0
    do until thisarg = '' 
      t = t + 1 ; parse var thisarg arg.t thisarg ; end
    /* 
    Set parameters for frames to be drawn in the window. Pairs of
    rectangles are arranged so as to create frames in colour 3. 
    */
    top = 16 ; bottom = 180 
    left1 = 12 ;  right1 = 232 ; left2 = 240 ; right2 = 572
    call SetApen(HOST,3)
    call RectFill(HOST,left1,top,right1,bottom) 
    call RectFill(HOST,left2,top,right2,bottom)
    call SetApen(HOST,0) 
    call RectFill(HOST,left1+4,top+2,right1-4,bottom-2) 
    call RectFill(HOST,left2+4,top+2,right2-4,bottom-2)
    
    /* 
    Another array, 'v.', for vertical positions.  This array made it
    easy to adjust spacing during program development. 
    */
    do i = 1 to 14 ; v.i = 24 + i*12 ; end
    
    /* 'pat()' is a user-defined 'print-at' function.         */
    call SetApen(HOST,2) ; call pat(48,26,'EXPLORE THESE') 
    call pat(300,26,'WATCH FOR FEEDBACK HERE') ; call SetApen(HOST,1)
    call pat(16,v.1,'· The small sample menu')                    
    call pat(16,v.2,'· Alphanumeric keys')
    call pat(16,v.3,'· Help, F keys, etc.')
    call pat(16,v.4,'· Click outside and')    
    call pat(16,v.5,'    then inside window') 
    call pat(16,v.6,'· (Sensible) resizing')       
    call pat(16,v.7,'· Use the drag bar')    
    call pat(16,v.8,'· Remove and insert a disk')    
    call pat(16,v.9,'· Draw with the mouse')      
    /* 
    You can get a NEWPREFS message if, for example, you go into Printer
    Prefs and click on 'Save' (without necessarily changing anything). 
    I can't imagine ever wanting to use this feature, and don't know
    if it responds to new Prefs settings other than 'Printer'.      
    */
    call pat(16,v.10,'· Reset your PREFS')    
    call pat(16,v.11,'· Edit')           /* Two gadgets are drawn to */
    call pat(16,v.12,'· Click in')       /* complete these entries   */
    
    /* 
    Messages returned by gadgets are defined within the AddGadget()
    function call, not by ModifyHost().  
    */  
    call AddGadget(HOST,72,v.11 - 7,1,'this gadget','%l %d %g',94)    
    call AddGadget(HOST,104,v.12 - 7,2,'this gadget','%l %d %g')
    
    /* 
    The following list draws up labels ready for the data items which
    are to be obtained.  Once the list has been drawn, the seven 'arg.'
    entries as stored at the time of the first 'ACTIVEWINDOW' have no
    further function.   
    */
    call pat(248,v.1,'IDCMP class   ->  '|| arg.1) 
    call pat(248,v.2,'ASCII code    ->')
    call pat(248,v.3,'Button code   ->')
    call pat(248,v.4,'Gadget number ->')
    call pat(248,v.5,'Gadget 1 text ->') 
    call pat(248,v.6,'Menu item     ->')
    call pat(248,v.7,'Win. left/top -> '|| arg.3 || ' ' arg.2) 
    call pat(248,v.8,'... wide/high -> '|| arg.4 || ' ' arg.5)
    call pat(248,v.9,'Mouse x and y -> '|| arg.6 || ' ' arg.7)
    call pat(248,v.10,'Time          ->')
    call pat(248,v.11,'No. of idcmp classes activated  -> 0')

    /* 
    This is NOT a computer game, but a running score is displayed of how
    many different IDCMP classes out of the 16 you have used. I haven't
    been able to score more than 13.  What about the other 3?  If you
    activate 'CLOSEWINDOW', the program ends anyway. And I have never been
    able to understand, use, or in any way communicate with the IDCMP
    classes 'MOUSEMOVE' and 'DELTAMOVE'.  Suggestions are welcome.      
    */
    call SetApen(HOST,2)
    call pat(260,v.12,'Can anyone get more than 13?') ;call SetApen(HOST,1)
    
    /* 
    Messages returned by menu selection are defined within the AddMenu()
    function call, not by ModifyHost().  I should have been able to set the
    AddMenu messages as '%l %b %i' to obtain class, button, and item
    number, in which 'button' would return 'MENUDOWN' or 'MENUUP'. I have
    not been able to obtain a 100% reliable return from the '%b' sequence,
    and so I have now omitted it from the setting, and cover up for its
    omission by a hack which will be acknowledged when we get there.
    Item counting is always from zero.
    */  
    call AddMenu(HOST,'One and only menu')    
    call AddItem(HOST,'Item #0    ','%l %i','I')    
    call AddItem(HOST,'Item #1    ','%l %i','J')    
    
    /* 
    Initialisation of 'flag.' to mark which of 16 classes have been used.
    */        
    flag. = 0 ; nflags = 0
    
    /* --------------------------------------------------------- */
    /*                Start of the main loop.                    */
    
    do forever  
     call pat(384,v.10,time())  /* Cosmetic!  ARexx time(), not '%t', */
                                /* which returns secs since 1/1/1980! */
    
    /* Wait for a message. Receive a message. */    
     call waitpkt(PORT) ; p = getpkt(PORT)
    
    /* If it is not empty, read and acknowledge it. */
     if p ~== NULL() then do                
        i = getarg(p) ; t = reply(p, 0)
        if i = 'CLOSEWINDOW' then do ; call CloseWindow(HOST) ; exit ; end
        end
        if i = '' then iterate  /* Should not happen, but just in case.*/
    
    /* 
    If we get to here, a real message has been received. Its first word
    ought to be the name of the IDCMP class as coded by '%l'. Remove it by
    parsing and call the rest 'rest'.  In its turn, 'rest' will be parsed
    as required under each heading in the following 'select' set. 
    */
       parse var i class rest
    /* 
    A 'CHAR' check filters out any spurious x values which tend to sneak
    in here at the end of drawing with the mouse. 
    */
       if datatype(class) = 'CHAR'
        then do
    
    /* Display class; clean out the rest of any previous longer entry */
          call pat(384,v.1,class || copies(' ',23-length(class)))
    
    /* If this class has not yet been used, increment the score */
          found = 0 ; n = 0
          do until found | (n = 16)
            n = n + 1 ; found = ( class = i.n ) ; end
          if found & (flag.n = 0) then do
            flag.n = 1 ; nflags = nflags+1 ; call pat(528,v.11,nflags)
            end
        end
    /* 
                       Start of 'select'
    
             To deal with each 'class' as required.
    */
       select
        when index(class,'MENUPICK') then do
          call pat(384,v.3,'MENUUP    ')    /* One nasty little hack */
          call pat(384,v.6,'Menu item' || rest) ; end
    
        when index(class,'KEY') > 0 then do
          parse var rest asc char . 
          call pat(384,v.2,asc || '               ') 

    /* 
    VANILLAKEYS and RAWKEYS are covered here. They both return a number,
    and that number has a corresponding character, but since in the case
    of RAWKEYS that character is surely irrelevant to anything, it is not
    displayed.      
    */
          if index(class,'VANILLA') then 
              call pat(444,v.2,"Char -> "char || '    ') 
          end  
        
        when index(class,'MOUSEBUTTONS') then do
          parse var rest button x y . 
          call pat(384,v.3,button || '    ')    
          call pat(384,v.9,x || ' ' y || '  ')
          call SetAPen(HOST,2)    
          do forever                  /* 'do' = 'draw' */
    
    /* 
    This is where you can draw with the mouse, though there isn't much
    space left in the window to do so.  X and y values are continually
    being displayed, so the program is quite busy here, and the actual
    drawn response is not fast. 

    There is an oddity in the mouse behaviour which I just happened to
    notice.  I didn't plan it, and don't know why it happens, but it could
    be useful :

      While you are drawing with the left button down, if you click the 
      right button, and then release both buttons, you will be able to 
      continue to draw with [4mneither[0m button pressed. Pressing both
      buttons again will reset it so that you draw with the left button
      held down. 
    */  
            dx = x ; dy = y ; call RHost('%x %y') ; parse var thisarg x y . 
            if datatype(x) = 'CHAR' then leave 
            call pat(384,v.9,x || ' ' y || '  ')
            call Move(HOST,dx,dy) ; call Draw(HOST,x,y) 
          end            /* of 'draw forever'   */ 
          call SetAPen(HOST,1)     
          call pat(384,v.3,'SELECTUP  ')  /* Another little hack */
        end              /* of 'when'           */

    /* 
    In '(Sensible) resizing' the first word is to remind you that if you
    do something like resize down to small and then back up to original
    size you will lose most of the screen contents, since I have included
    nothing to safeguard them.
    The drag bar can be used to change the position of the window. I
    don't know why, but no message is transmitted when the drag bar is
    used unless the resize gadget has already been used.
      
    "index(class,'WINDOW')" should detect 'ACTIVE-', 'INACTIVE-' and
    'REFRESHWINDOW'.            
    */
         when index(class,'WINDOW') > 0 | class = 'NEWSIZE' then do
           parse var rest top left width height .
           call pat(384,v.7,left || ' ' || top || ' ')
           call pat(384,v.8,width || ' ' height || ' ') 
           end
   
    /* 
    Two demo gadgets supplied are a string gadget and a Boolean gadget.
    They respond in different ways.    
    */
         when index(class,'GADGET') then do
          parse var rest gadno gadtxt ; call pat(384,v.4,gadno) 
            if gadno = 1 then do
              if length(gadtxt) > 23 then gadtxt = left(gadtxt,23)
              else gadtxt = gadtxt || copies(' ',23-length(gadtxt))
            call pat(376,v.5,gadtxt) ; end 
          end
    
    /* 
    If you remove and insert a disk (if only to see what happens), the
    insertion will cause a message box to appear on the screen containing
    the sort of table you normally get from the AmigaDOS 'info' command. 
    It is a sample of the kind of task which can be initiated by the IDCMP
    tracing we are doing here.  A temporary file call 'Ram:inf' is created
    if no file of that name already exists.
     
    One line of the display is a bit untidy because of hidden tab
    characters, but I have done nothing to fix it.  

    A click on 'Okay' will remove the message box.    
    */
         when class = 'DISKINSERTED' then do
          msg = 'OKAY' 
          if exists('ram:inf') then
             msg = Request(30,30,'Overwrite existing file?',,'Okay','No')
          if msg = 'OKAY' then do
            address command 'info >ram:inf' 
            op = open(i,'ram:inf','r') ; t = '' 
            do until eof(i) 
              if ~eof(i) then t = t || readln(i) || '\' 
            end
            cl = close(i)
            msg = Request(30,30,t,,'Okay',)
            address command 'delete >NIL: ram:inf'
            end               /* of 'if .. then do'   */
          end                 /* of 'when .. then do' */
        otherwise ; end       /* end of 'select'      */
      end                     /* end of 'do forever'  */
     exit
    
    /*             User-defined functions             */
    pat:
     parse arg col, row, text 
     call Move(HOST,col,row) ; call Text(HOST,text)
    return    
    
    RHost:
     call ReadHost(HOST,PORT,arg(1)) 
     call waitpkt(PORT) ;  p = getpkt(PORT)
     if p ~==NULL() then do ; thisarg = getarg(p) ; t = reply(p,0) ;  end
    return thisarg
                            
    syntax:
      call CloseWindow(HOST)
      if rc ~= 0 then say "*** Error:" errortext(rc) "at" sigl
      exit
            /* -------------- End of program -------------- */     

    This program itself has little use other than as a learning and
    exploring tool, but some of its patches could be useful in other
    situations.
    
    Just before you go: 
    Can anyone clarify references to Arg[0], %0-%15, and Arg[3] in the
    'rexxarplib' docs section on OpenWindow?  Those [4mare[0m square brackets, 
    aren't they?  How [4mcan[0m they be used in ARexx?
    Even if they were ordinary brackets, Arg(0) would be invalid, since
    "the argument number given must be an integer greater than zero."

    [33;3mHamilton, N.Z.   June, 1993[0m
      
                -------------------------------------
