#!/usr/bin/perl #BEGIN {$^W=1}; use Gimp::Feature qw(perl-5.005 gtk-1.2); use Gimp (':consts','__','N_'); use Gimp::Fu; use Gtk; use Gtk::Gdk; 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"); 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 = ( &PARAM_BOUNDARY => 'BOUNDARY', &PARAM_CHANNEL => 'CHANNEL', &PARAM_COLOR => 'COLOR', &PARAM_DISPLAY => 'DISPLAY', &PARAM_DRAWABLE => 'DRAWABLE', &PARAM_FLOAT => 'FLOAT', &PARAM_IMAGE => 'IMAGE', &PARAM_INT32 => 'INT32', &PARAM_FLOATARRAY => 'FLOATARRAY', &PARAM_INT16 => 'INT16', &PARAM_PARASITE => 'PARASITE', &PARAM_STRING => 'STRING', &PARAM_PATH => 'PATH', &PARAM_INT16ARRAY => 'INT16ARRAY', &PARAM_INT8 => 'INT8', &PARAM_INT8ARRAY => 'INT8ARRAY', &PARAM_LAYER => 'LAYER', &PARAM_REGION => 'REGION', &PARAM_STRINGARRAY => 'STRINGARRAY', &PARAM_SELECTION => 'SELECTION', &PARAM_STATUS => 'STATUS', &PARAM_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($blurb); for(0..$args-1) { push(@args,[Gimp->procedural_db_proc_arg($fun,$_)]); } new_cinfo; $help_text->delete_text(0,-1); $help_text->insert_text($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))); }; } } 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(scalar@matches.__" matching functions"); } else { set_clist @matches,@matches; $synopsis->set($matches[0].__" (press Tab to complete)"); } } sub complete_type { my($type,$name,$desc)=@_; if($type==PARAM_IMAGE) { set_clist(map(("$$_: ".$_->get_filename,$$_),Gimp->image_list)); } elsif($type==PARAM_LAYER) { set_clist(map { my $i = $_; map(("$$_: ".$i->get_filename."/".$_->get_name,$$_),$i->get_layers)} Gimp->image_list); } elsif($type==PARAM_CHANNEL) { set_clist(map { my $i = $_; map(("$$_: ".$i->get_filename."/".$_->get_name,$$_),$i->get_channels)} Gimp->image_list); } elsif($type==PARAM_DRAWABLE) { set_clist(map { my $i = $_; map(("$$_: ".$i->get_filename."/".$_->get_name,$$_),($i->get_layers,$i->get_channels))} Gimp->image_list); } elsif ($type==PARAM_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 { Gtk::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($@); $result->set_text(""); Gtk::Gdk->beep; } else { $statusbar->set(''); $result->set_text($res); $rlist->prepend_items(new Gtk::ListItem $res); } } sub idle { Gtk->idle_remove($idle) if $idle; undef $idle; update_completion; } sub do_idle { $idle=Gtk->idle_add(\&idle) unless $idle; } eval "use Gtk::Keysyms ()"; $Gtk::Keysyms{Tab} ||= 0xFF09; sub inputline { my $e = new Gtk::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 { undef $last_arg; do_idle; if ($_[1]->{keyval} == $Gtk::Keysyms{Tab}) { $_[0]->signal_emit_stop_by_name('key_press_event'); do_completion; 1; } else { (); } }); $e->signal_connect("activate",\&execute_command); #$e->set_usize($ex*40,0); $inputline=$e; my $c = new Gtk::CList(1); setheight $c, 6; $clist = $c; $c->set_selection_mode(-extended); $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 Gtk::List; $rlist = $r; $r->set_selection_mode(-single); $r->set_selection_mode(-browse); } sub info { my $info = new Gtk::Dialog; $info->set_title(__"Function Info"); $info->signal_connect(delete_event => sub { $info->hide }); my $close = new Gtk::Button __"Close"; $close->signal_connect(clicked => sub { $info->hide }); $info->action_area->add($close); my $table = new Gtk::Table 2,9+1,0; my $y = 0; $info->vbox->add($table); local *add_info = sub { my ($label, $widget, $large) = @_; $label = new Gtk::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 Gtk::Label; add_info(__"Menu Path", $menupath_label = new Gtk::Label); add_info(__"Accelerator", $accelerator_label = new Gtk::Label); add_info(__"Image Types", $imagetypes_label = new Gtk::Label); add_info(__"Author", $author_label = new Gtk::Label); add_info(__"Copyright", $copyright_label = new Gtk::Label); add_info(__"Date/Version", $date_label = new Gtk::Label); add_info(__"Last Modified", $last_modified_label = new Gtk::Label); add_info(__"Plug-In Path", $plugin_path_label = new Gtk::Label); $help_text = new Gtk::Text; $help_text->set_editable(0); $help_text->set_word_wrap(1); my $cs = new Gtk::ScrolledWindow undef,undef; $cs->set_policy(-automatic,-automatic); $cs->add ($help_text); add_info (__"Description", $cs, 2); my $h = new Gtk::HBox(0,5); my $more = new Gtk::Button __"More..."; $more->signal_connect(clicked => sub { $info->visible ? $info->hide : $info->show_all }); $h->add($blurb_label); $h->add($more); $h; } sub create_main { my $b; my $t; $t = new Gtk::Tooltips; my $w = new Gtk::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 - the olof edition (yet still an alpha 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 $vpane = new Gtk::VPaned; $w->vbox->add($vpane); $vpane->add1(my $f1 = new Gtk::VBox 0,0); $vpane->add2(my $f2 = new Gtk::VBox 0,0); my $h = new Gtk::HBox (0,5); $f1->pack_start ($h,0,0,0); inputline; $synopsis = new Gtk::Label ""; $synopsis->set_justify(-left); my $table = new Gtk::Table 3,4,0; $f1->pack_start($table,1,1,0); my $cs = new Gtk::ScrolledWindow undef,undef; $cs->set_policy(-automatic,-automatic); $cs->add ($clist); #$cs->set_usize(0,$ey*6); my $rs = new Gtk::ScrolledWindow undef,undef; $rs->set_policy(-automatic,-automatic); $rs->add_with_viewport ($rlist); $result = new Gtk::Entry; $result->set_editable(0); #$result->set_usize($ex*30,0); $statusbar = new Gtk::Label; $table->border_width(10); $table->attach(new Gtk::Label(__"Synopsis") ,0,1,0,1,{},{},0,0); $table->attach($synopsis ,1,2,0,1,{},{},0,0); $table->attach(Gimp::UI::logo($w),2,3,0,1,{},{},0,0); $table->attach(new Gtk::Label(__"Command") ,0,1,1,2,{},{},0,0); $table->attach($inputline,1,3,1,2,['expand','fill'],{},0,0); # $table->attach($result,2,3,1,2,['expand','fill'],{},0,0); $table->attach(new Gtk::Label(__"Shortcuts"),0,1,2,3,{},{},0,0); $table->attach($cs ,1,3,2,3,['expand','fill'],['expand','fill'],0,0); # $table->attach($rs,2,3,2,3,['expand','fill'],['expand','fill'],0,0); $table->attach(new Gtk::Label(__"Status"),0,1,3,4,{},{},0,0); $table->attach($statusbar,1,3,3,4,{},{},0,0); $f2->pack_start(info,0,1,5); my $sw = new Gtk::ScrolledWindow; $sw->set_policy(-automatic, -automatic); $cinfo = new_with_titles Gtk::CList '',__"TYPE",__"NAME",__"DESCRIPTION"; $cinfo->set_column_auto_resize (0,1); $cinfo->set_column_auto_resize (1,1); $cinfo->set_column_auto_resize (2,1); $cinfo->set_selection_mode('single'); setheight $cinfo, 8; $sw->add ($cinfo); $f2->pack_start ($sw,1,1,5); idle; $w->realize; show_all $w; } register "extension_pdb_explorer", "Procedural Database Explorer", "This is a more interactive and less broken / script-fu-centric version of the DB Browser", "Marc Lehmann", "Marc Lehmann", "0.4alpha", N_"/Xtns/PDB Explorer", "", [], sub { Gimp::gtk_init; refresh; create_main; main Gtk; (); }; exit main;