- learn Tk by writing it
- add real scientific notation
- make the onscreen keys match the positions of the keypad on my Mac keyboard
- split the expression and results
Mysterious bugs:
- The text widget doesn't always accumulate characters. SMH 20050427 Fixed. Changed by removing $ from set $::estate notDone
- The presence of the insertion cursor doesn't tell the whole story about focus.
- Superscripts are being translated back into ASCII. I have tried just an entry widget in wish and not gotten correct results then either. DHP 2005-04-30 Actually they weren't being translated at all, becaues of a reference to array superscripts without a scope operator. Fixed.
- escargo 27 Apr 2005 - Numbers entered through the keyboard are entered twice. (I suspect that perhaps two different bindings are firing.) DHP 28 Apr - It has something to do with focus. I thought the problem might be writing <Key- instead of <KeyPress- but changing to <KeyPress- doesn't fix the bug.
- DHP 2005-04-30 Now that the superscripts are being correctly translated to Unicode, I see garbage in the entry widget. But I get the same results in wish so in a sense I've still moved forward.
Other changes:
- DHP 2005-04-28 Multiplying large integers may give a negative integer. Added a second "string map" entry, from * to *1.0*.
- DHP 2005-04-28 Changed [0-9][0-9]* to [0-9]+ in superscriptRE.
- DHP 2005-04-30 Put all Unicode hex codes in uppercase.
- DHP 2005-04-30 Added break to all bindings.
- DHP 2005-04-30 Added wm protocol command at very beginning.
- DHP 2005-04-30 Put title setting just after wm protocol command.
- DHP 2005-04-30 Added scope to array set superscripts for consistency.
- DHP 2005-04-30 Corrected the Unicode constants for superscripts 1, 2, and 3.
- DHP 2005-04-30 Added mathMinus, mathTimes, mathDivide, timesTenToThe, timesTenToTheN character constants.
- DHP 2005-04-30 Reformatted lines that pack .r into the grid.
- DHP 2005-04-30 Changed <Key-/> to <Key-slash> and <Key-*> to <Key-asterisk>.
- DHP 2005-04-30 Added binding for <Key-equal>.
- DHP 2005-04-30 Now stripping + (but not -) from exponent. Also stripping leading zeroes.
- DHP 2005-04-30 Used correct scope on superscripts in string map command inside setr. Now mapping is being done. I was wrong about characters being transformed back to ASCII -- they weren't being changed at all.
Grand plans for later, from other people's code if it works:
- Get rid of the bugs.
- Find a place for a "change sign of result" key. When entering expressions, the - key will work as unary and binary minus, but for chained calculations you would need two keys.
- Auto-scroll expression as you type.
- The string map trick still doesn't work everywhere, for exasmple when adding a large integer to a number in scientific notation. It makes more sense to me to change the math routines themselves anyway.
- Switch to RPN. Display as much stack (or memory or whatever) as possible.
- A text widget that fully acts like a calculator display, with insertion and deletion and selection allowed only when they make sense.
- More accurate math.
- Some kind of type system, because with an entire programming language at your disposal, using regexes on the results can't possibly work reliably. Also expr may not be predictable enough.
- Features, always features....
- Use of the option database or another way to separate preferences from hardcoded behavior.
- A single and short representation of the grid layout data.
- Better Macintosh compatibility.
- Good UNIX and Windows compatibility would also be nice.
- As much as I like HP calculators, borrowing the shift keys and programming mentality from them (as in Programmable RPN calculator) seems misguided. So some kind of better languge (Tcl? FORTH? LISP?).
package require Tk ### Arrange for a clean exit. wm protocol . WM_DELETE_WINDOW exit ### Set the window title. wm title . Calculator ### Constants and variables. set ::n 0 ;# current button-widget name set ::c 4 ;# number of columns set ::upToDateForeground black ;# for result that has just been computed # when expression is shown, and for # expression that can be edited set ::staleForeground gray ;# for result that remains from last # calculation but expression isn't shown, # or for expression that can't be edited set ::errorForeground red ;# for result after a Tcl error set ::staleErrorForeground darkred # for stale erroneous result ## As well as representing colors, these change the behavior of procedures. set ::estate notDone ;# notDone (can be edited, # upToDateForeground) or done (can't be # edited, staleForeground) set ::rstate upToDate ;# upToDate or stale or error or staleError # as explained next to colors array set ::superscripts { 0 \u2070 1 \u00B9 2 \u00B2 3 \u00B3 4 \u2074 5 \u2075 6 \u2076 7 \u2077 8 \u2078 9 \u2079 + \u207A - \u207B } # 1 2 3 4 set superscriptRE {^([-+]?[0-9]+(?:\.[0-9]+)?)(?:[DdEe]([-+]?[0-9]+))?$} # 1 is sign, integer, fraction if any; integer. with no fraction is not # allowed # 2 is fraction if any, an operand to the ? afterward, not returned # by regex because of the ?: modifier # 3 is exponent character, exponent sign, exponent, not returned by regex # because of the ?: modifier # 4 is the part of 3 that I want to translate to Unicode # ASCII "+" is fine for math plus set ::mathMinus "\u2212" set ::mathTimes "\u00D7" set ::mathDivide "\u00F7" set ::timesTenToThe "\u00D710" set ::timesTenToTheN "\u00D710\u207F" ## Widgets created later: .e is the expression (an entry widget) ## and .r is the result (a second entry widget). .0 through .17 ## are the buttons. ### Create buttons but don't do anything with them yet. ## keypad button widget names ## layout and "grid" command args ## ## c = / * .0 .1 .2 .3 ## 7 8 9 - .4 .5 .6 .7 ## 4 5 6 + .8 .9 .10 .11 ## 1 2 3 e .12 .13 .14 .15 ## 0 . .16 - .17 ^ ## ## c represents "clear" ## e represents "enter" ## ## button purposes like keypad except enter position is = purpose ## and = position is exponent purpose foreach key { C e / * 7 8 9 - 4 5 6 + 1 2 3 = 0 . } { ## Set each key's button's text. ## "default" applies to C 7 8 9 4 5 6 + 1 2 3 = 0 . keys switch -- $key { e {set keytext $::timesTenToTheN} / {set keytext $::mathDivide}
- {set keytext $::mathTimes}
- {set keytext $::mathMinus} default {set keytext $key} } ## Set each key's button's command. See "procedures" below. ## "default" applies to e / * 7 8 9 - 4 5 6 + 1 2 3 0 . keys switch -- $key { C {set cmd clearboth} = {set cmd =} default {set cmd "hit $key"} } ## Create a button with the text and command as just set. ## The grid manager changes a button's width automatically, ## but not its height, so do that now. if [expr $::n == 15] { button .$::n -text $keytext -command $cmd -width 4 -height 2 } else { button .$::n -text $keytext -command $cmd -width 4 } incr ::n } ### Lay out the entry widgets and buttons in a grid. ## Macintosh system dependency -- This is the only font I've found ## with superscripts that are all the same size. grid [entry .e -textvar e -font {{Hoefler Text} 24} -just left] \ -sticky we -columnspan $::c -pady 5 grid [entry .r -font {{Hoefler Text} 24} -just right] \ -sticky we -columnspan $::c -pady 5 grid .0 .1 .2 .3 grid .4 .5 .6 .7 grid .8 .9 .10 .11 grid .12 .13 .14 .15 grid .16 - .17 ^ grid configure .0 -sticky we grid configure .1 -sticky we grid configure .2 -sticky we grid configure .3 -sticky we grid configure .4 -sticky we grid configure .5 -sticky we grid configure .6 -sticky we grid configure .7 -sticky we grid configure .8 -sticky we grid configure .9 -sticky we grid configure .10 -sticky we grid configure .11 -sticky we grid configure .12 -sticky we grid configure .13 -sticky we grid configure .14 -sticky we grid configure .15 -sticky nsew grid configure .16 -sticky we grid configure .17 -sticky we ### Probably Macintosh system dependency -- Bind keyboard keys. ### Focus will be set later, and we assume it is never reset. bind .e <Key-Num_Lock> {clearboth; break} bind .e <Key-c> {clearboth; break} bind .e <Key-KP_Equal> {hit e; break} bind .e <Key-e> {hit e; break} bind .e <Key-KP_Divide> {hit /; break} bind .e <Key-slash> {hit /; break} bind .e <Key-KP_Multiply> {hit *; break} bind .e <Key-asterisk> {hit *; break} bind .e <Key-KP_7> {hit 7; break} bind .e <Key-7> {hit 7; break} bind .e <Key-KP_8> {hit 8; break} bind .e <Key-8> {hit 8; break} bind .e <Key-KP_9> {hit 9; break} bind .e <Key-9> {hit 9; break} bind .e <Key-KP_Subtract> {hit -; break} bind .e <Key-minus> {hit -; break} bind .e <Key-KP_4> {hit 4; break} bind .e <Key-4> {hit 4; break} bind .e <Key-KP_5> {hit 5; break} bind .e <Key-5> {hit 5; break} bind .e <Key-KP_6> {hit 6; break} bind .e <Key-6> {hit 6; break} bind .e <Key-KP_Add> {hit +; break} bind .e <Key-plus> {hit +; break} bind .e <Key-KP_1> {hit 1; break} bind .e <Key-1> {hit 1; break} bind .e <Key-KP_2> {hit 2; break} bind .e <Key-2> {hit 2; break} bind .e <Key-KP_3> {hit 3; break} bind .e <Key-3> {hit 3; break} bind .e <Key-KP_Enter> {=; break} bind .e <Key-Return> {=; break} bind .e <Key-equal> {=; break} bind .e <Key-KP_0> {hit 0; break} bind .e <Key-0> {hit 0; break} bind .e <Key-KP_Decimal> {hit .; break} bind .e <Key-period> {hit .; break} ### Procedures that widgets call. proc clearboth {} { focus .e set ::e "" .e config -foreground $::upToDateForeground set ::estate notDone set ::r "" setr .r config -foreground $::upToDateForeground set ::rstate upToDate } proc = {} { focus .e .e config -foreground $::staleForeground set ::estate done if {![catch [set ::r [expr [string map {* *1.0* / *1.0/} $::e]]]]} { # what value should r show? its previous one? expr's best nuemrical # approximation? text of the error? .r config -foreground $::errorForeground # is this really a good idea? setr set ::rstate error } else { .r config -foreground $::upToDateForeground setr set ::rstate upToDate } } proc hit {key} { focus .e switch -- $::estate { notDone { .e insert end $key .e icursor end } done { if [regexp {[-+*/]} $key] { set ::e $::r } else { set ::e "" } .e insert end $key .e icursor end set ::estate notDone .e config -foreground $::upToDateForeground } default { # ignore for now } } switch -- $::rstate { upToDate { .r config -foreground $::staleForeground set ::rstate stale } stale { # remains stale } error { .r config -foreground $::staleErrorForeground set ::rstate staleError } staleError { # remains staleError } default { # ignore for now } } } ### Subroutines. proc setr {} { # Hopefully end will be rounded, as described in manual .r delete 0 end set ignored "" set mantissa "" set exponent "" set rdisplay "" if {[regexp $::superscriptRE $::r ignored mantissa exponent]} { if {[string length $exponent] > 0} { set unicodeExponent [regsub ^\\+0* $exponent ""] set unicodeExponent [regsub ^-0* $unicodeExponent "-"] set unicodeExponent [string map [array get ::superscripts] $unicodeExponent] set rdisplay "$mantissa$::timesTenToThe$unicodeExponent" } else { set rdisplay $mantissa } } .r insert end $rdisplay } ### Main loop. encoding system utf-8 focus .e wm resizable . 0 0