EXAMPLE_2_bas: Something more meaningful

This is a fully functioning application, the purpose of which is to display the configuration lines in your ProWesS config file, and to change a setting immediately.

It contains an edline object which lets you edit the name of the ProWesS config file: Either you HIT the Edline object, in which case you can edit the name directly, or you DO the Edline object, in which case it pops up a file select window and lets you select your config file.

Once the file name is given, the file is opened, a (cursory) check is made on whether it is a ProWesS config file, and then the content of the file, i.e. the config lines, are displayed. You can now click on a line, this is then proposed for editing, and the change is made.

The program first calls the set_windows procedure, to make sure that the outline is set if we are in the SBasic interpreter and to initialise the ysize% variable, which contains the y size of the screen. Then procedure test is called.

  set_windows:test
  :
  DEFine PROCedure test
  LOCal object_hit,hit%,lp%,none_yet$
  LOCal text$(2,20),file_name$(50),option_chosen
  LOCAL config_options$(120,80),my_option$(81)
  LOCal number_of_elements%,watermark$,box_left,loose_item
  LOCal fname_edline,infotext,sep_obj,menu_object,fs,add_info
  :
    rem note how I use a Local string variables here, since 
    rem the window will be gone when we leave this procedure!
  :
    rem first initialise some variables
  :
    watermark$="% configuration file for ProWesS"
    option_chosen=0
    mem=0:hit%=0:object_hit=0
    text$(1)="ProWesS Config"&CHR$(0)     : rem the title, and button text
    text$(2)="Config file: "&CHR$(0)      : rem label text
    none_yet$=""&CHR$(0)
    file_name$=none_yet$                  : REMark name of the config file

:
    rem now create all of the objects needed at first
  :
    outl=PWcreate(0,PW('TYPE_OUTLINE'),
                    PW('OUTLINE_SLEEP'),PW('OUTLINE_SLEEP_TEXT'),text$(1),
                    PW('OUTLINE_QUIT'),PW('OUTLINE_QUIT_KEYPRESS'),27,
                    PW('OUTLINE_TITLE_TEXT'),text$(1))
  :
    rem use chr$(27) = ESC as keypress for the quit item
  :
    ylines=PWquery(outl,PW('DEFAULT_FONTSIZE'))
    ylines=INT(ylines/65536)+6
    ylines=INT((ysize%-80)/ylines)
  :
    infotext=PWcreate(outl,PW('TYPE_LABEL'),PW('LABEL_TEXT'),text$(2))
  :
    fname_edline=PWcreate(outl,PW('TYPE_EDLINE'),
                               PW('EDLINE_MAXLENGTH'),50,
                               PW('EDLINE_SET'),file_name$,
                               PW('EDLINE_ACTION_AFTER'),HIT_ROUTINE,
                               PW('EDLINE_KEYPRESS'),CODE('c'),
                               PW('EDLINE_ACTION_DO'),DO_ROUTINE)
  :
    sep_obj=PWcreate(outl,PW('TYPE_SEPARATOR'))
  :
    menu_object=PWcreate(outl,PW('TYPE_MENU'),
                              PW('MENU_ITEMWIDTH_PIX'),480,
                              PW('MENU_VISIBLE_LINES'),ylines,
                              PW('MENU_ACTION_SELECT'),DO_ROUTINE,
                              PW('MENU_UNIQUE'))
  :
    fs= PWcreate(outl,PW('TYPE_FILESELECT'))
  :
    box_left=PWquery(outl,PW('OUTLINE_BOX_LEFT') )
    loose_item=PWcreate(box_left,PW('TYPE_LOOSE_ITEM'),
                                 PW('LOOSE_TEXT_COPY'),"Save",
                                 PW('LOOSE_ACTION_HIT'),HIT_ROUTINE,
                                 PW('LOOSE_KEYPRESS'),CODE('s'))
  :
  rem now the main loop
  :
    REPeat lp%
      mem=PWactivate(outl,mem,object_hit,add_info,hit%)
      IF NOT mem:EXIT lp%                 : rem prog is finished
      SELect ON object_hit                : rem let's see what was hit
        =fname_edline                     : rem object hit= filename editor
           SELect ON hit%                 : rem HIT or DO?
             =0
               file_name$=get_from_edline$ (fname_edline,0)
             =1
               file_name$=select_file$
               PWchange fname_edline,PW('EDLINE_SET'),file_name$
           END SELect
           load_the_file
        =menu_object                      : rem object hit was menu
           get_choice                     : rem get the string to configure
           IF LEN(my_option$)>1
             popup_window                 : rem popup win with this string
             set_option                   : rem do the configuration
           END IF
           PWchange menu_object,PW('MENU_STATUS_CURRENT'),
                                PW('STATUS_AVAILABLE')
        =loose_item
           save_the_file
      END SELect
    END REPeat lp%
  :
    PWremove outl     : rem this also removes all objects owned by this one
  END DEFine test
  :
  rem -----------------------------------------------------------------
Here, first of all, we create an outline, which contains a sleep item and a quit item. The quit item is also actioned by the ESC keypress (remember, ESC=CHR$(27)). The outline has a text in the title, which is nicer than just the name of the program (since that will always be 'SBasic' in the interpreter).

Please note that, for the sake of clarity, I have split up the parameters for the function over several lines. Unless you have the Basic Linker, you cannot do that, and should always have the parameters for the function on the same line.

We then create a label and an edline object. The edline object will contain the name of the file to edit. We set the maximum length of this object to 50 character, since a filename cannot be longer than that anyway, and set the text in the object to file_name$, which, for the time being, is still "no file yet".

After this, we set the ACTION_AFTER to HIT_ROUTINE, and the ACTION_DO to DO_ROUTINE. This is quite important, since it will allow us to distinguish between a hit and a do on the object. If the object is hit, then you can edit the name, and after you finished editing the name, HIT_ROUTINE will be called, which then returns to SBasic, showing that the object was "hit". If you "DO" the object, you cannot edit the name, since DO_ROUTINE will be called instead. This also returns to SBasic, showing that the object was "done".

We then create a separator (a nice line) and a menu object. The width (440 pixels) and the heigth of the menu object are also set. The heigth has been calculated by dividing the screen y size by the fontsize of the default font. This is an attempt to make sure that the window always fits the screen whilst displaying a maximum amount of information for those having a larger screen. The size of the default font is obtained with the DEFAULT_FONTSIZE query tag which returns a number that must be divided by 65536 to obtain the fontsize. The menu object is not (yet) filled in, so it is just empty. Notice the MENU_UNIQUE tag.

After this, we create a fileselect object which we will need later. and, last but not least, we set up the loose item with which to save the file. We put this in the empty box to the left of the title, so we first of all get the object which is the container to the left of the title, with the PWquery. Then we put the loose item into that container by making the container the owner of the loose item.

The keypress for the item will be 's', so we use CODE('s') as keypress parameter.

Then we set up the main loop of the program. Notice how mem was set to 0 first. As usual, the main loop activates the object. This will await all of your actions and return with mem set to 0 if you quit the program (or break the window down with PWbreak), else mem will be <>0. If you quit the program, we leave the loop, remove the object, and the program just stops.

Else, we determine what object was hit or done, with the SELect. If the object actioned was the filename edline object, we further determine whether it has been hit or done. If it has been hit then the filename was edited first, so we just get this filename from the object. If this object was "done", then we call the select_file$ function, which opens the fileselect window. We will come to that in a moment. After that, we call the load_the_file procedure, which does as its name suggests...

You will also note that most variables are LOCal variables, apart from outl and mem, of course. These variables are all visible in all other procedures/functions called by the test procedure but will not interfere with any variables used in function that might call test - useful if you would want to incorporate this into one of your programs... (How's this for scope of variables? Eat your heart out, 'C'!).

Now we can examine the procedures or functions called:


  rem -------------------------------------------------------------------
  rem the following 2 functions get the filename, they are called 
  rem by a hit or a do on the edline object
  rem -------------------------------------------------------------------
  :
  DEFine FuNction get_from_edline$ (object,strip_chr0%)
  rem this gets the text from the edline object and returns it
  rem if strip_chr0% is set, the CHR$(0) at the end of the string is
  rem stripped off
  rem the max length of the string is 80 characters
  LOCal my_string$(81)
    PWchange object,PW('EDLINE_GET'),80,my_string$ : rem get str frm edline
    IF strip_chr0%
      MKLEN my_string$                    : rem set new length w/o chr$(0)
    ELSE
      MKLEN0 my_string$                   : rem set new length keep chr$(0)
    END IF
    RETurn my_string$                     : rem and return the string
  END DEFine get_from_edline$
  :
  DEFine FuNction select_file$
  rem this activates the fileselect object and
  rem gets the string (composed of dirname + filename) from
  rem the fileselect object
  LOCal f_name$
    PWchange fs,PW('FILESELECT_ACTIVATE') : rem activate fileselect window
    f_name$=PWquery(fs,PW('FILESELECT_DIRECTORY'))
    f_name$=f_name$&PWquery(fs,PW('FILESELECT_FILENAME'))
    return f_name$&chr$(0) 
  END DEFine select_file$
  :
The get_from_edline$ function gets the text from the edline object, using the PWchange command. For various reasons one doesn't use the PWquery function for this, which would have been more logical. Notice how I use a local one-dimensional array set to the maximum length of a filename to get this string. Once I have got the string, I make it the correct length for SBasic, either stripping off the CHR$(0) at the end or not, depending on the strip_chr$% parameter to the function. The string is then returned to the caller. This function is used directly from the main loop if the edline object was hit, since in that case the string had been edited in that object first.

The select_file$ function is called from the main loop when the edline object was "done". First of all, it activates the fileselect object which is set up in the procedure test. This activation is achieved, curiously enough, with a PWchange, and not an PWactivate as one would expect... The fileselect object is displayed, and will come back if you select a file (with DO) or quit. Then we query the fileselect object about the directory of the file, and the filename (which does not contain the name of the directory).

When you query a fileselect object for the directory and the filename, it returns a string directly, which is one of the few exceptions to the PWquery function which normally returns a value.

Please note that, if we had set up the fileselect object in such a way that it allows multiple filenames to be selected, the object returned (for the filenames) would have be a menu object (i.e. a value), not a string. One can then query the menu object for the files.






  rem --------------------------------------------------------------
  rem the following procedure displays a small 'please wait' window & opens
  rem the file, reads in the options and changes the menu object
  rem -------------------------------------------------------------------
  :
  DEFine PROCedure load_the_file
  REMark this displays the 'please wait' window
  REMark and loads the file
  LOCal lp%,a%,a$,f$,wait$,loop%,dummy,dummy%
  LOCal lf_info
  LOCal lf_out,lf_mem
  :
    wait$="Reading file - please wait"&CHR$(0)
    lf_mem=0:dummy=0:dummy%=0:number_of_elements%=0
  :
    REMark now create the objects
  :
    lf_outl=PWcreate(0,PW('TYPE_OUTLINE'),
                       PW('SYSTEM_ACTION_INIT'),INIT_ROUTINE)
  :
    lf_info=PWcreate(lf_outl,PW('TYPE_INFOSTRING'),
                             PW('INFOSTRING_TEXT'),wait$)
  :
    REMark now the main 'loop'
    REPeat loop%
      lf_mem=PWactivate(lf_outl,lf_mem,dummy,dummy,dummy%)
      IF NOT lf_mem:EXIT loop%            : REMark quits after PWbreak
      f$=file_name$(1 TO LEN(file_name$)-1) : REMark strip off chr$(0)
      a%=FOP_IN(f$)                       : REMark open file
      IF a%<0
         err_window a%,""    
         PWbreak lf_outl
         NEXT loop%  
      endif
      INPUT#a%,a$
      IF a$<>watermark$
        CLOSE#a%                          : REMark make a small check
        err_window 0,'This is not a ProWesS config file'
        PWbreak lf_outl                   : REMark oops...
        NEXT loop%
      END IF
      REPeat lp%
        IF EOF(#a%):EXIT lp%
        INPUT#a%;a$
        config_options$(number_of_elements%)=a$&CHR$(0)
        number_of_elements%=number_of_elements%+ 1
      END REPeat lp%
      CLOSE#a%
      PWbreak lf_outl
      PWchange menu_object,PW('MENU_CLEAR')    : REM get rid of old items
      PWchange menu_object,PW('MENU_ADD_ARRAY'),
                             number_of_elements%,
                             DIMN(config_options$,2)+2,
                             config_options$
    END REPeat loop%
    PWremove lf_outl
  END DEFine load_the_file
  :
  REMark
-------------------------------------------------------------------

We now come to the procedure load_the_file. here, I want to display a small window that says 'reading file, please wait', and then reads in the configuration lines of the file, makes the menu, closes the small window and returns to the main loop.

I won't comment on the part that concerns the actual loading of the file, and putting the lines of the file into an array, except to say that this routine expects the line "% configuration file for ProWesS" to be the very first line in the file, else it doesn't believe that this is a ProWesS configuration file. Then, and also if there if a file error, it calls the err_window procedure, which we will comment later.

The load_the_file procedure is interesting since it uses the SYSTEM_ACTION_INIT tag. This is a tag which is followed by a routine, in our case INIT_ROUTINE (we could actually have used any other of the xxxx_ROUTINEs in this example). This routine is called as soon as the window is drawn by the Pwactivate keyword. In our case, INIT_ROUTINE is called which does a (fake...) exit to SBasic. So, as soon as the window is drawn, control is passed back to SBasic, which is just what we want, since we don't want to wait for the user to hit a key, we only want to display the window whilst working.

This function doesn't need the normal object, add_info and hit_or_do% parameters in the PWactivate call since we don't care about the object returned etc.. So we just use some dummy variables instead.

This way, we come back after the PWactivate call, and then read the file etc... Once this is finished, we use the PWbreak command. This tells the system to remove the window from the screen at the next PWactivate call, and come back to SBasic with lf_mem set to 0, as if the user had pressed "quit".

Then we set the content of the menu, after having cleared it to make sure that nothing remains of a possible earlier file. The MENU_ADD_ARRAY tag needs three parameters: the size of the array (number of elements and maximum length of an element), and the array itself. I use number_of_elements% to set the number of elements. I could have used DIMN(config_options$,1)+1 since that gives the number of elements in the array, but it will cause the menu object to display several empty lines at the end, since the config_options$ array is probably not filled in entirely.

After this, we come to the end of the loop, i.e. we go back to the beginning, into PWactivate and then come back from this immediately since we had used PWbreak. We then leave the loop, and the procedure. The menu object is automatically redrawn, and shows the configuration options.

You will notice that I have made the outline and the memory pointer (lf_outl and lf_mem) into LOCal variables. I ONLY DID THAT ONCE I HAD THOROUGHLY DEBUGGED THIS PART OF THE PROGRAM. Always remember to let the variables for the outline and the memory pointer accessible.

Now the configuration options are displayed, and you can hit one of them. The PWactiavte call in the procedure test will then come back, showing that the object hit was menu_object. This then calls, one after the other, the procedures get_choice, popup_window and set_option. The latter two are only called when the option chosen was not an empty line - see the line IF len(my_option$)>1


  rem -------------------------------------------------------------------
  rem the following procedure gets the choice hit in the menu
  rem -------------------------------------------------------------------
  :
  DEFine PROCedure get_choice
  rem get the choice hit in the menu
  LOCal address, a%
    option_chosen=0
    address=PWquery(menu_object,PW('MENU_SELECTED_FIRST'))
    IF NOT address:RETurn                    : rem oops!
    my_option$=MKSTRING$(address)&CHR$(0)    : rem get the string
    option_chosen=arrsrch(config_options$,my_option$,0,0)
  END DEFine get_choice
  :
  rem -------------------------------------------------------------------

This first asks the menu object what is the first item selected in the menu. This must always be the item just selected, since I have made sure that only one item can be selected at a time (the MENU_UNIQUE tag when creating it). With this tag, the menu object does not return a number, but a pointer to an address where the string concerning this option lies. We use MKSTRING$ to make this address into the string my_option$ and we also find this string in the array using the arrsrch function (which I won't explain here, it should be obvious) so that we know which element in the array we are talking about.


  rem -------------------------------------------------------------------
  rem the following proc pops up the window where the string can be edited
  rem -------------------------------------------------------------------
  :
  DEFine PROCedure popup_window
  rem this pops up the window in which the string can be edited
  LOCal dummy,dummy%,lp%,string$
  local cfg_edline
  local pop_outl,pop_mem
  :
    string$='Please edit this string...'&CHR$(0)
    pop_mem= 0
  :
    pop_outl=PWcreate(0,PW('TYPE_OUTLINE'),
                        PW('OUTLINE_TITLE_TEXT'),string$ )
  :
    cfg_edline=PWcreate(pop_outl,PW('TYPE_DIRECT_EDLINE'),
                                 PW('EDLINE_MAXLENGTH'),80,
                                 PW('EDLINE_SET'),my_option$,
                                 PW('EDLINE_ACTION_AFTER'),HIT_ROUTINE)
  :
    rem now the main 'loop'
  :
    REPeat lp%
      pop_mem=PWactivate(pop_outl,pop_mem,dummy,dummy,dummy%)
      IF pop_mem=0:EXIT lp%
      my_option$=get_from_edline$(cfg_edline,0)
      PWbreak pop_outl          : rem show we don't want the window anymore
    END REPeat lp%
    PWremove pop_outl           : rem get rid of this window
  END DEFine popup_window
  :
  rem -------------------------------------------------------------------

Now we have chosen our option to configure, we pop up (or pull down) a small window which contains an edline where you can configure the option. string. Once this is done, we come back to SBasic because of the EDLINE_ACTION_AFTER tag and get the string from the edline via the get_from_edline$ function. We then break down the window and leave after removing this object.

It now only remains to set the option:

  rem -------------------------------------------------------------------
  rem the following procedure sets the configuration choice
  rem -------------------------------------------------------------------
  :
  DEFine PROCedure set_option
  rem this does the actual configuration with PWconfig
  LOCal string$,p$
    p$="prowess"
    string$=my_option$         : rem this is necessary, 
                                 pwconfig changes the string!
    PWconfig p$,string$
    config_options$(option_chosen)=my_option$
  END DEFine set_option
  :

This uses PWconfig to set the option. Notice how I copy this to a local variable first, since ProWesS changes the string passed to it via the configuration thing. I then update the string in my array, and reset the status of the item hit to available.

If there is an error loading the file, we call the procedure err_window:


  :
  REMark ----------------------------------------------------------------
  REMark the following procedure pops up an error window
  REMark ----------------------------------------------------------------
  :
  DEFine PROCedure err_window (error%,error_string$)
  REMark this makes the error string if that isn't set,
  REMark and sets it in the infostring item
  LOCal dummy,dummy%,lp%,string$(1,40)
  LOCal err_outl,err_mem
  :
    string$(0)='Attention, error:'&CHR$(0)
    IF error%
      string$(1)=ERRSTR$(error%)          : REMark make error string
    ELSE
      string$(1)=error_string$&CHR$(0)
    END IF
    pop_mem= 0
  :
    err_outl=PWcreate(0,PW('TYPE_OUTLINE'),
                        PW('OUTLINE_TITLE_TEXT'),string$,
                        PW('OUTLINE_QUIT'),PW('OUTLINE_QUIT_KEYPRESS'),27)
  :
    info1=PWcreate(err_outl, PW('TYPE_INFOSTRING'),
                             PW('INFOSTRING_TEXT'),file_name$)
    info1=PWcreate(err_outl, PW('TYPE_INFOSTRING'),
                             PW('INFOSTRING_TEXT'),string$(1))
  :
    REMark now the main 'loop'
  :
    REPeat lp%
      err_mem=PWactivate(err_outl,err_mem,dummy,dummy%)      IF err_mem=0:EXIT lp%
    END REPeat lp%
    PWremove err_outl           : REMark get rid of this window
  END DEFine err_window
  :
  REMark -------------------
  :
As you can see, this is nothing special, we make the window on the fly and draw it with PWactivate. Since the only thing the user can do is hit QUIT, there is no need for a SELect within the loop. The error message, if it is a file error, is made with the ERRSTR$ function.

There is also the procedure save_the_file which is modelled on the procedure load_the_file, so there is no need to print it here in full again.

Finally, there is also the source code for the ProWesS Config program. This is closely modeled on this example, but with enough differences to make it interesting to look at!


PROGS, Professional & Graphical Software
last edited 1996 May 30 (wl)