######################################################################
# Fs.pm
######################################################################
# Perl Power! - Michael Schilli 1998
######################################################################

######################################################################
# Fs - file selector package
######################################################################
package Fs;

use Tk;
use Path;
use strict;

######################################################################
# Define new file selector:
# $fs = Fs->new($topwindow, \& callback, $title);
######################################################################
sub new {
    my($type, $parentwin, $callbackref, $title) = @_;

    my $self = {};
                                            
                                            
    $self->{'callbackref'} = $callbackref;  # store parameter
    $self->{'parentwin'}   = $parentwin;    # in instance
    $self->{'title'}       = $title;        # variable

    bless($self, $type);
}

######################################################################
# Display and start file selector: $fs->start($startdir);
######################################################################
sub start {
    my $self     = shift;
    my $startdir = shift;

    $self->{'topwin'} = $self->{'parentwin'}->Toplevel;

    $self->{'topwin'}->configure(-title => $self->{'title'});

    # directory and file listboxes
    my $listFrame = $self->{'topwin'}->Frame();
    
    $self->{'dirList'}  = 
         $listFrame->ScrlListbox(-label => "Directories");
    $self->{'fileList'} = 
         $listFrame->ScrlListbox(-label => "Files");
    
    # entry widget for selected path/file
    my $fileText = 
             $self->{'topwin'}->Entry(-textvariable => 
                                      \${$self}{'pathtext'});
    
    # Buttons
    my $buttonFrame  = $self->{'topwin'}->Frame();
    my $okButton     = $self->{'topwin'}->Button(
                         -text => "OK", 
                         -command => sub { $self->okAction });
    
    my $rescanButton = $self->{'topwin'}->Button(
                         -text => "Rescan", 
                         -command => 
                                 sub { $self->rescanAction });

    my $cancelButton = $self->{'topwin'}->Button(
                         -text => "Cancel", 
                         -command => 
                                 sub { $self->cancelAction });
    
    # pack all
    $listFrame->pack(-fill => "both", -expand => "yes", 
                     -side => "top");
    
    $self->{'dirList'}->pack(-fill => "both", 
                             -expand => "yes", 
                             -side => "left");

    $self->{'fileList'}->pack(-fill => "both", 
                              -expand => "yes", 
                              -side => "left");
    
    $fileText->pack(-fill => "x", -expand => "yes", 
                    -anchor => "s");
    
    $buttonFrame->pack(-fill => "x", -expand => "yes", 
                       -anchor => "s");
    $okButton->pack(-side => "left");
    $cancelButton->pack(-side => "left");
    $rescanButton->pack(-side => "left");
    
    
    # define double-click actions on lists
    $self->{'dirList'}->bind("<Double-Button-1>" => sub { 
        $self->switch2dir($self->{'dirList'}->Getselected());
        $self->{'pathtext'} = $self->{'path'} });
    
    $self->{'fileList'}->bind("<Double-Button-1>" => 
                              sub { $self->fsexit() });
    

    # define return key action
    $self->{'topwin'}->bind("<KeyPress-Return>" => 
                            sub {$self->okAction});
    
    
    # set initial path to current directory
    $self->{'path'} = $startdir unless defined $self->{'path'};
    $self->switch2dir(".");
    $self->{'pathtext'} = $self->{'path'};
}
    
######################################################################
# Change to a new directory, update listboxes:
# $fs->switch2dir($directory);
######################################################################
sub switch2dir {
  my $self = shift;
  my $dir  = shift;
                                    # new path for test purpose
  my $newpath = Path::cd($self->{'path'}, $dir) || return 0; 

  return 0 unless opendir(DIR, "$newpath");

  my @files = sort readdir(DIR);    # read directory
  closedir(DIR);
                                    # update directory listbox
  $self->{'dirList'}->delete(0, "end"); 
  $self->{'dirList'}->insert("end", 
                             grep(-d "$newpath/$_" , @files));
  $self->{'dirList'}->selection("set", 0);
                                    # update file listbox
  $self->{'fileList'}->delete(0, "end");
  $self->{'fileList'}->insert("end", 
                              grep(-f "$newpath/$_", @files));

  $self->{'path'} = $newpath;       # set new path
}

######################################################################
# Action upon activation of the OK button
######################################################################
sub okAction { 
    my $self = shift;

    my $item;

    if($self->{'pathtext'} ne $self->{'path'}) { 
        # enter path string manually
        if($item = $self->switch2dir($self->{'pathtext'})) {
             $self->{'pathtext'} = 
                 $self->{'path'} = Path::absolute($item);
        } else {                 # new file selected
                                 # close dialog window
            $self->{'topwin'}->destroy; 
                                 # trigger callback
            &{$self->{'callbackref'}}($self->{'pathtext'});
        }
    } elsif(($item = $self->{'dirList'}->Getselected())) { 
        # new directory selected
        $self->switch2dir($item);
        $self->{'pathtext'} = $self->{'path'};

    } elsif($self->{'fileList'}->Getselected()) { 
        # file selected
        $self->fsexit();
    }
}

######################################################################
# Action upon activation of the Rescan button
######################################################################
sub rescanAction { 
    my $self = shift;

    my $item;

    if($self->{'pathtext'} ne $self->{'path'}) {
        # enter path string manually
        (($item = $self->switch2dir($self->{'pathtext'})) && 
            ($self->{'pathtext'} = 
             $self->{'path'} = Path::absolute($item))) ||
                ($self->{'pathtext'} = $self->{'path'});    

    } elsif(($item = $self->{'dirList'}->Getselected())) { 
        # new directory selected
        $self->switch2dir($item);
        $self->{'pathtext'} = $self->{'path'};
    } 
}
 
######################################################################
# Action upon activation of the Cancel button
######################################################################
sub cancelAction { 
    my $self = shift;

    $self->{'fileList'}->selection("clear", 0, "end");
    $self->fsexit();
}

######################################################################
# Store selected file together with path and remove window
######################################################################
sub fsexit {
    my $self = shift;

    # read selected path/file
    my $item = $self->{'fileList'}->Getselected();

    # append file to path
    $self->{'path'} =~ s,/$,,g;
    $self->{'selected'} = defined $item ? 
                                  "$self->{path}/$item" : "";

    $self->{'topwin'}->destroy;     # close dialog window

                                    # call callback function
    &{$self->{'callbackref'}}($self->{'selected'});  
}

######################################################################
# Interrogate selected directory/file
######################################################################
sub getselected {
    my $self = shift;

    $self->{'selected'};
}

1;

