1package Tk::ToolBar;
2
3use 5.005;
4use strict;
5use Tk::Frame;
6use Tk::Balloon;
7
8use base qw/Tk::Frame/;
9use Tk::widgets qw(Frame);
10
11use Carp;
12use POSIX qw/ceil/;
13
14Construct Tk::Widget 'ToolBar';
15
16use vars qw/$VERSION/;
17$VERSION = '0.12';
18
19my $edgeH = 24;
20my $edgeW = 5;
21
22my $sepH  = 24;
23my $sepW  = 3;
24
25my %sideToSticky = qw(
26		      top    n
27		      right  e
28		      left   w
29		      bottom s
30		      );
31
32my $packIn     = '';
33my @allWidgets = ();
34my $floating   = 0;
35my %packIn;
36my %containers;
37my %isDummy;
38
391;
40
41sub ClassInit {
42    my ($class, $mw) = @_;
43    $class->SUPER::ClassInit($mw);
44
45    # load the images.
46    my $imageFile = Tk->findINC('ToolBar/tkIcons');
47
48    if (defined $imageFile) {
49	local *F;
50	open F, $imageFile;
51
52	local $_;
53
54	while (<F>) {
55	    chomp;
56	    my ($n, $d) = (split /:/)[0, 4];
57
58	    $mw->Photo($n, -data => $d);
59	}
60	close F;
61    } else {
62	carp <<EOW;
63WARNING: can not find tkIcons. Your installation of Tk::ToolBar is broken.
64         No icons will be loaded.
65EOW
66;
67    }
68}
69
70sub Populate {
71    my ($self, $args) = @_;
72
73    $self->SUPER::Populate($args);
74    $self->{MW}     = $self->parent;
75    $self->{SIDE}   = exists $args->{-side}          ? delete $args->{-side}          : 'top';
76    $self->{STICKY} = exists $args->{-sticky}        ? delete $args->{-sticky}        : 'nsew';
77    $self->{USECC}  = exists $args->{-cursorcontrol} ? delete $args->{-cursorcontrol} : 1;
78    $self->{STYLE}  = exists $args->{-mystyle}       ? delete $args->{-mystyle}       : 0;
79    $packIn         = exists $args->{-in}            ? delete $args->{-in}            : '';
80
81    if ($packIn) {
82      unless ($packIn->isa('Tk::ToolBar')) {
83	croak "value of -packin '$packIn' is not a Tk::ToolBar object";
84      } else {
85	$self->{SIDE} = $packIn->{SIDE};
86      }
87    }
88
89    unless ($self->{STICKY} =~ /$sideToSticky{$self->{SIDE}}/) {
90	croak "can't place '$self->{STICKY}' toolbar on '$self->{SIDE}' side";
91    }
92
93    $self->{CONTAINER} = $self->{MW}->Frame;
94    $self->_packSelf;
95
96    my $edge = $self->{CONTAINER}->Frame(qw/
97					 -borderwidth 2
98					 -relief ridge
99					 /);
100
101    $self->{EDGE} = $edge;
102
103    $self->_packEdge($edge, 1);
104
105    $self->ConfigSpecs(
106		       -movable          => [qw/METHOD  movable          Movable             1/],
107		       -close            => [qw/PASSIVE close            Close              15/],
108		       -activebackground => [qw/METHOD  activebackground ActiveBackground/, Tk::ACTIVE_BG],
109		       -indicatorcolor   => [qw/PASSIVE indicatorcolor   IndicatorColor/,   '#00C2F1'],
110		       -indicatorrelief  => [qw/PASSIVE indicatorrelief  IndicatorRelief    flat/],
111		       -float            => [qw/PASSIVE float            Float              1/],
112		      );
113
114    push @allWidgets => $self;
115
116    $containers{$self->{CONTAINER}} = $self;
117
118    $self->{BALLOON} = $self->{MW}->Balloon;
119
120    # check for Tk::CursorControl
121    $self->{CC} = undef;
122    if ($self->{USECC}) {
123	local $^W = 0; # suppress message from Win32::API
124	eval "require Tk::CursorControl";
125	unless ($@) {
126	    # CC is installed. Use it.
127	    $self->{CC} = $self->{MW}->CursorControl;
128	}
129    }
130}
131
132sub activebackground {
133    my ($self, $c) = @_;
134
135    return unless $c; # ignore falses.
136
137    $self->{ACTIVE_BG} = $c;
138}
139
140sub _packSelf {
141    my $self = shift;
142
143    my $side = $self->{SIDE};
144    my $fill = 'y';
145    if ($side eq 'top' or $side eq 'bottom') { $fill = 'x' }
146
147    if ($packIn && $packIn != $self) {
148	my $side = $packIn->{SIDE} =~ /top|bottom/ ? 'left' : 'top';
149
150	$self->{CONTAINER}->pack(-in => $packIn->{CONTAINER},
151				 -side => $side,
152				 -anchor => ($fill eq 'x' ? 'w' : 'n'),
153				 -expand => 0);
154	$self->{CONTAINER}->raise;
155	$packIn{$self->{CONTAINER}} = $packIn->{CONTAINER};
156    } else {
157	# force a certain look! for now.
158	my $slave = ($self->{MW}->packSlaves)[0];
159
160	$self->configure(qw/-relief raised -borderwidth 1/);
161	$self->pack(-side => $side, -fill => $fill,
162		    $slave ? (-before => $slave) : ()
163		    );
164
165	$self->{CONTAINER}->pack(-in => $self,
166				 -anchor => ($fill eq 'x' ? 'w' : 'n'),
167				 -expand => 0);
168
169	$packIn{$self->{CONTAINER}} = $self;
170    }
171}
172
173sub _packEdge {
174    my $self = shift;
175    my $e    = shift;
176    my $w    = shift;
177
178    my $s    = $self->{SIDE};
179
180    my ($pack, $pad, $nopad, $fill);
181
182    if ($s eq 'top' or $s eq 'bottom') {
183      if ($w) {
184	$e->configure(-height => $edgeH, -width => $edgeW);
185      } else {
186	$e->configure(-height => $sepH, -width => $sepW);
187      }
188      $pack  = 'left';
189      $pad   = '-padx';
190      $nopad = '-pady';
191      $fill  = 'y';
192    } else {
193      if ($w) {
194	$e->configure(-height => $edgeW, -width => $edgeH);
195      } else {
196	$e->configure(-height => $sepW, -width => $sepH);
197      }
198
199      $pack  = 'top';
200      $pad   = '-pady';
201      $nopad = '-padx';
202      $fill  = 'x';
203    }
204
205    if (exists $self->{SEPARATORS}{$e}) {
206	$e->configure(-cursor => $pack eq 'left' ? 'sb_h_double_arrow' : 'sb_v_double_arrow');
207	$self->{SEPARATORS}{$e}->pack(-side   => $pack,
208				      -fill   => $fill);
209    }
210
211    $e->pack(-side  => $pack, $pad => 5,
212	     $nopad => 0,  -expand => 0);
213}
214
215sub movable {
216    my ($self, $value) = @_;
217
218    if (defined $value) {
219	$self->{ISMOVABLE} = $value;
220	my $e = $self->_edge;
221
222	if ($value) {
223	    $e->configure(qw/-cursor fleur/);
224	    $self->afterIdle(sub {$self->_enableEdge()});
225	} else {
226	    $e->configure(-cursor => undef);
227	    $self->_disableEdge($e);
228	}
229    }
230
231    return $self->{ISMOVABLE};
232}
233
234sub _enableEdge {
235  my ($self) = @_;
236
237  my $e     = $self->_edge;
238  my $hilte = $self->{MW}->Frame(-bg     => $self->cget('-indicatorcolor'),
239				 -relief => $self->cget('-indicatorrelief'));
240
241  my $dummy = $self->{MW}->Frame(
242				 qw/
243				 -borderwidth 2
244				 -relief ridge
245				 /);
246
247  $self->{DUMMY} = $dummy;
248
249  my $drag     = 0;
250  #my $floating = 0;
251  my $clone;
252
253  my @mwSize;  # extent of mainwindow.
254
255  $e->bind('<1>'         => sub {
256	     $self->{CC}->confine($self->{MW}) if defined $self->{CC};
257	     my $geom      = $self->{MW}->geometry;
258	     my ($rx, $ry) = ($self->{MW}->rootx, $self->{MW}->rooty);
259
260	     if ($geom =~ /(\d+)x(\d+)/) {#\+(\d+)\+(\d+)/) {
261#	       @mwSize = ($3, $4, $1 + $3, $2 + $4);
262	       @mwSize = ($rx, $ry, $1 + $rx, $2 + $ry);
263	     } else {
264	       @mwSize = ();
265	     }
266
267	     if (!$self->{ISCLONE} && $self->{CLONE}) {
268	       $self->{CLONE}->destroy;
269	       $self->{CLONE} = $clone = undef;
270	       @allWidgets = grep Tk::Exists, @allWidgets;
271	     }
272
273	   });
274
275  $e->bind('<B1-Motion>' => sub {
276	     my ($x, $y) = ($self->pointerx - $self->{MW}->rootx - ceil($e->width /2) - $e->x,
277			    $self->pointery - $self->{MW}->rooty - ceil($e->height/2) - $e->y);
278
279	     my ($px, $py) = $self->pointerxy;
280
281	     $dummy = $self->{ISCLONE} ? $self->{CLONE}{DUMMY} : $self->{DUMMY};
282
283	     unless ($drag or $floating) {
284	       $drag = 1;
285	       $dummy->raise;
286	       my $noclone = $self->{ISCLONE} ? $self->{CLONE} : $self;
287	       $noclone->packForget;
288	       $noclone->{CONTAINER}->pack(-in => $dummy);
289	       $noclone->{CONTAINER}->raise;
290	       ref($_) eq 'Tk::Frame' && $_->raise for $noclone->{CONTAINER}->packSlaves;
291	     }
292	     $hilte->placeForget;
293
294	     if ($self->cget('-float') &&
295		 (@mwSize and
296		 $px < $mwSize[0] or
297		 $py < $mwSize[1] or
298		 $px > $mwSize[2] or
299		 $py > $mwSize[3])) {
300
301	       # we are outside .. switch to toplevel mode.
302	       $dummy->placeForget;
303	       $floating = 1;
304
305	       unless ($self->{CLONE} || $self->{ISCLONE}) {
306		 # clone it.
307		 my $clone = $self->{MW}->Toplevel(qw/-relief ridge -borderwidth 2/);
308		 $clone->withdraw;
309		 $clone->overrideredirect(1);
310		 $self->_clone($clone);
311		 $self->{CLONE} = $clone;
312	       }
313
314	       $clone = $self->{ISCLONE} || $self->{CLONE};
315	       $clone->deiconify unless $clone->ismapped;
316	       $clone->geometry("+$px+$py");
317
318	     } else {
319	       $self->{ISCLONE}->withdraw if $self->{CLONE} && $self->{ISCLONE};
320
321	       $dummy->place('-x' => $x, '-y' => $y);
322	       $floating = 0;
323
324	       if (my $newSide = $self->_whereAmI($x, $y)) {
325		 # still inside main window.
326		 # highlight the close edge.
327		 $clone && $clone->ismapped && $clone->withdraw;
328		 #$self->{ISCLONE}->withdraw if $self->{CLONE} && $self->{ISCLONE};
329
330		 my ($op, $pp);
331		 if ($newSide =~ /top/) {
332		   $op = [qw/-height 5/];
333		   $pp = [qw/-relx 0 -relwidth 1 -y 0/];
334		 } elsif ($newSide =~ /bottom/) {
335		   $op = [qw/-height 5/];
336		   $pp = [qw/-relx 0 -relwidth 1 -y -5 -rely 1/];
337		 } elsif ($newSide =~ /left/) {
338		   $op = [qw/-width 5/];
339		   $pp = [qw/-x 0 -relheight 1 -y 0/];
340		 } elsif ($newSide =~ /right/) {
341		   $op = [qw/-width 5/];
342		   $pp = [qw/-x -5 -relx 1 -relheight 1 -y 0/];
343		 }
344
345		 $hilte->configure(@$op);
346		 $hilte->place(@$pp);
347		 $hilte->raise;
348	       }
349	     }
350	   });
351
352    $e->bind('<ButtonRelease-1>' => sub {
353	my $noclone = $self->{ISCLONE} ? $self->{CLONE} : $self;
354	$noclone->{CC}->free($noclone->{MW}) if defined $noclone->{CC};
355	return unless $drag;
356
357	$drag = 0;
358	$dummy->placeForget;
359
360	# forget everything if it's cloned.
361	return if $clone && $clone->ismapped;
362
363	# destroy the clone.
364	#$clone->destroy;
365
366	#return unless $self->_whereAmI(1);
367	$noclone->_whereAmI(1);
368	$hilte->placeForget;
369
370	# repack everything now.
371	my $ec = $noclone->_edge;
372	my @allSlaves = grep {$_ ne $ec} $noclone->{CONTAINER}->packSlaves;
373	$_   ->packForget for $noclone, @allSlaves, $noclone->{CONTAINER};
374
375	$noclone->_packSelf;
376	$noclone->_packEdge($ec, 1);
377	$noclone->_packWidget($_) for @allSlaves;
378    });
379}
380
381sub _whereAmI {
382    my $self = shift;
383
384    my $flag = 0;
385    my ($x, $y);
386
387    if (@_ == 1) {
388	$flag = shift;
389	my $e    = $self->_edge;
390	($x, $y) = ($self->pointerx - $self->{MW}->rootx - ceil($e->width /2) - $e->x,
391		    $self->pointery - $self->{MW}->rooty - ceil($e->height/2) - $e->y);
392    } else {
393	($x, $y) = @_;
394    }
395
396    my $x2 = $x + $self->{CONTAINER}->width;
397    my $y2 = $y + $self->{CONTAINER}->height;
398
399    my $w  = $self->{MW}->Width;
400    my $h  = $self->{MW}->Height;
401
402    # bound check
403    $x     = 1      if $x  <= 0;
404    $y     = 1      if $y  <= 0;
405    $x     = $w - 1 if $x  >= $w;
406    $y     = $h - 1 if $y  >= $h;
407
408    $x2    = 0      if $x2 <= 0;
409    $y2    = 0      if $y2 <= 0;
410    $x2    = $w - 1 if $x2 >= $w;
411    $y2    = $h - 1 if $y2 >= $h;
412
413    my $dx = 0;
414    my $dy = 0;
415
416    my $close = $self->cget('-close');
417
418    if    ($x       < $close) { $dx = $x }
419    elsif ($w - $x2 < $close) { $dx = $x2 - $w }
420
421    if    ($y       < $close) { $dy = $y }
422    elsif ($h - $y2 < $close) { $dy = $y2 - $h }
423
424    $packIn       = '';
425    if ($dx || $dy) {
426	my $newSide;
427	if ($dx && $dy) {
428	    # which is closer?
429	    if (abs($dx) < abs($dy)) {
430		$newSide = $dx > 0 ? 'left' : 'right';
431	    } else {
432		$newSide = $dy > 0 ? 'top' : 'bottom';
433	    }
434	} elsif ($dx) {
435	    $newSide = $dx > 0 ? 'left' : 'right';
436	} else {
437	    $newSide = $dy > 0 ? 'top' : 'bottom';
438	}
439
440	# make sure we're stickable on that side.
441	return undef unless $self->{STICKY} =~ /$sideToSticky{$newSide}/;
442
443	$self->{SIDE} = $newSide if $flag;
444	return $newSide;
445    } elsif ($flag) {
446	# check for overlaps.
447	for my $w (@allWidgets) {
448	    next if $w == $self;
449
450	    my $x1 = $w->x;
451	    my $y1 = $w->y;
452	    my $x2 = $x1 + $w->width;
453	    my $y2 = $y1 + $w->height;
454
455	    if ($x > $x1 and $y > $y1 and $x < $x2 and $y < $y2) {
456		$packIn = $w;
457		last;
458	    }
459	}
460
461      $self->{SIDE} = $packIn->{SIDE} if $packIn;
462#	if ($packIn) {
463#	  $self->{SIDE} = $packIn->{SIDE};
464#	} else {
465#	  return undef;
466#	}
467    } else {
468	return undef;
469    }
470
471    return 1;
472}
473
474sub _disableEdge {
475    my ($self, $e) = @_;
476
477    $e->bind('<B1-Motion>'       => undef);
478    $e->bind('<ButtonRelease-1>' => undef);
479}
480
481sub _edge {
482    $_[0]->{EDGE};
483}
484
485sub ToolButton {
486    my $self = shift;
487    my %args = @_;
488
489    my $type = delete $args{-type} || 'Button';
490
491    unless ($type eq 'Button' or
492	    $type eq 'Checkbutton' or
493	    $type eq 'Menubutton' or
494	    $type eq 'Radiobutton') {
495
496	croak "toolbutton can be only 'Button', 'Menubutton', 'Checkbutton', or 'Radiobutton'";
497    }
498
499    my $m = delete $args{-tip}         || '';
500    my $x = delete $args{-accelerator} || '';
501
502    my $but = $self->{CONTAINER}->$type(%args,
503				      $self->{STYLE} ? () : (
504							     -relief      => 'flat',
505							     -borderwidth => 1,
506							    ),
507				     );
508
509    $self->_createButtonBindings($but);
510    $self->_configureWidget     ($but);
511
512    push @{$self->{WIDGETS}} => $but;
513    $self->_packWidget($but);
514
515    $self->{BALLOON}->attach($but, -balloonmsg => $m) if $m;
516    $self->{MW}->bind($x => [$but, 'invoke'])         if $x;
517
518    # change the bind tags.
519    #$but->bindtags([$but, ref($but), $but->toplevel, 'all']);
520
521    return $but;
522}
523
524sub ToolLabel {
525    my $self = shift;
526
527    my $l = $self->{CONTAINER}->Label(@_);
528
529    push @{$self->{WIDGETS}} => $l;
530
531    $self->_packWidget($l);
532
533    return $l;
534}
535
536sub ToolEntry {
537    my $self = shift;
538    my %args = @_;
539
540    my $m = delete $args{-tip} || '';
541    $args{-width} = 5 unless exists $args{-width};
542    my $l = $self->{CONTAINER}->Entry(%args);
543
544    push @{$self->{WIDGETS}} => $l;
545
546    $self->_packWidget($l);
547    $self->{BALLOON}->attach($l, -balloonmsg => $m) if $m;
548
549    return $l;
550}
551
552sub ToolLabEntry {
553    my $self = shift;
554    my %args = @_;
555
556    require Tk::LabEntry;
557    my $m = delete $args{-tip} || '';
558    $args{-width} = 5 unless exists $args{-width};
559    my $l = $self->{CONTAINER}->LabEntry(%args);
560
561    push @{$self->{WIDGETS}} => $l;
562
563    $self->_packWidget($l);
564    $self->{BALLOON}->attach($l, -balloonmsg => $m) if $m;
565
566    return $l;
567}
568
569sub ToolOptionmenu {
570    my $self = shift;
571    my %args = @_;
572
573    my $m = delete $args{-tip} || '';
574    my $l = $self->{CONTAINER}->Optionmenu(%args);
575
576    push @{$self->{WIDGETS}} => $l;
577
578    $self->_packWidget($l);
579    $self->{BALLOON}->attach($l, -balloonmsg => $m) if $m;
580
581    return $l;
582}
583
584sub ToolBrowseEntry {
585    my $self = shift;
586    my %args = @_;
587
588	require Tk::BrowseEntry;
589    my $m = delete $args{-tip} || '';
590    my $l = $self->{CONTAINER}->BrowseEntry(%args);
591
592    push @{$self->{WIDGETS}} => $l;
593
594    $self->_packWidget($l);
595    $self->{BALLOON}->attach($l, -balloonmsg => $m) if $m;
596
597    return $l;
598}
599
600sub separator {
601    my $self = shift;
602    my %args = @_;
603
604    my $move = 1;
605    $move    = $args{-movable} if exists $args{-movable};
606    my $just = $args{-space} || 0;
607
608    my $f    = $self->{CONTAINER}->Frame(-width => $just, -height => 0);
609
610    my $sep  = $self->{CONTAINER}->Frame(qw/
611					 -borderwidth 5
612					 -relief sunken
613					 /);
614
615    $isDummy{$f} = $self->{SIDE};
616
617    push @{$self->{WIDGETS}} => $sep;
618    $self->{SEPARATORS}{$sep} = $f;
619    $self->_packWidget($sep);
620
621    $self->_createSeparatorBindings($sep) if $move;
622
623    if ($just eq 'right' || $just eq 'bottom') {
624      # just figure out the good width.
625    }
626
627    return 1;
628}
629
630sub _packWidget {
631    my ($self, $b) = @_;
632
633    return $self->_packEdge($b) if exists $self->{SEPARATORS}{$b};
634
635    my ($side, $pad, $nopad) = $self->{SIDE} =~ /^top$|^bottom$/ ?
636	qw/left -padx -pady/ : qw/top -pady -padx/;
637
638    if (ref($b) eq 'Tk::LabEntry') {
639	$b->configure(-labelPack => [-side => $side]);
640    }
641
642    my @extra;
643    if (exists $packIn{$b}) {
644	@extra = (-in => $packIn{$b});
645
646	# repack everything now.
647	my $top = $containers{$b};
648	$top->{SIDE} = $self->{SIDE};
649
650	my $e = $top->_edge;
651	my @allSlaves = grep {$_ ne $e} $b->packSlaves;
652	$_   ->packForget for @allSlaves;
653
654	$top->_packEdge($e, 1);
655	$top->_packWidget($_) for @allSlaves;
656    }
657
658    if (exists $isDummy{$b}) { # swap width/height if we need to.
659	my ($w, $h);
660
661	if ($side eq 'left' && $isDummy{$b} =~ /left|right/) {
662	    $w = 0;
663	    $h = $b->height;
664	} elsif ($side eq 'top'  && $isDummy{$b} =~ /top|bottom/) {
665	    $w = $b->width;
666	    $h = 0;
667	}
668
669	$b->configure(-width => $h, -height => $w) if defined $w;
670	$isDummy{$b} = $self->{SIDE};
671    }
672
673    $b->pack(-side => $side, $pad => 4, $nopad => 0, @extra);
674}
675
676sub _packWidget_old {
677    my ($self, $b) = @_;
678
679    return $self->_packEdge($b) if exists $self->{SEPARATORS}{$b};
680
681    my ($side, $pad, $nopad) = $self->{SIDE} =~ /^top$|^bottom$/ ?
682	qw/left -padx -pady/ : qw/top -pady -padx/;
683
684    if (ref($b) eq 'Tk::LabEntry') {
685	$b->configure(-labelPack => [-side => $side]);
686    }
687
688    my @extra;
689    if (exists $packIn{$b}) {
690	@extra = (-in => $packIn{$b});
691
692	# repack everything now.
693	my $top = $containers{$b};
694	$top->{SIDE} = $self->{SIDE};
695
696	my $e = $top->_edge;
697	my @allSlaves = grep {$_ ne $e} $b->packSlaves;
698	$_   ->packForget for @allSlaves;
699
700	$top->_packEdge($e, 1);
701	$top->_packWidget($_) for @allSlaves;
702    }
703
704    $b->pack(-side => $side, $pad => 4, $nopad => 0, @extra);
705}
706
707sub _configureWidget {
708    my ($self, $w) = @_;
709
710    $w->configure(-activebackground => $self->{ACTIVE_BG});
711}
712
713sub _createButtonBindings {
714    my ($self, $b) = @_;
715
716    my $bg = $b->cget('-bg');
717
718    $b->bind('<Enter>' => [$b, 'configure', qw/-relief raised/]);
719    $b->bind('<Leave>' => [$b, 'configure', qw/-relief flat/]);
720}
721
722sub _createSeparatorBindings {
723  my ($self, $s) = @_;
724
725  my ($ox, $oy);
726
727  $s->bind('<1>'         => sub {
728	     $ox = $s->XEvent->x;
729	     $oy = $s->XEvent->y;
730	   });
731
732  $s->bind('<B1-Motion>' => sub {
733	     my $x = $s->XEvent->x;
734	     my $y = $s->XEvent->y;
735
736	     my $f = $self->{SEPARATORS}{$s};
737
738	     if ($self->{SIDE} =~ /top|bottom/) {
739	       my $dx = $x - $ox;
740
741	       my $w  = $f->width + $dx;
742	       $w     = 0 if $w < 0;
743
744	       $f->GeometryRequest($w, $f->height);
745	     } else {
746	       my $dy = $y - $oy;
747
748	       my $h  = $f->height + $dy;
749	       $h     = 0 if $h < 0;
750
751	       $f->GeometryRequest($f->width, $h);
752	     }
753	   });
754}
755
756sub Button     { goto &ToolButton      }
757sub Label      { goto &ToolLabel       }
758sub Entry      { goto &ToolEntry       }
759sub LabEntry   { goto &ToolLabEntry    }
760sub Optionmenu { goto &ToolOptionmenu  }
761sub BrowseEntry { goto &ToolBrowseEntry }
762
763sub _clone {
764  my ($self, $top, $in) = @_;
765
766  my $new = $top->ToolBar(qw/-side top -cursorcontrol/, $self->{USECC}, ($in ? (-in => $in, -movable => 0) : ()));
767  my $e   = $self->_edge;
768
769  my @allSlaves = grep {$_ ne $e} $self->{CONTAINER}->packSlaves;
770  for my $w (@allSlaves) {
771    my $t = ref $w;
772    $t =~ s/Tk:://;
773
774    if ($t eq 'Frame' && exists $containers{$w}) { # embedded toolbar
775      my $obj = $containers{$w};
776      $obj->_clone($top, $new);
777    }
778
779    if ($t eq 'Frame' && exists $self->{SEPARATORS}{$w}) {  # separator
780      $new->separator;
781    }
782
783    my %c = map { $_->[0], $_->[4] || $_->[3] } grep {defined $_->[4] || $_->[3] } grep @$_ > 2, $w->configure;
784    delete $c{$_} for qw/-offset -class -tile -visual -colormap -labelPack/;
785
786    if ($t =~ /.button/) {
787      $new->Button(-type => $t,
788		   %c);
789    } else {
790      $new->$t(%c);
791    }
792  }
793
794  $new ->{MW}      = $self->{MW};
795  $new ->{CLONE}   = $self;
796  $new ->{ISCLONE} = $top;
797  $self->{ISCLONE} = 0;
798}
799
800__END__
801
802=pod
803
804=head1 NAME
805
806Tk::ToolBar - A toolbar widget for Perl/Tk
807
808=for category Tk Widget Classes
809
810=head1 SYNOPSIS
811
812        use Tk;
813        use Tk::ToolBar;
814
815        my $mw = MainWindow->new;
816        my $tb = $mw->ToolBar(qw/-movable 1 -side top
817                                 -indicatorcolor blue/);
818
819        $tb->ToolButton  (-text  => 'Button',
820                          -tip   => 'tool tip',
821                          -command => sub { print "hi\n" });
822        $tb->ToolLabel   (-text  => 'A Label');
823        $tb->Label       (-text  => 'Another Label');
824        $tb->ToolLabEntry(-label => 'A LabEntry',
825                          -labelPack => [-side => "left",
826                                         -anchor => "w"]);
827
828        my $tb2 = $mw->ToolBar;
829	$tb2->ToolButton(-image   => 'navback22',
830			 -tip     => 'back',
831			 -command => \&back);
832        $tb2->ToolButton(-image   => 'navforward22',
833			 -tip     => 'forward',
834			 -command => \&forward);
835        $tb2->separator;
836        $tb2->ToolButton(-image   => 'navhome22',
837			 -tip     => 'home',
838			 -command => \&home);
839        $tb2->ToolButton(-image   => 'actreload22',
840			 -tip     => 'reload',
841			 -command => \&reload);
842
843        MainLoop;
844
845=head1 DESCRIPTION
846
847This module implements a dockable toolbar. It is in the same spirit as the
848"short-cut" toolbars found in most major applications, such as most web browsers
849and text editors (where you find the "back" or "save" and other shortcut buttons).
850
851Buttons of any type (regular, menu, check, radio) can be created inside this widget.
852You can also create Label, Entry and LabEntry widgets.
853Moreover, the ToolBar itself can be made dockable, such that it can be dragged to
854any edge of your window. Dragging is done in "real-time" so that you can see the
855contents of your ToolBar as you are dragging it. Furthermore, if you are close to
856a stickable edge, a visual indicator will show up along that edge to guide you.
857ToolBars can be made "floatable" such that if they are dragged beyond their
858associated window, they will detach and float on the desktop.
859Also, multiple ToolBars are embeddable inside each other.
860
861If you drag a ToolBar to within 15 pixels of an edge, it will stick to that
862edge. If the ToolBar is further than 15 pixels away from an edge and still
863inside the window, but you
864release it over another ToolBar widget, then it will be embedded inside the
865second ToolBar. You can "un-embed" an embedded ToolBar simply by dragging it
866out. You can change the 15 pixel limit using the B<-close> option.
867
868Various icons are built into the Tk::ToolBar widget. Those icons can be used
869as images for ToolButtons (see L</SYNOPSIS>). A demo program is bundled with
870the module that should be available under the 'User Contributed Demonstrations'
871when you run the B<widget> program. Run it to see a list of the available
872images.
873
874Tk::ToolBar attempts to use Tk::CursorControl if it's already installed on
875the system. You can further control this using the I<-cursorcontrol> option.
876See L</PREREQUISITES>.
877
878The ToolBar is supposed to be created as a child of a Toplevel (MainWindow is
879a Toplevel widget) or a Frame. You are free to experiment otherwise,
880but expect the unexpected :-)
881
882=head1 WIDGET-SPECIFIC OPTIONS
883
884The ToolBar widget takes the following arguments:
885
886=over 4
887
888=item B<-side>
889
890This option tells the ToolBar what edge to I<initially> stick to. Can be one of 'top', 'bottom',
891'left' or 'right'. Defaults to 'top'. This option can be set only during object
892creation. Default is 'top'.
893
894=item B<-movable>
895
896This option specifies whether the ToolBar is dockable or not. A dockable ToolBar
897can be dragged around with the mouse to any edge of the window, subject to the
898sticky constraints defined by I<-sticky>. Default is 1.
899
900=item B<-close>
901
902This option specifies, in pixels, how close we have to drag the ToolBar an edge for the
903ToolBar to stick to it. Default is 15.
904
905=item B<-sticky>
906
907This option specifies which sides the toolbar is allowed to stick to. The value
908must be a string of the following characters 'nsew'. A string of 'ns' means that
909the ToolBar can only stick to the north (top) or south (bottom) sides. Defaults to
910'nsew'. This option can be set only during object creation.
911
912=item B<-in>
913
914This option allows the toolbar to be embedded within another already instantiated
915Tk::ToolBar object. The value must be a Tk::ToolBar object. This option can be set
916only during object creation.
917
918=item B<-float>
919
920This option specifies whether the toolbar should "float" on the desktop if
921dragged outside of the window. It defaults to 1. Note that this value is
922ignored if I<-cursorcontrol> is set to 1.
923
924=item B<-cursorcontrol>
925
926This option specifies whether to use Tk::CursorControl to confine the cursor
927during dragging. The value must be either 1 or 0. The default is 1 which
928checks for Tk::CursorControl and uses it if present.
929
930=item B<-mystyle>
931
932This option indicates that you want to control how the ToolBar looks like
933and not rely on Tk::ToolBar's own judgement. The value must be either
9341 or 0. For now, the only thing this controls is the relief of ToolButtons
935and the borderwidth. Defaults to 0.
936
937=item B<-indicatorcolor>
938
939This option controls the color of the visual indicator that tells you
940whether you are close enough to an edge when dragging the ToolBar.
941Defaults to some shade of blue and green (I like it :P).
942
943=item B<-indicatorrelief>
944
945This option controls the relief of the visual indicator that tells you
946whether you are close enough to an edge when dragging the ToolBar.
947Defaults to flat.
948
949=back
950
951=head1 WIDGET METHODS
952
953The following methods are used to create widgets that are placed inside
954the ToolBar. Widgets are ordered in the same order they are created, left to right.
955
956For all widgets, except Labels, a tooltip can be specified via the B<-tip> option.
957An image can be specified using the -image option for Button- and Label-based widgets.
958
959=over 4
960
961=item I<$ToolBar>-E<gt>B<ToolButton>(?-type => I<buttonType>,? I<options>)
962
963=item I<$ToolBar>-E<gt>B<Button>(?-type => I<buttonType>,? I<options>)
964
965This method creates a new Button inside the ToolBar.
966The I<-type> option can be used to specify
967what kind of button to create. Can be one of 'Button', 'Checkbutton', 'Menubutton', or
968'Radiobutton'. A tooltip message can be specified via the -tip option.
969An accelerator binding can be specified using the -accelerator option.
970The value of this option is any legal binding sequence as defined
971in L<bind|Tk::bind>. For example,
972C<-accelerator =E<gt> 'E<lt>fE<gt>'> will invoke the button when the 'f' key is pressed.
973Any other options will be passed directly to the constructor
974of the button. The Button object is returned.
975
976=item I<$ToolBar>-E<gt>B<ToolLabel>(I<options>)
977
978=item I<$ToolBar>-E<gt>B<Label>(I<options>)
979
980This method creates a new Label inside the ToolBar.
981Any options will be passed directly to the constructor
982of the label. The Label object is returned.
983
984=item I<$ToolBar>-E<gt>B<ToolEntry>(I<options>)
985
986=item I<$ToolBar>-E<gt>B<Entry>(I<options>)
987
988This method creates a new Entry inside the ToolBar.
989A tooltip message can be specified via the -tip option.
990Any other options will be passed directly to the constructor
991of the entry. The Entry object is returned.
992
993=item I<$ToolBar>-E<gt>B<ToolLabEntry>(I<options>)
994
995=item I<$ToolBar>-E<gt>B<LabEntry>(I<options>)
996
997This method creates a new LabEntry inside the ToolBar.
998A tooltip message can be specified via the -tip option.
999Any other options will be passed directly to the constructor
1000of the labentry. The LabEntry object is returned.
1001In horizontal ToolBars, the label of the LabEntry widget
1002will be packed to the left of the entry. On vertical
1003ToolBars, the label will be packed on top of the entry.
1004
1005=item I<$ToolBar>-E<gt>B<ToolOptionmenu>(I<options>)
1006
1007=item I<$ToolBar>-E<gt>B<Optionmenu>(I<options>)
1008
1009This method creates a new Optionmenu inside the ToolBar.
1010A tooltip message can be specified via the -tip option.
1011Any other options will be passed directly to the constructor
1012of the Optionmenu. The Optionmenu object is returned.
1013
1014=item I<$ToolBar>-E<gt>B<ToolBrowseEntry>(I<options>)
1015
1016=item I<$ToolBar>-E<gt>B<BrowseEntry>(I<options>)
1017
1018This method creates a new L<Tk::BrowseEntry> inside the ToolBar.
1019A tooltip message can be specified via the -tip option.
1020Any other options will be passed directly to the constructor
1021of the BrowseEntry. The BrowseEntry object is returned.
1022
1023=item I<$ToolBar>-E<gt>B<separator>(?-movable => 0/1, -space => num?)
1024
1025This method inserts a separator. Separators are movable by default.
1026To change that, set the -movable option to 0. If you want to add some
1027space to the left of a separator (or at the top if your ToolBar is
1028vertical), then you can specify the amount of space (in pixels) via
1029the -space option. This can be used to "right-justify" some buttons.
1030
1031=back
1032
1033=head1 IMAGES
1034
1035Tk::ToolBar now comes with a set of useful images that can be used
1036in your Tk programs. To view those images, run the B<widget> program
1037that is bundled with Tk, scroll down to the 'User Contributed
1038Demonstrations', and click on the Tk::ToolBar entry.
1039
1040Note that the images are created using the L<text|Tk::Photo> method. Also,
1041Tk::ToolBar, upon its creation, pre-loads all of the bundled images
1042into memory. This means that those images are available for use in other
1043widgets in your Tk program. This also means that unless those images
1044are explicitly destroyed, they will use up a small amount of memory even
1045if you are not using them explicitly.
1046
1047As far as I know, all the bundled images are in the free domain. If that
1048is not the case, then please let me know.
1049
1050=head1 BUGS
1051
1052Not really a bug, but a feature ;-)
1053The ToolBar widget assumes that you use I<pack> in its parent.
1054Actually, it will I<pack()> itself inside its parent. If you are using
1055another geometry manager, then you I<MIGHT> get some weird behaviour.
1056I have tested it very quickly, and found no surprises, but let me know
1057if you do.
1058
1059Another thing I noticed is that on slower window managers dragging a
1060ToolBar might not go very smoothly, and you can "drop" the ToolBar
1061midway through dragging it. I noticed this on Solaris 7 and 8, running
1062any of OpenLook, CDE or GNOME2 window managers. I would appreciate any
1063reports on different platforms.
1064
1065=head1 TODO
1066
1067I have implemented everything I wanted, and then some.
1068Here are things that were requested, but are not implemented yet.
1069If you want more, send me requests.
1070
1071=over 4
1072
1073=item o Allow buttons to be "tied" to menu items. Somewhat taken care of
1074with the -accelerator method for buttons.
1075
1076=item o Implement Drag-n-Drop to be able to move Tool* widgets interactively.
1077Do we really want this?
1078
1079=back
1080
1081
1082=head1 PREREQUISITES
1083
1084Tk::ToolBar uses only core pTk modules. So you don't need any special
1085prerequisites. But, if Tk::CursorControl is installed on your system,
1086then Tk::ToolBar will use it to confine the cursor to your window when
1087dragging ToolBars (unless you tell it not to).
1088
1089Note also that Tk::CursorControl is defined as a prerequisite in
1090Makefile.PL. So, during installation you might get a warning saying:
1091
1092C<Warning: prerequisite Tk::CursorControl failed to load ...>
1093
1094if you don't have it installed. You can ignore this warning if you
1095don't want to install Tk::CursorControl. Tk::ToolBar will continue
1096to work properly.
1097
1098=head1 INSTALLATION
1099
1100Either the usual:
1101
1102	perl Makefile.PL
1103	make
1104	make install
1105
1106or just stick it somewhere in @INC where perl can find it. It's in pure Perl.
1107
1108=head1 ACKNOWLEDGEMENTS
1109
1110The following people have given me helpful comments and bug reports to keep me busy:
1111Chris Whiting, Jack Dunnigan, Robert Brooks, Peter Lipecka, Martin Thurn and Shahriar Mokhtarzad.
1112
1113Also thanks to the various artists of the KDE team for creating those great icons,
1114and to Adrian Davis for packaging them in a Tk-friendly format.
1115
1116=head1 AUTHOR
1117
1118Ala Qumsieh I<aqumsieh@cpan.org>
1119
1120=head1 LICENSE
1121
1122This module is distributed under the same terms as Perl itself.
1123
1124=cut
1125