The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

our $VERSION = '0.04';
use strict;
=head1 NAME
Tk::DoubleClick - Correctly handle single-click vs double-click events,
=head1 VERSION
Version 0.04
=head1 SYNOPSIS
use Tk::Doubleclick;
bind_clicks(
$widget,
[ \&single_callback, @args ], # Single callback with args
\&double_callback, # Double callback without args
-delay => 500,
-button => 'right',
);
=head1 DESCRIPTION
Tk::DoubleClick module correctly handle single-click vs double-click events,
calling only the appropriate callback for the given event.
This module always exports C<bind_clicks()>.
=head1 FUNCTIONS
=head2 bind_clicks()
Required parameters:
=over 5
=item $widget
Widget to bind to mousebuttons. Typically a Tk::Button object, but could
actually be almost any widget.
=item [ \&single_click_callback, @single_click_args ],
The callback subroutine to invoke when the event is a single-click, along
with the arguments to pass. When no arguments are passed, the brackets
can be omitted.
=item [ \&double_click_callback, @double_click_args ],
The callback subroutine to invoke when the event is a double-click, along
with the arguments to pass. When no arguments are passed, the brackets
can be omitted.
=back
Options:
=over 5
=item -delay
Maximum delay time detween clicks in milliseconds. Default is 300.
If the second click of a two proximate mouse clicks occurs within the given
delay time, the event is considered a double-click. If not, the two clicks
are considered two separate (albeit nearly simultaneous) single-clicks.
=item -button
Mouse button to bind. Options are 1, 2, 3, or the corresponding synonyms
'left', 'middle', or 'right'. The default is 1 ('left').
=back
=head1 EXAMPLE
# Libraries
use strict;
use warnings;
use Tk;
use Tk::DoubleClick;
# User-defined
my $a_colors = [
[ '#8800FF', '#88FF88', '#88FFFF' ],
[ '#FF0000', '#FF0088', '#FF00FF' ],
[ '#FF8800', '#FF8888', '#FF88FF' ],
[ '#FFFF00', '#FFFF88', '#FFFFFF' ],
];
# Main program
my $nsingle = my $ndouble = 0;
my $mw = new MainWindow(-title => "Double-click example");
my $f1 = $mw->Frame->pack(-expand => 1, -fill => 'both');
my @args = qw( -width 12 -height 2 -relief groove -borderwidth 4 );
my @pack = qw( -side left -expand 1 -fill both );
# Display single/double click counts
my $lb1 = $f1->Label(-text => "Single Clicks", @args);
my $lb2 = $f1->Label(-textvar => \$nsingle, @args);
my $lb3 = $f1->Label(-text => "Double Clicks", @args);
my $lb4 = $f1->Label(-textvar => \$ndouble, @args);
$lb1->pack($lb2, $lb3, $lb4, @pack);
# Create button for each color, and bind single/double clicks to it
foreach my $a_color (@$a_colors) {
my $fr = $mw->Frame->pack(-expand => 1, -fill => 'both');
foreach my $bg (@$a_color) {
my $b = $fr->Button(-bg => $bg, -text => $bg, @args);
$b->pack(@pack);
bind_clicks($b, [\&single, $lb2, $bg], [\&double, $lb4, $bg]);
}
}
# Make 'Escape' quit the program
$mw->bind("<Escape>" => sub { exit });
MainLoop;
# Callbacks
sub single {
my ($lbl, $color) = @_;
$lbl->configure(-bg => $color);
++$nsingle;
}
sub double {
my ($lbl, $color) = @_;
$lbl->configure(-bg => $color);
++$ndouble;
}
=head1 ACKNOWLEDGEMENTS
Thanks to Mark Freeman for numerous great suggestions and documentation help.
=head1 AUTHOR
John C. Norton, C<< <jchnorton at verizon.net> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-tk-doubleclick at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Tk-DoubleClick>. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Tk::DoubleClick
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
=item * AnnoCPAN: Annotated CPAN documentation
=item * CPAN Ratings
=item * Search CPAN
=back
=head1 ACKNOWLEDGEMENTS
Thanks to Mark Freeman for numerous great suggestions and documentation help.
=head1 COPYRIGHT & LICENSE
Copyright 2009 John C. Norton.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
=cut
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(bind_clicks);
# Track last-clicked mouse number, widget, "after" event id and callback.
my $h_pend = { 'mn' => 0, 'wi' => 0, 'id' => 0, 'cb' => 0 };
sub bind_clicks {
my ($widget, $a_single, $a_double, %args) = @_;
my $delay = delete $args{-delay} || 300;
my $button = delete $args{-button} || 'left';
my $h_button = { left => 1, middle => 2, right => 3 };
my $mousenum = $h_button->{$button} || $button;
($mousenum =~ /^[123]$/) or $mousenum = 1;
my $c_single = $a_single;
if (ref $a_single eq 'ARRAY') {
my $c_cmd = shift @$a_single;
$c_single = sub { $c_cmd->(@$a_single) };
}
my $c_double = $a_double;
if (ref $a_double eq 'ARRAY') {
my $c_cmd = shift @$a_double;
$c_double = sub { $c_cmd->(@$a_double) };
}
my $button_name = "<Button-$mousenum>";
my $c_pending = sub {
my ($mousenum, $widget, $id) = @_;
$h_pend->{'mn'} = $mousenum;
$h_pend->{'wi'} = $widget;
$h_pend->{'id'} = $id;
$h_pend->{'cb'} = $c_single;
};
my $c_cmd = sub {
my $b_sched = 0; # Schedule new single-click?
if (!$h_pend->{'id'}) {
# No click is pending -- schedule a new one
$b_sched = 1;
} else {
# Cancel pending single-click event
$h_pend->{'wi'}->afterCancel($h_pend->{'id'});
$h_pend->{'id'} = 0;
if ($h_pend->{'mn'} == $mousenum and $h_pend->{'wi'} eq $widget) {
# Invoke double-click callback and reset pending event
$c_double->();
$c_pending->(0, 0, 0);
} else {
# Invoke previous single-click, and schedule a new one
$h_pend->{'cb'}->();
$b_sched = 1;
}
}
# Schedule new single-click subroutine when $delay expires
if ($b_sched) {
my $c_after = sub { $c_pending->(0, 0, 0); $c_single->() };
my $id = $widget->after($delay => $c_after);
$c_pending->($mousenum, $widget, $id);
}
};
$widget->bind($button_name => $c_cmd);
}
1;