Window and Desktop Manager For Micol Advanced BASIC IIe/c Updated for DeskTop Manager V2.0 Updated for Window Manager V2.0 The Micol Window Manager is a set of ROUTINEs and PROCEDUREs which any Micol Advanced BASIC IIe/c programmer can use to add and remove text "windows". These windows can be placed anywhere on the 80-column screen, without destroying the contents of the area of the screen over which the "window" is placed. When the window is removed, the area of the screen over which the window was placed is restored to how it looked before the window covered it. This releases a vast array of possibilities to the programmer, including pop-up context sensitive help screens, a Finder-like program launcher, an Appleworks-type interface, and more. You'll need to be fairly familiar with Micol Advanced BASIC before you'll be able to really use this. The DeskTop Manager uses the Window Manager to simulate a simple Apple Human User Interface DeskTop Metaphor; ie. windows, pull down menus and buttons. The Desktop Manager can be used in your own programs to add a desktop interface to your programs, making your programs easier to use and more professional looking. For those of you not familiar with Micol Advanced BASIC, or other structured languages, this file will give you a chance to see for yourself the many advantages of a structured language, and the advantages of Micol Advanced BASIC in particular. Modularization, better use of memory, local and global variables, and no line numbers are all used in these programs, and this will give you a chance to see how they operate to make your job easier, and your programs better. The Window and Desktop manager programs are written using Micol Advanced BASIC. You build your own programs using these files and "templates". The templates provide the functions required to do the housekeeping associated with window/desktop maintenance, and the code you add makes up the actual functionality of the program. The templates use the INCLUDE command to "bring in" a number of support files at compile time. Except in a few cases, you will rarely need to load or modify these files. However, the compiler MUST be able to find these files at the active PREFIX at compile time. These files are: If you are using the Window Manager or the Desktop Manager: Window.Globals Window.Routines Window.DIMS Window.Procs [1] If you are using the Desktop Manager: Desktop.procs Deskt.routines Desktop.DIMS Desktop.Globals If you are using the Viewer: Viewer.DIMS Viewer.Procs Viewer.Routines The next sections will describe how to use the Window Manager and the Desktop Manager. The Window Manager The Window Manager consists of a set of ROUTINEs, FUNCtions and PROCedures which save and restore screens to and from the computer's memory. Also supplied is a template which you can use to build your own programs which use the Window Manager. The theory of operation is rather simple... every time we add a new window, we grab the section of the screen which the window will be covering, and stash it in memory somewhere out of the way for safe storage. We also save related information, such as the cursor's current location and the current screen margins. And we repeat this for each new window added. Later, when we want to remove a window, we 'pop' it off by simply restoring the saved screen image and related information. Since the screen image was saved before the window was drawn on the screen, poof! the window disappears as the old screen is re-drawn. Pretty simple, huh? The only really small complication is that the text screen, as we see it, is actually sitting in two different areas of memory. Every even column is stored in 'Main Memory', and every odd column is stored in 'Auxiliary Memory'. Auxiliary memory is the alternative area of memory which is virtually impossible to access from Applesoft, since accessing it means "switching out" the area of memory where Applesoft sits. However, with Micol Advanced BASIC, we don't have that problem. With the MOV_MEM command, we can easily grab anything from the auxiliary memory, and move it anywhere we want. If this is confusing you, don't worry, it's not really required that you understand this. This Window manager is also "smart" in the sense that it only saves the area of the screen that is under the new window we are about to add. We could make our job much easier by just saving the whole screen, but the whole screen uses almost 2k of memory, and that means every time we added a new window, we would eat up 2k of program space (of course, every time we popped a window, we gain that space back). That means that even with only 5 small windows open on the screen, you would be using 10k of valuable memory. This may not mean much, since Micol Advanced BASIC IIe/c gives you 72k of memory, but on an Apple II it doesn't pay to take the easy way out [2] at the expense of memory. By saving only the area covered by the new window, we can fit two to three times as many saved windows in the same amount of memory. The Window Manager is separated into a few files which are INCLUDEd into one large file. This helps to keep your source code smaller and relevant (you don't see all the window ROUTINEs, etc in your source code). Here's a break down of each file: 1) Window.MGR: This is the main program which INCLUDEs the others. Use the program as a 'template' to build your own programs in. 2) Window.globals: This file contains all the global variables and their starting values. Do not use any of the variables in this file for other purposes, unless you are damn sure of what you're doing. 3) Window.PROCs: This contains PROCedures which the window manager uses. All of the variables in these PROCedures are local (except for those in Window.globals). Be careful not to accidentally make a global variable with the same name as one of the variables used in these PROCedures. 4) Window.ROUTINEs: These are ROUTINEs which the window manager uses. None of these ROUTINEs use any variables. Since ROUTINEs are quicker to call than PROCedures, we made these ROUTINEs (rather than PROCedures) to make them a bit quicker, and since there was no danger of accidentally messing with any of your program's variables. First a little window terminology. The "title bar" is the bar at the top of the window which contains a name for the window. On the left side of the title bar, you'll see a little square, which is the "close box". The close box is where a user with a mouse will click when they want to "close" the window, and generally has the same result as pressing ESC. The close box is drawn on the window, but isn't used by the window manager. The mouse is only supported by the Desktop manager. The topmost window will always be "active", which is where all the action will take place, such as printing. The active window will have a solid title bar. Non- active windows will have a "dimmed" title bar, which is not solid. (Hard to describe, but you'll understand when you run the demo). Ok, now here's a short summary of how to use the window manager, and how it works (its flow of control): 1) Add a new window: To do this, just do a GOSUB Add_Window, passing it a title for the window, and the top, left, bottom and right screen margins in that order. If you want, pass a null string ("") as the Title, and there will be no title for the window in the window's title bar. Alternatively, pass a "@" for the title, and no title bar at all will be drawn. This is useful for special types of windows, as we'll see later in the DeskTop manager. 2) Add_Window calls a PROCedure which saves the current screen's information and "dims" the title bar of the currently active window. It then calls a PROCedure which checks the screen margins you passed it Add_Window insure they are legal, and then sets some variables to these values for future reference. Next, it calls the Save_Screen PROCedure. [3] 3) Save_Screen is the heart of the system, it is what actually grabs the screen information and stashes it for later. It figures out how many bytes wide your window is, and how many rows down it is. It divides the amount of bytes across by two because every second byte is stored in auxiliary memory, which means if your window is 80 bytes across, 40 bytes will be in main memory, and 40 will be in auxiliary memory. Next, it goes down each row of the screen area to be saved, determines the memory address for that line, and then transfers the appropriate number of bytes to the "safe area" buffer, first from Main Memory, and then from Aux Memory. This is repeated for each line. The buffer to which the lines are moved resides at the very "top" of program space in main memory, directly below some library routines. The starting address is stored in the global variable "start%" which you will find in the "window.globals" file. This variable may need to be changed if the library grows, however, you will be informed of this in the supplement notes on any new releases of the compiler, and unless you recompile your programs with the new version, you will not need to change older programs. Anyway, as Save_Window moves bytes into this buffer, it increases a variable called "used%" which basically tells Save_Screen how many bytes have been used, and where the new destination for transferring bytes is (basically, at Start% - Used%). WARNING: Your programs build up from the bottom of program space, and the windows build down from the top of program space. If you have a large program, and then save many large windows, your window space could run into your program space, destroying your program! There is only one method to detect this. In the "Window.globals" file, you will find a variable called "Top_of_prog". You can set this variable to the topmost memory location used by your program, and the window manager will ensure that your program is not overwritten by saved screens (an error will be generated instead). The problem with this method is that during development, the length of your program will be constantly changing. Make sure you set this variable to be GREATER than the "top of your program". If you don't want any protection, you can set this variable to be equal to 0. To set the variable, compile your program, and watch for the information displayed after compilation. The first line of the information will contain the number of bytes generated. Then, load the file "WINDOW.GLOBALS" into your editor, and change the value of "Top_of_prog" to reflect this number, plus whatever LOMEM is set to. LOMEM defaults to $900, so unless you change it (see the LOMEM compiler option), you would set the variable to $900 + (NUMBER OF BYTES COMPILER GENERATES). 4) Once Save_Screen has done its thing, control is returned to Add_Window, which then calls a ROUTINE to reset the window margins to the maximum (so that it may draw anywhere on the screen, uninhibited). Then it draws your new window, and calls a ROUTINE to set the margins to the values you supplied for your new window, does a HOME, and presto! You've got your own window with which to do what you like. 5) Do whatever you want. The window will act like a miniature version of the screen. However, be aware that VTAB and HTAB still act globally on the whole screen, not relative to the top left corner of your window. 6) Add more windows if you want. The process repeats from Step #1, using more memory. 7) If you want to remove a window, call Pop_Window. This PROCedure basically does exactly what Save_Window does, except in reverse. First, it [4] figures out the number of bytes across for the current window, and halves it as before. Then, it transfers the memory back TO the screen FROM the buffer, and SUBTRACTS the number of bytes moved from Used%. In doing so, it restores the image of the screen from before the new window was added, effectively erasing the most current window. Next, Set_Window is called to restore the screen margins of the previous (and now current) window, and finally the menu bar (if it exists) of the now current window is highlighted to show it is active. 8) Again, add more windows, or pop them, as you please. Your only restrictions are: a) The amount of memory you use, as mentioned above, and; b) The Window Manager is currently set up to handle only 20 windows at once. This can easily be changed by increasing the value of the DIM statements in the Window.DIMS file. Increasing these will take memory away from your variable space, so try to keep them within reason. That should give you a pretty good idea of how the Window Manager works... if you're still unclear about anything, take a look at the source code. It's really easy to follow. Notes on the Window Manager: The window manager contains a ROUTINE called Quick_Window. Doing a GOSUB Quick_Window will give you a medium sized window in the centre of the screen. This is handy for many things, especially debugging. For instance, using quickwindow, you can get a window on the screen, print a few variables, wait for a keypress and then pop the window, and continue with your program. This allows you to 'peek' at the values of variables without disrupting the normal display of your program. How to USE the Window Manager: Use the file Window.MGR as the main template for your program. Fill in the blanks where appropriate, to add your own PROCs, ROUTINEs, etc. There are 8 PROCedures, FUNCtions, and ROUTINEs which you can call to handle housekeeping tasks. They are: 1) Add a window GOSUB Add_Window [ Name$, Top%, Left%, Bottom%, Right% ] Use this call to add a window to the screen, having the name you specify and the margins you specify. Note, the left margin must be an ODD number, and the right margin must be an EVEN number. If you pass Add_Window a Name$ of "@", the window will not have any title bar. Example: GOSUB Add_Window ["Hello World",5,7,15,60 ] This will create a window in the middle of the screen, with the title "Hello World" in the menu bar. The cursor will be placed in the top left hand corner of the new window. [5] 2) Remove topmost window GOSUB Pop_Window Use this call to remove the most recent window added (the top window). There are no parameters for this call. Example: GOSUB Pop_Window 3) Quickly add a window without having to specify size: GOSUB Quick_Window Use this call, as mentioned above, to put a window on the screen. Useful when you want a window, but don't want to be bothered with its name and location. Example: GOSUB Quick_Window 4) Close all Windows GOSUB Close_All Use this call to close all open windows, if any. Example: GOSUB Close_All 5) Print a string variable centred in a window. GOSUB Centre [ String$ ] Use this PROCedure to print a string centred in the currently active window. You only need pass it either a literal string (as in the example below), or the name of a string variable (as in the example above). This is useful, since you will often want to centre something in a window, and you don't always want to have to calculate the window's dimensions. Example: GOSUB Centre [ "This will be centred" ] 6) Format and Print a text string. Text$ = "whatever......" (up to 255 characters) GOSUB FPrint (or GOSUB FPrintx) Often you will want to print information in a window, but avoid having it "wrap-around" the sides of the window, breaking up words in the middle. The print formatter will print a text string, inserting a RETURN on each line as close to the right hand margin, without having any words wrapping around. This adds a very professional look to your program. [6] To use the print formatter, first assign what you would like printed to the global variable Text$. Then use GOSUB to call FPRINT. There is a variant on FPrint, called FPrintx that you can also use. FPrintx is essentially the exact same as FPrint, except it leaves the cursor positions immediately after the last word printed, rather than on the beginning of the next line. Here's an example: Text$ = "1" GOSUB FPrint Text$ = "2" GOSUB FPrint This will print: 1 2 Here's a different example: Text$ = "1" GOSUB Fprintx Text$ = "2" GOSUB Fprintx This will print 12 and will leave the cursor positions immediately after the "2" The Desktop Manager The Desktop Manager uses the window manager to simulate a "desktop metaphor", based on Apple's Human Interface Guidelines. The desktop manager lets you write programs with a professional pull-down-menu interface. The Desktop manager also integrates full mouse control into the user interface. First some desktop terminology. The bar across the top of the screen from which menus are pulled down is called the "Menu Bar". The individual items in the menu which can be selected are called "menu items" (pretty creative, eh?). The little squares which you can click on for an action (such as the square in the top left hand corner of a window) are called "buttons". The one in the top left hand corner of a window is called the "close button", because when you move the mouse cursor over it, and click on it, it will close the window. Because not all users have a mouse, every button will have an associated "hot-key". The hot-keys are displayed next to a button, and tell the users what key to press to get the same action as clicking on the button with the mouse. Hot-keys are always a combination of holding down the Open-Apple key and pressing a regular key. Sometimes it is necessary to prohibit access to a certain menu item or an entire menu. When we do this, we say we "dim" the menu or menu bar. This is from the Macintosh, where it is possible to actually display these menus and menu items dimmer on the screen than the other items, in order to indicate that they are not valid choices. On the text screen of the Apple II, it is impossible to display dimmed text, so instead we display a "**" in front of the menu or menu item to indicated it has been dimmed. Menus and menu items which have a "**" in front of them are not valid choices, and the menuing system will not allow you to choose them. Using the desktop manager is not easy to program. It requires a whole [7] new style of programming, a new way of thinking through the logic of your program. Traditional programs have a "straight-through" flow approach, where execution of the program flows downwards through a program, and progresses from beginning to end, with the user's input modifying the outcome. Desktop programs use an "event loop", which is a central part of the program which looks for keyboard/mouse activity and reacts to it by calling certain ROUTINEs and PROCedures. A programmer has very little control over the flow of the program... the user drives it. You must design your programs with this fact in mind that you may never know where it's headed next. As more of how the desktop manager is explained, you will begin to understand this better. Using the desktop manager also means supporting the mouse. Supporting the mouse has some very specific requirements. The reasons for these requirements are rather technical, but basically have to do with how the mouse driver saves and restores the characters which the mouse cursor covers as it moves around the screen. There are two aliases "~Mouse ON" and "~Mouse OFF", which you can use to turn the mouse cursor on and off. YOU MUST ALWAYS TURN THE MOUSE CURSOR OFF WHEN YOU USE PRINT TO PRINT TO THE SCREEN. IMMEDIATELY AFTER PRINTING, YOU MUST ALWAYS TURN THE MOUSE CURSOR BACK ON. If you use ANY of the Window Manager or Desktop Manager functions (such as GOSUB FPrint to print formatted output to the screen), these requirements are taken care of for you. You only need to shut the mouse cursor off yourself if you use a PRINT statement. This may sound like a large task, but you'll discover that you won't be using a raw PRINT statement too often, but you'll be using the Window and Desktop manager functions instead. You'll build your programs around the DT.MGR template. The desktop manager template also uses files from the window manager. You'll need all the Desktop Manager and Window manager files available on the disk to which the prefix is set when you compile your programs. Customizing the template isn't too difficult. You'll find it commented to tell you where to add your own routines, etc. The difficult part is to design a program which can operate successfully under the desktop environment. Here are a few things which you should consider: 1) The heart of the system is something called the Event Loop. The event loop allows the user to choose menus and items, as well as enter keyboard commands. Your program should be calling the event loop procedure constantly and consistently. (More info about how to actually call it will come later). If you are processing or waiting for input, you should include frequent calls to the event loop. 2) The event loop acts independently. For instance, let's assume your program is doing some work, but frequently calling the event loop, as required. While the program is executing the event loop, let's say the user moves the mouse and selects an item from one of the menus. Every item in the menus has an associated ROUTINE or PROCEDURE which the event loop knows about. When the user chooses an item, the event loop AUTOMATICALLY transfers control of the program to the ROUTINE or PROCEDURE which has been specified to handle that menu item. Therefore, your program could be in the middle of executing one routine, when suddenly execution is transferred to a different routine. Can your program handle this? 3) To make matters even more complicated, let's say the user chooses the item in the menu which your program is ALREADY running. Since Micol Advanced BASIC supports recursion, program execution will be transferred to the top of the same PROCEDURE or ROUTINE which you are already running. Can your PROCEDURE or ROUTINE handle this? If so, it is what is known as "re-entrant", and you are probably a very good programmer. Most ROUTINEs [8] and PROCEDUREs are not re-entrant. Imagine what would happen if your ROUTINE or PROCEDURE called itself. If it would work OK, then it is re- entrant. Luckily, there is a way to dim menu items so that they cannot be selected. We suggest that the first thing you do in your ROUTINEs and PROCEDUREs which are invoked by menu items is to DIM THE MENU ITEM(s) WHICH CAN INVOKE IT to prevent it from being called again, unless the particular ROUTINE or PROCEDURE is re-entrant. The DeskTop manager has essentially twelve main functions. These functions will be explained in depth a little later, but here's a list of what they are: 1) Add a menu. 2) Remove the last menu added. 3) Dim a menu. 4) Undim a menu. 5) Add an item to any menu. 6) Dim an item in a menu. 7) Undim an item in a menu. 8) Add a "Desktop Window" 9) Remove a "Desktop Window" 10) Add a button onto the screen 11) special INPUT routines for the desktop environment 12) The Event Loop Included on the disk the file "Mouse.Demo", this is a demo system which shows how to use the Desktop Manager. Here's how the demo system works: 1) The system starts up and calls the ROUTINE Draw_Desktop, which draws the desktop using a MOV_MEM to copy the lines, so it draws it quickly. 2) Next it calls the ROUTINE Set_Up_Menus, which installs some demo menus so you can see how the system works. 3) Set_Up_Menus shows you how to add menus and menu items. To add a menu, use the FUNCtion Add_Menu. It will add a menu with the name you pass to it, and will return a menu ID number. Use this menu ID # to access that menu later. To add an item to a menu, use the FUNCtion Add_Item. Just pass it the menu ID # of the menu you want to add the item to, and the item name. It will return an unique item ID # for the item, and you will use this ID # later to identify the item you just added. Using Add_Menu to create a menu with a title "@", it will appear as a closed-apple symbol in the menu bar. Using Add_Item to create a menu item called "-" will put a 'separator' in the menu... a line of dashes to break the menu up into smaller parts. Here's a short description of the variables used by Add_Menu: Menus% : Contains the total number of menus. Menu_Position%(Menu#) : Stores the position across the menu bar for each menu. Next_pos% : Temporary storage for Menu_Position% Menu_Item_Name$ (Menu#,Item#) : This string array holds the name of menus and menu items. Menu names are stored under Menu#0, with Item# being the menu numbers. Therefore Menu_Item_Name$ (0,2) is the name of the second menu, and Menu_Item_Name$ (2,1) is the name of [9] the first menu item in the second menu. Menu_Bar_Items% (Menu#) : This integer array holds the number of items in each menu. Menu_Bar_Items%(0) contains the total number of menus. Menu_Number% : a global variable containing the most recently accessed menu. Add_Menu also calls the ROUTINE Write_Menu_Bar_Entry, a short ROUTINE which writes the menu's name in the menu bar, according the Menu_Position% of the most recently accessed menu (Menu_Number%). The PROCedure Add_Item uses basically the same variables as Add_Menu, except it also uses Item_Identifier%, which basically = (100 * the menu #) + the item #, to make it unique. 4) The program then calls Intro which draws a single window on the screen and welcomes the user. Then, the flow of control passes to the ROUTINE Event_Loop. Event_Loop calls a function which checks to see if there are any events to report, such as a keypress, or a menu item being chosen. Get_Event checks to see if the Closed-Apple key has been pressed, or if the user has clicked the mouse on the menu bar. If the answer is YES to either of these situations, then Get_Event calls the FUNCtion Chooser, which lets the user chose a menu item, and returns the item ID# of the user chooses. If a 0 is returned by the FUNCtion, then the user pressed ESC in the chooser and did not choose any item. 5) The chooser highlights a menu name in the menu bar across the top of the screen. The user can press the left or right arrow to switch to the next menu in the respective direction, or ESC to exit. If the user presses the down arrow or RETURN key, the menu pops down, and the a menu bar appears, allowing the user to choose an item in the menu. Here's where that trick of placing a "@" as a window title mentioned earlier comes in handy. These menus which pop down are really just windows without any title bar. While in the menu, the user can press ESC to exit back one level to choose a new menu, or the up and down arrows to choose between the items. The left and right arrows will take the user to the next left or right menu, respectively, and it will automatically pop down. If the user presses return on an item, the chooser closes the menu and exits, returning the unique item ID # of the item the user has chosen. 6) The Chooser exits back to the Event_Loop, returning the item identifier of the item chosen. The Event_Loop then decides what to do based on this number, and then resumes the event loop, by calling the appropriate PROCedure or ROUTINE, as determined by its jump table. Whatever ROUTINE or PROCEDURE is invoked by Event_Loop will call event_loop periodically to get any user input. Go back to Step 4. The best way to understand the DeskTop Manager is to play with it. Try adding your own menus, or build your own program around it. Experiment. Using the DeskTop Manager Here are the calls you can make to the Desktop Manager, and what you need to pass them: [10] 1) Add a Menu to the Menu Bar FUNC Add_Menu [ Menu_Name$, Child! ] Use this FUNCtion to add a new menu onto the screen. Menu_Name$ is the name of the menu as you want it to appear in the menu bar. If you pass "@" as the name of the menu, you'll get an Apple symbol. The Boolean variable Child! is used to indicate whether this menu "belongs" to the active window. Let's say we add a window on the screen which contains the word "HELLO", and we add a menu which contains an item which calls a ROUTINE which will clear (HOME) the window and print the word "Goodbye". That's no problem. Ok, so we have our window with the word "HELLO" on the screen, and the user goes into the menu, and selects this item, the window clears, and then the word "goodbye" appears. That works fine. However, what if the user did not go into that menu immediately, but instead selected a different item, such as "HELP", which calls up a new window. Now the help window is on the screen, and presumably contains the some information helping the user. The user then goes back into the menu system and selects the menu item which clears the window and prints "goodbye". Instead of clearing the window with the word "HELLO", it (of course), clears the ACTIVE window, which is the help window. The ROUTINE which this item calls ASSUMES that the "HELLO" window is active. Because this menu is dependant on a particular window being active, we call it a "child" of this window. What setting Child! to TRUE does is AUTOMATICALLY dims this menu when a new window is added, so none of the menu items in this menu can be activated out of the context of the window for which they are intended. Some menus, such as the Apple menu in the example are designed so that they can be accessed anytime. An example of a child menu is the Face Control menu in the demo. When you choose the face demo, a new menu is added called "Face Control", which can change the face from happy to sad. However, the items in the menu are intended to be used only when the face window is the active window. By declaring this menu as a child, when a new window is added, the menu is automatically dimmed. Add_Menu will return a number which you will later use to identify that menu, so you'll want to assign it to a meaningful variable. This number is known as an "identifier", since we'll use it later to tell other ROUTINEs and PROCedures that it is this menu in specific which we are referring to. Each menu will have a unique identifier. Example: File_Menu_Number%= FN Add_Menu [ "File Menu", FALSE ] This will add a new menu to the menu bar, which will appear as "File Menu". This menu is not a child of the active window, and will not automatically be dimmed when a new window is added. 2) Delete the last menu added. ROUTINE Delete_Menu Use this ROUTINE to delete the LAST, or most recently added, menu. The menu name will disappear from the menu bar. Example: GOSUB Delete_Menu [11] 3) Dim a menu. ROUTINE Dim_Menu Use this routine to dim a menu item so that no items on it can be chosen. This is useful if all the menu items in a menu cannot be used in certain situations. Dimming the menu shuts off access to all the items within it. To use this routine, you must first set the global variable Menu_Number% to the identifier of the menu which we want to dim. For example let's say we want to dim the File Menu, which we created above in example 1). Menu_Number% = File_Menu_Number% GOSUB Dim_Menu The File Menu menu bar item will now have a "**" on each side of it, to indicate it has been dimmed. 4) Undim an a menu. Of course, we also need to be able to undim a menu to allow it to be chosen again, when its contents become valid options. The method used is the exact same as to dim a menu. Here's an example to undim the menu we created in example 1) and dimmed in example 3). Menu_Number% = File_Menu_Number% GOSUB Undim_Menu The "**" from around the File Menu menu bar item will be removed, and items within that menu will now be able to be chosen. 5) Add an item (or choice) to a menu FUNC Add_Item [ Menu Number%, Item Name$ ] Use this FUNCtion to add menu items to a menu. Add the menu items in the order in which you would like them to appear in the menu. Pass it the menu identifier for the menu which you are adding the item to, and the name of the item to add. If you use a "-" for the item name, it will place a "separating" line into the menu bar. Do not use a "*" in a menu item name. A "*" is used to denote a dimmed menu item (not selectable) and you may confuse the user by using this symbol in another way. This function will return a unique item Identifier which you will use later to reference this menu item, similar to the way menus are identified. Example: Quit_Item% = FN [File_Menu_Number%,"Quit"] This will add an item to the File Menu, which will appear as "Quit". 6) Dim an item in a menu. PROC Dim_Item [ Item ID Number% ] [12] Use this PROCedure to dim a menu item, making it unselectable by the user. The item will be denoted as being dimmed in the menu because it will have an "*" before and after it's name. The menu bar will automatically skip over it, not allowing the user to select it. Be sure not to dim an item which has already been dimmed. If you do, BAD THINGS happen. Example: GOSUB Dim_Item [ Quit_Item% ] This will dim the "Quit" item we added in the above example. 7) Undim a menu item. PROC Undim_Item [ Item ID Number% ] Use this to undim a previously dimmed item. Do not use this on an item which is not dimmed, or BAD THINGS will happen. Example: GOSUB Undim_Item [ Quit_Item% ] This will undim the "Quit" item dimmed in the above example. 9) Add a DeskTop window PROC Add_DT_Window [Title$,Top_Col%,Left_Row%,Bot_Row%,Right_Col%] This is essentially the same as the Add_Window call in the Window Manager, but it includes support for the mouse, plus child menus (described in part 1). NEVER CALL Add_Window FROM A DESKTOP PROGRAM, ONLY USE Add_DT_Window! 10) Remove a Desktop Window PROC Pop_DT_Window As in the case above, this PROCedure performs essentially the same function as the Pop_Window call in the Window Manager, but it includes support for the mouse and child menus. NEVER CALL Pop_Window FROM A DESKTOP PROGRAM, ONLY USE Pop_DT_Window! 11) Add a button FUNC Add_Button [ Draw!, X%, Y%, Equiv$ ] Buttons are useful ways to allow users to respond to questions, or to pick certain options. The Add_Button function informs the mouse and event loop routines that you are adding a button onto the screen. Then the event loop routines will take over and inform you if the button has been activated, either by a keypress or by the user placing the mouse cursor over the button location and pressing the mouse button. The FUNCtion returns a unique identifier, similar to menus and menu items, which you will use later to identify the button. The button also contains a routine to draw a standard button on the screen at the current cursor location. If you pass the function a TRUE for the Draw! variable, a button will be drawn. [13] The X% and Y% locations can be specified to place a button on the screen a specific location. Note that if Draw! is set to TRUE, the current cursor location will be used for X% and Y%, regardless of what is contained in the variables passed. Otherwise, Add_Button will create an "invisible" button at the location on the screen you specify with X% and Y%. This allows you to make custom buttons by drawing something on the screen that a user can click on. The Equiv$ variable contains the keyboard equivalent for clicking on the button, or the "hot-key". Since not all users have a mouse, you should almost always specify a hot-key. Simply pass a single letter, which will become the hotkey. Example: Yes_Button% = FN Add_Button [ TRUE, 0, 0, "Y" ] Text$ = " Yes - Save the file!" GOSUB FPrint This will draw a button at the current cursor location, and also place the information beside it indicating the hot-key for it. Then it will print a message after the button indicating its purpose. Note that we passed zeros for X% and Y% because we used the Draw! feature so their values are not important. The result (assume the @ is an Open-Apple symbol, and the # is a box symbol) will look like this: # (@Y) Yes - Save the file! Buttons are only active while the current window (at the time the buttons are added) is active. If you add more windows, then all the current buttons will become "non-active", until the new windows are removed and the window to which the buttons were added is active again. When the window to which the buttons were added is removed, all the buttons for that window are lost. 12) User Input Routines PROC DT_INPUT [ String_Var$, Max_Length% ] PROC Default_INPUT [ String_Var$, Max_Length%, Default$ ] PROC Get_Line [ String_Var$, Max_Length%, EmptyOK!,Hide!,DoCR!] These three PROCedures are designed to be used where you would normally use an input statement. A NORMAL INPUT STATEMENT CANNOT BE USED WITHIN THE DESKTOP MANAGER! The reasons for this are technical, and related to the mouse. However, you will find these PROCedures far more useful than INPUT ever was. The first two PROCedures call the third procedure, which is the main input routine. Since the third PROCedure has so many parameters, the first two PROCedures were provided for convenience, since they just call the third PROCedure with certain defaults set. These input routines are recursive... up to ten may be open at once. To increase this limit, modify the DIMs found in DESKTOP.DIMS. The first PROCedure is the standard INPUT that you'll use while using the desktop. Pass it a string variable, into which the input will be placed. Max_Length% is an integer specifying the maximum length the input can be. The DT_Input will not allow the user to type any more than this maximum length. The user will not be allowed to press return unless he/she has entered something (an empty result is not permitted). Example: GOSUB DT_Input [A$, 5] [14] This will allow the user to enter up to 5 letters, and will return the result in A$ The second PROCedure, Default_Input, is intended for times when you have a default input string to be used if the user does not enter anything. The first two parameters are the same as above, the third parameter is the default. Example: GOSUB Default_Input [ A$, 5, "HELLO" ] The user will be allowed to type up to 5 characters. If the user presses return without entering anything, the word "HELLO" will be printed (to indicate it was used as a default) and the result (either what the user entered, or the default) will be returned in the parameter A$. The third PROCedure is by far the most complicated and flexible. The first two parameters are as above. The third parameter, EmptyOK!, indicates whether the user is permitted to press return without entering anything (and hence returning an empty string). The fourth parameter, Hide!, indicates if the input routine should hide what is being typed, printing a "#" instead of the characters typed. This is useful for entering passwords. The last parameter, DoCR!, indicates whether the cursor should be put to the beginning of the next line when return is pressed, or left at the end of the current line. There is also a global variable used in all three cases called HotKeys!. If this variable is set to TRUE, and the Maximum_Length% is only one character, the user will not be required to hit return after typing the single character. The user should be able to set this variable according to their preference (some users love hotkeys, others hate them). This "hotkeys" is not to be confused with the hot-keys assigned to buttons. 13) The Event Loop ROUTINE Event_Loop As mentioned earlier, this routine is the heart of your program. Contained in the DT.MGR file, it is one of the few parts of the manager which you must modify yourself. Your programs will call this routine anytime they are waiting for input. This routine allows the user to select menu items, or to press keys. When the program returns from the even loop call, the global variable Event% may contain a value. There are four possibilities of what this value may be. Your program must be prepared to deal with each possibility: 1) IF Event% > 1 AND Event% < 255 THEN Event% contains the ASCII value of a keypress. This means that the user has pressed a key (other than the ones used for manipulating menus). If your program is waiting for a keypress, then you now have one (NOTE: Never use a GET statement, since GOSUB Event_Loop essentially handles keypresses). If you are not waiting for a keypress, then you might as well ignore it. 2) IF Event% = ~CLOSE THEN the user has pushed ESC or clicked on the current window's close box. This is your cue to finish up or abort [15] whatever you're doing, and close/pop the active window. 3) If Event% is not either of the above, then it is probably a "button identifier". By comparing Event% with the button identifiers returned by the Add_Button function, you can determine which button was clicked on (or equivalent hot-key was pressed) and take appropriate action. The event loop itself must be modified... you must add a "jump table" to it, so that it knows what to do when certain menu items are chosen. In the template, you'll find a line in the event_loop which reads: {ADD YOUR JUMP TABLE HERE!} This, as you probably guessed, is where your jump table goes. Your jump table consists of a series of IF... THEN statements, one for each menu item used in your program. Remember the menu item (the "Quit" item) we added above? We need to make a jump table entry to tell the event manager what to do when the user picks this item: IF Event% = Quit_Item_Number% THEN GOSUB My_Quit_Routine That's it... and you'll need similar entries for each and every menu item. Of course, you don't have to use a ROUTINE, you could GOSUB a PROCedure too. This pretty well covers the DeskTop manager. The BEST way to understand it is to look at the MOUSE.DEMO file included on the disk. THE VIEWER/CHOOSER The viewer/chooser is an addition to the Desktop manager. The viewer is a vertically scrolling window which allows you to view information which is `longer' than the screen. The chooser is also a vertically scrolling window which, through use of a scrolling light bar, allows you to select an item from a list. Both of these routines are fully recursive, meaning that multiple windows of each can be open at any time. Beware, however, that these routines are quite memory hungry. Also included is a file viewer. The file viewer will automatically load a text file into a viewer window, formatting the text so it appears properly according to the width of the window. The Viewer/Chooser Files There are three files which make must be included in your main program to access the viewer and chooser. They are VIEWER (the main PROCedures), VIEWER.DIMS (DIM statements) and VIEWER.GLOBALS (global variable declarations). Viewer/Chooser also creates some temporary files when more than one viewer or chooser window is open at once. These files are automatically deleted by the system as the windows are closed. However, this introduces a limitation of the viewer/chooser: no disk swaps can occur when using the viewer/chooser, unless under very controlled circumstances. Specifically, if you need to change disks, the user cannot be allowed to open or close viewer/chooser windows. This essentially means locking out all user activity (such as keyboard, mouse and menu access) until the original disk is swapped back in. Perhaps in a later release we can overcome this limitation, but for now this rule stands. You'll find the necessary INCLUDES required to use the viewer/chooser already in the DESKTOP.MGR program, but commented out using the brace [16] brackets ( { like this } ). If you want to use the viewer/chooser, merely remove the brace brackets. In the VIEWER.DIMS file, you'll find two arrays. The first array is Scroll_Buffer$, and it is DIMed to 800 elements by default. This controls the number of lines which can be in the viewer or chooser at any one time. Increasing this number will allow for more lines, but will consume more memory (string arrays can use gobs of memory). Reducing this number will free up more variable space for other uses, but may result in problems if you need to view more lines than allowed by the DIM. The second array is Save_Lines%, and it is DIMed to 20 elements by default. This controls the number of chooser/viewer windows which may be open at once. In the VIEWER.GLOBALS file, you'll find three variables which control some of the internal workings of the viewer/chooser. The first is Scroll_Buffer_Max%. This variable must be set to the exact same number of lines as the array Scroll_Buffer$ is DIMed to in the VIEWER.DIMS file. The default value for Scroll_Buffer_Max% is 800 to match the default for the Scroll_Buffer$ DIM. You must modify this variable if you modify the Scroll_Buffer$ DIM statement. The second and third variables, Scroll_Buffer_Level% and Reload! are used for managing the viewer and chooser windows. NEVER USE THESE VARIABLE NAMES IN YOUR PROGRAMS. DO NOT MODIFY THESE VARIABLES. Modifying these variables will cause BAD THINGS to happen. The VIEWER file contains the heart of the viewer/chooser. In this file are two PROCedure and one FUNCtion which you can call directly, and a few support PROCedures which you'll never need to call. We'll just deal with these in the order they appear in the file: PROC DISPLAY_FILE [Filename$, Fast!] This PROCedure will automatically read a file from disk, change the current window to a viewer window, and load the file into the view window, formatting it as it reads the file. This is probably the most useful part of the entire viewer/chooser. Using this, you can keep a library of information on disk, such as help files or descriptions, and load them into viewer windows as needed. You can write the files using an ordinary word processor, as long as they are saved as ASCII files. There are two very unique features of this PROCedure: auto-formatting and background-updating. Auto-formatting will process the text in the file as it is loaded into the scroll window to ensure that words are not `broken' as they wrap around the right margin of the window. Essentially, it performs a real-time word wrap, filling in the lines word by word and automatically going the next line if it finds that the next word is too long for the current line. This allows a text file to be stored in normal paragraph style format, without regard to the size of the window it will be placed in. Because of this, when you create files to be viewed using DISPLAY_FILE, only a put CR [also known as RETURN, ENTER and CHR$(13)] at the end of your paragraphs, not at the end of each line. This will allows for the greatest flexibility. Auto-update is the second unique feature. What auto-update does is let you start viewing and scrolling through the file in the view window as it is being read from disk. This means that the user does not need to wait for the whole file to be read from disk before he/she can start reading it. As the file is read from disk, the scroll bar on the side of the viewer window is automatically updated. Note that the [17] scroll bar is a "proportional scroll bar", which means it reflects not only the users position within the document being viewed, but also the size of the window of information relative to the entire document. What this means is that if the scroll bar is half the length of the window, then what the user sees in the window is half of the file. If the scroll bar is quite small, then what the user sees in the window represents only a small part of the overall document. Even Windows 3.0 on that `other' computer doesn't have this feature yet. Mac users are just getting it now for the first time with System Disk 7.0. Apple IIgs users have had it for quite a while already. Now Apple IIe/c users have it too. There are two parameters for DISPLAY_FILE, namely Filename$ and Fast!. No explanation is needed for the first parameter. The second parameter allows you to speed up the viewer in certain situations where you know that there are no lines longer than 256 characters in the file you want to read. If this is the case, pass a TRUE to the Fast! parameter. Using this feature on a file which contains lines longer than 256 lines [this means more than 256 characters before a CR/Enter/Return/CHR$(13)] will cause a fatal error. You must already have a window open when you call DISPLAY_FILE. The currently open window will be used as the viewer window. Here's an example of using display file: GOSUB Add_Dt_Window [ "File: A.FILE", 5, 10, 20, 70 ] GOSUB Display_File [ "A.FILE", FALSE ] GOSUB Close_DT_Window { Now we're finished with the window } That's all that is required to view a file on disk. PROC Viewer [ Number_of_lines%, New!, Return!, ADDRESS Cancel! ] The viewer is what does the work of displaying and scrolling the information within your window. As with the DISPLAY_FILE, you must already have opened the window you want to use. Whatever the most current window is will be automatically converted to a scroll window. Here's a quick overview of the parameters that Viewer uses: Number_of_Lines% tells the viewer how many lines are in the scroll buffer. New! must be used whenever a new scroll window is opened. Return! controls the background-updating feature. Cancel! is a global variable which will tell the calling PROCedure/ROUTINE that the user has closed the viewer window (required to tell the routine doing background-updating to stop and close its files). Here's step by step instructions on how to use the viewer (auto- updating will be explained afterwards): 1) Use Add_DT_Window to add the window you want to use as your scroll window. 2) GOSUB Viewer [ 0, TRUE, TRUE, Cancel! ] This tells the viewer to prepare itself for a new scroll window. This involves writing information about the currently open scroll window (if any) to disk and initializing internal variables. It also draws the scroll bar on the open window. 3) Load your information into the array Scroll_Buffer$. Each line in Scroll_Buffer$ will be a line in the scroll window. Therefore, be sure that the strings in Scroll_Buffer$ are shorter than the width of the scroll window. In other words, if the window you are using as your scroll window is only 15 characters wide, don't do this: Scroll_Buffer$(x) = "Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" because that line is too wide for your window. Also be sure not to exceed [18] the maximum number of scroll lines by checking the global variable Scroll_Buffer_Max%. 4) GOSUB Viewer [ Number of lines, FALSE, FALSE, Cancel! ] 5) Reset the Close! variable by making it FALSE. Those five steps are all you need. Here's an example that will draw a scroll window containing as many lines as we can fit, with each line numbered: GOSUB Add_DT_Window ["Lines",4,3,20,76] GOSUB Draw_Scroll_Window [0, TRUE, TRUE, Cancel! ] FOR Loop% = 1 TO Scroll_Buffer_Max% Scroll_Buffer$ (Loop%) = "This is Line #"+STR$(Loop%) NEXT Loop% Return! = FALSE { background-updating off } Cancel! = FALSE { Doesn't really matter } New! = FALSE { only use once for each window.. we did so above} GOSUB Viewer [ Loop%, New!, Return!, Cancel! ] Close! = FALSE { Cancel will set close... we must reset it } GOSUB Pop_DT_Window { We're finished with our window } This program segment will result in a scroll window with about 800 lines (by default) containing "This is line #1" and so on. The only problem with this is that we must wait for the loop to completely fill the array before we have a chance to look at it. Using background updating, we can view the scroll window while more information is being loaded into it. Background updating works basically the same way as above, except that control from the step four back to step three until complete. When the RETURN! parameter is true, the Viewer will execute for a short time, allowing the user to use the scroll keys to view the scroll buffer. Then, the viewer will pass control back to the calling routine, which will spend a short time loading more information into the buffer and then passing control back to the viewer. This continuous until there is no more information to load into the buffer, at which time the viewer should be called with Return! set to FALSE. The best way to show this is by modifying the above example to work with auto-updating: GOSUB Add_DT_Window ["Lines",4,3,20,76] { same as above } GOSUB Draw_Scroll_Window [0, TRUE, TRUE, Cancel! ] {same as above} Return! = FALSE { background-updating ON } Cancel! = FALSE { initialize this } New! = FALSE { we only use this once.. above } FOR Loop% = 1 TO Scroll_Buffer_Max% UNTIL Cancel! Scroll_Buffer$ (Loop%) = "This is Line #"+STR$(Loop%) GOSUB Viewer [ Loop%, New!, Return!, Cancel! ] NEXT Loop% IF NOT Cancel! Return! = FALSE { background-updating off } Cancel! = FALSE { Doesn't really matter } New! = FALSE GOSUB Viewer [ Loop%, New!, Return!, Cancel! ] {same as above} ENDIF Close! = FALSE { Cancel will set close... we must reset it } GOSUB Pop_DT_Window { We're finished with our window } This program segment is essentially the same as above, except that now [19] we are calling the viewer from within the loop. The viewer executes for a short time and then passes control back. The only other difference is that we must be able to abort out of the loop if the user closes the viewer box while we have passed control to it. This is achieved by using the Cancel! global variable. When using the viewer, there is built-in help that tells the user what they can do. Basically, pressing OPEN-APPLE along with a number from 1 to 0 (along the top of the keyboard) will jump to that relative position in the buffer. For example, OPEN-APPLE-5 will jump to the middle of the scroll buffer. OPEN-APPLE-UP ARROW and OPEN-APPLE-DOWN ARROW will jump to the very top or bottom of the scroll buffer, respectively. Pressing the up or down arrow keys or clicking on those buttons in the side bar will cause the scroll buffer to scroll one line. FUNC Chooser [ Number_of_Lines%, New! ] The chooser allows you to display a long list of items in a scroll window and have the user pick an item. It is similar to the Viewer, however it is a function and does not support background-updating. To use the chooser, you use the exact same steps as the viewer: 1) Add a new window 2) Dummy_Variable% = FN Chooser [ 0, TRUE ] { to initialize it } 3) Load your choices into your array 4) Choice% = FN Chooser [ Number of choices, FALSE ] This will cause a scroll window to appear which lists the lines in scroll buffer. The same rules regarding line widths apply. There will be a lightbar which the user can scroll down by using the arrow keys (or using the mouse and clicking on the arrow buttons in the scroll bar). Pressing space will toggle the lightbar on and off, allowing for faster scrolling (when the light bar is off). When the user presses enter, the line number which the light bar was on will be returned by the function. If a 0 is returned, the user closed the chooser window without picking anything. Here's an example based on the example we used for the viewer: GOSUB Add_DT_Window ["Lines",4,3,20,76] GOSUB Draw_Scroll_Window [0, TRUE, TRUE, Cancel! ] FOR Loop% = 1 TO Scroll_Buffer_Max% Scroll_Buffer$ (Loop%) = "This is Line #"+STR$(Loop%) NEXT Loop% New! = FALSE { only use once for each window.. we did so above} Choice% = FN Chooser [ Loop%, New! ] Close! = FALSE { Cancel will set close... we must reset it } GOSUB Pop_DT_Window { We're finished with our window } The variable Choice% now contains the line number which the user was on when he/she pressed enter. To see the actual line: PRINT Scroll_Buffer$ (Choice%) And that's all you need to know about the Viewer/Chooser. Again, if you have questions, your first resource should be the MOUSE.DEMO program on your distribution disk. Otherwise, you can contact me by: Phone: (416) 731 - 1544 { Note: East cost time } EMAIL (GENIE): MICOL.SYSTEM [20] (Proline): ronl@pro-micol (Internet): ronl@pro-micol.cts.com Paper Mail: Ron Lewin Micol Systems 9 Lynch Road Willowdale, ON CANADA M2J 2V6