The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

# MListbox demonstration application.

# Author: Hans J. Helgesen, December 1999.
#
# Before March 2000:
#
# Please send comments, suggestions and error reports to 
# hans_helgesen@hotmail.com.
#
# From March 2000: hans.helgesen@novit.no
#
use Tk;
use Tk::MListbox;


my $intro = <<EOT;
This is a very simple file manager application that demonstrates the use of MListbox $Tk::MListbox::VERSION.

* To resize any of the columns, drag the vertical bar to the RIGHT of the column.
* To move any of the columns, drag the column header left or right.
* To sort the table, click on any of the column headers. A new click will reverse the sort order.
* To scroll fast (scan) in any direction, "drag" with middle mouse button (you have to make the window narrower first to see horizontal scanning).
* To hide/show any of the columns, use the right mouse button on the column headings (or the "Show All" button).
* To see another directory, double click on a directory below.
EOT
    
# Create main perl/tk window.
my $mw = new MainWindow;

# Show some "help" text.
$mw->Label(-text=>$intro,-justify=>'left')->pack(-anchor=>'w');

# Create the MListbox widget. 
# Specify alternative comparison routine for integers and date.
# (The MListbox is actually pack'ed below the button
# frame, but since the "Show All" button references $ml, we have to create
# it now. 
my $ml = $mw->Scrolled('MListbox',
		       -scrollbars => 'oe',
		       -selectmode => 'extended',
		       -bd=>2,-relief=>'sunken',
		       -columns=>[[-text=>'Mode',-textwidth=>10],
				  [-text=>'NLink', -textwidth=>3,
				   -comparecmd => sub {$_[0] <=> $_[1]}],
				  [-text=>'Uid'],
				  [-text=>'Gid'],
				  [-text=>'Size',
				   -comparecmd => sub {$_[0] <=> $_[1]}],
				  [-text=>'Mtime',
				   -comparecmd => \&compareDate],
				  [-text=>'Name']]);


# Put the exit button and the "Show All" button in a separate frame.
my $f = $mw->Frame(-bd=>2,-relief=>'groove')
    ->pack(-anchor=>'w', -expand=>0,-fill=>'x');

$f->Button(-text=>'Exit',-command=>sub{exit})
    ->pack(-side=>'right',-anchor=>'e');

$f->Button(-text=>'Show All', 
	   -command=>sub {
	       foreach ($ml->columnGet(0,'end')) {
		   $ml->columnShow($_);
	       }
	   })->pack(-side=>'left',-anchor=>'w');

# Put the MListbox widget on the bottom of the main window.
$ml->pack (-expand=>1, -fill=>'both', -anchor=>'w');

# Double clicking any of the data rows calls openFileOrDir()
# (But only directories are handled for now...)
$ml->bindRows("<Double-Button-1>", \&openFileOrDir);

# Right-clicking the column heading creates the hide/show popup menu.
$ml->bindColumns("<Button-3>", \&columnPopup);

# Start by showing the current directory.
directory (".");


MainLoop;

#----------------------------------------------------------
#
sub directory
{
    my ($dir) = @_;

    chdir($dir);
    
    my $pwd = `pwd`; chomp $pwd;
    $mw->title ("Directory: $pwd");
    
    # Empty $ml
    $ml->delete(0,'end');
    
    opendir (DIR, ".") or die "Cannot open '.': $!\n";
    
    foreach my $name (readdir(DIR)) {	
	my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
	    $atime, $mtime, $ctime, $blksize, $blocks) = stat($name);
	
	my $type = do {
	    if (-l $name) {
		$mode = 0777;
		'l';
	    } elsif (-f $name) {
		'-';
	    } elsif (-d $name) {
		'd';
	    } elsif (-p $name) {
		'p';
	    } elsif (-b $name) {
		'b';
	    } elsif (-c $name) {
		'c';
	    } else {
		' ';
	    }};
	    
	$gid = getgrgid ($gid);
	$uid = getpwuid ($uid);

	$mtime = localtime($mtime);
	$mode = $type . convMode ($mode);

	$ml->insert('end', [$mode,$nlink,$uid,$gid,$size,$mtime,$name]);
    }
}

# This callback is called if the user double-clicks one of the rows in
# the MListbox. If the selected file is a directory, open it.
#
sub openFileOrDir
{
    my @sel = $ml->curselection;
    if (@sel == 1) {
	my ($mode, $name) = ($ml->getRow($sel[0]))[0,6];
	if ($mode =~ m/^d/) {   # Directory?
	    directory ($name);
	}
    }
}

# This callback is called if the user right-clicks the column heading.
# Create a popupmenu with hide/show options.
sub columnPopup
{
    my ($w, $index) = @_;
    
    # Create popup menu.
    my $menu = $w->Menu(-tearoff=>0);

    # First item is "Hide (this column)".
    #
    $menu->add ('command',
		-label=>"Hide ".$w->columnGet($index)->cget(-text),
		-command=>sub {
		    $w->columnHide($index);
		});
    $menu->add ('separator');

    # Create a "Show" entry for each column that is not currently visible.
    #
    foreach ($w->columnGet(0,'end')) {  # Get all columns from $w.
	unless ($_->ismapped) {
	    $menu->add('command',
		       -label=>"Show ".$_->cget(-text),
		       -command=>[ $w => 'columnShow', $_, -before=>$index],
		       );
	}
    }
    $menu->Popup(-popover=>'cursor');
}

# Converts a numeric file mode to the format provided by the ls command.
#
sub convMode 
{
    my $mode = shift;
    my $result = '';

    $result .= ($mode & 0400) ? 'r' : '-';
    $result .= ($mode & 0200) ? 'w' : '-';
    if ($mode & 0100) {
	if ($mode & 04000) {
	    $result .= 's';
	} else {
	    $result .= 'x';
	}
    } else {
	$result .= '-';
    }

    $result .= ($mode & 040) ? 'r' : '-';
    $result .= ($mode & 020) ? 'w' : '-';
    if ($mode & 010) {
	if ($mode & 02000) {
	    if (($mode & 02010) || 
		($mode & 02030) ||
		($mode & 02050) ||
		($mode & 02070))
	    {
		$result .= 's';
	    } else {
		$result .= 'l';
	    }
	} else {
	    $result .= 'x';
	}
    } else {
	$result .= '-';
    }

    $result .= ($mode & 04) ? 'r' : '-';
    $result .= ($mode & 02) ? 'w' : '-';
    $result .= ($mode & 01) ? 'x' : '-';

    return $result;
}

# Callback for date comparison. Expects that the dates are on the format
# "day mon dd hh:mm:ss yyyy", for example "Tue Dec  7 12:13:11 1999".
#
sub compareDate
{
    my ($d1, $d2) = @_;
    convertDate($d1) cmp convertDate($d2);
}
sub convertDate
{
    my ($str) = @_;
    my ($wday,$mon,$day,$hour,$min,$sec,$year) = 
	($str =~ m/(\S*)\s*(\S*)\s*(\d*)\s*(\d\d):(\d\d):(\d\d)\s*(\d\d\d\d)/);

    my $month=0;
    foreach (qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/) {
	if ($mon eq $_) {
	    last;
	} else {
	    $month++;
	}
    }
    return sprintf ("%04d%02d%02d%02d%02d%02d", 
		    $year,$month,$day,$hour, $min, $sec);
}