#!/bin/sh # -*- tcl -*- \ exec wish "$0" "$@" # Copyright (C) 1998-2004 Robert Widhopf-Fenk # # Author: Robert Widhopf-Fenk # X-URL: http://www.robf.de/Hacking/tcltk.html # X-RCS: $Id: tkcalc.tk,v 1.8 2004/12/30 23:15:44 fenk Exp $ # This code is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 1, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. set debug 1 set debug 0 proc debug {msg} { global debug if {$debug} { puts stderr $msg } } array set vars { e 2.7182818 pi 3.1415927 g 9.80665 } set history {} array set funs { {log2} {{x} {log(x)/log(2)}} {pow2} {{x} {pow(2,x)}} } set calc(buttons) { {{1/x} {x^2} {Sqr} {CE/C} {AC}} {{INV} {sin} {cos} {tan} {DRG}} {{e} {EE} {log} {ln} {y^x}} {{PI} {x!} "(" ")" "/"} {{STO} {7} {8} {9} {*}} {{RCL} {4} {5} {6} {-}} {{SUM} {1} {2} {3} {+}} {{EXC} {0} {.} {+/-} {=}} } proc matchParen {direction lp rp} { set i 0 if {$direction=="-forward"} { set pos [.display index "insert -1 char"] } else { set pos insert } while {$pos!={} && $i<10} { if {$direction=="-forward"} { set pos [.display search -regexp $direction -count len -- \ "\\$lp|\\$rp" "$pos +1 char" end] } else { set pos [.display search -regexp $direction -count len -- \ "\\$lp|\\$rp" $pos 1.0] } if {[string length $pos]} { if {[string compare [.display get $pos] $lp]} { incr i -1 if {$i<0} { bell .display tag remove paren 1.0 end break } elseif {$i>0} { } else { if {$direction=="-forward"} { .display tag add paren insert "$pos +1 char" } else { .display tag add paren $pos insert } break } } else { incr i } } } if {$i>0} {bell} } # This is a funny trick: in order to avoid parsing the parens structure # we transform it into a list and iterate that one proc yammFun {expr {level {}}} { global funs debug "$level$expr<" set expr [eval $e] } {^@ } { # sub expression set e [yammFun [string range $e 2 end] "$level\t"] # regsub -all -- "\[\t \]+" $e "" e set fun $name # we car only for the one just before # set name "" debug "$level[info exist funs($fun)]" global funs vars if {[info exist funs($fun)]} { set args [split $e ","] set fargs [lindex $funs($fun) 0] set fexpr [lindex $funs($fun) 1] debug "$level>>$fun=$funs($fun)>$args<$var<$fexpr<<<<<<<<<<" if {[info exists fvars($var)]} { regsub -all -- "\\\$$var" $fexpr "$fvars($var)" fexpr } elseif {[info exists vars($var)]} { regsub -all -- "\\\$$var" $fexpr "$vars($var)" fexpr } } } else { while {[regexp -- {([A-Za-z][A-Za-z0-9]*) *([^\(A-Za-z0-9]|$)} \ $fexpr junk var]} { debug "$level <<~~$var<$fexpr<" if {[info exists fvars($var)]} { regsub -all -- "$var *(\[^\(a-z\]|\$)" $fexpr \ "$fvars($var)\\1" fexpr } elseif {[info exists vars($var)]} { regsub -all -- "$var *(\[^\(a-z\]|\$)" $fexpr \ "$vars($var)\\1" fexpr } } } debug "${level}fexpr=$fexpr" } global debug if {$debug} { set expr [lreplace $expr [expr $i - 1] $i "($fexpr)"] } else { if {$feval} { set expr [lreplace $expr [expr $i - 1] $i [subst $fexpr]] } else { set expr [lreplace $expr [expr $i - 1] $i [eval expr $fexpr]] } } } else { global debug if {1 || $debug} { set expr [lreplace $expr $i $i "($e)"] } else { ## todo do we need this? set expr [lreplace $expr $i $i [eval expr $e]] } } } } } # regsub -all -- "\[\t \]+" $expr "" expr debug "$level<$expr>$expr<<" regsub -all "\\(" $expr " {@ " expr regsub -all "\\)" $expr " } " expr regsub -all "\\\[" $expr " {\[ " expr regsub -all "\\\]" $expr " } " expr debug ">>$expr<<" set expr [yammFun $expr] return $expr } proc calcLine {} { regsub {\..*} [.display index "insert linestart"] {} i while {$i > 0} { set expr [.display get "$i.0" "insert lineend"] if {[info complete $expr]} {break} incr i -1 } if {[info complete $expr] || [regexp {sum} $expr]} { set doeval 1 set nohist 0 regsub -- {^ *\$[0-9]+ = } $expr "" expr switch -regexp -- $expr { {^sum } { set sum 0 regsub {^sum} $expr {} expr foreach v $expr { incr sum $v } set doeval 0 set expr $sum } {^cls$} { .display delete 1.0 end return } {^history$} { global history set expr "" set i 0 foreach h $history { append expr [format "%3s = %s\n" "\$$i" $h] incr i } set doeval 0 set nohist 1 } {^vars$} { global vars set expr [lsort -dictionary [array names vars]] set doeval 0 set nohist 1 } {^debug$} { global debug set expr [set debug [expr !$debug]] set doeval 0 set nohist 1 } {^quit$} { exit } {^funs$} { global funs set expr [lsort -dictionary [array names funs]] set doeval 0 set nohist 1 } {^\[.*\]$} { debug tcleval set expr [uplevel \#0 subst $expr] set doeval 0 } {[A-Za-z][A-Za-z0-9]* *\([^\(]*\) *=} { # function assignment debug funset regexp -- {([A-Za-z][A-Za-z0-9]*) *\(([^\(]*)\) *= *(.*)} \ $expr junk fun args expr global funs set funs($fun) [list [split $args ","] $expr] .message insert end "\nfunction $fun saved" .message see end .display insert insert "\n" return } {[A-Za-z][A-Za-z0-9]* *\([^\(]*\)} { # function substitution debug funsubst set expr [funSubst $expr] global debug if {$debug} { .display mark set insert "insert lineend" .display insert "insert" "\n" .display insert "insert" "$expr" subst } } {[A-Za-z][A-Za-z0-9]* *= *} { # variable assignment debug varset regexp -- {([A-Za-z][A-Za-z0-9]*) *= *(.*)} $expr junk var expr global vars set expr [set vars($var) [expr $expr]] .message insert end "\nvariable $var saved" .message see end .display insert insert "\n" return } {[A-Za-z][A-Za-z0-9]* *([^\(A-Za-z0-9.]|$)} { # variable substitution debug varsubst global vars while {[regexp -- {(^|[*/+()-])([A-Za-z][A-Za-z0-9]*)([*/+()-]|$)} \ $expr junk junk var]} { if {[info exist vars($var)]} { regsub -- {(^|[*/+()-])([A-Za-z][A-Za-z0-9]*)([*/+()-]|$)} \ $expr "\\1$vars($var)\\3" expr } else { error "No variable with name `$var'" } } } {\$-?[0-9]+} { while {[regexp -- {\$(-?[0-9]+)} $expr junk pos]} { global history set pos [expr $pos < 0 ? [llength $history] - $pos : $pos] if {$pos >= 0 && $pos < [llength $history]} { regsub -- {\$(-?[0-9]+)} $expr [lindex $history $pos] expr } else { error "No history element $pos" } } } default { # do nothing } } puts $expr if {$doeval} { set expr [expr $expr] } .display mark set insert "insert lineend" .display insert "insert" "\n" set pos [.display index insert] if {$nohist} { .display insert "insert" $expr } else { global history .display insert "insert" [format "%4s = %s" "\$[llength $history]" $expr] lappend history $expr } .display tag add result $pos insert .display see insert } else { .display insert insert "\n" } } proc getLine {{offset 0}} { set line [.display get \ "insert -$offset lines linestart" \ "insert -$offset lines lineend"] regsub -- { +\$[0-9]+ = } $line {} line return $line } proc putLine {{expr {}} {args {}}} { # .display insert insert "\n" if {$expr!={}} { global history # puts $expr .display insert "insert" [format "\n%4s = %s" \ "\$[llength $history]" $expr] lappend history $expr .display mark set insert end .display see insert } } proc mathFac {n} { if {int($n) != $n} { .message "WARNING: $n is no integer!" error set n [expr int($n)] } if {$n < 1} { error "ERROR: $i is negative!" } else { for {set i [expr $n - 1]} {$i > 0} {incr i -1} { set n [expr $n*$i] } } return $n } proc mathSwitchSign {} { set i [.display index insert] set pos [.display search -regexp -backwards -count len\ {[^0-9][^0-9]} insert {insert linestart}] if {![llength $pos]} { set pos [.display search -regexp -backwards -count len \ {[^0-9]} insert {insert linestart}] } if {[llength $pos]} { set c [.display get $pos "$pos +$len char"] switch -regexp -- $c { {\+} {.display delete $pos; .display insert $pos -} {.-} {.display delete "$pos +1 char"} {-} {.display delete $pos; .display insert $pos +} default {.display insert "$pos +[string length $c] char" -} } } } proc calc {fun} { global M .message delete 1.0 end set code [catch { switch -exact -- $fun { {1/x} {putLine [expr 1/[getLine]]} {x^2} {putLine [expr [getLine]*[getLine]]} {Sqr} {putLine [expr sqrt([getLine])]} {CE/C} {.display delete "insert linestart" "insert lineend"} {AC} {.display delete 1.0 end; set M 0} {INV} {putLine [expr [getLine]]} {sin} {putLine [expr sin([getLine])]} {cos} {putLine [expr cos([getLine])]} {tan} {putLine [expr tan([getLine])]} {DRG} {putLine [error "ERROR: no supported"]} {e} {.display insert insert "2.7182818"} {EE} {.display insert insert "E"} {log} {putLine [expr log([getLine])]} {ln} {putLine [expr log([getLine])/log(2.7182818)]} {y^x} {putLine [expr pow([getLine 1],[getLine])]} {PI} {.display insert insert "3.1415927"} {x!} {putLine [mathFac [getLine]]} "(" {.display insert insert "("} ")" {.display insert insert ")"} {/} {.display insert insert "/"} {STO} {set M [getLine]} {7} {.display insert insert "7"} {8} {.display insert insert "8"} {9} {.display insert insert "9"} {*} {.display insert insert "*"} {RCL} {putLine $M} {4} {.display insert insert "4"} {5} {.display insert insert "5"} {6} {.display insert insert "6"} {-} {.display insert insert "-"} {SUM} {putLine [set M [expr $M+[getLine]]]} {1} {.display insert insert "1"} {2} {.display insert insert "2"} {3} {.display insert insert "3"} {+} {.display insert insert "+"} {EXC} {set dummy $M; set M [getLine]; putLine $dummy} {0} {.display insert insert "0"} {.} {.display insert insert "."} {+/-} {mathSwitchSign} {=} {calcLine} default {putLine "\nUnknown command!" error} } } err] if {$code && [string length $err]} { global errorInfo .message insert end "\n$err\n$errorInfo" error .message see end bell } } ############################################################################ if {![winfo exists .dframe]} { frame .dframe text .display -bg white -width 40 -height 16 -setgrid 1 \ -yscrollcommand {.scrollbar set} .display tag configure paren -background darkseagreen .display tag configure result -foreground blue .display tag configure subst -foreground green scrollbar .scrollbar -command {.display yview} frame .messv text .message -bg grey80 -width 36 -height 4 .message tag configure error -foreground red .message insert end "See the TCL manpages on `expr' for \nvalid expressions!" set buttons "v" button .v -textvariable buttons -command { if {$buttons == "^"} { set buttons v pack forget .buttons } else { set buttons ^ update pack .buttons -fill both -side bottom } } pack .scrollbar -side right -fill y -in .dframe pack .display -fill both -expand 1 -in .dframe pack .dframe -pady 2 -padx 2 -fill both -expand 1 pack .v -side right -in .messv pack .message -fill x -in .messv pack .messv -fill x frame .buttons set buttonnr 0 set linenr 0 foreach l $calc(buttons) { incr linenr frame .buttons$linenr pack .buttons$linenr -in .buttons -fill x foreach b $l { incr buttonnr button .button$buttonnr -font fixed -width 2 -pady 0 \ -text $b -command "calc $b" pack .button$buttonnr -side left -in .buttons$linenr -fill x -expand 1 } } bindtags .display "before Text .display . all" # state of entering numbers set RET 0 bind before {exit} bind before {source $argv0; debug "$argv sourced"} bind before {calcLine} bind before {calcEnter} bind before {calcEnter} bind before {%W insert insert "."; break} set selection "" bind before {getSelection} bind before {getSelection 1} bind before {insertSelection} focus .display wm title . "Tk Calculator: (c) 1998-2002 by Robert Fenk" bind .display { .display delete 1.0 end .display insert end "%A" bind .display { .display tag remove paren 1.0 end if {[.display compare insert > 1.0] && \ [.display get "insert -1c"]==")"} { matchParen -backward ")" "(" } elseif {[.display get insert]=="("} { matchParen -forward "(" ")" } set RET 1 } } .display insert end { This calculation is intended to be used like a sheet of paper, i.e. enter the expressions, scroll backward and forward, use earlier results, modify calculations ... Valid commands are: cls clear screen sum N1 N2 ... calculate sum history show history vars list variables funs list functions quit Servus debug enable debug Valid variables are: pi, e Valid functions are: pow2, log2 Define an own variable by: VARNAME = value Define an own function: FUNNAME(VARS) = FORMULAR Use the calculation history results: $NUMBER Keybindings: RET calculate result, hit it twice to get an empty line C-c copy last result to clipboard C-x cut last result to clipboard C-v paste from clipboard Example Session: 1+2 RETURN $0 = 3 RETURN pgsize=1024 RETURN mb(pgcount)=pgsize*pgcount RETURN mb(10) RETURN $1 = 10240/87 $2 = 117 $3 = 1198080.0/3 $4 = 399360.0 } } proc calcEnter {} { global RET puts "RET=$RET" if {$RET} { calc {=} set RET 0 .display see insert return -code break } else { putLine } } proc getSelection {{delete 0}} { global selection if {[.display tag ranges sel] == ""} { set rselection [.display get "insert linestart" "insert lineend"] regsub -- {^ +\$[0-9]+ = } $rselection "" selection set start [expr [string length $rselection] - [string length $selection]] .display tag add sel "insert linestart + $start chars " "insert lineend" } else { set selection "" foreach {s e} [.display tag ranges sel] { append selection [.display get $s $e] regsub -- {^ *\$[0-9]+ = } $selection "" selection } } if {$delete} { foreach {s e} [.display tag ranges sel] { .display delete $s $e } } selection own -command "set selection" } proc insertSelection {} { global selection if {[catch {.display insert insert [selection get -type STRING]}]} { .display insert insert $selection } }