3 # DCT - desktop calculator thingy
5 # reverse polish notition calculator using curses
17 use vars qw(@stack %val %var %set %alias %action %hook);
20 base => 10, # decimal; set using commands bin/oct/dec/hex/base
21 numb => 0, # fixed scientific engineering
22 card => 1, # degrees radians grades
23 coord => 0, # cartesian polar spherical
24 complex => 0, # real complex
26 height => 4, # stack depth (lines of stack plus one)
27 width => 42, # limit value precision, stetch menu
30 %alias = (' '=>'enter', "\004"=>'quit', 'q'=>'quit'); # rudimentary default key bindings
33 "chs" => [1, sub { -$_[0] }], # negative
35 "drop" => [1, sub { defined $val{i} ? '' : () }], # drop
36 "back" => [1, sub { () }], # drop essentially
37 "clear" => [0, sub { @stack = (); undef %val; () }], # clear all #todo: if (val{i}) delete char after cursor
40 local $_ = defined $val{i} ? $val{i} : $stack[0];
42 return defined $_ ? $_ : ();
45 "swap" => [2, sub { reverse @_ }], # swap x<->y
47 ($var{undo}, @stack) = ([@stack], @{ $var{undo} });
50 $var{stackpos} = 0 unless $var{stackpos}; # initialize
51 $var{stackpos} %= @stack; # cycle
52 $val{i} = $stack[$var{stackpos}++];
55 "version" => [-1, sub { error("Desktop Calculator Thingy $VERSION by Shiar"); () }], # version
57 "sto" => [1, sub { $var{a} = $_[0] }], # copy
58 '?' => [1, sub { $var{a} = $_[0] }], # assign
69 ReadKey; # wait for confirm
70 1 while defined ReadKey(-1); # clear key buffer
75 my ($val, $base) = @_;
76 return '' unless defined $val;
77 return $val if $base==10;
87 while ($int>$base**10) {
93 my $char = $int%$base;
94 $txt = ($char<10 ? $char : chr($char+55)).$txt;
98 $txt .= '.' if $frac>0;
99 for (my $i = 0; length $txt<$set{width}-2 && $frac>0; $i++) {
101 my $char = int $frac;
103 $txt .= $char<10 ? $char : chr($char+55);
106 $txt = "-".$txt if $sign;
107 $txt .= 'e'.showval($exp, $base) if $exp;
114 addstr($set{height}-$_, 1, "$_: ".showval($stack[$_], $set{base}));
117 clrtoeol($set{height}-@stack, 1);
122 eval 'require $_' ? push @modules, $_
123 : print STDERR "error loading $_\n".(join "", map "\t$_\n", split /\n/, $@)
127 ReadMode 3; # cbreak mode
131 } # restore terminal on quit
133 $set{height} = $LINES-2 if $LINES>=3;
134 $set{width} = $COLS if $COLS;
135 $_->() for @{ $hook{init} };
140 $_->() for @{ $hook{refresh} };
142 addstr($set{height}+1, 0, "> "); # prompt
146 addstr($set{height}+1, 2, showval($val{i}, $set{base}));
147 for my $cmd (@{ $hook{showentry} }) {
148 addstr($_) if $_ = $cmd->();
149 } # showentry functions
150 addstr($val{alpha}) if exists $val{alpha};
155 if ($key eq chr 27) {
156 $key .= $_ while defined ($_ = ReadKey(-1)); # read additional keys
158 $_ = $alias{$key} || $key; #if exists $alias{$key}; # command shortkeys
159 $_ = delete $val{alpha} if $_ eq "enter" and exists $val{alpha}; # use manual command
161 for my $cmd (@{ $hook{precmd} }) {
162 next LOOP if $cmd->();
165 last if $_ eq 'quit';
166 goto DRAW if $_ eq 'refresh';
168 if (exists $val{alpha} or /^\033?[A-Z]$/) {
169 if (defined $val{i}) {
170 unshift @stack, $val{i};
173 } # enter present value
176 $val{alpha} = substr $val{alpha}, 0, -1 or delete $val{alpha};
178 elsif ($_ eq "drop") {
182 $val{alpha} .= $key =~ /^\033(.)/ ? uc $1 : lc $key;
184 } # manual command entry
187 $val{i} = 0 unless defined $val{i};
188 $_ = -$_ if $val{i}<0; # substract from negative value
189 $val{i} = ($val{frac} and $val{frac} *= 10) ? $val{i}+$_/$val{frac}
193 $val{i} = 0 unless defined $val{i};
196 elsif ($_ eq "eex") {
197 $val{i} = 1 unless defined $val{i};
200 elsif ($_ eq "chs" and defined $val{i}) {
203 elsif ($_ eq "back" and defined $val{i}) {
204 $val{i} = ($val{frac} = int $val{frac}/10)
205 ? int($val{i}*$val{frac})/$val{frac} : int $val{i}/10
208 elsif (exists $action{$_}) {
209 my ($type, $cmd) = @{ $action{$_} };
210 unshift @stack, $action{enter}[1]->()
211 if $type>0 and defined $val{i}; # auto enter
212 if ($type>0 and $type>@stack) {
213 error("insufficient stack arguments for operation");
215 } # insufficient arguments
218 $var{undo} = [@stack]; # if $_ ne 'undo';
219 unshift @stack, $cmd->(splice @stack, 0, $type);
221 } # stack-modifying operation
228 error("unrecognised command: ".join(' ', map ord, split //, $_));
229 goto DRAW; # screen messed up
235 1.01 06-18 - start (curses, some basic commands)
236 1.02 06-20 - function keys select command/submenu from (sub)menu
237 - backspace to undo last digit
238 1.03 06-25 - values displayable in arbitrary base
239 - can enter fractions (.) and negative values (_)
240 1.04 08-04 14:45 - error dialog (don't mess up screen)
241 - manual command input using capital letters
243 pre 09-09 22:00 - overhaul in stack handling
244 1.05 09-10 19:45 - hp48-like drop (backspace but not editing value)
245 - error on insufficient arguments for command
246 - command backspacing
247 - some unit conversion (mostly lengths) from menu
248 - q for sq(rt) (formerly quit, now only ^D/quit)
249 1.06 09-15 23:10 - menu contents in module
250 - new commands: a?(sin|cos|tan)h, inv, !, rand
252 1.07 09-24 23:50 - numeric modifiers hardcoded instead of in action hash
253 - action undo: last stack alteration can be undone
254 - enter on no value repeats last val on stack
255 - new commands: sr/sr, shortkeys ( )
256 1.08 09-26 22:10 - additional digits were not correctly applied to negative values
257 - negative numbers displayed correctly in different bases
260 - stack command (cursor up) cycles through values in stack
261 1.09 09-27 00:57 - all key aliases moved to module DCT::Bindings
262 09-29 12:15 - number of menu items depends on screen width
263 10-11 21:30 - hooks allowing for extra code at reload, showentry, and precmd
264 21:50 - all menu related functions moved to menu.pm
265 22:05 - unit conversion out of main program (entirely into unitconv.pm)
266 10-12 01:50 - backspace becomes "back" (soft drop, like old "drop")
267 - normal drop command (alt+bs) removes input/stack value at once
268 02:13 - $val{frac} default undefined instead of 0