# -*- perl -*- # # $Id: CanvasBalloon.pm,v 1.10 2004/03/28 18:23:38 eserte Exp $ # Author: Slaven Rezic # # Copyright (C) 1998, 2001, 2004 Slaven Rezic. All rights reserved. # This package is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. # # Mail: slaven@rezic.de # WWW: http://user.cs.tu-berlin.de/~eserte/ # package Tk::CanvasBalloon; use Tk qw(Exists); use Tk::Toplevel; use strict; use vars qw($VERSION @ISA $latency $MEMORY_LEAK_WORKAROUND); use constant XDELTA => 7; use constant YDELTA => 7; $VERSION = '0.08'; Construct Tk::Widget 'CanvasBalloon'; @ISA = qw(Tk::Toplevel); $latency = 20; # ms $MEMORY_LEAK_WORKAROUND = 0; sub Populate { my ($w, $args) = @_; $w->SUPER::Populate($args); $w->{'screenwidth'} = $w->screenwidth; $w->{'screenheight'} = $w->screenheight; $w->overrideredirect(1); $w->withdraw; # Only the container frame's background should be black... makes it # look better. $w->configure(-background => "black"); my $a = $w->Frame; my $m = $w->Frame; $a->configure(-bd => 0); my $have_arrow = (exists $args->{'-arrow'} ? delete $args->{'-arrow'} : 1); if ($have_arrow) { my $al = $a->Label(-bd => 0, -relief => "flat", -bitmap => '@' . Tk->findINC("balArrow.xbm")); $al->pack(-side => "left", -padx => 1, -pady => 1, -anchor => "nw"); } $m->configure(-bd => 0); my $ml = $m->Label(-bd => 0, -padx => 0, -pady => 0, -text => $args->{-message}, -wraplength => $w->{screenwidth} - 20, -justify => "left"); $w->Advertise("message" => $ml); $ml->pack(-side => "left", -anchor => "w", -expand => 1, -fill => "both", -padx => 10, -pady => 3); $a->pack(-fill => "both", -side => "left"); $m->pack(-fill => "both", -side => "left"); $w->bind('' => sub { $w->Pos(1); }); $w->bind('' => sub { $w->Deactivate(undef, -from => 'event'); }); # # append to global list of balloons # push(@balloons, $w); $w->{'popped'} = 0; # $w->{"buttonDown"} = 0; # $w->{"menu_index"} = 'none'; $w->ConfigSpecs (#-installcolormap => ["PASSIVE", "installColormap", "InstallColormap", 0], -initwait => ["PASSIVE", "initWait", "InitWait", 350], #-state => ["PASSIVE", "state", "State", "both"], -statusbar => ["PASSIVE", "statusBar", "StatusBar", undef], -postcommand => ["CALLBACK", "postCommand", "PostCommand", undef], #-followmouse => ["PASSIVE", "followMouse", "FollowMouse", 0], -show => ["PASSIVE", "show", "Show", 1], -background => ["DESCENDANTS", "background", "Background", "#C0C080"], -font => [$ml, "font", "Font", "-*-helvetica-medium-r-normal--*-120-*-*-*-*-*-*"], -borderwidth => ["SELF", "borderWidth", "BorderWidth", 1] ); } sub Popup { my($w, $msg, $statusmsg) = @_; $w->Subwidget('message')->configure(-text => $msg); my $sb = $w->cget('-statusbar'); if (defined $statusmsg and defined $sb and Exists($sb)) { $sb->configure(-text => $statusmsg); } $w->Pos; if (defined $w->{'delay'}) { $w->{'delay'}->cancel; undef $w->{'delay'}; } if (!$w->{'popped'}) { $w->{'delay'} = $w->after($w->cget(-initwait), sub { return if !$w->cget(-show); $w->deiconify; $w->raise; $w->{'popped'} = 1; }); } } sub Deactivate { my($w, $immediate, %args) = @_; if (defined $args{-from} and $args{-from} eq 'event') { my $cont = $w->containing($w->pointerxy); if (Tk::Exists($cont) and $cont->toplevel eq $w) { # moving cursor over Balloon itself: don't deactivate return; } } if ($args{-delay}) { $w->{delay} = $w->after($args{-delay}, sub { undef $w->{delay}; $w->Deactivate; }); return; } if (defined $w->{'delay'}) { $w->{'delay'}->cancel; } if ($w->{'popped'}) { if ($immediate) { $w->withdraw; $w->{'popped'} = 0; } else { $w->{'delay'} = $w->after($latency, sub { $w->withdraw; $w->{'popped'} = 0; }); } } my $sb = $w->cget('-statusbar'); if (defined $sb and Exists($sb)) { $sb->configure(-text => ''); } } sub Track { my $w = shift; if ($w->{'popped'}) { if (defined $w->{'delay'}) { $w->{'delay'}->cancel; } $w->{'delay'} = $w->after($latency, sub { $w->Pos; undef $w->{'delay'}; }); } } sub Pos { my $w = shift; my $force = shift; my($x, $y) = $w->pointerxy; if (!$force && defined $w->{'lastx'} && defined $w->{'lasty'}) { return if sqrt(($w->{'lastx'}-$x)*($w->{'lastx'}-$x) + ($w->{'lasty'}-$y)*($w->{'lasty'}-$y)) < 4; } $w->idletasks; my($width, $height) = ($w->reqwidth, $w->reqheight); my $xx = ($x + XDELTA + $width > $w->{'screenwidth'} ? $w->{'screenwidth'} - $width : $x + XDELTA); my $yy = ($y + YDELTA + $height > $w->{'screenheight'} ? $w->{'screenheight'} - $height : $y + YDELTA); $w->geometry("+$xx+$yy"); ($w->{'lastx'}, $w->{'lasty'}) = ($x, $y); } sub attach { my($w, $c, $tag_ref, %args) = @_; my @tags = (ref $tag_ref eq 'ARRAY' ? @$tag_ref : $tag_ref); my $msg = delete $args{-msg}; my $balloonmsg = delete $args{-balloonmsg}; my $statusmsg = delete $args{-statusmsg}; $balloonmsg = $msg if (not defined $balloonmsg); $statusmsg = $msg if (not defined $statusmsg); foreach my $tag (@tags) { my($old_enter, $old_leave, $old_motion); if (!$MEMORY_LEAK_WORKAROUND) { # It seems that there are some SV's left after a croak() # in the Call_Tk function. Maybe a problem with eval? eval { $old_enter = $c->bind($tag, '') }; eval { $old_leave = $c->bind($tag, '') }; eval { $old_motion = $c->bind($tag, '')}; $w->{'oldenter'}{$tag} = $old_enter if (defined $old_enter); $w->{'oldleave'}{$tag} = $old_leave if (defined $old_leave); $w->{'oldmotion'}{$tag} = $old_motion if (defined $old_motion); } $c->bind($tag, '' => sub { $w->Popup($balloonmsg, $statusmsg); if ($old_enter) { $old_enter->Call(@_) } }); $c->bind($tag, '' => sub { $w->Deactivate(undef, -from => 'event', -delay => $latency); if ($old_leave) { $old_leave->Call(@_) } }); $c->bind($tag, '' => sub { $w->Track; if ($old_motion) { $old_motion->Call(@_) } }); } } sub detach { my($w, $c, $tag_ref) = @_; my @tags = (ref $tag_ref eq 'ARRAY' ? @$tag_ref : $tag_ref); foreach my $tag (@tags) { $c->bind($tag, '' => ($w->{'oldenter'}{$tag} ? $w->{'oldenter'}{$tag} : undef)); $c->bind($tag, '' => ($w->{'oldleave'}{$tag} ? $w->{'oldleave'}{$tag} : undef)); $c->bind($tag, '' => ($w->{'oldmotion'}{$tag} ? $w->{'oldmotion'}{$tag} : undef)); $w->_delete_internal($tag); } } sub _delete_internal { my($w, $tag) = @_; delete $w->{'oldenter'}{$tag}; delete $w->{'oldleave'}{$tag}; delete $w->{'oldmotion'}{$tag}; } sub delete_and_detach { my($w, $c, $tag_ref) = @_; my @tags = (ref $tag_ref eq 'ARRAY' ? @$tag_ref : $tag_ref); $w->detach($c, $tag_ref); foreach (@tags) { $c->delete($_); } $w->Deactivate; # XXX check whether deleted tag is active } 1; __END__ =head1 NAME Tk::CanvasBalloon - pop up help balloons over canvas items =head1 SYNOPSIS use Tk::CanvasBalloon; $b = $canvas->Balloon; $item = $canvas->createLine(0,0,100,100); $b->attach($canvas, $item, -msg => 'Message'); =head1 DESCRIPTION =head2 METHODS =over 4 =item BI, IB<)> =item BI, IB<)> =item BI, IB<)> =item BI, IB<)> =item B[I]B<)> =item B =head1 NOTES Tk::CanvasBalloon seems to work better with Tk800.xxx than with Tk40x.xxx, because the tracking of Enter, Motion and Leave events is better. Tk800.0xx and/or perl seem to have some memory leaks. This would appear if a lot of canvas items with balloons attached are created. To prevent this problem, set the variable C<$Tk::CanvasBalloon::MEMORY_LEAK_WORKAROUND> to a true value. As a side-effect, Enter, Leave and Motion events of attached canvas item are not remembered. =head1 BUGS B overwrites item bindings for Any-Enter, Any-Leave and Any-Motion. There is now way to track deleted items from a canvas, so deleting of attached items should be done with B. Tk::CanvasBalloon IS-not-A Tk::Balloon. =back =head1 AUTHOR Slaven Rezic =head1 COPYRIGHT Copyright (c) 1998 Slaven Rezic. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Some code is stolen from Tk::Balloon by Rajappa Iyer . =head1 SEE ALSO Tk::Balloon(3), Tk::Canvas(3). =cut