# Data-entry code for Tk-based RPN calculator

# This code is in the public domain. See the main program
# for disclaimer of liability.
#
# $Header: /home/neil/src/tcl/calc/RCS/calcentr,v 1.6 2000/12/28 14:38:45 neil Exp $
#
# MakeEntryBox makes a data-entry widget for an RPN calculator.
# "path" is the path of the widget in which we'll make the
# data-entry box; it is normally a frame or a toplevel.
# "pushProc" is the procedure to call when the
# "Enter" key is pressed; "pushProc" should accept a single
# floating-point argument.

proc MakeEntryBox {path pushProc} {
    global calcEntryInfo

    # Just in case "path" is "."

    set pathpref $path
    if {[string compare $path "."] == 0} {
	set pathpref ""
    }

    # This is a table describing all the buttons on the keypad.
    # The entries in this list come in multiples of seven; the
    # entries are, in order:
    #
    #	The button name
    #	The text on the button
    #	The row where the button is gridded
    #	The grid row where the button stops (usually the same
    #		as the previous entry; a larger number indicates
    #		that the button spans more than one row.)
    #	The column where the button is gridded
    #	The grid column where the button stops (again, usually
    #		the same as the previous entry, and a larger
    #		number indicates that the button spans multiple columns)
    #	The code to execute when the button is pressed.

    set kpButtonList [list \
	zero	"0"	3	3	0	0 \
				[list EnterSym $path 0] \
	one	"1"	2	2	0	0 \
				[list EnterSym $path 1] \
	two	"2"	2	2	1	1 \
				[list EnterSym $path 2] \
	three	"3"	2	2	2	2 \
				[list EnterSym $path 3] \
	four	"4"	1	1	0	0 \
				[list EnterSym $path 4] \
	five	"5"	1	1	1	1 \
				[list EnterSym $path 5] \
	six	"6"	1	1	2	2 \
				[list EnterSym $path 6] \
	seven	"7"	0	0	0	0 \
				[list EnterSym $path 7] \
	eight	"8"	0	0	1	1 \
				[list EnterSym $path 8] \
	nine	"9"	0	0	2	2 \
				[list EnterSym $path 9] \
	dot	"."	3	3	1	1 \
				[list EnterSym $path .] \
	ee	"EE"	3	3	2	2 \
				[list EnterSym $path E] \
	plus	"+"	4	4	0	0 \
				[list EnterSym $path +] \
	minus	"-"	4	4	1	1 \
				[list EnterSym $path -] \
	clr	"Clr"	4	4	2	2 \
				[list ClearEntry $path] \
	push	"Push/Enter"	5	5	0	2 \
				[list EnterPressed $pushProc $path] \
    ]

    foreach {name symbol startRow endRow startCol endCol proc} $kpButtonList {
	button $path.$name -text $symbol -command $proc
	set rowSpan [expr {$endRow - $startRow + 1}]
	set colSpan [expr {$endCol - $startCol + 1}]
	grid $path.$name -row $startRow -rowspan $rowSpan \
		-column $startCol -columnspan $colSpan \
		-sticky nsew
    }

    # Make the entry widget

    entry $pathpref.entry -width 25
    grid $pathpref.entry -row 6 -column 0 -columnspan 3 -sticky ew

    # Bind "enter" and "return" in the entry widget.

    bind $pathpref.entry <KeyPress-KP_Enter> [list EnterPressed $pushProc $path]
    bind $pathpref.entry <KeyPress-Return> [list EnterPressed $pushProc $path]

    # Initialize

    set calcEntryInfo(entered,$path) 0
}

# EnterSym puts a symbol into the entry widget
# in the data-entry box.

proc EnterSym {path str} {
    global calcEntryInfo

    set pathpref $path
    if {[string compare $path "."] == 0} {
	set pathpref ""
    }

    set ent $pathpref.entry
    if {$calcEntryInfo(entered,$path)} {
	$ent delete 0 end
	set calcEntryInfo(entered,$path) 0
    }

    $ent insert end $str
}

# EnterPressed is called whenever the "Push/Enter"
# button is pressed in the data entry area. It takes
# two arguments: the procedure to call to push the
# contents of the entry widget onto the stack, and
# the path of the data-entry box.

proc EnterPressed {pushProc path} {
    global calcEntryInfo

    set pathpref $path
    if {[string compare $path "."] == 0} {
	set pathpref ""
    }

    Error ""

    # Validate input.

    if {![ValidFloat [$pathpref.entry get]]} {
	Error "Invalid number entered"
	return
    }

    $pushProc [$pathpref.entry get]
    set calcEntryInfo(entered,$path) 1
}

# ClearEntry clears the entry widget in the data entry
# box specified by "path".

proc ClearEntry {path} {
    set pathpref $path
    if {[string compare $path "."] == 0} {
	set pathpref ""
    }

    set ent $pathpref.entry
    $ent delete 0 end
}

# ValidFloat returns 1 if "f" is a valid floating-point
# number, 0 otherwise.

proc ValidFloat {f} {
    set str ""

    # NOTE: this doesn't work right in tcl8.0 with strings
    # that are valid numbers followed by an "e" or an "E".
    # This works correctly with tcl8.3 and later.

    set errcode [catch [list scan $f "%g%s" fl str] nConv]
    if {$errcode != 0} {
	return 0
    }
    if {$nConv != 1} {
	return 0
    }
    if {[string length $str] != 0} {
	return 0
    }
    return 1
}

