The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Tk::DirTree;
# DirTree -- TixDirTree widget
#
# Derived from DirTree.tcl in Tix 4.1
#
# Chris Dean <ctdean@cogit.com>

use strict;
use vars qw($VERSION);
$VERSION = '4.019';

use Tk;
use Tk::Derived;
use Tk::Tree;
use Cwd;
use DirHandle;
use File::Spec qw();

use base  qw(Tk::Derived Tk::Tree);
use strict;

Construct Tk::Widget 'DirTree';

my $sep = $^O eq 'MSWin32' ? '\\' : '/';

*_fs_encode = eval { require Encode; 1 } ? sub { Encode::encode("iso-8859-1", $_[0]) } : sub { $_[0] };

sub Populate {
    my( $cw, $args ) = @_;

    $cw->SUPER::Populate( $args );

    $cw->ConfigSpecs(
        -dircmd         => [qw/CALLBACK dirCmd DirCmd DirCmd/],
        -showhidden     => [qw/PASSIVE showHidden ShowHidden 0/],
        -image          => [qw/PASSIVE image Image folder/],
        -directory      => [qw/SETMETHOD directory Directory ./],
        -value          => '-directory' );

    $cw->configure( -separator => $sep,
		    -itemtype => 'imagetext',
		  );
}

sub DirCmd {
    my( $w, $dir, $showhidden ) = @_;
    $dir .= $sep if $dir =~ /^[a-z]:$/i and $^O eq 'MSWin32';
    my $h = DirHandle->new( $dir ) or return();
    my @names = grep( $_ ne '.' && $_ ne '..', $h->read );
    @names = grep( ! /^[.]/, @names ) unless $showhidden;
    return( @names );
}

*dircmd = \&DirCmd;

sub fullpath
{
 my ($path) = @_;
 my $cwd = getcwd();
 if (CORE::chdir($path))
  {
   $path = getcwd();
   CORE::chdir($cwd) || die "Cannot cd back to $cwd:$!";
  }
 else
  {
   warn "Cannot cd to $path:$!"
  }
 $path = File::Spec->canonpath($path);
 return $path;
}

sub directory
{
    my ($w,$key,$val) = @_;
    # We need a value for -image, so its being undefined
    # is probably caused by order of handling config defaults
    # so defer it.
    $w->afterIdle([$w, 'set_dir' => $val]);
}

sub set_dir {
    my( $w, $val ) = @_;
    my $fulldir = fullpath( $val );

    my $parent = $sep;
    if ($^O eq 'MSWin32')
     {
      if ($fulldir =~ s/^([a-z]:)//i)
       {
        $parent = $1;
       }
     }
    $w->add_to_tree( $parent, $parent)  unless $w->infoExists($parent);

    my @dirs = ($parent);
    foreach my $name (split( /\Q$sep\E/, $fulldir )) {
        next unless length $name;
        push @dirs, $name;
	my $dir = File::Spec->catfile( @dirs );
        $w->add_to_tree( $dir, $name, $parent )
            unless $w->infoExists( $dir );
        $parent = $dir;
    }

    $w->OpenCmd( $parent );
    $w->setmode( $parent, 'close' );
}
*chdir = \&set_dir;


sub OpenCmd {
    my( $w, $dir ) = @_;

    my $parent = $dir;
    foreach my $name ($w->dirnames( $parent )) {
        next if ($name eq '.' || $name eq '..');
        my $subdir = File::Spec->catfile( $dir, $name );
	$subdir = _fs_encode($subdir);
        next unless -d $subdir;
        if( $w->infoExists( $subdir ) ) {
            $w->show( -entry => $subdir );
        } else {
            $w->add_to_tree( $subdir, $name, $parent );
        }
    }
}

*opencmd = \&OpenCmd;

sub add_to_tree {
    my( $w, $dir, $name, $parent ) = @_;

    my $dir8 = _fs_encode($dir);
    my $image = $w->cget('-image');
    if ( !UNIVERSAL::isa($image, 'Tk::Image') ) {
	$image = $w->Getimage( $image );
    }
    my $mode = 'none';
    $mode = 'open' if $w->has_subdir( $dir );

    my @args = (-image => $image, -text => $name);
    if( $parent ) {             # Add in alphabetical order.
        foreach my $sib ($w->infoChildren( $parent )) {
	    use locale;
	    my $sib8 = _fs_encode($sib);
	    if ($sib8 gt $dir8) {
                push @args, (-before => $sib);
                last;
            }
        }
    }

    $w->add( $dir, @args );
    $w->setmode( $dir, $mode );
}

sub has_subdir {
    my( $w, $dir ) = @_;
    foreach my $name ($w->dirnames( $dir )) {
        next if ($name eq '.' || $name eq '..');
        next if ($name =~ /^\.+$/);
        return( 1 ) if -d File::Spec->catfile( $dir, $name );
    }
    return( 0 );
}

sub dirnames {
    my( $w, $dir ) = @_;
    my @names = $w->Callback( '-dircmd', $dir, $w->cget( '-showhidden' ) );
    return( @names );
}

{
    package Tk::DirTreeDialog;
    use base qw(Tk::Toplevel);
    Construct Tk::Widget 'DirTreeDialog';

    sub Populate {
	my($w, $args) = @_;
	$w->{curr_dir} = delete $args->{-initialdir};
	if (!defined $w->{curr_dir}) {
	    require Cwd;
	    $w->{curr_dir} = Cwd::cwd();
	}
	if (defined $args->{-mustexist}) {
	    die "-mustexist is not yet implemented";
	}
	my $title = $args->{-title} || "Choose directory:";
	delete $args->{-popover};

	$w->title($title);
	$w->{ok} = 0; # flag: "1" means OK, "-1" means cancelled

	# Create Frame widget before the DirTree widget, so it's always visible
	# if the window gets resized.
	my $f = $w->Frame->pack(-fill => "x", -side => "bottom");

	my $d;
	$d = $w->Scrolled('DirTree',
			  -scrollbars => 'osoe',
			  -width => 35,
			  -height => 20,
			  -selectmode => 'browse',
			  -exportselection => 1,
			  -browsecmd => sub {
			      $w->{curr_dir} = shift;
			  },

			  # With this version of -command a double-click will
			  # select the directory
			  -command   => sub { $w->{ok} = 1 },

			  # With this version of -command a double-click will
			  # open a directory. Selection is only possible with
			  # the Ok button.
			  #-command   => sub { $d->opencmd($_[0]) },
			 )->pack(-fill => "both", -expand => 1);
	# Set the initial directory
	$d->set_dir($w->{curr_dir});

	$f->Button(-text => 'Ok',
		   -command => sub { $w->{ok} =  1 })->pack(-side => 'left');
	$f->Button(-text => 'Cancel',
		   -command => sub { $w->{ok} = -1 })->pack(-side => 'left');
	$w->OnDestroy(sub { $w->{ok} = -1 });
    }

    sub Show {
	my $w = shift;
	my $old_focus = $w->focusSave;
	my $old_grab = $w->grabSave;
	Tk::catch {
	    $w->grab;
	};
	$w->waitVariable(\$w->{ok});
	my $ret = $w->{ok} == 1 ? $w->{curr_dir} : undef;
	$w->grabRelease if Tk::Exists($w);
	&$old_focus;
	&$old_grab;
	$w->destroy if Tk::Exists($w);
	$ret;
    }
}

1;

__END__

#  Copyright (c) 1996, Expert Interface Technologies
#  See the file "license.terms" for information on usage and redistribution
#  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
#  The file man.macros and some of the macros used by this file are
#  copyrighted: (c) 1990 The Regents of the University of California.
#               (c) 1994-1995 Sun Microsystems, Inc.
#  The license terms of the Tcl/Tk distrobution are in the file
#  license.tcl.