1# contains: 2# AbstractDocker::Interface 3# SimpleWidgetDocker 4# ClientWidgetDocker 5# LinearWidgetDocker 6# FourPartDocker 7# ExternalDockerShuttle 8# InternalDockerShuttle 9# LinearDockerShuttle 10# SingleLinearWidgetDocker 11 12package Prima::Docks; 13 14use Prima; 15use Prima::RubberBand; 16use strict; 17use warnings; 18use Tie::RefHash; 19 20package Prima::AbstractDocker::Interface; 21 22sub open_session 23{ 24 my ( $self, $profile) = @_; 25 return unless $self-> check_session( $profile); 26 my @mgrs = grep { $_-> isa( 'Prima::AbstractDocker::Interface') } $self-> get_components; 27 if ($self-> {subdockers}) { 28 @{$self-> {subdockers}} = grep { $_-> alive} @{$self-> {subdockers}}; 29 push( @mgrs, @{$self-> {subdockers}}); 30 } 31 return { 32 SUBMGR => \@mgrs, 33 SUBMGR_ID => -1, 34 }; 35} 36 37sub check_session 38{ 39 my $p = $_[1]; 40 return 1 if $$p{CHECKED_OK}; 41 warn("No 'self' given\n"), return 0 unless $$p{self}; 42 for ( qw( sizes)) { 43 warn("No '$_' array specified\n"), return 0 44 if !defined($$p{$_}); 45 } 46 for ( qw( sizes sizeable position sizeMin)) { 47 warn("'$_' is not an array\n"), return 0 48 if defined($$p{$_}) && ( ref($$p{$_}) ne 'ARRAY'); 49 } 50 my $i = 0; 51 for ( @{$$p{sizes}}) { 52 warn("Size #$i is not an valid array"), return 0 if (ref($_) ne 'ARRAY') || ( @$_ != 2); 53 } 54 $$p{sizeable} = [0,0] unless defined $$p{sizeable}; 55 warn("No 'sizes' given, and not sizeable"), return 0 56 if (( 0 == @{$$p{sizes}}) && !$p-> {sizeable}-> [0] &&!$p-> {sizeable}-> [1]); 57 $$p{sizeMin} = [0,0] unless defined $$p{sizeMin}; 58 $$p{position} = [] unless defined $$p{position}; 59 $$p{CHECKED_OK} = 1; 60 return 1; 61} 62 63sub query 64{ 65 my ( $self, $session_id, @rect) = @_; 66 return unless (ref($session_id) eq 'HASH') && 67 exists($session_id-> {SUBMGR}) && exists($session_id-> {SUBMGR_ID}); 68 $session_id-> {SUBMGR_ID} = 0; 69 return $session_id-> {SUBMGR}-> [0]; 70} 71 72sub next_docker 73{ 74 my ( $self, $session_id, $posx, $posy) = @_; 75 return unless (ref($session_id) eq 'HASH') && 76 exists($session_id-> {SUBMGR}) && exists($session_id-> {SUBMGR_ID}); 77 my ( $id, $array) = ( $session_id-> {SUBMGR_ID}, $session_id-> {SUBMGR}); 78 while ( 1) { 79 return if $id < -1 || $id >= scalar(@$array) - 1; 80 $session_id-> {SUBMGR_ID}++; $id++; 81 return $$array[$id] if defined( $$array[$id]) && Prima::Object::alive($$array[$id]); 82 } 83 undef; 84} 85 86sub close_session 87{ 88# my ( $self, $session_id) = @_; 89 undef $_[1]; 90} 91 92 93sub undock 94{ 95 my ( $self, $who) = @_; 96# print $self-> name . "($self): ". $who-> name . " is undocked\n"; 97 return unless $self-> {docklings}; 98 @{$self-> {docklings}} = grep { $who != $_ } @{$self-> {docklings}}; 99} 100 101sub dock 102{ 103 my ( $self, $who) = @_; 104# print $self-> name . "($self): ". $who-> name . " is docked\n"; 105 $self-> {docklings} = [] unless $self-> {docklings}; 106 push ( @{$self-> {docklings}}, $who); 107} 108 109sub dock_bunch 110{ 111 my $self = shift; 112 push ( @{$self-> {docklings}}, @_); 113 $self-> rearrange; 114} 115 116sub docklings 117{ 118 return $_[0]-> {docklings} ? @{$_[0]-> {docklings}} : (); 119} 120 121sub replace 122{ 123 my ( $self, $wijFrom, $wijTo) = @_; 124# print $self-> name . "($self): ". $wijFrom-> name . " is replaced by ". $wijTo-> name ."\n"; 125 for (@{$self-> {docklings}}) { 126 next unless $_ == $wijFrom; 127 $_ = $wijTo; 128 $wijTo-> owner( $wijFrom-> owner) unless $wijTo-> owner == $wijFrom-> owner; 129 $wijTo-> rect( $wijFrom-> rect); 130 last; 131 } 132} 133 134 135sub redock_widget 136{ 137 my ( $self, $wij) = @_; 138 if ( $wij-> can('redock')) { 139 $wij-> redock; 140 } else { 141 my @r = $wij-> owner-> client_to_screen( $wij-> rect); 142 my %prf = ( 143 sizes => [[ $r[2] - $r[0], $r[3] - $r[1]]], 144 sizeable => [0,0], 145 self => $wij, 146 ); 147 my $sid = $self-> open_session( \%prf); 148 return unless defined $sid; 149 my @rc = $self-> query( $sid, @r); 150 $self-> close_session( $sid); 151 if ( 4 == scalar @rc) { 152 if (( $rc[2] - $rc[0] == $r[2] - $r[0]) && ( $rc[3] - $rc[1] == $r[3] - $r[1])) { 153 my @rx = $wij-> owner-> screen_to_client( @rc[0,1]); 154 $wij-> origin( $wij-> owner-> screen_to_client( @rc[0,1])) 155 if $rc[0] != $r[0] || $rc[1] != $r[1]; 156 } else { 157 $wij-> rect( $wij-> owner-> screen_to_client( @rc)); 158 } 159 $self-> undock( $wij); 160 $self-> dock( $wij); 161 } 162 } 163} 164 165sub rearrange 166{ 167 my $self = $_[0]; 168 return unless $self-> {docklings}; 169 my @r = @{$self-> {docklings}}; 170 @{$self-> {docklings}} = (); 171 $self-> redock_widget($_) for @r; 172} 173 174sub fingerprint { 175 return exists($_[0]-> {fingerprint})?$_[0]-> {fingerprint}:0xFFFF unless $#_; 176 $_[0]-> {fingerprint} = $_[1]; 177} 178 179sub add_subdocker 180{ 181 my ( $self, $subdocker) = @_; 182 push( @{$self-> {subdockers}}, $subdocker); 183} 184 185sub remove_subdocker 186{ 187 my ( $self, $subdocker) = @_; 188 return unless $self-> {subdockers}; 189 @{$self-> {subdockers}} = grep { $_ != $subdocker} @{$self-> {subdockers}}; 190} 191 192sub dockup 193{ 194 return $_[0]-> {dockup} unless $#_; 195 $_[0]-> {dockup}-> remove_subdocker( $_[0]) if $_[0]-> {dockup}; 196 $_[1]-> add_subdocker( $_[0]) if $_[1]; 197} 198 199package Prima::SimpleWidgetDocker; 200use vars qw(@ISA); 201@ISA = qw(Prima::Widget Prima::AbstractDocker::Interface); 202 203sub profile_default 204{ 205 my $def = $_[0]-> SUPER::profile_default; 206 my %prf = ( 207 fingerprint => 0x0000FFFF, 208 dockup => undef, 209 ); 210 @$def{keys %prf} = values %prf; 211 return $def; 212} 213 214sub init 215{ 216 my $self = shift; 217 my %profile = $self-> SUPER::init( @_); 218 $self-> $_( $profile{$_}) for ( qw(fingerprint dockup)); 219 return %profile; 220} 221 222 223sub open_session 224{ 225 my ( $self, $profile) = @_; 226 return unless $self-> enabled && $self-> showing; 227 return unless $self-> check_session( $profile); 228 229 my @sz = $self-> size; 230 my @asz; 231 my @able = @{$profile-> {sizeable}}; 232 my @minSz = @{$profile-> {sizeMin}}; 233 for ( @{$profile-> {sizes}}) { 234 my @xsz = @$_; 235 for ( 0, 1) { 236 next if ( $xsz[$_] >= $sz[$_]) && !$able[$_]; 237 next if $sz[$_] < $minSz[$_]; 238 $asz[$_] = $xsz[$_]; 239 } 240 } 241 242 return if !defined($asz[0]) || !defined($asz[1]); 243 244 my @offs = $self-> client_to_screen(0,0); 245 return { 246 minpos => \@offs, 247 maxpos => [ $offs[0] + $sz[0] - $asz[0] - 0, $offs[1] + $sz[1] - $asz[1] - 0,], 248 size => \@asz, 249 }; 250} 251 252sub query 253{ 254 my ( $self, $p, @rect) = @_; 255 my @npx; 256 my @pos = @rect[0,1]; 257 if ( scalar @rect) { 258 @npx = @pos; 259 for ( 0, 1) { 260 $npx[$_] = $$p{minpos}-> [$_] if $npx[$_] < $$p{minpos}-> [$_]; 261 $npx[$_] = $$p{maxpos}-> [$_] if $npx[$_] >= $$p{maxpos}-> [$_]; 262 } 263 } else { 264 @npx = @{$$p{minpos}}; 265 } 266 return @npx[0,1], $$p{size}-> [0] + $npx[0], $$p{size}-> [1] + $npx[1]; 267} 268 269package Prima::ClientWidgetDocker; 270use vars qw(@ISA); 271@ISA = qw(Prima::SimpleWidgetDocker); 272 273sub open_session 274{ 275 my ( $self, $profile) = @_; 276 return unless $self-> enabled && $self-> showing; 277 return unless $self-> check_session( $profile); 278 279 my @sz = $self-> size; 280 my @asz; 281 my @able = @{$profile-> {sizeable}}; 282 my @minSz = @{$profile-> {sizeMin}}; 283 for ( @{$profile-> {sizes}}) { 284 my @xsz = @$_; 285 for ( 0, 1) { 286 next if ( $xsz[$_] != $sz[$_]) && !$able[$_]; 287 next if $sz[$_] < $minSz[$_]; 288 $asz[$_] = $sz[$_]; 289 } 290 } 291 292 return if !defined($asz[0]) || !defined($asz[1]); 293 294 my @offs = $self-> client_to_screen(0,0); 295 return { 296 retval => [@offs, $offs[0] + $sz[0], $offs[1] + $sz[1]], 297 }; 298} 299 300sub query { return @{$_[1]-> {retval}}} 301 302sub on_paint 303{ 304 my ( $self, $canvas) = @_; 305 my @sz = $self-> size; 306 $canvas-> clear( 1, 1, $sz[0]-2, $sz[1]-2); 307 $canvas-> rect3d( 0, 0, $sz[0]-1, $sz[1]-1, 1, $self-> dark3DColor, $self-> light3DColor); 308} 309 310package 311 grow; 312# direct, ::vertical-independent 313use constant ForwardLeft => 0x01; 314use constant ForwardDown => 0x02; 315use constant ForwardRight => 0x04; 316use constant ForwardUp => 0x08; 317use constant BackLeft => 0x10; 318use constant BackDown => 0x20; 319use constant BackRight => 0x40; 320use constant BackUp => 0x80; 321use constant Left => ForwardLeft | BackLeft; 322use constant Down => ForwardDown | BackDown; 323use constant Right => ForwardRight| BackRight; 324use constant Up => ForwardUp | BackUp; 325 326# indirect, ::vertical-dependent 327use constant ForwardMajorLess => 0x0100; 328use constant ForwardMajorMore => 0x0200; 329use constant ForwardMinorLess => 0x0400; 330use constant ForwardMinorMore => 0x0800; 331use constant BackMajorLess => 0x1000; 332use constant BackMajorMore => 0x2000; 333use constant BackMinorLess => 0x4000; 334use constant BackMinorMore => 0x8000; 335use constant MajorLess => ForwardMajorLess | BackMajorLess; 336use constant MajorMore => ForwardMajorMore | BackMajorMore; 337use constant MinorLess => ForwardMinorLess | BackMinorLess; 338use constant MinorMore => ForwardMinorMore | BackMinorMore; 339 340# masks 341use constant Forward => 0x0F0F; 342use constant Back => 0xF0F0; 343 344package Prima::LinearWidgetDocker; 345use vars qw(@ISA); 346@ISA = qw(Prima::Widget Prima::AbstractDocker::Interface); 347 348sub profile_default 349{ 350 my $def = $_[0]-> SUPER::profile_default; 351 my %prf = ( 352 dockup => undef, 353 vertical => 0, 354 growable => 0, # grow::XXXX 355 hasPocket => 1, 356 fingerprint => 0x0000FFFF 357 ); 358 @$def{keys %prf} = values %prf; 359 return $def; 360} 361 362{ 363my %RNT = ( 364 %{Prima::Widget-> notification_types()}, 365 Dock => nt::Notification, 366 Undock => nt::Notification, 367 DockError => nt::Action, 368); 369 370sub notification_types { return \%RNT; } 371} 372 373sub init 374{ 375 my $self = shift; 376 $self-> {$_} = 0 for qw(growable vertical hasPocket fingerprint dockup); 377 my %profile = $self-> SUPER::init( @_); 378 $self-> $_( $profile{$_}) for ( qw( fingerprint growable hasPocket vertical dockup)); 379 return %profile; 380} 381 382sub vertical 383{ 384 return $_[0]-> {vertical} unless $#_; 385 my ( $self, $v) = @_; 386 $self-> {vertical} = $v; 387} 388 389sub hasPocket 390{ 391 return $_[0]-> {hasPocket} unless $#_; 392 my ( $self, $v) = @_; 393 $self-> {hasPocket} = $v; 394} 395 396 397sub growable 398{ 399 return $_[0]-> {growable} unless $#_; 400 my ( $self, $g) = @_; 401 $self-> {growable} = $g; 402} 403 404sub __docklings 405{ 406 my ( $self, $exclude) = @_; 407 my %hmap; 408 my $xid = $self-> {vertical} ? 0 : 1; # minor axis, further 'vertical' 409 my $yid = $self-> {vertical} ? 1 : 0; # major axis, further 'horizontal 410 my $min; 411 for ( @{$self-> {docklings}}) { 412 next if $_ == $exclude; # if redocking to another position, for example 413 my @rt = $_-> rect; 414 $hmap{$rt[$xid]} = [0,0,0,[],[]] unless $hmap{$rt[$xid]}; 415 my $sz = $rt[$xid+2] - $rt[$xid]; 416 my $xm = $hmap{$rt[$xid]}; 417 $min = $rt[$xid] if !defined($min) || $min > $rt[$xid]; 418 $$xm[0] = $sz if $$xm[0] < $sz; # max vert extension 419 $$xm[1] += $rt[$yid + 2] - $rt[$yid]; # total length occupied 420 $$xm[2] = $rt[$yid+2] if $rt[$yid+2] > $$xm[2]; # farthest border 421 push( @{$$xm[4]}, $_); # widget 422 } 423 424 # checking widgets 425 my @ske = sort { $a <=> $b }keys %hmap; 426 my @sz = $self-> size; 427 my $i; 428 for ( $i = 0; $i < @ske - 1; $i++) { 429 my $ext = $hmap{$ske[$i]}-> [0]; 430 if ( $ext + $ske[$i] < $ske[$i+1]) { # some gap here 431 $hmap{$ext + $ske[$i]} = [$ske[$i+1] - $ske[$i] - $ext, 0, 0, [], []]; 432 @ske = sort { $a <=> $b }keys %hmap; 433 } 434 } 435 if ( @ske) { 436 my $ext = $hmap{$ske[-1]}-> [0]; # last row 437 $hmap{$ext + $ske[-1]} = [$sz[$xid] - $ske[-1] - $ext, 0, 0, [], []]; 438 } else { 439 $hmap{0} = [ $sz[$xid], 0, 0, [], []]; 440 } 441 $hmap{0} = [ $min, 0, 0, [], []] unless $hmap{0}; 442# hmap structure: 443# 0 - max vert extension 444# 1 - total length occupied 445# 2 - farther border by major axis 446# 3 - array of accepted sizes 447# 4 - widget list 448 return \%hmap; 449} 450 451sub read_growable 452{ 453 my ($self,$directionMask) = @_; 454 my $g = $self-> {growable} & $directionMask; 455 my $xid = $self-> {vertical} ? 0 : 1; 456 my $gMaxG = ( $g & grow::MajorMore) || ($g & ($xid ? grow::Right : grow::Up)); 457 my $gMaxL = ( $g & grow::MajorLess) || ($g & ($xid ? grow::Left : grow::Down)); 458 my $gMinG = ( $g & grow::MinorMore) || ($g & ($xid ? grow::Up : grow::Right)); 459 my $gMinL = ( $g & grow::MinorLess) || ($g & ($xid ? grow::Down : grow::Left)); 460 461 return ( $gMaxG, $gMaxL, $gMinG, $gMinL); 462} 463 464sub open_session 465{ 466 my ( $self, $profile) = @_; 467 return unless $self-> enabled && $self-> visible; 468 return unless $self-> check_session( $profile); 469 470 my @sz = $self-> size; 471 my @msz = $self-> sizeMax; 472 my $xid = $self-> {vertical} ? 0 : 1; # minor axis, further 'vertical' 473 my $yid = $self-> {vertical} ? 1 : 0; # major axis, further 'horizontal' 474 my ( $gMaxG, $gMaxL, $gMinG, $gMinL) = $self-> read_growable( grow::Forward); 475 my %hmap = %{$self-> __docklings( $$profile{self})}; 476 477 # calculating row extension 478 my $rows = scalar keys %hmap; 479 my $majExt = ( $gMaxG || $gMaxL) ? $msz[ $yid] : $sz[ $yid]; 480 my $minExt = ( $gMinG || $gMinL) ? $msz[ $xid] : $sz[ $xid]; 481 482 push( @{$$profile{sizes}}, [ @sz]) unless @{$$profile{sizes}}; 483 484 # total vertical occupied size 485 my ( $gap, $vo) = (0, 0); 486 for ( keys %hmap) { 487 $hmap{$_}-> [1] ? 488 ( $vo += $hmap{$_}-> [0]) : 489 ( $gap += $hmap{$_}-> [0]) ; 490 } 491 492 # put acceptable set of sizes for every line 493 my @minSz = @{$$profile{sizeMin}}; 494 for ( keys %hmap) { 495 my ( $y, $ext, $total, $border, $array) = ( $_, @{$hmap{$_}}); 496 for ( @{$$profile{sizes}}) { 497 my @asz = @$_; 498 #print "@asz:ext:$ext, minext:$minExt, vo:$vo\n"; 499 #print "row $y:($total $majExt)"; 500 #if ( $asz[$xid] > $minExt - $vo) { 501 if (( $asz[$xid] > $ext) && ($asz[$xid] > $minExt - $vo)) { 502 next unless $profile-> {sizeable}-> [$xid]; 503 my $n_ext = $minExt - $vo; 504 next if $n_ext < $minSz[$xid] && $n_ext < $asz[$xid]; 505 $asz[$xid] = $n_ext; 506 } 507 #print "step1 $y :@asz|$ext $total $border = $majExt\n"; 508 if ($total + $asz[$yid] > $majExt) { 509 if ( !$self-> {hasPocket} || ( $border >= $majExt)) { 510 next unless $profile-> {sizeable}-> [$yid]; 511 my $nb = ( $self-> {hasPocket} ? $border : $majExt) - $total; 512 #print "3: $nb $yid\n"; 513 next if $nb < $minSz[$yid] && $nb < $asz[$yid]; 514 $asz[$yid] = $nb; 515 } 516 } 517 # print "@$_:@asz\n"; 518 push ( @$array, \@asz); 519 } 520 # print "$_(" . scalar(@{$hmap{$_}->[4]}). ')'; 521 } 522 523 # add decrement line 524 if ( $vo) { 525 # print " and - "; 526 for (@{$$profile{sizes}}) { 527 my @asz = @$_; 528 next if $hmap{- $asz[$xid]}; 529 next if $asz[$xid] > $minExt - $vo; 530 $hmap{ - $asz[$xid]} = [ $asz[$xid], 0, 0, [\@asz], []]; 531 # print "|$asz[$xid] "; 532 } 533 } 534 # print "\n"; 535 536 537 # sort out accepted sizes by 'verticalness' 538 for ( keys %hmap) { 539 my $s = $hmap{$_}-> [3]; 540 @$s = map { 541 [$$_[0], $$_[1]] # remove ratio field 542 } sort { 543 $$a[2] <=> $$b[2] # sort by xid/yid ratio 544 } map { 545 [@$_, $$_[$xid] / ($$_[$yid]||1)] # calculate xid/yid ratio 546 } @$s; 547 } 548 return { 549 offs => [ $self-> client_to_screen(0,0)], 550 size => \@sz, 551 sizeMax => \@msz, 552 hmap => \%hmap, 553 rows => scalar keys %hmap, 554 vmap => [ sort { $a <=> $b } keys %hmap], 555 sizes => [ sort { $$a[2] <=> $$b[2]} map { [ @$_, $$_[$yid] / ($$_[$xid]||1)]} @{$$profile{sizes}}], 556 sizeable => $$profile{sizeable}, 557 sizeMin => $$profile{sizeMin}, 558 grow => [ $gMinG, $gMinL, $gMaxG, $gMaxL], 559 }; 560} 561 562sub query 563{ 564 my ( $self, $p, @rect) = @_; 565 my $xid = $self-> {vertical} ? 0 : 1; 566 my $yid = $self-> {vertical} ? 1 : 0; 567 my @asz; 568 my @offs = @{$p-> {offs}}; 569 my $hmap = $$p{hmap}; 570 my $vmap = $$p{vmap}; 571 my ( $i, $closest, $idx, $side); 572 my $rows = $$p{rows}; 573 my $useSZ = 1; 574 575 $useSZ = 0, @rect = ( 0, 0, 1, 1) unless scalar @rect; 576 my %skip = (); 577AGAIN: 578 $i = 0; $idx = undef; 579 for ( $i = 0; $i < $rows; $i++) { 580 next if $skip{$$vmap[$i]}; 581 my $dist = ( $rect[ $xid] - ( $offs[$xid] + $$vmap[$i])); 582 $dist *= $dist; 583 $side = 0, $idx = $$vmap[$i], $closest = $dist if !defined($idx) || ( $closest > $dist); 584 if ( $$vmap[$i] == 0 && !$$p{noDownSide}) { 585 $dist = ( $rect[ $xid + 2] - ( $offs[$xid] + $$vmap[$i])); 586 $dist *= $dist; 587 $side = 1, $idx = $$vmap[$i], $closest = $dist if $closest > $dist; 588 } 589 } 590 return unless defined $idx; 591 if ( @{$hmap-> {$idx}-> [3]}) { 592 @asz = @{$hmap-> {$idx}-> [3]-> [0]}; 593 } else { 594 # print "$idx rejected\n"; 595 $skip{$idx} = 1; 596 goto AGAIN; 597 } 598 599 @rect = ( 0, 0, @asz) unless $useSZ; 600 $idx -= $rect[$xid+2] - $rect[$xid] if $side; 601 if ( $rect[$yid] < $offs[$yid]) { 602# $asz[$yid] -= $offs[$yid] - $rect[$yid] if $$p{sizeable}->[$yid]; 603 $rect[$yid] = $offs[$yid]; 604 } 605 my $sk = ( $p-> {sizeMin}-> [$yid] > $asz[$yid]) ? $asz[$yid] : $p-> {sizeMin}-> [$yid]; 606 $rect[ $yid] = $offs[$yid] + $p-> {size}-> [$yid] - $sk if 607 $rect[$yid] > $offs[$yid] + $p-> {size}-> [$yid] - $sk; 608# unless ( $self-> {vertical}) { 609#my @r = ( $rect[0], $idx + $offs[1], $rect[0] + $asz[0], $idx + $offs[1] + $asz[1]); 610#print "q :@r\n"; 611# } 612 return $self-> {vertical} ? 613 ( $idx + $offs[0], $rect[1], $idx + $offs[0] + $asz[0], $rect[1] + $asz[1]) : 614 ( $rect[0], $idx + $offs[1], $rect[0] + $asz[0], $idx + $offs[1] + $asz[1]); 615} 616 617sub dock 618{ 619 my ( $self, $who) = @_; 620 $self-> SUPER::dock( $who); 621 my $xid = $self-> {vertical} ? 0 : 1; 622 my $yid = $self-> {vertical} ? 1 : 0; 623 my @rt = $who-> rect; 624 my @sz = $self-> size; 625 my $hmap = $self-> __docklings( $who); 626 my ( $gMaxG, $gMaxL, $gMinG, $gMinL) = $self-> read_growable( grow::Forward); 627 628 # for ( keys %$hmap) { print "hmap:$_\n"; } 629 630 if ( !exists $hmap-> {$rt[$xid]}) { 631 if ( $rt[$xid] >= 0 || $rt[$xid+2] != 0) { 632 $self-> notify(q(DockError), $who); 633 return; 634 } 635 $hmap-> {$rt[$xid]} = [$rt[$xid+2]-$rt[$xid], 0, 0, [], [], 0]; 636 } 637 638 # minor axis 639 my $doMajor = $hmap-> {$rt[$xid]}-> [1]; 640 641 my $gap = 0; 642 for ( keys %$hmap) { 643 next if $_ < 0 || $hmap-> {$_}-> [1]; 644 $gap += $hmap-> {$_}-> [0]; 645 } 646 647# print "key : $rt[$xid] $rt[$xid+2]\n"; 648 my $maxY = $hmap-> {$rt[$xid]}-> [1] ? $hmap-> {$rt[$xid]}-> [0] : 0; 649 #my $tail = $rt[$xid+2] - $rt[$xid] - $hmap->{$rt[$xid]}->[0]; 650 my $tail = $rt[$xid+2] - $rt[$xid] - $maxY; 651 #print "$self:tail:$tail $maxY @rt\n"; 652 if ( $tail > 0 || $rt[$xid] < 0) { 653 my @fmp = sort { $a <=> $b} keys %$hmap; 654 my $prop = $self-> {vertical} ? 'left' : 'bottom'; 655 my $last = 0; 656 for ( @fmp) { 657 my @rp = @{$hmap-> {$_}-> [4]}; 658 my $ht = $hmap-> {$_}-> [0]; 659 if ( $_ == $rt[$xid]) { 660 push ( @rp, $who); 661 $ht = $rt[$xid+2] - $rt[$xid] if $ht < $rt[$xid+2] - $rt[$xid]; 662 } 663 next unless scalar @rp; 664 $_-> $prop( $last) for @rp; 665 $last += $ht; 666 # print "adde $hmap->{$_}->[0]\n"; 667 } 668 $tail = ($last > $sz[$xid]) ? ( $last - $sz[$xid]) : 0; 669 @rt = $who-> rect; 670 # print "last:$last, tail:$tail\n"; 671 } else { 672 $tail = 0; 673 } 674 675 if ( $tail) { 676 if ( $gMinG) { 677 $sz[$xid] += $tail; 678 $self-> size( @sz); 679 } else { 680 my @rect = $self-> rect; 681 $rect[ $xid] -= $tail; 682 $self-> rect( @rect); 683 } 684 @sz = $self-> size; 685 } 686 687 # major axis 688 689 unless ( $self-> {hasPocket}) { 690 my @o = @rt[0,1]; 691 $o[$yid] = $sz[$yid] - $rt[$yid+2] + $rt[$yid] if $rt[$yid+2] > $sz[$yid]; 692 $o[$yid] = 0 if $o[$yid] < 0; 693 # print "@o:@rt\n"; 694 $who-> origin( @o) if $o[$yid] != $rt[$yid]; 695 @rt[0,1] = @o; 696 } 697 698 my @fmp; 699 my $edge = 0; 700 for ( $who, @{$hmap-> {$rt[$xid]}-> [4]}) { 701 my @rxt = $_-> rect; 702 push ( @fmp, [ $_, $rxt[ $yid], $rxt[ $yid + 2] - $rxt[ $yid]]); 703 $edge = $rxt[$yid+2] if $edge < $rxt[$yid+2]; 704 } 705 706 if ( $doMajor) { 707 @fmp = sort { $$a[1] <=> $$b[1]} @fmp; 708 my $prop = $self-> {vertical} ? 'bottom' : 'left'; 709 my $overlap; 710 my $last = 0; 711 for ( @fmp) { 712 $overlap = 1, last if $$_[1] < $last; 713 $last = $$_[1] + $$_[2]; 714 } 715 if ( $overlap) { 716 $last = 0; 717 my $i = 0; 718 my @sizeMax = $self-> sizeMax; 719 my @msz = ( $gMaxG || $gMaxL) ? @sizeMax : @sz; 720 my $stage = 0; 721 for ( @fmp) { 722 $$_[1] = $last; 723 $$_[0]-> $prop( $last); 724 $last += $$_[2]; 725 $i++; 726 } 727 $edge = $last; 728 } 729 } 730 731 if ( $edge > $sz[$yid] && ($gMaxL || $gMaxG)) { 732 if ( $gMaxG) { 733 $sz[$yid] = $edge; 734 $self-> size( @sz); 735 } else { 736 my @r = $self-> rect; 737 $r[$yid] -= $edge - $sz[$yid]; 738 $self-> rect( @r); 739 } 740 @sz = $self-> size; 741 } 742 743 # redocking non-fit widgets 744 my $stage = 0; 745 my @repush; 746 for ( @fmp) { 747 if ( $self-> {hasPocket}) { 748 next if $$_[1] <= $sz[$yid] - 5; 749 $stage = 1, next unless $stage; 750 } else { 751 next if $$_[1] + $$_[2] <= $sz[$yid]; 752 } 753 push( @repush, $$_[0]); 754 } 755 $self-> redock_widget($_) for @repush; 756 757 $self-> notify(q(Dock), $who); 758} 759 760sub undock 761{ 762 my ( $self, $who) = @_; 763 $self-> SUPER::undock( $who); 764 my $xid = $self-> {vertical} ? 0 : 1; 765 my $yid = $self-> {vertical} ? 1 : 0; 766 my @rt = $who-> rect; 767 my @sz = $self-> size; 768 my $hmap = $self-> __docklings( $who); 769 my ( $gMaxG, $gMaxL, $gMinG, $gMinL) = $self-> read_growable( grow::Back); 770 771# collapsing minor axis 772 my $xd = $rt[$xid+2] - $rt[$xid]; 773 if (( !$hmap-> {$rt[$xid]}-> [1] || ($hmap-> {$rt[$xid]}-> [0] < $xd)) && ( $gMinG || $gMinL)) { 774 my $d = $xd - ( $hmap-> {$rt[$xid]}-> [1] ? $hmap-> {$rt[$xid]}-> [0] : 0); 775 my @asz = @sz; 776 $asz[$xid] -= $d; 777 $self-> size( @asz); 778 @sz = $self-> size; 779 my $prop = $self-> {vertical} ? 'left' : 'bottom'; 780 for ( keys %$hmap) { 781 next if $_ <= $rt[$xid]; 782 $_-> $prop( $_-> $prop() - $d) for @{$hmap-> {$_}-> [4]}; 783 } 784 if ( $gMinL) { 785 my @o = $self-> origin; 786 $o[$xid] += $d; 787 $self-> origin( @o); 788 } 789 } 790# collapsing major axis 791 my @fmp; 792 my $adjacent; 793 for ( @{$hmap-> {$rt[$xid]}-> [4]}) { 794 my @rxt = $_-> rect; 795 next if $rxt[$yid] < $rt[$yid]; 796 push( @fmp, $_); 797 $adjacent = 1 if $rxt[$yid] == $rt[$yid + 2]; 798 } 799 if ( $adjacent) { 800 my $d = $rt[$yid+2] - $rt[$yid]; 801 my $prop = $self-> {vertical} ? 'bottom' : 'left'; 802 $_-> $prop( $_-> $prop() - $d) for @fmp; 803 } 804 805 if ( $gMaxG || $gMaxL) { 806 my $edge = 0; 807 for ( keys %$hmap) { 808 for ( @{$hmap-> {$_}-> [4]}) { 809 my @rxt = $_-> rect; 810 $edge = $rxt[$yid+2] if $edge < $rxt[$yid+2]; 811 } 812 } 813 if ( $edge < $sz[$yid]) { 814 if ( $gMaxG) { 815 $sz[$yid] = $edge; 816 $self-> size( @sz); 817 } else { 818 my @r = $self-> rect; 819 $r[$yid] += $edge - $sz[$yid]; 820 $self-> rect( @r); 821 } 822 } 823 } 824 825 $self-> notify(q(Undock), $who); 826} 827 828sub on_dockerror 829{ 830 my ( $self, $urchin) = @_; 831 my @rt = $urchin-> rect; 832 my $xid = $self-> {vertical} ? 0 : 1; 833 warn "The widget $urchin didn't follow docking conventions. Info: $rt[$xid] $rt[$xid+2]\n"; 834 $self-> redock_widget( $urchin); 835} 836 837package Prima::SingleLinearWidgetDocker; 838use vars qw(@ISA); 839@ISA = qw(Prima::LinearWidgetDocker); 840 841sub profile_default 842{ 843 my $def = $_[0]-> SUPER::profile_default; 844 my %prf = ( 845 growMode => gm::Client, 846 hasPocket => 0, 847 growable => grow::MajorMore, 848 ); 849 @$def{keys %prf} = values %prf; 850 return $def; 851} 852 853sub open_session 854{ 855 my ( $self, $profile) = @_; 856 my $res = $self-> SUPER::open_session( $profile); 857 return unless $res; 858# keep only one row of docklings 859 my %hmap = %{$res-> {hmap}}; 860 my @k = keys %hmap; 861 for ( @k) { 862 delete $hmap{$_} if $_ != 0; 863 } 864 $res-> {noDownSide} = 1; 865 return $res if scalar(keys %hmap) == scalar(@k); 866 $res-> {hmap} = \%hmap; 867 $res-> {rows} = scalar keys %hmap; 868 $res-> {vmap} = [sort { $a <=> $b } keys %hmap]; 869 return $res; 870} 871 872package Prima::FourPartDocker; 873use vars qw(@ISA); 874@ISA = qw(Prima::Widget Prima::AbstractDocker::Interface); 875 876sub profile_default 877{ 878 my $def = $_[0]-> SUPER::profile_default; 879 my %prf = ( 880 indents => [ 0, 0, 0, 0], 881 growMode => gm::Client, 882 dockup => undef, 883 fingerprint => 0x0000FFFF, 884 dockerClassLeft => 'Prima::LinearWidgetDocker', 885 dockerClassRight => 'Prima::LinearWidgetDocker', 886 dockerClassTop => 'Prima::LinearWidgetDocker', 887 dockerClassBottom => 'Prima::LinearWidgetDocker', 888 dockerClassClient => 'Prima::ClientWidgetDocker', 889 dockerProfileLeft => {}, 890 dockerProfileRight => {}, 891 dockerProfileTop => {}, 892 dockerProfileBottom => {}, 893 dockerProfileClient => {}, 894 dockerDelegationsLeft => [qw(Size)], 895 dockerDelegationsRight => [qw(Size)], 896 dockerDelegationsTop => [qw(Size)], 897 dockerDelegationsBottom => [qw(Size)], 898 dockerDelegationsClient => [], 899 dockerCommonProfile => {}, 900 ); 901 @$def{keys %prf} = values %prf; 902 return $def; 903} 904 905sub profile_check_in 906{ 907 my ( $self, $p, $default) = @_; 908 $self-> SUPER::profile_check_in( $p, $default); 909 for ( qw( Left Right Top Bottom)) { 910 my $x = "dockerDelegations$_"; 911 # append user-specified delegations - it may not be known beforehand 912 # which delegations we are using internally 913 next unless exists $p-> {$x}; 914 splice( @{$p-> {$x}}, scalar(@{$p-> {$x}}), 0, @{$default-> {$x}}); 915 } 916} 917 918sub init 919{ 920 my $self = shift; 921 my %profile = $self-> SUPER::init( @_); 922 $self-> $_( $profile{$_}) for ( qw( dockup indents fingerprint)); 923 my @sz = $self-> size; 924 my @i = @{$self-> indents}; 925 $self-> insert([ $profile{dockerClassLeft} => 926 origin => [ 0, $i[1]], 927 size => [ $i[0], $sz[1] - $i[3] - $i[1]], 928 vertical => 1, 929 growable => grow::Right, 930 growMode => gm::GrowHiY, 931 name => 'LeftDocker', 932 delegations => $profile{dockerDelegationsLeft}, 933 %{$profile{dockerProfileLeft}}, 934 %{$profile{dockerCommonProfile}}, 935 ], [ $profile{dockerClassRight} => 936 origin => [ $sz[0] - $i[2], $i[1]], 937 size => [ $i[2], $sz[1] - $i[3] - $i[1]], 938 vertical => 1, 939 growable => grow::Left, 940 growMode => gm::GrowHiY|gm::GrowLoX, 941 name => 'RightDocker', 942 delegations => $profile{dockerDelegationsRight}, 943 %{$profile{dockerProfileRight}}, 944 %{$profile{dockerCommonProfile}}, 945 ], [ $profile{dockerClassTop} => 946 origin => [ 0, $sz[1] - $i[3]], 947 size => [ $sz[0], $i[3]], 948 vertical => 0, 949 growable => grow::Down, 950 growMode => gm::GrowLoY|gm::GrowHiX, 951 name => 'TopDocker', 952 delegations => $profile{dockerDelegationsTop}, 953 %{$profile{dockerProfileTop}}, 954 %{$profile{dockerCommonProfile}}, 955 ], [ $profile{dockerClassBottom} => 956 origin => [ 0, 0], 957 size => [ $sz[0], $i[1]], 958 vertical => 0, 959 growable => grow::Up, 960 growMode => gm::GrowHiX, 961 name => 'BottomDocker', 962 delegations => $profile{dockerDelegationsBottom}, 963 %{$profile{dockerProfileBottom}}, 964 %{$profile{dockerCommonProfile}}, 965 ], [ $profile{dockerClassClient} => 966 origin => [ @i[0,1]], 967 size => [ $sz[0]-$i[2], $sz[1]-$i[3]], 968 growMode => gm::Client, 969 name => 'ClientDocker', 970 delegations => $profile{dockerDelegationsClient}, 971 %{$profile{dockerProfileClient}}, 972 %{$profile{dockerCommonProfile}}, 973 ]); 974 return %profile; 975} 976 977sub indents 978{ 979 return $_[0]-> {indents} unless $#_; 980 my @i = @{$_[1]}; 981 for ( @i) { 982 $_ = 0 if $_ < 0; 983 } 984 return unless 4 == @i; 985 $_[0]-> {indents} = \@i; 986} 987 988sub LeftDocker_Size 989{ 990 my ( $self, $dock, $ox, $oy, $x, $y) = @_; 991 return if $self-> {indents}-> [0] == $x; 992 return unless $self-> can_event; 993 $self-> {indents}-> [0] = $x; 994 $self-> ClientDocker-> set( 995 left => $x, 996 right => $self-> ClientDocker-> right, 997 ); 998 $self-> repaint; 999} 1000 1001sub RightDocker_Size 1002{ 1003 my ( $self, $dock, $ox, $oy, $x, $y) = @_; 1004 return if $self-> {indents}-> [2] == $x; 1005 return unless $self-> can_event; 1006 $self-> {indents}-> [2] = $x; 1007 $self-> ClientDocker-> width( $self-> width - $x - $self-> {indents}-> [0]); 1008 $self-> repaint; 1009} 1010 1011sub TopDocker_Size 1012{ 1013 my ( $self, $dock, $ox, $oy, $x, $y) = @_; 1014 return if $self-> {indents}-> [3] == $y; 1015 return unless $self-> can_event; 1016 $self-> {indents}-> [3] = $y; 1017 my $h = $self-> height - $y - $self-> {indents}-> [1]; 1018 1019 $self-> LeftDocker-> height( $h); 1020 $self-> RightDocker-> height( $h); 1021 $self-> ClientDocker-> height( $h); 1022 $self-> repaint; 1023} 1024 1025sub BottomDocker_Size 1026{ 1027 my ( $self, $dock, $ox, $oy, $x, $y) = @_; 1028 return if $self-> {indents}-> [1] == $y; 1029 return unless $self-> can_event; 1030 $self-> {indents}-> [1] = $y; 1031 my $h = $self-> height; 1032 $self-> LeftDocker-> height( $h - $y - $self-> {indents}-> [3]); 1033 $self-> LeftDocker-> bottom( $self-> {indents}-> [1]); 1034 $self-> RightDocker-> height( $h - $y - $self-> {indents}-> [3]); 1035 $self-> RightDocker-> bottom( $self-> {indents}-> [1]); 1036 $self-> ClientDocker-> set( 1037 bottom => $y, 1038 top => $self-> ClientDocker-> top, 1039 ); 1040 $self-> repaint; 1041} 1042 1043package Prima::InternalDockerShuttle; 1044use vars qw(@ISA); 1045@ISA = qw(Prima::Widget); 1046 1047{ 1048my %RNT = ( 1049 %{Prima::Widget-> notification_types()}, 1050 GetCaps => nt::Command, 1051 Landing => nt::Request, 1052 Dock => nt::Notification, 1053 Undock => nt::Notification, 1054 FailDock => nt::Notification, 1055 EDSClose => nt::Command, 1056); 1057 1058sub notification_types { return \%RNT; } 1059} 1060 1061sub profile_default 1062{ 1063 my $def = $_[ 0]-> SUPER::profile_default; 1064 my %prf = ( 1065 externalDockerClass => 'Prima::ExternalDockerShuttle', 1066 externalDockerModule => 'Prima::MDI', 1067 externalDockerProfile => {}, 1068 dockingRoot => undef, 1069 dock => undef, 1070 snapDistance => 10, # undef for none 1071 indents => [ 5, 5, 5, 5], 1072 x_sizeable => 0, 1073 y_sizeable => 0, 1074 fingerprint => 0x0000FFFF, 1075 ); 1076 @$def{keys %prf} = values %prf; 1077 return $def; 1078} 1079 1080sub init 1081{ 1082 my $self = shift; 1083 my %profile = $self-> SUPER::init( @_); 1084 $self-> $_( $profile{$_}) for ( qw( indents x_sizeable y_sizeable 1085 externalDockerClass externalDockerModule externalDockerProfile fingerprint 1086 dockingRoot snapDistance)); 1087 $self-> {__dock__} = $profile{dock}; 1088 return %profile; 1089} 1090 1091 1092sub setup 1093{ 1094 $_[0]-> SUPER::setup; 1095 $_[0]-> dock( $_[0]-> {__dock__}); 1096 delete $_[0]-> {__dock__}; 1097} 1098 1099sub cleanup 1100{ 1101 my $self = $_[0]; 1102 $self-> SUPER::cleanup; 1103 $self-> {dock}-> undock( $self) if $self-> {dock}; 1104 my $d = $self-> {externalDocker}; 1105 $self-> {externalDocker} = $self-> {dock} = undef; 1106 $d-> destroy if $d; 1107} 1108 1109 1110sub snapDistance { 1111 return $_[0]-> {snapDistance} unless $#_; 1112 my $sd = $_[1]; 1113 $sd = 0 if defined( $sd) && ($sd < 0); 1114 $_[0]-> {snapDistance} = $sd; 1115} 1116 1117sub externalDockerClass { 1118 return $_[0]-> {externalDockerClass} unless $#_; 1119 $_[0]-> {externalDockerClass} = $_[1]; 1120} 1121 1122sub externalDockerModule { 1123 return $_[0]-> {externalDockerModule} unless $#_; 1124 $_[0]-> {externalDockerModule} = $_[1]; 1125} 1126 1127sub externalDockerProfile { 1128 return $_[0]-> {externalDockerProfile} unless $#_; 1129 $_[0]-> {externalDockerProfile} = $_[1]; 1130} 1131 1132sub dockingRoot { 1133 return $_[0]-> {dockingRoot} unless $#_; 1134 $_[0]-> {dockingRoot} = $_[1] if !defined($_[1]) || $_[1]-> isa('Prima::AbstractDocker::Interface'); 1135} 1136 1137sub x_sizeable { 1138 return $_[0]-> {x_sizeable} unless $#_; 1139 $_[0]-> {x_sizeable} = $_[1]; 1140} 1141 1142sub y_sizeable { 1143 return $_[0]-> {y_sizeable} unless $#_; 1144 $_[0]-> {y_sizeable} = $_[1]; 1145} 1146 1147sub fingerprint { 1148 return $_[0]-> {fingerprint} unless $#_; 1149 $_[0]-> {fingerprint} = $_[1]; 1150} 1151 1152sub client 1153{ 1154 return $_[0]-> {client} unless $#_; 1155 my ( $self, $c) = @_; 1156 if ( !defined($c)) { 1157 return if !$self-> {client}; 1158 } else { 1159 return if defined( $self-> {client}) && ($c == $self-> {client}); 1160 } 1161 $self-> {client} = $c; 1162 return unless defined $c; 1163 return unless $self-> {externalDocker}; 1164 my $ed = $self-> {externalDocker}; 1165 my @cf = $ed-> client2frame( $c-> rect); 1166 $ed-> size( $cf[2] - $cf[0], $cf[3] - $cf[1]); 1167 $c-> set( map {'owner' . $_ => 0} qw( Font Hint Palette Color BackColor)); 1168 $c-> owner( $ed-> client); 1169 $c-> clipOwner(1); 1170 $c-> origin( 0, 0); 1171} 1172 1173sub frame2client 1174{ 1175 my ( $self, $x1, $y1, $x2, $y2) = @_; 1176 my @i = @{$self-> {indents}}; 1177 return ( $x1 + $i[0], $y1 + $i[1], $x2 - $i[2], $y2 - $i[3]); 1178} 1179 1180sub client2frame 1181{ 1182 my ( $self, $x1, $y1, $x2, $y2) = @_; 1183 my @i = @{$self-> {indents}}; 1184 return ( $x1 - $i[0], $y1 - $i[1], $x2 + $i[2], $y2 + $i[3]); 1185} 1186 1187sub xorrect 1188{ 1189 my ( $self, $x1, $y1, $x2, $y2, $width) = @_; 1190 if ( defined $x1 ) { 1191 $x2--; $y2--; 1192 $::application-> rubberband( 1193 rect => [ $x1, $y1, $x2, $y2 ], 1194 breadth => $width, 1195 ); 1196 } else { 1197 $::application-> rubberband( destroy => 1 ) 1198 } 1199} 1200 1201sub on_paint 1202{ 1203 my ( $self, $canvas) = @_; 1204 my @sz = $canvas-> size; 1205 $canvas-> clear( 1, 1, $sz[0]-2, $sz[1]-2); 1206 $canvas-> rectangle( 0, 0, $sz[0]-1, $sz[1]-1); 1207} 1208 1209sub indents 1210{ 1211 return $_[0]-> {indents} unless $#_; 1212 my @i = @{$_[1]}; 1213 for ( @i) { 1214 $_ = 0 if $_ < 0; 1215 } 1216 return unless 4 == @i; 1217 $_[0]-> {indents} = \@i; 1218} 1219 1220sub drag 1221{ 1222 return $_[0]-> {drag} unless $#_; 1223 my ( $self, $drag, $rect, $x, $y) = @_; 1224 my @rrc; 1225 if ( $drag) { 1226 return if $self-> {drag}; 1227 $self-> {orgRect} = $rect; 1228 $self-> {anchor} = [$x, $y]; 1229 $self-> {drag} = 1; 1230 $self-> {pointerSave} = $self-> pointer; 1231 $self-> {focSave} = $::application-> get_focused_widget; 1232 $self-> capture(1); 1233 $self-> {oldRect} = [@{$self-> {orgRect}}]; 1234 $self-> {sessions} = {}; 1235 tie %{$self-> {sessions}}, 'Tie::RefHash'; 1236 @rrc = @{$self-> {oldRect}}; 1237 $self-> pointer( cr::Move); 1238 $self-> xorrect( @rrc, 3); 1239 } else { 1240 return unless $self-> {drag}; 1241 $self-> capture(0); 1242 @rrc = @{$self-> {oldRect}}; 1243 $self-> pointer( $self-> {pointerSave}); 1244 $_-> close_session( $self-> {sessions}-> {$_}) for keys %{$self-> {sessions}}; 1245 delete $self-> {$_} for qw( anchor drag orgRect oldRect pointerSave sessions dockInfo); 1246 $self-> xorrect; 1247 } 1248 1249 unless ( $drag) { 1250 $self-> {focSave}-> focus if 1251 $self-> {focSave} && ref($self-> {focSave}) && $self-> {focSave}-> alive; 1252 delete $self-> {focSave}; 1253 } 1254} 1255 1256sub on_mousedown 1257{ 1258 my ( $self, $btn, $mod, $x, $y) = @_; 1259 return unless $btn == mb::Left; 1260 $self-> drag(1, [$self-> owner-> client_to_screen( $self-> rect)], $x, $y); 1261 $self-> clear_event; 1262} 1263 1264sub on_mouseup 1265{ 1266 my ( $self, $btn, $mod, $x, $y) = @_; 1267 return unless ($btn == mb::Left) && $self-> {drag}; 1268 my @rc; 1269 $rc[$_] = $self-> {orgRect}-> [$_] - $self-> {anchor}-> [0] + $x for ( 0, 2); 1270 $rc[$_] = $self-> {orgRect}-> [$_] - $self-> {anchor}-> [1] + $y for ( 1, 3); 1271 my ( $dm, $rect); 1272 if ( $self-> {dockingRoot}) { 1273 ( $dm, $rect) = $self-> find_docking($self-> {dockingRoot}, \@rc); 1274 } 1275 $self-> drag(0); 1276 if ( $self-> {dockingRoot}) { 1277 if ( $dm) { 1278 $self-> dock( $dm, @$rect); # dock or redock 1279 } elsif ( $self-> {externalDocker}) { 1280 $self-> {externalDocker}-> origin( @rc[0,1]); # just move external docker 1281 $self-> notify(q(FailDock), @rc[0,1]); 1282 } else { 1283 $self-> dock( undef, @rc); # convert to external state 1284 } 1285 } 1286 $self-> clear_event; 1287} 1288 1289sub on_mousemove 1290{ 1291 my ( $self, $mod, $x, $y) = @_; 1292 return unless $self-> {drag}; 1293 my @rc; 1294 my $w = 3; 1295 $rc[$_] = $self-> {orgRect}-> [$_] - $self-> {anchor}-> [0] + $x for ( 0, 2); 1296 $rc[$_] = $self-> {orgRect}-> [$_] - $self-> {anchor}-> [1] + $y for ( 1, 3); 1297 goto LEAVE unless $self-> {dockingRoot}; 1298 my ( $dm, $rect) = $self-> find_docking($self-> {dockingRoot}, \@rc); 1299 goto LEAVE unless $dm; 1300 @rc = @$rect; 1301 $w = 1; 1302LEAVE: 1303 $self-> {oldRect} = \@rc; 1304 $self-> xorrect( @{$self-> {oldRect}}, $w); 1305 $self-> clear_event; 1306} 1307 1308sub on_keydown 1309{ 1310 my ( $self, $code, $key, $mod) = @_; 1311 if ( $self-> {drag} && $key == kb::Esc) { 1312 $self-> drag(0); 1313 $self-> clear_event; 1314 } 1315} 1316 1317sub on_mouseclick 1318{ 1319 my ( $self, $btn, $mod, $x, $y, $dbl) = @_; 1320 return unless $dbl; 1321 $self-> dock( undef); 1322} 1323 1324sub on_getcaps 1325{ 1326 my ( $self, $docker, $prf) = @_; 1327 push( @{$prf-> {sizes}}, [$self-> size]); 1328 $prf-> {sizeable} = [ $self-> {x_sizeable}, $self-> {y_sizeable}]; 1329 $prf-> {sizeMin} = [ $self-> {indents}-> [2] + $self-> {indents}-> [0], $self-> {indents}-> [3] + $self-> {indents}-> [1]]; 1330} 1331 1332sub find_docking 1333{ 1334 my ( $self, $dm, $pos) = @_; 1335 my $sid; 1336 unless ( exists $self-> {sessions}-> {$dm}) { 1337 if ( $self-> fingerprint & $dm-> fingerprint) { 1338 my %caps; 1339 $self-> notify(q(GetCaps), $dm, \%caps); 1340 if ( keys %caps) { # $dm is user-approved 1341 $caps{position} = [ @$pos] if $pos; 1342 $caps{self} = $self; 1343 $sid = $dm-> open_session( \%caps); 1344 } 1345 } 1346 $self-> {sessions}-> {$dm} = $sid; 1347 } else { 1348 $sid = $self-> {sessions}-> {$dm}; 1349 } 1350 return unless $sid; 1351 my $relocationCount; 1352AGAIN: 1353 #print "{$dm:@$pos:"; 1354 my @retval; 1355 my @rc = $dm-> query( $sid, $pos ? @$pos : ()); 1356 #print "(@rc)\n"; 1357 goto EXIT unless scalar @rc; 1358 if ( 4 == scalar @rc) { # rect returned 1359 my $sd = $self-> {snapDistance}; 1360 if ( $pos && defined($sd)) { 1361 if ( $self-> {drag} && 1362 ( # have to change the shape 1363 (( $$pos[2] - $$pos[0]) != ( $rc[2] - $rc[0])) || 1364 (( $$pos[3] - $$pos[1]) != ( $rc[3] - $rc[1])))) { 1365 my @pp = $::application-> pointerPos; 1366 my @newpos; 1367 #print '.'; 1368 for ( 0, 1) { 1369 my ( $a, $b) = ( $_, $_ + 2); 1370 my $lb = (( $$pos[$a] + $$pos[$b]) / 2) > $pp[$a]; # 1 if pointer is closer to left/bottom 1371 my $pdist = $lb ? $pp[$a] - $$pos[$a] : $$pos[$b] - $pp[$a]; 1372 my $sz1 = $rc[$b] - $rc[$a]; 1373 if ( $sz1 <= $pdist * 2) { 1374 $newpos[$a] = $pp[$a] - int( $sz1/2); 1375 } else { 1376 $newpos[$a] = $lb ? ( $pp[$a] - $pdist) : ( $pp[$a] + $pdist - $sz1); 1377 } 1378 $newpos[$b] = $newpos[$a] + $sz1; 1379 } 1380 # asking for the new position for the shape, if $dm can accept that... 1381 if ( 2 >= $relocationCount++) { 1382 #print "case1: @newpos\n"; 1383 $pos = \@newpos; 1384 goto AGAIN; 1385 } 1386 } elsif ( $self-> {drag} && ( # have to change the position 1387 ( $$pos[0] != $rc[0]) || ( $$pos[1] != $rc[1]))) { 1388 my @pp = $::application-> pointerPos; 1389 my @newpos = @pp; 1390 #print ','; 1391 for ( 0, 1) { 1392 my ( $a, $b) = ( $_, $_ + 2); 1393 $newpos[$a] = $rc[$a] if $newpos[$a] < $rc[$a]; 1394 $newpos[$a] = $rc[$b] if $newpos[$a] > $rc[$b]; 1395 } 1396 goto EXIT if ( $sd < abs($pp[0] - $newpos[0])) || ( $sd < abs($pp[1] - $newpos[1])); 1397 # asking for the new position, and maybe new shape... 1398 if ( 2 >= $relocationCount++) { 1399 #print "case2: @rc\n"; 1400 $pos = [@rc]; 1401 goto AGAIN; 1402 } 1403 } 1404 goto EXIT if ($sd < abs($rc[0] - $$pos[0])) || ($sd < abs($rc[1] - $$pos[1])); 1405 } 1406 goto EXIT unless $self-> notify(q(Landing), $dm, @rc); 1407 #print "@rc\n"; 1408 @retval = ($dm, \@rc); 1409 } elsif ( 1 == scalar @rc) { # new docker returned 1410 my $next = $rc[0]; 1411 while ( $next) { 1412 my ( $dm_found, $npos) = $self-> find_docking( $next, $pos); 1413 @retval = ($dm_found, $npos), goto EXIT if $npos; 1414 $next = $dm-> next_docker( $sid, $pos ? @$pos[0,1] : ()); 1415 } 1416 } 1417EXIT: 1418 unless ( $self-> {drag}) { 1419 $dm-> close_session( $sid); 1420 delete $self-> {sessions}; 1421 } 1422 return @retval; 1423} 1424 1425sub dock 1426{ 1427 return $_[0]-> {dock} unless $#_; 1428 my ( $self, $dm, @rect) = @_; 1429 if ( $dm) { 1430 my %caps; 1431 my $stage = 0; 1432 my ( $sid, @rc, @s1rc); 1433AGAIN: 1434 if ( $self-> fingerprint && $dm-> fingerprint) { 1435 $self-> notify(q(GetCaps), $dm, \%caps); 1436 if ( keys %caps) { # $dm is user-approved 1437 unshift(@{$caps{sizes}}, [$rect[2] - $rect[0], $rect[3] - $rect[1]]) if scalar @rect; 1438 $caps{position} = [ @rect[0,1]] if scalar @rect; 1439 $caps{self} = $self; 1440 $sid = $dm-> open_session( \%caps); 1441 } 1442 } 1443 return 0 unless $sid; 1444 @rc = $dm-> query( $sid, scalar(@rect) ? @rect : ()); 1445 @s1rc = $dm-> rect; 1446 $dm-> close_session( $sid); 1447 if ( 1 == scalar @rc) { # readdress 1448 my ( $dm2, $rc) = $self-> find_docking( $dm, @rect ? [@rect] : ()); 1449 $self-> dock( $dm2, $rc ? @$rc : ()); 1450 return; 1451 } 1452 return 0 if 4 != scalar @rc; 1453 return 0 unless $self-> notify(q(Landing), $dm, @rc); 1454 1455 unless ( $stage) { 1456 $self-> {dock}-> undock( $self) if $self-> {dock}; 1457 # during the undock $dm may change its position ( and/or size), so retrying 1458 my @s2rc = $dm-> rect; 1459 if ( grep { $s1rc[$_] != $s2rc[$_] } (0..3)) { 1460 $stage = 1; 1461 goto AGAIN; 1462 } 1463 } 1464 $self-> hide; 1465 $self-> owner( $dm); 1466 my @sz = $self-> size; 1467 $dm-> close_session( $sid); 1468 1469 if ( $rc[2] - $rc[0] == $sz[0] && $rc[3] - $rc[1] == $sz[1]) { 1470 $self-> origin( $self-> owner-> screen_to_client( @rc[0,1])); 1471 } else { 1472 $self-> rect( $self-> owner-> screen_to_client( @rc)); 1473 } 1474 unless ( $self-> {dock}) { 1475 my $c = $self-> client; 1476 if ( $c) { 1477 $c-> owner( $self); 1478 $c-> clipOwner(1); 1479 $c-> rect( $self-> frame2client( 0, 0, $self-> width, $self-> height)); 1480 } 1481 if ($self-> {externalDocker}) { 1482 my $d = $self-> {externalDocker}; 1483 delete $self-> {externalDocker}; 1484 $d-> destroy; 1485 } 1486 } 1487 $self-> {dock} = $dm; 1488 $self-> show; 1489 $dm-> dock( $self); 1490 $self-> notify(q(Dock)); 1491 } else { 1492 return if $self-> {externalDocker}; 1493 my $c = $self-> client; 1494 my $s = $c || $self; 1495 if ( defined $self-> {externalDockerModule}) { 1496 eval "use $self->{externalDockerModule};"; 1497 die $@ if $@; 1498 } 1499 my $ed = $self-> {externalDockerClass}-> create( 1500 %{$self-> {externalDockerProfile}}, 1501 visible => 0, 1502 shuttle => $self, 1503 owner => $::application, 1504 text => $self-> text, 1505 onClose => sub { $_[0]-> clear_event unless $self-> notify(q(EDSClose))}, 1506 ); 1507 my @r = $s-> owner-> client_to_screen( $s-> rect); 1508 $ed-> rect( $ed-> client2frame( @r)); 1509 $ed-> origin( @rect[0,1]) if @rect; 1510 if ( $c) { 1511 $c-> set( map {'owner' . $_ => 0} qw( Font Hint Palette Color BackColor)); 1512 $c-> owner( $ed-> client); 1513 $c-> clipOwner(1); 1514 $c-> origin( 0, 0); 1515 } 1516 if ( $self-> visible) { 1517 $ed-> show; 1518 $ed-> bring_to_front; 1519 } 1520 $self-> {externalDocker} = $ed; 1521 if ( $self-> {dock}) { 1522 $self-> {lastUsedDock} = [ $self-> {dock}, [$self-> owner-> client_to_screen( $self-> rect)]]; 1523 $self-> {dock}-> undock( $self) if $self-> {dock}; 1524 $self-> {dock} = undef; 1525 } 1526 $self-> hide; 1527 $self-> owner( $::application); 1528 $self-> notify(q(Undock)); 1529 } 1530} 1531 1532sub externalDocker 1533{ 1534 return $_[0]-> {externalDocker} unless $#_; 1535} 1536 1537sub dock_back 1538{ 1539 my $self = $_[0]; 1540 return if $self-> {dock}; 1541 my ( $dm, $rect); 1542 if ( $self-> {lastUsedDock}) { 1543 ( $dm, $rect) = @{$self-> {lastUsedDock}}; 1544 delete $self-> {lastUsedDock}; 1545 } 1546 if ( !defined($dm) || !Prima::Object::alive( $dm)) { 1547 ( $dm, $rect) = $self-> find_docking( $self-> {dockingRoot}); 1548 } 1549 return unless $dm; 1550 $self-> dock( $dm, $rect ? @$rect : ()); 1551} 1552 1553sub redock 1554{ 1555 my $self = $_[0]; 1556 return unless $self-> {dock}; 1557 $self-> dock( undef); 1558 $self-> dock_back; 1559} 1560 1561sub set_text 1562{ 1563 $_[0]-> SUPER::set_text( $_[1]); 1564 $_[0]-> {externalDocker}-> text($_[1]) if $_[0]-> {externalDocker}; 1565} 1566 1567package Prima::ExternalDockerShuttle; 1568use vars qw(@ISA); 1569@ISA = qw(Prima::MDI); 1570 1571sub profile_default 1572{ 1573 my $def = $_[ 0]-> SUPER::profile_default; 1574 my $fh = int($def-> {font}-> {height} / 1.5); 1575 my %prf = ( 1576 titleHeight => $fh + 4, 1577 borderIcons => bi::TitleBar | ( bi::TitleBar << 1 ), 1578 clipOwner => 0, 1579 shuttle => undef, 1580 borderStyle => bs::Dialog, 1581 ); 1582 @$def{keys %prf} = values %prf; 1583 $def-> {font}-> {height} = $fh; 1584 $def-> {font}-> {width} = 0; 1585 return $def; 1586} 1587 1588sub init 1589{ 1590 my $self = shift; 1591 my %profile = $self-> SUPER::init(@_); 1592 $self-> $_($profile{$_}) for qw(shuttle); 1593 return %profile; 1594} 1595 1596sub shuttle 1597{ 1598 return $_[0]-> {shuttle} unless $#_; 1599 $_[0]-> {shuttle} = $_[1]; 1600} 1601 1602sub on_mousedown 1603{ 1604 my ( $self, $btn, $mod, $x, $y) = @_; 1605 if (q(caption) ne $self-> xy2part( $x, $y)) { 1606 $self-> SUPER::on_mousedown( $btn, $mod, $x, $y); 1607 return; 1608 } 1609 $self-> clear_event; 1610 return if $self-> {mouseTransaction}; 1611 $self-> bring_to_front; 1612 $self-> select; 1613 return if $btn != mb::Left; 1614 my $s = $self-> shuttle; 1615 if ( $s-> client) { 1616 $s-> rect( $s-> client2frame( $s-> client-> rect)); 1617 } else { 1618 $s-> rect( $self-> frame2client( $self-> rect)); 1619 } 1620 $s-> drag( 1, [ $self-> rect], $s-> screen_to_client( $self-> client_to_screen($x, $y))); 1621 $self-> clear_event; 1622} 1623 1624sub on_mouseclick 1625{ 1626 my ( $self, $btn, $mod, $x, $y, $dbl) = @_; 1627 if (!$dbl || (q(caption) ne $self-> xy2part( $x, $y))) { 1628 $self-> SUPER::on_mouseclick( $btn, $mod, $x, $y, $dbl); 1629 return; 1630 } 1631 $self-> clear_event; 1632 $self-> shuttle-> dock_back; 1633} 1634 1635sub windowState 1636{ 1637 return $_[0]-> {windowState} unless $#_; 1638 my ( $self, $ws) = @_; 1639 if ( $ws == ws::Maximized) { 1640 $self-> shuttle-> dock_back; 1641 } else { 1642 $self-> SUPER::windowState( $ws); 1643 } 1644} 1645 1646package Prima::LinearDockerShuttle; 1647use vars qw(@ISA); 1648@ISA = qw(Prima::InternalDockerShuttle); 1649 1650sub profile_default 1651{ 1652 my $def = $_[ 0]-> SUPER::profile_default; 1653 my %prf = ( 1654 indent => 2, 1655 headerBreadth => 8, 1656 vertical => 0, 1657 ); 1658 @$def{keys %prf} = values %prf; 1659 return $def; 1660} 1661 1662sub init 1663{ 1664 my $self = shift; 1665 $self-> {$_} = 0 for ( qw(indent headerBreadth vertical)); 1666 my %profile = $self-> SUPER::init( @_); 1667 $self-> $_( $profile{$_}) for ( qw(indent headerBreadth vertical)); 1668 return %profile; 1669} 1670 1671sub indent 1672{ 1673 return $_[0]-> {indent} unless $#_; 1674 my ($self, $i) = @_; 1675 $i ||= 0; 1676 $i = 0 if $i < 0; 1677 return if $i == $self-> {indent}; 1678 $self-> {indent} = $i; 1679 $self-> update_indents; 1680} 1681 1682sub headerBreadth 1683{ 1684 return $_[0]-> {headerBreadth} unless $#_; 1685 my ($self, $i) = @_; 1686 $i ||= 0; 1687 $i = 0 if $i < 0; 1688 return if $i == $self-> {headerBreadth}; 1689 $self-> {headerBreadth} = $i; 1690 $self-> update_indents; 1691} 1692 1693 1694sub vertical 1695{ 1696 return $_[0]-> {vertical} unless $#_; 1697 my ($self, $i) = @_; 1698 $i ||= 0; 1699 $i = 0 if $i < 0; 1700 return if $i == $self-> {vertical}; 1701 $self-> {vertical} = $i; 1702 $self-> update_indents; 1703 $self-> repaint; 1704} 1705 1706sub update_indents 1707{ 1708 my $self = $_[0]; 1709 my $vs = $self-> { vertical}; 1710 my $i = $self-> {indent}; 1711 my $hb = $self-> {headerBreadth}; 1712 $self-> indents([ $vs ? $i : $i + $hb, $i, $i, $vs ? $i + $hb : $i]); 1713} 1714 1715sub on_paint 1716{ 1717 my ( $self, $canvas) = @_; 1718 my $vs = $self-> {vertical}; 1719 my $i = $self-> {indent}; 1720 my $hb = $self-> {headerBreadth}; 1721 my @sz = $self-> size; 1722 my @rc = ( $self-> light3DColor, $self-> dark3DColor); 1723 $canvas-> clear( 1, 1, $sz[0] - 2, $sz[1] - 2); 1724 $canvas-> color( $rc[1]); 1725 $canvas-> rectangle( 0, 0, $sz[0] - 1, $sz[1] - 1); 1726 my $j; 1727 for ( $j = $i; $j < $hb; $j += 4) { 1728 $vs ? 1729 $canvas-> rect3d( $i, $sz[1] - 3 - $j, $sz[0] - $i - 1, $sz[1] - 1 - $j, 1, @rc) : 1730 $canvas-> rect3d( $j, $i, $j+2, $sz[1] - $i - 1, 1, @rc); 1731 } 1732} 1733 17341; 1735 1736=pod 1737 1738=head1 NAME 1739 1740Prima::Docks - dockable widgets 1741 1742=head1 DESCRIPTION 1743 1744The module contains a set of classes and an implementation of dockable widgets 1745interface. The interface assumes two parties, the dockable widget 1746and the dock widget; the generic methods for the dock widget class are contained in 1747C<Prima::AbstractDocker::Interface> package. 1748 1749=head1 USAGE 1750 1751A dockable widget is required to take particular steps before 1752it can dock to a dock widget. It needs to talk to the dock and 1753find out if it is allowed to land, or if the dock contains lower-level dock widgets 1754that might suit better for docking. If there's more than one dock 1755widget in the program, the dockable widget can select between the targets; this is 1756especially actual when a dockable widget is dragged by mouse and 1757the arbitration is performed on geometrical distance basis. 1758 1759The interface implies that there exists at least one tree-like hierarchy of dock widgets, 1760linked up to a root dock widget. The hierarchy is not required to follow 1761parent-child relationships, although this is the default behavior. 1762All dockable widgets are expected to know explicitly what hierarchy tree they 1763wish to dock to. C<Prima::InternalDockerShuttle> introduces C<dockingRoot> property 1764for this purpose. 1765 1766The conversation between parties starts when a dockable widget 1767calls C<open_session> method of the dock. The dockable widget passes 1768set of parameters signaling if the widget is ready to change its size 1769in case the dock widget requires so, and how. C<open_session> method can either refuse 1770or accept the widget. 1771In case of the positive answer from C<open_session>, the dockable widget 1772calls C<query> method, which either returns a new rectangle, or another dock widget. 1773In the latter case, the caller can enumerate all available dock widgets by 1774repetitive calls to C<next_docker> method. The session is closed by C<close_session> 1775call; after that, the widget is allowed to dock by setting its C<owner> 1776to the dock widget, the C<rect> property to the negotiated position and size, and 1777calling C<dock> method. 1778 1779C<open_session>/C<close_session> brackets are used to cache all necessary 1780calculations once, making C<query> call as light as possible. This design allows 1781a dockable widget, when dragged, repeatedly ask all reachable docks in an 1782optimized way. The docking sessions are kept open until the drag 1783session is finished. 1784 1785The conversation can be schematized in the following code: 1786 1787 my $dock = $self-> dockingRoot; 1788 my $session_id = $dock-> open_session({ self => $self }); 1789 return unless $session_id; 1790 my @result = $dock-> query( $session_id, $self-> rect ); 1791 if ( 4 == scalar @result) { # new rectangle is returned 1792 if ( ..... is new rectangle acceptable ? ... ) { 1793 $dock-> close_session( $session_id); 1794 $dock-> dock( $self); 1795 return; 1796 } 1797 } elsif ( 1 == scalar @result) { # another dock returned 1798 my $next = $result[0]; 1799 while ( $next) { 1800 if ( ... is new docker acceptable? ....) { 1801 $dock-> close_session( $session_id); 1802 $next-> dock( $self); 1803 return; 1804 } 1805 $next = $dock-> next_docker( $session_id, $self-> origin ); 1806 } 1807 } 1808 $dock-> close_session( $session_id); 1809 1810Since even the simplified code is quite cumbersome, direct calls to 1811C<open_session> are rare. Instead, C<Prima::InternalDockerShuttle> 1812implements C<find_docking> method which performs the arbitration automatically 1813and returns the appropriate dock widget. 1814 1815C<Prima::InternalDockerShuttle> is a class that implements dockable 1816widget functionality. It also employs a top-level window-like wrapper widget 1817for the dockable widget when it is not docked. 1818By default, C<Prima::ExternalDockerShuttle> is used as the wrapper widget class. 1819 1820It is not required, however, to use neither C<Prima::InternalDockerShuttle> 1821nor C<Prima::AbstractDocker::Interface> to implement a dockable widget; 1822the only requirements to one is to respect C<open_session>/C<close_session> 1823protocol. 1824 1825C<Prima::InternalDockerShuttle> initiates a class hierarchy of dockable widgets. 1826Its descendants are C<Prima::LinearWidgetDocker> and, in turn, C<Prima::SingleLinearWidgetDocker>. 1827C<Prima::SimpleWidgetDocker> and C<Prima::LinearWidgetDocker>, derived from 1828C<Prima::AbstractDocker::Interface>, begin hierarchy of dock widgets. 1829The full hierarchy is as follows: 1830 1831 Prima::AbstractDocker::Interface 1832 Prima::SimpleWidgetDocker 1833 Prima::ClientWidgetDocker 1834 Prima::LinearWidgetDocker 1835 Prima::FourPartDocker 1836 1837 Prima::InternalDockerShuttle 1838 Prima::LinearDockerShuttle 1839 Prima::SingleLinearWidgetDocker 1840 1841 Prima::ExternalDockerShuttle 1842 1843All docker widget classes are derived from C<Prima::AbstractDocker::Interface>. 1844Depending on the specialization, they employ more or less sophisticated schemes 1845for arranging dockable widgets inside. The most complicated scheme is implemented 1846in C<Prima::LinearWidgetDocker>; it does not allow children overlapping and is 1847able to rearrange with children and resize itself when a widget is docked or undocked. 1848 1849The package provides only basic functionality. Module C<Prima::DockManager> 1850provides common dockable controls, - toolbars, panels, speed buttons etc. 1851based on C<Prima::Docks> module. See L<Prima::DockManager>. 1852 1853=head1 Prima::AbstractDocker::Interface 1854 1855Implements generic functionality of a docket widget. The class is 1856not derived from C<Prima::Widget>; is used as a secondary ascendant class 1857for dock widget classes. 1858 1859=head2 Properties 1860 1861Since the class is not C<Prima::Object> descendant, it provides 1862only run-time implementation of its properties. It is up to the 1863descendant object whether the properties are recognized on the creation stage 1864or not. 1865 1866=over 1867 1868=item fingerprint INTEGER 1869 1870A custom bit mask, to be used by docking widgets to reject inappropriate 1871dock widgets on early stage. The C<fingerprint> property is not part 1872of the protocol, and is not required to be present in a dockable widget implementation. 1873 1874Default value: C<0x0000FFFF> 1875 1876=item dockup DOCK_WIDGET 1877 1878Selects the upper link in dock widgets hierarchy tree. The upper 1879link is required to be a dock widget, but is not required to be 1880a direct or an indirect parent. In this case, however, the maintenance 1881of the link must be implemented separately, for example: 1882 1883 $self-> dockup( $upper_dock_not_parent ); 1884 1885 $upper_dock_not_parent-> add_notification( 'Destroy', sub { 1886 return unless $_[0] == $self-> dockup; 1887 undef $self-> {dockup_event_id}; 1888 $self-> dockup( undef ); 1889 }, $self); 1890 1891 $self-> {destroy_id} = $self-> add_notification( 'Destroy', sub { 1892 $self-> dockup( undef ); 1893 } unless $self-> {destroy_id}; 1894 1895=back 1896 1897=head2 Methods 1898 1899=over 1900 1901=item add_subdocker SUBDOCK 1902 1903Appends SUBDOCK to the list of lower-level docker widgets. The items of the list are 1904returned by C<next_docker> method. 1905 1906=item check_session SESSION 1907 1908Debugging procedure; checks SESSION hash, warns if its members are 1909invalid or incomplete. Returns 1 if no fatal errors were encountered; 19100 otherwise. 1911 1912=item close_session SESSION 1913 1914Closes docking SESSION and frees the associated resources. 1915 1916=item dock WIDGET 1917 1918Called after WIDGET is successfully finished negotiation with 1919the dock widget and changed its C<owner> property. The method 1920adapts the dock widget layout and lists WIDGET into list of 1921docked widgets. The method does not change C<owner> property of WIDGET. 1922 1923The method must not be called directly. 1924 1925=item dock_bunch @WIDGETS 1926 1927Effectively docks set of WIDGETS by updating internal structures 1928and calling C<rearrange>. 1929 1930=item docklings 1931 1932Returns array of docked widgets. 1933 1934=item next_docker SESSION, [ X, Y ] 1935 1936Enumerates lower-level docker widgets within SESSION; returns 1937one docker widget at a time. After the last widget returns 1938C<undef>. 1939 1940The enumeration pointer is reset by C<query> call. 1941 1942X and Y are coordinates of the point of interest. 1943 1944=item open_session PROFILE 1945 1946Opens docking session with parameters stored in PROFILE 1947and returns session ID scalar in case of success, or C<undef> otherwise. 1948The following keys must be set in PROFILE: 1949 1950=over 1951 1952=item position ARRAY 1953 1954Contains two integer coordinates of the desired position of 1955a widget in (X,Y) format in screen coordinate system. 1956 1957=item self WIDGET 1958 1959Widget that is about to dock. 1960 1961=item sizeable ARRAY 1962 1963Contains two boolean flags, representing if the widget can be resized 1964to an arbitrary size, horizontally and vertically. The arbitrary resize 1965option used as last resort if C<sizes> key does not contain the desired 1966size. 1967 1968=item sizeMin ARRAY 1969 1970Two integers; minimal size that the widget can accept. 1971 1972=item sizes ARRAY 1973 1974Contains arrays of points in (X,Y) format; each point represents an 1975acceptable size of the widget. If C<sizeable> flags are set to 0, 1976and none of C<sizes> can be accepted by the dock widget, C<open_session> 1977fails. 1978 1979=back 1980 1981=item query SESSION [ X1, Y1, X2, Y2 ] 1982 1983Checks if a dockable widget can be landed into the dock. 1984If it can, returns a rectangle that the widget must be set to. 1985If coordinates ( X1 .. Y2 ) are specified, returns the 1986rectangle closest to these. If C<sizes> or C<sizeable> 1987keys of C<open_session> profile were set, the returned size 1988might be different from the current docking widget size. 1989 1990Once the caller finds the result appropriate, it is allowed to change 1991its owner to the dock; after that, it must change its origin and size correspondingly 1992to the result, and then call C<dock>. 1993 1994If the dock cannot accept the widget, but contains lower-lever 1995dock widgets, returns the first lower-lever widget. The caller 1996can use subsequent calls to C<next_docker> to enumerate all 1997lower-level dock widgets. A call to C<query> 1998resets the internal enumeration pointer. 1999 2000If the widget cannot be landed, an empty array is returned. 2001 2002=item rearrange 2003 2004Effectively re-docks all the docked widgets. The effect is 2005as same as of 2006 2007 $self-> redock_widget($_) for $self-> docklings; 2008 2009but usually C<rearrange> is faster. 2010 2011=item redock_widget WIDGET 2012 2013Effectively re-docks the docked WIDGET. If WIDGET has C<redock> 2014method in its namespace, it is called instead. 2015 2016=item remove_subdocker SUBDOCK 2017 2018Removes SUBDOCK from the list of lower-level docker widgets. 2019See also L<add_subdocker>. 2020 2021=item replace FROM, TO 2022 2023Assigns widget TO same owner and rectangle as FROM. The FROM widget 2024must be a docked widget. 2025 2026=item undock WIDGET 2027 2028Removes WIDGET from list of docked widgets. The layout of the dock widget 2029can be changed after execution of this method. The method does not 2030change C<owner> property of WIDGET. 2031 2032The method must not be called directly. 2033 2034=back 2035 2036=head1 Prima::SimpleWidgetDocker 2037 2038A simple dock widget; accepts any widget that geometrically fits into. 2039Allows overlapping of the docked widgets. 2040 2041=head1 Prima::ClientWidgetDocker 2042 2043A simple dock widget; accepts any widget that can be fit to cover all 2044dock's interior. 2045 2046=head1 Prima::LinearWidgetDocker 2047 2048A toolbar-like docking widget class. The implementation does 2049not allow tiling, and can reshape the dock widget and rearrange 2050the docked widgets if necessary. 2051 2052C<Prima::LinearWidgetDocker> is orientation-dependent; its main axis, 2053governed by C<vertical> property, is used to align docked widgets in 2054'lines', which in turn are aligned by the opposite axis ( 'major' and 'minor' terms 2055are used in the code for the axes ). 2056 2057=head2 Properties 2058 2059=over 2060 2061=item growable INTEGER 2062 2063A combination of C<grow::XXX> constants, that describes how 2064the dock widget can be resized. The constants are divided in two 2065sets, direct and indirect, or, C<vertical> property independent and 2066dependent. 2067 2068The first set contains explicitly named constants: 2069 2070 grow::Left grow::ForwardLeft grow::BackLeft 2071 grow::Down grow::ForwardDown grow::BackDown 2072 grow::Right grow::ForwardRight grow::BackRight 2073 grow::Up grow::ForwardUp grow::BackUp 2074 2075that select if the widget can be grown to the direction shown. 2076These do not change meaning when C<vertical> changes, though they do 2077change the dock widget behavior. The second set does not affect 2078dock widget behavior when C<vertical> changes, however the names 2079are not that illustrative: 2080 2081 grow::MajorLess grow::ForwardMajorLess grow::BackMajorLess 2082 grow::MajorMore grow::ForwardMajorMore grow::BackMajorMore 2083 grow::MinorLess grow::ForwardMinorLess grow::BackMinorLess 2084 grow::MinorMore grow::ForwardMinorMore grow::BackMinorMore 2085 2086C<Forward> and C<Back> prefixes select if the dock widget can be 2087respectively expanded or shrunk in the given direction. C<Less> and 2088C<More> are equivalent to C<Left> and C<Right> when C<vertical> is 0, 2089and to C<Up> and C<Down> otherwise. 2090 2091The use of constants from the second set is preferred. 2092 2093Default value: 0 2094 2095=item hasPocket BOOLEAN 2096 2097A boolean flag, affects the possibility of a docked widget to reside 2098outside the dock widget inferior. If 1, a docked wigdet is allowed 2099to stay docked ( or dock into a position ) further on the major axis 2100( to the right when C<vertical> is 0, up otherwise ), as if there's 2101a 'pocket'. If 0, a widget is neither allowed to dock outside the 2102inferior, nor is allowed to stay docked ( and is undocked automatically ) 2103when the dock widget shrinks so that the docked widget cannot stay in 2104the dock boundaries. 2105 2106Default value: 1 2107 2108=item vertical BOOLEAN 2109 2110Selects the major axis of the dock widget. If 1, it is vertical, 2111horizontal otherwise. 2112 2113Default value: 0 2114 2115=back 2116 2117=head2 Events 2118 2119=over 2120 2121=item Dock 2122 2123Called when C<dock> is successfully finished. 2124 2125=item DockError WIDGET 2126 2127Called when C<dock> is unsuccessfully finished. This only 2128happens if WIDGET does not follow the docking protocol, and inserts 2129itself into a non-approved area. 2130 2131=item Undock 2132 2133Called when C<undock> is finished. 2134 2135=back 2136 2137=head1 Prima::SingleLinearWidgetDocker 2138 2139Descendant of C<Prima::LinearWidgetDocker>. In addition 2140to the constraints, introduced by the ascendant class, 2141C<Prima::SingleLinearWidgetDocker> allows only one line ( or row, 2142depending on C<vertical> property value ) of docked widgets. 2143 2144=head1 Prima::FourPartDocker 2145 2146Implementation of a docking widget, with its four sides 2147acting as 'rubber' docking areas. 2148 2149=head2 Properties 2150 2151=over 2152 2153=item indents ARRAY 2154 2155Contains four integers, specifying the breadth of offset for 2156each side. The first integer is width of the left side, the second - height 2157of the bottom side, the third - width of the right side, the fourth - height 2158of the top side. 2159 2160=item dockerClassLeft STRING 2161 2162Assigns class of left-side dock window. 2163 2164Default value: C<Prima::LinearWidgetDocker>. 2165Create-only property. 2166 2167=item dockerClassRight STRING 2168 2169Assigns class of right-side dock window. 2170 2171Default value: C<Prima::LinearWidgetDocker>. 2172Create-only property. 2173 2174=item dockerClassTop STRING 2175 2176Assigns class of top-side dock window. 2177 2178Default value: C<Prima::LinearWidgetDocker>. 2179Create-only property. 2180 2181=item dockerClassBottom STRING 2182 2183Assigns class of bottom-side dock window. 2184 2185Default value: C<Prima::LinearWidgetDocker>. 2186Create-only property. 2187 2188=item dockerClassClient STRING 2189 2190Assigns class of center dock window. 2191 2192Default value: C<Prima::ClientWidgetDocker>. 2193Create-only property. 2194 2195=item dockerProfileLeft HASH 2196 2197Assigns hash of properties, passed to the left-side dock widget during the creation. 2198 2199Create-only property. 2200 2201=item dockerProfileRight HASH 2202 2203Assigns hash of properties, passed to the right-side dock widget during the creation. 2204 2205Create-only property. 2206 2207=item dockerProfileTop HASH 2208 2209Assigns hash of properties, passed to the top-side dock widget during the creation. 2210 2211Create-only property. 2212 2213=item dockerProfileBottom HASH 2214 2215Assigns hash of properties, passed to the bottom-side dock widget during the creation. 2216 2217Create-only property. 2218 2219=item dockerProfileClient HASH 2220 2221Assigns hash of properties, passed to the center dock widget during the creation. 2222 2223Create-only property. 2224 2225=item dockerDelegationsLeft ARRAY 2226 2227Assigns the left-side dock list of delegated notifications. 2228 2229Create-only property. 2230 2231=item dockerDelegationsRight ARRAY 2232 2233Assigns the right-side dock list of delegated notifications. 2234 2235Create-only property. 2236 2237=item dockerDelegationsTop ARRAY 2238 2239Assigns the top-side dock list of delegated notifications. 2240 2241Create-only property. 2242 2243=item dockerDelegationsBottom ARRAY 2244 2245Assigns the bottom-side dock list of delegated notifications. 2246 2247Create-only property. 2248 2249=item dockerDelegationsClient ARRAY 2250 2251Assigns the center dock list of delegated notifications. 2252 2253Create-only property. 2254 2255=item dockerCommonProfile HASH 2256 2257Assigns hash of properties, passed to all five dock widgets during the creation. 2258 2259Create-only property. 2260 2261=back 2262 2263=head1 Prima::InternalDockerShuttle 2264 2265The class provides a container, or a 'shuttle', for a client widget, while is docked to 2266an C<Prima::AbstractDocker::Interface> descendant instance. The functionality includes 2267communicating with dock widgets, the user interface for dragging and interactive dock selection, 2268and a client widget container for non-docked state. The latter is implemented by 2269reparenting of the client widget to an external shuttle widget, selected by C<externalDockerClass> 2270property. Both user interfaces for the docked and the non-docked shuttle states are minimal. 2271 2272The class implements dockable widget functionality, served by C<Prima::AbstractDocker::Interface>, 2273while itself it is derived from C<Prima::Widget> only. 2274 2275See also: L</Prima::ExternalDockerShuttle>. 2276 2277=head2 Properties 2278 2279=over 2280 2281=item client WIDGET 2282 2283Provides access to the client widget, which always resides either in 2284the internal or the external shuttle. By default there is no client, 2285and any widget capable of changing its parent can be set as one. 2286After a widget is assigned as a client, its C<owner> and C<clipOwner> 2287properties must not be used. 2288 2289Run-time only property. 2290 2291=item dock WIDGET 2292 2293Selects the dock widget that the shuttle is landed on. If C<undef>, 2294the shuttle is in the non-docked state. 2295 2296Default value: C<undef> 2297 2298=item dockingRoot WIDGET 2299 2300Selects the root of dock widgets hierarchy. 2301If C<undef>, the shuttle can only exist in the non-docked state. 2302 2303Default value: C<undef> 2304 2305See L</USAGE> for reference. 2306 2307=item externalDockerClass STRING 2308 2309Assigns class of external shuttle widget. 2310 2311Default value: C<Prima::ExternalDockerShuttle> 2312 2313=item externalDockerModule STRING 2314 2315Assigns module that contains the external shuttle widget class. 2316 2317Default value: C<Prima::MDI> ( C<Prima::ExternalDockerShuttle> is derived from C<Prima::MDI> ). 2318 2319=item externalDockerProfile HASH 2320 2321Assigns hash of properties, passed to the external shuttle widget during the creation. 2322 2323=item fingerprint INTEGER 2324 2325A custom bit mask, used to reject inappropriate dock widgets on early stage. 2326 2327Default value: C<0x0000FFFF> 2328 2329=item indents ARRAY 2330 2331Contains four integers, specifying the breadth of offset in pixels for each 2332widget side in the docked state. 2333 2334Default value: C<5,5,5,5>. 2335 2336=item snapDistance INTEGER 2337 2338A maximum offset, in pixels, between the actual shuttle coordinates and the coordinates 2339proposed by the dock widget, where the shuttle is allowed to land. 2340In other words, it is the distance between the dock and the shuttle when the latter 2341'snaps' to the dock during the dragging session. 2342 2343Default value: 10 2344 2345=item x_sizeable BOOLEAN 2346 2347Selects whether the shuttle can change its width in case the dock widget suggests so. 2348 2349Default value: 0 2350 2351=item y_sizeable BOOLEAN 2352 2353Selects whether the shuttle can change its height in case the dock widget suggests so. 2354 2355Default value: 0 2356 2357=back 2358 2359=head2 Methods 2360 2361=over 2362 2363=item client2frame X1, Y1, X2, Y2 2364 2365Returns a rectangle that the shuttle would occupy if 2366its client rectangle is assigned to X1, Y1, X2, Y2 2367rectangle. 2368 2369=item dock_back 2370 2371Docks to the recent dock widget, if it is still available. 2372 2373=item drag STATE, RECT, ANCHOR_X, ANCHOR_Y 2374 2375Initiates or aborts the dragging session, depending on STATE boolean 2376flag. 2377 2378If it is 1, RECT is an array with the coordinates of the shuttle rectangle 2379before the drag has started; ANCHOR_X and ANCHOR_Y are coordinates of the 2380aperture point where the mouse event occurred that has initiated the drag. 2381Depending on how the drag session ended, the shuttle can be relocated to 2382another dock, undocked, or left intact. Also, C<Dock>, C<Undock>, or 2383C<FailDock> notifications can be triggered. 2384 2385If STATE is 0, RECT, ANCHOR_X ,and ANCHOR_Y parameters are not used. 2386 2387=item find_docking DOCK, [ POSITION ] 2388 2389Opens a session with DOCK, unless it is already opened, 2390and negotiates about the possibility of landing ( 2391at particular POSITION, if this parameter is present ). 2392 2393C<find_docking> caches the dock widget sessions, and provides a 2394possibility to select different parameters passed to C<open_session> 2395for different dock widgets. To achieve this, C<GetCaps> request 2396notification is triggered, which fills the parameters. The default 2397action sets C<sizeable> options according to C<x_sizeable> 2398and C<y_sizeable> properties. 2399 2400In case an appropriate landing area is found, C<Landing> 2401notification is triggered with the proposed dock widget 2402and the target rectangle. The area can be rejected on this stage 2403if C<Landing> returns negative answer. 2404 2405On success, returns a dock widget found and the target rectangle; 2406the widget is never docked though. On failure returns an empty array. 2407 2408This method is used by the dragging routine to provide a visual feedback to 2409the user, to indicate that a shuttle may or may not land in a particular 2410area. 2411 2412=item frame2client X1, Y1, X2, Y2 2413 2414Returns a rectangle that the client would occupy if 2415the shuttle rectangle is assigned to X1, Y1, X2, Y2 2416rectangle. 2417 2418=item redock 2419 2420If docked, undocks form the dock widget and docks back. 2421If not docked, does not perform anything. 2422 2423=back 2424 2425=head2 Events 2426 2427=over 2428 2429=item Dock 2430 2431Called when shuttle is docked. 2432 2433=item EDSClose 2434 2435Triggered when the user presses close button or otherwise activates the 2436C<close> function of the EDS ( external docker shuttle ). To cancel 2437the closing, C<clear_event> must be called inside the event handler. 2438 2439=item FailDock X, Y 2440 2441Called after the dragging session in the non-docked stage is finished, 2442but did not result in docking. X and Y are the coordinates 2443of the new external shuttle position. 2444 2445=item GetCaps DOCK, PROFILE 2446 2447Called before the shuttle opens a docking session with DOCK 2448widget. PROFILE is a hash reference, which is to be filled 2449inside the event handler. After that PROFILE is passed 2450to C<open_session> call. 2451 2452The default action sets C<sizeable> options according to C<x_sizeable> 2453and C<y_sizeable> properties. 2454 2455=item Landing DOCK, X1, Y1, X2, Y2 2456 2457Called inside the docking session, after an appropriate dock 2458widget is selected and the landing area is defined as 2459X1, Y1, X2, Y2. To reject the landing on either DOCK or 2460area, C<clear_event> must be called. 2461 2462=item Undock 2463 2464Called when shuttle is switched to the non-docked state. 2465 2466=back 2467 2468=head1 Prima::ExternalDockerShuttle 2469 2470A shuttle class, used to host a client of C<Prima::InternalDockerShuttle> 2471widget when it is in the non-docked state. The class represents an 2472emulation of a top-level window, which can be moved, resized ( this 2473feature is not on by default though ), and closed. 2474 2475C<Prima::ExternalDockerShuttle> is inherited from C<Prima::MDI> class, and 2476its window emulating functionality is a subset of its ascendant. 2477See also L<Prima::MDI>. 2478 2479=head2 Properties 2480 2481=over 2482 2483=item shuttle WIDGET 2484 2485Contains reference to the dockable WIDGET 2486 2487=back 2488 2489=head1 Prima::LinearDockerShuttle 2490 2491A simple descendant of C<Prima::InternalDockerShuttle>, used 2492for toolbars. Introduces orientation and draws a tiny header along 2493the minor shuttle axis. All its properties concern only 2494the way the shuttle draws itself. 2495 2496=head2 Properties 2497 2498=over 2499 2500=item headerBreadth INTEGER 2501 2502Breadth of the header in pixels. 2503 2504Default value: 8 2505 2506=item indent INTEGER 2507 2508Provides a wrapper to C<indents> property; besides the 2509space for the header, all indents are assigned to C<indent> 2510property value. 2511 2512=item vertical BOOLEAN 2513 2514If 1, the shuttle is drawn as a vertical bar. 2515If 0, the shuttle is drawn as a horizontal bar. 2516 2517Default value: 0 2518 2519=back 2520 2521=head1 AUTHOR 2522 2523Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>. 2524 2525=head1 SEE ALSO 2526 2527L<Prima>, L<Prima::Widget>, L<Prima::MDI>, L<Prima::DockManager>, F<examples/dock.pl> 2528 2529=cut 2530