package Gimp::UI;

use Gimp ('__');
use Gimp::Fu;
use base 'DynaLoader';

BEGIN {
   $VERSION = 1.211;
   eval {
      require XSLoader;
      XSLoader::load Gimp::UI $VERSION;
   } or do {
      require DynaLoader;
      @ISA=qw(DynaLoader);
      bootstrap Gimp::UI $VERSION;
   }
}

=head1 NAME

Gimp::UI - "simulation of libgimpui", and more!

=head1 SYNOPSIS

  use Gimp::UI;

=head1 DESCRIPTION

Due to the braindamaged (read: "unusable") libgimpui API, I had to
reimplement all of it in perl.

=over 4

 $option_menu = new Gimp::UI::ImageMenu
 $option_menu = new Gimp::UI::LayerMenu
 $option_menu = new Gimp::UI::ChannelMenu
 $option_menu = new Gimp::UI::DrawableMenu (constraint_func, active_element, \var);
 
 $button = new Gimp::UI::PatternSelect;
 $button = new Gimp::UI::BrushSelect;
 $button = new Gimp::UI::GradientSelect;

 $button = new Gimp::UI::ColorSelectButton;

=back

=cut

if (eval { require Gtk; import Gtk (); 1 }) {
   local $/;
   eval <DATA>;
   die $@ if $@;
   close DATA;
}

1;

# All Gtk-dependent functions are put below
__DATA__
#line 58

@Gimp::UI::ImageMenu::ISA   =qw(Gimp::UI);
@Gimp::UI::LayerMenu::ISA   =qw(Gimp::UI);
@Gimp::UI::ChannelMenu::ISA =qw(Gimp::UI);
@Gimp::UI::DrawableMenu::ISA=qw(Gimp::UI);

sub image_name {
   my $name = $_[0]->get_filename;
   $name.="-".${$_[0]} if $name eq "Untitled";
   $name;
}

sub Gimp::UI::ImageMenu::_items {
  map [[$_],$_,image_name($_)],
      Gimp->image_list ();
}
sub Gimp::UI::LayerMenu::_items {
  map { my $i = $_; map [[$i,$_],$_,image_name($i)."/".$_->get_name],$i->get_layers }
      Gimp->image_list ();
}

sub Gimp::UI::ChannelMenu::_items {
  map { my $i = $_; map [[$i,$_],$_,image_name($i)."/".$_->get_name],$i->get_channels }
      Gimp->image_list ();
}

sub Gimp::UI::DrawableMenu::_items {
  map { my $i = $_; map [[$i,$_],$_,image_name($i)."/".$_->get_name],($i->get_layers, $i->get_channels) }
      Gimp->image_list ();
}

sub new($$$$) {
   my($class,$constraint,$active,$var)=@_;
   my(@items)=$class->_items;
   my $menu = new Gtk::Menu;
   for(@items) {
      my($constraints,$result,$name)=@$_;
      next unless $constraint->(@{$constraints});
      my $item = new Gtk::MenuItem $name;
      $item->signal_connect(activate => sub { $$var=$result });
      $menu->append($item);
   }
   if (@items) {
      $$var=$items[0]->[1];
   } else {
      my $item = new Gtk::MenuItem __"(none)";
      $menu->append($item);
      $$var=undef;
   }
   $menu->show_all;
   $menu;
}

package Gimp::UI::PreviewSelect;

use Gimp '__';

sub GTK_CLASS_INIT {
   my $class = shift;
   add_arg_type $class "active", "GtkString", 3;
}

sub GTK_OBJECT_SET_ARG {
   my($self,$arg,$id,$value) = @_;
   if ($arg eq "active") {
      my $count;
      
      if (!defined $self->{value} || $value ne $self->{value}) {
         $self->{value}=$value=$self->set_preview($value);
         $self->{label}->set($value);
         $self->{list}->foreach(sub {
            if ($_[0]->children->get eq $value) {
               $self->{list}->select_item($count);
            };
            $count++;
         });
      }
   }
}

sub GTK_OBJECT_GET_ARG {
   my($self,$arg,$id) = @_;
   $self->{label}->get;
}

sub GTK_OBJECT_INIT {
   my $self = shift;
   (my $label = new Gtk::Label "")->show;
   $self->add($label);
   $self->{label}=$label;
   
   my $w = new Gtk::Dialog;
   $w->set_title($self->get_title);
   $w->set_usize(400,300);
   $w->action_area->set_border_width(2);
   $w->action_area->set_homogeneous(0);
   
   (my $h=new Gtk::HBox 0,0)->show;
   $w->vbox->pack_start($h,1,1,0);
   
   (my $preview = $self->new_preview)->show;
   
   (my $s=new Gtk::ScrolledWindow undef,undef)->show;
   $s->set_policy(-automatic, -automatic);
   $s->set_usize(200,300);
   $h->pack_start($s,1,1,0);
   $h->pack_start($preview,1,1,0);
   
   my $l=new Gtk::List;
   $l->set_selection_mode(-single);
   $l->set_selection_mode(-browse);
   $self->{list}=$l;
   
   for(sort $self->get_list) {
      $l->add(new Gtk::ListItem $_);
   }
   
   $l->show_all;
   $l->signal_connect("selection_changed",sub {
      $l->selection and
         $self->set_preview($l->selection->children->get);
   });
   $s->add_with_viewport ($l);

   my $hbbox = new Gtk::HButtonBox;
   $hbbox->set_spacing(4);
   $w->action_area->pack_end($hbbox,0,0,0);
   show $hbbox;

   my $button = new Gtk::Button __"OK";
   signal_connect $button "clicked", sub {
      hide $w;
      if($l->selection) {
         my $p = $l->selection->children->get;
         $label->set($p);
      }
   };
   $hbbox->pack_start($button,0,0,0);
   can_default $button 1;
   grab_default $button;
   show $button;
   
   $button = new Gtk::Button __"Cancel";
   signal_connect $button "clicked", sub {hide $w};
   $hbbox->pack_start($button,0,0,0);
   can_default $button 1;
   show $button;
   
   $self->signal_connect("clicked",sub {show $w});
}

package Gimp::UI::PatternSelect;

use Gimp '__';
use Gimp::basewidget Gtk::Button;

sub GTK_CLASS_INIT	{ goto &Gimp::UI::PreviewSelect::GTK_CLASS_INIT     }
sub GTK_OBJECT_SET_ARG	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_SET_ARG }
sub GTK_OBJECT_GET_ARG	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_GET_ARG }
sub GTK_OBJECT_INIT	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_INIT    }

sub get_title { __"Pattern Selection Dialog" }
sub get_list { Gimp->patterns_list }

sub new_preview {
   my $self = shift;
   my $cp = $self->{"color_preview"}=new Gtk::Preview "color";
   my $gp = $self->{"gray_preview"} =new Gtk::Preview "grayscale";
   my $preview = new Gtk::HBox 0,0;
   $preview->add($cp);
   $preview->add($gp);
   $preview;
}

sub set_preview {
   my $self = shift;
   my $value = shift;
   
   my $cp = $self->{"color_preview"};
   my $gp = $self->{"gray_preview"};
   
   my ($name,$w,$h,$bpp,$mask)=Gimp->patterns_get_pattern_data ($value);
   unless (defined $name) {
      $name=Gimp->patterns_get_pattern;
      ($name,$w,$h,$bpp,$mask)=Gimp->patterns_get_pattern_data ($name);
   }
   hide $cp;
   hide $gp;
   my $p = $bpp == 1 ? $gp : $cp;
   $p->size ($w, $h);
   for(0..$h-1) {
      $p->draw_row (substr ($mask, $w*$bpp*$_), 0, $_, $w);
   }
   $p->draw(undef);
   show $p;
   
   $name;
}

package Gimp::UI::BrushSelect;

use Gimp '__';
use Gimp::basewidget Gtk::Button;

sub GTK_CLASS_INIT	{ goto &Gimp::UI::PreviewSelect::GTK_CLASS_INIT     }
sub GTK_OBJECT_SET_ARG	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_SET_ARG }
sub GTK_OBJECT_GET_ARG	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_GET_ARG }
sub GTK_OBJECT_INIT	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_INIT    }

sub get_title { __"Brush Selection Dialog" }
sub get_list { Gimp->brushes_list }

sub new_preview {
   my $self=shift;
   $self->{"preview"}=new Gtk::Preview "grayscale";
}

sub set_preview {
   my $self = shift;
   my $value = shift;
   
   my $p = $self->{"preview"};
   
   my ($name,$opacity,$spacing,$mode,$w,$h,$mask)=eval { Gimp->brushes_get_brush_data ($value) };
   if ($@) {
      $name=Gimp->brushes_get_brush;
      ($name,$opacity,$spacing,$mode,$w,$h,$mask)=Gimp->brushes_get_brush_data ($name);
   }
   $mask=pack("C*",@$mask);
   $xor="\xff" x $w;
   hide $p;
   my $l=length($mask);
   $p->size ($w, $h);
   for(0..$h-1) {
     $p->draw_row (substr ($mask, $w*$_) ^ $xor, 0, $_, $w);
   }
   $p->draw(undef);
   show $p;
   
   $name;
}

package Gimp::UI::GradientSelect;

use Gimp '__';
use Gimp::basewidget Gtk::Button;

sub GTK_CLASS_INIT	{ goto &Gimp::UI::PreviewSelect::GTK_CLASS_INIT     }
sub GTK_OBJECT_SET_ARG	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_SET_ARG }
sub GTK_OBJECT_GET_ARG	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_GET_ARG }
sub GTK_OBJECT_INIT	{ goto &Gimp::UI::PreviewSelect::GTK_OBJECT_INIT    }

sub get_title { __"Gradient Selection Dialog" }
sub get_list { keys %gradients }

sub new_preview {
   my $self = shift;
   new Gtk::Frame;
}

sub set_preview {
   my $self = shift;
   my $value = shift;
   exists $gradients{$value} ? $value : Gimp->gradients_get_active;
}

sub new {
   unless (defined %gradients) {
      undef @gradients{Gimp->gradients_get_list};
   }
   Gtk::Object::new @_;
}

package Gimp::UI::ColorSelectButton;

use Gimp '__';
use Gimp::basewidget Gtk::Button;

# Class defaults data
my @class_def_color = (255,175,0);

sub GTK_CLASS_INIT {
	my($class) = shift;
	add_arg_type $class "color", "GtkString", 3; #R/W
}

sub GTK_OBJECT_INIT {
    my (@color) = @class_def_color;
    
    shift unless ref $_[0];
    my($color_button) = @_;
    
    $color_button->{_color} ||= [@color];

    my $preview = new Gtk::Preview -color;
    
    $color_button->{_preview} = $preview;
    
    # work around funny bug somewhere in gtk...
    $preview->size(300,50);
    $preview->show;
    $color_button->add($preview);
        
    signal_connect $color_button "size_allocate" => sub {
    	my($self,$allocation) = @_;
    	my($x,$y,$w,$h) = @$allocation;
    	$w -= 6;
    	$h -= 6;
    	$self->{_preview_width} = $w;
    	$self->{_preview_height} = $h;
        $self->{_preview}->size($self->{_preview_width},$self->{_preview_height});
    	$self->update_color;
    };
    
    signal_connect $color_button "clicked" => \&cb_color_button;
}

sub GTK_OBJECT_SET_ARG {
   my($self,$arg,$id, $value) = @_;
   $self->{_color} = [split(' ',$value)];
   $self->update_color;
}

sub GTK_OBJECT_GET_ARG {
   my($self,$arg,$id) = @_;
   return join(' ',@{$self->{_color}});
}

sub update_color($) {
    my($this) = shift;
    
    return unless defined $this->{_preview} and defined $this->{_preview_width};
    
    my($preview, $color) = ($this->{_preview}, $this->{_color});
    my($width, $height) = ($this->{_preview_width}, $this->{_preview_height});
    
    my($buf) = pack("C3", @$color) x $width;

    for(0..$height-1) {
       $preview->draw_row($buf, 0, $_, $width);
    }
    $preview->draw(undef);
}

sub color_selection_ok {
    my($widget, $dialog, $color_button) = @_;
	
    my(@color) = $dialog->colorsel->get_color;
    @{$color_button->{_color}} = map(int(255.99*$_),@color);

    $color_button->update_color();
    $dialog->destroy();
    delete $color_button->{_cs_window};
}

sub cb_color_button {
    my($color_button) = @_;
    
    if (defined $color_button->{_cs_window}) {
    	if (!$color_button->{_cs_window}->mapped) {
	    	$color_button->{_cs_window}->hide;
	    }
    	$color_button->{_cs_window}->show;
    	$color_button->{_cs_window}->window->raise;
    	return;
    }

    my $cs_window=new Gtk::ColorSelectionDialog(__"Color");
    $cs_window->colorsel->set_color(map($_*1/255,@{$color_button->{_color}}));
    $cs_window->show();
    $cs_window->ok_button->signal_connect("clicked",
					  \&color_selection_ok,
					  $cs_window,
					  $color_button);
    $cs_window->cancel_button->signal_connect("clicked",
					      sub { $cs_window->destroy; delete $color_button->{_cs_window} });
    $color_button->{_cs_window} = $cs_window;
}

package Gimp::UI;

sub logo {
   &logo_xpm;
}

sub logo_xpm {
   my $window=shift;
   new Gtk::Pixmap(Gtk::Gdk::Pixmap->create_from_xpm_d($window->window,undef,
      #%XPM:logo%
      '108 33 18 1', '  c #030305', '. c #181818', 'X c #222221', 'o c Gray19',
      'O c Gray25', '+ c #4e4e4f', '@ c #606060', '# c #777776', '$ c #8a8a89',
      '% c #a0a09f', '& c #b7b7b7', '* c #c7c7c6', '= c #d5d5d4', '- c gainsboro',
      '; c #e5e5e4', ': c #fbfbf9', '> c Gray100', ', c None',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::,,,,,:::,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#@@$&,,,,,,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:;$@+#&::,,:@+-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,&#@%,,,,,,,#++$+O%&,,,,,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:%.     O-:,%  #:,,,,,,,,,,,,,,,,,,,,,,,,,,,+$#+#+#%,,,,,#OOO+@++%%,,,,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,#,,,:%   .X   X;,=. &:,,,,,,,,,,,,,,,,,,,,,,,,,,+@$%@@@@#,,,,,OoXoo++O##,,,,,,,',
      ',,,,,,&,,,,,,,,,,,,,,,,,,,,,##,,,:.  #::*XO&:,:::::::::;&-::;&-:,:::::**::,,,###&$#+oo#,,,+XXoXX#%@+@*,,,,,,',
      ',,,,,,&&,,,,,,,,,,,,,,,,,,,,##,,*&  O:::::::::&  %:#  %.  #$   %:;  $o  X*:,,,#++#&+o+$,#OO#OoXO#&%@O#,,,,,,',
      ',,,,,,,%%,,,,,,,,,,,,,,,,,,#@@,,;#  $::@OOOOO:&  %:#  .+   .O  .:;   ..  X:,,,,+o+#o+O$#OOO#$O+$%*;%$#$,,,,,',
      ',,,,,,,%%$,,,,,,,,,,,,,,,,@@@@,,;#  $::X    .:&  %:#  *:@  *:+  ;;  O::+  &,,,,+o++#@@$Oo+$&=#&%%&-*$#@,,,,,',
      ',,,,,,,,%$#@,,,,,,,,,,,,@@@@@@,,-%  @::%$$  X:&  %:#  ;:#  ::#  ;;  %:,&  %:,,,@@$@Oo+#XO@&;=%@+#@#%@@$$,,,,',
      ',,,,,,,,%$##@,,,,,,,,,###@@@++,,,;.  *:::%  @:&  %:#  ::#  ::#  ;;  $::%  %,,,,+O#+O+#+o#%&;=&%&#$+%&%%#,,,,',
      ',,,,,,,,$$$#####,,,@@@@@@@@++,,,,:$  .@$+  .=,&  %:#  ::#  ::#  ;;  .%%.  =,,,,@+@%@#%Oo@@-;-*&%&%*;;*%+$,,,',
      ',,,,,,,,*&$$#$%$##@@@@@@@+++O,,,,,:#      .&:,&  %:#  ::#  ::#  ;;  X    @:,,,,#oO$##%#oo@*=&$&$%&=::%&#+$,,',
      ',,,,,,,;::;$*:::&#@@@++++++O,,,,,,,:*#OO+$;:,,=##*:&##::&##::&##:;  $%OO$:,,,,,#oo#&#&$Ooo$*#%$$$#%=&O##+@,,',
      ',,Xo..$:;%*-::;;;%@@++OOOOO,,,,,,,,,,:::::,,,,::::,:::,,:::,,:::,;  $::::,,,,,,,+o@%%*%+O.#$+%*#@+#@oX@++@,,',
      ',X#%+X@;%Oo;:;%+&%@@+OOOOo,,,,,,,,,,,,,,,,,,,::::::::,,,,,,,,,,,,;  $:,,,,,,,,,,%+O@**$oo.##@&%+oooo.X@+O+$,',
      ',X$&+oO$&o@&:=+.#$@++OOOo,,,,,,,,,,,,,,,,,,,,=$$$$$$$=,,,,,,,,,,,;oX%:,,,,,,,,,,,%++@$XXX.X+#&##OOOOoo#@@+,,',
      ',.+@OO+@###$*=#+$@@+OOOOo,,,,,,,,,,::::,,,,,,%       &,,,,,,::::,::::,,,,,,,,,,,,,$@O@O...X.O$#%OXX@@@+@++,,',
      ',.oOO+@@@####$$$@@++OOOoX,,,,,,,,,-&&&&&-:,,,-%%%%%%%-,,,,,:#  -,,,,,,,,,,,,,,,,,,,,&,OXOo#@O+o$oXo,#+@O@%,,',
      ',.oO+@#@@@###@@@@OOOOOOo,,,,,,,,,:%      @:,,,:::::::,,,,,,:#  =,,,,,,,,,,,,,,,,,,,,,,@oXo,OooO$OX+,,+O+%,,,',
      ',,O+@##+++@@@@@+Oo@OOOoX,,,,,,,,,:%       $:,,,,,,,,,,,,,,,:#  =,,,,,,,,,,,,,,,,,,,,,,@oXO,Ooo#,Oo@,,@oO,,,,',
      ',,o+##@OO++++OOoo+#OOoX,,,,,,,,,,:%  $;%  O:,::*=::,,:::::;:#  =,,,,,,,,,,,,,,,,,,,,,,@oo@@ooo@,++@,,%OO,,,,',
      ',,,,OOooooXXXXoO+@+OoX,,,,,,,,,,,:%  $:;  o::$.  X%::%..*o %#  =,,,,,,,,,,,,,,,,,,,,,,@O+,@@@@$,+$%,,,++,,,,',
      ',,,,,,... ..oO+@+OX.,,,,,,,,,,,,,:%  +$O  +:$ .#@  &:$  o  *#  =,,,,,,,,,,,,,,,,,,,,,,#O+,+#&,,,$*@,,,+@,,,,',
      ',,,,,,,,,,.....X.,,,,,,,,,,,,,,,,:%       %;. #--o +:$  .%&:#  =,,,,,,,,,,,,,,,,,,,,,,,@@,,O%&,,%=,,,,@#,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:%    .o%:*       .:$  %:,:#  =,,,,,,,,,,,,,,,,,,,,,,,+@,,,+&,#&,,,,,#@,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:%  $::::,*  #%%%%&:$  *,,:#  =,,,,,,,,,,,,,,,,,,,,,,,#@,,,,@-%&,,,,,$#,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:%  $:,,,,:. #::$%::$  *,,:#  =,,,,,,,,,,,,,,,,,,,,,,,@&,,,,,@-$,,,,,$%,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:%  $:,,,,:%  X.  %:$  *,,:#  =,,,,,,,,,,,,,,,,,,,,,,$%,,,,,$@+%&,,,,@%,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:*@@&:,,,,,:*+XX+&::&@@-,,:%@@;,,,,,,,,,,,,,,,,,,,,,,$=,,,,@@##+#,,,@%&,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::,,,,,,,::::::,,::::,,,::::,,,,,,,,,,,,,,,,,,,,,%$*,,,,$%&,@,,,,@+@,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,+@%#,,,,,,,,,,,,,@@#,,,,',
      ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,o$&,,,,,,,,,,,,,,,,,,,,,'
      #%XPM%
   ))
}

sub _new_adjustment {
   my @adj = eval { @{$_[1]} };

   $adj[2]||=($adj[1]-$adj[0])*0.01;
   $adj[3]||=($adj[1]-$adj[0])*0.01;
   $adj[4]||=0;
   
   new Gtk::Adjustment $_[0],@adj;
}

# find a suitable value for the "digits" value
sub _find_digits {
   my $adj = shift;
   my $digits = log($adj->step_increment || 1)/log(0.1);
   $digits>0 ? int $digits+0.9 : 0;
}

sub help_window(\$$$) {
   my($helpwin,$blurb,$help)=@_;
   unless ($$helpwin) {
      $$helpwin = new Gtk::Dialog;
      $$helpwin->set_title(__("Help for ").$Gimp::function);
      $$helpwin->action_area->set_border_width(2);
      my($font,$b);

      $b = new Gtk::Text;
      $b->set_editable (0);
      $b->set_word_wrap (1);

      $font = load Gtk::Gdk::Font __"9x15bold";
      $font = fontset_load Gtk::Gdk::Font __"-*-courier-medium-r-normal--*-120-*-*-*-*-*" unless $font;
      $font = $b->style->font unless $font;
      my $cs = new Gtk::ScrolledWindow undef,undef;
      $cs->set_policy(-automatic,-automatic);
      $cs->add($b);
      $$helpwin->vbox->add($cs);
      $b->insert($font,$b->style->fg(-normal),undef,__"BLURB:\n\n$blurb\n\nHELP:\n\n$help");
      $b->set_usize($font->string_width('M')*80,($font->ascent+$font->descent)*26);

      my $button = Gtk::Button->new(__"OK");
      signal_connect $button "clicked",sub { hide $$helpwin };
      $$helpwin->action_area->add($button);
      
      $$helpwin->signal_connect("destroy",sub { undef $$helpwin });

      Gtk->idle_add(sub {
         require Gimp::Pod;
         my $pod = new Gimp::Pod;
         my $text = $pod->format;
         if ($text) {
            $b->insert($font,$b->style->fg(-normal),undef,__"\n\nEMBEDDED POD DOCUMENTATION:\n\n");
            $b->insert($font,$b->style->fg(-normal),undef,$text);
         }
      });
   }

   $$helpwin->show_all();
}
sub interact($$$$@) {
   local $^W=0;
   my($function)=shift;
   my($blurb)=shift;
   my($help)=shift;
   my(@types)=@{shift()};
   my(@getvals,@setvals,@lastvals,@defaults);
   my($button,$box,$bot,$g);
   my($helpwin);
   my $res=0;
   my @res;

   Gimp::gtk_init;

   my $gimp_10 = Gimp->major_version==1 && Gimp->minor_version==0;
   
   my $t = new Gtk::Tooltips;
   my $w = new Gtk::Dialog;
   my $accel = new Gtk::AccelGroup;

   for(;;) {
     $accel->attach($w);

     set_title $w $Gimp::function;
     $w->action_area->set_border_width(2);
     $w->action_area->set_homogeneous(0);

     my $h = new Gtk::HBox 0,2;
     $h->add(new Gtk::Label Gimp::wrap_text($blurb,40));
     $w->vbox->pack_start($h,1,1,0);
     realize $w;
     my $l = logo($w);
     $h->add($l);
     
     $g = new Gtk::Table scalar@types,2,0;
     $g->border_width(4);
     $w->vbox->pack_start($g,1,1,0);

     for(@types) {
        my($label,$a);
        my($type,$name,$desc,$default,$extra)=@$_;
        my($value)=shift;
        
        local *new_PF_STRING = sub {
           my $e = new Gtk::Entry;
           set_usize $e 0,25;
           push(@setvals,sub{set_text $e defined $_[0] ? $_[0] : ""});
           #select_region $e 0,1;
           push(@getvals,sub{get_text $e});
           $a=$e;
        };

        if($type == PF_ADJUSTMENT) { # support for scm2perl
           my(@x)=@$default;
           $default=shift @x;
           $type = pop(@x) ? PF_SPINNER : PF_SLIDER;
           $extra=[@x];
        }
        
        $value=$default unless defined $value;
        # massage label text a small bit (works only for english)
        $label="$name: ";
        $label =~ y/_/ /; $label =~ s/^(\w)/\U$1/g;
        
        if($type == PF_INT8		# perl just maps
        || $type == PF_INT16		# all this crap
        || $type == PF_INT32		# into the scalar
        || $type == PF_FLOAT		# domain.
        || $type == PF_STRING) {	# I love it
           &new_PF_STRING;
           
        } elsif($type == PF_FONT) {
           my $fs=new Gtk::FontSelectionDialog __"Font Selection Dialog ($desc)";
           my $def = __"-*-helvetica-medium-r-normal-*-34-*-*-*-p-*-iso8859-1";
           my $val;
           
           my $l=new Gtk::Label "!error!";
           my $setval = sub {
              $val=$_[0];
              unless (defined $val && $fs->set_font_name ($val)) {
                 warn __"Illegal default font description for $function: $val\n" if defined $val;
                 $val=$def;
                 $fs->set_font_name ($val);
              }
              
              my($n,$t)=Gimp::xlfd_size($val);
              $l->set((split(/-/,$val))[2]."\@$n".($t ? "p" : ""));
           };
           
           $fs->ok_button->signal_connect("clicked",sub {$setval->($fs->get_font_name); $fs->hide});
           $fs->cancel_button->signal_connect("clicked",sub {$fs->hide});
           
           push(@setvals,$setval);
           push(@getvals,sub { $val });
           
           $a=new Gtk::Button;
           $a->add($l);
           $a->signal_connect("clicked", sub { show $fs });
           
        } elsif($type == PF_SPINNER) {
           my $adj = _new_adjustment ($value,$extra);
           $a=new Gtk::SpinButton $adj,1,0;
           $a->set_digits (_find_digits $adj);
           $a->set_usize (120,0);
           push(@setvals,sub{$adj->set_value($_[0])});
           push(@getvals,sub{$adj->get_value});
           
        } elsif($type == PF_SLIDER) {
           my $adj = _new_adjustment ($value,$extra);
           $a=new Gtk::HScale $adj;
           $a->set_digits (_find_digits $adj);
           $a->set_usize (120,0);
           push(@setvals,sub{$adj->set_value($_[0])});
           push(@getvals,sub{$adj->get_value});
           
        } elsif($type == PF_COLOR) {
           $a=new Gtk::HBox (0,5);
           my $b=new Gimp::UI::ColorSelectButton -width => 90, -height => 18;
           $a->pack_start ($b,1,1,0);
           $default = [216, 152, 32] unless defined $default;
           push(@setvals,sub{$b->set('color', "@{defined $_[0] ? Gimp::canonicalize_color $_[0] : [216,152,32]}")});
           push(@getvals,sub{[split ' ',$b->get('color')]});
           set_tip $t $b,$desc;
           
           my $c = new Gtk::Button __"FG";
           signal_connect $c "clicked", sub {
             $b->set('color', "@{Gimp::Palette->get_foreground}");
           };
           set_tip $t $c,__"get current foreground colour from the gimp";
           $a->pack_start ($c,1,1,0);
           
           my $d = new Gtk::Button __"BG";
           signal_connect $d "clicked", sub {
             $b->set('color', "@{Gimp::Palette->get_background}");
           };
           set_tip $t $d,__"get current background colour from the gimp";
           $a->pack_start ($d,1,1,0);
           
        } elsif($type == PF_TOGGLE) {
           $a=new Gtk::CheckButton $desc;
           push(@setvals,sub{set_state $a ($_[0] ? 1 : 0)});
           push(@getvals,sub{state $a eq "active"});
           
        } elsif($type == PF_RADIO) {
           my $b = new Gtk::HBox 0,5;
           my($r,$prev);
           my $prev_sub = sub { $r = $_[0] };
           while (@$extra) {
              my $label = shift @$extra;
              my $value = shift @$extra;
              my $radio = new Gtk::RadioButton $label;
              $radio->set_group ($prev) if $prev;
              $b->pack_start ($radio,1,0,5);
              $radio->signal_connect(clicked => sub { $r = $value });
              my $prev_sub_my = $prev_sub;
              $prev_sub = sub { $radio->set_active ($_[0] eq $value); &$prev_sub_my };
              $prev = $radio;
           }
           $a = new Gtk::Frame;
           $a->add($b);
           push(@setvals,$prev_sub);
           push(@getvals,sub{$r});
           
        } elsif($type == PF_IMAGE) {
           my $res;
           $a=new Gtk::HBox (0,5);
           my $b=new Gtk::OptionMenu;
           $b->set_menu(new Gimp::UI::ImageMenu(sub {1},-1,\$res));
           $a->pack_start ($b,1,1,0);
           push(@setvals,sub{});
           push(@getvals,sub{$res});
           set_tip $t $b,$desc;
           
#           my $c = new Gtk::Button "Load";
#           signal_connect $c "clicked", sub {$res = 2; main_quit Gtk};
##           $g->attach($c,1,2,$res,$res+1,{},{},4,2);
#           $a->pack_start ($c,1,1,0);
#           set_tip $t $c,"Load an image into the Gimp";
           
        } elsif($type == PF_LAYER) {
           my $res;
           $a=new Gtk::OptionMenu;
           $a->set_menu(new Gimp::UI::LayerMenu(sub {1},-1,\$res));
           push(@setvals,sub{});
           push(@getvals,sub{$res});
           
        } elsif($type == PF_CHANNEL) {
           my $res;
           $a=new Gtk::OptionMenu;
           $a->set_menu(new Gimp::UI::ChannelMenu(sub {1},-1,\$res));
           push(@setvals,sub{});
           push(@getvals,sub{$res});
           
        } elsif($type == PF_DRAWABLE) {
           my $res=13;
           $a=new Gtk::OptionMenu;
           $a->set_menu(new Gimp::UI::DrawableMenu(sub {1},-1,\$res));
           push(@setvals,sub{});
           push(@getvals,sub{$res});
           
        } elsif($type == PF_PATTERN) {
           if ($gimp_10) {
              &new_PF_STRING;
           } else {
              $a=new Gimp::UI::PatternSelect -active => defined $value ? $value : (Gimp->patterns_get_pattern)[0];
              push(@setvals,sub{$a->set('active',$_[0])});
              push(@getvals,sub{$a->get('active')});
           }
           
        } elsif($type == PF_BRUSH) {
           if ($gimp_10) {
              &new_PF_STRING;
           } else {
              $a=new Gimp::UI::BrushSelect -active =>  defined $value ? $value : (Gimp->brushes_get_brush)[0];
              push(@setvals,sub{$a->set('active',$_[0])});
              push(@getvals,sub{$a->get('active')});
           }
           
        } elsif($type == PF_GRADIENT) {
           if ($gimp_10) {
              &new_PF_STRING;
           } else {
              $a=new Gimp::UI::GradientSelect -active => defined $value ? $value : (Gimp->gimp_gradients_get_active)[0];
              push(@setvals,sub{$a->set('active',$_[0])});
              push(@getvals,sub{$a->get('active')});
           }
           
        } elsif($type == PF_CUSTOM) {
           my (@widget)=&$extra;
           $a=$widget[0];
           push(@setvals,$widget[1]);
           push(@getvals,$widget[2]);
           
        } elsif($type == PF_FILE) {
           &new_PF_STRING;
           my $s = $a;
           $a = new Gtk::HBox 0,5;
           $a->add ($s);
           my $b = new Gtk::Button __"Browse";
           $a->add ($b);
           my $f = new Gtk::FileSelection $desc;
           $b->signal_connect (clicked => sub { $f->set_filename ($s->get_text); $f->show_all });
           $f->ok_button    ->signal_connect (clicked => sub { $f->hide; $s->set_text ($f->get_filename) });
           $f->cancel_button->signal_connect (clicked => sub { $f->hide });
           
        } elsif($type == PF_TEXT) {
           $a = new Gtk::Frame;
           my $h = new Gtk::VBox 0,5;
           $a->add($h);
           my $e = new Gtk::Text;
           my %e;
           %e = $$extra if ref $extra eq "HASH";

           my $sv = sub { 
              my $t = shift,
              $e->delete_text(0,-1);
              $e->insert_text($t,0);
           };
           my $gv = sub {
              $e->get_chars(0,-1);
           };

           $h->add ($e);
           $e->set_editable (1);

           my $buttons = new Gtk::HBox 1,5;
           $h->add($buttons);

           my $load = new Gtk::Button __"Load"; $buttons->add($load);
           my $save = new Gtk::Button __"Save"; $buttons->add($save);
           my $edit = new Gtk::Button __"Edit"; $buttons->add($edit);

           $edit->signal_connect(clicked => sub {
              my $editor = $ENV{EDITOR} || "vi";
              my $tmp = Gimp->temp_name("txt");
              open TMP,">$tmp" or die __"FATAL: unable to create $tmp: $!\n"; print TMP &$gv; close TMP;
              $w->hide;
              main_iteration Gtk;
              system ('xterm','-T',"$editor: $name",'-e',$editor,$tmp);
              $w->show;
              if (open TMP,"<$tmp") {
                 local $/; &$sv(scalar<TMP>); close TMP;
              } else {
                 Gimp->message(__"unable to read temporary file $tmp: $!");
              }
           });

           my $filename = ($e{prefix} || eval { Gimp->directory } || ".") . "/";
           
           my $f = new Gtk::FileSelection __"Fileselector for $name";
           $f->set_filename($filename);
           $f->cancel_button->signal_connect (clicked => sub { $f->hide });
           my $lf =sub {
              $f->hide;
              my $fn = $f->get_filename;
              if(open TMP,"<$fn") {
                 local $/; &$sv(scalar<TMP>);
                 close TMP;
              } else {
                 Gimp->message(__"unable to read '$fn': $!");
              }
           };
           my $sf =sub {
              $f->hide;
              my $fn = $f->get_filename;
              if(open TMP,">$fn") {
                 print TMP &$gv;
                 close TMP;
              } else {
                 Gimp->message(__"unable to create '$fn': $!");
              }
           };
           my $lshandle;
           $load->signal_connect (clicked => sub {
              $f->set_title(__"Load $name");
              $f->ok_button->signal_disconnect($lshandle) if $lshandle;
              $lshandle=$f->ok_button->signal_connect (clicked => $lf);
              $f->show_all;
           });
           $save->signal_connect (clicked => sub {
              $f->set_title(__"Save $name");
              $f->ok_button->signal_disconnect($lshandle) if $lshandle;
              $lshandle=$f->ok_button->signal_connect (clicked => $sf);
              $f->show_all;
           });

           push @setvals,$sv;
           push @getvals,$gv;

        } else {
           $label=__"Unsupported argumenttype $type";
           push(@setvals,sub{});
           push(@getvals,sub{$value});
        }
        
        push(@lastvals,$value);
        push(@defaults,$default);
        $setvals[-1]->($value);
        
        $label=new Gtk::Label $label;
        $label->set_alignment(0,0.5);
        $g->attach($label,0,1,$res,$res+1,{},{},4,2);
        $a && do {
           set_tip $t $a,$desc;
           $g->attach($a,1,2,$res,$res+1,["expand","fill"],["expand","fill"],4,2);
        };
        $res++;
     }
     
     my $v = new Gtk::HBox 0,4;
     $w->vbox->pack_start($v,0,0,4);

     my $hbbox=new Gtk::HButtonBox;
     $hbbox->set_spacing(4);
     $v->pack_end($hbbox,0,0,2);
     
     $button = new Gtk::Button __"Defaults";
     signal_connect $button "clicked", sub {
       for my $i (0..$#defaults) {
         $setvals[$i]->($defaults[$i]);
       }
     };
     $hbbox->pack_start($button,0,0,0);
     set_tip $t $button,__"Reset all values to their default";
     
     $button = new Gtk::Button __"Previous";
     signal_connect $button "clicked", sub {
       for my $i (0..$#lastvals) {
         $setvals[$i]->($lastvals[$i]);
       }
     };
     $hbbox->pack_start($button,0,0,0);
     set_tip $t $button,__"Restore values to the previous ones";
     
     signal_connect $w "destroy", sub { main_quit Gtk };

     my $hbbox = new Gtk::HButtonBox;
     $hbbox->set_spacing(4);
     $w->action_area->pack_start($hbbox,0,0,0);
     show $hbbox;

     $button = new Gtk::Button __"Help";
     $hbbox->pack_start($button,0,0,0);
     signal_connect $button "clicked", sub { help_window($helpwin,$blurb,$help) };
     can_default $button 1;
     
     my $hbbox = new Gtk::HButtonBox;
     $hbbox->set_spacing(4);
     $w->action_area->pack_end($hbbox,0,0,0);
     show $hbbox;

     $button = new Gtk::Button __"OK";
     signal_connect $button "clicked", sub {$res = 1; hide $w; main_quit Gtk};
     $hbbox->pack_start($button,0,0,0);
     can_default $button 1;
     grab_default $button;
     add $accel 0xFF0D, [], [], $button, "clicked";
     
     $button = new Gtk::Button __"Cancel";
     signal_connect $button "clicked", sub {hide $w; main_quit Gtk};
     $hbbox->pack_start($button,0,0,0);
     can_default $button 1;
     add $accel 0xFF1B, [], [], $button, "clicked";
     
     $res=0;
     
     show_all $w;
     main Gtk;
     
     if ($res == 0) {
        @res = ();
        last;
     }
     @_ = map {&$_} @getvals;
     if ($res == 1) {
        @res = (1,@_);
        last;
     }
#     Gimp->file_load(&Gimp::RUN_INTERACTIVE,"","");
   }
   @getvals=
   @setvals=
   @lastvals=();
   @res;
}

1;

=head1 AUTHOR

Marc Lehmann <pcg@goof.com>. The ColorSelectButton code (now
rebundled into the Gtk module) is written by Dov Grobgeld
<dov@imagic.weizmann.ac.il>, with modifications by Kenneth Albanowski
<kjahds@kjahds.com>.

=head1 SEE ALSO

perl(1), L<Gimp>.

=cut
