#!perl -w use strict; our $VERSION = '2.2'; use Tk; use Tk::ROText; use Tk::LabFrame; use Tk::Pane; use Tk::Balloon; use Tk::HistEntry; use Tk::NumEntry; use Cwd; use FindBin; use File::Spec; use Data::Dumper; use Pod::Simple::Text; use PAR::Packer; my $optref = PAR::Packer::OPTIONS; our ( @opts, @type, @def, @chkd, @value ); our ( $source_file, $output_file, $log_file_ref, %hist_refs ); my $mw = MainWindow->new( -title => "gpp $VERSION - gui for pp" ); my $default_size = '500x500'; # for $mw, $hw and $lw $mw->geometry($default_size); $mw->minsize( 250, 250 ); $mw->setPalette('cornsilk3'); $mw->optionAdd( '*font' => 'Courier 10' ); my $entry_font_color = 'blue'; my $balloon_font = 'Courier 8'; my $balloon_color = 'yellow'; my $dots_font = 'Courier 5'; my $pl_types = [ [ 'pp source', [ '.par', '.pl', '.ptk', '.pm' ] ], [ 'All files', '*' ] ]; my $gpp_types = [ [ 'gpp options', ['.gpp'] ], [ 'All files', '*' ] ]; my $default_gpp_ext = '.gpp'; my $pp = find_pp(); if ( !$pp ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "Can't find pp !!", -type => 'OK' ); exit(1); } if ( !open PP, "<$pp" ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "Can't open $pp: $!", -type => 'OK' ); exit(1); } my $pp_text; { undef $/; $pp_text = ; } close PP; @opts = sort { lc( substr( $a, 0, index( $a, '|' ) ) ) cmp lc( substr( $b, 0, index( $b, '|' ) ) ) || $a cmp $b } keys %$optref; for (@opts) { push @def, $$optref{$_} } # parse option specifiers for ( 0 .. $#opts ) { my ($short) = ( $opts[$_] =~ /([^|]+)/ ); $type[$_] = ''; $type[$_] = $1 if $opts[$_] =~ /([=:].*)/; $opts[$_] = $short; $chkd[$_] = 0; $value[$_] = 0 if $type[$_] =~ /i/; $value[$_] = '' if $type[$_] =~ /[fs]/; $log_file_ref = \$value[$_] if $opts[$_] eq 'L'; } my $f = $mw->Frame( -borderwidth => 5 )->pack( -expand => 1, -fill => 'both' ); my $fb = $f->Frame()->pack( -fill => 'x' ); my $fb1 = $fb->Frame()->pack( -side => 'left', -expand => 'y', -fill => 'x' ); $fb1->Button( -text => 'Pack', -command => sub { run_pp() } )->pack( -expand => 1, -fill => 'x' ); $fb1->Button( -text => 'View Log', -command => sub { view_log() } ) ->pack( -expand => 1, -fill => 'x' ); my $fb2 = $fb->Frame()->pack( -side => 'left', -expand => 'y', -fill => 'x' ); $fb2->Button( -text => 'Open Opts', -command => sub { open_opts(); } )->pack( -expand => 1, -fill => 'x' ); $fb2->Button( -text => 'Save Opts', -command => sub { save_opts(); } )->pack( -expand => 1, -fill => 'x' ); my $fb3 = $fb->Frame()->pack( -side => 'left', -expand => 'y', -fill => 'x' ); $fb3->Button( -text => 'Exit', -command => sub { save_hist() } ) ->pack( -expand => 1, -fill => 'x' ); $fb3->Button( -text => 'Help', -command => sub { help() } )->pack( -expand => 1, -fill => 'x' ); my $ff = $f->Frame( -borderwidth => 5, )->pack( -fill => 'x' ); my $fn = $ff->Frame()->pack( -side => 'left' ); $fn->Label( -text => 'Source File:' )->pack( -anchor => 'e' ); $fn->Label( -text => 'Output File:' )->pack( -anchor => 'e' ); my $fe = $ff->Frame()->pack( -side => 'left', -expand => 1, -fill => 'x' ); my $source_entry = $fe->HistEntry( -textvariable => \$source_file, -width => 1, -fg => $entry_font_color, -selectbackground => $entry_font_color, -dup => 0, -case => 0, # works opposite of pod -match => 1, -limit => 10, -command => sub { } )->pack( -expand => 1, -fill => 'x' ); $source_entry->Subwidget('slistbox')->configure( -bg => 'white' ); my $output_entry = $fe->HistEntry( -textvariable => \$output_file, -width => 1, -fg => $entry_font_color, -selectbackground => $entry_font_color, -dup => 0, -case => 0, # works opposite of pod -match => 1, -limit => 10, -command => sub { } )->pack( -expand => 1, -fill => 'x' ); $output_entry->Subwidget('slistbox')->configure( -bg => 'white' ); my $fg = $ff->Frame()->pack( -side => 'left', -fill => 'y' ); $fg->Button( -text => '...', -font => $dots_font, -command => sub { my $file = $mw->getOpenFile( -filetypes => $pl_types ); if ($file) { $source_file = $file; $source_file = '"' . $source_file . '"' if $source_file =~ / / and $^O =~ /win32/i; $source_entry->xview('end'); $source_entry->historyAdd(); } } )->pack(-expand => 'y', -fill => 'y'); $fg->Button( -text => '...', -font => $dots_font, -command => sub { my $file = $mw->getSaveFile(); if ($file) { $output_file = $file; $output_file = '"' . $output_file . '"' if $output_file =~ / / and $^O =~ /win32/i; $output_entry->xview('end'); $output_entry->historyAdd(); } } )->pack(-expand => 'y', -fill => 'y'); my $fo = $f->LabFrame( -label => 'Options', -labelside => 'acrosstop' ) ->pack( -expand => 1, -fill => 'both' ); my $p = $fo->Scrolled( 'Pane', -scrollbars => 'osw', -sticky => 'we', )->pack( -expand => 1, -fill => 'both' ); for ( 0 .. $#opts ) { next if $opts[$_] =~ /^[oh]$/; my $fp = $p->Frame()->pack( -expand => 'y', -fill => 'both' ); my $c = $fp->Checkbutton( -text => $opts[$_], -variable => \$chkd[$_], -selectcolor => 'white' )->pack( -side => 'left' ); $fp->Balloon( -bg => $balloon_color, -font => $balloon_font ) ->attach( $c, -balloonmsg => $def[$_] ); if ( $type[$_] =~ /[@%]/ ) { if ( $type[$_] =~ /=/ ) { $fp->Label( -text => '+' )->pack( -side => 'left' ); } else { $fp->Label( -text => '*' )->pack( -side => 'left' ); } } else { $fp->Label( -text => ' ' )->pack( -side => 'left' ); } my $he; if ( $type[$_] =~ /[fs]/ ) { $he = $fp->HistEntry( -textvariable => \$value[$_], -width => 1, -fg => $entry_font_color, -selectbackground => $entry_font_color, -dup => 0, -case => 0, # works opposite of pod -match => 1, -limit => 10, -command => sub { }, )->pack( -side => 'left', -expand => 'y', -fill => 'x' ); $he->Subwidget('slistbox')->configure( -bg => 'white' ); $hist_refs{ $opts[$_] } = $he; } if ( $type[$_] =~ /f/ ) { $he->Subwidget('entry')->configure( -validate => 'key' ); $he->Subwidget('entry')->configure( -validatecommand => sub { $_[0] =~ /^[+-]?\.?$| # starting entry ^[+-]?(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d*))?$ # continuing entry /x; # not validated if the entry ever actually finishes } ); } if ( $type[$_] =~ /i/ ) { $fp->NumEntry( -textvariable => \$value[$_], -width => 5, -fg => $entry_font_color, -selectbackground => $entry_font_color, )->pack( -side => 'left' ); } } my ( $hw, $hwt ); # help toplevel/text my ( $lw, $lwt ); # view log toplevel/text $mw->waitVisibility; open_opts( $ARGV[0] ) if $ARGV[0]; my $gpp_history = $ENV{HOME} || $ENV{HOMEPATH} || $FindBin::Bin; $gpp_history .= '/.gpp.history'; open_hist(); $source_entry->focus; MainLoop; sub find_pp { my $pp = 'pp'; $pp .= '.bat' if $^O =~ /win32/i; return File::Spec->catfile( cwd(), $pp ) if -e $pp; my @path = File::Spec->path(); for (@path) { my $full_name = File::Spec->catfile( $_, $pp ); return $full_name if -e $full_name; } return undef; } sub open_opts { my $opts_file = shift; if ( !$opts_file ) { $opts_file = $mw->getOpenFile( -filetype => $gpp_types ); } return if !$opts_file; my ( $save_chkd, $save_value ); if ( !open OH, "<$opts_file" ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$opts_file: $!", -type => 'OK' ); return; } my $opts_dump; { undef $/; $opts_dump = ; } close OH; if ( $opts_dump !~ /\$save_chkd\s*=.*?\$save_value\s*=/s ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$opts_file: Not a gpp option file !!", -type => 'OK' ); return; } eval $opts_dump; if ($@) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$opts_file: $@", -type => 'OK' ); return; } for ( 0 .. $#opts ) { if ( exists $save_chkd->{ $opts[$_] } ) { $chkd[$_] = $save_chkd->{ $opts[$_] }; $value[$_] = $save_value->{ $opts[$_] }; } } } ## end sub open_opts sub save_opts { my $opts_file = $mw->getSaveFile( -filetypes => $gpp_types, -defaultextension => $default_gpp_ext ); return if !$opts_file; my ( %save_chkd, %save_value ); for ( 0 .. $#opts ) { $save_chkd{ $opts[$_] } = $chkd[$_]; $save_value{ $opts[$_] } = $value[$_]; } if ( !open OH, ">$opts_file" ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$opts_file: $!", -type => 'OK' ); return; } print OH Data::Dumper->Dump( [ $source_file, $output_file, \%save_chkd, \%save_value ], [qw( source_file output_file save_chkd save_value )] ); close OH; } sub open_hist { return if !-e $gpp_history; my ( $source_hist, $output_hist, $opts_hist ); if ( !open HH, "<$gpp_history" ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$gpp_history: $!", -type => 'OK' ); return; } my $hist_dump; { undef $/; $hist_dump = ; } close HH; if ( $hist_dump !~ /\$source_hist\s*=.*?\$output_hist\s*=/s ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$gpp_history: Not a gpp history file !!", -type => 'OK' ); return; } eval $hist_dump; if ($@) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$gpp_history: $@", -type => 'OK' ); return; } $source_entry->history($source_hist); $output_entry->history($output_hist); for ( 0 .. $#opts ) { if ( exists $opts_hist->{ $opts[$_] } ) { $hist_refs{ $opts[$_] }->history( $opts_hist->{ $opts[$_] } ); } } } ## end sub open_hist sub save_hist { if ( !open HH, ">$gpp_history" ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$gpp_history: $!", -type => 'OK' ); return; } my ( $source_hist, $output_hist ); $source_hist = [ $source_entry->history() ]; $output_hist = [ $output_entry->history() ]; for ( keys %hist_refs ) { $hist_refs{$_} = [ $hist_refs{$_}->history() ]; } print HH Data::Dumper->Dump( [ $source_hist, $output_hist, \%hist_refs ], [qw( source_hist output_hist opts_hist )] ); close HH; exit(); } sub view_log { my $file = $$log_file_ref; $file =~ s/^"(.*)"$/$1/; return if !$file; if ( !open LH, "<$file" ) { $mw->messageBox( -title => 'Error', -icon => 'error', -message => "$file: $!", -type => 'OK' ); return; } my $log_text; { undef $/; $log_text = ; } close LH; if ( !Exists($lw) ) { $lw = $mw->Toplevel( -title => 'Log file' ); my ( $x, $y ) = ( $mw->geometry() =~ /^\d+x\d+\+(\d+)\+(\d+)/ ); $lw->geometry( $default_size . '+' . ( $x + 20 ) . '+' . ( $y + 20 ) ); $lw->minsize( 200, 30 ); my $fb = $lw->Frame()->pack( -fill => 'x' ); $fb->Button( -text => 'Close', -command => sub { $lw->withdraw() } ) ->pack( -side => 'left', -expand => 'y', -fill => 'x' ); $fb->Button( -text => 'Clear Log file', -command => sub { open LH, ">$file"; close LH; $lw->withdraw() } )->pack( -side => 'right' ); $lwt = $lw->Scrolled( "Text", -scrollbars => 'osw', -wrap => 'none', -height => 1, -width => 1 )->pack( -expand => 1, -fill => 'both' ); $lwt->insert( 'end', $log_text ); $lw->focus(); } else { $lwt->delete( '0.0', 'end' ); $lwt->insert( 'end', $log_text ); $lw->deiconify(); $lw->raise(); $lw->focus(); } } ## end sub view_log sub help { if ( !Exists($hw) ) { $hw = $mw->Toplevel( -title => 'Help for pp' ); my ( $x, $y ) = ( $mw->geometry() =~ /^\d+x\d+\+(\d+)\+(\d+)/ ); $hw->geometry( $default_size . '+' . ( $x + 40 ) . '+' . ( $y + 40 ) ); $hw->minsize( 100, 30 ); $hw->Button( -text => 'Close', -command => sub { $hw->withdraw } )->pack( -fill => 'x' ); my $parser = Pod::Simple::Text->new(); my $pod; $parser->output_string( \$pod ); $parser->parse_string_document($pp_text); $hwt = $hw->Scrolled( "Text", -scrollbars => 'osw', -wrap => 'none', -height => 1, -width => 1 )->pack( -expand => 1, -fill => 'both' ); $hwt->insert( 'end', $pod ); $hw->focus(); } else { $hw->deiconify(); $hw->raise(); $hw->focus(); } } sub run_pp { my @pp_opts = (); for ( 0 .. $#opts ) { if ( $chkd[$_] ) { if ( ( $type[$_] eq '' ) or ( $type[$_] =~ /:/ and $value[$_] eq '' ) ) { push @pp_opts, '-' . $opts[$_]; } elsif ( $type[$_] =~ /[ifs]$/ ) { push @pp_opts, '-' . $opts[$_]; push @pp_opts, $value[$_]; } elsif ( $type[$_] =~ /[fs][@%]/ ) { my @multi = (); my $value = $value[$_]; # Look for quoted strings first, then non-blank strings, # separated by spaces, commas or semicolons while ( $value =~ /\G\s*((['"])[^\2]*?\2)\s*[,;]?|\G\s*([^\s,;]+)\s*[,;]?/g ) { push( @multi, defined($1) ? $1 : $3 ); } for $value (@multi) { push @pp_opts, '-' . $opts[$_]; push @pp_opts, $value; } } } } if ($output_file) { push @pp_opts, '-o'; push @pp_opts, $output_file; } if ($source_file) { push @pp_opts, $source_file; } print "$pp @pp_opts\n"; $mw->Busy(); system $pp, @pp_opts; $mw->Unbusy(); print "Done.\n\n"; } ## end sub run_pp