home *** CD-ROM | disk | FTP | other *** search
- /*
- MSGBOX.PRG: MsgBox(), a user interface building block function.
- Calls should be made through the MESSAGE user-defined
- command found in MSGBOX.CH.
- Author: Craig Yellick
- Excerpted from "Clipper 5: A Developer's Guide"
- Copyright (c) 1991 M&T Books
- 501 Galveston Drive
- Redwood City, CA 94063-4728
- (415) 366-3600
- */
-
- //───── NOTE: must compile with the /N option!
-
- // Required for using the MESSAGE command.
- #include "msgbox.ch"
-
- // Required for using the ColorSet() function.
- #include "colors.ch"
-
- // Other handy programming things.
- #include "inkey.ch"
- #command DEFAULT <a> := <b> => <a> := if(<a> = nil, <b>, <a>)
-
-
- /*
- This #define will cause the testing/demo routine to be included in
- the compile. Delete the line and recompile to get rid of the test
- code and produce a smaller OBJ file. However-- leave the source
- code here so you can more easily test changes and additions in the
- future.
- */
- #define TESTING
- #ifdef TESTING
- function MsgTest()
- local dir_, sel, mess
-
- // Must be called prior to using ColorSet().
- ColorInit()
-
- // Clean up screen so this demo looks nice.
- setcursor(0)
- @ 0,0 clear
-
- message "This is a simple test of the MESSAGE command."
-
- message "Can direct message to specific",;
- "row/col: 12,40." at 12,40
-
- message "Multiple","Lines","Per","Message" at 1,50
-
- message "Notice how box is being sized", ;
- "exactly large enough to hold the", ;
- "lines of text, automatically?" at 19, 1
-
- message "Of course,","we can","control this", ;
- "by specifying","all the","coordinates." at 6,3 to 16,20
-
- message "Press Any Key to Continue..." ;
- wait 0 at 8,30 color C_MESSAGE
-
- @ 0,0 clear
-
- message "We can also specify the WIDTH and DEPTH", ;
- "of the box directly, rather than trying to", ;
- "calculate the ending coordinates. This", ;
- "box is directly set to be 10 by 40..." ;
- at 2,2 width 40 depth 10 color C_ENHANCED
-
- // Load an array with filenames
- dir_ := {}
- aeval(directory(), { |f_| aadd(dir_, f_[1]) } )
-
- message "Better yet, we can specify that the lower", ;
- "part of the box be used as a scrolling", ;
- "window where you can make a selction.", "" , ;
- "Pick a file..." color C_ENHANCED ;
- choose dir_ choosecolor C_BOLD into sel ;
- at 8,32 depth 14
-
- message "You selected: " +if(sel = 0, "<none>", dir_[sel]), ;
- "Press any key to continue..." color C_WARNING ;
- wait 0 at 9,38
-
- // Can stick message lines in an array.
- mess := {"There's always the option to restore the", ;
- "screen that was underneath, should we cover", ;
- "up something important. Press a key to", ;
- "bring back the File Selection message."}
-
- message mess at 7,23 wait 0 color C_MESSAGE restore
-
- message "That's it for the demo..." at 18,1 ;
- color C_BLINK wait 5
-
- @ 0,0 clear
-
- return nil
- #endif
-
- /*------------------------------------------------------------------*/
-
-
- function MsgBox(r1, c1, r2, c2, width, depth, ;
- msg_, msgClr, ;
- ch_, chClr, ;
- wait, restore)
- /*
- General-purpose message display and choice selection function.
-
- Parameter Description
- --------- -----------
- r1 Starting row, default is row().
- c1 Starting column, default is col().
-
- r2 Ending row, default based on messages/choices.
- c2 Ending column, default based on messages/choices.
-
- width Columns wide, defaults to length of longest line.
- depth Rows deep, defaults to number of message/choice lines.
- (Width and depth are ignored if r2 and/or c2
- are specified.
-
- msg_ Array of message lines.
- msgClr Color number for main box and message section.
- ch_ Array of choice lines.
- chClr Color number for choice section.
-
- wait How long to wait (nil = don't)
- restore True = restore screen afterwards, False/nil = leave.
-
- ┌────────────────┐
- │ Message Line 1 │
- General layout if │ Message Line 2 │
- both Message lines │ : etc │
- and Choice lines ├────────────────┤
- are used: │ Choice Line 1 │
- │ Choice Line 2 │
- │ : etc │
- └────────────────┘
- */
-
- local sel // User's selection to return (if any)
- local msgLen := 0, chLen := 0 // Length of longest line in msg_, ch_
- local boxWide, boxDeep // Box dimensions
- local oldCur, oldClr, oldScr // Existing color, cursor and screen
- local oldR, oldC // Existing row/column position
- local cr1 // Starting choice row
- local i // Everyone's favorite iterator
-
-
- // Some parameters have direct default values.
- default r1 := row()
- default c1 := col()
- default msg_ := {}
- default ch_ := {}
- default msgClr := C_NORMAL
- default chClr := C_ENHANCED
- default restore := .f.
-
- // Nil is used to indicate no message is desired, just choices.
- if (len(msg_) > 0) .and. (msg_[1] = nil)
- msg_ := {}
- endif
-
-
- /*
- Message and choices may be passed as a nested array due to the way
- the MESSAGE #command works, to provide maximum flexibility.
- Aclone() is used because we're dealing with an array reference, and
- yanking out a nested level may not please the calling routine which
- owns the array.
- */
- if (len(msg_) > 0) .and. (valtype(msg_[1]) = "A")
- msg_ := aclone(msg_[1])
- endif
- if (len(ch_) > 0) .and. (valtype(ch_[1]) = "A")
- ch_ := aclone(ch_[1])
- endif
-
-
- /*
- At this point we can determine some default dimensions. User can
- override them based on the "TO R, C" or DEPTH, WIDTH parameters.
- */
- // Length of longest line in message area.
- aeval(msg_, { |s| msgLen := max(msgLen, len(s)) })
-
- // Length of longest line in choice area.
- aeval(ch_, { |s| chLen := max(chLen, len(s)) })
-
- // Initial depth of box is messages plus choice lines.
- // Add one line for bottom of box and one for separator line.
- boxDeep := if(len(msg_) = 0, 0, len(msg_) +1) ;
- +if(len(ch_) = 0, 0, len(ch_) +1)
- boxDeep := min(boxDeep, maxrow() -r1)
-
- // Initial width of box is longest of message and choice lines.
- // Add one column for right edge of box.
- boxWide := max(msgLen, chLen) +1
-
-
- /*
- Some parameters can now be defaulted based on others.
- */
- default r2 := r1 +if(depth = nil, boxDeep, depth)
- default c2 := c1 +if(width = nil, boxWide, width)
-
-
- /*
- Adjust confused parameters so programmer can see what's happened
- and keep on testing.
- */
- if r2 <= r1
- r2 := maxrow()
- endif
- if c2 <= c1
- c2 := maxcol()
- endif
-
-
- /*
- Now that we know the dimensions we can save
- the screen region and draw the initial box.
- */
- if restore
- oldScr := savescreen(r1, c1, r2, c2)
- endif
- oldR := row() ; oldC := col()
- oldCur := setcursor(0)
- oldClr := ColorSet(msgClr)
- @ r1, c1 clear to r2, c2
- @ r1, c1 to r2, c2
-
-
- // Display as many message lines as can fit in box.
- // Trim line widths to fit in interior of box.
- for i := 1 to min(len(msg_), r2 -(r1 +1))
- devpos(r1 +i, c1 +1)
- devout(left(msg_[i], c2 -(c1 +1)))
- next i
-
- // If a choice array was sent.
- if len(ch_) > 0
-
- // If a message array was sent.
- if len(msg_) >0
- // Separator line between message and choice sections.
- devpos(r1 +len(msg_) +1, c1)
- devout(chr(195) +replicate(chr(196), c2-(c1+1)) +chr(180))
- cr1 := r1 +len(msg_) +2
- else
- cr1 := r1 +1
- endif
-
- // Use "choices" color setting.
- ColorSet(chClr)
-
- // ENTER or ESC key allow exit.
- do while .t.
- sel := achoice(cr1, c1 +1, r2 -1, c1 +(c2 -c1) -1, ch_)
- if (lastkey() = K_ENTER) .or. (lastkey() = K_ESC)
- exit
- endif
- enddo
-
- elseif valtype(wait) = "N"
- sel := inkey(wait)
- endif
-
- // Restore the screen environment before exit.
- if restore
- restscreen(r1, c1, r2, c2, oldScr)
- endif
- setcolor(oldClr)
- setcursor(oldCur)
- devpos(oldR, oldC)
-
- return sel
-
- // end of file MSGBOX.PRG
-