#!/usr/bin/perl

# not fully functional yet.  

use Gimp::Feature qw(perl-5.005 gtk-2.0);
use Gimp (':consts','__','N_');
use Gimp::Fu;
use Gtk2;
use Gtk2::Gdk::Keysyms;
use Glib;
#use Gimp::UI (); # for the logo
use POSIX 'strftime';

#Gimp::set_trace(TRACE_ALL);

#my $ex;		# average font width for default font
#my $ey;		# average font height for default font

my $window;	# the main window
my $clist;	# the list of completions
my $rlist;	# the results list
my $inputline;	# the input entry
my $result;	# the result entry
my $synopsis;	# the synopsis label
my $statusbar;	# the statusbar
my $cinfo;	# command info

#my $idle;	# the idle function id

my($blurb,$help,$author,$copyright,$date,$type,$args,$results);
my @args;	# the arguments of the current function

my @function;	# the names of all functions
my %function;	# the same as hash
my @completion;	# list of completions
my @compdata;	# index completion -> data
my %plugin_info;# function -> [...]

sub refresh {
   undef %function;
   @function = Gimp->procedural_db_query("","","","","","","");
   @function{@function}=(1) x @function;
   eval {
      my ($a, $b, $c, $d, $e, $f) = Gimp->plugins_query("");
      for $i (0..$#$a) {
         $plugin_info{$f->[$i]} = [map $_->[$i], $a, $b, $c, $d, $e];
      }
   }
}

sub get_words {
   my $text = $inputline->get_text;
   my $i = 0;
   my($p,$idx,$pos);
   my $word;
   my @words;
#   substr($text,$inputline->get('text_position'),0,"\0");
   substr($text,
          $inputline->get_position, #magic makes $inputline a GtkEditable
	  0,"\0");
   while ($text =~ /("(?:[^"\\]*(?:\\.[^"\\]*)*)")[ ,]*|([^ ,]+)[ ,]*|[ ,]+/g) {
      $word = defined $1 ? $1 : $2;
      if (($p = index($word, "\0")) >= 0) {
         $idx=$i; $pos=$p;
         substr ($word, $p, 1, "");
      }
      $i++;
      push(@words,$word);
   }
   ($idx,$pos,@words);
}

sub set_words {
   my $text=shift;
   $text.=" ".join(",",@_) if scalar@_;
   my $pos=index($text,"\0");
   if ($pos) {
      substr($text,$pos,1,"");
      $inputline->set_text($text);
      $inputline->set_position($pos);
   } else {
      $inputline->set_text($text);
   }
}

my $last_func;
my $last_arg;

my %type2str = (
   &PDB_BOUNDARY    => 'BOUNDARY',
   &PDB_CHANNEL     => 'CHANNEL',
   &PDB_COLOR       => 'COLOR',
   &PDB_DISPLAY     => 'DISPLAY',
   &PDB_DRAWABLE    => 'DRAWABLE',
   &PDB_FLOAT       => 'FLOAT',
   &PDB_IMAGE       => 'IMAGE',
   &PDB_INT32       => 'INT32',
   &PDB_FLOATARRAY  => 'FLOATARRAY',
   &PDB_INT16       => 'INT16',
   &PDB_PARASITE    => 'PARASITE',
   &PDB_STRING      => 'STRING',
   &PDB_PATH        => 'PATH',
   &PDB_INT16ARRAY  => 'INT16ARRAY',
   &PDB_INT8        => 'INT8',
   &PDB_INT8ARRAY   => 'INT8ARRAY',
   &PDB_LAYER       => 'LAYER',
   &PDB_REGION      => 'REGION',
   &PDB_STRINGARRAY => 'STRINGARRAY',
   &PDB_SELECTION   => 'SELECTION',
   &PDB_STATUS      => 'STATUS',
   &PDB_INT32ARRAY  => 'INT32ARRAY',
);

sub setheight {
   my($w,$y)=@_;
#   $w->set_usize(-1, ($w->style->font->ascent + $w->style->font->descent) * $y);

}

sub new_cinfo {
#   $cinfo->freeze;
#   $cinfo->clear;

   my $add_split = sub {
      my($t,$n,$d)=@_;
      $d=~s/^(.{40,60})[ \t]*([\{[:\(])/$1\n$2/mg;
      for(split/\n/,Gimp::wrap_text($d,60)) {
#         $cinfo->append("",$t,$n,$_);
         $t=$n="";
      }
   };
   
   if($args) {
#      $cinfo->append("In:","","","");
      for(@args) {
         $add_split->($type2str{$_->[0]},$_->[1],$_->[2]);
      }
   }
   if($results) {
#      $cinfo->append("Out:","","","");
      for(0..$results-1) {
         my($type,$name,$desc)=Gimp->procedural_db_proc_val ($last_func, $_);
         $add_split->($type2str{$type},$name,$desc);
      }
   }

#   $cinfo->thaw;
}

sub set_current_function {
   my $fun = shift;
   return if $last_func eq $fun || !$function{$fun};
   $last_func = $fun;
   $last_arg = 0;
   @args=();
   eval {
      $function{$fun} or die;
      ($blurb,$help,$author,$copyright,$date,$type,$args,$results)=
         Gimp->procedural_db_proc_info($fun);
      $blurb_label->set_text($blurb);
      for(0..$args-1) {
         push(@args,[Gimp->procedural_db_proc_arg($fun,$_)]);
      }
      new_cinfo;

      $help_text->delete(0,-1);
      $help_text->insert($help,0);
      $author_label->set($author);
      $copyright_label->set($copyright);
      $date_label->set($date);
      eval {
         my ($menupath, $accel, $path, $imagetypes, $mtime) = @{$plugin_info{$fun}};
         $menupath_label->set($menupath);
         $accelerator_label->set($accel);
         $plugin_path_label->set($path);
         $imagetypes_label->set($imagetypes);
         $last_modified_label->set(strftime("%Y-%m-%d %H:%M:%S (%Z)", localtime ($mtime)));
      };
      warn $@ if $@;
   };
   warn $@ if $@;
}

sub set_clist {
   #$clist->freeze;
   # $clist->clear;
   @completion = ();
   @compdata = ();
   while(@_) {
#      $clist->append(@_[0]);
      push @completion, shift;
      push @compdata, shift;
   }
   #$clist->thaw;
}

sub complete_function {
   my $name = shift;
   $name=~s/[-_]/[-_]/g;
   my @matches = eval { sort grep /$name/i,@function };
   if(@matches>1) {
      set_clist map(($_,$_),@matches);
      $synopsis->set_text(scalar@matches.__" matching functions");
   } else {
      set_clist @matches,@matches;
      $synopsis->set_text($matches[0].__" (press Tab to complete)");
   }
}

sub complete_type {
   my($type,$name,$desc)=@_;

   if($type==PDB_IMAGE) {
      set_clist(map(("$$_: ".$_->get_filename,$$_),Gimp->image_list));
   } elsif($type==PDB_LAYER) {
      set_clist(map { my $i = $_; map(("$$_: ".$i->get_filename."/".$_->get_name,$$_),$i->get_layers)} Gimp->image_list);
   } elsif($type==PDB_CHANNEL) {
      set_clist(map { my $i = $_; map(("$$_: ".$i->get_filename."/".$_->get_name,$$_),$i->get_channels)} Gimp->image_list);
   } elsif($type==PDB_DRAWABLE) {
      set_clist(map { my $i = $_; map(("$$_: ".$i->get_filename."/".$_->get_name,$$_),($i->get_layers,$i->get_channels))} Gimp->image_list);
   } elsif ($type==PDB_INT32) {
      if ($name eq "run_mode") {
         set_clist("RUN_NONINTERACTIVE","RUN_NONINTERACTIVE",
                   "RUN_INTERACTIVE","RUN_INTERACTIVE",
                   "RUN_WITH_LAST_VALS","RUN_WITH_LAST_VALS");
      } elsif ($desc=~s/(?::\s*)?{(.*)}.*?$//) {
         $_=$1;
         my @args;
         while(s/^.*?([A-Za-z_-]+)\s*\(\s*(\d+)\s*\)//) {
            push(@args,"$2: $1",$2);
         }
         set_clist(@args);
      } else {
         set_clist;
      }
   } else {
      set_clist;
   }
   $synopsis->set($desc);
}

sub update_completion {
   my($idx,$pos,@words)=get_words();

   return unless $idx ne $last_arg;
   $last_arg=$idx;

   set_current_function $words[0];

   if ($idx == 0) {
      complete_function($words[0]);
   } elsif ($idx>@args) {
      $synopsis->set(__"too many arguments");
      set_clist;
   } else {
      complete_type(@{$args[$idx-1]});
   }
}

sub do_completion {
   update_completion;

   my($idx,$pos,@words)=get_words();
   my($word)=$words[$idx];

   $word=~s/[-_]/[-_]/g;
   my(@matches)=grep /$word/i,@completion;
   my $new;
   if (@matches>1) {
      if (join("\n",@matches) =~ ("^(".$words[$idx].".*).*?".("\n\\1.*" x scalar@matches-1))) {
         $new=$1;
      }
   } elsif(@matches==1) {
      $new=$compdata[0];
   } else {
      Gtk2::Gdk->beep;
   }
   if (defined $new) {
      $words[$idx]=$new;
      set_current_function $words[0] if $idx==0;
      if($idx<@args) {
         $words[$idx+1]="\0".$words[$idx+1];
      } else {
         $words[$idx].="\0";
      }
      set_words @words;
   }
   undef $last_arg;
}

sub execute_command {
   my($idx,$pos,$fun,@args)=get_words();
   $res=eval { Gimp->$fun(@args) };
   if ($@) {
      $statusbar->set_text($@);
      $result->set_text("");
      Gtk2::Gdk->beep;
   } else {
      $statusbar->set_text('');
      $result->set_text($res);
      $rlist->prepend_items(new Gtk2::ListItem $res);
   }
}

#sub idle {
#   Gtk2->idle_remove($idle) if $idle;
#   undef $idle;
#   update_completion;
#}

#sub do_idle {
#   $idle=Gtk2->idle_add(\&idle) unless $idle;
#}

eval "use Gtk2::Keysyms ()";
$Gtk2::Keysyms{Tab} ||= 0xFF09;

sub inputline {
   my $e = new Gtk2::Entry;
   $e->set_text("");
   $e->signal_connect("changed",sub {
      undef $last_arg;
#      do_idle;
   });
#   $e->signal_connect("focus_in_event",\&do_idle);
#   $e->signal_connect("button_press_event",\&do_idle);
   $e->signal_connect("key_press_event",sub {
      my ($event_box, $event) = @_;
      undef $last_arg;
      if ($event -> keyval == $Gtk2::Gdk::Keysyms{ Tab }) {
         # keep a 'tab' from going to next window, but instead have it do
	 # completion.  Keyboard navigation still possible through arrows
	 Glib::Object::signal_stop_emission_by_name($event_box, 'key_press_event');
         do_completion;
         1;
      } else {
         ();
      }
   });
   # For now, take out running the command
#   $e->signal_connect("activate",\&execute_command);

#$e->set_usize($ex*40,0);
   $inputline=$e;

#my $c = new Gtk2::CList(1);
   my $model = Gtk2::ListStore -> new(qw(Glib::Int));
   my $c = Gtk2::TreeView -> new($model);
   my $sel = $c->get_selection();

   setheight $c, 6;
   $clist = $c;
   $sel->set_mode(-multiple);
#   $c->signal_connect("select_row", sub {
#      eval {
#         my($idx,$pos,@words)=get_words();
#         $words[$idx]=$compdata[$_[1]]."\0";
#         set_words (@words);
#         set_current_function (substr($words[0],0,-1)) unless $idx;
#      };
#do_idle;
#   });

#   my $r = new Gtk2::ListStore;
#   my $rsel = $r->get_seleciton();
#   $rlist = $r;
#   $rsel->set_mode(-single);
#   $rsel->set_mode(-browse);
}

sub info {
   my $info = new Gtk2::Dialog;
   $info->set_title(__"Function Info");
   $info->signal_connect(delete_event => sub { $info->hide });
   my $close = new Gtk2::Button->new_from_stock('gtk-close');
   $close->signal_connect(clicked => sub { $info->hide });
   $info->action_area->add($close);

   my $table = new Gtk2::Table 2,9+1,0;
   my $y = 0;
   $info->vbox->add($table);

   local *add_info = sub {
      my ($label, $widget, $large) = @_;
      $label = new Gtk2::Label $label.": ";
      $label->set_alignment(0, 0);
      $table->attach($label,0,1,$y,$y+1,['fill'],['fill'],0,0);
      if ($large) {
         $y++;
         $table->attach($widget,0,2,$y,$y+$large,['expand','fill'],['expand','fill'],0,0);
         $y+=$large;
      } else {
         $widget->set_alignment(0, 0);
         $table->attach($widget,1,2,$y,$y+1,['fill'],['fill'],0,0);
         $y++;
      }
   };

   $blurb_label = new Gtk2::Label;
   add_info(__"Menu Path", $menupath_label = new Gtk2::Label);
   add_info(__"Accelerator", $accelerator_label = new Gtk2::Label);
   add_info(__"Image Types", $imagetypes_label = new Gtk2::Label);
   add_info(__"Author", $author_label = new Gtk2::Label);
   add_info(__"Copyright", $copyright_label = new Gtk2::Label);
   add_info(__"Date/Version", $date_label = new Gtk2::Label);
   add_info(__"Last Modified", $last_modified_label = new Gtk2::Label);
   add_info(__"Plug-In Path", $plugin_path_label = new Gtk2::Label);

   my $help_view = new Gtk2::TextView;
   $help_text = $help_view->get_buffer();
   $help_view->set_editable(0);
   $help_view->set_wrap_mode('word');
   my $cs = new Gtk2::ScrolledWindow undef,undef;
#   $cs->set_policy(-automatic,-automatic);
   $cs->add ($help_view);
   add_info (__"Description", $cs, 2);

   my $h = new Gtk2::HBox(0,5);
   my $more = new Gtk2::Button __"Function Information...";
   $more->signal_connect(clicked => sub { $info->visible ? $info->hide : $info->show_all });
   $h->add($blurb_label);
   $h->add($more);
   $h;
}

sub list_append_text {
   my ($l,@texts) = @_;
   my $m = $l->get_model();
   my $iter = $m -> append(undef); 
   $m -> set($iter,
             0 => "foo",
             1 => "bar",
	     2 => "baz",
	     3 => "bee");
}

sub list_add_columns { 
  my ($l,@columns) = @_;
  my $m = $l->get_model;
  my $renderer = Gtk2::CellRendererText->new;
  $column_num = 0;
  foreach (@columns) {
    my $column = Gtk2::TreeViewColumn->new_with_attributes($_, $renderer,text=>$column_num);
    $column_num++;
    $l->append_column($column);
  }
}

sub create_main {
   my $b;
#   my $t;

#   $t = new Gtk::Tooltips;
   my $w = new Gtk2::Dialog;
   $window = $w;
   $w->realize;
#   $ex = $w->style->font->string_width ('Mn')*0.5;
#   $ey = $w->style->font->string_width ('My');

   $w->set_title(__"PDB Explorer");
   $w->signal_connect("destroy",sub {main_quit Gtk2});

   $b = new Gtk2::Button->new_from_stock('gtk-close');
   $w->action_area->add($b);
   $b->signal_connect("clicked",sub {main_quit Gtk2});

   my $vpane = new Gtk2::VPaned; $w->vbox->add($vpane);
   $vpane->add1(my $f1 = new Gtk2::VBox 0,0);
   $vpane->add2(my $f2 = new Gtk2::VBox 0,0);

   my $h = new Gtk2::HBox (0,5);
   $f1->pack_start ($h,0,0,0);

   inputline;

   $synopsis = new Gtk2::Label "";
   $synopsis->set_justify(-left);

   my $table = new Gtk2::Table 3,4,0;
   $f1->pack_start($table,1,1,0);

   my $cs = new Gtk2::ScrolledWindow undef,undef;
   $cs->set_policy(-automatic,-automatic);
   $cs->add ($clist);
   #$cs->set_usize(0,$ey*6);

   my $rs = new Gtk2::ScrolledWindow undef,undef;
#  $rs->set_policy(-automatic,-automatic);
#   $rs->add_with_viewport ($rlist);

   $result = new Gtk2::Entry;
   $result->set_editable(0);
#$result->set_usize($ex*30,0);

   $statusbar = new Gtk2::Label;

   # $table->border_width(10);

   $table->attach(new Gtk2::Label(__"Synopsis:") ,
                  0, # left
		  1, # right
		  0, # top
		  1, # bottom
		  ['expand'], # xoptions
		  ['expand'], # yoptions
		  2, # xpadding
		  2);# ypadding
      $table->attach($synopsis ,1,2,0,1,['expand'],['expand'],2,2);
#         $table->attach(Gimp::UI::logo($w),2,3,0,1,['expand'],['expand'],0,0);
   $table->attach(new Gtk2::Label(__"Command:")  ,0,1,1,2,['expand'],['expand'],2,2);
      $table->attach($inputline,1,3,1,2,['expand','fill'],['expand'],2,2);
   $table->attach(new Gtk2::Label(__"Shortcuts:"),0,1,2,3,['expand'],['expand'],2,2);
      $table->attach($cs       ,1,3,2,3,['expand','fill'],['expand','fill'],2,2);
#         $table->attach($rs,2,3,2,3,['expand','fill'],['expand','fill'],0,0);
   $table->attach(new Gtk2::Label(__"Status:"),0,1,3,4,['expand'],['expand'],2,2);
   $table->attach($statusbar,1,3,3,4,['expand'],['expand'],2,2);

   $f2->pack_start(info,0,1,5);
   my $sw = new Gtk2::ScrolledWindow;
   $sw->set_policy(-automatic, -automatic);
   $cinfo = new Gtk2::TreeView ();
   $cmodel = new Gtk2::TreeStore ("Glib::String","Glib::String","Glib::String","Glib::String");
   $cinfo->set_model($cmodel);
   list_append_text($cinfo, "Foo", "Bar", "Baz", "Bee");
   list_add_columns($cinfo, "Oy", "hmm", "shh", "bzzz");

   $sw->add ($cinfo);
   $f2->pack_start ($sw,1,1,5);

   $w->realize;
   show_all $w;
}

register "extension_pdb_explorer",
         "Procedural Database Explorer (perl)",
         "A more interactive version of the DB Browser",
         "Marc Lehmann",
         "Marc Lehmann, Seth Burgess",
         "0.5alpha",
         N_"<Toolbox>/Xtns/PDB Explorer...",
         "",
         [],
         sub {
   Gimp::gtk_init;
   refresh;
   create_main;
   main Gtk2;
   ();
};

exit main;

=head1 LICENSE

Copyright Marc Lehman.

Distributed under the same terms as Gimp-Perl.

=cut

