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