#!/usr/bin/perl -w # Author: Chao-Kuei Hung # Primary site: http://www.cyut.edu.tw/~ckhung # License: GNU General Public License # Version: 0.4 # Note: This old version is kept along with the newer version because # it does not require extra modules (such as IPC::Run) except Tk. # For the newer, development version, please see ./dynagpt use Tk; use Tk::DialogBox; use strict; use Getopt::Std; my ( %opts, $main, $set_cmd, $prev_set_cmd, $plot_cmd, $scale, $i, $f_type, $gnuplot_pid, $update_needed, ); getopts('d', \%opts); # available options: # -d Debugging mode. When making an excruciatingly slow plot in # debugging mode, dynagpt prints lots of "." showing it # patiently waiting for gnuplot to finish its work at hand # before dynagpt sending it more work to do. $gnuplot_pid = open(GPT_PIPE, "| gnuplot") or die; select GPT_PIPE; $| = 1; select STDOUT; print GPT_PIPE <new(); $main->protocol("WM_DELETE_WINDOW", \&OnQuit); $main->{menubar} = $main->Frame(-relief=>"raised", -bd=>2); $main->{status} = $main->Frame(-relief=>"sunken", -bd=>2); $main->{worksp} = $main->Frame(-relief=>"sunken", -bd=>2); $main->{sc} = $main->Frame(-relief=>"sunken", -bd=>2); $main->{menubar}->pack(-side=>"top", -fill=>"both"); $main->{status}->pack(-side=>"bottom", -fill=>"both"); $main->{worksp}->pack(-side=>"top", -fill=>"both", -expand=>"yes"); $main->{sc}->pack(-side=>"top", -fill=>"both", -expand=>"yes"); $main->{menubar}{quit} = $main->{menubar}->Button( -text=>"Quit", -command=>\&OnQuit); $main->{menubar}{set} = $main->{menubar}->Menubutton(-text=>"Set"); $main->{menubar}{set}{menu} = $main->{menubar}{set}->Menu(-tearoff=>0); # Note: $set_cmd is initialized in the BEGIN block. foreach $i (sort keys %$set_cmd) { my ($t, $j) = $i; if (0) { } elsif ($t =~ /
/ || $t =~ s/%f/.../g) { $main->{menubar}{set}{menu}->add("checkbutton", -label=>$t, -variable=>\$set_cmd->{$i}{showscale}, -command=>sub { ShowScale($i); }, ); $main->{menubar}{set}{menu}->invoke("end") if ($set_cmd->{$i}{init}); } elsif ($t =~ s/%s/.../g) { $main->{menubar}{set}{menu}->add("cascade", -label=>$t); $main->{menubar}{set}{menu}{$t} = $main->{menubar}{set}{menu}->Menu(-tearoff=>0); foreach $j (@{$set_cmd->{$i}{choice}}) { $main->{menubar}{set}{menu}{$t}->add("radiobutton", -label=>$j, -value=>$j, -variable=>\$set_cmd->{$i}{new_val}[0], -command=>sub { SelectRadio($i); }, ); } $main->{menubar}{set}{menu}->entryconfigure("end", -menu=>$main->{menubar}{set}{menu}{$t} ); } elsif ($t !~ /%\w/) { $main->{menubar}{set}{menu}->add("checkbutton", -label=>$t, -variable=>\$set_cmd->{$i}{new_val}[0], -command=>sub { ToggleBool($i); }, ); } else { print STDERR "no match! '$t'\n"; } } $main->{menubar}{set}->configure(-menu=>$main->{menubar}{set}{menu}); $main->{menubar}{help} = $main->{menubar}->Button( -text=>"About", -command=>\&ShowAbout); $main->{menubar}{quit}->pack($main->{menubar}{set}, -side=>"left", -fill=>"both"); $main->{menubar}{help}->pack(-side=>"right", -fill=>"both"); $main->{sc}->gridColumnconfigure(1, -minsize=>40); $main->{sc}->gridColumnconfigure(2, -weight=>1); $main->{worksp}{go} = $main->{worksp}->Button( -text=>"go!", -command=>\&ChangePlotCmd ); $main->{worksp}{equation} = $main->{worksp}->Entry(-width=>"10"); $main->{worksp}{go}->pack(-side=>"right", -fill=>"both"); $main->{worksp}{equation}->pack(-side=>"left", -fill=>"both", -expand=>"yes"); $main->{status}{f_type} = $main->{status}->Label(-textvariable=>\$f_type); $main->{status}{f_type}->pack(-side=>"left", -fill=>"both"); &UpdateGnuplot; $i = "splot a*x*x+b*y*y"; $i .= "; ang=0; load 'revolve.gpt'" if $opts{d}; $main->{worksp}{equation}->insert(0, $i); $main->{worksp}{go}->invoke; foreach $i (@ARGV) { print GPT_PIPE "load '$i'\n"; } MainLoop(); #================================================================= sub OnQuit { close GPT_PIPE; exit; } sub ShowAbout { my ($t) = <messageBox(-title=>"About dynagpt", -type=>"OK", -message=>$t ); } sub ToggleBool { # my ($name) = @_; # die unless exists $set_cmd->{$name}; # print "Toggled: $name: $set_cmd->{$name}{new_val}[0]\n"; $update_needed = 1; } sub SelectRadio { # my ($name) = @_; # die unless exists $set_cmd->{$name}; # print "Selected: $name: $set_cmd->{$name}{new_val}[0]\n"; $update_needed = 1; } sub ScrollValue { # my ($name, $i, $val) = @_; # die unless exists $set_cmd->{$name}; # print "Scrolled: $name:$i $val\n"; $update_needed = 1; } sub ChangePlotCmd { $plot_cmd = $main->{worksp}{equation}->get; $update_needed = 1; } sub ShowScale { my ($name) = @_; die unless exists $set_cmd->{$name}; my ($t, $x, $n, $i); $x = $set_cmd->{$name}; $t = $name; $n = $t =~ s/%f/.../g; $n = 2 if $name =~ /
/; if ($x->{showscale}) { for ($i=0; $i<$n; ++$i) { my ($loc_i); $loc_i = $i; # $loc_i must be local to the loop, so that the closure will # get a different copy through each iteration of the loop. $main->{sc}{"$name:$i"} = $main->{sc}->Scale( -variable=>\$x->{new_val}[$i], -orient=>"horizontal", -showvalue=>"no", -resolution=>0, -command=>sub { ScrollValue($name, $loc_i, $_[0]); }, @{$x->{opt}[$i]}, ); my ($t) = $i==0 ? ${name} : ($name=~/
/) ? " scale: 2 ^" : ""; $main->{sc}{"$name:$i:L1"} = $main->{sc}->Label(-text=>$t); $main->{sc}{"$name:$i:L2"} = $main->{sc}->Label( -textvariable=>\$x->{new_val}[$i] ); $main->{sc}{"$name:$i:R"} = $main->{sc}->Button( -text=>"r", -command=>sub { ChangeRange($name, $loc_i); } ) if (! $x->{fixed}); $main->{sc}{"$name:$i:L1"}->grid( $main->{sc}{"$name:$i:L2"}, $main->{sc}{"$name:$i"}, ($main->{sc}{"$name:$i:R"} or "-"), -sticky=>"w", ); } } else { for ($i=0; $i<$n; ++$i) { $main->{sc}{"$name:$i"}->destroy(); $main->{sc}{"$name:$i:L1"}->destroy(); $main->{sc}{"$name:$i:L2"}->destroy(); $main->{sc}{"$name:$i:R"}->destroy() if defined($main->{sc}{"$name:$i:R"}); } } } sub ChangeRange { my ($sname, $index) = @_; my ($d, $t0, $t1, $lo, $t2, $hi, $ans); my ($msg) = "Enter scrollbar range for '$sname'"; my ($sc) = $main->{sc}{"$sname:$index"}; $msg .= " (log scale)" if (($index==1) && ($msg =~ s/
/scaling factor of/)); $d = $main->DialogBox(-title=>"Change scrollbar range", -buttons => ["OK", "Cancel"]); $t0 = $d->add("Label", -text=>$msg); $t1 = $d->add("Label", -text=>"from:"); $lo = $d->add("Entry"); $lo->insert(0, $sc->cget(-from)); $t2 = $d->add("Label", -text=>"to:"); $hi = $d->add("Entry"); $hi->insert(0, $sc->cget(-to)); $t0->pack(-side=>"top"); $t1->pack($lo, $t2, $hi, -side=>"left"); $ans = $d->Show; $lo = $lo->get; $hi = $hi->get; $d->destroy; return unless $ans eq "OK"; if (! ($lo =~ m/^\s*-?\d+(\.\d*)?\s*$/)) { $main->messageBox(-text=>"'$lo' does not look like a number"); return; } if (! ($hi =~ m/^\s*-?\d+(\.\d*)?\s*$/)) { $main->messageBox(-text=>"'$hi' does not look like a number"); return; } if ($lo >= $hi) { $main->messageBox(-text=>"'from' should be smaller than 'to'"); return; } $main->{sc}{"$sname:$index"}->configure(-from=>$lo, -to=>$hi); } sub UpdateGnuplot { # This subprogram repeatedly gets executed every so often, # but it sends commands to gnuplot only if it there are # changes in the set_cmdeters and gnuplot is not busy. my ($name, $i, $cmd, $t, $any_change_for_this_item, $snapshot); $main->after(100, \&UpdateGnuplot); return unless $update_needed; $cmd = ""; foreach $name (keys %$set_cmd) { next unless defined $set_cmd->{$name}{new_val}[0]; $any_change_for_this_item = 0; @{$snapshot->{$name}} = @{$set_cmd->{$name}{new_val}}; for ($i=0; $i<=$#{$set_cmd->{$name}{new_val}}; ++$i) { next if (defined($prev_set_cmd->{$name}[$i]) and $snapshot->{$name}[$i] eq $prev_set_cmd->{$name}[$i]); $any_change_for_this_item = 1; } next unless $any_change_for_this_item; if ($name =~ /
*(\w+)/) { my ($ofs, $sc) = @{$snapshot->{$name}}; $ofs = 0 unless $ofs; $sc = 0 unless $sc; $sc = exp(log(2) * $sc); $t = sprintf "${1}range [%f:%f]", $ofs-$sc, $ofs+$sc; } elsif ($name =~ /%\w/) { $t = sprintf $name, @{$snapshot->{$name}}; $t =~ s/(\w+)\s+NO/no$1/; } else { # bool $t = $name; $t =~ s/^ */no/ unless ($snapshot->{$name}[0]); # print "set $t\n"; } if ($t =~ s/ *//) { $cmd .= "$t\n"; } else { $cmd .= "set $t\n"; } } # autrijus@autrijus.org provided hints for porting across variants # of freenix $t = `ps --no-headers -o stat,comm $gnuplot_pid`; die "<$t>\nYou found a portability bug." unless ($t =~ m/^(\S+)\s+gnuplot/); if ($1 eq 'S') { print "\n" if $opts{d}; } else { print "." if $opts{d}; return; } # gnuplot doesn't look too busy. OK, send work for it to do. # Without this check, continuous dragging of any scale would # keep gnuplot's response lagging far behind user control. foreach $name (keys %$snapshot) { @{$prev_set_cmd->{$name}} = @{$snapshot->{$name}}; } $update_needed = 0; print GPT_PIPE "\n${cmd}$plot_cmd\n"; # The assumption is that no other event handlers should # intervene between the above few statements. } BEGIN { $set_cmd = { "style data %s" => { new_val=>["linespoints"], choice=>[qw(lines points linespoints)] }, "contour %s" => { new_val=>["base"], choice=>[qw(NO base surface both)] }, "isosamples %f" => { opt=>[ [ -from=>2, -to=>50 ], ], }, " a=%f" => { init=>"on", new_val=>[1], opt=>[ [ -from=>-20, -to=>20 ], ], }, " b=%f" => { new_val=>[-1], init=>"on", opt=>[ [ -from=>-20, -to=>20 ], ], }, " c=%f" => { opt=>[ [ -from=>-20, -to=>20 ], ], }, "view %f,%f" => { fixed=>1, init=>"on", new_val=>[60, 30], opt=>[ [ -from=>0, -to=>180 ], [ -from=>0, -to=>360 ] ], }, "
x" => { init=>"on", opt=>[ [ -from=>-5, -to=>5 ], [ -from=>-5, -to=>5 ] ], }, "
y" => { init=>"on", opt=>[ [ -from=>-5, -to=>5 ], [ -from=>-5, -to=>5 ] ], }, "
z" => { init=>"on", opt=>[ [ -from=>-5, -to=>5 ], [ -from=>-5, -to=>5 ] ], }, "autoscale x" => { # new_val=>[1], }, "autoscale y" => { # new_val=>[1], }, "autoscale z" => { # new_val=>[1], }, "parametric" => { # new_val=>[1], }, }; # $set_cmd = { ... }