You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
353 lines
8.1 KiB
353 lines
8.1 KiB
# |
|
# BrowseEntry is a stripped down version of ComboBox.tcl from Tix4.0 |
|
|
|
package Milkbone::BrowseEntry; |
|
|
|
use vars qw($VERSION); |
|
$VERSION = '3.030'; # $Id$ |
|
|
|
use Tk qw(Ev); |
|
use Carp; |
|
use strict; |
|
|
|
require Tk::Frame; |
|
require Tk::LabEntry; |
|
|
|
use base qw(Tk::Frame); |
|
Construct Tk::Widget 'MBCombo'; |
|
|
|
sub Populate { |
|
my ($w, $args) = @_; |
|
|
|
$w->SUPER::Populate($args); |
|
|
|
# entry widget and arrow button |
|
my $lpack = delete $args->{-labelPack}; |
|
if (not defined $lpack) { |
|
$lpack = [-side => 'left', -anchor => 'e']; |
|
} |
|
my $var = ""; |
|
my $e = $w->LabEntry(-labelPack => $lpack, |
|
-label => delete $args->{-label}, |
|
-textvariable => \$var, -background => "white"); |
|
my $b = $w->Button(-bitmap => '@' . Tk->findINC('cbxarrow.xbm')); |
|
$w->Advertise('entry' => $e); |
|
$w->Advertise('arrow' => $b); |
|
$b->pack(-side => 'right', -padx => 1); |
|
$e->pack(-side => 'right', -fill => 'x', -expand => 1, -padx => 1); |
|
|
|
# popup shell for listbox with values. |
|
my $c = $w->Toplevel(-bd => 2, -relief => 'raised'); |
|
$c->overrideredirect(1); |
|
$c->withdraw; |
|
my $sl = $c->Scrolled( qw/Listbox -selectmode browse -scrollbars oe/ ); |
|
$w->Advertise('choices' => $c); |
|
$w->Advertise('slistbox' => $sl); |
|
$sl->pack(-expand => 1, -fill => 'both'); |
|
|
|
# other initializations |
|
$w->SetBindings; |
|
$w->{'popped'} = 0; |
|
$w->Delegates('insert' => $sl, 'delete' => $sl, get => $sl, DEFAULT => $e); |
|
$w->ConfigSpecs( |
|
-listwidth => [qw/PASSIVE listWidth ListWidth/, undef], |
|
-listcmd => [qw/CALLBACK listCmd ListCmd/, undef], |
|
-browsecmd => [qw/CALLBACK browseCmd BrowseCmd/, undef], |
|
-choices => [qw/METHOD choices Choices/, undef], |
|
-state => [qw/METHOD state State normal/], |
|
-arrowimage => [ {-image => $b}, qw/arrowImage ArrowImage/, undef], |
|
-variable => '-textvariable', |
|
-colorstate => [qw/PASSIVE colorState ColorState/, undef], |
|
-command => '-browsecmd', |
|
-options => '-choices', |
|
DEFAULT => [$e] ); |
|
} |
|
|
|
sub SetBindings { |
|
my ($w) = @_; |
|
|
|
my $e = $w->Subwidget('entry'); |
|
my $b = $w->Subwidget('arrow'); |
|
|
|
# set bind tags |
|
$w->bindtags([$w, 'Tk::BrowseEntry', $w->toplevel, 'all']); |
|
$e->bindtags([$e, $e->toplevel, 'all']); |
|
|
|
# bindings for the button and entry |
|
$b->bind('<1>',[$w,'BtnDown']); |
|
$b->toplevel->bind('<ButtonRelease-1>',[$w,'ButtonHack']); |
|
$b->bind('<space>',[$w,'space']); |
|
|
|
# bindings for listbox |
|
my $sl = $w->Subwidget('slistbox'); |
|
my $l = $sl->Subwidget('listbox'); |
|
$l->bind('<ButtonRelease-1>',[$w,'ListboxRelease',Ev('x'),Ev('y')]); |
|
$l->bind('<Escape>' => [$w,'LbClose']); |
|
$l->bind('<Return>' => [$w,'Return',$l]); |
|
|
|
# allow click outside the popped up listbox to pop it down. |
|
$w->bind('<1>','BtnDown'); |
|
} |
|
|
|
sub space |
|
{ |
|
my $w = shift; |
|
$w->BtnDown; |
|
$w->{'savefocus'} = $w->focusCurrent; |
|
$w->Subwidget('slistbox')->focus; |
|
} |
|
|
|
|
|
sub ListboxRelease |
|
{ |
|
my ($w,$x,$y) = @_; |
|
$w->ButtonHack; |
|
$w->LbChoose($x, $y); |
|
} |
|
|
|
sub Return |
|
{ |
|
my ($w,$l) = @_; |
|
my($x, $y) = $l->bbox($l->curselection); |
|
$w->LbChoose($x, $y) |
|
} |
|
|
|
|
|
sub BtnDown { |
|
my ($w) = @_; |
|
return if $w->cget( '-state' ) eq 'disabled'; |
|
|
|
if ($w->{'popped'}) { |
|
$w->Popdown; |
|
$w->{'buttonHack'} = 0; |
|
} else { |
|
$w->PopupChoices; |
|
$w->{'buttonHack'} = 1; |
|
} |
|
} |
|
|
|
sub PopupChoices { |
|
my ($w) = @_; |
|
|
|
if (!$w->{'popped'}) { |
|
$w->Callback(-listcmd => $w); |
|
my $e = $w->Subwidget('entry'); |
|
my $c = $w->Subwidget('choices'); |
|
my $s = $w->Subwidget('slistbox'); |
|
my $a = $w->Subwidget('arrow'); |
|
my $y1 = $e->rooty + $e->height + 3; |
|
my $bd = $c->cget(-bd) + $c->cget(-highlightthickness); |
|
my $ht = $s->reqheight + 2 * $bd; |
|
my $x1 = $e->rootx; |
|
my ($width, $x2); |
|
if (defined $w->cget(-listwidth)) { |
|
$width = $w->cget(-listwidth); |
|
$x2 = $x1 + $width; |
|
} else { |
|
$x2 = $a->rootx + $a->width; |
|
$width = $x2 - $x1; |
|
} |
|
my $rw = $c->reqwidth; |
|
if ($rw < $width) { |
|
$rw = $width |
|
} else { |
|
if ($rw > $width * 3) { |
|
$rw = $width * 3; |
|
} |
|
if ($rw > $w->vrootwidth) { |
|
$rw = $w->vrootwidth; |
|
} |
|
} |
|
$width = $rw; |
|
|
|
# if listbox is too far right, pull it back to the left |
|
# |
|
if ($x2 > $w->vrootwidth) { |
|
$x1 = $w->vrootwidth - $width; |
|
} |
|
|
|
# if listbox is too far left, pull it back to the right |
|
# |
|
if ($x1 < 0) { |
|
$x1 = 0; |
|
} |
|
|
|
# if listbox is below bottom of screen, pull it up. |
|
my $y2 = $y1 + $ht; |
|
if ($y2 > $w->vrootheight) { |
|
$y1 = $y1 - $ht - ($e->height - 5); |
|
} |
|
|
|
$c->geometry(sprintf('%dx%d+%d+%d', $rw, $ht, $x1, $y1)); |
|
$c->deiconify; |
|
$c->raise; |
|
$e->focus; |
|
$w->{'popped'} = 1; |
|
|
|
$c->configure(-cursor => 'arrow'); |
|
$w->grabGlobal; |
|
} |
|
} |
|
|
|
# choose value from listbox if appropriate |
|
sub LbChoose { |
|
my ($w, $x, $y) = @_; |
|
my $l = $w->Subwidget('slistbox')->Subwidget('listbox'); |
|
if ((($x < 0) || ($x > $l->Width)) || |
|
(($y < 0) || ($y > $l->Height))) { |
|
# mouse was clicked outside the listbox... close the listbox |
|
$w->LbClose; |
|
} else { |
|
# select appropriate entry and close the listbox |
|
$w->LbCopySelection; |
|
$w->Callback(-browsecmd => $w, $w->Subwidget('entry')->get); |
|
} |
|
} |
|
|
|
# close the listbox after clearing selection |
|
sub LbClose { |
|
my ($w) = @_; |
|
my $l = $w->Subwidget('slistbox')->Subwidget('listbox'); |
|
$l->selection('clear', 0, 'end'); |
|
$w->Popdown; |
|
} |
|
|
|
# copy the selection to the entry and close listbox |
|
sub LbCopySelection { |
|
my ($w) = @_; |
|
my $index = $w->LbIndex; |
|
if (defined $index) { |
|
$w->{'curIndex'} = $index; |
|
my $l = $w->Subwidget('slistbox')->Subwidget('listbox'); |
|
my $var_ref = $w->cget( '-textvariable' ); |
|
$$var_ref = $l->get($index); |
|
if ($w->{'popped'}) { |
|
$w->Popdown; |
|
} |
|
} |
|
$w->Popdown; |
|
} |
|
|
|
sub LbIndex { |
|
my ($w, $flag) = @_; |
|
my $sel = $w->Subwidget('slistbox')->Subwidget('listbox')->curselection; |
|
if (defined $sel) { |
|
return int($sel); |
|
} else { |
|
if (defined $flag && ($flag eq 'emptyOK')) { |
|
return undef; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
# pop down the listbox |
|
sub Popdown { |
|
my ($w) = @_; |
|
if ($w->{'savefocus'} && Tk::Exists($w->{'savefocus'})) { |
|
$w->{'savefocus'}->focus; |
|
delete $w->{'savefocus'}; |
|
} |
|
if ($w->{'popped'}) { |
|
my $c = $w->Subwidget('choices'); |
|
$c->withdraw; |
|
$w->grabRelease; |
|
$w->{'popped'} = 0; |
|
} |
|
} |
|
|
|
# This hack is to prevent the ugliness of the arrow being depressed. |
|
# |
|
sub ButtonHack { |
|
my ($w) = @_; |
|
my $b = $w->Subwidget('arrow'); |
|
if ($w->{'buttonHack'}) { |
|
$b->butUp; |
|
} |
|
} |
|
|
|
sub choices |
|
{ |
|
my ($w,$choices) = @_; |
|
if (@_ > 1) |
|
{ |
|
$w->delete( qw/0 end/ ); |
|
my %hash; |
|
my $var = $w->cget('-textvariable'); |
|
my $old = $$var; |
|
foreach my $val (@$choices) |
|
{ |
|
$w->insert( 'end', $val); |
|
$hash{$val} = 1; |
|
} |
|
$old = (@$choices) ? $choices->[0] : undef unless exists $hash{$old}; |
|
$$var = $old; |
|
} |
|
else |
|
{ |
|
return( $w->get( qw/0 end/ ) ); |
|
} |
|
} |
|
|
|
sub _set_edit_state { |
|
my( $w, $state ) = @_; |
|
|
|
my $entry = $w->Subwidget( 'entry' ); |
|
my $button = $w->Subwidget( 'arrow' ); |
|
|
|
if ($w->cget( '-colorstate' )) { |
|
my $color; |
|
if( $state eq 'normal' ) { # Editable |
|
$color = 'gray95'; |
|
} else { # Not Editable |
|
$color = $w->cget( -background ) || 'lightgray'; |
|
} |
|
$entry->Subwidget( 'entry' )->configure( -background => $color ); |
|
} |
|
|
|
if( $state eq 'readonly' ) { |
|
$entry->configure( -state => 'disabled' ); |
|
$button->configure( -state => 'normal' ); |
|
} else { |
|
$entry->configure( -state => $state ); |
|
$button->configure( -state => $state ); |
|
} |
|
} |
|
|
|
sub state { |
|
my $w = shift; |
|
unless( @_ ) { |
|
return( $w->{Configure}{-state} ); |
|
} else { |
|
my $state = shift; |
|
$w->{Configure}{-state} = $state; |
|
$w->_set_edit_state( $state ); |
|
} |
|
} |
|
|
|
sub _max { |
|
my $max = shift; |
|
foreach my $val (@_) { |
|
$max = $val if $max < $val; |
|
} |
|
return( $max ); |
|
} |
|
|
|
sub shrinkwrap { |
|
my( $w, $size ) = @_; |
|
|
|
unless( defined $size ) { |
|
$size = _max( map( length, $w->get( qw/0 end/ ) ) ) || 0;; |
|
} |
|
|
|
my $lb = $w->Subwidget( 'slistbox' )->Subwidget( 'listbox' ); |
|
$w->configure( -width => $size ); |
|
$lb->configure( -width => $size ); |
|
} |
|
|
|
|
|
1; |
|
|
|
__END__ |
|
|
|
|