#
#  Copyright (c) 1997-2002 The Protein Laboratory, University of Copenhagen
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
#  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#  SUCH DAMAGE.
#
#  Created by:
#     Vadim Belman <voland@lflat.org>
#
#  $Id: FrameSet.pm,v 1.3 2002/05/14 13:22:21 dk Exp $
use strict;
use Prima;
use Prima::Const;

package fra; # Frame arragement constants.

use constant Vertical => 0;
use constant Horizontal => 1;

package frr; # Frame resize method constants.

use constant Simple => 0;
use constant Proportional => 1; # XXX Yet to be implemented.

package Prima::FrameSet::Frame;
use strict;
use vars qw(@ISA);

@ISA = qw(Prima::Widget Prima::MouseScroller);

# Initialization
sub profile_default
{
    return {
	%{ $_[0]->SUPER::profile_default},
	minFrameWidth => 5,
	maxFrameWidth => undef,
    };
}

sub init
{
    my $me = shift;

    my %profile = $me->SUPER::init(@_);

    foreach my $key (qw(minFrameWidth maxFrameWidth)) {
	$me->$key($profile{$key});
    }

    return %profile;
}

# Properties
sub minFrameWidth
{
    my $me = shift;
    if (@_ > 0) {
	return if defined ($me->{minFrameWidth}) && $me->{minFrameWidth} == $_[0];
	if (defined $_[0]) {
	    $me->{minFrameWidth} = $_[0] < 0 ? 0 : $_[0];
	} else {
	    undef $me->{minFrameWidth};
	}
    } else {
	return $me->{minFrameWidth};
    }
}

sub maxFrameWidth
{
    my $me = shift;
    if (@_ > 0) {
	return if defined($me->{maxFrameWidth}) && ($me->{maxFrameWidth} == $_[0]);
	if (defined $_[0]) {
	    $me->{maxFrameWidth} = $_[0] < 0 ? 0 : $_[0];
	} else {
	    undef $me->{maxFrameWidth};
	}
    } else {
	return $me->{maxFrameWidth};
    }
}



package Prima::FrameSet::Slider;
use strict;
use vars qw(@ISA);

@ISA = qw(Prima::Widget);

# Initialization
sub profile_default
{
    return {
	%{$_[0]->SUPER::profile_default},
	vertical	=> 0,
	thickness	=> 4,
	growMode	=> gm::GrowHiX | gm::GrowLoX,
        frame1          => undef,
        frame2          => undef,
        sliderIndex     => 0,
    };
}

sub profile_check_in
{
    my ($me, $p, $default) = @_;
    my $userPointer = exists $p->{pointerType};
    $me->SUPER::profile_check_in($p, $default);
    if (exists $p->{vertical} ? $p->{vertical} : $default->{vertical}) {
	$p->{growMode} = gm::GrowHiY | gm::GrowLoY unless exists $p->{growMode};
    }
}

sub init
{
    my $me = shift;
    my %profile = $me->SUPER::init(@_);

    foreach my $prop (qw(thickness vertical frame1 frame2 sliderIndex)) {
	$me->$prop($profile{$prop});
    }

    $me->adjust_sizes;

    return %profile;
}

# Event handlers
sub on_paint
{
    my ($me, $canvas) = @_;
   my @sz = $canvas->size;
   unless ($me-> enabled) {
        $me->color( $me-> disabledBackColor);
	$me->bar( 0, 0, $me->size);
   } else {
	$me->rect3d(
		    0,
		    0,
		    $sz[0]-1,
		    $sz[1]-1,
		    1,
		    $me->{draggingMode} ? (
			    $me->dark3DColor,
			    $me->light3DColor,
			) : (
			    $me->light3DColor,
			    $me->dark3DColor,
			),
 		        $me->backColor
		   );
   }
}

sub on_enable  { $_[0]-> repaint; }
sub on_disable { $_[0]-> repaint; }

sub on_mousedown
{
    my $me = shift;
    my ($btn, $mod, $x, $y) = @_;

    $me->clear_event;
    if ($btn == mb::Left) {
	$me->start_dragging;
	$me->repaint;
	$me->update_view;
	@{ $me}{qw(spotX spotY)} = ($x, $y);
	$me->{traceRect} = [$me->client_to_screen(0, 0), $me->client_to_screen($me->size)];
	$me->xorrect(@{$me->{traceRect}}) unless $me->owner->opaqueResize;
	$me->capture(1);
    }
}

sub on_mouseup
{
    my $me = shift;
    my ($btn, $mod, $x, $y) = @_;

    $me->clear_event;

    if ($btn == mb::Left) {
	$me->stop_dragging;
	$me->owner->slider_moved( $me, $me->get_delta($x, $y));
	$me->repaint;
    }
}

sub on_mousemove
{
    my $me = shift;
    my ($mod, $x, $y) = @_;

    if ($me->{draggingMode}) {
	$me->clear_event;

	my $delta = $me->get_delta($x, $y);
	my @sz = $me->size;
	my ($fmin, $fmax);
	if ($delta > 0) {
	    $fmin = $me->frame2;
	    $fmax = $me->frame1;
	} else {
	    $fmin = $me->frame1;
	    $fmax = $me->frame2;
	}
	my ($fminWidth, $fmaxWidth);
	if ($me->{vertical}) {
	    $fminWidth = $fmin->width;
	    $fmaxWidth = $fmax->width;
	} else {
	    $fminWidth = $fmin->height;
	    $fmaxWidth = $fmax->height;
	}
	$delta = ($fminWidth - $fmin->{minFrameWidth}) * ($delta < 0 ? -1 : 1)
	    if defined($fmin->{minFrameWidth})
		&& (($fminWidth - abs($delta)) < $fmin->{minFrameWidth});
	$delta = ($fmax->{maxFrameWidth} - $fmaxWidth) * ($delta < 0 ? -1 : 1)
	    if defined($fmax->{maxFrameWidth})
		&& (($fmaxWidth + abs($delta)) > $fmax->{maxFrameWidth});

	if ($me->owner->opaqueResize) {
	    $me->owner->slider_moved($me, $delta);
	} else {
	    my @oldrect = @{$me->{traceRect}};
	    if ($me->{vertical}) {
		$me->{traceRect} = [$me->client_to_screen($delta, 0), $me->client_to_screen($sz[0] + $delta, $sz[1])];
	    } else {
		$me->{traceRect} = [$me->client_to_screen(0, $delta), $me->client_to_screen($sz[0], $sz[1] + $delta)];
	    }
	    my $different = 0;
	    $different ||= $oldrect[$_] != $me->{traceRect}->[$_] foreach 0..3;
	    if ($different) {
# Don't redraw dragging rect if the old and the new rectangle are the same.
		$me->xorrect(@oldrect);
		$me->xorrect(@{$me->{traceRect}});
	    }
	}
    }
}

sub on_keydown
{
    my $me = shift;
    my ($code, $key, $mod) = @_;

    if (($key == kb::Esc) && $me->{draggingMode}) {
	$me->stop_dragging;
	$me->repaint;
    }
}

# Helpers
sub adjust_sizes
{
    my $me = shift;
    my ($w, $h);
    my $owner = $me->owner;
    if ($me->{vertical}) {
	$h = $owner->height;
	$w = $me->{thickness};
    } else {
	$h = $me->{thickness};
	$w = $owner->width;
    }
    $me->size($w, $h);
}

sub xorrect
{
   my ( $self, @r) = @_;
   $r[2]--;
   $r[3]--;
   my $o = $::application;
   $o-> begin_paint;
   my $oo = $self-> clipOwner ? $self-> owner : $::application;
   $o-> clipRect( $oo-> client_to_screen( 0,0,$oo->size));
   $o-> set(
      fillPattern => fp::SimpleDots,
      color       => cl::Set,
      backColor   => cl::Clear,
      rop         => rop::XorPut,
   );
   $o-> bar( @r);
   $o-> end_paint;
}

sub get_delta
{
    my $me = shift;
    my ($x, $y) = @_;
    return $me->{vertical} ? $x - $me->{spotX} : $y - $me->{spotY};
}

sub start_dragging
{
    my $me = shift;
    $me->{draggingMode} = 1;
    $me->{oldFocus} = $::application->get_focused_widget;
    $me->focus;
}

sub stop_dragging
{
    my $me = shift;

    $me->xorrect(@{$me->{traceRect}}) unless $me->owner->opaqueResize;
    $me->{draggingMode} = 0;
    $me->capture(0);
    $me->{oldFocus}->focus
	if defined $me->{oldFocus};
}

# Properties
sub vertical
{
    my $me = shift;
    if (@_ > 0) {
	return if exists($me->{vertical}) && $me->{vertical} == $_[0];
	$me->{vertical} = $_[0];
	$me->pointerType($_[0] ? cr::SizeWE : cr::SizeNS);
	$me->adjust_sizes;
	$me->repaint;
    }
    else {
	return $me->{vertical};
    }
}

sub thickness
{
    my $me = shift;
    if (@_ > 0) {
	return if exists($me->{thickness}) && $me->{thickness} == $_[0];
	$me->{thickness} = $_[0];
	$me->adjust_sizes;
	$me->repaint;
    } else {
	return $me->{thickness};
    }
}


sub frame1
{
    my $me = shift;
    if (@_ > 0) {
	return unless $_[0] && $_[0]->isa('Prima::FrameSet::Frame');
	$me->{frame1} = $_[0];
    } else {
	return $me->{frame1};
    }
}

sub frame2
{
    my $me = shift;
    if (@_ > 0) {
	return unless $_[0] && $_[0]->isa('Prima::FrameSet::Frame');
	$me->{frame2} = $_[0];
    } else {
	return $me->{frame2};
    }
}

sub sliderIndex
{
    my $me = shift;
    if (@_ > 0) {
	$me->{sliderIndex} = $_[0];
    } else {
	return $me->{sliderIndex};
    }
}



package Prima::FrameSet;
use strict;
use vars qw(@ISA);

@ISA = qw(Prima::Widget);

# Initialization.
sub profile_default
{
    return {
	%{ $_[0]->SUPER::profile_default},
	arrangement => fra::Horizontal,		# Vertical or horizontal insertion of frames.
	frameCount => 2,			# Number of frames, no less than 2.
 	flexible => 1,				# Frame can be resized by user at any time at any place.
	sliders => [qw(1)],			# Where resize sliders must be located. Array size depends on number of frames and must be (nframes+1) long.
	sliderWidth => 4,
	separatorWidth => 1,			# Separator is an immovable slider.
	frameSizes => [qw(50% *)],		# Sizes of frames in percents, pixel or '*' for automatic.
	growMode => gm::Client,
	origin => [0, 0],
	opaqueResize => 0,
	resizeMethod => frr::Simple,
        frameClass    => 'Prima::FrameSet::Frame',
        frameProfile  => {},
        frameProfiles => [],
        sliderClass    => 'Prima::FrameSet::Slider',
        sliderProfile  => {},
        sliderProfiles => [],
    };
}

sub profile_check_in
{
    my $me = shift;
    my ($profile, $default) = @_;
    $me->SUPER::profile_check_in(@_);
    $profile->{frameCount} = @{$profile->{frameSizes}} unless exists $profile->{frameCount};
    $profile->{frameCount} = 2 if $profile->{frameCount} < 2;
    if (! exists($profile->{sliders}) && ! exists($profile->{flexible})) {
	$profile->{sliders} = [(1) x ($profile->{frameCount} - 1)];
    }
}

sub init
{
    my $me = shift;
    my %profile = $me->SUPER::init(@_);

    $me->{frameSizes} = $profile{frameSizes};

    foreach my $prop (qw(frameCount arrangement sliderWidth separatorWidth
			 flexible opaqueResize resizeMethod)) {
	$me->$prop($profile{$prop});
    }

    for (my $i = 0; $i < $profile{frameCount}; $i++) {
        my %xp = %{$profile{frameProfile}};
        %xp = ( %xp, %{$profile{frameProfiles}->[$i]}) 
           if $profile{frameProfiles}->[$i] && ref($profile{frameProfiles}->[$i]) eq 'HASH';
	my $frame = $me->insert(
				$profile{frameClass} =>
				name => "Frame$i",
                                %xp,
	);
	push @{$me->{frames}}, $frame;
    }

    my $sn;
    for ($sn = 0; $sn < ($profile{frameCount} - 1); $sn++) {
	my $moveable = $profile{sliders}->[$sn] ? 1 : 0;
	$moveable = $profile{flexible};
        my %xp = %{$profile{sliderProfile}};
        %xp = ( %xp, %{$profile{sliderProfiles}->[$sn]}) 
           if $profile{sliderProfiles}->[$sn] && ref($profile{sliderProfiles}->[$sn]) eq 'HASH';
	my $slider = $me->insert(
	    $profile{sliderClass} =>
	    name => "Slider#$sn",
	    $moveable ?
		(
		 thickness => $profile{sliderWidth},
		 enabled => 1,
		) : (
		 thickness => 1,
		 enabled => 0,
		),
# Horizontal arrangement of frames means we need a vertically oriented slider.
	    vertical => $me->{arrangement} == fra::Horizontal,
	    frame1 => $me->{frames}->[$sn],
	    frame2 => $me->{frames}->[$sn + 1],
	    sliderIndex => $sn,
            %xp,
	);
	push @{$me->{sliders}}, $slider;
    }

    $me->recalc_frames(initial => 1);
    $me->reset;

    return %profile;
}

# Event handlers
sub on_size
{
    my $me = shift;

    if ($_[0] == 0 && $_[1] == 0) {
# We get it when initial resize is performed.
	$me->recalc_frames(initial => 1);
    }
    else {
	$me->recalc_frames(resize => 1, sizes => \@_);
    }
    $me->reset;
}

sub on_paint
{
    my ($me, $canvas) = @_;

    $canvas->fillPattern(fp::Interleave);
    $canvas->bar(0, 0, $me->size);
}

# Helpers
sub recalc_frames
{
    my $me = shift;
    my (%profile) = @_;

    return unless $me->owner;
    
    my @sizes = @{$me->{sizes} || []};
    if ($profile{initial}) {

	my @frameSizes = @{$me->{frameSizes}};
	my $asterixCount = 0;
	my $percents = 0;
	my $pixels = 0;

	foreach my $fsz (@frameSizes) {
	    if ($fsz eq '*') {
		$asterixCount++;
		next;
	    }

	    (my $nfsz = $fsz) =~ s/\%$//;

            $nfsz = 0 if $nfsz < 0;

	    if ($fsz =~ /\%$/) {
		$percents += $nfsz;
	    } else {
		$pixels += $nfsz;
	    }
	}

	my $totalSize;	# Total width or height depending on arrangement.
	if ($me->{arrangement} == fra::Vertical) {
	    $totalSize = $me->height;
	} else {
	    $totalSize = $me->width;
	}

	my $size = $totalSize - $pixels; # Size left after discounting all pixel-based and sliders sizes.

	foreach my $slider (@{$me->{sliders}}) {
	    $size -= $slider->thickness;
	}

	my $percentSize = ($size * $percents / 100.);

	my $autoSize = $asterixCount ? ($size - $percentSize) / $asterixCount : 0; # Size of an automaticly-sized frame.

	@sizes = (0) x $me->{frameCount};
	my $frac = 0;
	my $origSize;
	for (my $i = 0; $i < $me->{frameCount}; $i++) {
	    if (! defined($frameSizes[$i])
		|| ($frameSizes[$i] eq '*')) {
		$sizes[$i] = int($autoSize + .5);
		$frac += $autoSize - $sizes[$i];
	    }
	    elsif ($frameSizes[$i] =~ /\%$/) {
		(my $nfsz = $frameSizes[$i]) =~ s/\%$//;
		$sizes[$i] = int( ($origSize = ($size * $nfsz) / 100.) + .5);
		$frac += $origSize - $sizes[$i];
	    }
	    else {
		$sizes[$i] = int(($origSize = $frameSizes[$i]) + .5);
		$frac += $origSize - $sizes[$i];
	    }

	    if (abs($frac) >= 1) {
		$sizes[$i] += int($frac);
		$frac = $frac - int($frac);
	    }
	}

	$me->{sizes} = [@sizes];

    } elsif ($profile{resize}) {

	my $idx = $me->{arrangement} == fra::Horizontal ? 0 : 1;
	my $old_size = @{$profile{sizes}}[$idx];
	my $new_size = @{$profile{sizes}}[$idx + 2];
	return if ($old_size == $new_size);

	if ($me->{resizeMethod} == frr::Simple) {
	    my $i;
	    for ($i = 0; $i < ($me->{frameCount} - 1); $i++) {
		$old_size -= $me->{sliders}->[$i]->{thickness};
		$new_size -= $me->{sliders}->[$i]->{thickness};
	    }
	    my @nsizes;
	    my $newTotal = 0;
	    my ($f, $ns);
	    for ($i = 0; $i < ($me->{frameCount} - 1); $i++) {
		$f = $me->{frames}->[$i];
		$ns = int(($sizes[$i] * $new_size) / $old_size + .5);
		$ns = $f->{minFrameWidth}
		    if defined($f->{minFrameWidth}) && ($ns < $f->{minFrameWidth});
		$ns = $f->{maxFrameWidth}
		    if defined($f->{maxFrameWidth}) && ($ns > $f->{maxFrameWidth});
		$nsizes[$i] = $ns;
		$newTotal += $ns;
	    }
# Calculate for the last frame.
	    $f = $me->{frames}->[$i];
	    $ns = $new_size - $newTotal;
	    $ns = 0 if $ns < 0;
	    $ns = $f->{minFrameWidth}
		if defined($f->{minFrameWidth}) && ($ns < $f->{minFrameWidth});
	    $ns = $f->{maxFrameWidth}
		if defined($f->{maxFrameWidth}) && ($ns > $f->{maxFrameWidth});
	    $nsizes[$i] = $ns;

	    $me->{sizes} = \@nsizes;
	}
	
    }
    return (wantarray ? @sizes : \@sizes);
}

sub reset
{
    my $me = shift;

    return unless $me->owner;

    my $origin = [0, 0];
    my $end = [$me->{arrangement} == fra::Vertical ? ($me->width, 1) : (1, $me->height)];
    my $idx = $me->{arrangement} == fra::Vertical ? 1 : 0; # What element of origin/size array we change.
    my @sliders = @{$me->{sliders}};

    for (my $i = 0; $i < $me->{frameCount}; $i++) {
	$end->[$idx] = $origin->[$idx] + $me->{sizes}->[$i];
	$me->{frames}->[$i]->rect(@$origin, @$end);
	$origin->[$idx] += $me->{sizes}->[$i];
	if ($i < @{$me->{sliders}}) {
	    $sliders[$i]->origin(@$origin);
	    $origin->[$idx] += $sliders[$i]->thickness;
	}
    }
}

sub slider_moved
{
    my $me = shift;
    my ($slider, $delta) = @_;
    return unless $delta;

    my $si = $slider->sliderIndex;

    if ($me->{resizeMethod} == frr::Simple) {
	my $frame1 = $slider->frame1;
	my $frame2 = $slider->frame2;

	my ($w1, $w2);
	if ($me->{arrangement} == fra::Horizontal) {
	    $w1 = $frame1->width;
	    $w2 = $frame2->width;
	} else {
	    $w1 = $frame1->height;
	    $w2 = $frame2->height;
	}

# Adjust delta in a way it doesn't clash with frame's min/max sizes.
	my $nw1 = $w1 + $delta;
	if (defined $frame1->minFrameWidth && $nw1 < $frame1->minFrameWidth) {
	    $nw1 = $frame1->minFrameWidth;
	} elsif (defined $frame1->maxFrameWidth && $nw1 > $frame1->maxFrameWidth) {
	    $nw1 = $frame1->maxFrameWidth;
	}
	$delta = $nw1 - $w1;
	my $nw2 = $w2 - $delta;
	if (defined $frame2->minFrameWidth && $nw2 < $frame2->minFrameWidth) {
	    $nw2 = $frame2->minFrameWidth;
	} elsif (defined $frame2->maxFrameWidth && $nw2 > $frame2->maxFrameWidth) {
	    $nw2 = $frame2->maxFrameWidth;
	}
	$delta = $w2 - $nw2;
	$nw1 = $w1 + $delta;

	my @rect;
	if ($me->{arrangement} == fra::Horizontal) {
	    $frame1->width($nw1);
	    @rect = $slider->rect;
	    $rect[0] += $delta;
	    $rect[2] += $delta;
	    $slider->rect(@rect);
	    @rect = $frame2->rect;
	    $rect[0] += $delta;
	    $frame2->rect(@rect);
	} else {
	    $frame1->height($nw1);
	    @rect = $slider->rect;
	    $rect[1] += $delta;
	    $rect[3] += $delta;
	    $slider->rect(@rect);
	    @rect = $frame2->rect;
	    $rect[1] += $delta;
	    $frame2->rect(@rect);
	}

	$me->{sizes}->[$si] = $nw1;
	$me->{sizes}->[$si + 1] = $nw2;
    }
}

# Properties
sub arrangement
{
    my $me = shift;
    if (@_ > 0) {
	my $haveIt = exists($me->{arrangement});
	return if $haveIt && ($me->{arrangement} == $_[0]);
	$me->{arrangement} = $_[0];
	if ($me->{sliders}) {
	    for (my $i = 0; $i < ($me->{frameCount} - 1); $i++) {
		$me->{sliders}->[$i]->vertical($_[0] == fra::Horizontal);
	    }
	}
	if ($haveIt) {
	    $me->recalc_frames;
	    $me->reset;
	}
    } else {
	return $me->{arrangement};
    }
}

sub frameCount
{
# XXX frameCount property only sets number of frames. Needed adjustments
# are too complicated and various to be implemented right here.
    my $me = shift;
    if (@_ > 0) {
	my $haveIt = exists $me->{frameCount};
	return if $haveIt && $me->{frameCount} == $_[0];

	my $oldval = ($haveIt ? $me->{frameCount} : undef);

	$me->{frameCount} = $_[0];
    } else {
	return $me->{frameCount};
    }
}

sub sliderWidth
{
    my $me = shift;
    if (@_ > 0) {
	my $haveIt = exists($me->{sliderWidth});
	return if ($haveIt && ($me->{sliderWidth} == $_[0])) || ($_[0] < 0);
	$me->{sliderWidth} = $_[0];
	if ($haveIt) {
	    for (my $i = 0; $i < ($me->{frameCount} - 1); $i++) {
		if ($me->{sliders}->[$i]->enabled) {
		    $me->{sliders}->[$i]->thickness($_[0]);
		}
	    }
	    $me->recalc_frames;
	    $me->reset;
	}
    } else {
	return $me->{sliderWidth};
    }
}

sub separatorWidth
{
    my $me = shift;
    if (@_ > 0) {
	my $haveIt = exists($me->{separatorWidth});
	return if ($haveIt && ($me->{separatorWidth} == $_[0])) || ($_[0] < 0);
	$me->{separatorWidth} = $_[0];
	if ($haveIt) {
	    for (my $i = 0; $i < ($me->{frameCount} - 1); $i++) {
		if ($me->{sliders}->[$i]->enabled) {
		    $me->{sliders}->[$i]->thickness($_[0]);
		}
	    }
	    $me->recalc_frames;
	    $me->reset;
	}
    } else {
	return $me->{separatorWidth};
    }
}

sub flexible
{
    my $me = shift;
    if (@_ > 0) {
	my $haveIt = exists($me->{flexible});
	return if $haveIt && ! ($me->{flexible} xor $_[0]);
	$me->{flexible} = $_[0] ? 1 : 0;
	if ($haveIt) {
	    for (my $i = 0; $i < ($me->{frameCount} - 1); $i++) {
		$me->{sliders}->[$i]->thickness($me->{flexible} ? $me->{sliderWidth} : $me->{separatorWidth});
		$me->{sliders}->[$i]->enabled( $me->{flexible});
	    }
	    $me->recalc_frames;
	    $me->reset;
	}
    } else {
	return $me->{flexible};
    }
}

sub frameSizes
{
   return [@{$_[0]->{frameSizes}}] unless $#_;
   my $me = shift;
   my @fs = ( $_[0] && ref($_[0]) eq 'ARRAY' && 1 == scalar @_) ? @{$_[0]} : @_;
   $me-> {frameSizes} = \@fs;
   $me-> recalc_frames( initial => 1);
   $me-> reset;
}

sub opaqueResize
{
    my $me = shift;
    if (@_ > 0) {
	return if exists($me->{opaqueResize}) && $me->{opaqueResize} == $_[0];
	$me->{opaqueResize} = $_[0];
    } else {
	return $me->{opaqueResize};
    }
}

sub resizeMethod
{
    my $me = shift;
    if (@_ > 0) {
	return if exists($me->{resizeMethod}) && $me->{resizeMethod} == $_[0];
	$me->{resizeMethod} = $_[0];
    } else {
	return $me->{resizeMethod};
    }
}

# User interface
sub firstFrame
{
    my $me = shift;
    return $me->{frames}->[0];
}

sub lastFrame
{
    my $me = shift;
    return $me->{frames}->[-1];
}

sub frames
{
    my $me = shift;
    return wantarray ? @{$me->{frames}} : $me->{frames};
}

sub insert_to_frame
{
    my $me = shift;
    my $frameIdx = shift;

    $frameIdx = $me->{frameCount} - 1 if $frameIdx > ($me->{frameCount} - 1);
    
    $me->lock;
    my @ctrls = $me->{frames}->[$frameIdx]->insert(@_);
    $me->unlock;
    
    return wantarray ? @ctrls : $ctrls[0];
}



1;
