package Win32::Editor::PFE;
$VERSION=0.2;

use Win32::DDE;
use Win32::DDE::Client;
use Carp;
use Cwd;

use strict;

sub new {
    my $class = shift;
    my $self = {};
    bless $self, $class;
    if (@_) {
     $self->Connect() or return;
     $self->Open(@_);
    }
    return $self;
}

sub _run {
    my $pid;
    eval "use Win32::Registry";
    my $pfe = $Win32::Registry::HKEY_CLASSES_ROOT->Open('PFE32\shell\open')->QueryValue('command');
    $pfe =~ s/\.exe.*$/.exe/;
    Win32::Spawn $pfe, '', $pid;
    1;
}

sub Connected {
    return $_[0]->{DDE};
}

sub Reconnect {
    my $self = shift;
    $self->Disconnect;
    $self->Connect;
}

sub Connect {
    my $self=shift;
    return $self->{DDE} if $self->{DDE};
    my $dde = new Win32::DDE::Client ('PFE32', 'Editor');
    if ($dde->Error) {
        $dde->Disconnect;
        undef $dde;
        return undef unless _run();
#print "Spawned, connecting ...\n";
        my $i;
        for $i (1..5) {
         $dde = new Win32::DDE::Client ('PFE32', 'Editor');
         last unless $dde->Error;
         sleep(1);
        }
        return undef if $dde->Error;
#print "Connected ...\n";
    }
    $self->{DDE} = $dde;
}

sub Disconnect {
    delete $_[0]->{DDE};
}

*DESTROY = \&Disconnect;

sub Execute {
    my $self = shift;$self->Connect() or return;
    my $cmd = shift or return 1;
    if ($cmd =~ /\(.*\)/) {
     $cmd = "[$cmd]" if $cmd !~ /^\[.*\]$/;
    } else {
     my @param = @_;
     @param = map {if (/^".*"|\d+$/) {$_} else {$_ = qq{"$_"}} } @param;
     $cmd = "[$cmd(".join(', ',@param).")]";
    }
#print "COMMAND: $cmd\n";
    $self->{DDE}->Execute($cmd);
    my $res = $self->{DDE}->Request('Result');
#print "RESULT: $res\n";
    if ($res eq 'OK') { return 1 } elsif ($res eq 'Busy') {return 0} else {return};
}

*Do = \*Execute;
*Exec = \*Execute;
*Run = \*Execute;

sub Relative2Absolute {
    foreach (@_) {
        s#/#\\#g;
        m#^\\[^\\]# and $_ = substr( getcwd, 0,2) . $_;
        m#^\w:\\# or m#^\\\\#
         or $_ = getcwd . '\\' . $_
          and $_ =~ s{\\[^\\]+\\\.\.(\\|$)}{\\}g;
    }
}

sub Open {
    my $self = shift;
    my ($count,$opt,$file);
    if ($_[-1]+0 eq $_[-1]) {
        $opt = ','.pop;
    }
    while (defined ($file = shift)) {
        Relative2Absolute $file;
        $self->Execute( qq{FileOpen( "$file" $opt)} )
        and $count++;
    }
    $count;
}

sub Get {
    $_[0]->{DDE}->Request($_[1]);
}

sub Result {
    $_[0]->{DDE}->Request('Result');
}

sub Pos {
    my $self = shift;$self->Connect() or return;
    my $ln = $self->{DDE}->Request('LineNumber');
    my $cn;$cn = $self->{DDE}->Request('ColumnNumber') if wantarray();
    if (@_) {
     my $new = shift;
     $new = '+0' unless defined $new;
     $new .= '/'.$_[0] if $_[0];
     $self->EditGotoLine($new,0) if $new;
    }
    return (wantarray() ? ($ln,$cn) : $ln);
}

sub OpenAt {
    croak "ussage: Win32::Editor::PFE->OpenAt(\$filename, \$line [, \$row])\n"
     unless @_ >= 2;
    my $self = shift;
    my ($file,@opt) = @_;
    $self->FileOpen($file) or return;
    $self->Pos(@opt);
}

sub FileOpen {
    my $self = shift;
    my $file = shift;
    Relative2Absolute $file;
    $self->Execute('FileOpen',$file,@_);
}

sub PFEVersion {
 $_[0]->Get('VersionString');
}

sub AUTOLOAD {
    my $self = shift;
    my $fun = $Win32::Editor::PFE::AUTOLOAD;
    $fun =~ s/^.*:://;
    $self->Execute($fun,@_);
}


=head1 Win32::Editor::PFE 0.2

This module symplifies the DDE link to the great PFE (Programmer's File Editor).
It provides all DDE functions mentioned in PFE's DDE help plus some more.
It takes care of spawning PFE if it's not already running.

=head2 Ussage

    use Win32::Editor::PFE;
    $pfe = new Win32::Editor::PFE;
    $pfe->Open('script.pl');
    $id = $pfe->Get('WindowID');
    $pfe->Pos(23,10);
    $pfe->EditInsert("<THIS TEXT WAS INSERTED BY A SCRIPT>");
    $pfe->WindowActivate($id);
    $pfe->ComeToForeground();
    undef $pfe;

=head2 Functions

=item new

 $pfe = new Win32::Editor::PFE;
 $pfe = new Win32::Editor::PFE (@files_to_open);

This constructor creates a new Win32::Editor::PFE object and connects it
to an instance of PFE. If PFE is not running at the moment, this
function starts it.

=item Connect

 $pfe->Connect();

This functions connects the object to an instance of PFE if it's not
already connected. This function is called by almost all methods to
ensure there is a PFE to send the command to.

=item Disconnect

 $pfe->Disconnect();

Disconnects the object from PFE. You should call this method as soon as
posible after virtualy each method call. The thing is that PFE is able
to take only one open connection at a time, so if you keep the
connection you block out other programs and scripts.

=item Connected

 $isConnected = $pfe->Connected()

Checks if the object is connected to an instance of PFE.

=item Open

 $pfe->Open(@list_of_filenames);
 $pfe->Open(@list_of_filenames, $use_saved_position);

Opens all the files in the list and returns the number of successfuly
opened files. If $use_saved_position=1, PFE tries to open the files
using saved possitions. See FileOpen() in DDE Commands in PFE's help.

You do not have to use absolute paths for the filenames, the function
takes care of this automaticaly.

=item OpenAt

 $pfe->OpenAt( $filename, $line_number [, $column_number]);
 $pfe->OpenAt( $filename, $position_specification);

Opens a file and possitions the cursor on a particular line and column.

See the PFE's help for file position specifications.

=item FileOpen

This function is exactly as described in PFE's help, except the fact that it
converts relative paths to absolute.

=item Get

 $value = $pfe->Get("property name");

You may get the value of all data items mentioned in PFE's help,
"DDE Data Items" topic.

=item Pos

 $line_number = $pfe->Pos();
 ($line_number, $column_number) = $pfe->Pos();
 $pfe->Pos( $line_number);
 $pfe->Pos( $line_number, $column_number);
 $pfe->Pos( $position_specification);

Using this function you may get the current position in the active
window and/or change it. If you change the position, the function
returns the original position.

=item Execute

 $pfe->Execute("[TheCommand(...)]");
 $pfe->Execute('TheCommand',@parameters);

This function sends the specified command to PFE. You may either enter
the command directly as is ("[FileNew()]", "[CaretDown(3,1)]",
"[FileOpen("c:\autoexec.bat")]", etc.) or specify the function name and
the parameters. The functions takes care of the quoting, I hope it does
it right. 

You should not have to use this function, use the AUTOLOADing feature instead and
write

    $pfe->CaretDown(3,1);
   instead of
    $pfe->Execute('CaretDown','3','1');

=item Result

 $pfe->Result();

Gives you the result of the last command executed over the DDE link. The
reply will be one of the strings "OK" or "Error" if a result is
available, and "Busy" if the server is currently processing a DDE link
command. The results of commands executed from the keyboard, menu or
toolbar cannot be read by this method.

=head2 Author

Jenda@Krynicky.cz

=cut
