The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/app/bin/perl

#BEGIN {$^W=1};

use Gimp::Feature qw(perl-5.005 gtk-1.2);
use Gimp ('__','N_');
use Gimp::Fu;
use Gtk;

$VERSION=0.91;

#Gimp::set_trace(TRACE_ALL);

my $window;	# the main window
my $current;
my $list_func, $find_func, $attach_func, $detach_func;
my $clist;
my $name, $flags, $data;

sub deserialize {
   local $_ = shift;
   my $res;
   eval {
      while (length ($_)) {
         my $type = unpack ("C", substr $_,0,1,"");
         if ($type == 0) {
            $res.="END";
            last;
         } elsif ($type == 1) {
            $res.="INT8 ".(unpack ("C", substr $_,0,1,""));
         } elsif ($type == 2) {
            $res.="INT16 ".(unpack ("n", substr $_,0,2,""));
         } elsif ($type == 3) {
            $res.="INT32 ".(unpack ("N", substr $_,0,4,""));
         } elsif ($type == 101) {
            $res.="INT32 ".(unpack ("N", substr $_,0,4,""));
         } else {
            die "unknown serialize type\n";
         }
         print "RES: $res\n";
         print "Type = $type\n";
      }
   };
   $@ ? $@ : $res;
}

sub is_binary {
   $_[0] =~ y/\x00-\x09\x0b-\x1f//;
}

sub is_gserialize {
   0;
}

sub format_flags {
   sprintf "0x%08x",$_[0];
}

sub unformat_flags {
   eval shift;
}

sub format_plain {
   my $x=shift;
   $x=~s/\r/\\r/g;
   $x=~s/\n/\\n/g;
   $x=~s/\t/\\t/g;
   $x=~s/([\x00-\x1f])/sprintf "\\x%02x",ord($1)/eg;
   $x=~s/\\n/\n/g;
   $x;
}

sub unformat_plain {
   my $x=shift;
   $x=~s/\\r/\r/g;
   $x=~s/\\t/\t/g;
   $x=~s/\\x([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
   $x;
}

sub format_hex {
   join (" ", map { sprintf "%02x",ord($_) } split //,shift);
}

sub unformat_hex {
   my $x = shift;
   $x =~ y/0-9a-fA-F//cd;
   $x=pack("H*",$x);
   $x;
}

sub format_gserialize {
   format_hex(@_);
}

sub unformat_gserialize {
   unformat_hex(@_);
}

sub escape($) {
   my $x=shift;
   is_gserialize($x) ? format_gserialize($x)
                     : is_binary($x) ? format_hex($x)
                                     : format_plain($x);
}

sub refresh_names {
   undef $parasite;
   $name->set_text ("");
   $flags->set_text ("");
   $data->set_text ("");
   $clist->freeze;
   $clist->clear;
   my @list = sort &$list_func($current);
   for (@list) {
      $clist->append($_);
   }
   $clist->show_all;
   $clist->thaw;
}

sub new_Entry {
   my $hbox = new Gtk::HBox 0,0;
   my $label = new Gtk::Label $_[1];
   my $name = $_[2];

   $label->set_alignment (0, 0.5);

   $hbox->pack_start ($label, 0, 0, 0);
   $hbox->pack_start ($name , 1, 1, 0);

   $_[0]->add ($hbox);

   $name->set_editable (0);
   $name;
}

sub create_main {
   my $b;
   my $t;

   $t = new Gtk::Tooltips;
   my $w = new Gtk::Dialog;

   $ex = $w->style->font->string_width ('Mn')*0.5;
   $eX = $w->style->font->ascent + $w->style->font->descent + 10;

   $window = $w;

   $w->set_title("Parasite Editor - version $VERSION");
   $w->signal_connect("destroy",sub {main_quit Gtk});

   $b = new Gtk::Button "Close";
   $w->action_area->add($b);
   $b->signal_connect("clicked",sub {main_quit Gtk});

   my $v = new Gtk::VBox (0,5);
   $w->vbox->add ($v);

   my $top = new Gtk::HBox (0,5);
   $v->pack_start($top,0,1,5);
   my $bot = new Gtk::HBox (0,5);
   $v->add($bot);
   my $cmd = new Gtk::HBox (0,5);
   $v->pack_start($cmd,0,1,5);

   $top->set_usize (-1, $eX+5);

   $clist = new Gtk::CList (1);
   $clist->set_selection_mode (-single);
   $clist->signal_connect ("select_row" => sub {
      $parasite = $_[0]->get_text($_[1],$_[2]);
      my ($par) = &$find_func($current,$parasite);
      $name->set_text ($par->[0]);
      $flags->set_text (sprintf "0x%08x", $par->[1]);
      $data->set_text (escape $par->[2]);
   });

   my $cs = new Gtk::ScrolledWindow undef,undef;
   $cs->set_policy(-automatic,-automatic);
   $cs->add ($clist);
   $bot->add ($cs);

   my $parbox = new Gtk::VBox (0,5);
   $bot->set_usize ($ex*80,$eX*4);
   $bot->add ($parbox);

   $name  = new_Entry $parbox,"Name: ",new Gtk::Entry;
   $flags = new_Entry $parbox,"Flags: ",new Gtk::Entry;
   $data  = new_Entry $parbox,"Data: ",new Gtk::Entry;

   {
      my $menu = new Gtk::Menu;
      my $item;
      my $subtype = new Gtk::OptionMenu;

      my $set_submenu = sub {
         my $submenu = new Gtk::Menu;
         for(@_) {
            my $cur = $_->[0];
            my $item = new Gtk::MenuItem $_->[1];
            $item->signal_connect ("activate" => sub { $current = $cur; refresh_names });
            $submenu->append ($item);
         }
         $subtype->set_menu($submenu);
         $submenu->show_all;
         if(@_) {
            $submenu->get_active->signal_emit("activate");
         } else {
            undef $current;
            refresh_names;
         }
      };

      $item = new Gtk::MenuItem "Global";
      my $act_global = sub {
         $list_func   = sub { Gimp->parasite_list   };
         $find_func   = sub { shift; Gimp->parasite_find   (shift) };
         $attach_func = sub { shift; Gimp->parasite_attach (shift) };
         $detach_func = sub { shift; Gimp->parasite_detach (shift) };
         &$set_submenu();
      };
      $item->signal_connect("activate", $act_global);
      $menu->append($item);

      $item = new Gtk::MenuItem "Image";
      $item->signal_connect("activate", sub {
         $list_func   = sub { $_[0] ? shift->parasite_list : () };
         $find_func   = sub { shift->parasite_find   (shift) };
         $attach_func = sub { shift->parasite_attach (shift) };
         $detach_func = sub { shift->parasite_detach (shift) };
         &$set_submenu(map [$_,"$$_: ".$_->get_filename],Gimp->image_list);
      });
      $menu->append($item);

      $item = new Gtk::MenuItem "Drawable";
      $item->signal_connect("activate", sub {
         $list_func   = sub { $_[0] ? shift->parasite_list : () };
         $find_func   = sub { shift->parasite_find   (shift) };
         $attach_func = sub { shift->parasite_attach (shift) };
         $detach_func = sub { shift->parasite_detach (shift) };
         &$set_submenu(map [$_,$_->image->get_filename." / "
                               .($_->is_layer ? $_->layer_get_name : $_->channel_get_name)],
                           map (($_->get_layers,$_->get_channels),Gimp->image_list));
      });
      $menu->append($item);

      my $type = new Gtk::OptionMenu;
      $type->set_menu ($menu);

      $top->add($type);
      $top->add($subtype);

      &$act_global;
   }

   local *addcmd = sub {
      my $label = new Gtk::Button shift;
      $label->signal_connect(clicked => shift);
      $cmd->add($label);
   };

   addcmd ("New", sub {Gimp->message("NYI!!")});
   addcmd ("Edit", sub {
      (new ParasiteEditor($find_func,$attach_func,$detach_func,$current,$parasite))->show_all
         if $parasite ne "";
   });
   addcmd ("Delete", sub {$detach_func->($current,$parasite) if $parasite; refresh_names});

   show_all $w;
}

register "extension_parasite_editor",
         "Parasite Editor",
         "This plug-in can be used to view (and in a future version edit) existing parasites",
         "Marc Lehmann",
         "Marc Lehmann",
         $VERSION,
         N_"<Toolbox>/Xtns/Parasite Editor...",
         "",
         [],
         [],
         ['gimp-1.1'],
         sub {

   Gimp::gtk_init;
   create_main;
   main Gtk;

   ();
};

package ParasiteEditor;

use Gtk;
use Gimp::basewidget Gtk::Dialog;

my $init;

sub unformat {
   my $self=shift;
   $self->{data_} = $self->{unformat}->($self->{-data}->get_chars(0,-1)) if $self->{unformat};
   $self->{name_} = $self->{-name}->get_text;
   $self->{flags_} = ::unformat_flags($self->{-flags}->get_text);
}

sub format {
   my $self=shift;
   $self->{format}->($self->{data_});
}

sub refresh {
   my $self=shift;
   $self->{-name}->set_text($self->{name_});
   $self->{-flags}->set_text(::format_flags $self->{flags_});
   $self->{-data}->delete_text(0,-1);
   $self->{-data}->insert_text($self->format,0);
}

sub undirty {
   my $self=shift;
}

sub GTK_OBJECT_INIT {
   shift unless ref $_[0]; # care for "old" Gtk modules
   my $self = shift;
   @{$self}{qw(find_func attach_func detach_func current parasite)}=@$init;
   @{$self}{qw(name  flags  data)}=
   @{$self}{qw(name_ flags_ data_)}=@{$self->{find_func}->(@{$self}{qw(current parasite)})};

   my $table = new Gtk::Table (2,3,0);
   $table->attach(new Gtk::Label("Name")	,0,1,0,1,{},{},0,0);
   $table->attach(new Gtk::Label("Flags")	,0,1,1,2,{},{},0,0);
   $table->attach(new Gtk::Label("Format")	,0,1,2,3,{},{},0,0);
   $table->attach(new Gtk::Label("Data")	,0,1,3,4,{},{},0,0);

   $self->{-name} = new Gtk::Entry;
   $self->{-flags} = new Gtk::Entry;
   $self->{-data} = new Gtk::Text;

   $self->{format} = \&::format_plain;
   $self->refresh;

   my $format = new Gtk::HBox 0,5;
   my $radio;
   local *newformat = sub {
      my ($label,$in,$out,$enable)=@_;
      my $r = new Gtk::RadioButton $label,$radio ? $radio : ();
      $format->add($r);
      $r->signal_connect(clicked => sub {
         $self->unformat;
         $self->{format}=$in;
         $self->{unformat}=$out;
         $self->refresh;
      });
      $r->signal_emit_by_name("clicked") if $enable;
      $radio = $r;
   };

   $table->attach($self->{-name}	,1,2,0,1,{},{},0,0);
   $table->attach($self->{-flags}	,1,2,1,2,{},{},0,0);
   $table->attach($format		,1,2,2,3,{},{},0,0);
   $table->attach($self->{-data}	,1,2,3,4,['fill'],{},0,0);

   $detect = ::is_gserialize($self->{data_}) ? 3
           : ::is_binary($self->{data_})     ? 2
           :                                   1;

   newformat("Text",\&::format_plain,\&::unformat_plain,$detect==1);
   newformat("Hex",\&::format_hex,\&::unformat_hex,$detect==2);
   newformat("GSerialize",\&::format_gserialize,\&::unformat_gserialize,$detect==3);

   $self->vbox->add($table);

   $self->refresh;

   $self->{-data}->set_editable(1);

   my $b = new Gtk::Button "OK";
   $b->signal_connect(clicked => sub {
      $self->unformat;
      $self->{detach_func}->(@{$self}{'current','parasite'});
      $self->{attach_func}->($self->{'current'},[@{$self}{'name_','flags_','data_'}]);
      main::refresh_names;
      destroy $self;
   });
   $self->action_area->add($b);
   my $b = new Gtk::Button "Cancel";
   $b->signal_connect(clicked => sub { destroy $self });
   $self->action_area->add($b);
}

sub new {
   $init=\@_;
   Gtk::Object::new shift;
}

package main;

exit main;