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