# ClamTk, copyright (C) 2004-2010 Dave M
#
# This file is part of ClamTk.
#
# ClamTk is free software; you can redistribute it and/or modify it
# under the terms of either:
#
# a) the GNU General Public License as published by the Free Software
# Foundation; either version 1, or (at your option) any later version, or
#
# b) the "Artistic License".
package ClamTk::GUI;

use strict;
#use warnings;    # disabled upon release
$|++;

use encoding 'utf8';

use ClamTk::App;
use ClamTk::Prefs;
use ClamTk::Results;
use ClamTk::Update;
use ClamTk::Schedule;
use ClamTk::Device;

use Cwd qw/abs_path/;
use File::Basename qw/basename dirname/;
use POSIX qw/locale_h strftime/;
use File::Find::Rule;
use Date::Calc qw/Delta_Days/;
use Locale::gettext;
use File::Copy;
use Digest::MD5 qw/md5_hex/;
use LWP::UserAgent;

textdomain('clamtk');
setlocale( LC_MESSAGES, '' );
bind_textdomain_codeset( 'clamtk', 'UTF-8' );

use Gtk2;
use Glib qw/TRUE FALSE/;

my $window;
my $top_label;

my ( $save_log, $recursive, $hidden, $size_set, $thorough ) = (0) x 5;
my ( @files, @quoted, @new );
my %dirs_scanned;
my $step = 0;    # step for progressbar
my $pb;          # progressbar
my $tt;          # tooltips
my $SCAN;        # scan handle; must be global for stop button
my $scan_pid;    # pid of scan handle; must be global for stop button
my $stopped = 0;
my $scan_option;
my $scan_frame;
my $start_time;
my ( $left_status, $mid_status, $right_status );
my $found;       # stores info about possibly infected files
my ( $found_count, $num_scanned, $num_so_far, $current ) = (0) x 4;

my ( $engine_version_text, $defs_version_text, $last_date_text );
my ($last_infect_date);
my ( $engine_gui_img, $defs_gui_img, $status_gui_img );

my ($scan_status_label);
my $av_remote = '';

my $actions;

my ( @q_files,   @h_files );
my ( $q_label,   $h_label );
my ( $new_slist, $new_hlist );

my $proxy_status_img;

sub start_gui {
    INIT { Gtk2->init; }    ## no critic

    # The main window
    $window = Gtk2::Window->new();
    $window->signal_connect( destroy => sub { Gtk2->main_quit; } );
    $window->set_title( 'ClamTk ' . gettext('Virus Scanner') );
    $window->set_border_width(5);
    $window->set_resizable(FALSE);
    if ( -e '/usr/share/pixmaps/clamtk.png' ) {
        $window->set_default_icon_from_file('/usr/share/pixmaps/clamtk.png');
    } elsif ( -e '/usr/share/pixmaps/clamtk.xpm' ) {
        $window->set_default_icon_from_file('/usr/share/pixmaps/clamtk.xpm');
    }

    my $vbox = Gtk2::VBox->new( FALSE, 0 );
    $window->add($vbox);

    # @entries contain the menubar items
    my @entries = (
        [ 'FileMenu',       undef, gettext('_Scan') ],
        [ 'ViewMenu',       undef, gettext('_View') ],
        [ 'OptionsMenu',    undef, gettext('_Options') ],
        [ 'QuarantineMenu', undef, gettext('_Quarantine') ],
        [ 'AdvancedMenu',   undef, gettext('_Advanced') ],
        [ 'HelpMenu',       undef, gettext('_Help') ],

        # These fall under "Scan", although it's still called FileMenu
        [   'Scan_File',        'gtk-copy',
            gettext('A _File'), '<control>F',
            gettext('Scan a file'), sub { ClamTk::GUI->getfile('file') }
        ],
        [   'Quick_Home',
            'gtk-go-down',
            gettext('Home (_Quick)'),
            '<control>Q',
            gettext('Quick Home Scan'),
            sub { ClamTk::GUI->getfile('home') }
        ],
        [   'Full_Home',
            'gtk-goto-bottom',
            gettext('Home (Recursive)'),
            '<control>Z',
            gettext('Full Home Scan'),
            sub { ClamTk::GUI->getfile('full-home') }
        ],
        [   'Scan_Directory',
            'gtk-zoom-in',
            gettext('A _Directory'),
            '<control>D',
            gettext('Scan a Directory'),
            sub { ClamTk::GUI->getfile('dir') }
        ],
        [   'Scan_Device',
            'gtk-cdrom',
            gettext('A Device'),
            '<control>J',
            gettext('Scan a Device'),
            sub { ClamTk::Device->look_for_device() }
        ],
        [   'Recursive_Scan',
            'gtk-zoom-fit',
            gettext('_Recursive Scan'),
            '<control>R',
            gettext('Recursively scan a directory'),
            sub { ClamTk::GUI->getfile('recur') }
        ],
        [   'Exit',           'gtk-quit',
            gettext('E_xit'), '<control>X',
            gettext('Quit this program'), sub { Gtk2->main_quit }
        ],

        # These fall under View
        [   'ManageHistories',
            'gtk-index',
            gettext('Manage _Histories'),
            "<control>H",
            gettext('Select Histories to Delete'),
            sub { history('delete') },
            FALSE
        ],
        [   'ClearOutput',                'gtk-clear',
            gettext('Clear _Output'),     '<control>O',
            gettext('Clear the Display'), \&clear_output,
            FALSE
        ],

        # These fall under Quarantine
        [   'Status', 'gtk-edit', gettext('_Status'), '<control>S',
            gettext('See how many files are quarantined'),
            \&quarantine_check
        ],
        [   'Maintenance',
            'gtk-preferences',
            gettext('_Maintenance'),
            '<control>M',
            gettext('View files that have been quarantined'),
            \&maintenance,
        ],
        [   'Empty',
            'gtk-delete',
            gettext('_Empty Quarantine Folder'),
            '<control>E',
            gettext('Delete all files that have been quarantined'),
            \&del_quarantined
        ],

        # These fall under Help
        [   'UpdateSig',
            'gtk-network',
            gettext('_Check for updates'),
            '<control>U',
            gettext('Check for updates'),
            sub { ClamTk::Update->update_dialog() }
        ],
        [   'About',                          'gtk-about',
            gettext("_About"),                '<control>A',
            gettext('About this program...'), \&about
        ],

        # The final three here are for Advanced
        [   'Scheduler',
            'gtk-media-forward',
            gettext('_Scheduler'),
            '<control>T',
            gettext('Schedule a daily scan'),
            sub { ClamTk::Schedule->schedule_dialog() }
        ],
        [   "AVsetup",
            'gtk-refresh',
            gettext('Rerun AV setup _wizard'),
            '<control>W',
            gettext('Rerun AV setup wizard'),
            sub { ClamTk::Update->av_db_select() }
        ],
        [   'Preferences',          'gtk-properties',
            gettext('Preferences'), '<control>P',
            gettext('Preferences'), \&preferences,
            FALSE
        ],
    );

    # $ui_info dictates where the options fall
    my $ui_info = "<ui>
        <menubar name='MenuBar'>
         <menu action='FileMenu'>
          <menuitem action='Scan_File'/>
          <menuitem action='Scan_Directory'/>
          <menuitem action='Scan_Device'/>
          <menuitem action='Recursive_Scan'/>
          <menuitem action='Quick_Home'/>
          <menuitem action='Full_Home'/>
          <separator/>
          <menuitem action='Exit'/>
         </menu>
          <menu action='ViewMenu'>
          <menuitem action='ManageHistories'/>
          <menuitem action='ClearOutput'/>
         </menu>
         <menu action='QuarantineMenu'>
          <menuitem action='Status'/>
          <menuitem action='Maintenance'/>
          <menuitem action='Empty'/>
         </menu>
         <menu action='AdvancedMenu'>
           <menuitem action='Scheduler'/>
           <menuitem action='AVsetup'/>
           <menuitem action='Preferences'/>
 	 </menu>
         <menu action='HelpMenu'>
          <menuitem action='UpdateSig'/>
          <menuitem action='About'/>
         </menu>
        </menubar>
</ui>";

    $actions = Gtk2::ActionGroup->new('Actions');
    $actions->add_actions( \@entries, undef );

    my $ui = Gtk2::UIManager->new;
    $ui->insert_action_group( $actions, 0 );

    $window->add_accel_group( $ui->get_accel_group );
    $ui->add_ui_from_string($ui_info);
    $vbox->pack_start( $ui->get_widget('/MenuBar'), FALSE, FALSE, 0 );

    # We have to declare this now for items in the Actions frame
    $tt = Gtk2::Tooltips->new();

    $top_label = Gtk2::Label->new;
    $vbox->pack_start( $top_label, FALSE, FALSE, 5 );

##########################################################
## Actions Frame 					##
## These are for easy access to buttons for scanning 	##
##########################################################

    my $gui_frame = Gtk2::Frame->new( gettext('Actions') );
    $vbox->pack_start( $gui_frame, FALSE, FALSE, 0 );

    my $gui_box = Gtk2::VBox->new();
    $gui_frame->add($gui_box);

    my $bbox = Gtk2::HButtonBox->new;
    $gui_box->pack_start( $bbox, TRUE, TRUE, 0 );

    # Using gtk2's icons rather than including our own, we
    # can keep the package smaller. The good news is that we can
    # borrow the icon and customize its associated text.

    # Button for quick home folder scan
    my $home_img = Gtk2::Image->new_from_stock( 'gtk-home', 'small-toolbar' );
    my $home_btn = Gtk2::Button->new();
    $home_btn->signal_connect( clicked => sub { ClamTk::GUI->getfile('home') }
    );
    $home_btn->set_property( 'image' => $home_img );
    $home_btn->set_label( gettext('Home') );
    $tt->set_tip( $home_btn, gettext('Scan your home directory') );
    $bbox->add($home_btn);

    # Button for scanning just a file
    my $file_img = Gtk2::Image->new_from_stock( 'gtk-copy', 'small-toolbar' );
    my $file_btn = Gtk2::Button->new();
    $file_btn->signal_connect( clicked => sub { ClamTk::GUI->getfile('file') }
    );
    $file_btn->set_property( 'image' => $file_img );
    $file_btn->set_label( gettext('File') );
    $tt->set_tip( $file_btn, gettext('Scan a file') );
    $bbox->add($file_btn);

    # Button for scanning a directory
    my $dir_img =
        Gtk2::Image->new_from_stock( 'gtk-directory', 'small-toolbar' );
    my $dir_btn = Gtk2::Button->new();
    $dir_btn->signal_connect( clicked => sub { ClamTk::GUI->getfile('dir') }
    );
    $dir_btn->set_property( 'image' => $dir_img );
    $dir_btn->set_label( gettext('Directory') );
    $tt->set_tip( $dir_btn, gettext('Scan a directory') );
    $bbox->add($dir_btn);

    # Button to exit
    my $quit_img = Gtk2::Image->new_from_stock( 'gtk-quit', 'small-toolbar' );
    my $quit_btn = Gtk2::Button->new();
    $quit_btn->set_property( 'image' => $quit_img );
    $quit_btn->set_label( gettext('Exit') );
    $quit_btn->signal_connect( clicked => sub { Gtk2->main_quit } );
    $tt->set_tip( $quit_btn, gettext('Exit this program') );
    $bbox->add($quit_btn);

##########################################################
## Status frame 					##
## This shows versions of ClamAV, ClamTk, signatures, 	##
## date of last scan, date of last infected file 	##
##########################################################

    my $status_frame = Gtk2::Frame->new( gettext('Status') );
    $vbox->pack_start( $status_frame, FALSE, FALSE, 0 );

    my $status_table = Gtk2::Table->new( 5, 3, FALSE );
    $status_frame->add($status_table);

    # Version of ClamAV
    my $engine_gui_box = Gtk2::HBox->new( TRUE, 0 );
    my $engine_text = Gtk2::Label->new( gettext('Antivirus engine') );
    $engine_version_text = Gtk2::Label->new( gettext('Unknown') );
    $engine_gui_img =
        Gtk2::Image->new_from_stock( 'gtk-apply', 'small-toolbar' );
    $tt->set_tip( $engine_gui_img, gettext('Current') );
    $engine_gui_box->pack_start( $engine_gui_img,      TRUE, TRUE, 0 );
    $engine_gui_box->pack_start( $engine_text,         TRUE, TRUE, 0 );
    $engine_gui_box->pack_start( $engine_version_text, TRUE, TRUE, 0 );
    $status_table->attach_defaults( $engine_gui_box, 0, 3, 0, 1 );

    # Version of ClamTk
    my $status_gui_box   = Gtk2::HBox->new( TRUE, 0 );
    my $gui_text         = Gtk2::Label->new( gettext('GUI version') );
    my $gui_version_text = Gtk2::Label->new( ClamTk::App->get_TK_version() );
    $status_gui_img =
        Gtk2::Image->new_from_stock( 'gtk-apply', 'small-toolbar' );
    $tt->set_tip( $status_gui_img, gettext('Current') );
    $status_gui_box->pack_start( $status_gui_img,   TRUE, TRUE, 0 );
    $status_gui_box->pack_start( $gui_text,         TRUE, TRUE, 0 );
    $status_gui_box->pack_start( $gui_version_text, TRUE, TRUE, 0 );
    $status_table->attach_defaults( $status_gui_box, 0, 3, 1, 2 );

    # Date of virus signatures
    my $defs_gui_box = Gtk2::HBox->new( TRUE, 0 );
    my $defs_text = Gtk2::Label->new( gettext('Virus definitions') );
    $defs_version_text = Gtk2::Label->new( gettext('Unknown') );
    $defs_gui_img =
        Gtk2::Image->new_from_stock( 'gtk-apply', 'small-toolbar' );
    $tt->set_tip( $defs_gui_img, gettext('Current') );
    $defs_gui_box->pack_start( $defs_gui_img,      TRUE, TRUE, 0 );
    $defs_gui_box->pack_start( $defs_text,         TRUE, TRUE, 0 );
    $defs_gui_box->pack_start( $defs_version_text, TRUE, TRUE, 0 );
    $status_table->attach_defaults( $defs_gui_box, 0, 3, 2, 3 );

    # Last virus scan date
    my $last_gui_box = Gtk2::HBox->new( TRUE, 0 );
    my $last_gui_img =
        Gtk2::Image->new_from_stock( 'gtk-print-preview', 'small-toolbar' );
    my $last_text = Gtk2::Label->new( gettext('Last virus scan') );
    $last_date_text = Gtk2::Label->new( gettext('Unknown') );
    $tt->set_tip( $last_gui_img, gettext('Date of your last virus scan') );
    $last_gui_box->pack_start( $last_gui_img,   TRUE, TRUE, 0 );
    $last_gui_box->pack_start( $last_text,      TRUE, TRUE, 0 );
    $last_gui_box->pack_start( $last_date_text, TRUE, TRUE, 0 );
    $status_table->attach_defaults( $last_gui_box, 0, 3, 3, 4 );

    # Last infected file date
    my $last_infect_box = Gtk2::HBox->new( TRUE, 0 );
    my $last_infect_img =
        Gtk2::Image->new_from_stock( 'gtk-dnd', 'small-toolbar' );
    my $last_infect_text = Gtk2::Label->new( gettext('Last infected file') );
    $last_infect_date = Gtk2::Label->new( gettext('Unknown') );
    $tt->set_tip( $last_infect_img, gettext('Date of last known infection') );
    $last_infect_box->pack_start( $last_infect_img,  TRUE, TRUE, 0 );
    $last_infect_box->pack_start( $last_infect_text, TRUE, TRUE, 0 );
    $last_infect_box->pack_start( $last_infect_date, TRUE, TRUE, 0 );
    $status_table->attach_defaults( $last_infect_box, 0, 3, 4, 5 );

##################################################################
## Scan frame 							##
## This holds the progressbar and keeps track			##
## of files scanned, viruses found and the status/elapsed time 	##
##################################################################

    $scan_frame = Gtk2::Frame->new( gettext('Scan') );
    $vbox->pack_start( $scan_frame, FALSE, FALSE, 0 );

    my $scan_box = Gtk2::VBox->new();
    $scan_frame->add($scan_box);

    $scan_status_label = Gtk2::Label->new('');
    $scan_status_label->set_justify('center');
    $scan_status_label->set_ellipsize('middle');
    $scan_box->pack_start( $scan_status_label, FALSE, FALSE, 0 );

    my $scan_table = Gtk2::Table->new( 1, 2, FALSE );
    $scan_box->pack_start( $scan_table, FALSE, FALSE, 0 );

    $pb = Gtk2::ProgressBar->new;
    $scan_table->attach( $pb, 0, 1, 0, 1, [qw/fill expand/], [qw/shrink/], 0,
        0 );
    $pb->set_fraction(0);

    my $stop_image =
        Gtk2::Image->new_from_stock( 'gtk-stop', 'small-toolbar' );
    my $stop_btn = Gtk2::Button->new();
    $stop_btn->set_property( image => $stop_image );
    $stop_btn->set_relief('none');
    $tt->set_tip( $stop_btn, gettext('Stop scanning now') );
    $scan_table->attach( $stop_btn, 1, 2, 0, 1, [qw/shrink shrink/],
        [qw/shrink/], 0, 0 );
    $stop_btn->signal_connect(
        'clicked' => sub {

            if ( !scalar(@files) ) {
                $scan_frame->hide();
            }
            @quoted = ();
            kill 15, $scan_pid if ($scan_pid);
            $scan_status_label->set_text( gettext('Please wait...') );
            waitpid( $scan_pid, 0 );

            # this close returns the stupid readline() error.
            # not sure how to fix it yet, besides commenting
            # out 'use warnings' :) it's the only way to immediately
            # stop the $SCAN so far...
            close($SCAN);    # or warn "Unable to close scanner! $!\n";
            $scan_status_label->set_text('');
            $stopped = 1;
        }
    );

    my $bottom_box = Gtk2::HBox->new( FALSE, 0 );
    $scan_box->pack_start( $bottom_box, FALSE, FALSE, 0 );

    $left_status = Gtk2::Label->new( gettext('Files Scanned: ') );
    $bottom_box->pack_start( $left_status, TRUE, TRUE, 5 );

    $mid_status = Gtk2::Label->new( gettext('Viruses Found: ') );
    $bottom_box->pack_start( $mid_status, TRUE, TRUE, 5 );

    $right_status = Gtk2::Label->new( gettext('Ready') );
    $bottom_box->pack_start( $right_status, TRUE, TRUE, 5 );

    $window->show_all();
    $scan_frame->hide;

    # Query user's preferences for loading
    load_prefs();

    # The only reason we take @ARGV is for right-click scanning,
    # not arguments (e.g., -v).
    if (@ARGV) {
        update_status_frame();
        if ( -d $ARGV[0] ) {
            my $d = abs_path( $ARGV[0] );
            if ( not $d ) {    # no permissions
                $found->{$found_count}->{name} = $ARGV[0];
                $found->{$found_count}->{status} =
                    gettext('Could not scan (permissions)');
                $found->{$found_count}->{action} = gettext('None');
                $found_count++;
                ClamTk::Results->display( 1, $found );
                clean_up();
            } elsif ( $ARGV[0] =~ m#^/(proc|sys|dev)# ) {
                $found->{$found_count}->{name} = $ARGV[0];
                $found->{$found_count}->{status} =
                    gettext('Directory excluded from scan');
                $found->{$found_count}->{action} = gettext('None');
                $found_count++;
                ClamTk::Results->display( 1, $found );
                clean_up();
            } else {
                ClamTk::GUI->getfile( 'cmd-scan', $d );
            }
        } else {
            my $pos = 0;
            for my $f (@ARGV) {
                $f = abs_path($f);
                if ( $f =~ m#^/(proc|sys|dev)#
                    or not -r $f )
                {
                    splice( @ARGV, $pos, 1 );
                    $pos++;
                }
            }
            ClamTk::GUI->getfile( 'cmd-scan', @ARGV );
        }
    } else {
        update_status_frame();
    }

    # quick check for 'crontab'.
    # probably unnecessary with today's package managers,
    # but it's easy enough to just disable the Scheduler link
    # if it doesn't exist
    my $crontab = qx!which crontab 2>/dev/null!;
    chomp($crontab);
    if ( !$crontab ) {
        warn "crontab not installed - disabling Scheduler\n";
        my $del = $ui->get_widget('/MenuBar/AdvancedMenu/Scheduler');
        $del->set_sensitive(FALSE);
    }

    Gtk2->main();

    return $window;
}

sub update_status_frame {
    # This is useful as its own subroutine so we
    # can call it from anywhere
    $top_label->set_text( gettext('Please wait, initializing...') );
    Gtk2->main_iteration while ( Gtk2->events_pending );

    # ClamAV version
    my ($version) = ClamTk::App->get_AV_version();
    $engine_version_text->set_text($version);

    # Check for latest version
    $av_remote = ClamTk::App->get_AV_remote();

    if ( $av_remote eq 'undef' || $av_remote eq 'unavailable' ) {
        $engine_gui_img->set_from_stock( 'gtk-dialog-question',
            'small-toolbar' );
        $tt->set_tip( $engine_gui_img, gettext('Unable to check') );
        $window->queue_draw;
        Gtk2->main_iteration while ( Gtk2->events_pending );
    } elsif ( ( $version cmp $av_remote ) == -1 ) {
        $engine_gui_img->set_from_stock( 'gtk-dialog-error',
            'small-toolbar' );
        $tt->set_tip( $engine_gui_img,
            gettext('The antivirus engine is reporting an outdated engine') );
        $window->queue_draw;
        Gtk2->main_iteration while ( Gtk2->events_pending );
    } elsif ( ( $version cmp $av_remote ) == 0 ) {
        $engine_gui_img->set_from_stock( 'gtk-apply', 'small-toolbar' );
        $tt->set_tip( $engine_gui_img, gettext('Current') );
        $window->queue_draw;
        Gtk2->main_iteration while ( Gtk2->events_pending );
    } elsif ( ( $version cmp $av_remote ) == 1 ) {    # shouldn't happen
        $engine_gui_img->set_from_stock( 'gtk-apply', 'small-toolbar' );
        $tt->set_tip( $engine_gui_img, gettext('Current') );
        $window->queue_draw;
        Gtk2->main_iteration while ( Gtk2->events_pending );
    } else {
        $engine_gui_img->set_from_stock( 'gtk-dialog-question',
            'small-toolbar' );
        $tt->set_tip( $engine_gui_img, gettext('Unable to check') );
        $window->queue_draw;
        Gtk2->main_iteration while ( Gtk2->events_pending );
    }

    # ClamTk version
    Gtk2->main_iteration while ( Gtk2->events_pending );
    my ($ret) = ClamTk::Update->update_gui('startup');
    if ( $ret == -1 ) {
        $status_gui_img->set_from_stock( 'gtk-dialog-question',
            'small-toolbar' );
        $tt->set_tip( $status_gui_img, gettext('Unable to check') );
    } elsif ( $ret == 1 || $ret == 3 ) {
        $status_gui_img->set_from_stock( 'gtk-apply', 'small-toolbar' );
        $tt->set_tip( $status_gui_img, gettext('Current') );
    } else {
        $status_gui_img->set_from_stock( 'gtk-dialog-error',
            'small-toolbar' );
        $tt->set_tip( $status_gui_img,
            gettext('A newer version is available') );
    }
    Gtk2->main_iteration while ( Gtk2->events_pending );

    # Signatures
    set_sig_status();

    # Last scan
    my $last_scan = ClamTk::Prefs->get_preference('LastScan');
    if ($last_scan) {
        $last_date_text->set_text($last_scan);
    } else {
        $last_date_text->set_text( gettext('Never') );
    }

    # Last infection
    my $last_infection = ClamTk::Prefs->get_preference('LastInfection');
    if ($last_infection) {
        $last_infect_date->set_text($last_infection);
    } else {
        $last_infect_date->set_text( gettext('Never') );
    }
    $top_label->set_text('');
    return;
}

sub set_sig_status {
    if ( !$defs_version_text ) {

        # This may get called from Update.pm directly
        # and so may not yet exist
        $defs_version_text = Gtk2::Label->new;
    }
    if ( !$defs_gui_img ) {

        # This may get called from Update.pm directly
        # and so may not yet exist
        $defs_gui_img = Gtk2::Image->new;
    }
    my $sig_date = ClamTk::App->get_date_sigs();
    if ($sig_date) {
        $defs_version_text->set_text($sig_date);
    } else {
        $defs_version_text->set_text( gettext('None found') );
    }

    my ( $d, $m, $y ) = split / /, $sig_date;
    my $date_ret = date_diff( $d, $m, $y );
    if ( !$tt ) {
        $tt = Gtk2::Tooltips->new();
    }
    if ( $date_ret eq 'outdated' || !$sig_date ) {
        $defs_gui_img->set_from_stock( 'gtk-dialog-error', 'small-toolbar' );
        $tt->set_tip( $defs_gui_img,
            gettext('Your antivirus signatures are out-of-date') );
    } else {
        $defs_gui_img->set_from_stock( 'gtk-apply', 'small-toolbar' );
        $tt->set_tip( $defs_gui_img, 'Current' );
    }
    return;
}

sub set_tk_status {
    shift;    # throw away package name
    my ($ret) = shift;

    if ( $ret == 4 || $ret == 5 ) {
        $status_gui_img->set_from_stock( 'gtk-dialog-question',
            'small-toolbar' );
        $tt->set_tip( $status_gui_img, gettext('Unable to check') );
    } elsif ( $ret == 1 || $ret == 3 ) {
        $status_gui_img->set_from_stock( 'gtk-apply', 'small-toolbar' );
        $tt->set_tip( $status_gui_img, gettext('Current') );
    } else {
        $status_gui_img->set_from_stock( 'gtk-dialog-error',
            'small-toolbar' );
        $tt->set_tip( $status_gui_img,
            gettext('A newer version is available') );
    }
    Gtk2->main_iteration while ( Gtk2->events_pending );
    $window->queue_draw;
    return;
}

sub date_diff {
    my ( $day2, $month2, $year2 ) = @_;
    my ( $day1, $month1, $year1 ) = split / /,
        strftime( '%d %m %Y', localtime );
    my %months = (
        'Jan' => 1,
        'Feb' => 2,
        'Mar' => 3,
        'Apr' => 4,
        'May' => 5,
        'Jun' => 6,
        'Jul' => 7,
        'Aug' => 8,
        'Sep' => 9,
        'Oct' => 10,
        'Nov' => 11,
        'Dec' => 12,
    );
    return unless ( $day2 && $month2 && $year2 );

    my $diff =
        Delta_Days( $year1, $month1, $day1, $year2, $months{$month2}, $day2 );
    $diff *= -1;

    # A week or more is outdated
    if ( $diff >= 7 ) {
        return 'outdated';
    } else {
        return 'current';
    }
}

sub getfile {
    shift;
    my ($option) = shift;
    my @cmd_input = @_;

    my $paths = ClamTk::App->get_path('all');

    # Don't bother doing anything if clamscan can't be found
    warn "Cannot scan without clamscan!\n" unless $paths->{clampath};
    return unless $paths->{clampath};

    # these lines are for 'thorough' :)
    # If it's selected, we add detection for both
    # potentially unwanted applications and broken executables.
    if ($thorough) {
        $paths->{clamscan} .= ' --detect-pua --detect-broken';
    } elsif ( !$thorough ) {
        $paths->{clamscan} =~ s/\s--detect-pua --detect-broken//;
    }

    $scan_option = $option;

    # $option will be either "home", "full-home", "file", "dir",
    # "recur", "cmd-scan" or "device"
    $pb->set_fraction(0);
    Gtk2->main_iteration while ( Gtk2->events_pending );
    clear_output();
    chdir( $paths->{directory} ) or chdir('/tmp');

    my ( $dir, $dialog );
    Gtk2->main_iteration while ( Gtk2->events_pending );
    my $rule = File::Find::Rule->new;
    $rule->file;
    $rule->exists;
    $rule->readable;
    $rule->maxdepth(1)
        unless ( $option eq 'recur'
        or $option eq 'full-home'
        or $recursive );

    my $title =
          ( $option eq 'file' ) ? gettext('Select File')
        : ( $option eq 'dir' && !$recursive )
        ? gettext('Select a Directory (directory scan)')
        : ( $option eq 'recur' or $recursive )
        ? gettext('Select a Directory (recursive scan)')
        : '';

    if ( $option eq 'home' ) {
        $top_label->set_text( gettext('Please wait...') );
        Gtk2->main_iteration while ( Gtk2->events_pending );
        @files = $rule->in( $paths->{directory} );
    } elsif ( $option eq 'file' ) {
        $dialog = Gtk2::FileChooserDialog->new(
            gettext($title), undef, 'open',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok',
        );
        $dialog->set_select_multiple(TRUE);
        if ( "ok" eq $dialog->run ) {
            $top_label->set_text( gettext('Please wait...') );
            $window->queue_draw;
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $dialog->get_filenames;
            $window->queue_draw;
            $dialog->destroy;
            $window->queue_draw;
            Gtk2->main_iteration while ( Gtk2->events_pending );
        } else {
            $dialog->destroy;
            return;
        }
    } elsif ( $option eq 'device' ) {
        my $dir = $cmd_input[0];
        return if ( not $dir or not -d $dir );
        Gtk2->main_iteration while ( Gtk2->events_pending );
        @files = $rule->in($dir);
        Gtk2->main_iteration while ( Gtk2->events_pending );
    } elsif ( $option eq 'full-home' ) {
        $top_label->set_text( gettext('Please wait...') );
        Gtk2->main_iteration while ( Gtk2->events_pending );
        @files = $rule->in( $paths->{directory} );
    } elsif ( $option eq 'dir' or $option eq 'recur' ) {
        $dialog = Gtk2::FileChooserDialog->new(
            gettext($title), undef,
            'select-folder',
            'gtk-cancel' => 'cancel',
            'gtk-ok'     => 'ok',
        );
        if ( "ok" eq $dialog->run ) {
            $dir = $dialog->get_filename;
            if ( $dir =~ m{^/(proc|sys|dev)}
                or not -r $dir )
            {
                $dialog->destroy;
                $found->{$found_count}->{name} = $dir;
                $found->{$found_count}->{status} =
                    ( not -r $dir )
                    ? gettext('Could not scan (permissions)')
                    : gettext('Will not scan that directory');
                $found->{$found_count}->{action} = gettext('None');
                $found_count++;
                ClamTk::Results->display( 1, $found );
                clean_up();
                return;
            }
            $top_label->set_text( gettext('Please wait...') );
            Gtk2->main_iteration while ( Gtk2->events_pending );
            $window->queue_draw;
            $dialog->destroy;
            $window->queue_draw;
            $dir ||= $paths->{directory};
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $rule->in($dir);
            Gtk2->main_iteration while ( Gtk2->events_pending );
        } else {
            $dialog->destroy;
            return;
        }
    } elsif ( $option eq 'cmd-scan' ) {
        if ( -d $cmd_input[0] ) {
            $top_label->set_text( gettext('Please wait...') );
            $window->queue_draw;

            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = $rule->in( $cmd_input[0] );
        } else {
            $top_label->set_text( gettext('Please wait...') );
            $window->queue_draw;
            Gtk2->main_iteration while ( Gtk2->events_pending );
            @files = @cmd_input;
        }
    } else {
        warn "Unknown option '$option'.\n";
        return;
    }

    # open the scan frame for display
    $scan_frame->show_all();
    Gtk2->main_iteration while ( Gtk2->events_pending );
    $window->queue_draw;
    Gtk2->main_iteration while ( Gtk2->events_pending );

    # start the timer - replaces the "Ready"
    $start_time = time;
    $right_status->set_text( gettext('Elapsed time: ') );
    $top_label->set_text('');

    # reset %$found
    $found = {};

    # only a single file
    if ( $option eq 'file' ) {
        scan(@files);
    } else {
        @files = filter( $option, @files );
        # the pulse of the progressbar
        if ( scalar(@files) > 1 ) {
            $step = 1 / scalar(@files);
        } else {
            $step = 1;
        }

        # By default, 20Mb is the largest we go -
        # unless the preference is to ignore size.
        if ( !$size_set ) {
            my @large;
            foreach my $foo (@files) {
                if ( -s $foo >= 20_000_000 ) {
                    push( @large, $foo );
                } else {
                    push( @new, $foo );
                }
            }

            # These files will be "skipped and assumed clean", which
            # is exactly what the switch --max-filesize does.
            if (@large) {
                foreach my $too_big (@large) {
                    $scan_status_label->set_text(
                        sprintf gettext('Scanning %s...'),
                        dirname($too_big) );
                    $num_scanned++;
                    timer();
                    next;
                }
            }
        } else {
            @new = @files;
        }

        if (@new) {

            # This seems ugly, but there are issues/limits giving
            # arguments to commands. This still adds a pause after each
            # 255 files sent, but works.
            my @send;
            while ( my $t = pop(@new) ) {
                last if ($stopped);
                push( @send, $t );
                if ( scalar(@send) == 255 ) {
                    scan(@send);
                    @send = ();
                } else {
                    next;
                }
            }
            scan(@send) if (@send);
        }
    }
    clean_up();
}

sub filter {
    my $opt    = shift;
    my @filter = @_;

    # remove the hidden files if chosen
    if ( $hidden == 0 && $opt ne 'recur' && $opt ne 'full-home' ) {
        @filter = grep { basename($_) !~ /^\./ } @filter;
    }

    # Attempt to remove dangling symlinks
    my $index = 0;
    while ( $index <= $#filter ) {
        if ( -l $filter[$index] && !-e $filter[$index] ) {
            splice( @filter, $index, 1 );
        } else {
            $index++;
        }
    }

    # Remove whitelisted directories
    # (unless it's a single file - or option eq 'file')
    my $paths = ClamTk::App->get_path('all');

    for my $ignore (
        split(
            /;/,
            ClamTk::Prefs->get_preference('Whitelist')
                . $paths->{whitelist_dir}
        )
        )
    {
        @filter = grep { !m#$ignore# } @filter;
    }
    return @filter;
}

sub scan {
    my @get = @_;
    # We need to quotemeta the filenames
    @quoted = map { quotemeta($_) } @get;
    timer();

    my $paths   = ClamTk::App->get_path('all');
    my $command = $paths->{clamscan};

    # Use the user's sig db if it's selected
    if ( ClamTk::Prefs->get_preference('Update') eq 'single' ) {
        $command .= " --database=$paths->{db}";
    }

    # implicit fork; gives us the PID of clamscan so we can
    # kill it if the user hits the Stop button
    my $pid = open( $SCAN, "-|", "$command @quoted 2>&1" );
    defined($pid) or die "couldn't fork: $!\n";
    my $scan_count = 0;
    $scan_status_label->set_text(
        sprintf gettext('Scanning %s...'),
        $scan_option ne 'file'
        ? dirname( $get[$scan_count] )
        : $get[$scan_count]
    );
    # this is for the 'stop button'
    $scan_pid = $pid;

    Gtk2->main_iteration while Gtk2->events_pending;
    while (<$SCAN>) {
        Gtk2->main_iteration while Gtk2->events_pending;
        if ( $av_remote eq 'undef' && /ClamAV engine is outdated/ ) {
            $engine_gui_img->set_from_stock( 'gtk-dialog-error',
                'small-toolbar' );
            #$engine_version_text->set_text( gettext('Outdated') );
            $tt->set_tip( $engine_gui_img,
                gettext('The antivirus is reporting an outdated engine') );
            $window->queue_draw;
            Gtk2->main_iteration while ( Gtk2->events_pending );
        }
        next if (/^LibClamAV Warning/);

        #my ( $file, $status ) = split /:/;
        my ( $file, $status );
        if (/(.*?): (.*?) FOUND/) {
            $file   = $1;
            $status = $2;
        } elsif (/(.*?): (OK)/) {
            $file   = $1;
            $status = $2;
        }    #else {
             #	warn "something else: file = <$file>, stat = <$status>\n";
             #}

        chomp($file)   if ( defined $file );
        chomp($status) if ( defined $status );

        # Ensure the file is still there (things get moved)
        # and that it got scanned
        next unless ( -e $file && $status );
        next if ( $status =~ /module failure/ );

        $dirs_scanned{ dirname($file) } = 1
            unless ( dirname($file) =~ /\/tmp\/clamav/
            || dirname($file) eq '.' );

        $status =~ s/\s+FOUND$//;

        # do not show files in archives - we just want the end-result.
        # it still scans and we still show the result.
        next if ( $file =~ /\/tmp\/clamav/ );

        timer();
        my $clean_words = join(
            '|',
            # These aren't necessarily clean (despite the scalar name);
            # we just don't want them counted as viruses
            'OK',                          'Zip module failure',
            "RAR module failure",          'Encrypted.RAR',
            'Encrypted.Zip',               'Empty file',
            'Excluded',                    'Input/Output error',
            'Files number limit exceeded', 'handler error',
            'Broken.Executable',           'Oversized.Zip'
        );

        if ( $status !~ /$clean_words/ ) {    # a virus
            $found->{$found_count}->{name}   = $file;
            $found->{$found_count}->{status} = $status;
            $found->{$found_count}->{action} = gettext('None');
            $found_count++;
        }

        $num_so_far = keys %$found;

        # If we have possible threats, highlight it with bold
        if ( $num_so_far > 0 ) {
            $mid_status->set_markup(
                sprintf gettext('<b>Viruses Found: %d</b>'), $num_so_far );
        } else {
            $mid_status->set_text( sprintf gettext('Viruses Found: %d'),
                $num_so_far );
        }
        $num_scanned++;

        # Pulse the progressbar
        if ( $current + $step <= 0 || $current + $step >= 1.0 ) {
            $current = .99;
        } else {
            $current += $step;
        }
        $pb->set_fraction($current);
        $pb->set_text( sprintf gettext('Percent complete: %2d'),
            $current * 100 );
        Gtk2->main_iteration while ( Gtk2->events_pending );

        # Get ready for the next file to be scanned
        # This is important for the next step
        $scan_count++;

        # We do this to show the name of the file being
        # scanned in "real time". Otherwise it shows up late.
        if ( defined( $quoted[$scan_count] ) ) {
            Gtk2->main_iteration while ( Gtk2->events_pending );
            $scan_status_label->set_text(
                sprintf gettext('Scanning %s...'),
                $scan_option ne 'file'
                ? dirname( $get[$scan_count] )
                : basename( $get[$scan_count] )
            );
            Gtk2->main_iteration while ( Gtk2->events_pending );
        }
    }

    # No more files - close the filehandle and end the progressbar
    if ( !@new ) {
        close($SCAN);    # or warn "Unable to close scanner! $!\n";
        $pb->set_text( gettext('Percent complete: 100') );
    }
    Gtk2->main_iteration while ( Gtk2->events_pending );
}

sub clear_output {
    # Return if there are files being scanned
    return if ( scalar(@files) > 0 );
    # Reset the progressbar
    $pb->set_fraction(0);
    # Refresh the main window
    $window->queue_draw;
    # Clear the text
    $top_label->set_text('');
    $scan_status_label->set_text('');
    # Reset the bottom labels
    $left_status->set_text( gettext('Files Scanned: ') );
    $mid_status->set_text( gettext('Viruses Found: ') );
    $right_status->set_text( gettext('Ready') );
    # Hide the scanning frame
    $scan_frame->hide();
    return;
}

sub timer {
    Gtk2->main_iteration while ( Gtk2->events_pending );
    my $now     = time;
    my $seconds = $now - $start_time;
    my $s       = sprintf '%02d', ( $seconds % 60 );
    my $m       = sprintf '%02d', ( $seconds - $s ) / 60;
    $right_status->set_text( sprintf gettext('Elapsed time: %s'), "$m:$s" );
    $left_status->set_text( sprintf gettext('Files Scanned: %d'),
        $num_scanned );
    $window->queue_draw;
    Gtk2->main_iteration while ( Gtk2->events_pending );
    return;
}

sub clean_up {
    # Ensure the progressbar has hit 100%...
    $pb->set_fraction(1.0);
    # ...and remove the text
    $pb->set_text("");

    my $db_total = ClamTk::App->get_num_sigs();
    my $REPORT;    # filehandle for histories log
    my ( $mon, $day, $year ) = split / /, strftime( '%b %d %Y', localtime );

    # Save date of scan
    if ( $num_so_far > 0 ) {
        $last_infect_date->set_text("$day $mon $year");
        ClamTk::Prefs->set_preference( 'LastInfection', "$day $mon $year" );
        $window->queue_draw;
    }

    $last_date_text->set_text("$day $mon $year");
    ClamTk::Prefs->set_preference( 'LastScan', "$day $mon $year" );
    $window->queue_draw;
    Gtk2->main_iteration while ( Gtk2->events_pending );

    if ($save_log) {
        my $paths     = ClamTk::App->get_path('history');
        my $virus_log = $paths . "/" . "$mon-$day-$year" . ".log";

        # sort the directories scanned for display
        my @sorted = sort { $a cmp $b } keys %dirs_scanned;
        if ( open $REPORT, ">>:encoding(UTF-8)", $virus_log ) {
            print $REPORT "-" x 77, "\n";
            print $REPORT "\nClamTk, v", ClamTk::App->get_TK_version(), "\n",
                scalar localtime, "\n";
            print $REPORT sprintf gettext("ClamAV Signatures: %d\n"),
                $db_total;
            print $REPORT gettext("Directories Scanned:\n");
            for my $list (@sorted) {
                print $REPORT "$list\n";
            }
            printf $REPORT gettext(
                "\nFound %d possible %s (%d %s scanned).\n\n"), $num_so_far,
                $num_so_far == 1 ? gettext('virus') : gettext('viruses'),
                $num_scanned,
                $num_scanned == 1 ? gettext('file') : gettext('files');
        } else {
            $scan_status_label->set_text(
                gettext('Could not write to logfile. Check permissions.') );
            $save_log = 0;
        }
    }
    $db_total =~ s/(\w+)\s+$/$1/;
    $scan_status_label->set_text(
        sprintf gettext('Scanning complete (%d signatures)'), $db_total );
    $left_status->set_text( sprintf gettext('Files Scanned: %d'),
        $num_scanned );
    if ( $num_so_far != 0 ) {
        $mid_status->set_text( sprintf gettext('Viruses Found: %d'),
            $num_so_far );
    }
    $right_status->set_text( gettext('Ready') );
    $window->queue_draw;

    if ( $num_so_far == 0 ) {
        print $REPORT gettext("No viruses found.\n") if ($save_log);
    } else {
        if ($save_log) {
            for my $num ( sort keys %$found ) {
                printf $REPORT "%-38s %38s\n",
                    $found->{$num}->{name}, $found->{$num}->{status};
            }
        }
    }
    if ($save_log) {
        print $REPORT '-' x 77, "\n";
        close($REPORT);
    }

    if ($num_so_far) {
        ClamTk::Results->display( 0, $found );
    } elsif ( !keys %$found && $scan_option eq 'cmd-scan' ) {
        for my $f (@files) {
            $found->{$found_count}->{name}   = $f;
            $found->{$found_count}->{status} = gettext('Nothing detected');
            $found->{$found_count}->{action} = gettext('None');
            $found_count++;
        }
        ClamTk::Results->display( 1, $found );
    }

    # reset things
    $num_so_far   = 0;
    $num_scanned  = 0;
    $found_count  = 0;
    %dirs_scanned = ();
    @files        = ();
    @new          = ();
    @quoted       = ();
    $current      = 0;
    $stopped      = 0;
}

sub show_message_dialog {
    my ( $parent, $type, $button, $message ) = @_;

    my $dialog;
    $dialog =
        Gtk2::MessageDialog->new_with_markup( $parent,
        [qw(modal destroy-with-parent)],
        $type, $button, $message );

    $dialog->run;
    $dialog->destroy;
    return;
}

sub maintenance {
    my $main_win = Gtk2::Window->new;
    $main_win->signal_connect( destroy => sub { $main_win->destroy; } );
    $main_win->set_default_size( 250, 200 );
    $main_win->set_title( gettext('Quarantine') );

    my $new_vbox = Gtk2::VBox->new( FALSE, 0 );
    $main_win->add($new_vbox);

    my $paths = ClamTk::App->get_path('viruses');
    @q_files = glob "$paths/*";
    my $s_win = Gtk2::ScrolledWindow->new;
    $s_win->set_shadow_type('etched-in');
    $s_win->set_policy( 'automatic', 'automatic' );
    $new_vbox->pack_start( $s_win, TRUE, TRUE, 0 );

    $new_slist = Gtk2::SimpleList->new( gettext('File') => 'text', );
    $s_win->add($new_slist);

    my $new_hbox = Gtk2::HButtonBox->new;
    $new_vbox->pack_start( $new_hbox, TRUE, TRUE, 0 );

    my $pos_quit = Gtk2::Button->new_with_label( gettext('Close Window') );
    $new_hbox->add($pos_quit);
    $pos_quit->signal_connect( clicked => sub { $main_win->destroy } );
    my $restore = Gtk2::Button->new_with_label( gettext('Restore') );
    $new_hbox->add($restore);
    $restore->signal_connect(
        clicked => \&restore,
        'false_pos'
    );
    my $del_pos = Gtk2::Button->new_with_label( gettext('Delete') );
    $new_hbox->add($del_pos);
    $del_pos->signal_connect( clicked => \&main_del_pos, 'false_pos' );

    $q_label = Gtk2::Label->new();
    $new_vbox->pack_start( $q_label, FALSE, FALSE, 0 );

    for my $opt (@q_files) {
        push @{ $new_slist->{data} }, basename($opt);
    }
    $main_win->set_position('mouse');
    $main_win->show_all;
}

sub restore {
    my @sel = $new_slist->get_selected_indices;
    return if ( !@sel );

    my $paths = ClamTk::App->get_path('all');

    my $deref     = $sel[0];
    my $data      = $new_slist->{data}[$deref];
    my $top_dir   = $paths->{viruses} .= "/";
    my $full_path = $top_dir .= $data->[0];
    return if ( not exists $data->[0] );

    my $ctx = Digest::MD5->new;
    open( my $F, '<', $full_path ) or do {
        show_message_dialog( $window, 'warning', 'ok',
            'Cannot open ' . $full_path . ": $!\n" );
        return;
    };
    $ctx->addfile(*$F);
    my $md5 = $ctx->hexdigest;
    close($F);

    my ( $path, $perm ) = ClamTk::Prefs->restore( $md5, 'exists' );
    if ( !$path ) {
        show_message_dialog( $window, 'warning', 'ok',
            "Problems opening restore file: $!\n" );
        return;
    }

    # By default, the final destination will be $HOME
    my $final_destination = $paths->{directory};

    if ($path) {
        if ( -e $path ) {    # another file already exists there
            $final_destination = $path .= '.restore';
        } else {
            $final_destination = $path;
        }
    } else {
        $final_destination = $paths->{directory} . "/" . $data->[0];
    }

    system( 'mv', $full_path, $final_destination ) == 0 or do {
        show_message_dialog( $window, 'warning', 'ok',
            "Couldn't move file to $final_destination: $!\n" );
        return;
    };

    # Strip .VIRUS extension if it exists
    my $new = $final_destination;
    $new =~ s/\.VIRUS$//;
    rename( $final_destination, $new ) or do {
        show_message_dialog( $window, 'warning', 'ok',
            "Couldn't rename $final_destination to $new: $!\n" );
    };

    # If we obtained permissions, apply them; or, 644 by default. (?)
    # Unless someone has a better idea for default perms.
    $perm ||= 644;
    chmod oct($perm), $new;

    if ( -e $full_path ) {
        show_message_dialog( $window, 'error', 'ok',
            gettext('Operation failed.') );
        return;
    }

    # Update restore file: remove the file from it
    ClamTk::Prefs->restore( $md5, 'remove' );

    # Remove the file from view
    splice @{ $new_slist->{data} }, $deref, 1;

    my $msg = sprintf gettext('Restored as %s.'), $new;
    show_message_dialog( $window, 'info', 'ok', $msg );

    # Get a fresh listing of quarantined files
    @q_files = glob "$paths->{viruses}/*";
}

sub main_del_pos {
    my @sel = $new_slist->get_selected_indices;
    return if ( !@sel );
    my $deref = $sel[0];
    my $data  = $new_slist->{data}[$deref];

    my $paths = ClamTk::App->get_path('viruses');

    my $top_dir = $paths . "/";
    my $full_path = $top_dir .= $data->[0];
    return if ( !-e $full_path );

    my $ctx = Digest::MD5->new;
    open( my $F, '<', $full_path ) or do {
        show_message_dialog( $window, 'warning', 'ok',
            'Cannot open ' . $full_path . " for md5-ing: $!\n" );
        return;
    };
    $ctx->addfile(*$F);
    my $md5 = $ctx->hexdigest;
    close($F);

    # Update restore file: remove the file from it
    ClamTk::Prefs->restore( $md5, 'remove' );

    # Delete the file
    unlink $full_path or do {
        show_message_dialog( $window, 'warning', 'ok', "Couldn't delete ",
            $data->[0], "\n" );
    };
    if ( -e $full_path ) {
        show_message_dialog( $window, 'error', 'ok',
            gettext('Operation failed.') );
        return;
    }

    # Remove the file from view
    splice @{ $new_slist->{data} }, $deref, 1;

    # Get a fresh listing of files
    @q_files = glob "$paths/*";
}

sub quarantine_check {
    my $paths = ClamTk::App->get_path('viruses');
    if ( !-d $paths ) {
        show_message_dialog( $window, 'error', 'close',
            gettext('No virus directory available.') );
        return;
    }
    my @trash;
    unless ( opendir( DIR, $paths ) ) {
        show_message_dialog( $window, 'error', 'close',
            gettext('Unable to open the virus directory.') );
        return;
    }
    @trash = grep { -f "$paths/$_" } readdir(DIR);
    closedir(DIR);
    my $del = scalar(@trash);
    if ( !$del ) {
        show_message_dialog( $window, 'info', 'ok',
            gettext('No items currently quarantined.') );
    } else {
        my $notice = sprintf gettext('%d item(s) currently quarantined.'),
            $del;
        show_message_dialog( $window, 'info', 'ok', $notice );
    }
}

sub del_quarantined {
    my $paths = ClamTk::App->get_path('viruses');
    unless ( -e $paths ) {
        show_message_dialog( $window, 'error', 'close',
            gettext('There is no quarantine directory to empty.') );
        return;
    } else {
        my $confirm_message = gettext('Really delete all quarantined files?');
        my $confirm =
            Gtk2::MessageDialog->new( $window,
            [qw(modal destroy-with-parent)],
            'question', 'ok-cancel', $confirm_message );

        if ( 'cancel' eq $confirm->run ) {
            $confirm->destroy;
            return;
        } else {
            $confirm->destroy;
            my @trash;
            unless ( opendir( DIR, $paths ) ) {
                show_message_dialog( $window, 'error', 'close',
                    gettext('Unable to open the virus directory.') );
                return;
            }
            @trash = grep { -f "$paths/$_" } readdir(DIR);
            closedir(DIR);
            if ( scalar(@trash) == 0 ) {
                show_message_dialog( $window, 'info', 'close',
                    gettext('There are no quarantined items to delete.') );
            } else {
                my $del = 0;
                foreach my $f (@trash) {
                    my $full_path = $paths . "/";
                    $full_path .= $f;
                    my $ctx = Digest::MD5->new;
                    open( my $F, '<', $full_path ) or do {
                        show_message_dialog( $window, 'warning', 'ok',
                                  'cannot open '
                                . $full_path
                                . " for md5-ing: $!\n" );
                        next;
                    };
                    $ctx->addfile(*$F);
                    my $md5 = $ctx->hexdigest;
                    close($F);

                    # update restore file: remove the file from it
                    # if it exists there
                    ClamTk::Prefs->restore( $md5, 'remove' );
                    unlink $full_path and $del++;
                }
                my $notice = sprintf gettext('Removed %d item(s).'), $del;
                show_message_dialog( $window, 'info', 'close', $notice );
            }
        }
    }
}

sub history {
    my $paths = ClamTk::App->get_path('history');
    @h_files = glob "$paths/*.log";
    if ( scalar(@h_files) > 1 ) {
        @h_files = history_sort(@h_files);
    }
    my $new_win = Gtk2::Window->new;
    $new_win->signal_connect( destroy => sub { $new_win->destroy } );
    $new_win->set_default_size( 260, 200 );
    $new_win->set_title( gettext('Scanning Histories') );

    my $top_dir = $paths . "/";
    my $sortnum = 0;              # 0 = asc, 1 = desc

    my $new_vbox = Gtk2::VBox->new;
    $new_win->add($new_vbox);

    my $s_win = Gtk2::ScrolledWindow->new;
    $s_win->set_shadow_type('etched-in');
    $s_win->set_policy( 'automatic', 'automatic' );
    $new_vbox->pack_start( $s_win, TRUE, TRUE, 0 );

    $new_hlist = Gtk2::SimpleList->new( gettext('Histories') => 'text', );
    $s_win->add($new_hlist);
    $new_hlist->set_headers_clickable(TRUE);
    $new_hlist->get_column(0)->signal_connect(
        clicked => sub {
            $sortnum ^= 1;
            splice @{ $new_hlist->{data} }, 0, scalar(@h_files);
            my %cache;
            my @sort_this;
            if ( !$sortnum ) {
                @sort_this = sort {
                    ( $cache{$a} ||= -M $a ) <=> ( $cache{$b} ||= -M $b )
                } @h_files;
            } else {
                @sort_this = sort {
                    ( $cache{$b} ||= -M $b ) <=> ( $cache{$a} ||= -M $a )
                } @h_files;
            }
            for my $t (@sort_this) {
                $t = basename($t);
            }
            push @{ $new_hlist->{data} }, @sort_this;
        }
    );

    my $new_hbox = Gtk2::HButtonBox->new;
    $new_vbox->pack_start( $new_hbox, FALSE, FALSE, 0 );

    my $hist_view = Gtk2::Button->new_with_label( gettext('View') );
    $new_hbox->add($hist_view);
    $hist_view->signal_connect( clicked => \&view_box, 'viewer' );

    my $pos_quit = Gtk2::Button->new_with_label( gettext('Close Window') );
    $new_hbox->add($pos_quit);
    $pos_quit->signal_connect( clicked => sub { $new_win->destroy } );
    my $del_single = Gtk2::Button->new_with_label( gettext('Delete') );
    $new_hbox->add($del_single);
    $del_single->signal_connect(
        clicked => \&history_del_single,
        'del_single'
    );
    my $del_all = Gtk2::Button->new_with_label( gettext('Delete All') );
    $new_hbox->add($del_all);
    $del_all->signal_connect(
        clicked => \&history_del_all,
        'del_all'
    );

    $h_label = Gtk2::Label->new();
    $new_vbox->pack_start( $h_label, FALSE, FALSE, 2 );

    for my $opt (@h_files) {
        push @{ $new_hlist->{data} }, basename($opt);
    }
    $new_win->set_position('mouse');
    $new_win->show_all;
    return;
}

sub history_sort {
    my %or_cache;
    return
        sort { ( $or_cache{$a} ||= -M $a ) <=> ( $or_cache{$b} ||= -M $b ) }
        @_;
}

sub history_del_single {
    my @sel = $new_hlist->get_selected_indices;
    return if ( !@sel );
    my $deref = $sel[0];
    my $data  = $new_hlist->{data}[$deref];

    my $history = ClamTk::App->get_path('history');

    my $top_dir   = $history . "/";
    my $full_path = $top_dir . $data->[0];
    return if ( !-e $full_path );

    unlink $full_path or do {
        show_message_dialog( $window, 'warning', 'ok',
            "Could not delete " . $data->[0] . "\n" );
    };
    if ( -e $full_path ) {
        my $notice = sprintf gettext('Unable to delete %s!'), $data->[0];
        show_message_dialog( $window, 'error', 'ok', $notice );
        return;
    }
    splice @{ $new_hlist->{data} }, $deref, 1;
    @h_files = glob "$history/*";
    return;
}

sub history_del_all {
    return unless (@h_files);
    my $paths = ClamTk::App->get_path('history');
    @h_files = glob "$paths/*";
    my $confirm_message = gettext('Really delete all history logs?');
    my $confirm =
        Gtk2::MessageDialog->new( $window, [qw(modal destroy-with-parent)],
        'question', 'ok-cancel', $confirm_message );

    if ( 'cancel' eq $confirm->run ) {
        $confirm->destroy;
        return;
    } else {
        $confirm->destroy;
        my @not_del;
        my $size = @h_files;
        foreach (@h_files) {
            unlink($_) or push( @not_del, $_ );
        }
        if ( scalar(@not_del) >= 1 ) {
            my $msg = sprintf gettext('Could not delete files: %s!'),
                @not_del;
            show_message_dialog( $window, 'warning', 'ok', $msg );
        } else {
            show_message_dialog( $window, 'info', 'ok',
                gettext('Successfully removed history logs.') );
        }
        splice @{ $new_hlist->{data} }, 0, $size;
        @h_files = glob "$paths/*";
    }
    return;
}

sub view_box {
    my @sel = $new_hlist->get_selected_indices;
    return if ( !@sel );

    my $paths = ClamTk::App->get_path('history');

    my $deref     = $sel[0];
    my $data      = $new_hlist->{data}[$deref];
    my $full_path = $paths . "/" . $data->[0];
    return if ( !-e $full_path );

    my $view_win =
        Gtk2::Dialog->new( sprintf( gettext('Viewing %s'), $data->[0] ),
        undef, [], 'gtk-close' => 'close' );
    $view_win->set_default_response('close');
    $view_win->signal_connect( response => sub { $view_win->destroy } );
    $view_win->set_default_size( 600, 350 );

    my $textview = Gtk2::TextView->new;
    $textview->set( editable => FALSE );

    my $FILE;    # filehandle for histories log
    unless ( open( $FILE, "<:encoding(UTF-8)", $full_path ) ) {
        my $notice = sprintf gettext('Problems opening %s...'), $data->[0];
        show_message_dialog( $window, 'error', 'ok', $notice );
        return;
    }
    my $text;
    $text = do {
        local $/ = undef;
        $text = <$FILE>;
    };
    close($FILE)
        or warn sprintf gettext("Unable to close FILE %s! %s\n"),
        $data->[0];

    my $textbuffer = $textview->get_buffer;
    $textbuffer->create_tag( 'mono', family => 'Monospace' );
    $textbuffer->insert_with_tags_by_name( $textbuffer->get_start_iter, $text,
        'mono' );

    my $scroll_win = Gtk2::ScrolledWindow->new;
    $scroll_win->set_border_width(5);
    $scroll_win->set_shadow_type('etched-in');
    $scroll_win->set_policy( 'automatic', 'automatic' );

    $view_win->vbox->pack_start( $scroll_win, TRUE, TRUE, 0 );

    $scroll_win->add($textview);
    $view_win->show_all();
    return;
}

sub load_prefs {
    my %prefs = ClamTk::Prefs->get_all_prefs();
    return unless %prefs;

    if ( $prefs{SaveToLog} ) {
        $save_log = 1;
    }
    if ( $prefs{ScanHidden} ) {
        $hidden = 1;
    }
    if ( $prefs{SizeLimit} ) {
        $size_set = 1;
    }
    if ( $prefs{Thorough} ) {
        $thorough = 1;
    }
    if ( $prefs{Recursive} ) {
        $recursive = 1;
    }
    return;
}

sub preferences {
    my %prefs = ClamTk::Prefs->get_all_prefs();

    my $pref = Gtk2::Window->new();
    $pref->signal_connect( destroy => sub { $pref->destroy } );
    $pref->set_title( gettext('Preferences') );
    $pref->set_border_width(5);
    $pref->set_position('center-always');

    my $box = Gtk2::VBox->new( FALSE, 0 );
    $pref->add($box);
    $box->set_size_request( 530, 300 );

    my $nb = Gtk2::Notebook->new;
    $nb->set_scrollable(TRUE);
    $nb->set_show_border(TRUE);

    my $scan_table = Gtk2::Table->new( 5, 1, TRUE );
    $nb->insert_page( $scan_table, gettext('Scanning Preferences'), 0 );

    my $hidden_box = Gtk2::CheckButton->new_with_label(
        gettext('Scan files beginning with a dot (.*)') );
    $hidden_box->set_active(TRUE) if ( $prefs{ScanHidden} );
    $scan_table->attach_defaults( $hidden_box, 0, 1, 0, 1 );
    $hidden_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'ScanHidden',
                $hidden_box->get_active ? 1 : 0 );
            $hidden ^= 1;
        }
    );

    my $recur_box = Gtk2::CheckButton->new_with_label(
        gettext('Scan all files and directories within a directory') );
    $recur_box->set_active(TRUE) if ( $prefs{Recursive} );
    $scan_table->attach_defaults( $recur_box, 0, 1, 1, 2 );
    $recur_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'Recursive',
                $recur_box->get_active ? 1 : 0 );
            $recursive ^= 1;
        }
    );

    my $deep_box = Gtk2::CheckButton->new_with_label(
        gettext('Enable extra scan settings') );
    $deep_box->set_active(TRUE) if ( $prefs{Thorough} );
    $scan_table->attach_defaults( $deep_box, 0, 1, 2, 3 );
    $deep_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'Thorough',
                $deep_box->get_active ? 1 : 0 );
            $thorough ^= 1;
        }
    );

    my $size_box = Gtk2::CheckButton->new_with_label(
        gettext('Scan files larger than 20 MB') );
    $size_box->set_active(TRUE) if ( $prefs{SizeLimit} );
    $scan_table->attach_defaults( $size_box, 0, 1, 3, 4 );
    $size_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'SizeLimit',
                $size_box->get_active ? 1 : 0 );
            $size_set ^= 1;
        }
    );

    my $log_box = Gtk2::CheckButton->new_with_label(
        gettext('Keep a record of this scan') );
    $log_box->set_active(TRUE) if ( $prefs{SaveToLog} );
    $scan_table->attach_defaults( $log_box, 0, 1, 4, 5 );
    $log_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'SaveToLog',
                $log_box->get_active ? 1 : 0 );
            $save_log ^= 1;
        }
    );

    my $startup_table = Gtk2::Table->new( 3, 1, TRUE );
    $nb->insert_page( $startup_table, gettext('Startup Preferences'), 1 );

    my $av_box = Gtk2::CheckButton->new_with_label(
        gettext('Check for AV Engine updates') );
    $av_box->set_active(TRUE) if ( $prefs{AVCheck} );
    $startup_table->attach_defaults( $av_box, 0, 1, 0, 1 );
    $av_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'AVCheck',
                $av_box->get_active ? 1 : 0 );
        }
    );

    my $gui_box =
        Gtk2::CheckButton->new_with_label( gettext('Check for GUI updates') );
    $gui_box->set_active(TRUE) if ( $prefs{GUICheck} );
    $startup_table->attach_defaults( $gui_box, 0, 1, 1, 2 );
    $gui_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'GUICheck',
                $gui_box->get_active ? 1 : 0 );
        }
    );

    my $dns_box =
        Gtk2::CheckButton->new_with_label( gettext('Use OpenDNS servers') );
    $dns_box->set_active(TRUE) if ( $prefs{OpenDNS} );
    $startup_table->attach_defaults( $dns_box, 0, 1, 2, 3 );
    $dns_box->signal_connect(
        toggled => sub {
            ClamTk::Prefs->set_preference( 'OpenDNS',
                $dns_box->get_active ? 1 : 0 );
        }
    );

    my $whitebox = Gtk2::VBox->new;
    $nb->insert_page( $whitebox, gettext('Whitelist'), 2 );

    # First, we'll need what the user already has set:
    my $user_whitelist   = ClamTk::Prefs->get_preference('Whitelist');
    my $system_whitelist = ClamTk::App->get_path('whitelist_dir');

    my $s_win = Gtk2::ScrolledWindow->new;
    $s_win->set_shadow_type('etched-in');
    $s_win->set_policy( 'automatic', 'automatic' );
    $whitebox->pack_start( $s_win, TRUE, TRUE, 0 );

    my $ex_slist = Gtk2::SimpleList->new( gettext('Directory') => 'text', );
    $s_win->add($ex_slist);
    push @{ $ex_slist->{data} }, $_ for split /;/, $user_whitelist;

    my $ex_hbox = Gtk2::HButtonBox->new;
    $whitebox->pack_start( $ex_hbox, TRUE, TRUE, 0 );
    $ex_hbox->set_layout('end');

    my $add_btn = Gtk2::Button->new_from_stock('gtk-add');
    $ex_hbox->add($add_btn);
    $add_btn->signal_connect(
        clicked => sub {
            my $dir    = '';
            my $dialog = Gtk2::FileChooserDialog->new(
                gettext('Select a Directory'), undef,
                'select-folder',
                'gtk-cancel' => 'cancel',
                'gtk-ok'     => 'ok',
            );
            if ( "ok" eq $dialog->run ) {
                $dir = $dialog->get_filename;
                if ( $dir eq "/" ) {
                    # Just in case someone clicks the root (/).
                    $dialog->destroy;
                    return;
                }
                # See if it's already included...
                if ( !grep {/^$dir$/} split /;/,
                    $user_whitelist . $system_whitelist )
                {
                    # If not, add to GUI...
                    push @{ $ex_slist->{data} }, $dir;

                    # then add to user's prefs...
                    ClamTk::Prefs->set_preference( 'Whitelist',
                        $user_whitelist . "$dir;" );

                    # ...and refresh the whitelist
                    $user_whitelist =
                        ClamTk::Prefs->get_preference('Whitelist');
                }
            }
            $dialog->destroy;
        }
    );

    my $del_btn = Gtk2::Button->new_from_stock('gtk-delete');
    $ex_hbox->add($del_btn);
    $del_btn->signal_connect(
        clicked => sub {
            my @sel = $ex_slist->get_selected_indices;
            return unless (@sel);
            my $deref  = $sel[0];
            my $remove = $ex_slist->{data}[$deref][0] . ';';

            # refresh our whitelist
            $user_whitelist = ClamTk::Prefs->get_preference('Whitelist');

            # yank the selected from the whitelist
            $user_whitelist =~ s/$remove//;

            # save the whitelist
            ClamTk::Prefs->set_preference( 'Whitelist', $user_whitelist );

            # remove it from the GUI
            splice @{ $ex_slist->{data} }, $deref, 1;
        }
    );

    my $big_proxy = Gtk2::VBox->new( FALSE, 5 );
    $nb->insert_page( $big_proxy, gettext('Proxy settings'), 3 );

    my $no_proxy_box = Gtk2::VBox->new;
    $big_proxy->pack_start( $no_proxy_box, FALSE, FALSE, 0 );
    my $proxy_btn = Gtk2::RadioButton->new( undef, gettext('No Proxy') );
    $no_proxy_box->pack_start( $proxy_btn, FALSE, FALSE, 0 );

    my $sys_proxy_box = Gtk2::VBox->new;
    $big_proxy->pack_start( $sys_proxy_box, FALSE, FALSE, 0 );
    my $sys_btn =
        Gtk2::RadioButton->new( $proxy_btn, gettext('Environment settings') );
    $sys_proxy_box->pack_start( $sys_btn, FALSE, FALSE, 0 );

    my $manual_proxy_box = Gtk2::VBox->new;
    $big_proxy->pack_start( $manual_proxy_box, FALSE, FALSE, 0 );
    my $man_btn =
        Gtk2::RadioButton->new( $proxy_btn, gettext('Set manually') );
    $manual_proxy_box->pack_start( $man_btn, FALSE, FALSE, 0 );

    my $man_table = Gtk2::Table->new( 3, 3, TRUE );
    $manual_proxy_box->pack_start( $man_table, FALSE, FALSE, 0 );
    $man_table->set_row_spacings(5);

    my $ip_label   = Gtk2::Label->new( gettext('IP address or host:') );
    my $ip_example = Gtk2::Label->new('(e.g., proxy.domain.com)');

    # We can't go with max_length(IP address) because they
    # can insert a hostname here as well. 63 should be enough for anyone.
    my $ip_box = Gtk2::Entry->new_with_max_length(63);
    $ip_box->signal_connect(
        'insert-text' => sub {
            my ( $widget, $string, $position ) = @_;

            # http://www.ietf.org/rfc/rfc1738.txt
            if ( $string !~ m#[a-zA-Z0-9\.\-\+/:\@\#]# ) {
                $ip_box->signal_stop_emission_by_name('insert-text');
            }
            return;
        }
    );

    my $port_label   = Gtk2::Label->new( gettext('Port:') );
    my $port_example = Gtk2::Label->new('(e.g., 8080)');
    my $port_box     = Gtk2::SpinButton->new_with_range( 1, 65535, 1 );
    $port_box->set_value(8080);

    $man_table->attach_defaults( $ip_label,   0, 1, 0, 1 );
    $man_table->attach_defaults( $ip_example, 1, 2, 0, 1 );
    $man_table->attach( $ip_box, 2, 3, 0, 1, [qw/expand expand/],
        [qw/shrink/], 0, 0 );

    $man_table->attach_defaults( $port_label,   0, 1, 1, 2 );
    $man_table->attach_defaults( $port_example, 1, 2, 1, 2 );
    $man_table->attach( $port_box, 2, 3, 1, 2, [qw/shrink shrink/],
        [qw/shrink/], 0, 0 );

    $proxy_btn->signal_connect(
        toggled => sub {
            $ip_box->set_sensitive(FALSE)   if ( $proxy_btn->get_active );
            $port_box->set_sensitive(FALSE) if ( $proxy_btn->get_active );
        }
    );
    $sys_btn->signal_connect(
        toggled => sub {
            $ip_box->set_sensitive(FALSE)   if ( $sys_btn->get_active );
            $port_box->set_sensitive(FALSE) if ( $sys_btn->get_active );
        }
    );
    $man_btn->signal_connect(
        toggled => sub {
            if ( $man_btn->get_active ) {
                $ip_box->set_sensitive(TRUE);
                $port_box->set_sensitive(TRUE);
            }
        }
    );

    # proxy_btn_box contains the (status_btn)/apply/clear buttons
    my $proxy_btn_box = Gtk2::HButtonBox->new;
    $big_proxy->pack_start( $proxy_btn_box, FALSE, FALSE, 0 );

    # will be hidden shortly after being added
    $proxy_status_img = Gtk2::Image->new;

    my $proxy_apply_btn = Gtk2::Button->new_from_stock('gtk-apply');
    $proxy_apply_btn->signal_connect(
        clicked => sub {
            my $choice;
            if ( $sys_btn->get_active ) {
                $choice = 1;
            } elsif ( $man_btn->get_active ) {
                $choice = 2;
            } else {
                $choice = 0;
            }
            if ( $choice == 0 || $choice == 1 ) {
                if ( ClamTk::Prefs->set_preference( 'HTTPProxy', $choice ) ) {
                    proxy_non_block_status('yes');
                } else {
                    proxy_non_block_status('no');
                }
            }

            if ( $man_btn->get_active ) {
                if ( length( $ip_box->get_text ) < 1 ) {
                    $proxy_btn->set_active(TRUE);
                    return;
                }
                my $ip = $ip_box->get_text;
                if ( $ip !~ m#://# ) {
                    $ip = 'http://' . $ip;
                }
                my $port = $port_box->get_value_as_int;

                # Hate to pull in LWP::UserAgent just for this,
                # but we need to sanity check it before they get
                # to using it in the first place
                eval {
                    my $ua = LWP::UserAgent->new;
                    $ua->proxy( http => "$ip:$port" );
                };
                if ($@) {
                    proxy_non_block_status('no');
                    $ip_example->set_markup(
                        qq(<span underline="error" underline_color="red">(e.g., proxy.domain.com)</span>)
                    );
                    return;
                }
                $ip_example->set_text('(e.g., proxy.domain.com)');
                if (   ClamTk::Prefs->set_preference( 'HTTPProxy', $choice )
                    && ClamTk::Prefs->set_proxy( $ip, $port ) )
                {
                    proxy_non_block_status('yes');
                    $ip_box->set_text($ip);
                } else {
                    proxy_non_block_status('no');
                    $ip_box->set_text($ip);
                }
            }
        }
    );

    my $proxy_clear_btn = Gtk2::Button->new_from_stock('gtk-clear');
    $proxy_clear_btn->signal_connect(
        clicked => sub {
            $ip_box->set_text('');
            $port_box->set_text('');
            $proxy_btn->set_active(TRUE);
            $ip_example->set_text('(e.g., proxy.domain.com)');
            if ( ClamTk::Prefs->set_preference( 'HTTPProxy', 0 ) ) {
                proxy_non_block_status('yes');
            } else {
                proxy_non_block_status('no');
            }
        }
    );

    $proxy_btn_box->add($proxy_status_img);
    $proxy_btn_box->add($proxy_apply_btn);
    $proxy_btn_box->add($proxy_clear_btn);
    $proxy_btn_box->set_layout('end');

    # What does the user have set?
    # 0 = no proxy, 1 = env_proxy and 2 = manual proxy

    $ip_box->set_sensitive(FALSE);
    $port_box->set_sensitive(FALSE);

    my $setting = ClamTk::Prefs->get_preference('HTTPProxy');
    if ( !$setting ) {
        $proxy_btn->set_active(TRUE);
    } elsif ( $setting == 1 ) {
        $sys_btn->set_active(TRUE);
    } elsif ( $setting == 2 ) {
        $man_btn->set_active(TRUE);
    }
    my $path = ClamTk::App->get_path('db');
    $path .= '/local.conf';
    if ( -f $path ) {
        if ( open( my $FH, '<', $path ) ) {
            while (<$FH>) {
                chomp;
                my $set;
                if (/HTTPProxyServer\s+(.*?)$/) {
                    $set = $1;
                    if ( $set !~ m#://# ) {
                        $set = 'http://' . $set;
                    }
                    $ip_box->set_text($set);
                    if ( !$setting || $setting == 1 ) {
                        $ip_box->set_sensitive(FALSE);
                    }
                }
                if (/HTTPProxyPort\s+(.*?)$/) {
                    $port_box->set_value($1);
                    if ( !$setting || $setting == 1 ) {
                        $port_box->set_sensitive(FALSE);
                    }
                }
            }
            close($FH);
        }
    }

    $box->pack_start( $nb, TRUE, TRUE, 0 );

    # This buttonbox shows up at the bottom of the notebook (nb).
    # Besides the close button, the forward and back buttons allow
    # for fast scrolling through the tabs.
    # What's great is that the way it's written, it will
    # never have to be updated.
    my $buttonbox = Gtk2::HButtonBox->new;
    $box->pack_start( $buttonbox, FALSE, FALSE, 5 );
    $buttonbox->set_layout('end');

    my $prev_btn = Gtk2::Button->new_from_stock('gtk-go-back');
    $buttonbox->add($prev_btn);
    $prev_btn->signal_connect(
        clicked => sub {
            if ( $nb->get_current_page == 0 ) {
                $nb->set_current_page( $nb->get_n_pages - 1 );
            } else {
                $nb->prev_page;
            }
        }
    );

    my $next_btn = Gtk2::Button->new_from_stock('gtk-go-forward');
    $buttonbox->add($next_btn);
    $next_btn->signal_connect(
        clicked => sub {
            if ( $nb->get_current_page == ( $nb->get_n_pages - 1 ) ) {
                $nb->set_current_page(0);
            } else {
                $nb->next_page;
            }
        }
    );

    my $close_btn = Gtk2::Button->new_from_stock('gtk-close');
    $buttonbox->add($close_btn);
    $close_btn->signal_connect( clicked => sub { $pref->destroy } );

    $pref->show_all();
    $proxy_status_img->hide;
    return;
}

sub proxy_non_block_status {
    # This is a non-blocking way to show success or failure
    # in the proxy configuration dialog.
    # I think muppet came up with this.
    my $status = shift;
    $proxy_status_img->set_from_stock(
        ( $status eq 'yes' ) ? 'gtk-yes' : 'gtk-no',
        'small-toolbar' );
    $proxy_status_img->show;
    my $loop = Glib::MainLoop->new;
    Glib::Timeout->add(
        1000,
        sub {
            $loop->quit;
            FALSE;
        }
    );
    $loop->run;
    $proxy_status_img->hide;
    return;
}

sub about {
    my $about = Gtk2::AboutDialog->new;
    $about->set_authors("Dave M, 2004-2010\ndave.nerd <at> gmail.com");
    $about->set_version( ClamTk::App->get_TK_version() );
    my $contributors = 'Please see http://clamtk.sf.net/credits.html';
    $about->set_translator_credits($contributors);
    $about->set_artists($contributors);
    my $logo =
          -e '/usr/share/pixmaps/clamtk.png' ? '/usr/share/pixmaps/clamtk.png'
        : 'usr/share/pixmaps/clamtk.xpm'     ? '/usr/share/pixmaps/clamtk.xpm'
        :                                      '';
    my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($logo);
    $about->set_logo($pixbuf);
    $about->set_website('http://clamtk.sf.net');
    $about->set_comments(
        gettext(
            'ClamTk is a GUI front-end for the ClamAV antivirus using gtk2-perl.'
        )
    );
    $about->set_license(
        gettext(
                  "ClamTk, (c) 2004-2010. All rights reserved.\n\n"
                . "This program is free software; you can redistribute it\n"
                . "and/or modify it under the terms of either:\n\n"
                . "a) the GNU General Public License as published by\n"
                . "the Free Software Foundation; either version 1, or\n"
                . "(at your option) any later version, or\n\n"
                . "b) the 'Artistic License'.\n\n"
        )
    );
    $about->run;
    $about->destroy;
    return;
}

1;
