F.G.H. 7/2/85 LispCourse #40: Using the Display: Bitmaps, DisplayStreams, & Windows LispCourse #40: Using the Display: Bitmaps, DisplayStreams, & Windows Introduction Interaction with the display from Interlisp-D programs involves 3 basic types of Interlisp objects bitmaps, display streams, and windows. A bitmap is a two-dimensional array of bits (1s and 0s) in your computer's memory. You deal with bitmaps by setting specific bits to 1 or 0, or by copying rectanglular arrays of bits around from bitmap to bitmap. The Interlisp-D display screen is just a special bitmap (called the SCREENBITMAP) that is 808 bits high and 1024 bits wide that is displayed on the screen with every 1-bit is black and every 0-bit is white (or vice versa if you change the VIDEOCOLOR parameter as per page 19.7 in the IRM). Display streams represent an interface that allows you to deal with bit maps at a level higher than bits, i.e., in terms of characters, fonts, lines, circles, regions, etc. You can call functions like DRAWCURVE on a display stream. The result will be a curve drawn on the destination bitmap of that display stream. A display stream is a datatype that represents some destination bitmap. Stored in this datatype are fields that contain things like the current font to be used for writing characters to the destination bitmap, the X-Y position of the "cursor" in the bitmap, left and right margins for writing and drawing on the bitmap, etc. Windows are a way of managing what is being displayed on the most important bitmap of all, the Interlisp-D display screen. A window is a datatype that represents the window object that can be displayed on the Interlisp screen. Stored in the fields of the window datatype are all kinds of functions that specify what is to be done when various operations are carried on the window, e.g., when the window is opened, closed, shrunk, or moved. Also stored in each window datatype is a display stream through which characters, lines, curves, etc. are drawn on the window's bitmap (i.e., the display stream's destination bitmap) which is always SCREENBITMAP. In summary, the data structures underlying each window you see on the screen are as follows: (NIL ((0.0 84.0 NIL) (WIRE ((280.0 . 176.0) (280.0 . 200.0) (416.0 . 200.0) (416.0 . 112.0) (248.0 . 112.0) (248.0 . 72.0)) 2 ((LINE 30.0 12.0) NIL) NIL)) ((0.0 84.0 NIL) (WIRE (( 272.0 . 304.0) (272.0 . 328.0) (416.0 . 328.0) (416.0 . 248.0) (248.0 . 248.0) (248.0 . 208.0)) 2 ((LINE 30.0 12.0) NIL) NIL)) ((.06400001 108.0 NIL) (TEXTBOX (152.0 8.0 216.0 64.0 ) ("SCREENBITMAP") 1.0 (CENTER CENTER) (HELVETICA 10) ((209.0 34.0 103.0 12.0)) NIL 2)) (( .06400001 108.0 NIL) (TEXTBOX (152.0 144.0 216.0 64.0) ("DisplayStream " " " "Fields: Destination bitmap , X-Y" "cursor position, font, offset within" "destination bitmap, etc. ") 1.0 (LEFT CENTER) (HELVETICA 10) ((154.0 194.0 93.0 12.0) (154.0 182.0 6.0 12.0) (154.0 170.0 196.0 12.0) (154.0 158.0 203.0 12.0) (154.0 146.0 143.0 12.0)) NIL 2)) ((.06400001 108.0 NIL) (TEXTBOX (160.0 272.0 216.0 64.0) ("Window " " " "Fields: DisplayStream , location on" "screen, order in window stack," "open function, close function, etc.") 1.0 (LEFT CENTER) (HELVETICA 10) ((162.0 322.0 55.0 12.0) (162.0 310.0 9.0 12.0) (162.0 298.0 216.0 12.0) (162.0 286.0 184.0 12.0) (162.0 274.0 208.0 12.0)) NIL 2)) ((.05 13.0 NIL) (TEXT (144.0 . 304.0) ("Screen" "Management") 1.0 (RIGHT BASELINE) (HELVETICA 12) ((92.0 307.0 52.0 13.0) (50.0 294.0 94.0 13.0)) NIL)) (( .05 13.0 NIL) (TEXT (136.0 . 176.0) ("Drawing objects" "like characters" "and curves") 1.0 ( RIGHT BASELINE) (HELVETICA 12) ((18.0 186.0 118.0 13.0) (27.0 173.0 109.0 13.0) (55.0 160.0 81.0 13.0)) NIL)) ((.05 13.0 NIL) (TEXT (136.0 . 40.0) ("The actual bits" "displayed" "on the screen") 1.0 (RIGHT BASELINE) (HELVETICA 12) ((27.0 50.0 109.0 13.0) (67.0 37.0 69.0 13.0) (35.0 24.0 101.0 13.0)) NIL))) (0 0 423.0 348.0) 1.0 8.0 Coordinate Systems, Positions, & Regions Coordinate Systems for Specifying Locations in Bitmaps, et al. Each bitmap, display stream, and window has its own coordinate system used to specify locations within the object. When dealing with the display, these coordinate systems are always measured in bits (or screen units or the area that it takes to display 1 bit on the screen or 1/72nd of an inch). For all three objects, the coordinate system is a standard Cartesian system in the standard orientation. (NIL ((0.0 60.0 NIL) (WIRE ((112.0 . 144.0) (112.0 . 40.0) (112.0 . 24.0)) 2 ((LINE 30.0 12.0) (LINE 30.0 12.0)) NIL)) ((.05 12.0 NIL) (TEXT (112.0 . 152.0) ("(0,Y)") 1.0 (CENTER BASELINE) (HELVETICA 10) ((98.0 150.0 28.0 12.0)) NIL)) ((.05 12.0 NIL) (TEXT (128.0 . 88.0) ("(0,0)") 1.0 (CENTER BASELINE) (HELVETICA 10) ((115.0 86.0 26.0 12.0)) NIL)) ((.05 12.0 NIL) (TEXT (16.0 . 80.0) ("(--X,0)") 1.0 (CENTER BASELINE) (HELVETICA 10) ((-2.0 78.0 36.0 12.0)) NIL)) ((0.0 72.0 NIL) (WIRE ((40.0 . 80.0) (184.0 . 80.0)) 2 ((LINE 30.0 12.0) (LINE 30.0 12.0)) NIL)) ((.05 12.0 NIL) (TEXT (112.0 . 8.0) ("(0,--Y)") 1.0 (CENTER BASELINE) ( HELVETICA 10) ((94.0 6.0 36.0 12.0)) NIL)) ((.05 12.0 NIL) (TEXT (200.0 . 80.0) ("(X,0)") 1.0 (CENTER BASELINE) (HELVETICA 10) ((186.0 78.0 28.0 12.0)) NIL))) (0 0 211.0 162.0) 1.0 8.0 Bitmaps have a finite size. Thus, for bitmaps the origin of the coordinate system is placed at the lower-left corner of the bitmap and only the upper-right quadrant (positive X and Y) is used to specify locations in the bitmap. Display streams and windows are considered to look onto an infinite plane. Thus the origin is arbitrarily placed (see below) and the entire coordinate system is used. The coordinate system for a window is the same as the coordinate system for its underlying display stream. The coordinate system for a display stream is mapped onto the coordinate system for its destination bitmap using X and Y translation parameters as discussed below. Positions and Regions Positions and regions are data structures that represent X-Y locations and rectangles, respectively, in an arbitrary coordinate system. A POSITION is a record with two fields, XCOORD and YCCORD. Most functions that take an X-Y location as an argument require a POSITION record. To create a POSITION record: (create POSITION XCOORD _ X YCOORD _ Y) A REGION is a record with four fields: LEFT, BOTTOM, WIDTH, and HEIGHT specifying the lower-left corner and extent of a rectangular region in some coordinate space. To create a REGION record: (CREATEREGION Left Bottom Width Height) There are several functions available to manipulate positions and regions, including the following: (INSIDEP Region Position) returns T if Position is inside Region. Examples: 1_ (INSIDEP (CREATEREGION 100 100 10 10)(create POSITION XCOORD _ 150 YCOORD _ 100)) NIL 2_ (INSIDEP (CREATEREGION 100 100 100 10)(create POSITION XCOORD _ 150 YCOORD _ 100)) T (INTERSECTREGIONS Region1 Region2 ... RegionN) returns the region that is the intersections of Region1, Region2, ..., and RegionN. NIL, if there is no intersection. (UNIONREGIONS Region1 Region2 ... RegionN) returns the region that is the union of Region1, Region2, ..., and RegionN. The union is the smallest (rectangular) region that contains all of the given regions. BITMAPs Introduction A bitmap is datatype that represents an N by M array of bits in memory. The bits in a bitmap are identified using a positive integer coordinate system whose origin (0,0) is the lower-left corner of the bitmap. For example, (10,2) represents the bit that is 10 to the left of and 2 up from the bit in the lower-left corner of the bitmap. DDDDDDDDDDDDDDDDDDDDDDDDDD@Gꪪ@G>UUUUUUUUUUW@UUUUUUUUUUWꪪGƌ|:j@GsyuuUUUUUUUUUUW@߳UUUUUUUUUUW߷ꪪG߷ꪪ@G߷UUUUUUUUUUW@ywUUUUUUUUUUWJꪪGꪪ@GUUUUUUUUUUW@UUUUUUUUUUWG@G@UUUUUUUUUUUUUUUUUUUUUUSGUUUUUUUUUUUUUUUUUUUUUUS@G@UUUUUUUUUUUUUUUUUUUUUUSGUUUUUUUUUUUUUUUUUUUUUUS@G@UUUUUUUUUUUUUUUUUUUUUUSGUUUUUUUUUUUUUUUUUUUUUUS@G@UUUUUUUUUUUUUUUUUUUUUUSGUUUUUUUUUUUUUUUUUUUUUUS@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@DDDDDDDDDDDDDDDDDDDDDDDDDD@DDDDDDDDDDDDDDDDDDDDDDDDDD@ Creating Bitmaps To create a bitmap use the following function: (BITMAPCREATE Width Height) creates and returns a bitmap Height bits high and Width bits wide. BITBLT The major operation on bitmaps is the moving of bits from one bit map to another using the BITBLT function: (BITBLT SourceBitMap SourceLeft SourceBottom DestBitMap DestLeft DestBottom Width Height SourceType Operation Texture) copies some bits in SourceBitMap and combines them with some bits in DestBitMap, resulting in a change to these bits in DestBitMap. The bits copied from SourceBitMap are those in the region defined by SourceLeft, SourceBottom, Width, & Height. The bits effected in the DestBitMap are those in the region defined by DestLeft, DestBottom, Width, & Height. If either of these regions overflows the edges of its bitmap, then Width and/or Height are decreased until both regions fit into their bitmaps. The way in which the bits are copied from the SourceBitMap is determined by SourceType and Texture as follows: If SourceType is INPUT, then the bits are copied directly from the region in SourceBitMap. If SourceType is INVERT, then the bits are copied from the region in SourceBitMap, but each bit is inverted (i.e., 1s become 0s and vice versa). If SourceType is TEXTURE, then SourceBitMap, SourceLeft, and SourceBottom are ignored and the bits to be copied are taken from the bitmap specified by Texture. If the Texture bitmap is smaller than Width by Height, then it is repeated as many times as necessary to make a rectangle of bits that is of size Width by Height. Note: the global variables WHITESHADE, BLACKSHADE and GRAYSHADE are small bitmaps for white, black, and gray, respectively. SourceType defaults to INPUT. The way in which the copied bits are combined with the bits already in DestBitMap is determined by Operation as follows: If Operation is REPLACE, the bits in DestBitMap are replaced by the copied bits. If Operation is PAINT, the bits in DestBitMap are logically ORed with the copied bits. If Operation is INVERT, the bits in DestBitMap are logically XORed with the copied bits. If Operation is ERASE, the bits in DestBitMap are logically ANDed with the inversion of the copied bits. Operation defaults to REPLACE. Examples: START BitMap1: yN pp@p^ߨ4+Rxpfjm؞׃0?` (BITBLT BitMap1 0 0 BitMap2 0 0 125 175 'TEXTURE 'PAINT GRAYSHADE) BitMap1: yN 뽿ouUUUUUUUUUUUUUUUUUUUUUUUX"""#]}oZ"""""#]}oZ""]}oUUUUUUUUUUUUUUUUUUUUUUUXywUUUUUUUUUUUUUUUUUUUUUUUX"""#>ox^"""""#""{UUUUUUUUUUUUUUUUUUUUUUUXUUUUUUUUUUUUUUUUUUUUUUUX"""""""8#"8#"8"xGxGx""" @ @"""D$H"D$H"D8pÇ8pÇ8p"""""""8pÇ8pÇ8p""""D$H"D$H"D""" H" H"8pÇ8pÇ8p"" H" H""""" H" H""""D$H"D$H"D8pÇ8pÇ8p""""""">8㏎>8㏎>8"""D $@D $@D""@A@A8C8C8" B B """ B B ""DADDADD@8@8@8@"""""""8pÇ8pÇ8p""""D$H"D$H"D"" H H <8Ï<8Ï<8"" H" H""""" H" H""""D$H"D$H"D8pÇ8pÇ8p""""""">8㏎>8㏎>8""" DH DH D"" H H <8Ï<8Ï<8"" H" H"""" @ @"""D$H"D$H"D8pÇ8pÇ8p""7"""""""7 80Ã 80Ã 80"""DQDEDQDEDP""P@EP@EP$8C$8C$8""7$@I$@I$""">O>O>""DDADDAD8C8C8""7"""""""78pÇ8pÇ8p""""D$H"D$H"D"" @ @8pÇ8pÇ8p""7 @ @""" @ @"""D$H"D$H"D8pÇ8pÇ8p""7"""""""78pÇ8pÇ8p""""D$H"D$H"D""" H" H"8C8C8""7 B B """@D@D@"" DH DH D>8㏎>8㏎>8""7"""""""78 8 8 """DaFDaFD`""(J(J(8 8 8 ""7 B B """ B B ""D!BD!BD >8㏎>8㏎>8""7"""""""78pÇ8pÇ8p""""D$H"D$H"D""&`I&`I&*8⣊*8⣊*8""7*J*J*"""2 L2 L2"""D$H"D$H"D8pÇ8pÇ8p""7"""""""78pÇ8pÇ8p""""D$H"D$H"D"""D$H"D$H"D"" "" """7xAxAx""" " " """@$"@$"@|q|q|p"""""""8pÇ8pÇ8p""""D$H"D$H"D"""D$H"D$H"Dp!p!p""B!"B!""""" "" "" """@$"@$"@|q|q|p""""""">8㏎>8㏎>8"""D $@D $@D""DDADDAD @ @" @ @ """   ""@A@A@@|A|A|@"""""""8pÇ8pÇ8p""""D$H"D$H"D"" DH DH D<#<#<""B!"B!""""" "" "" """@$"@$"@|q|q|p""""""">8㏎>8㏎>8""" DH DH D"" DH DH D<#<#<""B!"B!"""" " " """@$"@$"@|q|q|p""""""" 80Ã 80Ã 80"""DQDEDQDEDP""DQDEDQDEDP$"@$"@$"$BA $BA $"""> > > ""@D@D@|G|G|"""""""8pÇ8pÇ8p""""D$H"D$H"D""D $@D $@Dp!p!p"@!@!""" " " """@$"@$"@|q|q|p"""""""8pÇ8pÇ8p""""D$H"D$H"D"""D$H"D$H"D @ @" @ @ """ @ @ @"" @ @ @>|ϟ>|ϟ>|"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Menus The Menu package is very closely related to the Window package. Basically, a menu is a window (or part of a window) in which pressing a mouse button in certain specified regions (i.e., menu items) causes certain specified events to happen. In Interlisp-D, a menu is implemented as window with a special BUTTONEVENFN that uses a menu data structure to determine what action to take depending on what menu item the cursor is over when the button was pressed. Bringing a menu up on the display requires two steps: 1) creating the menu data structure, and 2) displaying the menu in the window. Creating the menu data structure A MENU is a system datatype. Cached on the data type is all the information the menu package needs in order to display the menu in a window and in order to carry out actions when the mouse is clicked within the menu. The MENU datatype has the following fields: Fields that describe the menu's behavior ITEMS a list of the items to be included in the menu. If an item is an NLISTP, then it is displayed in the menu. If an item is a list, then the CAR of that list is displayed in the menu. The display consists of the print name of the item or its CAR unless it is a bitmap, in which case the bitmap is displayed. Under most circumstances, each item will be a list of three elements: the item label, the item action, and the item message. This list is interpreted by the default WHENSELECTEDFN as described below. If a WHENSELECTEDFN other than the default is used, then each item can consist of whatever that WHENSELECTEDFN requires. WHENSELECTEDFN a function that gets called whenever an item is selected in the menu by a mouse click. The function will get called with three arguments: 1) the item slected, 2) the menu, and 3) the mouse key used (LEFT, MIDDLE, or RIGHT). The WHENSELECTEDFN defaults to the function DEFAULTWHENSELECTEDFN which operates as follows: If the CADR of the item is non-NIL, then that CADR is evaluated and returned as the value of the function. Otherwise, the item itself is returned. WHENHELDFN a function that gets called whenever the user holds the mouse button down inside an item for more than MENUHELWAIT milliseconds (defaults to 1200). The function will get called with three arguments: 1) the item slected, 2) the menu, and 3) the mouse key used (LEFT, MIDDLE, or RIGHT). The WHENHELDFN defaults to the function DEFAULTMENUHELDFN which operates as follows: If the CADDR of the item is non-NIL, then that CADDR is printed in the prompt window. Otherwise, This item will be slected when the button is released will be printed in the prompt window. WHENUNHELDFN a function that gets called whenever the WHENHELDFN has been called and the user lets up on the mouse button or moves the cursor out of the item. The function will get called with three arguments: 1) the item slected, 2) the menu, and 3) the mouse key used (LEFT, MIDDLE, or RIGHT). The default WHENUNHELDFN is CLRPROMPT, which clears the prompt window. Fields that describe the menu's looks TITLE the title to appear in the title bar of the menu. If NIL, then the menu will have no title bar. MENUFONT the font in which the items will be printed. Defaults to Helvetica 10. MENUROWS or MENUCOLUMNS the number of rows or columns the menu is to have. If both of these are NIL, the menu will have one column. ITEMHEIGHT, ITEMWIDTH the height (width) of each item in the menu. If ITEMHEIGHT is not specified, then the height will be the maximum height of any item in the menu (as determined by the sizes of any bitmaps and the height of the MENUFONT). If ITEMWIDTH is not specified, the maximum width of any item will be used. CENTERFLG if non-NIL, the menu items are printed centered in their areas. Otherwise, they are left-justified. MENUBORDERSIZE the width in bits of the black border around each item. Defaults to 0. MENUOUTLINESIZE the width in bits of the black border that outlines the entire menu. Defaults to the maximum of 1 and MENUBORDERSIZE. Fields that describe the menu's positioning MENUPOSITION the default position of the menu in screen coordinates (for pop-up menus) or window coordinates (for permanent menus) [See below for op-up versus permamnent menus]. If NIL, then the memu is paced at the cursor. The point of the menu to be placed at MENUPOSITION is determined by MENUOFFSET. MENUOFFSET the point inside the menu that will be placed over MENUPOSITION. Defaults to 0,0, i.e., the lower-left corner. To create a menu data structure use the standard CREATE statement from the Record Package: Example: (SETQ MenuX (create MENU ITEMS _ '(Yes No) CENTERFLG _ T TITLE _ "Yes or No??")) To change a menu data structure use the standard replace statement from the Record Package: Example: (replace (MENU TITLE) of MenuX with "Well??") Bringing up a menu on the screen: Pop-up and Fixed Menus There are two way to use a menu: pop-up and fixed. Pop-up menus are used in a program to get some information from a user. A program using a pop-up menu brings the menu up on the screen and then waits for the user to select an item. When the item is selected, the menu is removed from the screen and the menu's WHENSELECTEDFN is called. The WHENSELECTEDFN will carry out some action, return a value to the program, or both. Fixed menus remain on the screen "permanently. Whenever the user clicks a mouse button in one of the menu's items, the menu's WHENSELECTEDFN is called to carry out some action. Since fixed menus are not part of the ongoing processing of a program, the value returned by the WHENSELECTEDFN is ignored. The MENU data structure is identical for pop-up and fixed menus. The difference is in the function used to bring the menu up on the screen. The function for displaying a pop-up menus is: (MENU Menu Position) displays Menu at Position (in the screen coordinate system) and then waits for the user to press and release a mouse button. Pressing a mouse button has the following effects: If the cursor is an item in Menu, that item is inverted on the screen. If the user holds the mouse button down inside the item for MENUHELDWAIT milliseconds, then Menu's WHENHELDFN is called. If the user lets up on all mouse buttons while the cursor is still in the item, then Menu's WHENSELECTEDFN is called. If the user lets up on all mouse buttons while the cursor is outside of Menu, then no action is taken. MENU returns the value returned by the call to the WHENSELECTEDFN, unless the user releases the mouse button while the cursor is outside of Menu in which case MENU returns NIL. If the Position argument is NIL, then the MENUPOSITION field of Menu is used. If the MENUPOSITION field is also NIL, then the current cursor position is used. Example: (SELECTQ (MENU (create MENU ITEMS _'(Yes No))) (Yes (PRINT "The answer is Yup.")) (No (PRINT "The answer is Nope.")) (PRINT "No answer given.")) The function for displaying a fixed menus is: (ADDMENU Menu Window Position) displays Menu in Window at Position (in the window's coordinate system) and returns immediately. The CURSORINFN and BUTTONEVENTFN of Window are replaced by the function MENUBUTTONFN so that when the user presses a mouse button inside an item in Menu (in Window) the following events take place: The item is inverted. If the user holds the mouse button down inside the item for MENUHELDWAIT milliseconds, then Menu's WHENHELDFN is called. If the user lets up on all mouse buttons while the cursor is still in the item, then Menu's WHENSELECTEDFN is called. If the user lets up on all mouse buttons while the cursor is outside of Menu, then no action is taken. If no Position argument is given, then the MENUPOSITION field of Menu is used. If the MENUPOSITION field is also NIL, then the current cursor position is used. If no Window argument is given, then a window is created just big enough to hold the menu and placed at Position (in the screen coordinate system). ADDMENU always returns the window the menu was placed in. Note: you can add more than one menu to a single window. But you cannot add a single menu to more than one window! Since fixed menus are no automatically removed, the following function must be used to remove a fixed menu from the display: (DELETEMENU Menu CloseFlg Window) deletes Menu from Window. If CloseFlg is non-NIL and there are no other menus in Window, then CLOSEW is called on Window. If Window is not given, (OPENWINDOWS) is searched for the window containing Menu. If no such window is found, DELETEMENU is a no-op. Hierarchical Menus You can create hierarchical menus by creating menu items that have subitems. A menu item with subitems is displayed with a small gray arrowhead at the right edge of the item. If the user, drags the cursor from inside the item to outside the item across the right edge, then a submenu containing the subitems will be brought up. Selecting one of these subitem from this submenu is functionally equivalent to selecting an item from the main menu. Note that the subitems may themseleves have subitems, making a fully hierarchical menu. The MENU datatype has a field called SUBITEMFN. The value of the field should be a predicate that determines whether an item has subitems or not. The SUBITEMFN is called with the menu and the item as arguments. It should return either NIL to indicate that the item has no subitems or a list of subitems from which to use as the ITEMS field while constructing the subitem menu. The default SUBITEMFN is the function DEFAULTSUBITEMFN which checks to see if the item is a list of 4 elements with the 4th element being a list whose CAR is SUBITEMS. If it is, it returns the CDR of this 4th element. Otherwise, it returns NIL. Example of a menu with subitems and the default SUBITEMFN: (create MENU ITEMS _ '((Yes 'Yes "Answer Yes" (SUBITEMS ("Yes w/ Check" 'Yes1 "Anwser Yes, but check first.") ("Yes w/o Check" 'Yes2 "Anwser Yes, without checking first."))) (No 'No "Answer No."))) 2'DDDDDD@DDDDDD@GD@DD@DDDD@DD8pD@(DU|pD@ DD@DDD@8qDD@DD@DD@D<D@BBDBD@DBD@<DD@GD@DDDDDD@DDDDDD@DDDDDD@DDDDDD@ Menu Example The following set of functions implements a fixed menu that sits left-flushed along the bottom of the screen. The menu has some common commands that you usually invoke from the Exec window. Note: The function CM.MakeCommandMenu creates a fixed menu while the function CM.TEdit uses a pop-up menu. (CM.MakeCommandMenu (LAMBDA NIL (ADDMENU (CREATE MENU ITEMS _ '((TEdit (CM.TEdit) "Opens up a new TEdit window.") (FileBrowse (CM.FB) "Opens a File Browser window.") (Lafite (CM.Lafite) "Starts up Lafite mail program.")) TITLE _ "Common Commands" MENUFONT _ '(HELVETICA 14 BOLD) MENUROWS _ 1 CENTERFLG _ T ITEMHEIGHT _ 50 MENUBORDERSIZE _ 2) NIL (create POSITION XCOORD _ 0 YCOORD _ 0)))) (CM.TEdit (LAMBDA NIL (* * Ask user if New or Old file to TEdit, then open a Tedit) (SELECTQ (MENU (create MENU ITEMS _ '(New Old) MENUFONT _ '(HELVETICA 14) ITEMHEIGHT _ 30 CENTERFLG _ T)) (New (TEDIT)) (Old (TEDIT (PROGN (PRIN1 "Enter file name: ")(READ)))) NIL))) (CM.FB (LAMBDA NIL (FILEBROWSER))) (CM.Lafite (LAMBDA NIL (LAFITE))) Using the Mouse Writing BUTTONEVENTFNs, CURSORxxxFNs, and WHENSELECTEDFNs In general, programs that depend on mouse actions are best written using the Window and/or Menu packages. The BUTTONEVENTFNs and CURSORxxFNS for windows and the WHENSELECTEDFN for menus allow you to carry out arbitrary actions whenever the user buttons down inside a window or menu item or even whenever the user moves a the cursor within a window. For example, the GRAPHER program for editing node-link graphs is built almost entirely on BUTTONEVENTFNs that fire whenever the user buttons down inside the GRAPH window. The BUTTONEVENTFNs determine what button was pressed and where in the window it was pressed relative to the graph being displayed. Theuse this information to decide what action to carry out. There is an important factor to pay attention to when writing BUTTONEVENTFNs, CURSORxxxFNs, and WHENSELECTEDFNs. In particular, these functions are evaluated as part of the Mouse process (i.e., the process that tracks the cursor and interprets mouse button presses). While these functions are be evaluated, the Mouse process can't be doing anything else, i.e., it can't be carrying out its normal job of tracking the cursor and interpreting button presses. If a BUTTONEVENTFN, CURSORxxxFN, or WHENSELECTEDFN is going to be long running, it is good practice to spin this evaluation off of the mouse process, so that the user can go off an do other things with the mouse while the function is being evaluated. To do this, you can use the function call (SPAWN.MOUSE) in the BUTTONEVENTFN, CURSORxxxFN, or WHENSELECTEDFN. As described in LispCourse #14 (page 16), this will make the current mouse process (the one thats going to do the long running evaluation) into a process called OLDMOUSE and then start up a new mouse process. The BUTTONEVENTFN, CURSORxxxFN, or WHENSELECTEDFN will thus run under the OLDMOUSE process and not interfere with ongoing mouse operations. Also, there is a convention in Interlisp-D that a events don't occur until the user releases a mouse button. For example, menu items are selected when the user releases the mouse button inside the item, rather than when she presses the mouse button. When writing BUTTONEVENTFNs for windows, the programmer is responsible for adhering to this convention. When the BUTTONEVENTFN is called, it is good practice to wait until the user lets up on the mouse button before carryoing out any actions. If the user lets up on the mouse button outside the window, then it is considered correct return without carrying out the action. So, at the begining of a BUTTONEVENTFN it is common practice to have a (UNTILMOUSESTATE UP) and (OR (INSIDEP (WINDOWPROP Window 'REGION) (CURSORPOSITION Window)) (RETURN NIL)). [See below for discussion of UNTILMOUSESTATE and CURSORPOSITION. Getting Information about the Mouse and the Cursor Many programs require information about the state of the mouse buttons or the cursor. The following functions return this information: Position of the Cursor (CURSORPOSITION NewPosition Window) Reads and then returns the location of the cursor in the coordinate system of Window. If NewPosition is specified, sets the cursor to be at the specified position in the window's coordinate system. (LASTMOUSEX Window), (LASTMOUSEY Window) Returns the X (Y) location of the cursor in the coordinate system of Window as of the last time the cursor position was read. Does not actually read the current position. LASTMOUSEX, LASTMOUSEY global variables that contain the X (Y) position of the cursor in the screen coordinate system as of the last time the cursor position was read. (GETMOUSESTATE) Reads the current mouse state including the cursor location and sets the global variables LASTMOUSEX and LASTMOUSEY. CURSORPOSITION calls GETMOUSESTATE before returning the cursor position. The functions LASTMOUSEX and LASTMOUSEY do not. State of the Mouse Buttons (MOUSESTATE ButtonSpec) Reads the mouse button state and returns T if that state matches ButtonSpec, NIL otherwise. ButtonSpec is description of the mouse buttons having one of the following forms: LEFT, MIDDLE, RIGHT indicating the corresponding mouse button is down. UP indicating all mouse buttons are released. (ONLY Button) indicating that mouse button Button (i.e., one of LEFT, MIDDLE, RIGHT) is the ONLY button down. (AND ButtonSpecs), (OR ButtonSpecs), (NOT ButtonSpec) indicating the logical combinations of other ButtonSpecs. Note: MOUSESTATE is a macro which is like an NLAMBDA function in that its arguments are not quoted. Examples: (MOUSESTATE LEFT) (MOUSESTATE (ONLY LEFT)) (MOUSESTATE (OR LEFT MIDDLE)) (MOUSESTATE (AND (NOT UP)(NOT (ONLY LEFT))) (LASTMOUSESTATE ButtonSpec) Like MOUSESTATE but does not first read the current mouse state. Thus it returns the mouse state as of the time it was last read. Good for looking at exactly what caused MOUSESTATE to return T. (Does not first call GETMOUSESTATE as does MOUSESTATE.) (UNTILMOUSESTATE ButtonSpec Interval) waits until (MOUSESTATE ButtonSpec) returns T or until Interval milliseconds have elapsed. If (MOUSESTATE ButtonSpec) returns T, then UNTILMOUSESTATE returns T. If Interval milliseconds elapses without (MOUSESTATE ButtonSpec) returning T, then UNTILMOUSESTATE eretunrs NIL. If Interval is NIL, then UNTILMOUSESTATE will wait indefinitely. Example of Using the Mouse The following two functions implement a trap window if the user rolls the cursor into the window, he cannot roll it out again. Every time the cursor nears the edge of the window, the window jumps to be centered around the cursor location. Thus wherever the cursor moves, the window will follow. (Being a nice guy, there is an escape.) (EX.CreateWindow (LAMBDA NIL (* fgh: "29-Jun-85 16:26") (LET ((Window (CREATEW (CREATEREGION 100 100 300 150)))) (* * Add special CURSORINFN to Window) (WINDOWPROP Window (QUOTE CURSORINFN) (FUNCTION EX.CursorInFn)) (* * Add a warning title) (WINDOWPROP Window (QUOTE TITLE) "Caution: This window is trap")))) (EX.CursorInFn (LAMBDA (Window) (* fgh: "29-Jun-85 16:28") (* * The user has moved inside of the window, don't let him out) (LET ((EdgeWidth 10) (Region (DSPCLIPPINGREGION NIL Window)) (HalfWidth (QUOTIENT (fetch (REGION WIDTH) of (WINDOWPROP Window (QUOTE REGION))) 2)) (HalfHeight (QUOTIENT (fetch (REGION HEIGHT) of (WINDOWPROP Window (QUOTE REGION))) 2)) (Position (CURSORPOSITION NIL Window)) NewRegion) (* * Compute a region just inside of the border of the window) (SETQ NewRegion (CREATEREGION (PLUS EdgeWidth (fetch (REGION LEFT) of Region)) (PLUS EdgeWidth (fetch (REGION BOTTOM) of Region)) (DIFFERENCE (fetch (REGION WIDTH) of Region) EdgeWidth) (DIFFERENCE (fetch (REGION HEIGHT) of Region) EdgeWidth))) (* * Forever, if the cursor moves out of the inner region move the window to center on the cursor position) (while T do (COND ((INSIDEP NewRegion (CURSORPOSITION NIL Window Position)) (* Allow user to escape if chords the left and right mouse buttons) (AND (LASTMOUSESTATE (AND RIGHT LEFT)) (RETURN NIL))) (T (MOVEW Window (DIFFERENCE LASTMOUSEX HalfWidth) (DIFFERENCE LASTMOUSEY HalfHeight)))) (* Allow other processes to run) (BLOCK))))) References Chapter 19 of the IRM! But beware that this material is fairly old and out-of-date. Look in the Interlisp release messages for updates. (LIST ((PAGE NIL NIL (0 0 17 22) ((HEADING NIL (HEADINGTYPE YYY) (540 756 72 36) NIL) (TEXT NIL NIL (72 72 468 648) NIL))) (PAGE NIL NIL (0 0 17 22) ((FOLIO NIL (PARALOOKS (QUAD RIGHT) CHARLOOKS (SUPERSCRIPT 0 SIZE 12 FAMILY TIMESROMAN OVERLINE OFF STRIKEOUT OFF UNDERLINE OFF SLOPE REGULAR WEIGHT MEDIUM)) (468 756 72 36) NIL) (HEADING NIL (HEADINGTYPE XXX) (72 756 72 36) NIL) (TEXT NIL NIL (72 72 468 648) NIL))) (PAGE NIL NIL (0 0 17 22) ((FOLIO NIL (PARALOOKS (QUAD RIGHT) CHARLOOKS (SUPERSCRIPT 0 SIZE 12 FAMILY TIMESROMAN OVERLINE OFF STRIKEOUT OFF UNDERLINE OFF SLOPE REGULAR WEIGHT MEDIUM)) (468 756 72 36) NIL) (HEADING NIL (HEADINGTYPE XXX) (72 756 72 36) NIL) (TEXT NIL NIL (72 72 468 648) NIL)))))C$$ HHllDD  HHllHHll  $$HHHH  ll$$ $$ ll$$" ll ll$$$$llHH HH$$ ll HH HH PAGEHEADINGXXXH PAGEHEADINGYYY TIMESROMAN TIMESROMAN TIMESROMAN  TIMESROMAN TIMESROMAN GACHA GACHA TIMESROMAN TIMESROMAN  TIMESROMAN TIMESROMAN TIMESROMAN  TIMESROMAN TIMESROMAN TIMESROMAN  TIMESROMAN  TIMESROMAN TIMESROMAN CBGAG@ <e<K??S <??G<t>h==<]< SKIO.GETFN.2 TIMESROMAN ;*$? >u>>i' SKIO.GETFN.2 TIMESROMAN >>>% >>=&   > =&     :d5    ' #U##V#5    3"%5    +"Z;< 9H98( BMOBJ.GETFN2 TIMESROMAN  6 9/5    7 >l5             % ) 3 $  3 $ 0C:3.   1 @ 1 8 ?1  N,],   =1 3G   1  "1  *1  +1  <1 / 1 +  BMOBJ.GETFN2+  BMOBJ.GETFN2.7 +  BMOBJ.GETFN2+  BMOBJ.GETFN2.5 +  BMOBJ.GETFN2+  BMOBJ.GETFN2.6 +  BMOBJ.GETFN2*  BMOBJ.GETFN2 TIMESROMAN  .C +  BMOBJ.GETFN2+  BMOBJ.GETFN2.7 +  BMOBJ.GETFN2*  BMOBJ.GETFN2 TIMESROMAN  - >I5     / 0( &5  15  !5  :B5 5 *7 :  [)a1| BMOBJ.GETFN2 TIMESROMAN )))c2O2H  27 94U@$$$8 >"E& R& & )& & ~& & &J"8/  "e(u1> (c1[1m( 111(U1Y $2 >>s>I=     =     &$( > = =O >H =       %   @  %    =       (   @  % 0 =         @  %   >F &a  6 0 4 & !q   BMOBJ.GETFN2 TIMESROMAN =       D  & ! SKIO.GETFN.2 TIMESROMAN &. !M SKIO.GETFN.2 TIMESROMAN &/ !L SKIO.GETFN.2 TIMESROMAN "          #   "             g   @<H ? ? < <# ?U ? ?[ 8 8  SKIO.GETFN.2 TIMESROMAN T 8   .  9  =   ( w (  (y (   - (    (  (  = =     % B  G   F  / (  (  4  #  =m   2 ) = = &o =g & ! )  =|  &D  =A =   5  ; & )  =  *  ;  =  F  =  G  =  =   =      :  \ &m  !   &" &  =        L ; = 4 4| 2 4F 3 " 3  3 , 3  " 3     3 3 " 3   1x "4 3     a 3    3   3    3   t   3   :  F "6 3    1d 13   0 0S 3   0\ 3&   ", 3   ' 3   3   ! 7 4e 3        4 /  p ' Q   3      &       [ 3     (      =G 2 2 4 3& 3W 3T ! !D ! !M q ~   3& " 37  e 31      3*   # " /a ID1.B&08'4LKMK$:J;BDD(MH/" IILLLFH>2.88CL:EFLEI..@+!MMJD1#4!$,# ; / BMOBJ.GETFN2 TIMESROMAN  <@99<<! 99,4) 3 0|0O   N0y3 1]k(3 "1UV 5 '3  1G4& 3 d3 K3  p3  ,3 h3 K3 z4, 3 1.1P3 r[)  -  \) . 9 9!=x=/::/=   h(30'0\0U0H&!(1\( / # # ."    cj#\UH/3\&\$&:" n}"   ,2C6 >M)b88u8X|";   6 A"B BMOBJ.GETFN2 TIMESROMAN   >>k9     4 4 7        +   > /     1 2  2 @<: 9j4(l4q(Y(0* 7004'w'G O A<3 99 4   Q b4   Ha4 4 w W49 4  D  $$2 62 .2  =2    0 8d ,4  4    2 1* 226< ?+)(I=1,,$'5IK/A>H 4G!.IICKCF>;G>=vH2C7$A>:  {z