#!/usr/bin/perl
#
# xumod - GUI frontend to umod the all-purpose Unreal mod tool
#
# Copyright (C) 2000 Avatar <avatar@deva.net>
#
# You may distribute this file under the terms of the Artistic License,
# as distributed with Perl.  A copy is available here:
# http://language.perl.com/misc/Artistic.html
#
# $Id: xumod,v 1.34 2001/01/18 16:07:24 avatar Exp $

require 5.005;
use Umod;
use Config::Ini;
use Tk;
use Tk::Tiler;
use Tk::ROText;
use Tk::Tree;
use Tk::ItemStyle;
use Tk::NoteBook;
use Tk::LabEntry;
use Tk::LabRadio;
use Tk::Dialog;
use Tk::DialogBox;
use Tk::FBox;
use Tk::Balloon;
use FileHandle;
use File::Find;
use File::Spec;
use UNIVERSAL qw(isa);
use POSIX qw(tmpnam);
use strict;

my ($hasArchiveZip);

BEGIN {
    eval {
	require Archive::Zip;
    };
    if ($@) {
	eval 'BEGIN { use constant AZ_OK => 0; }';
    } else {
	import Archive::Zip qw(:ERROR_CODES);
	$hasArchiveZip = 1;
    }
}

#
# Globals
#

my ($rcfile, %pref);
my ($manifest, @products);
my %umod;

#
# House Keeping
#

my ($tmpdir, @tmpfiles);

do { $tmpdir = tmpnam(); } until mkdir $tmpdir, 0700;

END {
    foreach my $tmpfile (@tmpfiles) {
	unlink $tmpfile
	    or warn "$0: Cannot remove temporary file $tmpfile: $!\n";
    }
    rmdir $tmpdir
	or warn "$0: Cannot remove temporary directory $tmpdir: $!\n";
}

sub cleanup {
    exit;
}

$SIG{INT} = \&cleanup;

#
# Setup Params
#

# Defaults.
if ($^O eq "MSWin32") {
	# options
	$pref{force} = 'yes';

	# files and directories
	$rcfile = "$ENV{windir}/umod.ini";
	$pref{basedir} = '';
	$pref{extractdir} = '.';

	# applications
	$pref{umod} = 'umod';
	$pref{umr} = '';
	$pref{showtext} = '(builtin)';
	$pref{showimage} = '';
	$pref{showmusic} = '';
	$pref{showsound} = '';
	$pref{showhtml} = '';
	$pref{showurl} = '';
} else { # UNIX-like
	# options
	$pref{force} = 'yes';

	# files and directories
	$rcfile = "$ENV{HOME}/.umodrc";
	$pref{basedir} = '';
	$pref{extractdir} = '.';

	# applications
	$pref{umod} = 'umod';
	$pref{umr} = 'umr';
	$pref{showtext} = '(builtin)';
	$pref{showimage} = 'display %s &';
	$pref{showmusic} = 'xmms -e %s &';
	$pref{showsound} = 'xmms -e %s &';
	$pref{showhtml} = q[netscape -remote 'openURL(%s)' || netscape %s];
	$pref{showurl} = q[netscape -remote 'openURL(%s)' || netscape %s];
}

loadOptions();

#
# Main GUI
#

my $mw = MainWindow->new;
$mw->title("XUmod");

prepIcons();
scansystem();

my ($mb, $tb, $nb, $st);

# menu bar
$mb = $mw->Frame(-relief => 'raised', -borderwidth => 2)
	->pack(-side => 'top', -fill => 'x');

my $modkey = 'Control';

my $filem = $mb->Menubutton(-text => 'File', -underline => 0,
    -tearoff => 'false')
    ->pack(-side => 'left');
#$filem->command(-label => '~New', -accelerator => "$modkey+N",
#    -state => 'disabled');
$filem->command(-label => '~Open...', -accelerator => "$modkey+O",
    -command => \&getOpenUmodFile);
$mw->bind("<$modkey-o>" => \&getOpenUmodFile);
my $closemi = $filem->command(-label=> "~Close",
    -state => 'disabled',
    -command => \&closeUmodFile);
$filem->separator;
#$filem->command(-label => '~Save', -accelerator => "$modkey+S",
#    -state => 'disabled');
#$filem->command(-label => 'Save ~As...',
#    -state => 'disabled');
#$filem->separator;
$filem->command(-label => '~Quit', -accelerator => "$modkey+Q",
    -command => \&cleanup);
$mw->bind("<$modkey-q>" => \&cleanup);

my $umodm = $mb->Menubutton(-text=> 'umod', -underline => 1,
    -tearoff => 'false',
    -state => 'disabled')
    ->pack(-side => 'left');
my $installmi = $umodm->command(-label => '~Install',
    -accelerator => "$modkey+I",
    -state => 'disabled',
    -command => \&installUmodFile);
$mw->bind("<$modkey-i>" => \&installUmodFile);
my $uninstallmi = $umodm->command(-label => '~Uninstall',
    -accelerator => "$modkey+U",
    -state => 'disabled',
    -command => \&uninstallInstalledUmod);
my $checkmi = $umodm->command(-label => '~Check',
    -state => 'disabled',
    -command => \&checkUmodFile);
$umodm->separator;
my $readmemi = $umodm->command(-label => 'See ~Release Notes',
    -state => 'disabled',
    -command => \&viewReadme);
my $browseproductmi = $umodm->command(-label => 'Browse ~Product Home Page',
    -state => 'disabled',
    -command => sub { $umod{$nb->raised}->{producte}->menu->invoke(0); });
my $browseversionmi = $umodm->command(-label => 'Browse ~Version Home Page',
    -state => 'disabled',
    -command => sub { $umod{$nb->raised}->{versione}->menu->invoke(0); });
my $browsedevelopermi = $umodm->command(-label => 'Browse ~Developer Home Page',
    -state => 'disabled',
    -command => sub { $umod{$nb->raised}->{developere}->menu->invoke(0); });
$umodm->separator;
my $viewmi = $umodm->command(-label => '~View',
    -state => 'disabled',
    -command => \&viewFile);
my $extractmi = $umodm->command(-label => 'E~xtract',
    -state => 'disabled',
    -command => \&extractFile);

my $optionm = $mb->Menubutton(-text => 'Options', -underline => 0,
    -tearoff => 'false')
    ->pack(-side => 'left');
$optionm->checkbutton(-label => '~Overwrite Existing Files',
    -variable => \$pref{force},
    -command => \&saveOptions);
$optionm->separator();
$optionm->command(-label => "XUmod ~Setup", -command => \&setup);

my $helpm = $mb->Menubutton(-text => 'Help', -underline => 0,
    -tearoff => 'false')
    ->pack(-side => 'right');
$helpm->command(-label => "~About XUmod", -command => \&about);

# toolbar
$tb = $mw->Tiler(-rows => 1)->pack(-anchor => 'nw', -fill => 'x');
my $newb = $tb->Button(-image => $mw->Getimage('file'), -relief => 'flat',
    -state => 'disabled');
my $openb = $tb->Button(-image => 'openfile', -relief => 'flat',
    -command => \&getOpenUmodFile);
my $saveb = $tb->Button(-image => 'savefile', -relief => 'flat',
    -state => 'disabled');
my $installb = $tb->Button(-image => 'hammer', -relief => 'flat',
    -state => 'disabled',
    -command => \&installUmodFile);
my $viewb = $tb->Button(-image => 'magnify', -relief => 'flat',
    -state => 'disabled',
    -command => \&viewFile);
my $extractb = $tb->Button(-image => 'extract', -relief => 'flat',
    -state => 'disabled',
    -command => \&extractFile);
my $uninstallb = $tb->Button(-image => 'trash', -relief => 'flat',
    -state => 'disabled',
    -command => \&uninstallInstalledUmod);
my $checkb = $tb->Button(-image => 'check', -relief => 'flat',
    -state => 'disabled',
    -command => \&checkUmodFile);
$tb->Manage(
#    $newb,
    $openb,
#    $saveb,
    $installb, $viewb, $extractb,
    $uninstallb,
    $checkb,
);

# notebook
$nb = $mw->NoteBook(-ipadx => 6, -ipady => 6)->pack(
    -side => 'top',
    -expand => 'yes', -fill => 'both', -padx => 3, -pady => 3);

# status line
$st = $mw->Label(-anchor => 'w', -relief => 'groove')
    ->pack(-fill => 'x');

my $bl = $mw->Balloon(-statusbar => $st);
$bl->attach($newb, -msg => "Create a new umod file.");
$bl->attach($openb, -msg => "Open an umod file.");
$bl->attach($saveb, -msg => "Save this umod file.");
$bl->attach($installb, -msg => "Install this product.");
$bl->attach($viewb, -msg => "View the selected files.");
$bl->attach($extractb, -msg => "Extract the selected files.");
$bl->attach($uninstallb, -msg => "Uninstall the selected products.");
$bl->attach($checkb, -msg => "Check all the products.");

# Start up.

openInstalledUmod();
foreach my $filename (@ARGV) {
    if (-e $filename) {
	openUmodFile($filename);
    } else {
	$mw->Dialog(-title => 'XUmod: Warning',
	    -bitmap => 'warning',
	    -text => "Cannot open $filename: No such file or directory",
	    -buttons => [qw/Ok/])->Show;
    }
}

MainLoop();

#
# GUI Interaction
#

sub changeApplicability {
    my (%args) = @_;

    while (my ($key, $value) = each %args) {
	$value = 0 if (!$value or $value eq 'no' or $value eq 'false');

	if ($key eq '-close') {
	    $closemi->configure(-state => $value ? 'normal' : 'disabled'); }

	if ($key eq '-umod') {
	    $umodm->configure(-state => $value ? 'normal' : 'disabled'); }
	if ($key eq '-install') {
	    $installmi->configure(-state => $value ? 'normal' : 'disabled');
	    $installb->configure(-state => $value ? 'normal' : 'disabled'); }
	if ($key eq '-readme') {
	    $readmemi->configure(-state => $value ? 'normal' : 'disabled'); }
	if ($key eq '-browseproduct') {
	    $browseproductmi->configure(
		-state => $value ? 'normal' : 'disabled'); }
	if ($key eq '-browseversion') {
	    $browseversionmi->configure(
		-state => $value ? 'normal' : 'disabled'); }
	if ($key eq '-browsedeveloper') {
	    $browsedevelopermi->configure(
		-state => $value ? 'normal' : 'disabled'); }

	if ($key eq '-view') {
	    $viewmi->configure(-state => $value ? 'normal' : 'disabled');
	    $viewb->configure(-state => $value ? 'normal' : 'disabled');
	    $umod{$nb->raised}->{viewpmi}->configure(
		-state => $value ? 'normal' : 'disabled')
		if (defined $umod{$nb->raised}->{viewpmi}); }
	if ($key eq '-extract') {
	    $extractmi->configure(-state => $value ? 'normal' : 'disabled');
	    $extractb->configure(-state => $value ? 'normal' : 'disabled');
	    $umod{$nb->raised}->{extractpmi}->configure(
		-state => $value ? 'normal' : 'disabled')
		if (defined $umod{$nb->raised}->{extractpmi}); }

	if ($key eq '-uninstall') {
	    $uninstallmi->configure(-state => $value ? 'normal' : 'disabled');
	    $uninstallb->configure(-state => $value ? 'normal' : 'disabled');
	    $umod{$nb->raised}->{uninstallpmi}->configure(
		-state => $value ? 'normal' : 'disabled')
		if (defined $umod{$nb->raised}->{uninstallpmi}); }

	if ($key eq '-check') {
	    $checkmi->configure(-state => $value ? 'normal' : 'disabled');
	    $checkb->configure(-state => $value ? 'normal' : 'disabled');
	    $umod{$nb->raised}->{checkpmi}->configure(
		-state => $value ? 'normal' : 'disabled')
		if (defined $umod{$nb->raised}->{checkpmi}); }
    }
}

sub onFileSelectionChange {
    my ($tree) = $umod{$nb->raised}->{filetree};

    changeApplicability(-view => 'no', -extract => 'no');

    # Allow view/extract only if selected item is a file, and a viewer
    # has been defined.
    foreach my $item ($tree->info('selection')) {
	next if (defined $tree->info('children', $item));

	changeApplicability(-extract => 'yes');

	SWITCH: for (lc($item)) {
	    /\.html?$/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showhtml});
		last; };
	    /\.(bmp|gif|jpe?g|png|xpm)$/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showimage});
		last; };
	    /\.(uax|umx)$/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showmusic});
		last; };
	    /\.(txt|ini|int|log)/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showtext});
		last; };
	}
    }
}

sub onPageChange {
    if ($nb->raised eq '_system') {
	$mw->title('XUmod');

	changeApplicability(-close => 'no', -umod => 'yes',
	    -install => 'no',
	    -uninstall => 'no',
	    -check => 'yes',
	    -readme => 'no',
	    -browseproduct => 'no',
	    -browseversion => 'no',
	    -browsedeveloper => 'no',
	    -extract => 'no');

	$bl->attach($newb, -msg => "Create a new umod file.");
	$bl->attach($openb, -msg => "Open an umod file.");
	$bl->detach($saveb);
	$bl->detach($installb);
	$bl->attach($viewb, -msg => "View the selected files.");
	$bl->detach($extractb);
	$bl->attach($uninstallb, -msg => "Uninstall the selected products.");
	$bl->attach($checkb, -msg => "Check all the products.");

	onInstalledFileSelectionChange();

	return;
    }

    if (isa($umod{$nb->raised}->{file}, 'Archive::Zip')) {
	$mw->title("XUmod: ".$nb->raised);

	changeApplicability(-close => 'yes', -umod => 'yes',
	    -install => 'yes',
	    -uninstall => 'no',
	    -check => 'no',
	    -readme => 'no',
	    -browseproduct => 'no',
	    -browseversion => 'no',
	    -browsedeveloper => 'no',
	    -extract => 'no');

	$bl->attach($newb, -msg => "Create a new umod file.");
	$bl->attach($openb, -msg => "Open an umod file.");
	$bl->detach($saveb);
	$bl->detach($installb);
	$bl->attach($viewb, -msg => "View the selected files.");
	$bl->detach($extractb);
	$bl->attach($uninstallb, -msg => "Uninstall the selected products.");
	$bl->attach($checkb, -msg => "Check all the products.");

	onFileSelectionChange();

	return;
    }

    my $umodfile = $umod{$nb->raised}->{file};

    $mw->title("XUmod: ".$nb->raised);

    changeApplicability(-close => 'yes', -umod => 'yes',
	-install => 'yes',
	-uninstall => 'no',
	-check => 'no',
	-readme => ($umodfile->readmefile ? 'yes' : 'no'),
	-browseproduct =>
	    ($umodfile->producturl and $umodfile->producturl ne 'none'),
	-browseversion =>
	    ($umodfile->versionurl and $umodfile->versionurl ne 'none'),
	-browsedeveloper =>
	    ($umodfile->developerurl and $umodfile->developerurl ne 'none'));

    $bl->attach($newb, -msg => "Create a new umod file.");
    $bl->attach($openb, -msg => "Open an umod file.");
    $bl->attach($saveb, -msg => "Save this umod file.");
    $bl->attach($installb, -msg => "Install this product.");
    $bl->attach($viewb, -msg => "View the selected files.");
    $bl->attach($extractb, -msg => "Extract the selected files.");
    $bl->detach($uninstallb);
    $bl->attach($checkb, -msg => "Check this products.");

    onFileSelectionChange();
}

sub postFileTreePopupMenu {
    my ($w, $X, $Y, $y) = @_;

    # if the cursor is not positioned inside the selected range, select
    # the item under the cursor
    if (!$w->selectionIncludes($w->nearest($y))) {
	$w->selectionClear;
	$w->selectionSet($w->nearest($y))
    }

    onFileSelectionChange($w);

    $w->PostPopupMenu($X, $Y);
}

sub postInstalledFileTreePopupMenu {
    my ($w, $X, $Y, $y) = @_;

    # if the cursor is not positioned inside the selected range, select
    # the item under the cursor
    if (!$w->selectionIncludes($w->nearest($y))) {
	$w->selectionClear;
	$w->selectionSet($w->nearest($y))
    }

    onInstalledFileSelectionChange($w);

    $w->PostPopupMenu($X, $Y);
}

sub postPopupMenu {
    my ($w, $X, $Y) = @_;

    $w->PostPopupMenu($X, $Y);
}

#
# File Handling
#

sub getOpenUmodFile {
    my $filename = $mw->getOpenFile(-filetypes => [
	    ['All supported files', ['.umod', '.zip']],
	    ['Unreal mod files', '.umod'],
	    ['Zip archives', '.zip'],
	    ['All files', '*' ]]);

    # User cancelled.
    return 0 if (!$filename);

    return openUmodFile($filename);
}

sub openUmodFile {
    my ($filename) = @_;

    my $shortname = $filename;
    $shortname =~ s-.*[/\\]--;
    $shortname =~ s-\.(umod|zip)$--i;

    if (exists $umod{$shortname}) {
	my $i = 1;
	do { $i++; } until (!exists $umod{"$shortname#$i"});
	$shortname .= "#$i";
    }

    # Retrieve umod file from a zip file.
    if ($filename =~ /\.zip$/i) {
	if (!$hasArchiveZip) {
	    $mw->Dialog(-title => 'XUmod: Warning',
		-bitmap => 'warning',
		-text => "Cannot open $filename as a zip file: Module Archive::Zip not installed",
		-buttons => [qw/Ok/])->Show;
	    return 0;
	}

	my $zipfile = new Archive::Zip;
	if ($zipfile->read($filename) != AZ_OK) {
	    $mw->Dialog(-title => 'XUmod: Warning',
		-bitmap => 'warning',
		-text => "Cannot open $filename as a zip file",
		-buttons => [qw/Ok/])->Show;
	    return 0;
	}

	my @umodfiles = $zipfile->membersMatching('.*\.umod$');
	if (!exists $umodfiles[0]) {
	    return openPlainZipFile($filename);
	}

	# Munge a unique filename.  Beware of symlink attacks.
	my $i = 0;
	while ($filename = $tmpdir.'/'.$i.'-'.$umodfiles[0]->{fileName},
	    # Don't reuse old file, for it may not be the same one,
	    # or have been changed.
	    -e $filename
	    or $umodfiles[0]->extractToFileHandle(
		new FileHandle($filename, 'w')) != AZ_OK) {
	    $i++;
	}

	# Make sure the tmp file gets unlinked.
	push @tmpfiles, $filename;
    }

    my $umodfile = new Umod(-file => $filename);
    if (!defined $umodfile) {
	$mw->Dialog(-title => 'XUmod: Warning',
	    -bitmap => 'warning',
	    -text => "Cannot open or parse $filename",
	    -buttons => [qw/Ok/])->Show;
	return 0;
    }

    $umod{$shortname}->{file} = $umodfile;
    $umod{$shortname}->{filename} = $filename;

    # Add a new page.
    my $page = $nb->add($shortname, -label => $shortname, -wraplength => 128,
	-raisecmd => \&onPageChange);

    # Umod info frame.
    my $umodinfoframe = $page->Frame;

    my $producte = $umodinfoframe->LabEntry(-label => 'Product',
	-labelPack => [-side => 'left', -anchor => 'w'],
	-width => 75,
	-textvariable => \$umodfile->product,
	-state => 'disabled')
	->pack(-side => 'top', -anchor => 'ne');
    $producte->bind('<ButtonPress-3>' => [\&postPopupMenu, Ev('X'), Ev('Y')]);
    $umod{$shortname}->{producte} = $producte;

    my $productpm = $producte->menu;
    $productpm->delete(0);
    my $browseproductpmi;
    if ($umodfile->producturl and $umodfile->producturl ne 'none') {
	$browseproductpmi = $productpm->command(-label => 'Browse',
	    -command => [\&showUrl, $umodfile->producturl]);
    } else {
	$browseproductpmi = $productpm->command(-label => 'Browse',
	    -state => 'disabled');
    }

    my $versione = $umodinfoframe->LabEntry(-label => 'Version',
	-labelPack => [-side => 'left', -anchor => 'w'],
	-width => 75,
	-textvariable => \$umodfile->version,
	-state => 'disabled')
	->pack(-side => 'top', -anchor => 'ne');
    $versione->bind('<ButtonPress-3>' => [\&postPopupMenu, Ev('X'), Ev('Y')]);
    $umod{$shortname}->{versione} = $versione;

    my $versionpm = $versione->menu;
    $versionpm->delete(0);
    my $browseversionpmi;
    if ($umodfile->versionurl and $umodfile->versionurl ne 'none') {
	$browseversionpmi = $versionpm->command(-label => 'Browse',
	    -command => [\&showUrl, $umodfile->versionurl]);
    } else {
	$browseversionpmi = $versionpm->command(-label => 'Browse',
	    -state => 'disabled');
    }

    my $developere = $umodinfoframe->LabEntry(-label => 'Developer',
	-labelPack => [-side => 'left', -anchor => 'w'],
	-width => 75,
	-textvariable => \$umodfile->developer,
	-state => 'disabled')
	->pack(-side => 'top', -anchor => 'ne');
    $developere->bind('<ButtonPress-3>' => [\&postPopupMenu, Ev('X'), Ev('Y')]);
    $umod{$shortname}->{developere} = $developere;

    my $developerpm = $developere->menu;
    $developerpm->delete(0);
    my $browsedeveloperpmi;
    if ($umodfile->developerurl and $umodfile->developerurl ne 'none') {
	$browsedeveloperpmi = $developerpm->command(-label => 'Browse',
	    -command => [\&showUrl, $umodfile->developerurl]);
    } else {
	$browsedeveloperpmi = $developerpm->command(-label => 'Browse',
	    -state => 'disabled');
    }

    $umodinfoframe->pack(-anchor => 'nw', -fill => 'x');

    # File list.
    my $filetree;
    $filetree = $page->Scrolled('Tree',
	-separator => '\\',
	-selectmode => 'extended', -exportselection => 'no',
	-columns => 2, -height => 12,
	-header => 'yes',
	-scrollbars => 'e',
	-browsecmd => \&onFileSelectionChange,
	-command => \&viewFile)
	->pack(-expand => 'yes', -fill => 'both');
    $filetree->columnWidth(0, -char => 72);
    $filetree->header('create', 0, -text => 'File');
    $filetree->header('create', 1, -text => 'Size');
    $filetree->bind('<ButtonPress-3>' =>
	[\&postFileTreePopupMenu, Ev('X'), Ev('Y'), Ev('y')]);

    my $filetreepm = $filetree->menu;
    $filetreepm->delete(0);

    my $viewpmi = $filetreepm->command(-label => 'View',
	-state => 'disabled',
	-command => \&viewFile);
    $umod{$shortname}->{viewpmi} = $viewpmi;

    my $extractpmi = $filetreepm->command(-label => 'Extract',
	-state => 'disabled',
	-command => \&extractFile);
    $umod{$shortname}->{extractpmi} = $extractpmi;

    foreach my $file ($umodfile->packingList) {
	addFile($filetree, $umodfile->product, $file->{src}, $file->{size},
	    ($umodfile->readmefile and $file->{src} eq $umodfile->readmefile
	    ? 'doc' : undef));
    }

    $umod{$shortname}->{filetree} = $filetree;

    # Change list.
    my $changetree = $page->Scrolled('Tree',
	-separator => '/',
	-selectmode => 'browse', -exportselection => 'no',
	-columns => 2, -height => 5,
	-header => 'yes',
	-drawbranch => 'no',
	-indent => 0,
	-scrollbars => 'e')
	->pack(-fill => 'x');
    $changetree->columnWidth(0, -char => 52);
    $changetree->header('create', 0, -text => 'Change');
    $changetree->header('create', 1, -text => 'Value');

    foreach my $change ($umodfile->iniChanges) {
	addChange($changetree,
	    $change->{file},
	    $change->{section}, $change->{key}, $change->{value},
	    $change->{add});
    }

    $umod{$shortname}->{changetree} = $changetree;

    $nb->raise($shortname);

    return 1;
}

sub openPlainZipFile {
    my ($filename) = @_;

    my $shortname = $filename;
    $shortname =~ s-.*[/\\]--;
    $shortname =~ s-\.(umod|zip)$--i;

    if (exists $umod{$shortname}) {
	my $i = 1;
	do { $i++; } until (!exists $umod{"$shortname#$i"});
	$shortname .= "#$i";
    }

    my $productname = `$pref{umod} -l $filename`;
    chomp $productname;

    my $zipfile = new Archive::Zip;
    if ($zipfile->read($filename) != AZ_OK) {
	$mw->Dialog(-title => 'XUmod: Warning',
	    -bitmap => 'warning',
	    -text => "Cannot open $filename as a zip file",
	    -buttons => [qw/Ok/])->Show;
	return 0;
    }

    $umod{$shortname}->{file} = $zipfile;
    $umod{$shortname}->{filename} = $filename;

    # Add a new page.
    my $page = $nb->add($shortname, -label => $shortname, -wraplength => 128,
	-raisecmd => \&onPageChange);

    # Umod info frame.
    my $umodinfoframe = $page->Frame;

    my $producte = $umodinfoframe->LabEntry(-label => 'Product',
	-labelPack => [-side => 'left', -anchor => 'w'],
	-width => 75,
	-textvariable => \$productname,
	-state => 'disabled')
	->pack(-side => 'top', -anchor => 'ne');
    $producte->bind('<ButtonPress-3>' => [\&postPopupMenu, Ev('X'), Ev('Y')]);
    $umod{$shortname}->{producte} = $producte;

    my $productpm = $producte->menu;
    $productpm->delete(0);
    my $browseproductpmi;
    {
	$browseproductpmi = $productpm->command(-label => 'Browse',
	    -state => 'disabled');
    }

    $umodinfoframe->pack(-anchor => 'nw', -fill => 'x');

    # File list.
    my $filetree;
    $filetree = $page->Scrolled('Tree',
	-separator => '\\',
	-selectmode => 'extended', -exportselection => 'no',
	-columns => 2, -height => 12,
	-header => 'yes',
	-scrollbars => 'e',
	-browsecmd => \&onFileSelectionChange,
	-command => \&viewFile)
	->pack(-expand => 'yes', -fill => 'both');
    $filetree->columnWidth(0, -char => 72);
    $filetree->header('create', 0, -text => 'File');
    $filetree->header('create', 1, -text => 'Size');
    $filetree->bind('<ButtonPress-3>' =>
	[\&postFileTreePopupMenu, Ev('X'), Ev('Y'), Ev('y')]);

    my $filetreepm = $filetree->menu;
    $filetreepm->delete(0);

    my $viewpmi = $filetreepm->command(-label => 'View',
	-state => 'disabled',
	-command => \&viewFile);
    $umod{$shortname}->{viewpmi} = $viewpmi;

    my $extractpmi = $filetreepm->command(-label => 'Extract',
	-state => 'disabled',
	-command => \&extractFile);
    $umod{$shortname}->{extractpmi} = $extractpmi;

    foreach my $file ($zipfile->memberNames) {
	$file =~ s-/-\\-g;
	next if ($file =~ /\\$/);
	addFile($filetree, $productname, $file, , undef);
    }

    $umod{$shortname}->{filetree} = $filetree;

    $nb->raise($shortname);

    return 1;
}

sub addFile {
    my ($tree, $productname, $filename, $size, $type) = @_;

    if (!$tree->info('exists', $productname)) {
	$tree->add($productname, -text => $productname, -image => 'gear');
	$tree->setmode($productname, 'close');
    }

    # Make preceeding directories.
    my $path = $filename;
    my $prefix;
    while( $path =~ m/^([^\\]*)\\/ ) {
	if (defined $prefix) { $prefix .= "\\".$1; } else { $prefix = $1; }
	if (!$tree->info('exists', "$productname\\$prefix")) {
	    $tree->add("$productname\\$prefix", -text => $1,
		-image => $mw->Getimage('winfolder'));
	    $tree->setmode("$productname\\$prefix", 'close');
	}
	$path = $';
    }

    my $shortname = $filename;
    $shortname =~ s-.*[/\\]--;
    $tree->add("$productname\\$filename", -text => $shortname,
	-image => getIcon($filename, $type));

    $tree->itemCreate("$productname\\$filename", 1, -itemtype => 'text',
	-text => $size, -style => $tree->ItemStyle('text', -anchor => 'e'));
}

sub addChange {
    my ($tree, $file, $section, $key, $value, $add) = @_;

    $tree->add($file, -text => $file, -image => 'pencil')
	unless ($tree->info('exists', $file));

    $tree->add("$file/$section", -text => "[$section]", -image => 'null')
	unless ($tree->info('exists', "$file/$section"));

    $tree->add("$file/$section/$key", -text => $key,
	-image => $add ? 'plus' : 'equal')
	unless ($tree->info('exists', "$file/$section/$key"));

    $tree->itemCreate("$file/$section/$key", 1, -itemtype => 'text',
	-text => $value);
}

sub closeUmodFile {
    my $target = $nb->raised;

    delete $umod{$target};
    $nb->delete($target);

    if (!defined $nb->raised) {
	changeApplicability(-close => 'no', -umod => 'no',
	    -install => 'no', -readme => 'no',
	    -view => 'no', -extract => 'no');
	$mw->title('XUmod');
    } else {
	onFileSelectionChange();
    }
}

#
# System Umod Functions
#

sub openInstalledUmod {
    # Add a new page.
    my $page = $nb->add('_system', -label => 'System', -image => 'gear',
	-raisecmd => \&onPageChange);

    # Product list.
    my $filetree;
    $filetree = $page->Scrolled('Tree',
	-separator => '\\',
	-selectmode => 'extended', -exportselection => 'no',
	-columns => 2, -width => 80, -height => 24,
	-header => 'yes',
	-scrollbars => 'e',
	-browsecmd => \&onInstalledFileSelectionChange,
	-command => \&viewInstalledFile)
	->pack(-expand => 'yes', -fill => 'both');
    $filetree->columnWidth(0, -char => 72);
    $filetree->header('create', 0, -text => 'Product');
    $filetree->header('create', 1, -text => 'Version');
    $filetree->bind('<ButtonPress-3>' =>
	[\&postInstalledFileTreePopupMenu, Ev('X'), Ev('Y'), Ev('y')]);

    $umod{_system}->{filetree} = $filetree;

    my $filetreepm = $filetree->menu;
    $filetreepm->delete(0);

    my $viewpmi = $filetreepm->command(-label => 'View',
	-state => 'disabled',
	-command => \&viewInstalledFile);

    my $uninstallpmi = $filetreepm->command(-label => 'Uninstall',
	-command => \&uninstallInstalledUmod);

    $umod{_system}->{viewpmi} = $viewpmi;
    $umod{_system}->{uninstallpmi} = $uninstallpmi;

    refreshInstalled();
}

sub refreshInstalled {
    # Populate product list.
    my $manifestname =
	Config::Ini::adjustpathcase("$pref{basedir}/System/Manifest.ini");
    return 0 if (!-r $manifestname);
    $manifest = new Config::Ini($manifestname);
    @products = $manifest->get(['Setup', 'Group'], -mapping => 'multiple');

    # List products sorted by their localized captions (may be different
    # from the product names).
    foreach (
        map  { $_->[0] }
        sort { $a->[1] cmp $b->[1] }
        map  { [$_, $manifest->get( [$_, 'Caption'], -mapping => 'single')] }
	@products) {
	addInstalled($_, $manifest->get([$_, 'Version'], -mapping => 'single'),
	    'gear', $manifest->get([$_, 'File'], -mapping => 'multiple'));
    }

    addUnreferencedFiles();
}

sub addInstalled {
    my ($productname, $version, $icon, @files) = @_;

    my $tree = $umod{_system}->{filetree};
    $tree->add($productname, -text => $productname, -image => $icon);
    $tree->itemCreate($productname, 1, -itemtype => 'text', -text => $version,
	-style => $tree->ItemStyle('text', -anchor => 'e'));

    foreach my $file (@files) {
	$file =~ s-/-\\-g;

	# make preceeding directories
	my $path = $file;
	my $prefix;
	while( $path =~ m-^([^\\]*)\\- ) {
	    if (defined($prefix)) { $prefix .= "\\".$1; } else { $prefix = $1; }
	    if (!$tree->info('exists', "$productname\\$prefix")) {
		$tree->add("$productname\\$prefix", -text => $1,
		    -image => $mw->Getimage('winfolder'));
		$tree->setmode("$productname\\$prefix", 'close');
	    }
	    $path = $';
	}

	$tree->add("$productname\\$file", -text => $path,
	    -image => getIcon($path));
    }

    $tree->setmode($productname, 'close');
    $tree->close($productname);
}

sub addUnreferencedFiles {
    my (%allfiles);
    find({ wanted => sub {
	my $file = $File::Find::name;
	return 0 if (-d $file);

	$file =~ s-$pref{basedir}/--;
        $file =~ s-/-\\-g;

        $allfiles{ lc( $file ) } = $file; }, follow => 1 },
	$pref{basedir});

    # Remove files belonged to product groups.
    if (!defined $products[0]) {
	my $manifestname =
	    Config::Ini::adjustpathcase("$pref{basedir}/System/Manifest.ini");
	return 0 if (!-r $manifestname);

	$manifest = new Config::Ini($manifestname);
	@products = $manifest->get(['Setup', 'Group'], -mapping => 'multiple');
    }

    foreach ( @products ) {
	foreach ( $manifest->get( [$_, 'File'], -mapping => 'multiple') ) {
	    delete( $allfiles{ lc( $_ ) } )
	      if( exists( $allfiles{ lc( $_) } ) );
	}
    }

    addInstalled('Others', undef, 'questiongear', values %allfiles);
}

#
# Umod File Handling
#

sub installUmodFile {
    my ($filename) = $umod{$nb->raised}->{filename};
    my $errors;

    my $force = $pref{force} ? ' -f' : '';

    open OUT, "$pref{umod}$force -b $pref{basedir} -i $filename 2>&1|";
    while (<OUT>) { $errors .= $_; }
    close OUT;

    if ($errors) {
	$mw->Dialog(-title => 'XUmod: Error',
	    -bitmap => 'error',
	    -text => $errors,
	    -buttons => [qw/Ok/])->Show;
	$st->configure(-text => 'Installation failed.');
    } else {
	$st->configure(-text => 'Installation complete.');
    }

    $umod{_system}->{filetree}->delete('all');
    refreshInstalled();
}

sub checkUmodFile {
    return checkInstalledUmod() if ($nb->raised eq '_system');

    $mw->Dialog(-title => 'XUmod: Information',
	-bitmap => 'info',
	-text => 'Umod file checking not yet implemented.',
	-buttons => [qw/Ok/])->Show;
}

sub extractFile {
    my ($filename) = $umod{$nb->raised}->{filename};
    my $tree = $umod{$nb->raised}->{filetree};
    my ($errors, $good);

    my $force = $pref{force} ? ' -f' : '';

    foreach my $file ($tree->info('selection')) {
	next if (defined $tree->info('children', $file));

	my $fileEsc = $file;
	$fileEsc =~ s/[^\\]*\\//;
	unless ($^O eq 'MSWin32') {
	    $fileEsc =~ s/\\/\\\\/g;
	    $fileEsc =~ s/ /\\ /g;
	}
	my $line = `$pref{umod}$force -b $pref{extractdir} -x $fileEsc $filename 2>&1`;
	if ($line) {
	    $errors .= $line;
	} else {
	    $good++;
	    $tree->selectionClear($file);
	}
    }

    if ($errors) {
	$mw->Dialog(-title => 'XUmod: Error',
	    -bitmap => 'error',
	    -text => $errors,
	    -buttons => [qw/Ok/])->Show;

	if (!$good) {
	    $st->configure(-text => 'Extraction failed.');
	} else {
	    $st->configure(-text => "Only $good files extracted.");
	}
    } else {
	$st->configure(-text => 'Extraction complete.');
    }
}

sub viewFile {
    return viewInstalledFile() if ($nb->raised eq '_system');

    my $umodfile = $umod{$nb->raised}->{file};

    my ($tree) = $umod{$nb->raised}->{filetree};
    foreach my $file ($tree->info('selection')) {
	# Can't view a directory.
	next if (defined $tree->info('children', $file));

	$file =~ s/[^\\]*\\//;

	my ($packedFile, $content);
	if (isa($umodfile, 'Archive::Zip')) {
	    my $fileEsc = $file;
	    $fileEsc =~ s-\\-/-g;
	    my $packedFile = $umodfile->memberNamed($fileEsc);
	    $content = $umodfile->contents($packedFile);
	} else {
	    my @packingList = $umodfile->packingList;
	    ($packedFile) = grep ( { $_->{src} eq $file } @packingList);
	    $content = $umodfile->dump($packedFile);
	}

	SWITCH: for (lc($file)) {
	    /\.html?$/ && do {
		showHtml(-data => $content, -file => $file)
		    unless (!defined $content);
		last; };
	    /\.(bmp|gif|jpe?g|png|xpm)$/ && do {
		showImage(-data => $content, -file => $file)
		    unless (!defined $content);
		last; };
	    /\.(uax|umx)$/ && do {
		showUmx(-data => $content, -file => $file)
		    unless (!defined $content);
		last; };
	    /\.(txt|ini|int|log)$/ && do {
		showText(-data => $content, -file => $file)
		    unless (!defined $content);
		last; };
	}
    }
}

sub viewReadme {
    my ($tree) = $umod{$nb->raised}->{filetree};

    $tree->selectionClear;
    $tree->selectionSet($umod{$nb->raised}->{file}->product.'\\'
	.$umod{$nb->raised}->{file}->readmefile);

    viewFile();
}

#
# Installed File Handling
#

sub uninstallInstalledUmod {
    return 0 if ($nb->raised ne '_system');

    my ($tree) = $umod{_system}->{filetree};
    my ($errors, $good);

    foreach my $product ($tree->info('selection')) {
	next if (defined $tree->info('parent', $product));

	if ($product =~ m/Unreal ?Tournament/) {
	    $mw->Dialog(-title => 'XUmod: Warning',
		-bitmap => 'warning',
		-text => "The core module $product cannot be uninstalled.",
		-buttons => [qw/Ok/])->Show;

	    next;
	}

	my $answer = $mw->Dialog(-title => 'XUmod: Question',
	    -bitmap => 'question',
	    -text => "Are you sure you want to uninstall $product?",
	    -default_button => 'No',
	    -buttons => [qw/Yes No/])->Show;

	next if ($answer eq 'No');

	my $line = `$pref{umod} -b $pref{basedir} -u '$product' 2>&1`;
	if ($line) {
	    $errors .= $line;
	} else {
	    $good++;
	}
    }

    if ($errors) {
	$mw->Dialog(-title => 'XUmod: Error',
	    -bitmap => 'error',
	    -text => $errors,
	    -buttons => [qw/Ok/])->Show;

	if (!$good) {
	    $st->configure(-text => 'Uninstallation failed.');
	} else {
	    $st->configure(-text => "Only $good products extracted.");
	}
    } elsif ($good) {
	$st->configure(-text => 'Uninstallation complete.');
    } else {
	$st->configure(-text => 'Uninstallation aborted.');
    }

    $tree->delete('all');
    refreshInstalled();
}

sub viewInstalledFile {
    my ($tree) = $umod{_system}->{filetree};

    foreach my $file ($tree->info('selection')) {
	# Can't view a directory.
	next if (defined $tree->info('children', $file));

	$file =~ s/[^\\]*\\//;
	$file = Config::Ini::adjustpathcase("$pref{basedir}/$file");

	SWITCH: for (lc($file)) {
	    /\.html?$/ && do {
		showHtml(-file => $file); last; };
	    /\.(bmp|gif|jpe?g|png|xpm)$/ && do {
		showImage(-file => $file); last; };
	    /\.(uax|umx)$/ && do {
		showUmx(-file => $file); last; };
	    /\.(txt|ini|int|log)$/ && do {
		showText(-file => $file); last; };
	}
    }
}

sub checkInstalledUmod {
    return 0 if ($nb->raised ne '_system');

    my ($tree) = $umod{_system}->{filetree};

    my $missing;
    foreach my $product (@products) {
	foreach my $file ($manifest->get([$product, 'File'],
	    -mapping => 'multiple')) {
	    if (!-e Config::Ini::adjustpathcase("$pref{basedir}/$file")) {
		my $me = "$product\\$file";
		$tree->entryconfigure($me, -image => 'cross');

		# Open up preceeding directories.
		while ($tree->info('parent', $me)) {
		    $me = $tree->info('parent', $me);
		    $tree->open($me);
		}

		$missing++;
	    }
	}
    }

    if ($missing) {
	$mw->Dialog(-title => 'XUmod: Warning',
	    -bitmap => 'warning',
	    -text => "Some files are missing.  Missing files have been marked with a cross.",
	    -buttons => [qw/Ok/])->Show;

	$st->configure(-text => "Check complete.  $missing file"
	    .($missing > 1 ? 's' : '').' missing.');
    } else {
	$st->configure(-text => 'Check complete.  All products checked ok.');
    }
}

sub onInstalledFileSelectionChange {
    my ($tree) = $umod{_system}->{filetree};

    changeApplicability(-view => 'no', -uninstall => 'no');

    # Allow view/extract only if selected item is a file, and a viewer
    # has been defined.
    foreach my $item ($tree->info('selection')) {
	if (defined $tree->info('children', $item)) {
	    if ($item ne 'Others' and !defined $tree->info('parent', $item)) {
		changeApplicability(-uninstall => 'yes');
	    }
	    next;
	}

	SWITCH: for (lc($item)) {
	    /\.html?$/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showhtml});
		last; };
	    /\.(bmp|gif|jpe?g|png|xpm)$/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showimage});
		last; };
	    /\.(uax|umx)$/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showmusic});
		last; };
	    /\.(txt|ini|int|log)$/ && do {
		changeApplicability(-view => 'yes')
		    unless (!$pref{showtext});
		last; };
	}
    }
}

#
# Help Functions
#

sub about {
    my $message = <<EOT;
XUmod 0.5-beta16

Copyright (C) 2000 Avatar <avatar\@deva.net>

EOT

    $message .= `$pref{umod} -V`;
    $message =~ s/NO\nwarr/NO warr/s;

    my $db = $mw->DialogBox(-title => 'About XUmod', -buttons => ['Ok']);

    my $f = $db->Frame;
    $f->Label(-image => 'avatar')->pack(-expand => 'yes', -fill => 'both');
    $f->Label(-text => $message, -justify => 'left', -wraplength => 320)
	->pack(-expand => 'yes', -fill => 'both');
    $f->pack(-anchor => 'nw', -fill => 'both');

    $db->Show;
}

#
# Option Management
#

sub saveOptions {
    if (open RCFILE, ">$rcfile") {
	while (my ($key, $value) = each %pref) {
	    print RCFILE "$key = $value\n";
	}
	close RCFILE;
    } else { return 0; }

    return 1;
}

sub loadOptions {
    if (-e "$rcfile") {
	if (open(RCFILE, "<$rcfile")) {
	    while (<RCFILE>) {
		chomp;
		s/#.*//;
		s/^\s+//;
		s/\s+$//;
		next unless length;
		my ($var, $value) = split(/\s*=\s*/, $_, 2);
		$pref{$var} = $value;
		$pref{$var} = 0 if ($value eq 'no' or $value eq 'false');
	    }
	    close RCFILE;
	}
    }
}

sub scansystem {
    # Scan the system for Unreal games.
    if (!defined $pref{basedir}) {
	foreach my $dir ( '/usr/local/games/ut', '/usr/games/ut',
	    '/usr/local/games/UnrealTournament', '/usr/games/UnrealTournament',
	    'C:/UnrealTournament', '.' ) {
	    my $sysdir;
	    if ($sysdir = $dir.'/'
		    .(Config::Ini::adjustfilecase('System', $dir)),
		-e $sysdir.'/'
		    .(Config::Ini::adjustfilecase('Core.u', $sysdir))) {
		$pref{basedir} = $dir;
		saveOptions();
		last;
	    }
	}
    }

    # Ask for a valid base directory containing an Unreal game.
    my $sysdir;
    # Don't adjust the case of the base dir.
    while ($sysdir = $pref{basedir}.'/'
	    .(Config::Ini::adjustfilecase('System', $pref{basedir})),
	!-e $sysdir.'/'.(Config::Ini::adjustfilecase('Core.u', $sysdir))) {
	$mw->Dialog(-title => 'XUmod: Warning',
	    -bitmap => 'warning',
	    -text => "No Unreal game found"
		.($pref{basedir} ? " in $pref{basedir}.\n" : ".\n")
		."Please enter a valid path.",
	    -buttons => [qw/Ok/])->Show;

	setup();
    }

    # Check for the availability of the umod program.
    if (!-x $pref{umod}) {
	my $found;
	my $umod = ($^O eq 'MSWin32' and $pref{umod} !~ /.bat$/)
	    ? "$pref{umod}.bat" : $pref{umod};
    	while (!$found) {
	    foreach (File::Spec->path) {
		my $try = File::Spec->catfile($_, $umod);
		if (-x $try) {
		    $pref{umod} = $try;
		    saveOptions();

		    $found = 1;
		    last;
		}
	    }

	    if (!$found) {
		$mw->Dialog(-title => 'XUmod: Error',
		    -bitmap => 'error',
		    -text => qq[Cannot run the "umod" program .\nPlease make sure it is on the path.],
		    -buttons => [qw/Ok/])->Show;

		# Popping up the setup dialog probably won't help.  Just quit.
		exit(1);

		setup();
	    }
	}
    }

    # Call upon the service of umod to create the Manifest.ini if not exists.
    my $errors;
    if (!-e $sysdir.'/'
	    .(Config::Ini::adjustfilecase('Manifest.ini', $sysdir))) {
	$errors = `$pref{umod} -f -b $pref{basedir} 2>&1 >/dev/null`;
	$mw->Dialog(-title => 'XUmod: Information',
	    -bitmap => 'info',
	    -text => $errors,
	    -buttons => [qw/Ok/])->Show;
    }
}

sub setup {
    my $db = $mw->DialogBox(-title => 'XUmod: Setup',
	    -default_button => 'Ok',
	    -buttons => [qw/Ok Cancel/]);

    my $nb = $db->add('NoteBook', -ipadx => 6, -ipady => 6);

    my $dirpg = $nb->add('dir', -label => 'Directories', -underline => 0);
    $dirpg->LabEntry(-label => 'Unreal',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{basedir})
	->pack(-side => 'top', -anchor => 'ne');
    $dirpg->LabEntry(-label => 'Extract to',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{extractdir})
	->pack(-side => 'top', -anchor => 'ne');

    my $apppg = $nb->add('app', -label => 'Applications', -underline => 0);
    $apppg->LabEntry(-label => 'umod',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{umod})
	->pack(-side => 'top', -anchor => 'ne');
    $apppg->LabEntry(-label => 'umr',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{umr})
	->pack(-side => 'top', -anchor => 'ne');
    $apppg->LabEntry(-label => 'Text Viewer',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{showtext})
	->pack(-side => 'top', -anchor => 'ne');
    $apppg->LabEntry(-label => 'Image Viewer',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{showimage})
	->pack(-side => 'top', -anchor => 'ne');
    $apppg->LabEntry(-label => 'Music Player',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{showmusic})
	->pack(-side => 'top', -anchor => 'ne');
    $apppg->LabEntry(-label => 'HTML Viewer',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{showhtml})
	->pack(-side => 'top', -anchor => 'ne');
    $apppg->LabEntry(-label => 'Web Browser',
	-width => 40,
	-labelPack => [-side => 'left', -anchor => 'w'],
	-textvariable => \$pref{showurl})
	->pack(-side => 'top', -anchor => 'ne');

    $nb->pack(-expand => 'yes', -fill => 'both', -padx => 3, -pady => 3,
	-side => 'top');

    if ($db->Show eq 'Ok') { saveOptions(); }
}

#
# Utility Functions
#

sub showText {
    my (%args) = @_;

    if ($pref{showtext} eq '(builtin)') {
	my ($content, $title);

	if (exists $args{-file} and !exists $args{-data}) {
	    my $file = $args{-file};
	    
	    open FILE, "<$file"
		or $mw->Dialog(-title => 'XUmod: Warning',
		    -bitmap => 'warning',
		    -text => "Cannot open $file for reading: $!",
		    -buttons => [qw/Ok/])->Show,
		return;
	    undef $/;
	    $content = <FILE>;
	    $/ = "\n";
	    close FILE;
	}

	$content = $args{-data} if (exists $args{-data});

	$content =~ s/\r//mg;

	my $db = $mw->DialogBox(-title => $args{-file}, -buttons => ['Ok']);
	my $t = $db->Scrolled('ROText',
	    -height => 35, -width => 80,
	    -scrollbars => 'e',
	    -setgrid => 'yes')
	    ->pack(-expand => 'yes', -fill => 'both');
	$t->insert('0.0', $content);
	$db->Show;
    } else {
	my ($file);

	if (exists $args{-data}) {
	    $file = $args{-file} || 'noname.txt';
	    $file =~ s-\\-_-g;
	    $file =~ s-/-_-g;

	    # Munge a unique file.  Beware of symlink attacks.
	    my $i = 0;
	    my $tmpfile;
	    while ($tmpfile = $tmpdir.'/'.$i.'-'.$file,
		# Don't reuse old file, it may not be the same.
		-e $tmpfile or !open TMPFILE, ">$tmpfile") {
		$i++;
	    }

	    # Make sure the tmp file gets unlinked.
	    push @tmpfiles, $tmpfile;

	    print TMPFILE $args{-data};
	    close TMPFILE;

	    $file = $tmpfile;
	} else {
	    $file = $args{-file};
	}

	my $cmd = $pref{showtext};
	$cmd =~ s/%s/$file/g;
	system $cmd;
    }
}

sub showUmx {
    my (%args) = @_;
    my ($file, $tmpfile);

    if (exists $args{-data}) {
	$file = $args{-file} || 'noname.umx';
	$file =~ s-\\-_-g;
	$file =~ s-/-_-g;

	# Munge a unique file.  Beware of symlink attacks.
	my $i = 0;
	my $tmpfile;
	while ($tmpfile = $tmpdir.'/'.$i.'-'.$file,
	    # Don't reuse old file, it may not be the same.
	    -e $tmpfile or !open TMPFILE, ">$tmpfile") {
	    $i++;
	}

	# Make sure the tmp file gets unlinked.
	push @tmpfiles, $tmpfile;

	print TMPFILE $args{-data};
	close TMPFILE;

	$file = $tmpfile;
    } else {
	$file = $args{-file};
    }

    my $origdir = File::Spec->curdir;
    chdir $tmpdir
	or warn "$0: cannot change to temporary directory $tmpdir: $!\n";

    open OUT, "$pref{umr} $file|";
    while (<OUT>) {
	chomp;
	if (/\(Music, /) {
	    my ($extracted, $size) = m/(\S+)\s*\(Music, (\S+)/;

	    my $cmd = $pref{showmusic};
	    $cmd =~ s/%s/$extracted/g;
	    system $cmd;

	    push @tmpfiles, $extracted if ($extracted);
	}

	if (/\(Sound, /) {
	    my ($extracted, $size) = m/(\S+)\s*\(Sound, (\S+)/;

	    my $cmd = $pref{showsound};
	    $cmd =~ s/%s/$extracted/g;
	    system $cmd;

	    push @tmpfiles, $extracted if ($extracted);
	}
    }
    close(OUT);

    chdir $origdir
	or warn "$0: cannot change back to directory $origdir: $!\n";

    push @tmpfiles, $tmpfile if ($tmpfile);
}

sub showImage {
    my (%args) = @_;
    my ($file, $tmpfile);

    if (exists $args{-data}) {
	$file = $args{-file} || 'noname.bmp'; # Pure guess.
	$file =~ s-\\-_-g;
	$file =~ s-/-_-g;

	# Munge a unique file.  Beware of symlink attacks.
	my $i = 0;
	my $tmpfile;
	while ($tmpfile = $tmpdir.'/'.$i.'-'.$file,
	    # Don't reuse old file, it may not be the same.
	    -e $tmpfile or !open TMPFILE, ">$tmpfile") {
	    $i++;
	}

	# Make sure the tmp file gets unlinked.
	push @tmpfiles, $tmpfile;

	print TMPFILE $args{-data};
	close TMPFILE;

	$file = $tmpfile;
    } else {
	$file = $args{-file};
    }

    my $cmd = $pref{showimage};
    $cmd =~ s/%s/$file/g;
    system $cmd;

    push @tmpfiles, $tmpfile if ($tmpfile);
}

sub showHtml {
    my (%args) = @_;
    my ($file, $tmpfile);

    if (exists $args{-data}) {
	$file = $args{-file} || 'noname.html';
	$file =~ s-\\-_-g;
	$file =~ s-/-_-g;

	# Munge a unique file.  Beware of symlink attacks.
	my $i = 0;
	my $tmpfile;
	while ($tmpfile = $tmpdir.'/'.$i.'-'.$file,
	    # Don't reuse old file, it may not be the same.
	    -e $tmpfile or !open TMPFILE, ">$tmpfile") {
	    $i++;
	}

	# Make sure the tmp file gets unlinked.
	push @tmpfiles, $tmpfile;

	print TMPFILE $args{-data};
	close TMPFILE;

	$file = $tmpfile;
    } else {
	$file = $args{-file};
    }

    my $cmd = $pref{showhtml};
    $cmd =~ s/%s/$file/g;
    system $cmd;

    push @tmpfiles, $tmpfile if ($tmpfile);
}

sub showUrl {
    my ($url) = @_;

    if (defined $url) {
	my $cmd = $pref{showurl};
	$cmd =~ s/%s/$url/g;
	system $cmd;
    }
}

#
# Icon Management
#

# Prepare icons in main window.
sub prepIcons {
    my $avataricon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char *avatar_xpm[] = {
/* width height ncolors chars_per_pixel */
"32 40 6 1",
/* colors */
"` c #000000",
"a c #7F6464",
"b c #F8F8C0",
"c c None",
"d c #FFFF80",
"e c #FFFFFF",
/* pixels */
"cccccccccccccccccccccccccccccccc",
"ccccccccccccccccaaaaaacccccccccc",
"ccc``cccccccccaa``````aaaacccccc",
"ccc``cccccccca````````````aacccc",
"cccc`cccccccca``````````````aacc",
"ccc``````ccca```````````d`````ac",
"ccc``````ccca`````````````````ac",
"ccccccccccca``````aaaaaaa````acc",
"cccc`````cca```````````````aaccc",
"ccc`````ccca````````````````aacc",
"ccc`c`cc`ccaa`````````````````ac",
"ccc`c````cca````aabbbbbbba`aaaac",
"cccccc``ccca``````````````accccc",
"cccccccccccca``a`eee`b`eee`ccccc",
"ccc`cccc`ccca`ab`eee`b`eee`ccccc",
"ccc`cccc`ccca`abb```bbb```cccccc",
"cc```````ccccaabbbbbbbabaccccccc",
"cc``````ccccccabbabbbabbaccccccc",
"ccc`cccccccccaaa`babbbbaaccccccc",
"cccccccccccca``a``babba`aacccccc",
"cccc`````cca````a``aaa``aacccccc",
"ccc`````ccca``a``aaeea`a``accccc",
"ccc`c`cc`cca``a```aeeaa```accccc",
"ccc`c````cca``a````aea``a``ccccc",
"cccccc``ccca```a```aea``a``acccc",
"cccccccccca````a```aea``a``acccc",
"ccc``ccccca`````a``aea``a``acccc",
"ccc````cccaa```a```aaa`````acccc",
"cccccc```ccaa`a``a`a`a``a`accccc",
"ccccc````cccaa``a``a`a```acccccc",
"ccc````ccccca``````a`a```acccccc",
"ccc``ccccccca``````a`a```acccccc",
"cccccc```ccca``````aaa```acccccc",
"cccc`````ccca``````a`a```acccccc",
"cc`````cccccaaaa```a`a``aacccccc",
"cc``cc`ccccccccaaaaa`aaacccccccc",
"cccc```cccccccca```a``accccccccc",
"cccccc```ccccca````a```acccccccc",
"ccccccccccccccaaaaaaaaaacccccccc",
"cccccccccccccccccccccccccccccccc"
};
EOT

    my $ufileicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char *ufile_xpm[] = {
/* width height ncolors chars_per_pixel */
"12 12 8 1",
/* colors */
"  c None",
"` c #000000",
"a c #555555",
"b c #AAAAAA",
"c c #8E8E8E",
"d c #727272",
"e c #FFFFFF",
"f c #C7C7C7",
/* pixels */
" ````````   ",
" `eeeeee`   ",
" `eeeeee``` ",
" `eeeeeeee` ",
" `ea`eb`be` ",
" `eb`ee`be` ",
" `eb`ee`be` ",
" `eb`ee`be` ",
" `eb`ff`ce` ",
" `eecabdbe` ",
" `eeeeeeee` ",
" `````````` "
};
EOT

    my $graphicsfileicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char *graphicsfile_xpm[] = {
/* width height ncolors chars_per_pixel */
"12 12 14 1",
/* colors */
"  c None",
"` c #000000",
"a c #CBEBCF",
"b c #37B247",
"c c #BEC8B5",
"d c #F0EFF5",
"e c #1C49FF",
"f c #FFFFFF",
"g c #3CB34C",
"h c #355DFF",
"i c #FF1212",
"j c #B079B8",
"k c #A9DEB0",
"l c #D0E3E7",
/* pixels */
" ````````   ",
" `ffffff`   ",
" `fffdcf``` ",
" `fjiiifff` ",
" `fiijkgkf` ",
" `ffkgbgkf` ",
" `kbbgafff` ",
" `lkdlhehf` ",
" `fdeeehdf` ",
" `fdehffff` ",
" `ffffffff` ",
" `````````` "
};
EOT

    my $musicfileicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char *musicfile_xpm[] = {
/* width height ncolors chars_per_pixel */
"12 12 15 1",
/* colors */
"` c #000000",
"a c #A22222",
"b c #920404",
"c c #A43636",
"d c #BD2222",
"e c #C46767",
"f c #C45353",
"g c #F1E5E5",
"h c #B54E4E",
"i c #FFFFFF",
"j c #CE8989",
"k c None",
"l c #A40A0A",
"m c #D29393",
"n c #B73A3A",
/* pixels */
"k````````kkk",
"k`iiiiii`kkk",
"k`iijegi```k",
"k`iinbahgi`k",
"k`iinacchi`k",
"k`iinmiiii`k",
"k`iinmiiii`k",
"kffflmiiii`k",
"fdddlmiiii`k",
"kffbeiiiii`k",
"k`gmgiiiii`k",
"k``````````k"
};
EOT

    my $libraryfileicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char * binary_xpm[] = {
"12 12 3 1",
" 	c None",
".	c #000000",
"+	c #DDDDFF",
" ........   ",
" .++++++.   ",
" .++++++... ",
" .++++++++. ",
" .++..++.+. ",
" .+.++.+.+. ",
" .+.++.+.+. ",
" .+.++.+.+. ",
" .+.++.+.+. ",
" .++..++.+. ",
" .++++++++. ",
" .......... "};
EOT

    my $gearicon = <<EOT;
/* XPM */
/* Taken from KDE */
static char*wheel[]={
"18 18 6 1",
"d c #808080",
"b c #a0a0a4",
"c c #c0c0c0",
"# c #000000",
". c None",
"a c #ffffff",
"........##........",
".......#ab#.......",
"...##..#ab#..##...",
"..#ba##accd##ab#..",
"..#dcaaccccaacd#..",
"...#bccccccccd#...",
"...#accbdbcccb#...",
".##accbd##acccb##.",
"#aacccd#..#acccab#",
"#ddcccb#..#acccdd#",
".##bbcca##acccd##.",
"...#bcccaacccd#...",
"...#accccccccd#...",
"..#acdbcccccacd#..",
"..#bd##accb##bd#..",
"...##..#ad#..##...",
".......#ad#.......",
"........##........"};
EOT

    my $questiongearicon = <<EOT;
/* XPM */
/* Modified gear icon from KDE */
static char * questiongear_xpm[] = {
"18 18 42 1",
" 	c None",
".	c #646464",
"+	c #FFFFFF",
"A	c #C5C5C8",
"#	c #FF0000",
"S	c #595959",
"%	c #616161",
"&	c #9E9EA0",
"*	c #B2B2B2",
"=	c #D9D9D9",
"-	c #363636",
";	c #444444",
">	c #494949",
",	c #4C4C4C",
"'	c #A4A4A4",
")	c #BCBCBC",
"!	c #212121",
"~	c #C1C1C1",
"{	c #434344",
"]	c #4F4F4F",
"^	c #AEAEB1",
"/	c #2C2C2D",
"(	c #2A2A2A",
"_	c #D3D3D3",
":	c #B0B0B2",
"<	c #9A9A9A",
"[	c #575757",
"}	c #414141",
"|	c #AEAEAE",
"1	c #000000",
"2	c #1F1F1F",
"3	c #6D6D6D",
"4	c #191919",
"5	c #282828",
"6	c #C7C7C7",
"7	c #515151",
"8	c #4A4A4A",
"9	c #ADADAD",
"0	c #AFAFB2",
"a	c #737373",
"b	c #9E9E9E",
"c	c #222222",
"        ..        ",
"       .+A.       ",
"   ..  ######S%   ",
"  .A+.########&%  ",
"  .*=###-;>###,S  ",
"   .A###;')###!   ",
"   .+~%{]^###/(   ",
" ..+=_:<####-[&%. ",
".++===*####}[|_+A.",
".**===###123|_=**.",
" ..AA###456~_=*.. ",
"   .A###76_==*.   ",
"   .+~%8%~===*.   ",
"  .+=90)~_==+=*.  ",
"  .A*###~_A..A*.  ",
"   ..###ab.  ..   ",
"      1cab.       ",
"        S%        "};
EOT

    my $openfileicon = <<EOT;
/* XPM */
/* Taken from KDE */
/* Drawn  by Mark Donohoe for the K Desktop Environment */
/* See http://www.kde.org */
static char*fileopen[]={
"22 22 6 1",
"# c #000000",
"d c #808080",
"c c #c0c0c0",
"b c #ffffff",
"a c #dcdcdc",
". c None",
"......................",
"......................",
"............####......",
"...............##.#...",
"................###...",
"................###...",
"...............####...",
"....####..............",
"....#aba#######.......",
"....#babababab#.......",
"....#aa##########.....",
"....#ba#aacccccd#.....",
"....#a#aacacccd#......",
"....#a#aacccdcd#......",
"....##aacacccd#.......",
"....##aacccdcd#.......",
"....#dddddddd#........",
"....##########........",
"......................",
"......................",
"......................",
"......................"};
EOT

    my $savefileicon = <<EOT;
/* XPM */
/* Taken from KDE */
/* Drawn  by Mark Donohoe for the K Desktop Environment */
/* See http://www.kde.org */
static char*filefloppy[]={
"22 22 6 1",
"# c #000000",
"b c #ffffff",
"c c #c0c0c0",
"d c #808080",
"a c #c0c0c0",
". c None",
"......................",
"......................",
"......................",
"......................",
"....#############.....",
"....#abbbbbbbbcc#d....",
"....#ab#####bbcc#d....",
"....#abbbbbbbbcc#d....",
"....#ab###bbbbcc#d....",
"....#abbbbbbbbcc#d....",
"....#abbbbbbbbcc#d....",
"....#acccccccccc#d....",
"....#adddddddddd#d....",
"....#adddddddddd#d....",
"....#addaaaaaddc#d....",
"....#addaaddaddc#d....",
"....#addaaddaddc#d....",
"....#############d....",
".....ddddddddddddd....",
"......................",
"......................",
"......................"};
EOT

    my $pencilicon = <<EOT;
/* XPM */
/* Taken from KDE */
/* Drawn  by Mark Donohoe for the K Desktop Environment */
/* See http://www.kde.org */
static char*pencil[]={
"12 16 5 1",
"# c #000000",
"c c #ffffff",
"b c #a0a0a4",
"a c #dcdcdc",
". c None",
".....####...",
".....#aaa#..",
"....#aaaa#b.",
"....##aa#bb.",
"...#cc###b..",
"...#caa#bb..",
"..#ccaa#b...",
"..#caa#bb...",
".#ccaa#b....",
".#caa#bb....",
".##aa#b.....",
".####bb.....",
".###bb......",
".##bb.......",
".#bb........",
"..b........."};
EOT

    my $hammericon = <<EOT;
/* XPM */
/* Taken from KDE */
static char * mini-hammer_xpm[] = {
"16 14 4 1",
" 	c None",
".	c black",
"o	c white",
"O	c gray50",
"   .. .....     ",
"  .oo.ooooo.    ",
"  .ooooooooo.   ",
"  .oO.oooo..o.  ",
"   .. ....O ..  ",
"       ...   .  ",
"       ...      ",
"       ...      ",
"       ...      ",
"       ...      ",
"       ...      ",
"       ...      ",
"       ...      ",
"                "};
EOT

    my $magnifyicon = <<EOT;
/* XPM */
/* Taken from KDE */
/* Drawn  by Mark Donohoe for the K Desktop Environment */
/* See http://www.kde.org */
static char*find[]={
"22 22 6 1",
"# c #000000",
"c c #ffffff",
"b c #dcdcdc",
"a c #a0a0a4",
"d c #dcdcdc",
". c None",
"......................",
"......................",
"......................",
".......####...........",
".....a#bccd#a.........",
".....#ccaacc#a........",
"....#dcaccccd#........",
"....#cccccccc#........",
"....#cccccccc#........",
"....#dccccccd#........",
"....a#cccccc#a........",
".....a#dccd###........",
"......a####a###.......",
".......aaaaaa###......",
"............aa###.....",
".............aa###....",
"..............aa###...",
"...............aa#a...",
"................aa....",
"......................",
"......................",
"......................"};
EOT

    my $checkicon = <<EOT;
/* XPM */
/* Taken from KDE */
static char * spellcheck_xpm[] = {
"22 22 3 1",
".	c #000000",
"X	c #FF0000",
" 	c None",
"                      ",
"                      ",
"                      ",
"                      ",
"                      ",
"                      ",
"   ...  ....   ....   ",
"  .. .. ..... ..XX.   ",
"  .   . .  .. .XX     ",
"  ..... ....  .XX     ",
"  ..... ....  XX      ",
"  .   . .  .. XX...   ",
"  .   . .....XX....   ",
"  .   . .X.. XX       ",
"         XX XX        ",
"         XX XX        ",
"          XXX         ",
"           X          ",
"                      ",
"                      ",
"                      ",
"                      "};
EOT

    my $reloadicon = <<EOT;
/* XPM */
/* Taken from KDE */
/* Drawn  by Mark Donohoe for the K Desktop Environment */
/* See http://www.kde.org */
static char*reload[]={
"16 13 3 1",
"# c #808080",
"a c #000000",
". c None",
".....##aaa#.....",
"....#aaaaaaa....",
"...#aa#....#a...",
"...aa#..........",
"..aaa.......a...",
"aaaaaaa....aaa..",
".aaaaa....aaaaa.",
"..aaa....aaaaaaa",
"...a.......aaa..",
"..........#aa...",
"...a#....#aa#...",
"....aaaaaaa#....",
".....#aaa##....."};
EOT

    my $inserticon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
/* Based on file.xpm of Tk and mini-raise.xpm of KDE */
static char * insert_xpm[] = {
"19 12 4 1",
" 	c None",
".	c #000000",
"+	c #FFFFFF",
"@	c #F81440",
"        ........   ",
"        .++++++.   ",
"        .+@++++... ",
"        .+@@+++++. ",
" @@ @@@@@@@@@++++. ",
"   @ @ @@@@@@@+++. ",
" @  @ @@@@@@@@@++. ",
"   @ @@ @@@@@@+++. ",
" @ @ @@@@@@@@++++. ",
"        .+@@+++++. ",
"        .+@++++++. ",
"        .......... "};
EOT

    my $extracticon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
/* Based on file.xpm of Tk and mini-raise.xpm of KDE */
static char * ex_xpm[] = {
"19 12 4 1",
" 	c None",
".	c #000000",
"+	c #FFFFFF",
"@	c #F81440",
" ........          ",
" .++++++.          ",
" .++++++...  @     ",
" .++++++++.  @@    ",
" .++@@+@@@@@@@@@   ",
" .++++@+@+@@@@@@@  ",
" .++@++@+@@@@@@@@@ ",
" .++++@+@@.@@@@@@  ",
" .++@+@+@@@@@@@@   ",
" .++++++++.  @@    ",
" .++++++++.  @     ",
" ..........        "};
EOT

    my $trashicon = <<EOT;
/* XPM */
/* Taken from KDE */
/* Drawn by Anonymous for the K Desktop Environment */
/* See http://www.kde.org */
static char*trash2[]={
"16 16 11 1",
"h c #000000",
". c None",
"d c #004040",
"f c #c0c0c0",
"i c #585858",
"b c #a0a0a4",
"a c #dcdcdc",
"# c #000000",
"g c #808080",
"c c #404000",
"e c #ffffff",
".....##a#.......",
"..###abcd####...",
".#eeefffghffb#..",
".#abfeeeffiii#..",
"..dgaabbbbgg#...",
"..#bbbggiigi#...",
"..#bebagggii#...",
"..#bebagggii#...",
"..#bebagggii#...",
"..#bebagggii#...",
"..#bebagggii#...",
"..#bebagggii#...",
"..#bebagggii#...",
"..#aebagggii#...",
"...##aabbg##....",
".....#####......"};
EOT

    my $infoicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char * info_xpm[] = {
"12 12 7 1",
" 	c None",
".	c #FFFFFF",
"+	c #8B96FF",
"A	c #3447FF",
"#	c #061EFF",
"S	c #DDE0FF",
"%	c #0019FF",
"   +A##A+   ",
" SA%%%%%%AS ",
" A%%%..%%%A ",
"+%%%%..%%%%+",
"A%%%%%%%%%%A",
"#%%%...%%%%#",
"#%%%%..%%%%#",
"A%%%%..%%%%A",
"+%%%%..%%%%+",
" A%%....%%A ",
" SA%%%%%%AS ",
"   +A##A+   "};
EOT

    my $exclamationicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char * exclamation_xpm[] = {
"12 12 3 1",
" 	c None",
".	c #FF0000",
"+	c #000000",
"            ",
"      ..    ",
"      ..+   ",
"     ...+   ",
"     ..++   ",
"     ..+    ",
"     .++    ",
"    ..+     ",
"      +     ",
"   ..       ",
"   ..+      ",
"    ++      "};
EOT

    my $questionicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char * question_xpm[] = {
"12 12 3 1",
" 	c None",
".	c #FF0000",
"+	c #000000",
"            ",
"     ....   ",
"    .  ...  ",
"    ..  ..+ ",
"     + ..++ ",
"      ..++  ",
"     ..++   ",
"     .++    ",
"            ",
"    ..      ",
"    ..+     ",
"     ++     "};
EOT

    my $crossicon = <<EOT;
/* XPM */
/* Taken from KDE */
static char * mini-cross_xpm[] = {
"12 12 4 1",
" 	c None",
".	c red",
"X	c gray50",
"o	c black",
"         .  ",
" ..X    ... ",
"  ..X  .....",
"   .......oo",
"    .....o  ",
"    ....o   ",
"   ......   ",
"   ..o ...  ",
"  ..o   ... ",
"  .o     ..X",
" .o       .o",
" o         o"};
EOT

    my $plusicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char * plus_xpm[] = {
"16 13 3 1",
" 	c None",
".	c #FF0000",
"+	c #000000",
"      ...       ",
"      ...+      ",
"      ...+      ",
"      ...+      ",
"  ...........   ",
"  ...........+  ",
"  ...........+  ",
"   +++...+++++  ",
"      ...+      ",
"      ...+      ",
"      ...+      ",
"        ++      ",
"                "};
EOT

    my $equalicon = <<EOT;
/* XPM */
/* Copyright (C) 2000 Avatar <avatar\@deva.net> */
static char * equal_xpm[] = {
"16 13 3 1",
" 	c None",
".	c #0000FF",
"+	c #000000",
"                ",
"                ",
"   .........    ",
"   .........+   ",
"   .........+   ",
"   ++++++++++   ",
"                ",
"   .........    ",
"   .........+   ",
"   .........+   ",
"   ++++++++++   ",
"                ",
"                "};
EOT

    my $nullicon = <<EOT;
/* XPM */
/* I'm not going to copyright this one. :) */
static char * null_xpm[] = {
"16 13 3 1",
" 	c None",
".	c #FF0000",
"+	c #000000",
"                ",
"                ",
"                ",
"                ",
"                ",
"                ",
"                ",
"                ",
"                ",
"                ",
"                ",
"                ",
"                "};
EOT

    $mw->Pixmap('avatar', -data => $avataricon);

    $mw->Pixmap('ufile', -data => $ufileicon);
    $mw->Pixmap('graphicsfile', -data => $graphicsfileicon);
    $mw->Pixmap('musicfile', -data => $musicfileicon);
    $mw->Pixmap('libraryfile', -data => $libraryfileicon);

    $mw->Pixmap('gear', -data => $gearicon);
    $mw->Pixmap('questiongear', -data => $questiongearicon);

    $mw->Pixmap('openfile', -data => $openfileicon);
    $mw->Pixmap('savefile', -data => $savefileicon);

    $mw->Pixmap('pencil', -data => $pencilicon);
    $mw->Pixmap('hammer', -data => $hammericon);
    $mw->Pixmap('magnify', -data => $magnifyicon);
    $mw->Pixmap('check', -data => $checkicon);
    $mw->Pixmap('reload', -data => $reloadicon);
    $mw->Pixmap('insert', -data => $inserticon);
    $mw->Pixmap('extract', -data => $extracticon);
    $mw->Pixmap('trash', -data => $trashicon);

    $mw->Pixmap('info', -data => $infoicon);
    $mw->Pixmap('exclamation', -data => $exclamationicon);
    $mw->Pixmap('question', -data => $questionicon);
    $mw->Pixmap('cross', -data => $crossicon);
    $mw->Pixmap('plus', -data => $plusicon);
    $mw->Pixmap('equal', -data => $equalicon);
    $mw->Pixmap('null', -data => $nullicon);
}

# Return appropriate depending filename and optionally type.
# Type takes precedence over filename.
sub getIcon {
    my ($filename, $type) = @_;

    if ($type) {
	SWITCH: for ($type) {
	    /doc/ && do {
		    return("info"); };
	}
    }

    SWITCH: for (lc($filename)) {
	/\.(u|unr)$/ && do {
	    return('ufile'); };
	/\.(txt|ini|int|log|html?)$/ && do {
	    return($mw->Getimage('textfile')); };
	/\.(bmp|gif|jpe?g|png|xpm|utx)$/ && do {
	    return('graphicsfile'); };
	/\.(uax|umx)$/ && do {
	    return('musicfile'); };
	/\.(so|dll)$/ && do {
	    return('libraryfile'); };
	{
	    return($mw->Getimage('file')); };
    }
}
