#!/usr/bin/perl -w

use strict;
use Curses;
use Curses::Widgets;
use Curses::Widgets::TextField;
use Curses::Widgets::ButtonSet;
use Curses::Widgets::ListBox;
use Curses::Widgets::TextMemo;
use Curses::Widgets::ComboBox;

my $Version = 0.3;

my ($mwh, $actions, $filename, $ok_edit, $info,
        $filelist, $s_or_r, $recipient, $sign_menu); # Widget handles
	
my ($loop_control, $first_action, $maxy, $maxx, $fname,
    $file_sel, $output, $infile, $options, $srchoice,
    $sign_choice, $r_name);                       # Usual Scalars

my (@files);                                      # List of files in cur dir

# Get list of files in current dir.
@files = (`find . -maxdepth 1 -type f \! -name '.*' -print`);
# Put a space as first item in file list for use as an 'empty' selection
unshift(@files, ' ');

# Instantiate new Curses object and initialize
$mwh = new Curses;
noecho();
halfdelay(5);
$mwh->keypad(1);
curs_set(0);

# Check that size of terminal window is OK
$mwh->getmaxyx($maxy, $maxx);                                   
if ($maxx < 80 || $maxy < 24) {
	$mwh->erase();
	endwin();
	die ("Terminal is $maxx x $maxy; must be 80 x 24 min; aborting...\n");
}

# Create the widgets that are used for all actions; others when needed
$actions = action_widget();
$filename = file_in();
$ok_edit = ok_or_edit();
$info = info_win();
$filelist = filelist();

# Retrieve value from 'actions' widget and select appropriate routine
$actions->execute($mwh);
$first_action = $actions->getField('VALUE');

# Blank 'actions' widget to give a clear screen for other widgets
erase_widget($actions, $mwh);

if ($first_action == 0) { # Action is 'Verify'
	$loop_control = 1;
        while ($loop_control) { # 'while' loop allows 'OK/Edit' option

	# Use 'list_files' subroutine to select a filename
	$file_sel = list_files($filelist, $filename, $info, $mwh);

	# Set selected name in 'filename' widget window
	$filename->setField(VALUE => $file_sel);
	
	# Display 'filename' widget and accept or edit final filename
        $filename->execute($mwh);
        $fname = $filename->getField('VALUE');
        
	# Set 'verify' option for gpg
	$options = "--verify ";

	# Use the retrieved values to show gpg command to be executed, then
	# display OK/Edit button for final action

	$loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh);

	} # End of while loop

} elsif ($first_action == 1) { # Action is 'Encrypt'
	# Instantiate special widgets
	$s_or_r = s_or_r();
	$recipient = param_in();
	
	$loop_control = 1;
	while ($loop_control) {

        # Use 'list_files' subroutine to select a filename
        $file_sel = list_files($filelist, $filename, $info, $mwh);
        
        # Set selected name in 'filename' widget window
        $filename->setField(VALUE => $file_sel);
	$filename->execute($mwh); 

	# Get final filename and test for wildcards
	$fname = $filename->getField('VALUE');
	if ($fname =~ /[\*\?]/) {
		$options = "--encrypt-files ";
	} else {
		$options = "--encrypt ";
	}

	# Display 'recipient choice' menu
	$s_or_r->execute($mwh);
	
	# Get choice of recipient; display 'recipient ID' widget if needed
	$srchoice = $s_or_r->getField('VALUE');
	if ($srchoice) {
		# Display 'recipient' box for Recipient ID
		$recipient->execute($mwh);
		$recipient = $recipient->getField('VALUE');
		$options = "-a -r $recipient " . $options;
	}
	
	# Use the retrieved values to show gpg command to be executed, then
	# display OK/Edit button for final action

        $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh);
	
	} # End of while loop

} elsif ($first_action == 2) { # Action is 'Decrypt'
	$loop_control = 1;
	while ($loop_control) {

	# Use 'list_files' subroutine to select a filename
	$file_sel = list_files($filelist, $filename, $info, $mwh);

	# Set selected name in 'filename' widget window
	$filename->setField(VALUE => $file_sel);
        $filename->execute($mwh);
        
	# Get final filename and test for wildcards
	$fname = $filename->getField('VALUE');
	if ($fname =~ /[\*\?]/) {
		$options = "--decrypt-files ";
	} else {
		$options = " ";
	}

	# Use the retrieved values to show gpg command to be executed, then
	# display OK/Edit button for final action

        $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh);

	} # End of while loop


} elsif ($first_action == 3) {   # Action is 'Sign'
	# Instantiate special widgets
	$sign_menu = sign_menu();
	$recipient = param_in();
	$s_or_r = s_or_r();
	
	$loop_control = 1;
	while ($loop_control) {

	# Use 'list_files' subroutine to select a filename
	$file_sel = list_files($filelist, $filename, $info, $mwh);

	# Set selected name in 'filename' widget window
	$filename->setField(VALUE => $file_sel);
        $filename->execute($mwh);
        
        # Display 'sign_menu' for types of signatures and get a choice
	$sign_menu->execute($mwh);
	$sign_choice = $sign_menu->getField('VALUE');
	
	# Note selected filename for use later
	$fname = $filename->getField('VALUE');
	
	# Set 'options' for type of sig chosen
	if ($sign_choice == 0) {
		$options = "--clearsign ";
	} elsif  ($sign_choice == 1) { 
		$options = "--detach-sig ";
	} elsif  ($sign_choice == 2) {
		$options = "--sign ";
	} elsif  ($sign_choice == 3)  {
		$options = "--sign --encrypt ";

		# Call routines to select a recipient; set options accordingly
		$s_or_r->execute($mwh);
		$srchoice = $s_or_r->getField('VALUE');
		if ($srchoice) {
			$recipient->execute($mwh);
			$r_name = $recipient->getField('VALUE');
			$options = "-a -r $r_name " . $options;
		}
	} else { # Indicates program error
	$mwh->erase();
	endwin();
	die "Button Selection/Program Error\n";
	}

	# Use the retrieved values to show gpg command to be executed, then
	# display OK/Edit button for final action

        $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh);

	} # End of 'while' loop

} elsif ($first_action == 4) { # List Keys
	# Instantiate special widget
	$recipient = param_in();

        $loop_control = 1;
	while ($loop_control) { # Action is 'List Keys'
              
	# Modify caption of 'recipient' widget and display
	$recipient->setField('CAPTION' => 'ID of Keys to List/blank = All');
	$recipient->execute($mwh);

	# Erase 'recipient' widget for now
	erase_widget($recipient, $mwh);

	# Get key IDs to list; store in the 'fname' var
	$fname = $recipient->getField('VALUE');
	
	# Add a 'less' pipe if all keys are to be listed
	if ($fname eq " " || $fname eq "") {
	$fname = "|less";
	}

	# Set 'list-keys' option
	$options = "--list-keys ";
	
	# Use the retrieved values to show gpg command to be executed, then
	# display OK/Edit button for final action

	$loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh);
  
	} # End of while loop

} elsif ($first_action == 5) { # Edit Keys
	# Instantiate special widget
	$recipient = param_in();

        $loop_control = 1;
	while ($loop_control) { # Action is 'Edit Key'
              
	# Modify caption of 'recipient' widget and display
	$recipient->setField('CAPTION' => 'Key ID to Edit');
	$recipient->execute($mwh);
	
        # Erase 'recipient' widget for now	
	erase_widget($recipient, $mwh);

	# Get key ID to edit
	$fname = $recipient->getField('VALUE');

	# Set 'edit-key' option
	$options = "--edit-key ";
	
	# Use the retrieved values to show gpg command to be executed, then
	# display OK/Edit button for final action

	$loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh);
  
	} # End of while loop

} else { # Indicates Program Error
	$mwh->erase();
	endwin();
	die "Button Selection/Program Error\n";
}

# Clean up Curses stuff
$mwh->erase();
endwin();

# Leave the perl process and execute the gpg command in terminal
exec("gpg $options $fname");

exit; # For insurance

# Subroutine definitions follow

# Erase widget and reset window background to black
# Usage: blank_widget($widget_handle, $window_handle)
sub erase_widget {
	my($w_h, $mwh) = @_;
	$w_h->_init($mwh);
	bkgd($mwh, COLOR_BLACK);
	return;
}

# Instantiate main menu
sub action_widget {
my $act = Curses::Widgets::ButtonSet->new({
	LENGTH      => 15,
	VALUE       => 0,
	INPUTFUNC   => \&scankey,
	FOREGROUND  => 'blue',
	BACKGROUND  => 'white',
	BORDER      => 1,
	FOCUSSWITCH => "\t\n",
	HORIZONTAL  => 0,
	X           => 1,
	Y           => 1,
	LABELS      => [('Verify','Encrypt','Decrypt','Sign','List Keys','Edit Keys')],
});
return($act);
}

# Instantiate menu for type of sig wanted
sub sign_menu {
my $act = Curses::Widgets::ButtonSet->new({
	LENGTH      => 15,
	VALUE       => 0,
	INPUTFUNC   => \&scankey,
	FOREGROUND  => 'blue',
	BACKGROUND  => 'white',
	BORDER      => 1,
	FOCUSSWITCH => "\t\n",
	HORIZONTAL  => 0,
	X           => 1,
	Y           => 13,
	LABELS      => [('Clear Sign','Detached Sig','Sign','Sign/Encrypt')],
});
return($act);
}

# Instantiate menu for the usual 'OK/Edit' choice buttons
sub ok_or_edit { 
my $okq = Curses::Widgets::ButtonSet->new({
        LENGTH      => 15,
        VALUE       => 0, 
        FOREGROUND  => 'red',   
        BACKGROUND  => 'white',  
        BORDER      => 0,
        FOCUSSWITCH => "\n",
        HORIZONTAL  => 1,
	PADDING     => 5,
        X           => 22,
        Y           => 22,
        LABELS      => [('OK','Edit Input')],
});
return($okq);
}

# Instantiate menu to show/edit final file name
sub file_in {
my $file_in;
$file_in = Curses::Widgets::TextField->new({
	X		=> 32,
	Y		=> 1,
	FOREGROUND 	=> 'blue',
	BACKGROUND  	=> 'white',
	MAXLENGTH   	=> $maxx,
        COLUMNS         => 24,
	CAPTION		=> 'File Selected for Action'	
	});
return($file_in);
}

# Instantiate menu for Recipient ID or Key ID
sub param_in {
my $param_in = Curses::Widgets::TextField->new({
        X               => 32,
        Y               => 8,
        FOREGROUND      => 'blue', 
        BACKGROUND      => 'white',
        MAXLENGTH       => $maxx,
        COLUMNS         => 32,
        CAPTION         => 'Recipient ID'
        });
return($param_in);
}

# Instantiate 'info' widget to show operation status and gpg command
sub info_win {
my $info_win;
$info_win = Curses::Widgets::TextMemo->new({
	CAPTION       => 'Progress...',
	COLUMNS       => 50,
	LINES         => 4,
	VALUE         => '',
	FOREGROUND    => 'blue',
	BACKGROUND    => 'white',
	BORDER        => 1,
	FOCUSSWITCH   => "\t",
	CURSORPOS     => 0,
	TEXTSTART     => 0,
	X             => 22,
	Y             => 12,
	READONLY      => 0,
	});
return($info_win);
}

# Instantiate menu to select encrypt 'to self' or 'to named recipient'
sub s_or_r {
my $act;
$act = Curses::Widgets::ButtonSet->new({
	LENGTH      => 16,
	VALUE       => 0,
	INPUTFUNC   => \&scankey,
	FOREGROUND  => 'blue',
	BACKGROUND  => 'white',
	BORDER      => 1,
	FOCUSSWITCH => "\t\n",
	HORIZONTAL  => 0,
	X           => 60,
	Y           => 1,
	LABELS      => [('to Self', 'to Named ID')],
});
return($act);
}

# Instantiate scrolling widget to show files in current dir
sub filelist {
my $cb = Curses::Widgets::ComboBox->new({
           CAPTION     => 'Select the File',
           CAPTIONCOL  => 'blue',
           COLUMNS     => 22,
           MAXLENGTH   => 80,
           MASK        => undef,
           VALUE       => 'Down Arrow for List',
           INPUTFUNC   => \&scankey,
           FOREGROUND  => 'blue',
           BACKGROUND  => 'white',
           BORDER      => 1,
           BORDERCOL   => 'blue',
           FOCUSSWITCH => "\n\t",
           CURSORPOS   => 0,
           TEXTSTART   => 0,
           PASSWORD    => 0,
           X           => 1,
           Y           => 1,
           READONLY    => 0,
           LISTITEMS   => \@files,
           });
return $cb;
}

# Subroutine displays list of files in current dir; returns name selected
# Invoke as list_files($filelist, $filename, $info, $mwh)
sub list_files {
        my ($filelist, $filename, $info, $mwh) = @_;
        my $file_sel;

        # Display the filelist widget
        $filelist->execute($mwh);

        # Get file selected
        $file_sel = $filelist->getField('VALUE');

        # Erase filelist widget and redraw the others
        erase_widget($filelist, $mwh);
        $filename->draw($mwh, 0);
        $info->setField(VALUE => '...Waiting for remainder of input');
        $info->draw($mwh, 0);

        # Return the selected filename
        return($file_sel);
}

# Subroutine to show command to be executed and offer OK/Edit button
# for final action

# Invoke as final_phase($options, $fname, $info, $ok_edit, $mwh)

sub final_phase {
# Use the retrieved values to show command to be executed

my($options, $fname, $info, $ok_edit, $mwh) = @_;
my $output = "\ngpg " . $options . $fname . "\nChoose OK or Edit; hit 'Return'";
$info->setField('VALUE' => $output);
$info->setField('CAPTION' => ' >>>   The gpg Command Will Be...');
$info->draw($mwh, 0);

# Display OK/Edit button for final action

$ok_edit->execute($mwh);
#$loop_control = $ok_edit->getField('VALUE');
return($ok_edit->getField('VALUE'));
}

=pod

=head1 NAME

C<mygpg> - a terminal interface for the construction of routine gpg
commands.

=head1 DESCRIPTION

This script uses a curses interface with a terminal or xterm window in
a Q-and-A style to construct a command-line for common gpg operations.
There is no need to remember the syntax of gpg commands or options.

=head1 README

Navigation: The file selection widgets are scrolled with the up- and
down-arrow keys. To select a choice, hit 'Return.' To pass from one
widget to the next, hit 'Return' or 'Tab.'

C<mygpg> operates on the files in the current working directory, and
saves output there as well. The text in the C<filename> widget box can
be edited to include wildcards, or additional path info can be appended
if desired.

The C<gpg> command that is run when the script exits uses the gpg
defaults that are in the gpg config file, normally
C<~/.gnupg/gpg.conf>. This includes the C<default-key>, C<trust-model>,
C<armor> and C<load-extension> options.

However, during C<Encrypt> and C<Sign-and-Encrypt> operations,
if a recipient other than C<Self> is chosen, we assume that an ASCII
armored output is needed, and that option is included, overriding the
setting in the C<gpg> config file.

=head1 PREREQUISITES

=over
=item strict

=item Curses

=item Curses::Widgets

=item Curses::Widgets::TextField

=item Curses::Widgets::ButtonSet

=item Curses::Widgets::ListBox

=item Curses::Widgets::TextMemo

=item Curses::Widgets::ComboBox

=item And, of course, a gpg executable is required.

=back

=head1 SCRIPT CATEGORIES

UNIX : System_administration

=head1 AUTHOR / COPYRIGHT                                                       
                                                                                
Howard L. Arons, hlarons@CPAN.org                                                   
                                                                                
Copyright (c) 2005 by Howard L. Arons. All Rights Reserved.  This
script is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
                                                                                
If you have suggestions for improvement, please e-mail me.  If you make
improvements, kindly send me a copy of your changes. Thanks.

=cut
