# -*- perl -*-

#
# $Id: Placement.pm,v 1.7 2003/01/04 15:55:57 eserte Exp eserte $
# Author: Slaven Rezic
#
# Copyright (C) 2002 Slaven Rezic. All rights reserved.
# This package is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Mail: slaven@rezic.de
# WWW:  http://www.rezic.de/eserte/
#

package Tk::Placement;

use strict;
use vars qw($VERSION);
$VERSION = sprintf("%d.%02d", q$Revision: 1.7 $ =~ /(\d+)\.(\d+)/);

sub get_toplevel_regions {
    my $w = shift;
    my @win;
    $w->Walk(sub {
		 my $w = shift;
		 if ($w->isa("Tk::Toplevel") && $w->viewable &&
		     $w->state eq 'normal') {
		     push @win, {x => $w->rootx, y => $w->rooty,
				 width => $w->width, height => $w->height};
		 }
	     });
    @win;
}

sub placer {
    my($w, %args) = @_;
    my $mw = $w->MainWindow;
    my $tlparent   = $args{-toplevelparent} || $mw;
    my $scr        = $args{-screen}         || $mw;
    my $width      = $args{-width}          || $w->reqwidth;
    my $height     = $args{-height}         || $w->reqheight;
    my $pdeltax    = $args{-pdeltax}        || 20;
    my $pdeltay    = $args{-pdeltay}        || 20;
    my $usepercent = $args{-usepercent}     || 1;
    my $addx       = $args{-addx}           || 0; # for wms
    my $addy       = $args{-addy}           || 0; # for wms
    my $returnonly = $args{-returnonly}     || 0;
    my $placer     = $args{-placer}         || "clever";
    my @win = get_toplevel_regions($tlparent);
    my($x, $y) = any_placement
	(
	 $placer,
	 {width => $width, height => $height},
	 {x => $scr->rootx, y => $scr->rooty, width => $scr->width, height => $scr->height},
	 \@win, $pdeltax, $pdeltay, $usepercent,
	);
    $x += $addx;
    $y += $addy;
    if ($returnonly) {
	($x, $y);
    } else {
	$w->geometry(sprintf "+%d+%d", $x, $y);
    }
}

sub any_placement {
    my $placer = shift;
    if ($placer =~ /^clever$/i) {
	Tk::Placement::Clever::placement(@_);
    } else {
	die "Placer $placer not available";
    }
}

package Tk::Placement::Clever;

# REPO BEGIN
# REPO NAME min /home/e/eserte/src/repository 
# REPO MD5 80379d2502de0283d7f02ef8b3ab91c2

=head2 min(...)

=for category Math

Return minimum value.

=cut

if (eval { require List::Util; 1 }) {
    *min = \&List::Util::min;
} else {
    *min = sub {
	my $min = $_[0];
	foreach (@_[1..$#_]) {
	    $min = $_ if $_ < $min;
	}
	$min;
    };
}
# REPO END

# REPO BEGIN
# REPO NAME max /home/e/eserte/src/repository 
# REPO MD5 6b687abc440b6ae4b54d3e55858b10a3

=head2 max(...)

=for category Math

Return maximum value.

=cut

if (eval { require List::Util; 1 }) {
    *max = \&List::Util::max;
} else {
    *max = sub {
	my $max = $_[0];
	foreach (@_[1..$#_]) {
	    $max = $_ if $_ > $max;
	}
	$max;
    };
}
# REPO END

my $PLACEMENT_AVOID_COVER_99 = 12;
my $PLACEMENT_AVOID_COVER_95 =  6;
my $PLACEMENT_AVOID_COVER_85 =  4;
my $PLACEMENT_AVOID_COVER_75 =  1;

# Adapted from placement.c, fvwm2
#
# Original comment:
# CleverPlacement by Anthony Martin <amartin@engr.csulb.edu>
# This function will place a new window such that there is a minimum amount
# of interference with other windows.  If it can place a window without any
# interference, fine.  Otherwise, it places it so that the area of of
# interference between the new window and the other windows is minimized
#

sub placement {
    my($window_area, $screen_area, $windows, $pdeltax, $pdeltay, $use_percent) = @_;

    my $PageLeft = $screen_area->{x} - $pdeltax;
    my $test_x = $PageLeft;
    my $test_y = $screen_area->{y} - $pdeltay; # $PageTop;
    # area of interference
    my $aoi = test_fit($window_area, $screen_area, $windows,
		       $test_x, $test_y, -1,
		       $pdeltax, $pdeltay, $use_percent);
    my $aoimin = $aoi;
    my $xbest = $test_x;
    my $ybest = $test_y;

    while ($aoi != 0 && $aoi != -1) {
	if ($aoi > 0) {
	    # Windows interfere.  Try next x.
	    $test_x = get_next_x($window_area, $screen_area, $windows,
				 $test_x, $test_y,
				 $pdeltax, $pdeltay, $use_percent);
	} else {
	    # Out of room in x direction. Try next y. Reset x.
	    $test_x = $PageLeft;
	    $test_y = get_next_y($window_area, $screen_area, $windows,
				 $test_y, $pdeltay, $use_percent);
	}
	$aoi = test_fit($window_area, $screen_area, $windows,
			$test_x, $test_y,
			$aoimin, $pdeltax, $pdeltay, $use_percent);
	# I've added +0.00001 because whith my machine the < test fail with
	# certain *equal* float numbers!
	if ($aoi >= 0 && $aoi + 0.00001 < $aoimin) {
	    $xbest = $test_x;
	    $ybest = $test_y;
	    $aoimin = $aoi;
	}
    }

    (int($xbest), int($ybest));
}

sub get_next_x {
    my($window_area, $screen_area, $windows,
       $x, $y, $pdeltax, $pdeltay, $use_percent) = @_;

    my $PageLeft = $screen_area->{x} - $pdeltax;
    my $PageRight = $PageLeft + $screen_area->{width};
    my $minx_inc = $screen_area->{width} / 20;

    # Test window at far right of screen
    my $xnew = $PageRight;
    my $xtest = $PageRight - $window_area->{width};
    if ($xtest > $x) {
	$xnew = min($xnew, $xtest);
    }
    # Test the values of the right edges of every window
    for my $testw (@$windows) {
	if ($y < $testw->{height} + $testw->{y} &&
	    $testw->{y}  < $window_area->{height} + $y) {
	    $xtest = $PageLeft + $testw->{width} + $testw->{x};
	    if ($xtest > $x) {
		$xnew = min($xnew, $xtest);
	    }
	    $xtest = $PageLeft + $testw->{x} - $window_area->{width};
	    if ($xtest > $x) {
		$xnew = min($xnew, $xtest);
	    }
	}
    }
    if ($use_percent) {
	$xnew = min($xnew, $x + $minx_inc);
    }

    $xnew;
}

sub get_next_y {
    my($window_area, $screen_area, $windows, $y, $pdeltay, $use_percent) = @_;

    my $PageBottom = $screen_area->{y} + $screen_area->{height} - $pdeltay;
    my $miny_inc = ($screen_area->{height} / 20);

    # Test window at far bottom of screen
    my $ynew = $PageBottom;
    my $ytest = $PageBottom - $window_area->{height};
    if ($ytest > $y) {
	$ynew = min($ynew, $ytest);
    }
    # Test the values of the bottom edge of every window
    for my $testw (@$windows) {
	$ytest = $testw->{height} + $testw->{y};
	if ($ytest > $y) {
	    $ynew = min($ynew, $ytest);
	}
	$ytest = $testw->{y} - $window_area->{height};
	if ($ytest > $y) {
	    $ynew = min($ynew, $ytest);
	}
    }

    if ($use_percent) {
	$ynew = min($ynew, $y+$miny_inc);
    }

    $ynew;
}

sub test_fit {
    my($window_area, $screen_area, $windows,
       $x11, $y11, $aoimin, $pdeltax, $pdeltay, $use_percent) = @_;

    my $aoi = 0; # area of interference
    my $cover_factor = 1;
    my $PageRight = $screen_area->{x} + $screen_area->{width} - $pdeltax;
    my $PageBottom = $screen_area->{y} + $screen_area->{height} - $pdeltay;

    my $x12 = $x11 + $window_area->{width};
    my $y12 = $y11 + $window_area->{height};

    if ($y12 > $PageBottom) {
	# No room in y direction
	return -1;
    }
    if ($x12 > $PageRight) {
	# No room in x direction
	return -2;
    }
    for my $testw (@$windows) {

	my $x21 = $testw->{x};
	my $y21 = $testw->{y};
	my $x22 = $x21 + $testw->{width};
	my $y22 = $y21 + $testw->{height};

	if ($x11 < $x22 && $x12 > $x21 &&
	    $y11 < $y22 && $y12 > $y21) {
	    # Windows interfere
	    my $xl = max($x11, $x21);
	    my $xr = min($x12, $x22);
	    my $yt = max($y11, $y21);
	    my $yb = min($y12, $y22);
	    my $anew = ($xr - $xl) * ($yb - $yt);
	    my $avoidance_factor = 1.0; # XXX other factors...

	    if ($use_percent) {
		# normalisation
		if (($x22 - $x21) * ($y22 - $y21) != 0 &&
		    ($x12 - $x11) * ($y12 - $y11) != 0) {
		    $anew = 100 * max($anew / (($x22 - $x21) * ($y22 - $y21)),
				      $anew / (($x12 - $x11) * ($y12 - $y11)));
		    if      ($anew >= 99) {
			$cover_factor = $PLACEMENT_AVOID_COVER_99;
		    } elsif ($anew >= 95) {
			$cover_factor = $PLACEMENT_AVOID_COVER_95;
		    } elsif ($anew >= 85) {
			$cover_factor = $PLACEMENT_AVOID_COVER_85;
		    } elsif ($anew >= 75) {
			$cover_factor = $PLACEMENT_AVOID_COVER_75;
		    }
		    if ($avoidance_factor>1) {
			$avoidance_factor +=
			    ($cover_factor > 1) ? $cover_factor : 0;
		    } else {
			$avoidance_factor = $cover_factor;
		    }
		}
	    }
	    $anew *= $avoidance_factor;
	    $aoi += $anew;
	    if ($aoi > $aoimin && $aoimin != -1) {
		return $aoi;
	    }
	}
    }
    $aoi;
}

1;

__END__
