# -*-Perl-*- use Tk; require "$libdir/xpm"; # single bar graph pseudo-widget, built out of Tk canvases. package MGM::Bar; use vars qw($font); $font=MGM::Font::new(MGM::Xpm::read($main::fontfile)); sub drawborder{ my($w,$width,$height,$relief,$dim,$lit)=@_; $z=0; if($relief eq 'sunken' || $relief eq 'groove'){ $w->createLine($width-1,0, 0,0, 0,$height-1, -capstyle=>'projecting', -fill=>$dim); $w->createLine($width-1,0, $width-1,$height-1, 0,$height-1, -capstyle=>'projecting',-fill=>$lit); if($relief eq 'groove'){ $z=1; $width-=1; $height-=1; $relief='raised'; } } if($relief eq 'raised'){ $w->createLine($width-1,$z, $width-1,$height-1, $z,$height-1, -capstyle=>'projecting',-fill=>$dim); $w->createLine($width-1,$z, $z,$z, $z,$height-1, -capstyle=>'projecting',-fill=>$lit); } } sub place{ my$this=shift; $this->{"below"}->place(@_); $this; } sub new{ my ($ref,$p,$width,$height,$num)=@_; $orient=$main::orient; # we're not as flat as we like to think my$this=bless {}; my$below=$this->{"below"}=$p->Canvas(Name=>$num); my$above=$this->{"above"}=$below->Canvas()->place(-relx=>0,-rely=>0, -anchor=>'nw', -bordermode=>'outside'); # we configure the color and label via a configurable X resource; # it's cleaner than setting up a system that alters a single X resource $this->{"aforeXr"}='litforeground'; $this->{"abackXr"}='litbackground'; $this->{"labelXr"}='label'; $this->{"width"}=$width; $this->{"height"}=$height; $this->{"num"}=$num; $this->{"value"}=0; $this->drawit; $this; } sub configure{ my$this=shift; my ($arg,$val); while(defined($arg=shift)){ $val=shift; if(!defined($val)){last;} $this->{"$arg"}=$val; } $this->{'below'}->delete('xpmlabel'); $this->{'above'}->delete('xpmlabel'); $this->drawit; $this->set($this->{'value'}); } sub drawit{ my$this=shift; my$below=$this->{'below'}; my$above=$this->{'above'}; $orient=$main::orient; my$width=$this->{"width"}; my$height=$this->{"height"}; my$num=$this->{"num"}; my $pad=$below->optionGet("textpad",""); my $relief=$below->optionGet('dimrelief',''); my $arelief=$below->optionGet('litrelief',''); my $afore=$below->optionGet($this->{"aforeXr"},''); my $aback=$below->optionGet($this->{"abackXr"},''); my $label=$below->optionGet($this->{"labelXr"},''); my $fore=$below->optionGet('dimforeground',''); my $back=$below->optionGet('dimbackground',''); my $ratio=$below->optionGet('ratio',''); my$border=0; # $border=2 if ($relief eq 'groove'); # $border=1 if ($relief eq 'sunken'); # $border=1 if ($relief eq 'raised'); my$aborder=0; $aborder=2 if ($arelief eq 'groove'); $aborder=1 if ($arelief eq 'sunken'); $aborder=1 if ($arelief eq 'raised'); my($r,$g,$b)=$below->rgb($back); $r/=256; $g/=256; $b/=256; my$lum=$r*.3+$g*.5+$b*.2; my$dimdim=sprintf "#%02x%02x%02x", $r*.5,$g*.5,$b*.5; my$dimlit=sprintf "#%02x%02x%02x", &main::min($r+$lum*.7,255),&main::min($g+$lum*.7,255), &main::min($b+$lum*.7,255); if($orient eq 'vertical'){ my$text=$font->maketext($label,$width-($pad*2),$height-($pad*2), $ratio,1,1); my$unlittext=$below->Pixmap($below->PathName.".unlittext", -data=>$text->write($fore,$back,32)); my$littext=$below->Pixmap($below->PathName.".littext", -data=>$text->write($afore,$aback,32)); $this->{"barlength"}=$height; $this->{"litrelief"}=$arelief; $this->{"cfborder"}=$aborder; $below->configure(-borderwidth=>$aborder, -background=>$aback, -highlightthickness=>0, -width=>($width-$aborder*2), -height=>($height-$aborder*2), -relief=>$arelief); $above->configure(-borderwidth=>0, -background=>$back, -highlightthickness=>0, -width=>$width, -height=>$height, -relief=>'flat'); $this->{"reliefhack"}->destroy if(defined($this->{"reliefhack"})); if($aborder){ $this->{"reliefhack"}=$above-> Label(-borderwidth=>$aborder, -background=>$aback, -highlightthickness=>0, -width=>($width-$aborder*2), -height=>4, -relief=>$arelief)-> place('-x'=>0,-rely=>1.0,'-y'=>-$aborder, -anchor=>'nw',-bordermode=>'outside'); } $below->createImage($pad,$height-$pad,-image=>$littext,-anchor=>'sw', -tags=>['xpmlabel']); $above->createImage($pad,$height-$pad,-image=>$unlittext, -anchor=>'sw',-tags=>['xpmlabel']); &drawborder($above,$width,$height,$relief,$dimdim,$dimlit); }else{ my$text=$font->maketext($label,$height-($pad*2),$width-($pad*2), $ratio,0,1); my$unlittext=$below->Pixmap($below->PathName.".unlittext", -data=>$text->write($fore,$back,32)); my$littext=$below->Pixmap($below->PathName.".littext", -data=>$text->write($afore,$aback,32)); $this->{"barlength"}=$width; $this->{"cfborder"}=$aborder; $below->configure(-borderwidth=>0, -background=>$back, -highlightthickness=>0, -width=>$width, -height=>$height-$border*2, -relief=>'flat'); $above->configure(-borderwidth=>$aborder, -background=>$aback, -highlightthickness=>0, -width=>($width-2*$aborder), -height=>($height-2*$aborder), -relief=>$arelief); $above->createImage($pad,$height-$pad,-image=>$littext,-anchor=>'sw', -tags=>['xpmlabel']); $below->createImage($pad,$height-$pad,-image=>$unlittext, -anchor=>'sw',-tags=>['xpmlabel']); &drawborder($below,$width,$height,$relief,$dimdim,$dimlit); } $this; } sub set{ my$bar=shift; my$per=shift; $bar->{'value'}=$per; my $orient=$main::orient; if($orient eq 'vertical'){ $bar->{'above'}-> configure('-height'=>($bar->{"barlength"}- $per+$bar->{"cfborder"})); }else{ # handle another one-off bug in Tk my $border=$bar->{"cfborder"}; if($per-2*$border > 0){ $bar->{'above'}-> place('-x'=>0); $bar->{'above'}-> configure('-width'=>($per-2*$border)); }else{ $bar->{'above'}-> configure('-width'=>(0)); $bar->{'above'}-> place('-x'=>-1); } } } package MGM::Graph; use vars qw(%numbers %prompts); # all the labels are the same size/color, but we have to do this after the toplevel is created so we have the size/color resources sub cacheem{ my$size=$main::toplevel->optionGet("labelsize",""); my$fg=$main::foreground; my$bg=$main::background; my$orient=$main::orient; while(defined(my$num=shift)){ if(!defined($numbers{"$num"})){ $numbers{"$num"}=$main::toplevel-> Pixmap(-data=>$MGM::Bar::font-> maketext("$num",$size,undef,1, $orient eq 'vertical'?1:undef)-> write($fg,$bg,32,32)); } } } sub cacheinit{ if(!defined($numbers)){ &cacheem("0","1","2","4","8","16","32","64","128","256","512"); } } sub cacheprompt{ my$prompt=shift; if(!defined($prompts{"$prompt"})){ my$size=$main::toplevel->optionGet("labelsize",""); my$fg=$main::foreground; my$bg=$main::background; my$orient=$main::orient; $prompts{"$prompt"}=$main::toplevel->Pixmap(-data=> $MGM::Bar::font->maketext("$prompt",$size,undef,1, $orient eq 'vertical'?1:undef)-> write($fg,$bg,32,32)); $prompts{"k$prompt"}=$main::toplevel->Pixmap(-data=> $MGM::Bar::font->maketext("k$prompt",$size,undef,1, $orient eq 'vertical'?1:undef)-> write($fg,$bg,32,32)); $prompts{"M$prompt"}=$main::toplevel->Pixmap(-data=> $MGM::Bar::font->maketext("M$prompt",$size,undef,1, $orient eq 'vertical'?1:undef)-> write($fg,$bg,32,32)); $prompts{"G$prompt"}=$main::toplevel->Pixmap(-data=> $MGM::Bar::font->maketext("G$prompt",$size,undef,1, $orient eq 'vertical'?1:undef)-> write($fg,$bg,32,32)); } } sub promptsize{ my($prompt,$setting)=@_; if($main::orient eq 'vertical'){ ($numbers{"$setting"}->width, $numbers{"$setting"}->height+$prompts{"$prompt"}->height); }else{ ($numbers{"$setting"}->height, $numbers{"$setting"}->width+$prompts{"$prompt"}->width); } } sub scalemod{ my$scaleset=shift; my$multiplier=''; if($scaleset>512){ $scaleset/=1024; $multiplier="k"; if($scaleset>512){ $scaleset/=1024; $multiplier="M"; if($scaleset>512){ $scaleset/=1024; $multiplier="G"; } } } $scaleset=int($scaleset+.5); ($scaleset,$multiplier); } sub promptlabel{ my($p,$scaleset,$prompt)=@_; my $orient=$main::orient; my $mult; if($p->{'scalep'}){ ($scaleset,$mult)=&scalemod($scaleset); my$label; my($textw,$textl)=&promptsize("$mult$prompt",$scaleset); if($orient eq 'vertical'){ $label=$p->{"widget"}->Canvas(Name=>'scalerange', -borderwidth=>0, -highlightthickness=>0, -width=>$textw, -height=>$textl, -relief=>'flat')-> place('-x'=>0,'-y'=>2,-anchor=>'nw'); $label->createImage(0,0, -image=>$prompts{"$mult$prompt"},-anchor=>'nw'); $label->createImage(0,$prompts{"$mult$prompt"}->height, -image=>$numbers{"$scaleset"},-anchor=>'nw'); }else{ $label=$p->{"widget"}->Canvas(Name=>'scalerange', -borderwidth=>0, -highlightthickness=>0, -width=>$textl, -height=>$textw, -relief=>'flat')-> place(-relx=>1.0, '-x'=>-2,'-y'=>0, -anchor=>'ne'); $label->createImage($numbers{"$scaleset"}->width,0, -image=>$prompts{"$mult$prompt"},-anchor=>'nw'); $label->createImage(0,0, -image=>$numbers{"$scaleset"},-anchor=>'nw'); } $label; } } sub new{ my$class=shift; my$p=shift; my$toplevel=$p->{"toplevel"}; my$ret=bless {( 'width'=> $p->{"width"}, 'height'=> $p->{"height"}, 'name' => $p->{"name"}, 'rangesetting'=>1, 'rangecurrent'=>1, 'minscale'=>1, 'num' => '1', 'fixed' => '0')}; $ret->{"scaletimer"}=0; $ret->{"scalemax"}=0; $ret->_configure(@_); my$widget=$ret->{"widget"}= $toplevel->Canvas(-class=>$ret->{"name"}, Name=>$p->{"sequence"}); $ret->{"scalep"}=($widget->optionGet("scale","") eq 'true'); $ret->{"scroll"}=($widget->optionGet("scalescroll","") eq 'true'); $ret->{"scalereturn"}=$widget->optionGet("scalereturn",""); $ret->{"scalethresh"}=$widget->optionGet("scalethresh",""); $ret->drawit; $ret->{"meter"}=[(map{-1}(1..$ret->{"num"}))]; $ret; } sub calcsize{ my($this,$scaleset,$prompt,$num)=@_; my$barw=4*$num; my$barl=10; my$scalew=0; my$textl=0; my$textw=0; if (&main::moption($this,"scale") eq 'true'){ my($set,$mult)=&scalemod($scaleset); &cacheinit(); &cacheprompt($prompt); &cacheem($set); ($textw,$textl)=&promptsize("$mult$prompt",$set); $textw+=3; $textl+=4; $scalew=6; } $scalew=&main::max($scalew,$textw); $barl=&main::max($barl,$textl); ($barw+$scalew,$barl,$scalew); } sub calcxysize{ my($w,$l)=&calcsize(@_); if($main::orient eq 'vertical'){ ($w,$l); }else{ ($l,$w); } } sub drawit{ my$this=shift; # a 'graph' consists of a scale, and bars under central control my $num=$this->{"num"}; if($num){ my $name=$this->{"name"}; my $prompt=$this->{"prompt"}; my $fixed=$this->{"fixed"}; my $height=$this->{"height"}; my $width=$this->{"width"}; my $widget=$this->{"widget"}; $widget->configure(-borderwidth=>0, -highlightthickness=>0, -width=>$width, -height=>$height, -relief=>'flat'); my $rangesetting=$this->{"rangesetting"}; my($dummy,$dummy2,$scalew)=&calcsize($this,$rangesetting,$prompt,$num); $this->{"scalew"}=$scalew; $this->{"numlabel"}=$this->promptlabel($rangesetting,$prompt); my $orient=$main::orient; if($orient eq 'vertical'){ $this->drawvscale if($scalew); $this->{"barlength"}=$height; my$barwidth=$width-$scalew; my$frame=$this->{"barframe"}= $widget->Frame(Name=>'bar', -width=>$barwidth, -height=>$height, -borderwidth=>0)-> place('-x'=>$scalew, '-y'=>0, '-anchor'=>'nw'); $barwidth/=$num; $barwidth=int($barwidth); my$x=0; for(my$i=0;$i<$num;$i++){ $this->{$i}=MGM::Bar->new($frame,$barwidth-1, $height, $i)->place('-x'=>$x,'-y'=>0, '-anchor'=>'nw'); $x+=$barwidth; } }else{ $this->drawhscale if($scalew); $this->{"barlength"}=$width; my$barwidth=$height-$scalew; my$frame=$this->{"barframe"}= $widget->Frame(Name=>'bar', -width=>$width, -height=>$barwidth, -borderwidth=>0)-> place('-y'=>$scalew, '-x'=>0, -anchor=>'nw'); $barwidth/=$num; $barwidth=int($barwidth); my$y=0; for(my$i=0;$i<$num;$i++){ $this->{$i}=MGM::Bar->new($frame,$width, $barwidth-1, $i)->place('-x'=>0,'-y'=>$y, -anchor=>'nw'); $y+=$barwidth; } } } } sub place{ my$this=shift; $this->{"widget"}->place(@_); $this; } sub _configure{ my$this=shift; my ($arg,$val); while(defined($arg=shift)){ $val=shift; if(!defined($val)){last;} $this->{"$arg"}=$val; } # enforce power of two dynamic ranging $this->{"rangesetting"}=$this->{"minscale"} if($this->{"minscale"}> $this->{"rangesetting"}); $this->{"rangecurrent"}=$this->{"minscale"} if($this->{"minscale"}> $this->{"rangecurrent"}); if($this->{"fixed"}==0){ my$i=1; while($i<$this->{"rangesetting"}){ $i*=2; } $this->{"rangesetting"}=$i; } $this->{"rangediff"}=$this->{"rangesetting"}- $this->{"rangecurrent"}; } # we can't configure anything that requires size changes. sub configure{ my$this=shift; my%copy=%$this; $this->_configure(@_); if($copy{"rangesetting"} != $this->{"rangesetting"}){ my$val=int($this->{'rangesetting'}); $this->{"numlabel"}->destroy; $this->{"numlabel"}=$this->promptlabel($val,$this->{'prompt'}); $this->{"rangediff"}=$this->{"rangesetting"}- $this->{"rangecurrent"}; $this->{"scaletimer"}=0; # reset the timer $this->{"scalemax"}=0; $this->_animate(); } } # a way to configure individual bars sub barconfigure{ my$this=shift; my$bar=shift; $bar=$this->{$bar}; $bar->configure(@_); } sub drawvscale{ my$this=shift; # length is in barlength # current scale setting in rangecurrent # scale 'goal' in rangesetting (may smooth-scroll) # current max meter setting in metermax # (other meter settings in meter0, meter1, meter2....) # build new scale my$widget=$this->{"widget"}; my$color=$widget->optionGet("scalecolor",""); my$height=$this->{"height"}; my$width=4; my$xo=$this->{"scalew"}-2; $widget->createLine($xo,0,$xo,$height-1,$xo-$width,$height-1, -tags=>['newscale'],-fill=>$color, -capstyle=>'projecting'); # powers of two. tics no closer than every 4 pixels my$max=0.; if($this->{"rangesetting"}){ $max=$height/$this->{"rangecurrent"}*$this->{"rangesetting"}; }else{ if($this->{"rangecurrent"}){ $max=$height/$this->{"rangecurrent"}; } } if($this->{"rangecurrent"}==$this->{"rangesetting"}){ $widget->createLine($xo,0,$xo-$width,0, -tags=>['newscale'],-fill=>$color, -capstyle=>'projecting'); } my$step=1; for(my$div=2;;$div*=2){ my$flag=0; last if($width<1); last if($max/$div<4); for(my$tic=1;;$tic+=$step){ my$y=$height-$max/$div*$tic; if($y>=0){ $widget->createLine($xo,$y,$xo-$width,$y, -tags=>['newscale'],-fill=>$color, -capstyle=>'projecting'); $flag=1; }else{ last; } } $width-- if ($flag); $step=2; } # replace old scale with new scale $widget->delete('scale'); $widget->addtag('scale',withtag=>'newscale'); } sub drawhscale{ my$this=shift; # length is in barlength # current scale setting in rangecurrent # scale 'goal' in rangesetting (may smooth-scroll) # current max meter setting in metermax # (other meter settings in meter0, meter1, meter2....) # build new scale my$widget=$this->{"widget"}; my$color=$widget->optionGet("scalecolor","Scalecolor"); my$yo=$this->{"scalew"}-2; my$width=$this->{"width"}; my$height=4; $widget->createLine(0,$yo-$height,0,$yo,$width-1,$yo, -tags=>['newscale'],-fill=>$color, -capstyle=>'projecting'); # powers of two. tics no closer than every 4 pixels my$max=0.; if($this->{"rangesetting"}){ $max=$width/$this->{"rangecurrent"}*$this->{"rangesetting"}; }else{ if($this->{"rangecurrent"}){ $max=$width/$this->{"rangecurrent"}; } } if($this->{"rangecurrent"}==$this->{"rangesetting"}){ $widget->createLine($width-1,$yo-$height,$width-1,$yo, -tags=>['newscale'],-fill=>$color, -capstyle=>'projecting'); } my$step=1; for(my$div=2;;$div*=2){ my$flag=0; last if($max/$div<4); last if($height<1); for(my$tic=1;;$tic+=$step){ my$x=$max/$div*$tic; if($x<$width){ $widget->createLine($x,$yo-$height,$x,$yo, -tags=>['newscale'],-fill=>$color, -capstyle=>'projecting'); $flag=1; }else{ last; } } $height-- if ($flag); $step=2; } # replace old scale with new scale $widget->delete('scale'); $widget->addtag('scale',withtag=>'newscale'); } sub _newscale{ my$this=shift; my$val=$this->{'rangesetting'}; $this->{"numlabel"}->destroy; $this->{"numlabel"}=$this->promptlabel($val,$this->{'prompt'}); $this->{"rangediff"}=$this->{"rangesetting"}-$this->{"rangecurrent"}; $this->_animate(); } sub _animate{ my $this=shift; $scalep=$this->{"scalep"}; $scrollp=$this->{"scroll"}; if(!$scalep || !$scrollp){ $this->{"rangecurrent"}=$this->{"rangesetting"}; undef $this->{"anitimer"}; }else{ if($this->{"rangesetting"}==0){ if($this->{"rangecurrent"}<.02){ $this->{"rangecurrent"}=$this->{"rangesetting"}; undef $this->{"anitimer"}; }else{ $this->{"rangecurrent"}+=$this->{"rangediff"}*.1; $this->{"anitimer"}->cancel if(defined($this->{"anitimer"})); $this->{"anitimer"}=$this->{"widget"}->after(30,sub{$this->set;}); } }else{ if(abs($this->{"rangecurrent"}/($this->{"rangesetting"})-1)<.02){ $this->{"rangecurrent"}=$this->{"rangesetting"}; undef $this->{"anitimer"}; }else{ $this->{"rangecurrent"}+=$this->{"rangediff"}*.1; $this->{"anitimer"}->cancel if(defined($this->{"anitimer"})); $this->{"anitimer"}=$this->{"widget"}->after(30,sub{$this->set;}); } } } if($main::orient eq 'vertical'){ $this->drawvscale; }else{ $this->drawhscale; } my$length=$this->{"barlength"}; my$num=$this->{"num"}; if($this->{"rangecurrent"}){ for(my$i=0;$i<$num;$i++){ $this->{$i}-> set($this->{"meter"}->[$i]*$length/$this->{"rangecurrent"}); } }else{ for(my$i=0;$i<$num;$i++){ $this->{$i}-> set(0); } } } sub set{ my$this=shift; my$num=$this->{"num"}; if($num){ my$length=$this->{"barlength"}; # length is in barlength # current scale setting in rangecurrent # scale 'goal' in rangesetting (may smooth-scroll) if($#_>=0){ # we have new values incoming. We need to do full maneuvers if($#_!=$num-1){ # might as well. prevents spurious error printing when # something unexpected happens to a module (like a CPU # disappearing ;-) push @_,$#_+1,map{0}(1..$num); #lazy $#_=$num-1; } # find the largest value for scale adjustment my$metermax=0; map{$metermax=$_ if ($metermax<$_)}(@_, @{$this->{"meter"}}, $this->{"scalemax"}); $this->{"scalemax"}=$metermax; my$fixed=$this->{"fixed"}; # raise the scale range? my$redrawflag=0; if($fixed==0 && $metermax>$this->{"rangesetting"}){ splice @{$this->{"meter"}},0,$#_+1,@_; while($metermax>$this->{"rangesetting"}){ if($this->{"rangesetting"} == 0){ $this->{"rangesetting"}=1; }else{ $this->{"rangesetting"}*=2; } $this->{"scaletimer"}=0; # reset the timer $this->{"scalemax"}=$metermax; $this->_newscale(); } }else{ # lower the scale range? my$scaletimer=$this->{"scaletimer"}+1; if($fixed==0 && $scaletimer>=$this->{"scalereturn"} && $this->{"rangesetting"}>$this->{"minscale"} && $metermax*$this->{"scalethresh"}<$this->{"rangesetting"} && $this->{"rangesetting"}==$this->{"rangecurrent"}){ $this->{"rangesetting"}=int($this->{"rangesetting"}/2); $this->{"scaletimer"}=0; $this->{"scalemax"}=0; splice @{$this->{"meter"}},0,$#_+1,@_; $this->_newscale(); }else{ my$meter=$this->{"meter"}; for(my$i=0;defined(my$val=shift);$i++){ if($meter->[$i]!=$val){ $meter->[$i]=$val; $val*=$length/$this->{"rangecurrent"}; $this->{$i}->set($val); } } if($scaletimer>=$this->{"scalereturn"}){ $this->{"scaletimer"}=0; $this->{"scalemax"}=0; }else{ $this->{"scaletimer"}=$scaletimer; $this->{"scalemax"}=$metermax; } } } }else{ # just scale animation. No reconfig $this->_animate(); } } } 1;