#!/usr/bin/perl -w

#
# gtkgrepmail v.0.9 - (c) 2003 Terminus Network Services
#
# $Id: gtkgrepmail,v 1.17 2003/08/27 01:55:50 terminus Exp $
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.

# Define modules
use Gnome;
use Date::Manip;
use File::Basename;
use File::Find;
use URI::Escape;
use POSIX ":sys_wait_h";
use Net::Ping;

# Define variables
$defaultMailpath="$ENV{HOME}/Mail";
$defaultMailbox="search-results";
$defaultGrepmail="/usr/bin/grepmail";
$defaultIMAP="/usr/bin/X11/xterm -e /usr/bin/elm -f";
$defaultSSH="/usr/bin/ssh";
$defaultHost="localhost";
$defaultUser="$ENV{USER}";
$defaultSelectedFolders="";
$package = 'gtkgrepmail';
$VERSION = '0.90';
$debug = 0;
$configpath = $ENV{HOME} . "/.gtkgrepmailrc";
    
# Main program routine
Gnome->init("$package", "$VERSION");
LoadOptions();
dlgMain_new();
refreshfiles();
# Now let Gtk handle signals
Gtk->main;

sub msgError_new {
    $errortext = shift;
    #
    # Construct a GnomeMessageBox 'msgError'
    $msgError_form = new Gnome::MessageBox("$errortext", 'error');
    $msgError_form->close_hides(0 );
    $msgError_form->set_close(1 );
    $msgError_form->position('none' );
    $msgError_form->set_policy(0, 0, 0 );
    $msgError_form->set_modal(1 );
    $msgError_form->realize;
    $accelgroup = new Gtk::AccelGroup;
    $accelgroup->attach($msgError_form);
    $msgError = $msgError_form;
	#
	# Construct a GtkVBox 'dialog_vbox1'
	$dialog_vbox1 = $msgError->vbox;
	$dialog_vbox1->show;
	    #
	    # Construct a GtkHButtonBox 'dialog_action_area1'
	    $dialog_action_area1 = new Gtk::HButtonBox;
	    $dialog_vbox1->add($dialog_action_area1);
	    $dialog_action_area1->show;
	    $dialog_action_area1->set_layout('end' );
	    $dialog_action_area1->set_spacing(8 );
	    $dialog_action_area1->set_child_size(85, 27 );
	    $dialog_action_area1->set_child_ipadding(7, 0 );
		#
		# Construct a GtkButton 'btnOK'
		$btnOK = Gnome::Stock->button('Button_Ok' );
		$dialog_action_area1->add($btnOK);
		$btnOK->show;
		$btnOK->can_default(1 );
		$btnOK->can_focus(1 );
	    $dialog_vbox1->set_child_packing($dialog_action_area1, 0, 1, 0, 'end' );
    $btnOK->signal_connect( 'clicked', "on_btnOK_clicked" );

    $msgError_form->show;
}

sub on_btnOK_clicked {
    $msgError_form->destroy()
};

sub dlgOptions_new {
    #
    # Construct a GnomeDialog 'dlgOptions'
    $dlgOptions_form = new Gnome::Dialog('gtkgrepmail');
    $dlgOptions_form->close_hides(0 );
    $dlgOptions_form->set_close(0 );
    $dlgOptions_form->position('center' );
    $dlgOptions_form->set_policy(0, 0, 0 );
    $dlgOptions_form->set_modal(1 );
    $dlgOptions_form->realize;
    $accelgroup = new Gtk::AccelGroup;
    $accelgroup->attach($dlgOptions_form );
    $dlgOptions = $dlgOptions_form;
    $dlgOptions->border_width(10 );
	#
	# Construct a GtkVBox 'dvbMain'
	$dvbMain = $dlgOptions->vbox;
	$dvbMain->show;
	    #
	    # Construct a GtkVBox 'vboFields'
	    $vboFields = new Gtk::VBox(0, 0 );
	    $dvbMain->add($vboFields);
	    $vboFields->show;
		#
		# Construct a GtkLabel 'lblMailpath'
		$lblMailpath = new Gtk::Label('Path (local or remote) to mail folders');
		$lblMailpath->set_justify('left' );
		$lblMailpath->set_line_wrap(0 );
		$vboFields->add($lblMailpath);
		$lblMailpath->show;
		$lblMailpath->set_alignment(0, 0 );
		$vboFields->set_child_packing($lblMailpath, 0, 0, 0, 'start' );
		#
		# Construct a GnomeFileEntry 'fileMailpath'
		$fileMailpath = new Gnome::FileEntry('', '');
		$fileMailpath->gnome_entry->set_max_saved(10 );
		$fileMailpath->set_directory(0 );
		$fileMailpath->set_modal(0);
		$vboFields->add($fileMailpath);
		$fileMailpath->show;
		    #
		    # Construct a GtkEntry 'cmbMailpath'
		    $cmbMailpath = $fileMailpath->gtk_entry;
		    $cmbMailpath->show;
		    $cmbMailpath->can_focus(1 );
		    $cmbMailpath->set_max_length(0 );
		    $cmbMailpath->set_visibility(1 );
		    $cmbMailpath->set_editable(1 );
		$vboFields->set_child_packing($fileMailpath, 0, 0, 0, 'start' );
		#
		# Construct a GtkLabel 'lblIMAPRoot'
		$lblIMAPRoot = new Gtk::Label('IMAP root');
		$lblIMAPRoot->set_justify('left' );
		$lblIMAPRoot->set_line_wrap(0 );
		$vboFields->add($lblIMAPRoot);
		$vboFields->set_child_packing($lblIMAPRoot, 0, 0, 0, 'start' );
		$lblIMAPRoot->show;
		$lblIMAPRoot->set_alignment(0, 0 );
		#
		# Construct a GtkEntry 'entIMAPRoot'
		$entIMAPRoot = new Gtk::Entry;
		$vboFields->add($entIMAPRoot);
		$vboFields->set_child_packing($entIMAPRoot, 0, 0, 0, 'start' );
		$entIMAPRoot->show;
		$entIMAPRoot->can_focus(1 );
		$entIMAPRoot->set_max_length(0 );
		$entIMAPRoot->set_editable(1 );
		#
		# Construct a GtkLabel 'lblMailbox'
		$lblMailbox = new Gtk::Label('Relative path to temporary mailbox');
		$lblMailbox->set_justify('left' );
		$lblMailbox->set_line_wrap(0 );
		$vboFields->add($lblMailbox);
		$lblMailbox->show;
		$lblMailbox->set_alignment(0, 0 );
		$vboFields->set_child_packing($lblMailbox, 0, 0, 0, 'start' );
		#
		# Construct a GnomeFileEntry 'fileMailbox'
		$fileMailbox = new Gnome::FileEntry('', '');
		$fileMailbox->gnome_entry->set_max_saved(10 );
		$fileMailbox->set_directory(0 );
		$fileMailbox->set_modal(0);
		$vboFields->add($fileMailbox);
		$fileMailbox->show;
		    #
		    # Construct a GtkEntry 'cmbMailbox'
		    $cmbMailbox = $fileMailbox->gtk_entry;
		    $cmbMailbox->show;
		    $cmbMailbox->can_focus(1 );
		    $cmbMailbox->set_max_length(0 );
		    $cmbMailbox->set_visibility(1 );
		    $cmbMailbox->set_editable(1 );
		$vboFields->set_child_packing($fileMailbox, 0, 0, 0, 'start' );
		#
		# Construct a GtkLabel 'lblGrepmail'
		$lblGrepmail = new Gtk::Label('Path (local or remote) to grepmail');
		$lblGrepmail->set_justify('left' );
		$lblGrepmail->set_line_wrap(0 );
		$vboFields->add($lblGrepmail);
		$lblGrepmail->show;
		$lblGrepmail->set_alignment(0, 0 );
		$vboFields->set_child_packing($lblGrepmail, 0, 0, 0, 'start' );
		#
		# Construct a GnomeFileEntry 'fileGrepmail'
		$fileGrepmail = new Gnome::FileEntry('', '');
		$fileGrepmail->gnome_entry->set_max_saved(10 );
		$fileGrepmail->set_directory(0 );
		$fileGrepmail->set_modal(0);
		$vboFields->add($fileGrepmail);
		$fileGrepmail->show;
		    #
		    # Construct a GtkEntry 'cmbGrepmail'
		    $cmbGrepmail = $fileGrepmail->gtk_entry;
		    $cmbGrepmail->show;
		    $cmbGrepmail->can_focus(1 );
		    $cmbGrepmail->set_max_length(0 );
		    $cmbGrepmail->set_visibility(1 );
		    $cmbGrepmail->set_editable(1 );
		#
		# Construct a GtkLabel 'lblIMAP'
		$lblIMAP = new Gtk::Label('Path to IMAP or mailbox browser');
		$lblIMAP->set_justify('left' );
		$lblIMAP->set_line_wrap(0 );
		$vboFields->add($lblIMAP);
		$lblIMAP->show;
		$lblIMAP->set_alignment(0, 0 );
		$vboFields->set_child_packing($lblIMAP, 0, 0, 0, 'start' );
		#
		# Construct a GnomeFileEntry 'fileIMAP'
		$fileIMAP = new Gnome::FileEntry('', '');
		$fileIMAP->gnome_entry->set_max_saved(10 );
		$fileIMAP->set_directory(0 );
		$fileIMAP->set_modal(0);
		$vboFields->add($fileIMAP);
		$fileIMAP->show;
		    #
		    # Construct a GtkEntry 'cmbIMAP'
		    $cmbIMAP = $fileIMAP->gtk_entry;
		    $cmbIMAP->show;
		    $cmbIMAP->can_focus(1 );
		    $cmbIMAP->set_max_length(0 );
		    $cmbIMAP->set_visibility(1 );
		    $cmbIMAP->set_editable(1 );
		$vboFields->set_child_packing($fileIMAP, 0, 0, 0, 'start' );
		#
		# Construct a GtkLabel 'lblSSH'
		$lblSSH = new Gtk::Label('Path to ssh or rsh');
		$lblSSH->set_justify('left' );
		$lblSSH->set_line_wrap(0 );
		$vboFields->add($lblSSH);
		$lblSSH->show;
		$lblSSH->set_alignment(0, 0 );
		$vboFields->set_child_packing($lblSSH, 0, 0, 0, 'start' );
		#
		# Construct a GnomeFileEntry 'fileSSH'
		$fileSSH = new Gnome::FileEntry('', '');
		$fileSSH->gnome_entry->set_max_saved(10 );
		$fileSSH->set_directory(0 );
		$fileSSH->set_modal(0);
		$vboFields->add($fileSSH);
		$fileSSH->show;
		    #
		    # Construct a GtkEntry 'cmbSSH'
		    $cmbSSH = $fileSSH->gtk_entry;
		    $cmbSSH->show;
		    $cmbSSH->can_focus(1 );
		    $cmbSSH->set_max_length(0 );
		    $cmbSSH->set_visibility(1 );
		    $cmbSSH->set_editable(1 );
		$vboFields->set_child_packing($fileSSH, 0, 0, 0, 'start' );
		#
		# Construct a GtkTable 'tblLogin'
		# This table is actually redundant, FIXME
		#
		$tblLogin = new Gtk::Table(4, 2, 0 );
		$tblLogin->set_row_spacings(0 );
		$tblLogin->set_col_spacings(0 );
		$vboFields->add($tblLogin);
		$tblLogin->show;
		    #
		    # Construct a GtkLabel 'lblHost'
		    $lblHost = new Gtk::Label('Host');
		    $lblHost->set_justify('left' );
		    $lblHost->set_line_wrap(0 );
		    $tblLogin->attach($lblHost, 0, 1, 0, 1, ['fill'], [], 0, 0 );
		    $lblHost->show;
		    $lblHost->set_alignment(0, 0 );
		    #
		    # Construct a GtkEntry 'entHost'
		    $entHost = new Gtk::Entry;
		    $tblLogin->attach($entHost, 0, 1, 1, 2, ['expand', 'fill'], [], 0, 0 );
		    $entHost->show;
		    $entHost->can_focus(1 );
		    $entHost->set_max_length(50 );
		    $entHost->set_visibility(1 );
		    $entHost->set_editable(1 );
		    #
		    # Construct a GtkLabel 'lblUser'
		    $lblUser = new Gtk::Label('User');
		    $lblUser->set_justify('left' );
		    $lblUser->set_line_wrap(0 );
		    $tblLogin->attach($lblUser, 0, 1, 2, 3, ['fill'], [], 0, 0 );
		    $lblUser->show;
		    $lblUser->set_alignment(0, 0 );
		    #
		    # Construct a GtkEntry 'entUser'
		    $entUser = new Gtk::Entry;
		    $tblLogin->attach($entUser, 0, 1, 3, 4, ['expand', 'fill'], [], 0, 0 );
		    $entUser->show;
		    $entUser->can_focus(1 );
		    $entUser->set_text('');
		    $entUser->set_max_length(30 );
		    $entUser->set_visibility(1 );
		    $entUser->set_editable(1 );
	    #
	    # Construct a GtkHButtonBox 'dacButton'
	    $dacButton = new Gtk::HButtonBox;
	    $dvbMain->add($dacButton);
	    $dacButton->show;
	    $dacButton->set_layout('end' );
	    $dacButton->set_spacing(8 );
	    $dacButton->set_child_size(85, 27 );
	    $dacButton->set_child_ipadding(7, 0 );
		#
		# Construct a GtkButton 'pbSave'
		$pbSave = new Gtk::Button('_Save');
		$dacButton->add($pbSave);
		$pbSave->show;
		$pbSave->can_default(1 );
		$pbSave->can_focus(1 );
		$pbSave_key = $pbSave->child->parse_uline('_Save');
		$pbSave->add_accelerator('clicked', $accelgroup, $pbSave_key, 'mod1_mask', ['visible', 'locked'] );
		#
		# Construct a GtkButton 'pbCancel'
		$pbCancel = new Gtk::Button('_Cancel');
		$dacButton->add($pbCancel);
		$pbCancel->show;
		$pbCancel->can_default(1 );
		$pbCancel->can_focus(1 );
		$pbCancel_key = $pbCancel->child->parse_uline('_Cancel');
		$pbCancel->add_accelerator('clicked', $accelgroup, $pbCancel_key, 'mod1_mask', ['visible', 'locked'] );
	    $dvbMain->set_child_packing($dacButton, 0, 1, 0, 'end' );
    #
    # Connect all signals now that dlgOptions_form are constructed
    $dlgOptions->signal_connect( 'destroy', "on_dlgOptions_destroy" );
    $dlgOptions->signal_connect( 'show', "on_dlgOptions_show" );
    $pbSave->signal_connect( 'clicked', "on_pbSave_clicked" );
    $pbCancel->signal_connect( 'clicked', "on_pbCancel_clicked" );
    $cmbMailbox->signal_connect( 'activate', "on_cmbMailbox_activate" );

    $dlgOptions_form->show;
}

sub on_dlgOptions_destroy {
    on_pbCancel_clicked();
}

sub on_cmbMailbox_activate {
    $usrMailbox = $cmbMailbox->get_text();
    $cmbMailbox->set_text(basename($usrMailbox));
}

sub on_dlgOptions_show {
    LoadOptions();
    $cmbMailpath->set_text($usrMailpath);
    $entIMAPRoot->set_text($usrIMAPRoot);
    $cmbMailbox->set_text($usrMailbox);
    $cmbGrepmail->set_text($usrGrepmail);
    $cmbIMAP->set_text($usrIMAP);
    $cmbSSH->set_text($usrSSH);
    $entHost->set_text($usrHost);
    $entUser->set_text($usrUser);
}

sub LoadOptions {
    load_config();
    $usrMailpath = $cfg{Mailpath} || $defaultMailpath;
    $usrIMAPRoot = $cfg{IMAPRoot} || $usrMailpath;
    $usrMailbox = $cfg{Mailbox} || $defaultMailbox;
    $usrGrepmail = $cfg{Grepmail} || $defaultGrepmail;
    $usrIMAP = $cfg{IMAP} || $defaultIMAP;
    $usrSSH = $cfg{SSH} || $defaultSSH;
    $usrHost = $cfg{Host} || $defaultHost;
    $usrUser = $cfg{User} || $defaultUser;
    $usrSelectedFolders = $cfg{SelectedFolders} || $defaultSelectedFolders;
    @sshcommand = split(/ /, $usrSSH);
    mailpath_create();
}

sub mailpath_create {
    # Remove trailing slash, if any
    $len = length($usrMailpath);
    $pos = rindex($usrMailpath, '/');
    if ($pos == $len - 1) {
      $usrMailpath = substr($usrMailpath, 0, $len - 1)
    };
    # Escape shell characters
    $valMailpath = $usrMailpath;
    $valMailpath =~ s/([\&;\`'! \\\|"*?<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;
    $valMailpath =~ s/^\~/$ENV{HOME}/;
    $valMailbox = $usrMailbox;
    $valMailbox =~ s/([\&;\`'! \\\|\/"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;
    $valGrepmail = $usrGrepmail;
    $valGrepmail =~ s/([\&;\`'! \\\|"*?<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;
}

sub on_pbCancel_clicked {
    $dlgOptions_form->hide(); 
}

sub on_pbSave_clicked {
    $usrMailpath = $cmbMailpath->get_text();
    $usrIMAPRoot = $entIMAPRoot->get_text();
    $usrMailbox = $cmbMailbox->get_text();
    $usrGrepmail = $cmbGrepmail->get_text();
    $usrIMAP = $cmbIMAP->get_text();
    $usrSSH = $cmbSSH->get_text();
    $usrHost = $entHost->get_text();
    $usrUser = $entUser->get_text();
    # Do some rudimentary sanity checking
    ($IMAParg1,undef) = split(' ',$usrIMAP);
    if (! -x $IMAParg1 ) {
      push @errorlist, "* Cannot execute IMAP browser (use /bin/true if you are sure you do not want one)\n"
    };
    unless ( $usrHost ) {
      $usrHost = "localhost"
    };
    if ( ( $usrHost ne "localhost" ) &! -x $usrSSH ) {
      push @errorlist, "* Cannot execute ssh or rsh\n"
    };
    if ( ( $usrHost ne "localhost" ) &! $usrUser ) {
      push @errorlist, "* You must specify a username\n"
    };
    if ( ( $usrHost eq "localhost" ) &! -d $usrMailpath ) {
      push @errorlist, "* Mailbox path does not exist\n"
    };
    if ( ( $usrHost eq "localhost" ) &! -x $usrGrepmail ) {
      push @errorlist, "* Cannot execute grepmail\n"
    };
    unless ( $usrMailbox ) {
      push @errorlist, "* You must specify a mailbox for search results\n"
    };
    unless ( $usrMailpath ) {
      push @errorlist, "* You must specify a fully-qualified mailbox path\n"
    };
    if ( $usrHost =~ /([\&;\`'! \\\|\/"*?~<>^\(\)\[\]\{\}\$\n\r])/ ) {
      push @errorlist, "* Illegal characters in hostname\n"
    };
    if ( $usrUser =~ /([\&;\`'! \\\|\/"*?~<>^\(\)\[\]\{\}\$\n\r])/ ) {
      push @errorlist, "* Illegal characters in username\n"
    };
    if ( @errorlist ) {
      chomp $errorlist[$#errorlist];
      msgError_new("Please correct the following errors:\n@errorlist");
      undef (@errorlist);
    } else {
      open (CONFIG, ">$configpath") || msgError_new("Cannot write to configuration file $configpath");
      save_config();
      close CONFIG;
      mailpath_create;
      refreshfiles();
      $dlgOptions_form->hide(); 
    }
}

sub dlgMain_new {
    #
    # Construct a GnomeApp 'dlgMain'
    $dlgMain_form = new Gnome::App('gtkgrepmail', 'gtkgrepmail');
    $dlgMain_form->enable_layout_config(1 );
    $dlgMain_form->position('none' );
    $dlgMain_form->set_policy(0, 1, 0 );
    $dlgMain_form->set_modal(0 );
    $dlgMain_form->realize;
    $accelgroup = new Gtk::AccelGroup;
    $accelgroup->attach($dlgMain_form );
    $dlgMain = $dlgMain_form;
	#
	# Construct a GnomeDock 'dock1'
	$dock1 = $dlgMain->get_dock;
	$dock1->allow_floating_items(1 );
	$dock1->show;
	    #
	    # Construct a GnomeDockItem 'dockitem1'
	    $dockitem1 = new Gnome::DockItem('dockitem1', ['exclusive', 'never_vertical'] );
	    $dockitem1->set_shadow_type('out' );
	    $dock1->add_item($dockitem1, 'top', 0, 0, 0, 0 );
	    $dockitem1->show;
	    $dockitem1->border_width(2 );
		#
		# Construct a GtkMenuBar 'menubar'
		$menubar = new Gtk::MenuBar;
		$menubar->set_shadow_type('none' );
		$dockitem1->add($menubar);
		$menubar->show;
		    #
		    # Construct a GtkMenuItem 'mnuFile'
		    $mnuFile = new Gtk::MenuItem('_File');
		    $menubar->append($mnuFile);
		    $mnuFile->show;
		    $mnuFile_key = $mnuFile->child->parse_uline('_File');
		    $mnuFile->add_accelerator('activate_item', $accelgroup, $mnuFile_key, 'mod1_mask', ['visible', 'locked'] );
			#
			# Construct a GtkMenu 'mnuFile_menu'
			$mnuFile_menu = new Gtk::Menu;
			$mnuFile->set_submenu($mnuFile_menu);
			    #
			    # Construct a GtkPixmapMenuItem 'mnuExit'
			    $mnuExit = Gnome::Stock->menu_item('Menu_Quit', 'Exit');
			    $accelgroup->add(81, ['control_mask'], ['visible', 'locked'], $mnuExit, 'activate');
			    $mnuFile_menu->append($mnuExit);
			    $mnuExit->show;
		    #
		    # Construct a GtkMenuItem 'mnuSettings'
		    $mnuSettings = new Gtk::MenuItem('_Settings');
		    $menubar->append($mnuSettings);
		    $mnuSettings->show;
		    $mnuSettings_key = $mnuSettings->child->parse_uline('_Settings');
		    $mnuSettings->add_accelerator('activate_item', $accelgroup, $mnuSettings_key, 'mod1_mask', ['visible', 'locked'] );
			#
			# Construct a GtkMenu 'mnuSettings_menu'
			$mnuSettings_menu = new Gtk::Menu;
			$mnuSettings->set_submenu($mnuSettings_menu);
			    #
			    # Construct a GtkPixmapMenuItem 'mnuPreferences'
			    $mnuPreferences = Gnome::Stock->menu_item('Menu_Preferences', 'Preferences');
			    $mnuSettings_menu->append($mnuPreferences);
			    $mnuPreferences->show;
		    #
		    # Construct a GtkMenuItem 'mnuHelp'
		    $mnuHelp = new Gtk::MenuItem('_Help');
		    $menubar->append($mnuHelp);
		    $mnuHelp->show;
		    $mnuHelp_key = $mnuHelp->child->parse_uline('_Help');
		    $mnuHelp->add_accelerator('activate_item', $accelgroup, $mnuHelp_key, 'mod1_mask', ['visible', 'locked'] );
			#
			# Construct a GtkMenu 'mnuHelp_menu'
			$mnuHelp_menu = new Gtk::Menu;
			$mnuHelp->set_submenu($mnuHelp_menu);
			    #
			    # Construct a GtkPixmapMenuItem 'mnuManual'
			    $mnuManual = new Gtk::MenuItem('Manual');
			    $mnuHelp_menu->append($mnuManual);
			    $mnuManual->show;
			    #
			    # Let's have a separator here
			    $mnuSeparator = new Gtk::MenuItem();
			    $mnuHelp_menu->append($mnuSeparator);
			    $mnuSeparator->show;
			    #
			    # Construct a GtkPixmapMenuItem 'mnuAbout'
			    $mnuAbout = Gnome::Stock->menu_item('Menu_About', 'About');
			    $mnuHelp_menu->append($mnuAbout);
			    $mnuAbout->show;
	    #
	    # Construct a GtkVBox 'vbox1'
	    $vbox1 = new Gtk::VBox(0, 0 );
	    $dlgMain->set_contents($vbox1);
	    $vbox1->show;
		#
		# Construct a GtkNotebook 'notebook'
		$notebook = new Gtk::Notebook;
		$vbox1->add($notebook);
		$notebook->show;
		$notebook->can_focus(1 );
		$notebook->set_tab_pos('top' );
		$notebook->set_show_tabs(1 );
		$notebook->set_show_border(1 );
		$notebook->set_scrollable(0 );
		$notebook->set_tab_hborder(2 );
		$notebook->set_tab_vborder(2 );
		    #
		    # Construct a GtkTable 'tableSearch'
		    $tableSearch = new Gtk::Table(6, 2, 0 );
		    $tableSearch->set_row_spacings(0 );
		    $tableSearch->set_col_spacings(0 );
		    $tableSearch->show;
			#
			# Construct a GtkLabel 'lblSearchText'
			$lblSearchText = new Gtk::Label('');
			$lblSearchText->parse_uline('_Expression');
			$lblSearchText->set_justify('left' );
			$lblSearchText->set_line_wrap(0 );
			$tableSearch->attach($lblSearchText, 0, 1, 0, 1, [], [], 0, 0 );
			$lblSearchText->show;
			#
			# Construct a GtkEntry 'entSearch'
			$entSearch = new Gtk::Entry;
			$tableSearch->attach($entSearch, 1, 2, 0, 1, ['expand', 'fill'], [], 0, 0 );
			$entSearch->show;
			$entSearch->can_focus(1 );
			$entSearch->set_text('');
			$entSearch->set_max_length(0 );
			$entSearch->set_visibility(1 );
			$entSearch->set_editable(1 );
			#
			# Construct a GtkLabel 'lblContain'
			$lblContain = new Gtk::Label('contained in');
			$lblContain->set_justify('left' );
			$lblContain->set_line_wrap(0 );
			$tableSearch->attach($lblContain, 0, 1, 1, 2, [], [], 0, 0 );
			$lblContain->show;
			#
			# Construct a GtkHBox 'hbox1'
			$hbox1 = new Gtk::HBox(0, 0 );
			$tableSearch->attach($hbox1, 1, 2, 1, 2, ['fill'], ['fill'], 0, 0 );
			$hbox1->show;
			    #
			    # Construct a GtkCheckButton 'chkBody'
			    $chkBody = new Gtk::CheckButton('Body');
			    $chkBody->set_active(1 );
			    $hbox1->add($chkBody);
			    $chkBody->show;
			    $chkBody->can_focus(1 );
			    $hbox1->set_child_packing($chkBody, 0, 0, 0, 'start' );
			    #
			    # Construct a GtkCheckButton 'chkHeaders'
			    $chkHeaders = new Gtk::CheckButton('Headers');
			    $chkHeaders->set_active(1 );
			    $hbox1->add($chkHeaders);
			    $chkHeaders->show;
			    $chkHeaders->can_focus(1 );
			    $hbox1->set_child_packing($chkHeaders, 0, 0, 0, 'start' );
			#
			# Construct a GtkLabel 'lblContain2'
			$lblContain2 = new Gtk::Label('contained in');
			$lblContain2->set_justify('center' );
			$lblContain2->set_line_wrap(0 );
			$tableSearch->attach($lblContain2, 0, 1, 3, 4, [], [], 0, 0 );
			$lblContain2->show;
			$lblContain2->set_alignment(0.5, 0.5 );
			#
			# Construct a GtkHBox 'hbox2'
			$hbox2 = new Gtk::HBox(0, 0 );
			$tableSearch->attach($hbox2, 1, 2, 3, 4, ['fill'], ['fill'], 0, 0 );
			$hbox2->show;
			    #
			    # Construct a GtkCheckButton 'chkBody2'
			    $chkBody2 = new Gtk::CheckButton('Body');
			    $chkBody2->set_active(1 );
			    $hbox2->add($chkBody2);
			    $chkBody2->show;
			    $chkBody2->can_focus(1 );
			    $hbox2->set_child_packing($chkBody2, 0, 0, 0, 'start' );
			    #
			    # Construct a GtkCheckButton 'chkHeaders2'
			    $chkHeaders2 = new Gtk::CheckButton('Headers');
			    $chkHeaders2->set_active(1 );
			    $hbox2->add($chkHeaders2);
			    $chkHeaders2->show;
			    $chkHeaders2->can_focus(1 );
			    $hbox2->set_child_packing($chkHeaders2, 0, 0, 0, 'start' );
			#
			# Construct a GtkHBox 'hbox3'
			$hbox3 = new Gtk::HBox(0, 0 );
			$tableSearch->attach($hbox3, 0, 1, 2, 3, ['fill'], ['fill'], 0, 0 );
			$hbox3->show;
			    #
			    # Construct a GtkRadioButton 'rdbAnd'
			    $rdbAnd = new Gtk::RadioButton('and');
			    $hbox3->add($rdbAnd);
			    $rdbAnd->show;
			    $rdbAnd->can_focus(1 );
			    $rdbAnd->set_active(0 );
			    $hbox3->set_child_packing($rdbAnd, 0, 0, 0, 'start' );
			    #
			    # Construct a GtkRadioButton 'rdbOr'
			    $rdbOr = new Gtk::RadioButton('or', $rdbAnd);
			    $hbox3->add($rdbOr);
			    $rdbOr->show;
			    $rdbOr->can_focus(1 );
			    $rdbOr->set_active(0 );
			    $hbox3->set_child_packing($rdbOr, 0, 0, 0, 'start' );
			#
			# Construct a GtkEntry 'entAnd'
			$entAnd = new Gtk::Entry;
			$tableSearch->attach($entAnd, 1, 2, 2, 3, ['expand', 'fill'], [], 0, 0 );
			$entAnd->show;
			$entAnd->can_focus(1 );
			$entAnd->set_text('');
			$entAnd->set_max_length(0 );
			$entAnd->set_visibility(1 );
			$entAnd->set_editable(1 );
			#
			# Construct a GtkLabel 'lblNot'
			$lblNot = new Gtk::Label('and not');
			$lblNot->set_justify('center' );
			$lblNot->set_line_wrap(0 );
			$tableSearch->attach($lblNot, 0, 1, 4, 5, [], [], 0, 0 );
			$lblNot->show;
			$lblNot->set_alignment(0.5, 0.5 );
			#
			# Construct a GtkEntry 'entNot'
			$entNot = new Gtk::Entry;
			$tableSearch->attach($entNot, 1, 2, 4, 5, ['expand', 'fill'], [], 0, 0 );
			$entNot->show;
			$entNot->can_focus(1 );
			$entNot->set_text('');
			$entNot->set_max_length(0 );
			$entNot->set_visibility(1 );
			$entNot->set_editable(1 );
			#
			# Construct a GtkLabel 'lblContain3'
			$lblContain3 = new Gtk::Label('contained in');
			$lblContain3->set_justify('center' );
			$lblContain3->set_line_wrap(0 );
			$tableSearch->attach($lblContain3, 0, 1, 5, 6, [], [], 0, 0 );
			$lblContain3->show;
			$lblContain3->set_alignment(0.5, 0.5 );
			#
			# Construct a GtkHBox 'hbox6'
			$hbox6 = new Gtk::HBox(0, 0 );
			$tableSearch->attach($hbox6, 1, 2, 5, 6, ['fill'], ['fill'], 0, 0 );
			$hbox6->show;
			    #
			    # Construct a GtkCheckButton 'chkBody3'
			    $chkBody3 = new Gtk::CheckButton('Body');
			    $chkBody3->set_active(1 );
			    $hbox6->add($chkBody3);
			    $chkBody3->show;
			    $chkBody3->can_focus(1 );
			    $hbox6->set_child_packing($chkBody3, 0, 0, 0, 'start' );
			    #
			    # Construct a GtkCheckButton 'chkHeaders3'
			    $chkHeaders3 = new Gtk::CheckButton('Headers');
			    $chkHeaders3->set_active(1 );
			    $hbox6->add($chkHeaders3);
			    $chkHeaders3->show;
			    $chkHeaders3->can_focus(1 );
			    $hbox6->set_child_packing($chkHeaders3, 0, 0, 0, 'start' );
		    #
		    # Construct a GtkLabel 'lblSearchfor'
		    $lblSearchfor = new Gtk::Label('');
		    $lblSearchfor->parse_uline('Sea_rch for');
		    $lblSearchfor->set_justify('center' );
		    $lblSearchfor->set_line_wrap(0 );
		    $notebook->append_page($tableSearch, $lblSearchfor);
		    $lblSearchfor->show;
		    $lblSearchfor->set_alignment(0.5, 0.5 );
		    #
		    # Construct a GtkVBox 'vbox4'
		    $vbox4 = new Gtk::VBox(0, 0 );
		    $vbox4->show;
			#
			# Construct a GtkScrolledWindow 'scrFiles'
			$scrFiles = new Gtk::ScrolledWindow( undef, undef);
			$scrFiles->set_policy('always', 'always' );
			$scrFiles->border_width(0 );
			$scrFiles->hscrollbar->set_update_policy('continuous' );
			$scrFiles->vscrollbar->set_update_policy('continuous' );
			$vbox4->add($scrFiles);
			$scrFiles->show;
			    #
			    # Construct a GtkViewport 'vptFiles'
			    $vptFiles = new Gtk::Viewport(new Gtk::Adjustment( 0.0, 0.0, 101.0, 0.1, 1.0, 1.0), new Gtk::Adjustment( 0.0, 0.0, 101.0, 0.1, 1.0, 1.0) );
			    $vptFiles->set_shadow_type('in' );
			    $scrFiles->add_with_viewport($vptFiles);
			    $vptFiles->show;
				#
				# Construct a GtkTree 'lstFiles'
# FIXME - will be switching from a list widget to a tree widget (commented out)
#				$lstFiles = new Gtk::Tree();
                                $lstFiles = new Gtk::CList(1);
				$lstFiles->set_selection_mode('multiple' );
#				$lstFiles->set_view_mode('item');
				$lstFiles->set_shadow_type('none');
				$vptFiles->add($lstFiles);
				$lstFiles->show;
			$vbox4->set_child_packing($scrFiles, 1, 1, 0, 'start' );
			#
			# Construct a GtkHBox 'hbox4'
			$hbox4 = new Gtk::HBox(0, 0 );
			$vbox4->add($hbox4);
			$hbox4->show;
			    #
			    # Construct a GtkButton 'pbRefresh'
			    $pbRefresh = new Gtk::Button('Refresh');
			    $hbox4->add($pbRefresh);
			    $pbRefresh->show;
			    $pbRefresh->can_focus(1 );
			    $pbRefresh_key = $pbRefresh->child->parse_uline('Refresh');
			    $pbRefresh->add_accelerator('clicked', $accelgroup, $pbRefresh_key, 'mod1_mask', ['visible', 'locked'] );
			    $hbox4->set_child_packing($pbRefresh, 0, 0, 0, 'start' );
                            #
                            # Construct a GtkButton 'pbSavelist'
                            $pbSavelist = new Gtk::Button('Save');
                            $hbox4->add($pbSavelist);
                            $pbSavelist->show;
                            $pbSavelist->can_focus(1 );
                            $pbSavelist_key = $pbSavelist->child->parse_uline('Save');
                            $pbSavelist->add_accelerator('clicked', $accelgroup, $pbSavelist_key, 'mod1_mask', ['visible', 'locked'] );
                            $hbox4->set_child_packing($pbSavelist, 0, 0, 0, 'start' );
			$vbox4->set_child_packing($hbox4, 0, 0, 0, 'start' );
		    #
		    # Construct a GtkLabel 'lblSearchin'
		    $lblSearchin = new Gtk::Label('');
		    $lblSearchin->parse_uline('Search _in');
		    $lblSearchin->set_justify('center' );
		    $lblSearchin->set_line_wrap(0 );
		    $notebook->append_page($vbox4, $lblSearchin);
		    $lblSearchin->show;
		    $lblSearchin->set_alignment(0.5, 0.5 );
		    #
		    # Construct a GtkVBox 'vbox2'
		    $vbox2 = new Gtk::VBox(0, 0 );
		    $vbox2->show;
			#
			# Construct a GtkFrame 'frmDate'
			$frmDate = new Gtk::Frame('Search by date');
			$frmDate->set_label_align(0, 0 );
			$frmDate->set_shadow_type('etched_in' );
			$vbox2->add($frmDate);
			$frmDate->show;
			    #
			    # Construct a GtkVBox 'vbox3'
			    $vbox3 = new Gtk::VBox(0, 0 );
			    $frmDate->add($vbox3);
			    $vbox3->show;
				#
				# Construct a GtkTable 'tblSearch'
				$tblSearch = new Gtk::Table(4, 2, 0 );
				$tblSearch->set_row_spacings(0 );
				$tblSearch->set_col_spacings(0 );
				$vbox3->add($tblSearch);
				$tblSearch->show;
				    #
				    # Construct a GtkRadioButton 'rdbBetween'
				    $rdbBetween = new Gtk::RadioButton('_Between');
				    $tblSearch->attach($rdbBetween, 0, 1, 1, 2, [], [], 0, 0 );
				    $rdbBetween->show;
				    $rdbBetween->can_focus(1 );
				    $rdbBetween_key = $rdbBetween->child->parse_uline('_Between');
				    $rdbBetween->add_accelerator('clicked', $accelgroup, $rdbBetween_key, 'mod1_mask', ['visible', 'locked'] );
				    $rdbBetween->set_active(0 );
				    #
				    # Construct a GtkLabel 'lblAnd'
				    $lblAnd = new Gtk::Label('and');
				    $lblAnd->set_justify('right' );
				    $lblAnd->set_line_wrap(0 );
				    $tblSearch->attach($lblAnd, 0, 1, 2, 3, [], [], 0, 0 );
				    $lblAnd->show;
				    $lblAnd->set_alignment(0.5, 0.5 );
				    #
				    # Construct a GtkRadioButton 'rdbExpression'
				    $rdbExpression = new Gtk::RadioButton('_Expression', $rdbBetween);
				    $tblSearch->attach($rdbExpression, 0, 1, 3, 4, [], [], 0, 0 );
				    $rdbExpression->show;
				    $rdbExpression->can_focus(1 );
				    $rdbExpression_key = $rdbExpression->child->parse_uline('_Expression');
				    $rdbExpression->add_accelerator('clicked', $accelgroup, $rdbExpression_key, 'mod1_mask', ['visible', 'locked'] );
				    $rdbExpression->set_active(0 );
				    #
				    # Construct a GtkEntry 'dateExpression'
				    $dateExpression = new Gtk::Entry;
				    $tblSearch->attach($dateExpression, 1, 2, 3, 4, ['expand', 'fill'], [], 0, 0 );
				    $dateExpression->show;
				    $dateExpression->can_focus(1 );
				    $dateExpression->set_text('');
				    $dateExpression->set_max_length(0 );
				    $dateExpression->set_visibility(1 );
				    $dateExpression->set_editable(0 );
				    #
				    # Construct a GnomeDateEdit 'dateStart'
				    $dateStart = new Gnome::DateEdit(0, 0, 1);
				    $dateStart->set_popup_range(7, 19 );
				    $tblSearch->attach($dateStart, 1, 2, 1, 2, ['expand', 'fill'], [], 0, 0 );
				    $dateStart->show;
				    #
				    # Construct a GnomeDateEdit 'dateEnd'
				    $dateEnd = new Gnome::DateEdit(0, 0, 1);
				    $dateEnd->set_popup_range(7, 19 );
				    $tblSearch->attach($dateEnd, 1, 2, 2, 3, ['expand', 'fill'], [], 0, 0 );
				    $dateEnd->show;
				    #
				    # Construct a GtkRadioButton 'rdbAny'
				    $rdbAny = new Gtk::RadioButton('A_ny', $rdbBetween);
				    $tblSearch->attach($rdbAny, 0, 2, 0, 1, [], [], 0, 0 );
				    $rdbAny->show;
				    $rdbAny->can_focus(1 );
				    $rdbAny_key = $rdbAny->child->parse_uline('A_ny');
				    $rdbAny->add_accelerator('clicked', $accelgroup, $rdbAny_key, 'mod1_mask', ['visible', 'locked'] );
				    $rdbAny->set_active(1 );
				$vbox3->set_child_packing($tblSearch, 1, 1, 0, 'start' );
			$vbox2->set_child_packing($frmDate, 1, 1, 0, 'start' );
			#
			# Construct a GtkHBox 'hbox5'
			$hbox5 = new Gtk::HBox(0, 0 );
			$vbox2->add($hbox5);
			$hbox5->show;
			    #
			    # Construct a GtkCheckButton 'chkCase'
			    $chkCase = new Gtk::CheckButton('Case insensi_tive');
			    $chkCase->set_active(1 );
			    $hbox5->add($chkCase);
			    $chkCase->show;
			    $chkCase->can_focus(1 );
			    $chkCase_key = $chkCase->child->parse_uline('Case insensi_tive');
			    $chkCase->add_accelerator('clicked', $accelgroup, $chkCase_key, 'mod1_mask', ['visible', 'locked'] );
			    $hbox5->set_child_packing($chkCase, 0, 0, 0, 'start' );
			    #
			    # Construct a GtkCheckButton 'chkInverse'
			    $chkInverse = new Gtk::CheckButton('Inverse _match');
			    $chkInverse->set_active(0 );
			    $hbox5->add($chkInverse);
			    $chkInverse->show;
			    $chkInverse->can_focus(1 );
			    $chkInverse_key = $chkInverse->child->parse_uline('Inverse _match');
			    $chkInverse->add_accelerator('clicked', $accelgroup, $chkInverse_key, 'mod1_mask', ['visible', 'locked'] );
			    $hbox5->set_child_packing($chkInverse, 0, 0, 0, 'start' );
			$vbox2->set_child_packing($hbox5, 1, 1, 0, 'start' );
			#
			# Construct a GtkCheckButton 'chkSkipattach'
			$chkSkipattach = new Gtk::CheckButton('Skip non-te_xt attachments');
			$chkSkipattach->set_active(1 );
			$vbox2->add($chkSkipattach);
			$chkSkipattach->show;
			$chkSkipattach->can_focus(1 );
			$chkSkipattach_key = $chkSkipattach->child->parse_uline('Skip non-te_xt attachments');
			$chkSkipattach->add_accelerator('clicked', $accelgroup, $chkSkipattach_key, 'mod1_mask', ['visible', 'locked'] );
			$vbox2->set_child_packing($chkSkipattach, 0, 0, 0, 'start' );
		    #
		    # Construct a GtkLabel 'lblOptions'
		    $lblOptions = new Gtk::Label('');
		    $lblOptions->parse_uline('Search _options');
		    $lblOptions->set_justify('center' );
		    $lblOptions->set_line_wrap(0 );
		    $notebook->append_page($vbox2, $lblOptions);
		    $lblOptions->show;
		    $lblOptions->set_alignment(0.5, 0.5 );
		$vbox1->set_child_packing($notebook, 1, 1, 0, 'start' );
		#
		# Construct a GtkHButtonBox 'hbuttonbox1'
		$hbuttonbox1 = new Gtk::HButtonBox;
		$vbox1->add($hbuttonbox1);
		$hbuttonbox1->show;
		$hbuttonbox1->set_layout('default_style' );
		$hbuttonbox1->set_spacing(30 );
		$hbuttonbox1->set_child_size(85, 27 );
		$hbuttonbox1->set_child_ipadding(7, 0 );
		    #
		    # Construct a GtkButton 'pbClear'
		    $pbClear = new Gtk::Button('_Clear');
		    $hbuttonbox1->add($pbClear);
		    $pbClear->show;
		    $pbClear->can_default(1 );
		    $pbClear->can_focus(1 );
		    $pbClear_key = $pbClear->child->parse_uline('_Clear');
		    $pbClear->add_accelerator('clicked', $accelgroup, $pbClear_key, 'mod1_mask', ['visible', 'locked'] );
		    #
		    # Construct a GtkButton 'pbSearch'
		    $pbSearch = new Gtk::Button('Se_arch');
		    $hbuttonbox1->add($pbSearch);
		    $pbSearch->show;
		    $pbSearch->can_default(1 );
		    $pbSearch->can_focus(1 );
		    $pbSearch_key = $pbSearch->child->parse_uline('Se_arch');
		    $pbSearch->add_accelerator('clicked', $accelgroup, $pbSearch_key, 'mod1_mask', ['visible', 'locked'] );
		    #
		    # Construct a GtkButton 'pbView'
		    $pbView = new Gtk::Button('_View');
		    $hbuttonbox1->add($pbView);
		    $pbView->show;
		    $pbView->can_default(1 );
		    $pbView->can_focus(1 );
		    $pbView_key = $pbView->child->parse_uline('_View');
		    $pbView->add_accelerator('clicked', $accelgroup, $pbView_key, 'mod1_mask', ['visible', 'locked'] );
		$vbox1->set_child_packing($hbuttonbox1, 1, 1, 0, 'start' );
	#
	# Construct a GnomeAppBar 'appbar1'
	$appbar1 = new Gtk::Statusbar();
	$dlgMain->set_statusbar($appbar1);
	$appbar1->show;
	$context_id = $appbar1->get_context_id( "gtkgrepmail" );
    #
    # Connect all signals now that dlgMain_form are constructed
    $mnuExit->signal_connect( 'activate', "on_mnuExit_activate" );
    $mnuPreferences->signal_connect( 'activate', "on_mnuPreferences_activate" );
    $mnuManual->signal_connect( 'activate', "on_mnuManual_activate" );
    $mnuAbout->signal_connect( 'activate', "on_mnuAbout_activate" );
    $pbRefresh->signal_connect( 'clicked', "on_pbRefresh_clicked" );
    $pbSavelist->signal_connect( 'clicked', "on_pbSavelist_clicked" );
    $pbClear->signal_connect( 'clicked', "on_pbClear_clicked" );
    $pbSearch->signal_connect( 'clicked', "on_pbSearch_clicked" );
    $pbView->signal_connect( 'clicked', "on_pbView_clicked" );
# FIXME - will be switching from a list widget to a tree widget (commented out)
    $lstFiles->signal_connect( 'select_row', "on_lstFiles_select_row" );
#    $lstFiles->signal_connect( 'select_item', "on_lstFiles_select_item" );
    $lstFiles->signal_connect( 'unselect_row', "on_lstFiles_unselect_row" );
#    $lstFiles->signal_connect( 'unselect_item', "on_lstFiles_unselect_item" );
    $rdbExpression->signal_connect( 'toggled', "on_rdbExpression_toggled" );

    $dlgMain_form->show;
}

sub on_dlgMain_keypress {
  msgError_new("Operation cancelled at user's request");
  kill 15, $child
}

sub on_rdbExpression_toggled {
    $valrdbExpression = $rdbExpression->get_active;
    if ( $valrdbExpression ) {
      $dateExpression->set_editable(1)
    } else {
      $dateExpression->set_editable(0)
    };
}

sub on_pbClear_clicked {
    $appbar1->push($context_id,"Form cleared");
    $entSearch->set_text("");
    $entAnd->set_text("");
    $entNot->set_text("");
    $chkBody->set_active(1);
    $chkHeaders->set_active(1);
    $chkBody2->set_active(1);
    $chkHeaders2->set_active(1);
    $chkBody3->set_active(1);
    $chkHeaders3->set_active(1);
    $rdbAnd->set_active(1);
    $row = 0;
    $lstFiles->clear;
# FIXME - will need to do it this way for tree widget
#    while ( $row <= $#filelist ) {
#      $lstFiles->unselect_item($row,0);
#      $row++;
#    }
    @valfiles = "";
    $rdbAny->set_active(1);
    $dateExpression->set_text("");
    $chkCase->set_active(1);
    $chkInverse->set_active(0);
    $chkSkipattach->set_active(1);
    $dateStart->set_time(0);
    $dateEnd->set_time(0);
}

sub on_pbRefresh_clicked {
    refreshfiles()
}

sub on_pbSavelist_clicked {
    LoadOptions();
    open (CONFIG, ">$configpath") || msgError_new("Cannot write to configuration file $configpath");
    $usrSelectedFolders = join(",",@valfiles);
    save_config();
    $appbar1->push($context_id,"Selected folders saved as defaults");
    close CONFIG;
}

# FIXME - delete these two subroutines after we have switched from list to tree
sub on_lstFiles_select_row {
    ( $clist, $row, $column, undef, undef ) = @_;
    $text = $clist->get_text( $row, $column );
    push @valfiles, "$valMailpath/$text";
}

sub on_lstFiles_unselect_row {
    ( $clist, $row, $column, undef, undef ) = @_;
    $text = $clist->get_text( $row, $column );
    $i = @valfiles;
    while ( $i-- ) {
      $file = pop(@valfiles);
      if ( $text ) {
        unshift @valfiles, $file unless ( $file eq "$valMailpath/$text" );
      }
    }
}

sub on_lstFiles_select_item {
    # FIXME - does not work
    ( $tree, $row, $column, undef, undef ) = @_;
    $text = $tree->get_text( $row, $column );
    push @valfiles, "$valMailpath/$text";
}

sub select_item {
#    ( $widget, $path ) = @_;
    # FIXME - does not work
#    $entry->set_text( $path );
#    show_files( $path );
}

sub on_lstFiles_unselect_item {
    # FIXME - does not work
    ( $tree, $row, $column, undef, undef ) = @_;
    $text = $tree->get_text( $row, $column );
    $file = pop(@valfiles);
    if ( $text ) {
      push @valfiles, $file unless ( $file eq "$valMailpath/$text" )
    }
}

sub refreshfiles {
    if ( pingecho($usrHost) ) {
        msgError_new("Host $usrHost is uncontactable");
	return
    };
    $appbar1->push($context_id,"Creating file list, please wait...");
    $dlgMain_form->window->set_cursor( new Gtk::Gdk::Cursor( 150 ) );
    # Cursor does not redraw itself unless we do this
    $dlgMain_form->draw(undef);
# FIXME - does not work
#    $lstFiles->clear_items($start,$end);
    $lstFiles->clear;
    @valfiles = "";
    if ( $usrHost eq "localhost" ) {
      find(\&ls,"$valMailpath");
      sub ls() {
        if (-f) { push(@filelist,$File::Find::name) }
      };
    } else {
      $escape = "\\";
      $filelist = `@sshcommand $usrHost -l $usrUser "perl -e 'use File::Find; find(\\&ls,\\"$valMailpath\\"); sub ls(){if (-f){print \\"\\\$File::Find::name\\n\\"}}'"` || msgError_new("Error: could not list files on remote host");
      if ( $filelist ) { @filelist = split(/\s/, $filelist) }
    }
    # The old way - using the list widget
    foreach $file ( sort @filelist ) {
      chomp $file;
      if ( basename($file) eq "search-results" ) { next };
      $file =~ s/^$valMailpath\///;
      $lstFiles->append($file)
    };
# This will be the new way of doing it - using the tree widget
#        $leaf = new_with_label Gtk::TreeItem( $valMailpath );
#        $lstFiles->append( $leaf );
#        $leaf->signal_connect( 'select', select_item, $valMailpath );
#        $leaf->set_user_data( $valMailpath );
#        $leaf->show();
#	if ( has_sub_trees( $valMailpath ) )
#        {
#           $subtree = new Gtk::Tree();
#           $leaf->set_subtree( $subtree );
#           $leaf->signal_connect( 'expand', expand_tree, $subtree );
#           $leaf->signal_connect( 'collapse', collapse_tree );
#           $leaf->expand();
#        }
    # If there are any files to be selected by default, do it here
    foreach $selectedfolder ( split /,/,$usrSelectedFolders ) {
      $row = 0;
      while ( $text = $lstFiles->get_text( $row, 0 ) ) {
	if ( "$valMailpath/$text" eq $selectedfolder ) {
          $lstFiles->select_row( $row, 0 );
	}
	$row++;
      }
    }
    $appbar1->pop($context_id);
    $dlgMain_form->window->set_cursor( new Gtk::Gdk::Cursor( 68 ) );
}

# Next three subroutines will also be used by the tree widget only
sub expand_tree {
    ( $item, $subtree ) = @_;
    $dir = $item->get_user_data();
    chdir( $dir );
    foreach $dir_entry ( <*> )
    {
       if ( -d $dir_entry )
       {
         $path = $dir . "/" . $dir_entry;
         $path =~ s|//|/|g;
         $item_new = new_with_label Gtk::TreeItem( $dir_entry );
         $item_new->set_user_data( $path );
         $item_new->signal_connect( 'select', select_item, $path );
         $subtree->append( $item_new );
         $item_new->show();
         if ( has_sub_trees( $path ) )
         {
            $new_subtree = new Gtk::Tree();
            $item_new->set_subtree( $new_subtree );
            $item_new->signal_connect( 'expand',
                                       expand_tree,
                                       $new_subtree );
            $item_new->signal_connect( 'collapse', collapse_tree );
         }
      }
   }
   chdir( ".." );
}

sub collapse_tree {
   my ( $item ) = @_;
   my $subtree = new Gtk::Tree();
   $item->remove_subtree();
   $item->set_subtree( $subtree );
   $item->signal_connect( 'expand', expand_tree, $subtree );
}

sub has_sub_trees {
   my ( $dir ) = @_;
   my $file;
   foreach $file ( <$dir/*> )
   {
      return 0 if ( -d $file );
   } # FIXME - simplify this
   return ( 1 );
}
    
sub on_pbSearch_clicked {
    $appbar1->pop($context_id);
    # Reset old values
    $andspec = "";
    $orspec = "";
    $notspec = "";
    $datespec = "";
    $scopeflag = "";
    $scopeflag2 = "";
    $scopeflag3 = "";
    $valInverse = "";
    $valCase = "";
    $valSkipattach = "";
    # Grab new values
    $valSearch = $entSearch->get_text;
    $valAnd = $entAnd->get_text;
    $valNot = $entNot->get_text;
    # Now let's escape all the shell characters
    $valSearch =~ s/([\&;\`'!\\\|"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;
    $valAnd =~ s/([\&;\`'!\\\|"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;
    $valNot =~ s/([\&;\`'!\\\|"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;
    # Now all the body and header checkboxes
    $valBody = $chkBody->get_active;
    $valHeaders = $chkHeaders->get_active;
    if ( $valBody &! $valHeaders ) { $scopeflag = "-b" };
    if ( $valHeaders &! $valBody ) { $scopeflag = "-h" };
    $valBody2 = $chkBody2->get_active;
    $valHeaders2 = $chkHeaders2->get_active;
    if ( $valBody2 &! $valHeaders2 ) { $scopeflag2 = "-b" };
    if ( $valHeaders2 &! $valBody2 ) { $scopeflag2 = "-h" };
    $valBody3 = $chkBody3->get_active;
    $valHeaders3 = $chkHeaders3->get_active;
    if ( $valBody3 &! $valHeaders3 ) { $scopeflag3 = "-b" };
    if ( $valHeaders3 &! $valBody3 ) { $scopeflag3 = "-h" };
    # And the rest
    $valrdbAnd = $rdbAnd->get_active;
    $valBetween = $rdbBetween->get_active;
    $valExpression = $dateExpression->get_text;
    print $valExpression;
    if ($valExpression) {
      $valExpression =~ s/([\&;\`'!\\\|"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;
    };
    print $valExpression;
    $valrdbExpression = $rdbExpression->get_active;
    if ( $chkCase->get_active ) { $valCase = "-i" };
    if ( $chkInverse->get_active ) { $valInverse = "-v" };
    if ( $chkSkipattach->get_active ) { $valSkipattach  = "-M" };
    $valStart = $dateStart->get_date;
    $valEnd = $dateEnd->get_date;
    $datestart = &ParseDateString("epoch $valStart");
    $dateend = &ParseDateString("epoch $valEnd");
    if ( $valrdbExpression && $valExpression ) { $datespec = "-d \\\"$valExpression\\\"" }
    elsif ( $valBetween ) { $datespec = "-d \\\"between $datestart and $dateend\\\"" };
    if ( $valrdbAnd && $valAnd ) { $andspec = "| grepmail $scopeflag2 $valInverse $valCase -e \\\"$valAnd\\\"" }
    elsif ( $valAnd ) { $orspec = "\\|\\\"$valAnd\\\"" };
    if ( $valNot ) { $notspec = "| grepmail $scopeflag3 $valCase -v -e \\\"$valNot\\\"" };
    $grepmailcmd = "$valGrepmail -e \\($escape\"$valSearch$escape\"$orspec\\) $scopeflag $valInverse $valCase $valSkipattach $datespec @valfiles $andspec $notspec 2>&1 1>$valMailpath/$valMailbox";
    if ( $usrHost eq "localhost" ) {
      $grepmailcmd = "$grepmailcmd 2>/tmp/gtkgrepmail.$$"
    } else {
      $grepmailcmd = "@sshcommand $usrHost -l $usrUser \"$grepmailcmd\" >/tmp/gtkgrepmail.$$"
    };
    if ( $debug ) { print $grepmailcmd . "\n" };
    unless ( errorhandler() ) {
      $appbar1->push($context_id,"Initiating search...");
      # FIXME - how to refresh the status bar?
      $dlgMain_form->window->set_cursor( new Gtk::Gdk::Cursor( 150 ) );
      # Cursor does not redraw itself unless we do this
      $dlgMain_form->draw(undef);
      unless ($child = fork) {
        exec "$grepmailcmd";
      };
      $signalid = $dlgMain_form->signal_connect( 'key_press_event', "on_dlgMain_keypress" );
      do {
        # FIXME - does not seem to wait to hear the keypress event
        $kid = waitpid(-1,&WNOHANG);
      } until $kid == -1;
      $dlgMain_form->signal_disconnect( $signalid );
      $dlgMain_form->window->set_cursor( new Gtk::Gdk::Cursor( 68 ) );
      $resultsfile = "/tmp/gtkgrepmail.$$";
      if (-f $resultsfile) {
        open (RESULTS,$resultsfile);
        $results = (<RESULTS>);
        close RESULTS;
        unlink $resultsfile
      };
      if ( $results ) {
        chomp $results;
        msgError_new("The following warnings or errors were returned:\n$results");
        $appbar1->pop($context_id)
      } else {
        $appbar1->push($context_id,"Search complete")
      }
    }
}

sub display_results {
}

sub on_pbView_clicked {
  @imapcommand = split(/ /, $usrIMAP);
  $appbar1->push($context_id,"Attempting to launch browser...");
  $dlgMain_form->window->set_cursor( new Gtk::Gdk::Cursor( 150 ) );
  # Cursor does not redraw itself unless we do this
  $dlgMain_form->draw(undef);
  if ( $usrHost eq "localhost" ) {
    $returncode = system(@imapcommand, "$valMailpath/$valMailbox");
  } else {
    $imappath = $usrIMAPRoot;
    $imappath =~ s/^(($ENV{HOME}|~))*(\/)*//ig;
    $imappath = "IMAP://$usrHost/" . uri_escape($imappath) . "/" . uri_escape($usrMailbox);
    $returncode = system(@imapcommand, $imappath)
  }
  $appbar1->pop($context_id);
  if ( $returncode ) {
    msgError_new("Could not launch browser")
  } else {
    $appbar1->push($context_id,"Browser launched");
    $dlgMain_form->window->set_cursor( new Gtk::Gdk::Cursor( 68 ) );
  };
}

sub on_mnuManual_activate {
    unless ( $child = fork ) {
      exec "gnome-help-browser 'man:gtkgrepmail'";
    }
}

sub on_mnuAbout_activate {
    dlgAbout_new();
}

sub on_mnuExit_activate {
    Gtk->exit( 0 );
    return 0;
}

sub on_mnuPreferences_activate {
    dlgOptions_new();
}

sub errorhandler {
    if ( $valBetween ) {
      if ( $datestart eq "1970010108:00:01" ) {
        msgError_new("Error in start date");
        return 1
      } elsif ( $dateend eq "1970010108:00:01" ) {
        msgError_new("Error in end date");
        return 1
      } elsif ( $datestart gt $dateend ) {
        msgError_new("Start date is after end date");
	return 1
      }
    };
    if ( $valSearch ) {
      unless ( $valBody || $valHeaders ) {
        msgError_new("You must select at least Body or Headers to search");
	return 1
      }
    }
    if ( $valAnd ) {
      unless ( $valBody2 || $valHeaders2 ) {
        msgError_new("You must select at least Body or Headers to search");
	return 1
      }
    }
    if ( $valNot ) {
      unless ( $valBody3 || $valHeaders3 ) {
        msgError_new("You must select at least Body or Headers to search");
	return 1
      }
    }
    unless ( $#valfiles ) {
      msgError_new("Please select at least one mailbox to search");
      return 1
    };
    unless ( $valBetween || $valrdbExpression || $valSearch ) {
      msgError_new("Please specify a search term");
      return 1
    };
    if ( $valrdbExpression &! $valExpression ) {
      msgError_new("Please specify a date expression");
      return 1
    };
}

sub dlgAbout_new {
    #
    # Construct a GnomeAbout 'dlgAbout'
    $dlgAbout_form = new Gnome::About('gtkgrepmail', "$VERSION", '(C) 2003 Terminus Network Services', 'Jeremy Malcolm', 'A gtk front end to grepmail which provides a GUI interface to search through your sent and saved mail.');
    $dlgAbout_form->set_title('About');
    $dlgAbout_form->position('mouse' );
    $dlgAbout_form->set_policy(1, 1, 0 );
    $dlgAbout_form->set_modal(1 );
    $dlgAbout_form->realize;
    $accelgroup = new Gtk::AccelGroup;
    $accelgroup->attach($dlgAbout_form );

    $dlgAbout_form->show;
}

sub load_config {
    if ( open (CONFIG, "$configpath") ) {
      while ( <CONFIG> ) {
        m/^(#|\n)/ && next;
        chomp;
        /^([^=]+)=([^=]+)/;
        $cfg{$1} = $2;
      };
      close CONFIG
    }
}

sub save_config {
    print CONFIG qq{Mailpath=$usrMailpath
IMAPRoot=$usrIMAPRoot
Mailbox=$usrMailbox
Grepmail=$usrGrepmail
IMAP=$usrIMAP
SSH=$usrSSH
Host=$usrHost
User=$usrUser
SelectedFolders=$usrSelectedFolders
};
}
1;

__END__

=pod

=head1 NAME

gtkgrepmail - a gtk front end to grepmail

=head1 SYNOPSIS

  gtkgrepmail

=head1 DESCRIPTION

A gtk front end to grepmail which will allow you a GUI interface to search
through your sent and saved mail.  Support is provided for both local
mailboxes and remote mailboxes that are accessible using IMAP and ssh/rsh.

=head1 OPTIONS

Preferences are set by clicking the I<Settings> menu and selecting
I<Preferences>.  The options set out there are as follows:

=over 8

=item B<Path (local or remote) to mail folders>

Here you must specify the I<full> path to the directory that contains your
mail folders.  These must be in standard UNIX mailbox format, but may be
compressed by gzip, tzip or bzip2 if those utilities are present on the
same system.  You do not need to prepend a hostname to the path here,
this will automatically be assumed if a host other than C<localhost> is
specified below.

=item B<IMAP root>

The location of the root of your IMAP folder tree, if applicable.  Normally
this will be either the same as, or a subset of, the path to your mail
folders set above.

=item B<Relative path to temporary mailbox>

gtkgrepmail stores your search results in a temporary mailbox.  Use the
default (search-results) unless you have some particular reason to choose
differently.  Do not prepend a path.  Do not use spaces in the filename.

=item B<Path (local or remote) to grepmail>

Here you must specify the I<full> path to grepmail, on whichever system
contains your mailboxes.  You do not need to prepend a hostname to the
path here, this will automatically be assumed if a host other than
C<localhost> is specified below.

=item B<Path to IMAP or mailbox browser>

If your mailboxes are on the local computer, you can specify any program
(even a text editor such as vi) that can read the mailboxes.  The default
is "C</usr/bin/X11/xterm -e /usr/bin/elm -f>".

If your mailboxes are on a remote computer, specify a program that can
accept an IMAP URI such as C<IMAP://servername/path/to/mailbox>.  One
such program is Netscape Navigator.

=item B<Path to ssh or rsh>

If you intend to access remote mailboxes, gtkgrepmail calls grepmail on
the remote computer using ssh (recommended) or rsh.  It is important to
note that gtkgrepmail I<requires> that you can log into the remote
computer interactively entering a password.  It is recommended that you
achieve this through the use of the ssh-agent(1) facility.

=item B<Host>

If you intend to access your mailboxes on your local computer, leave
this set as "C<localhost>".  Otherwise, enter a name by which your
remote computer can be reached.

=item B<User>

If accessing your mailboxes remotely, enter your username here.  This is
used both for rsh/ssh and for IMAP.

=back

=head1 USAGE

The main window of the gtkgrepmail application has three tabbed panes:

=over 8

=item B<Search for>

Type the expression you wish to search for in the first textbox field.
In the second field you may type another expression which you wish to
combine with the first in order to return results which only match both
expressions (toggle the "and" radio button) or which match either
expression (toggle the "or" radio button).  If you wish to ensure that
a certain expression does not exist in the search results, enter that
expression in the third field.  In all three cases, you may choose to
restrict your search on that expression to the body or the header of
emails by untoggling the Header or Body checkboxes respectively.

=item B<Search in>

Here will be listed all of the mailboxes that are available for you to
search.  Select one or more mailboxes by clicking them.  Press "Refresh"
if the available mailboxes have changed since the list was drawn.
Press "Save" to save the currently selected mailboxes as your defaults.

=item B<Search options>

Here you can restrict your search by date: click the "Between" radio
button if you wish to search for emails between two dates, or click
the "Expression" radio button and describe the period in which you
are interested.  You can use expressions such as "today", "yesterday",
"5 days ago", "5 weeks ago", "before July 2001", "12/10/00" and so
on: for complete details see Date::Manip(3pm).

As for the other options found on this pane, "Case insensitive" should
be self-explanatory, "Inverse match" will find emails that do I<not>
match your first two search expressions.  The state of the "and/or"
toggle will not make any difference in this case; if "apple" and "pear"
are your first two expressions, the results will be all emails that
contain neither of those terms.  The "Skip non-text attachments" option
will ignore MIME attachments to your emails that are not in a plain
text format (this is generally a good idea when searching for short
words).

=item B<Buttons>

The buttons at the bottom of the main window control the main functions
of the program.  "Clear" will reset the data in all three tabs of the
form to their default values.  "Search" will commence the search that
you have specified.  "View" will launch your IMAP or mailbox browser so
that you can see the search results.

=back

=head1 AUTHOR

Jeremy Malcolm <Jeremy@Malcolm.id.au>

=head1 SEE ALSO

grepmail(1), grep(1), perl(1), ssh(1), ssh-agent(3), rsh(1),
Date::Manip(3pm)

=head1 BUGS

Does not offer complete feature-compatibility with grepmail.  For example
by clicking "Header" and "Body" checkboxes you are specifying that you
want the text to appear I<either> in the header or body, not that you
want it to appear in both.  There is no way of specifying that.

Has no provision to prompt you for your SSH password or to save or
cache that password.  The latter might be construed as as a feature,
rather than a bug.

The application will appear to hang while a search is in progress.
How noticeable that is will depend on how large your mailboxes are and
how busy your network connection is (if applicable).

The status bar does not update before a search is made or a directory
listing is compiled.

The "Inverse match" option does not do what you might expect.  The
state of the "and/or" radio buttons is ignored and the contents of
the "Not" field remains uninverted.  At the moment, I am disinclined
fix this bug, as it would only make the meaning of "Inverse match"
more confusing than it already is.

=cut

