$Tk::Calculator::RPN::HP::VERSION = '1.2'; package Tk::Calculator::RPN::HP; # OO base class and Exporter module for HP RPN calculators. use Exporter; use base qw/Exporter/; push @EXPORT, qw/ $BLUE $BLUE_DARKER $GRAY $GRAY_LIGHTER $GRAY_LIGHTEST $ORANGE $PI $ROLD $ROLU $SILVER $STACKM $SWAP /; push @EXPORT, qw/$AC $AS $AT $CO $EX $LG $LN $RP $SI $SQ $TA $TX/; push @EXPORT, qw/$AD $AN $DD $DM $DV $IO $ML $SB $XR $YX/; use POSIX; use Tk::widgets qw /Compound/; use base qw/Tk::Frame/; Construct Tk::Widget 'Calculator'; use subs qw/mode/; use strict; use constant E => 2.7182818;# e use constant PI => 3.1415926;# pi use constant D2R => PI / 180; # degrees per radian our $ROOT = 'Calculator/RPN/images'; # Exported global constants and mathematical functions. our $BLUE; # color our $BLUE_DARKER; # color our $GRAY; # color our $GRAY_LIGHTER; # color our $GRAY_LIGHTEST; # color our $ORANGE; # color our $PI; # image of pi our $ROLD; # roll stack up image our $ROLU; # roll stack down image our $SILVER; # color our $STACKM; # maximum stack length our $SWAP; # swap X/Y image our $AC; # arccosine our $AS; # arcsine our $AT; # arctangent our $CO; # cosine our $EX; # e**x our $LG; # log10 our $LN; # natural logarithm our $RP; # reciprocal our $SI; # sine our $SQ; # square root our $TA; # tangent our $TX; # 10**x our $AD; # add our $AN; # AND our $DD; # double divide our $DM; # double multiply our $DV; # divide our $IO; # inclusive OR our $ML; # multiply our $SB; # subtract our $XR; # exclusive OR our $YX; # exponentiation sub ClassInit { my ($class, $mw) = @_; # Exported global constants. $BLUE = 'steelblue1'; $BLUE_DARKER = 'steelblue'; $GRAY = 'gray25'; $GRAY_LIGHTER = 'gray31'; $GRAY_LIGHTEST = 'gray40'; $ORANGE = 'orange'; $SILVER = '#ef5bef5bef5b'; $STACKM = 4 - 1; # Exported mathematical functions expecting one operand, # X from the RPN stack. $AC = sub {acos mode(@_)}; # arccosine $AS = sub {asin mode(@_)}; # arcsine $AT = sub {atan mode(@_)}; # arctangent $CO = sub {sin mode(@_)}; # cosine $EX = sub {E ** $_[0]}; # e**x $LG = sub {log10 $_[0]}; # log10 $LN = sub {log $_[0]}; # natural logarithm $RP = sub {1 / $_[0]}; # reciprocal $SI = sub {sin mode(@_)}; # sine $SQ = sub {sqrt $_[0]}; # square root $TA = sub {tan mode(@_)}; # tangent $TX = sub {10 ** $_[0]}; # 10**x # Exported mathematical functions expecting two operands, # X and Y from the RPN stack. $AD = sub {$_[1] + $_[0]}; # addition $AN = sub {$_[1] & $_[0]}; # AND $DD = sub {$_[1] / $_[0]}; # double divide $DM = sub {$_[1] * $_[0]}; # double multiply $DV = sub {$_[1] / $_[0]}; # division $IO = sub {$_[1] | $_[0]}; # inclusive OR $ML = sub {$_[1] * $_[0]}; # multiplication $SB = sub {$_[1] - $_[0]}; # subtraction $XR = sub {$_[1] ^ $_[0]}; # exclusive OR $YX = sub {$_[1] ** $_[0]}; # exponentiation # Exported images. my (@cargs) = (-foreground => $BLUE, -background => $GRAY_LIGHTER); $PI = $mw->Bitmap(-file => Tk->findINC("$ROOT/pi.xbm"), @cargs); $ROLU = $mw->Compound; $ROLU->Text(-text => 'R', -foreground => $BLUE); $ROLU->Image(-image => $mw->Bitmap(-file => Tk->findINC("$ROOT/rolu.xbm"), @cargs)); @cargs = (-foreground => 'white', -background => $GRAY); $ROLD = $mw->Compound; $ROLD->Text(-text => 'R', -foreground => 'white'); $ROLD->Image(-image => $mw->Bitmap(-file => Tk->findINC("$ROOT/rold.xbm"), @cargs)); $SWAP = $mw->Compound; $SWAP->Text(-text => 'X', -foreground => 'white'); $SWAP->Image(-image => $mw->Bitmap(-file => Tk->findINC("$ROOT/swap.xbm"), @cargs)); $SWAP->Text(-text => 'Y', -foreground => 'white'); $class->SUPER::ClassInit($mw); } # end ClassInit sub Populate { # Populate() for this base class is rather tricky because it re-blesses # the incoming object (whether this is good style is another question). # It does this so that method lookups are dispatched to the new class # based on the type of calculator. This also means that superclass # lookups are found here, the HP RPN base class. # # This presents one problem - ClassInit() is never called for the new # subclass. We handle this situation just like a C widget, and invoke # InitClass on the re-blessed class, which eventually calls ClassInit. my ($self, $args) = @_; $self->SUPER::Populate($args); $self->{TYPE} = delete $args->{-type}; die "HP calculator type not specified." unless $self->{TYPE}; mkdir $^O eq "MSWin32" ? 'C:\.hp' : "$ENV{HOME}/.hp", 0755; my $calc = 'Tk::Calculator::RPN::HP_' . uc($self->{TYPE}); eval "require $calc"; die "Tk::Calculator::RPN::HP::HP.pm, error loading '$calc': $@" if $@; bless $self, $calc; $calc->InitClass($self->MainWindow); # called once/class/MainWindow # Instance pre-initialization. $self->{CLRX} = 0; # 1 IFF clear X before inserting next key $self->{F_PRESSED} = 0; # F-key modifier pressed $self->{G_PRESSED} = 0; # G-key modifier pressed $self->{ONOFF} = 0; # 1 IFF calculator on $self->{PUSHX} = 0; # 1 IFF push X before inserting next key $self->{MODE} = 0; # 0 = degress, 1 = radians $self->{PB} = delete $args->{-progressbar}; # possible ProgressBar $self->{XV} = ' '; # current display (X) value $self->clr; # clear entire stack # Build the calculator. $self->{PB_PERCENT} = 0; $self->Populate($args); $self->{PB}->set($self->{PB_PERCENT} = 100) if $self->{PB}; } # end Populate sub build_help_button { # Create the ? Button common to all calculator types. my ($self, $parent, $help) = @_; my $quest = $parent->Button( -text => '?', -font => '6x9', -relief => 'flat', -highlightthickness => 0, -background => $BLUE, -borderwidth => 0, -pady => 0, -command => sub {$help->deiconify}, ); $quest->bind('<2>' => sub { my (@register) = ('(X)', '(Y)', '(Z)', '(T)'); print "\n"; for (my $i = $STACKM; $i >= 0; $i--) { print "stack+$i $register[$i] : '", $self->{STACK}[$i], "'\n"; } }); $self->{PB}->set($self->{PB_PERCENT} += 10) if $self->{PB}; return $quest; } # end build_help_button sub build_help_window { # Called by ClassInit() to build a help window shared by all subclass instances. my ($class, $mw) = @_; my $help = $mw->Toplevel; $help->withdraw; $help->title('HP 21 Help'); $help->protocol('WM_DELETE_WINDOW' => sub {}); my $frame = $help->Frame->pack; my ($type) = $class =~ /.*::(.*)/; $frame->Label( -image => $mw->Photo(-file => Tk->findINC("$ROOT/" . lc($type) . '-back.gif')), )->pack; $frame->Label( -text => '? displays this window', -relief => 'ridge', )->pack(qw/-expand 1 -fill both/); $frame->Label( -text => '? prints the stack', -relief => 'ridge', )->pack(qw/-expand 1 -fill both/); $frame->Button( -text => 'Close', -command => sub {$help->withdraw}, )->pack(qw/-expand 1 -fill both/); return $help; } # end build_help_window sub mode { # Convert an argument from degees to radians, if required. my ($f, $mode) = @_; return $f if $mode; # already in radians return $f * D2R; # if degrees } # end mode sub set_keypad_bindings { # Now establish key bindings for the digits and common arithmetic # operations, including keypad keys. my ($self) = @_; my $mw = $self->MainWindow; foreach my $key ( qw/0 1 2 3 4 5 6 7 8 9/ ) { $mw->bind( "" => [$self => 'key', $key] ); $mw->bind( "" => [$self => 'key', $key] ); } foreach my $key ( qw/period KP_Decimal/ ) { $mw->bind( "<$key>" => [$self => 'key', '.'] ); } foreach my $key ( qw/Return KP_Enter/ ) { $mw->bind( "<$key>" => [$self => 'enter'] ); } foreach my $key ( qw/plus KP_Add/ ) { $mw->bind( "<$key>" => [$self => 'math3', $AD, undef, undef] ); } foreach my $key ( qw/minus KP_Subtract/ ) { $mw->bind( "<$key>" => [$self => 'math3', $SB, undef, undef] ); } foreach my $key ( qw/asterisk KP_Multiply/ ) { $mw->bind( "<$key>" => [$self => 'math3', $ML, undef, undef] ); } foreach my $key ( qw/slash KP_Divide/ ) { $mw->bind( "<$key>" => [$self => 'math3', $DV, undef, undef] ); } } # end set_keypad_bindings # Function key processors common to all classes. sub chs { # change sign my ($self) = @_; my $s = substr($self->{STACK}[0], 0, 1); substr($self->{STACK}[0], 0, 1) = ($s eq '-') ? ' ' : '-'; $self->end; } # end chs sub clr { # clear stack my ($self) = @_; $self->{STACK}[$_] = ' 0.00' foreach (0 .. $STACKM); $self->end; } # end clr sub clx { # clear x my ($self) = @_; $self->{STACK}[0] = 0; $self->{CLRX} = 1; $self->{PUSHX} = 0; $self->end; } # end clx sub end { # key and display cleanup my ($self) = @_; $self->{F_PRESSED} = $self->{G_PRESSED} = 0; $self->{XV} = $self->{STACK}[0]; } # end end sub enter { # enter key my ($self) = @_; unshift @{$self->{STACK}}, $self->{STACK}[0]; $#{$self->{STACK}} = $STACKM if $#{$self->{STACK}} > $STACKM; $self->{CLRX} = 1; $self->{PUSHX} = 0; $self->end; } # end enter sub err { # error my ($self) = @_; $self->bell if $self->{ONOFF}; } # end err sub f { # F key my ($self) = @_; $self->{F_PRESSED} = 1; } # end f sub g { # G key my ($self) = @_; $self->{G_PRESSED} = 1; } # end g sub hpshift { # empty HP stack my ($self) = @_; $#{$self->{STACK}} = $STACKM if $#{$self->{STACK}} > $STACKM; my $v = shift @{$self->{STACK}}; $self->{STACK}[$STACKM] = $self->{STACK}[$STACKM - 1] if $#{$self->{STACK}} == ($STACKM - 1); $self->end; return $v; } # end hpshift sub key { # process generic key clicks my ($self) = @_; shift if ref $_[0]; # toss bind() object my $key = $_[0]; return unless $self->{ONOFF}; if ($self->{F_PRESSED} or $self->{G_PRESSED}) { $self->bell; $self->end; return; } $self->enter if $self->{PUSHX}; $self->{STACK}[0] = ' ' if $self->{CLRX}; $self->{STACK}[0] .= $key; $self->{CLRX} = $self->{PUSHX} = 0; $self->end; } # end key sub math { # non-G key arithmetic operations # math() expects one code reference to an anonymous subroutine, which # expects one argument, X from the RPN stack. my $self = shift; $self->{STACK}[0] = &{$_[0]}($self->{STACK}[0]); $self->{STACK}[0] = ' ' . $self->{STACK}[0] if substr($self->{STACK}[0], 0, 1) ne '-'; $self->{CLRX} = $self->{PUSHX} = 1; $self->end; } # end math sub math3 { # tri-arithmetic keys # math3() expects three code references to anonymous subroutines, each # of which expects two arguments, X and Y from the RPN stack. # # $_[0] = normal button press # $_[1] = "f" qualified button press # $_[2] = "g" qualified button press my ($self) = @_; shift if ref $_[0]; # toss bind() object my $math = $_[0]; $math = $_[1] if $self->{F_PRESSED}; $math = $_[2] if $self->{G_PRESSED}; if (not defined $math) { $self->bell; $self->end; return; } my $x = $self->hpshift; my $y = $self->{STACK}[0]; $self->{STACK}[0] = &{$math}($x, $y); $self->{STACK}[0] = ' ' . $self->{STACK}[0] if substr($self->{STACK}[0], 0, 1) ne '-'; $self->{CLRX} = $self->{PUSHX} = 1; $self->end; } # end math3 sub on { # power on/off my ($self, $val) = @_; my $rc = $self->{RCFILE}; if ($self->{ONOFF}) { $self->{ONOFF} = 0; if (open(RC, ">$rc") or die"open write failed for '$rc': $!") { foreach (reverse @{$self->{STACK}}) { print RC "$_\n"; } close RC; } $self->end; $self->{XV} = ''; } else { $self->{ONOFF} = 1; if (open(RC, $rc)) { $self->{STACK} = [] if -s $rc; while ($_ = ) { chomp; unshift @{$self->{STACK}}, $_; } close RC; } $self->{CLRX} = $self->{PUSHX} = 1; $self->end; } } # end on sub pi { # return pi my ($self) = @_; $self->enter; $self->{STACK}[0] = PI; $self->end; } # end pi sub roll_down { # roll stack down my ($self) = @_; return unless $self->{ONOFF}; push @{$self->{STACK}}, shift @{$self->{STACK}}; $self->end; } # end roll_down sub roll_up { # roll stack up my ($self) = @_; return unless $self->{ONOFF}; unshift @{$self->{STACK}}, pop @{$self->{STACK}}; $self->end; } # end roll_up sub swapxy { # swap x and y my ($self) = @_; return unless $self->{ONOFF}; if ($self->{F_PRESSED} or $self->{G_PRESSED}) { $self->bell; $self->end; return; } (@{$self->{STACK}}[0, 1]) = (@{$self->{STACK}}[1, 0]); $self->end; } # end swapxy sub trig_math { # with degree to radian conversion # trig_math() expects one code reference to an anonymous subroutine, which # expects one argument, X from the RPN stack. Convert degrees to radians # as appropriate. my $self = shift; $self->{STACK}[0] = &{$_[0]}($self->{STACK}[0], $self->{MODE}); $self->{STACK}[0] = ' ' . $self->{STACK}[0] if substr($self->{STACK}[0], 0, 1) ne '-'; $self->{CLRX} = $self->{PUSHX} = 1; $self->end; } # end trig_math package Tk::Calculator::RPN::HP::Key3_16C; # Composite mega-widget Key3 - 3 operators per key. use Tk::widgets qw/Frame/; use base qw/Tk::Frame/; Construct Tk::Calculator::RPN::HP 'Key3_16C'; sub Populate { my ($self, $args) = @_; my $topl = delete $args->{-topl}; my $butl = delete $args->{-butl}; my $botl = delete $args->{-botl}; $self->SUPER::Populate($args); my (@pl) = qw/-side top -expand yes -fill both/; $self->{topl} = $self->Label(-text => $topl)->pack(@pl); $self->{topl}->configure( -image => $topl) if ref($topl); $self->{butl} = $self->Button( -text => $butl, -borderwidth => 2, )->pack(@pl); $self->{butl}->configure( -image => $butl) if ref($butl); $self->{botl} = $self->Label(-text => $botl)->pack(@pl); $self->{botl}->configure( -image => $botl) if ref($botl); $self->pack(qw/-side left -expand 1 -fill both -padx 3 -pady 3/); $self->ConfigSpecs( -background => [qw/METHOD background background yellow/], -command => [$self->{butl}, qw/command Command/, undef], -foreground => [qw/METHOD foreground Foreground red/], -font => [qw/METHOD font Font fixed/], -width => [qw/METHOD width Width 20/], -height => [$self->{butl}, qw/height Height 0/], ); } # end Populate sub background { my ($self, $bg) = @_; $self->{topl}->configure(-background => $GRAY_LIGHTER); $self->{butl}->configure(-background => $bg); $self->{botl}->configure(-background => $GRAY_LIGHTER); } sub font { my ($self) = @_; $self->{topl}->configure(-font => [qw/arial 9 bold/]); $self->{butl}->configure(-font => [qw/arial 10 bold/]); $self->{botl}->configure(-font => [qw/arial 9 bold/]); } sub foreground { my ($self) = @_; $self->{topl}->configure(-foreground => $ORANGE); my $text = $self->{butl}->cget(-text); my $fg = ($text =~ /^[fg]{1}$/) ? 'black' : 'white'; $self->{butl}->configure(-foreground => $fg); $self->{botl}->configure(-foreground => $BLUE); } sub width { my ($self) = @_; $self->{topl}->configure(-width => 6); $self->{butl}->configure(-width => 3); $self->{botl}->configure(-width => 4); } package Tk::Calculator::RPN::HP::Key2_21; # Composite mega-widget Key2 - 2 operators per key. use Tk::widgets qw/Frame/; use base qw/Tk::Frame/; Construct Tk::Calculator::RPN::HP 'Key2_21'; sub Populate { my ($self, $args) = @_; my $butl = delete $args->{-butl}; my $botl = delete $args->{-botl}; $self->SUPER::Populate($args); my (@pl) = qw/-side top -expand yes -fill both/; $self->{butl} = $self->Button( -text => $butl, -borderwidth => 2, )->pack(@pl); $self->{butl}->configure( -image => $butl) if ref($butl); $self->{botl} = $self->Label(-text => $botl)->pack(@pl); $self->{botl}->configure( -image => $botl) if ref($botl); $self->pack(qw/-side left -expand 1 -fill both -padx 3 -pady 3/); $self->ConfigSpecs( -background => [qw/METHOD background background yellow/], -command => [$self->{butl}, qw/command Command/, undef], -foreground => [qw/METHOD foreground Foreground red/], -font => [qw/METHOD font Font fixed/], -width => [qw/METHOD width Width 20/], -height => [$self->{butl}, qw/height Height 0/], ); } # end Populate sub background { my ($self, $bg) = @_; my $text = $self->{butl}->cget(-text); $bg = $BLUE if $text eq 'BLUE'; $self->{butl}->configure(-background => $bg); $self->{botl}->configure(-background => $GRAY_LIGHTER); } sub font { my ($self) = @_; $self->{butl}->configure(-font => [qw/arial 10 bold/]); $self->{botl}->configure(-font => [qw/arial 9 bold/]); } sub foreground { my ($self) = @_; my $text = $self->{butl}->cget(-text); my $fg = ($text =~ /^[\.\-\+\/x0123456789]$/ or $text eq 'DSP') ? 'black' : 'white'; $fg = $BLUE if $text eq 'BLUE'; $self->{butl}->configure(-foreground => $fg); $self->{botl}->configure(-foreground => $BLUE); } sub width { my ($self) = @_; $self->{butl}->configure(-width => 3); $self->{botl}->configure(-width => 4); } 1; __END__ =head1 NAME Tk::Calculator::RPN::HP - Hewlett-Packard RPN calculators =head1 SYNOPSIS use Tk::Calculator::RPN::HP; $mw->Calculator( -type => '21' | '16c' )->pack; =head1 DESCRIPTION Tk::Calculator::RPN::HP is the OO base class and Exporter module for Perl/Tk Hewlett-Packard Reverse Polish Notation (RPN) calculators. As a base class it provides methods common to all calculators; for instance, stack manipulation, function evaluation, and instance creation activities. As an exporter of data, it provides global variables and function subroutine definitions. Tk::Calculator::RPN::HP provides a single constructor, B, as shown in the B section. Tk::Calculator::RPN::HP provides a B method implicity used by all calculator subclasses. The only option that B requires is I<-type>, the type of calculator. Given I<-type>, B loads the appropriate module, performs common instance pre-initialization, and then calls out to the subclass' B method to create the actual calculator. Subclasses of Tk::Calculator::RPN::HP have this basic structure: $Tk::Calculator::RPN::HP_21::VERSION = '1.2'; package Tk::Calculator::RPN::HP_21; use Tk::widgets qw/SlideSwitch/; use Tk::Calculator::RPN::HP; use base qw/Tk::Calculator::RPN::HP/; use strict; our $HELP; sub ClassInit { my ($class, $mw) = @_; $HELP = $class->build_help_window($mw); $class->SUPER::ClassInit($mw); } # end ClassInit sub Populate { my ($self, $args) = @_; ... $self->build_help_button($frame1, $HELP)->pack(qw/-side left/); ... } # end Populate # Function key processors. sub clxclr { my ($self) = @_; return unless $self->{ONOFF}; if ($self->{G_PRESSED}) { # clr $self->clr; } else { # clx $self->clx; } } # end clxclr 1; As you can see, the module is simply a standard Perl/Tk mega-widget. You are required to invoke two methods, B and B. B creates a Toplevel that's exposed when the ? Button is pressed. B builds the ? Button proper. Because the Toplevel help window is used by all class instances, it's typically created in B. You call B when and where you want the ? packed. Although most calculator functions are provided by the base class, you may find it necessary to write your own function key processors. =head1 OPTIONS The following option/value pairs are supported: =over 4 =item B<-type> The type of HP RPN calculator. Currently I<21> and I<16c> are legal values. There is no default, this option is required. =item B<-progressbar> An optional reference to a Tk::ProgressBar::Mac widget. If specified, you are to update it periodically as the calculator takes shape. =back =head1 METHODS =head2 $HELP = $class->build_help_window($mw); Build a standard calculator help window and return a reference to the Toplevel. You must provide an image I<"images/hp_"> B I B I<"-back.gif"> (e.g. images/hp_21-back.gif) of the back of the calculator, since there might be useful data. B is a good place to do this. =head2 $self->build_help_button($parent, $HELP); Build the ? Button that displays the Toplevel window created by B. I<$parent> is the Button's parent widget. =head1 ADVERTISED WIDGETS Component subwidgets can be accessed via the B method. This mega widget has no advertised subwidgets. =head1 EXAMPLE This complete example incorprates a splashscreen with a progressbar. use Tk; use Tk::Calculator::RPN::HP; use Tk::ProgressBar::Mac; use Tk::Splashscreen; use subs qw/main/; use strict; main; sub main { my $type = $OPT{type}; my $mw = MainWindow->new; $mw->withdraw; $mw->title('Hewlett-Packard ' . $type . ' Calculator'); $mw->iconname('HP ' . $type); my $splash = $mw->Splashscreen; $splash->Label( -text => 'Building your HP ' . $type . ' ...', )->pack(qw/-fill both -expand 1/); my $pb = $splash->ProgressBar(-width => 300); $pb->pack(qw/-fill both -expand 1/); $splash->Label( -image => $mw->Photo( -file => Tk->findINC('Calculator/RPN/images/hp_' . $type . '-splash.gif') ), )->pack; $splash->Splash; $mw->Calculator( -type => $type, -progressbar => $pb, )->pack; $splash->Destroy; $mw->deiconify; MainLoop; } # end main =head1 AUTHOR sol0@Lehigh.EDU Copyright (C) 2001 - 2007, Steve Lidie. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 KEYWORDS calculator, HP, RPN =cut