#!/usr/bin/perl -w

# gutenbook.pl - Perl/Gtk program to download and view Project Gutenberg Etexts
# http://www.gutenbook.org/
# Copyright (C) 2000  Lee "Lefty" Burgess
#
# 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
# of the License, 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.

use Gtk;
use Gtk::Keysyms;
use PGB;
use PGB::Etext;
use PGB::Index;
use PGB::Network;

if ($PGB::DEBUG) {
  use strict;
}

##############
# DECLARATIONS

my $VERSION = "v0.1.10 - advanced prototype (beta)";
my $plist;

my $index;
my $index_hash;
my @selected_etext;

my $window_main;
my $entry_search;
my $label_message;
my $entry_page;
my $label_title;
my $text_left;
my $text_right;
my $label_left;
my $label_right;
my $window_dict;
my $window_prefs;
my $window_browse;
my $entry_title_search;
my $label_totals;
my $label_browse_message;
my $notebook_library;
my %scrolled_window_hash;
my $etext;
my $fileselection;
my $entry_dict_search;
my $text_definition;
my $clist_words;

my $really_destroy;
my $control_is_on;
my $main_has_focus;
my $page_has_focus;
my $search_has_focus;
my $browse_has_focus;
my $dict_has_focus;
my $selected_row;
my $sort_by_holder;
my $sort_order_holder;
my %total_hash;
my $webster_word;
my $webster_jumps;

sub autoclean_cache();
sub create_new_index();
sub event_for_key( $$ );
sub show_word_definition( $$ );
sub select_save_file( $ );
sub view_selected_file( $ );
sub select_row( $$$$ );
sub select_word( $$$$ );
sub click_column( $$ );
sub switch_page( $$$ );
sub view_selected_title();
sub populate_library( $$ );
sub create_dict_window();
sub create_prefs_window();
sub create_browser_window();
sub create_main_window();

######
# MAIN

$SIG{ALRM} = sub { die("Operation timed out...glaven!\n") };

gb_init();
Gtk->init();

$plist = new PGB::PropList($prefs_file);
$plist->read_plist();
if (defined($plist->proplist_hash())) { 
  $plist_hash = $plist->proplist_hash();
}

$control_is_on = 0;
$main_has_focus = 0;
$page_has_focus = 0;
$search_has_focus = 0;
$browse_has_focus = 0;
$dict_has_focus = 0;
$sort_by_holder = 1;
$sort_order_holder = "ascending";

autoclean_cache();
create_main_window();

#eval {
#  alarm(5);
#  while (1) { BUG_ME(0, "waiting...\n");  }
#  alarm(0);
#};
#if ($@) { 
#  BUG_ME(0, "$@");
#}

Gtk->main;

$plist->write_plist($plist_hash);
$plist->destroy();

if (defined($index)) {
  if ($plist_hash->{"cache_local_index"}) {
    $index->write_index();
  }
  $index->destroy();
}

#############
# SUBROUTINES

sub autoclean_cache() {
  if (-f $local_index && ($plist_hash->{"update_index_always"} || !$plist_hash->{"cache_local_index"})) {
    BUG_ME(0, "Autoclean always deleting $local_index...");
    unlink($local_index) || BUG_ME(0, "Cannot unlink $local_index: $!");
  }
  elsif (-f $local_index && $plist_hash->{"update_index_interval"}) {
    BUG_ME(0, "Checking update interval...");
    my ($cn1, $yr1, $mn1, $dy1) = Uncompress($plist_hash->{"update_index_date"});
    my ($yr2, $mn2, $dy2) = Today();
    if (Delta_Days($cn1, $mn1, $dy1, $yr2, $mn2, $dy2) >= $plist_hash->{"update_index_days"}) {
      BUG_ME(0, "Autoclean interval deleting $local_index...");
      unlink($local_index) || BUG_ME(0, "Cannot unlink $local_index: $!");
      $plist_hash->{"update_index_date"} = Compress(Today());
    }
  }
  else {
    BUG_ME(0, "Not doing a damn thing...");
    # $plist_hash->{"update_index_never"} etc...
    # i.e., do nothing
  }
  return();
}

sub create_new_index() {
  if (-f $local_index) {
    BUG_ME(0, "Deleting $local_index...");
    unlink($local_index) || BUG_ME(0, "Cannot unlink $local_index: $!");
  }
  if (defined($index)) {
    $index->destroy();
  }
  BUG_ME(0, "Creating new index...");
  $index = new PGB::Index();
  $index_hash = $index->library_hash();
  
  if (defined($window_browse)) {
    my $i = 0;
    foreach my $key (sort(keys(%scrolled_window_hash))) {
      populate_library(1, $i);
      $i++;
    }
  }
}

sub event_for_key( $$ ) {
  my ($widget, $event) = @_;
  my $keyval = $event->{'keyval'};
  my ($key) = chr($event->{'keyval'});
  my $search = $entry_search->get_text();
  my $title_search = $entry_title_search->get_text() if defined($window_browse); 
  my $dict_search = $entry_dict_search->get_text() if defined($window_dict); 
  my $page = $entry_page->get_text();
  
  # TODO: my life is made difficult now because Perl/GTK+ seems
  # not to implement key_release_event
  
  # NOTE: keyval 65293 is 
 (return)
  
  #    BUG_ME(0, Dumper(\@_));
  #    BUG_ME(0, "Got $event->{'type'}: keyval $keyval for key $key");
  #    BUG_ME(0, "Control is $control_is_on and Focus is $search_has_focus");
 
  #  foreach (keys(%SIG)) {print "$_ => $SIG{$_}\n"}
 
  if ($keyval == $Gtk::Keysyms{'Control_L'} || $keyval == $Gtk::Keysyms{'Control_R'}) {
    $control_is_on = 1;
  }
  # initiate search mode
  elsif ($control_is_on && $key eq "s" && $main_has_focus) {
    $entry_search->grab_focus();
    $control_is_on = 0;
  }
  elsif ($control_is_on && $key eq "s" && $browse_has_focus) {
    $entry_title_search->grab_focus();
    $control_is_on = 0;
  }
  # initiate goto-page mode
  elsif ($control_is_on && $key eq "p" && $main_has_focus) {
    $entry_page->grab_focus();
    $control_is_on = 0;
  }
  # stop search or goto-page mode
  elsif ($control_is_on && $key eq "g" && ($main_has_focus || $browse_has_focus)) {
    # just to get the focus out of $entry_field
    $etext->stop_search() if defined($etext);
    $entry_search->set_text("");
    $entry_title_search->set_text("") if defined($entry_title_search);
    $entry_page->set_text("");
    $label_message->set_text("");
    $label_browse_message->set_text("") if defined($label_browse_message);
    $text_left->grab_focus() if $main_has_focus;
    $notebook_library->grab_focus if $browse_has_focus;
    $control_is_on = 0;
  }
  else {
    $control_is_on = 0;
  }
  
  if ($search_has_focus) {
    if ($keyval == 65293 && $search && defined($etext) && $main_has_focus) {
      $etext->search_for_string($search, $label_message, $text_left, $text_right, $label_left, $label_right);
    }
    elsif ($keyval == 65293 && $title_search && $browse_has_focus) {
      $selected_row = $index->search_in_title($title_search, $notebook_library, \%scrolled_window_hash);
      if (!defined($selected_row)) {
	$label_browse_message->set_text("Not found!");
      }
      else {
	$label_browse_message->set_text("Found in row $selected_row");
      }
    }
    elsif ($keyval == 65293 && $dict_search && $dict_has_focus) {
      show_word_definition($dict_search, undef);
    } 
    else {
      # no else
    }
  }
  elsif ($page_has_focus) {
    if ($keyval == 65293 && $page && defined($etext) && $page =~ /^\d+$/) {
      $etext->display_page_for_position($page, 0, $text_left, $text_right, $label_left, $label_right);
    }
    elsif ($keyval == 65293 && !defined($etext)) {
      $label_message->set_text("No Etext loaded!");
    }
    elsif ($keyval == 65293 && (!$page || $page !~ /^\d+$/)) {
      $label_message->set_text("No page number specified!");
    }
    else {
      # no else
    }
  }
  else {
    if($key eq "o" && $main_has_focus){
      select_save_file("select");
    }
    if($key eq "s" && $main_has_focus){
      select_save_file("save");
    }
    if ($key eq "l" && $main_has_focus) { 
      create_browser_window();
    }
    if ($key eq "p" && $main_has_focus) { 
      create_prefs_window();
    }
    if ($key eq "f" && $main_has_focus) { 
      $etext->directional_page(1, undef, $text_left, $text_right, $label_left, $label_right) 
	if defined($etext);
    }
    if ($key eq "b" && $main_has_focus) { 
      $etext->directional_page(0, undef, $text_left, $text_right, $label_left, $label_right) 
	if defined($etext);
    }
    if ($key eq "q" && $main_has_focus) { 
      &Gtk::main_quit($window_main);
    }
    if (($key eq "r" || $keyval == 65293) && $browse_has_focus) { 
      view_selected_title();
    }
    if ($key eq "c" && $browse_has_focus) { 
      $window_browse->hide();
      $window_main->grab_focus();
    }
    if ($key eq "c" && $dict_has_focus) { 
      $window_dict->hide();
      $window_main->grab_focus();
    }
  }
  
  # must return 1 here, otherwise it gripes
  return(1);
}

sub show_word_definition( $$ ) {
  my ($word, $id) = @_;
  my $p;
  my ($array, $jumps, $entry);
  my $in = 0;
  my $base_url = "http://www.m-w.com/cgi-bin/dictionary?";
  my $request;
  
  if (defined($id)) {
    $request = $base_url . "hdwd=$webster_word&book=Dictionary&jump=$word&list=$webster_jumps";
  }
  else {
    $request = $base_url . "book=Dictionary&va=$word"
  }

  $clist_words->clear();
  $text_definition->backward_delete($text_definition->get_length());

  $array = http_get($request);

  foreach my $line (@{$array}) {
    if ($line =~ /type\=hidden name\=list/i) {
      $jumps = $line
    }
    if ($line =~ /copyrite\.htm/) {
      $entry .= $line;
    }
    if ($line =~ /Main Entry/i) {
      $in = 1;
      $entry .= $line;
    }
    elsif ($in) {
      if ($line =~ /HR width/i) {
	$in = 0;
	next;
      }
      $entry .= $line;
    }
  }
  
  if (!$entry || $entry !~ /Main Entry/i) {
    $webster_word = "";
  }
  else {
    $webster_word = $word;
  }

  if ($entry) {
    $p = HTML::Parser->new(api_version => 3,
			   text_h => [ sub {
					 $text_definition->insert(undef, undef, undef, shift);
				       }, 
				       "dtext" ]);
    $p->parse($entry);
  }

  if ($jumps) {
    $jumps =~ s/^.+value\=\"//;
    $jumps =~ s/\">$//;
    $webster_jumps = $jumps;
    foreach my $match (split(/;/, $jumps)) {
      my ($word, undef) = split(/\=/, $match);
      $clist_words->append(($word,
			    $match));
    }
  }
  $text_definition->insert(undef, undef, undef, "\nBased on Merriam-Webster's Collegiate(R) Dictionary, 10th Edition.");
  $text_definition->insert(undef, undef, undef, "\nhttp://www.m-w.com/");
  return();
}

sub select_save_file( $ ) {
  my ($action) = @_;
  my $filepath = "";
  
  if(defined($fileselection)) {
    return();
  }
  
  $filepath = $etext->etext_path() if defined($etext);
  
  if ($action eq "select") {
    $fileselection = Gtk::FileSelection->new("Select File...");
    $fileselection->set_filename($plist_hash->{"library_dir"} . "/");
    $fileselection->ok_button->signal_connect('clicked' => sub {
						# TODO: use dialog to notify user no file selected?
						if (-f $fileselection->get_filename()) {
						  view_selected_file($fileselection->get_filename());
						}
						$fileselection->destroy();
						undef($fileselection);
					      });
  }
  elsif (defined($etext)) {
    $fileselection = Gtk::FileSelection->new("Save As File...");
    $fileselection->set_filename($etext->etext_path());
    $fileselection->ok_button->signal_connect('clicked' => sub {
						$etext->save_as_file($fileselection->get_filename());
						$fileselection->destroy();
						undef($fileselection);
					      });
    # TODO: following line is a hack because files opened without the 
    # context provided by the index have no number or wildfile name
    if (defined($etext->etext_number())) {
      $index_hash->{$etext->etext_number()}{'LOCAL'} = $etext->etext_filename();
    }
  }
  else {
    $label_message->set_text("No Etext loaded!");
    return();
  }
  
  $fileselection->cancel_button->signal_connect('clicked' => sub {
						  $fileselection->destroy();
						  undef($fileselection);
						});
  
  $fileselection->show();
  
  return();
}

sub view_selected_file( $ ) {
  my ($file) = @_;
  
  BUG_ME(0, "Got selected file: $file");
  
  if (defined($etext)) {
    $etext->destroy();
  }

  $etext = new PGB::Etext($file, undef, undef);
  $etext->directional_page(1, $label_title, $text_left, $text_right, $label_left, $label_right);
  
  $label_message->set_text("");

  return();
}

sub select_row( $$$$ ) {
  my ($widget, $row, $column, $event) = @_;
  
  $selected_row = $row;
  return(1);
}

sub select_word( $$$$ ) {
  my ($widget, $row, $column, $event) = @_;
  
  my $word = $widget->get_text($row, 0);
  my $word_id = $widget->get_text($row, 1);

  show_word_definition($word, $word_id);

  return(1);
}

sub click_column( $$ ) {
  my ($widget, $column) = @_;
  
  $sort_by_holder = $column;
  $widget->set_sort_column($sort_by_holder);
  if ($column == 4 || $column == 5) {
    $sort_order_holder = "descending"
  }
  else {
    $sort_order_holder = "ascending"
  }
  $widget->set_sort_type($sort_order_holder);
  $widget->sort();

  return(1);
}

sub switch_page( $$$ ) {
  my ($widget, $page_widget, $page) = @_;

  $label_totals->set_text("Showing $total_hash{$page} titles out of " . $index->total_titles() . " total");

  if (!$search_has_focus) {
    my $clist = $scrolled_window_hash{(sort(keys(%scrolled_window_hash)))[$page]}[1];
    undef($selected_row);
    $clist->grab_focus();
    $clist->set_sort_column($sort_by_holder);
    $clist->set_sort_type($sort_order_holder);
    $clist->sort();
  }  
  

  return(1);
}

sub view_selected_title() {
  my ($widget) = @_;
  my $key = (sort(keys(%scrolled_window_hash)))[$notebook_library->get_current_page()];
  my $clist_library = $scrolled_window_hash{$key}[1];
  my $key_field;
  my $etext_file;
  
  if (!defined($selected_row)) {
    return();
  }
  
  $key_field = $clist_library->get_text($selected_row, 6);
  
  BUG_ME(0, "Got selected etext number: $key_field for row: $selected_row");
  
  # TODO: must account for FILE being wildfile name
  # or find better way to get definitive filenames for all Etexts
  if ($plist_hash->{"always_read_etexts"} && 
      $index_hash->{$key_field}{'LOCAL'} ne "" &&
      -f $plist_hash->{"library_dir"} ."/". $index_hash->{$key_field}{'LOCAL'}) {
    $etext_file = $plist_hash->{"library_dir"} ."/". $index_hash->{$key_field}{'LOCAL'};
  }
  else {
    $etext_file = derive_etext_path($key_field, $index_hash);
  }	 
  
  if ($etext_file =~ /ETEXT_NOT_AVAILABLE$/) {
    $label_browse_message->set_text("Selected Etext Not Found!");
    return();
  }
  
  if (defined($etext)) {
    $etext->destroy();
  }

  $etext = new PGB::Etext($etext_file, $key_field, $index_hash);
  $etext->directional_page(1, $label_title, $text_left, $text_right, $label_left, $label_right);
  
  if ($plist_hash->{"always_download_etexts"}) {
    $index_hash->{$key_field}{'LOCAL'} = $etext->etext_filename(); 
    $clist_library->set_text($selected_row, 4, $etext->etext_filename());
  }
  
  $label_browse_message->set_text("");
  return();
}

sub populate_library( $$ ) {
  my ($clear_list, $page) = @_;
  my $key = (sort(keys(%scrolled_window_hash)))[$page];
  my $start = substr($key, 0, 1);
  my $end = substr($key, 4, 1);
  my $clist_library = $scrolled_window_hash{$key}[1];
  my $range_total = 0;

  BUG_ME(0, "Populating library for notebook page: $key and widget: $clist_library");

  if ($clear_list) {
    $clist_library->clear();
  }
  
  foreach my $key_field (keys(%{$index_hash})) {
    if ($index_hash->{$key_field}{'TITLE'} =~ /^[$start-$end]/i) {
      $range_total++;
      $clist_library->append(($index_hash->{$key_field}{'NUMBER'}, 
			      $index_hash->{$key_field}{'TITLE'}, 
			      $index_hash->{$key_field}{'YEAR'}, 
			      $index_hash->{$key_field}{'FILE'},
			      $index_hash->{$key_field}{'LOCAL'},
			      $index_hash->{$key_field}{'COPYRIGHT'},
			      $index_hash->{$key_field}{'KEY'}));
    }
  }
  
  $total_hash{$page} = $range_total;

  return();
}

sub create_prefs_window() {
  my $vbox_main;
  my $hbox_lib;
  my $label_lib;
  my $entry_lib;
  my $hbox_file;
  my $label_file;
  my $entry_file;
  my $hbox_url;
  my $label_url;
  my $entry_url;
  my $hbox_proxy;
  my $label_proxy;
  my $entry_proxy;
  my $hbox_index;
  my $checkbutton_index;
  my $button_index;
  my $hbox_cache;
  my $label_cache;
  my $radiobutton_always;
  my $radiobutton_never;
  my $radiobutton_interval;
  my $entry_days;
  my $label_days;
  my $hbox_etexts;
  my $checkbutton_save;
  my $checkbutton_local;
  my $hbox_sizes;
  my $label_sizes;
  my $radiobutton_small;
  my $radiobutton_med;
  my $radiobutton_large;
  my $hbox_buttons;
  my $button_okay;
  my $button_cancel;
   
  my %previous_hash = %{$plist_hash};

  if (defined($window_prefs)) {
    $window_prefs->show();
    $window_prefs->grab_focus();
    return();
  }
  
  $window_prefs = new Gtk::Window('toplevel');
  $window_prefs->signal_connect('destroy' => sub { 
				  undef($window_prefs);
				  $window_main->grab_focus();
				});
  $window_prefs->set_title("Gutenbook User Preferences");
  $window_prefs->set_name('gutenbook.preferences');
  $window_prefs->set_wmclass('gutenbook', 'Gutenbook');
  $window_prefs->set_uposition(100, 200);
  $window_prefs->set_usize(500, 320);
  
  $vbox_main = new Gtk::VBox(0, 1);
  $window_prefs->add($vbox_main);
  $vbox_main->set_border_width(5);
  $vbox_main->show();
  
  $hbox_lib = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_lib, 0, 0, 5);
  $hbox_lib->show();
  
  $label_lib = new Gtk::Label("Local Gutenbook Library:");
  $label_lib->set_usize(150, 0);
  $label_lib->set_justify("right");
  $hbox_lib->pack_start($label_lib, 0, 0, 5);
  $label_lib->show();
  
  $entry_lib = new Gtk::Entry();
  $entry_lib->set_usize(300, 0);
  $entry_lib->set_text($plist_hash->{"library_dir"});
  $hbox_lib->pack_start($entry_lib, 0, 0, 5);
  $entry_lib->show();
  
  $hbox_file = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_file, 0, 0, 5);
  $hbox_file->show();
  
  $label_file = new Gtk::Label("PG Index File URL:");
  $label_file->set_usize(150, 0);
  $label_file->set_justify("right");
  $hbox_file->pack_start($label_file, 0, 0, 5);
  $label_file->show();
  
  $entry_file = new Gtk::Entry();
  $entry_file->set_usize(300, 0);
  $entry_file->set_text($plist_hash->{"http_index"});
  $hbox_file->pack_start($entry_file, 0, 0, 5);
  $entry_file->show();
  
  $hbox_url = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_url, 0, 0, 5);
  $hbox_url->show();
  
  $label_url = new Gtk::Label("PG Etext Mirror URL:");
  $label_url->set_usize(150, 0);
  $label_url->set_justify("right");
  $hbox_url->pack_start($label_url, 0, 0, 5);
  $label_url->show();
    
  $entry_url = new Gtk::Entry();
  $entry_url->set_usize(300, 0);
  $entry_url->set_text($plist_hash->{"http_url"});
  $hbox_url->pack_start($entry_url, 0, 0, 5);
  $entry_url->show();
  
  $hbox_proxy = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_proxy, 0, 0, 5);
  $hbox_proxy->show();
  
  $label_proxy = new Gtk::Label("HTTP Proxy:");
  $label_proxy->set_usize(150, 0);
  $label_proxy->set_justify("right");
  $hbox_proxy->pack_start($label_proxy, 0, 0, 5);
  $label_proxy->show();
  
  $entry_proxy = new Gtk::Entry();
  $entry_proxy->set_usize(300, 0);
  $entry_proxy->set_text($plist_hash->{"http_proxy"});
  $hbox_proxy->pack_start($entry_proxy, 0, 0, 5);
  $entry_proxy->show();
  
  $hbox_index = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_index, 0, 0, 5);
  $hbox_index->show();
  
  $checkbutton_index = new_with_label Gtk::CheckButton("Cache Index Locally");
  $checkbutton_index->set_active($plist_hash->{"cache_local_index"} || 0);
  $hbox_index->pack_start($checkbutton_index, 0, 0, 5);
  $checkbutton_index->show();
  
  $button_index = new Gtk::Button("Update Index Now");
  $button_index->signal_connect('clicked' => sub { 
				  create_new_index();
				});
  $hbox_index->pack_start($button_index, 1, 0, 5);
  $button_index->show();
  
  $hbox_cache = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_cache, 0, 0, 5);
  $hbox_cache->show();
  
  $label_cache = new Gtk::Label("Automatically Update Cached Index:");
  $hbox_cache->pack_start($label_cache, 0, 0, 5);
  $label_cache->show();
  
  $radiobutton_always = new_with_label Gtk::RadioButton("Always");
  $radiobutton_always->set_active($plist_hash->{"update_index_always"} || 0);
  $hbox_cache->pack_start($radiobutton_always, 0, 0, 5);
  $radiobutton_always->show();
  
  $radiobutton_never = new_with_label Gtk::RadioButton("Never", $radiobutton_always);
  $radiobutton_never->set_active($plist_hash->{"update_index_never"} || 0);
  $hbox_cache->pack_start($radiobutton_never, 0, 0, 5);
  $radiobutton_never->show();

  $radiobutton_interval = new_with_label Gtk::RadioButton("Every", $radiobutton_always);
  $radiobutton_interval->set_active($plist_hash->{"update_index_interval"} || 0);
  $hbox_cache->pack_start($radiobutton_interval, 0, 0, 0);
  $radiobutton_interval->show();
  
  $entry_days = new_with_max_length Gtk::Entry(3);
  $entry_days->set_usize(30, 0);
  $entry_days->set_text($plist_hash->{"update_index_days"});
  $hbox_cache->pack_start($entry_days, 0, 0, 0);
  $entry_days->show();
  
  $label_days = new Gtk::Label("Days");
  $hbox_cache->pack_start($label_days, 0, 0, 5);
  $label_days->show();
  
  $hbox_etexts = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_etexts, 0, 0, 5);
  $hbox_etexts->show();
  
  $checkbutton_save = new_with_label Gtk::CheckButton("Save Downloaded Etexts Automatically");
  $checkbutton_save->set_active($plist_hash->{"always_download_etexts"} || 0);
  $hbox_etexts->pack_start($checkbutton_save, 0, 0, 5);
  $checkbutton_save->show();
  
  $checkbutton_local = new_with_label Gtk::CheckButton("Always Read Local Etexts");
  $checkbutton_local->set_active($plist_hash->{"always_read_etexts"} || 0);
  $hbox_etexts->pack_start($checkbutton_local, 0, 0, 5);
  $checkbutton_local->show();
  
  $hbox_sizes = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_sizes, 0, 0, 5);
  $hbox_sizes->show();

  $label_sizes = new Gtk::Label("Main Window Size:");
  $hbox_sizes->pack_start($label_sizes, 0, 0, 5);
  $label_sizes->show();
  
  $radiobutton_small = new_with_label Gtk::RadioButton("640x480");
  $radiobutton_small->set_active($plist_hash->{"main_window_small"} || 0);
  $hbox_sizes->pack_start($radiobutton_small, 0, 0, 5);
  $radiobutton_small->show();
  
  $radiobutton_med = new_with_label Gtk::RadioButton("800x600", $radiobutton_small);
  $radiobutton_med->set_active($plist_hash->{"main_window_medium"} || 0);
  $hbox_sizes->pack_start($radiobutton_med, 0, 0, 5);
  $radiobutton_med->show();

  $radiobutton_large = new_with_label Gtk::RadioButton("1024x768", $radiobutton_small);
  $radiobutton_large->set_active($plist_hash->{"main_window_large"} || 0);
  $hbox_sizes->pack_start($radiobutton_large, 0, 0, 0);
  $radiobutton_large->show();

  $hbox_buttons = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_buttons, 0, 0, 5);
  $hbox_buttons->show();
  
  $button_okay = new Gtk::Button("OK");
  $button_okay->set_usize(50, 0);
  $button_okay->signal_connect('clicked' => sub { 
				 $plist_hash->{"library_dir"} = $entry_lib->get_text();
				 $plist_hash->{"http_index"} = $entry_file->get_text();
				 $plist_hash->{"http_url"} = $entry_url->get_text();
				 $plist_hash->{"http_proxy"} = $entry_proxy->get_text();
				 $plist_hash->{"cache_local_index"} = $checkbutton_index->get_active();
				 $plist_hash->{"update_index_always"} = $radiobutton_always->get_active();
				 $plist_hash->{"update_index_never"} = $radiobutton_never->get_active();
				 $plist_hash->{"update_index_interval"} = $radiobutton_interval->get_active();
				 $plist_hash->{"main_window_small"} = $radiobutton_small->get_active();
				 $plist_hash->{"main_window_medium"} = $radiobutton_med->get_active();
				 $plist_hash->{"main_window_large"} = $radiobutton_large->get_active();
				 $plist_hash->{"update_index_days"} = $entry_days->get_text();
				 $plist_hash->{"always_download_etexts"} = $checkbutton_save->get_active();
				 $plist_hash->{"always_read_etexts"} = $checkbutton_local->get_active();
				 if ($plist_hash->{"update_index_interval"}) {
				   if(!$plist_hash->{"update_index_days"}) {
				     BUG_ME(0, "No days set, assuming 0...");
				     $plist_hash->{"update_index_days"} = 0;
				     $plist_hash->{"update_index_date"} = Compress(Today());
				   }
				   # sneaky string compare because we will not always have numbers
				   elsif ($previous_hash{"update_index_days"} ne $plist_hash->{"update_index_days"}) {
				     BUG_ME(0, "Days changed, setting...");
				     $plist_hash->{"update_index_date"} = Compress(Today());
				   }
				 }
				 else {
				   BUG_ME(0, "Interval turned off, nulling fields...");
				   $plist_hash->{"update_index_days"} = "";
				   $plist_hash->{"update_index_date"} = "";
				 }
				 $plist->write_plist($plist_hash);
				 $window_prefs->destroy();
				 undef($window_prefs);
				 $window_main->grab_focus();
				 if ($previous_hash{"http_index"} ne $plist_hash->{"http_index"} ||
				     $previous_hash{"cache_local_index"} ne $plist_hash->{"cache_local_index"}) {
				   create_new_index();
				 }
				 if ($previous_hash{"main_window_small"} ne $plist_hash->{"main_window_small"} || 
				     $previous_hash{"main_window_medium"} ne $plist_hash->{"main_window_medium"} || 
				     $previous_hash{"main_window_large"} ne $plist_hash->{"main_window_large"}) {
				   $really_destroy = 0;
				   $window_main->destroy();
				   create_main_window();
				 }
			       });
  $hbox_buttons->pack_start($button_okay, 1, 0, 5);
  $button_okay->show();
  
  $button_cancel = new Gtk::Button("Cancel");
  $button_cancel->set_usize(50, 0);
  $button_cancel->signal_connect('clicked' => sub { 
				   $window_prefs->destroy();
				   undef($window_prefs);
				   $window_main->grab_focus();
				 });
  $hbox_buttons->pack_start($button_cancel, 1, 0, 5);
  $button_cancel->show();
  
  $window_prefs->show();
  $window_prefs->grab_focus();
  
  return();
}

sub create_dict_window() {
  my $vbox_main;
  my $hbox_buttons;
  my $button_close;
  my $label_dict_search;
  my $hbox_words;
  my $scrolled_window_words;
  my $hbox_defintion;
  my $scrolled_window;

  if (defined($window_dict)) {
    $window_dict->show();
    $window_dict->grab_focus();
    return();
  }

  $window_dict = new Gtk::Window('toplevel');
  $window_dict->signal_connect('destroy' => sub { 
				 undef($window_dict);
				 $window_main->grab_focus();
			       });
  $window_dict->set_title("Dictionary Interface");
  $window_dict->set_name('gutenbook.dictionary');
  $window_dict->set_wmclass('gutenbook', 'Gutenbook');
  $window_dict->set_uposition(100, 200);
  $window_dict->set_usize(450, 400);
  $window_dict->signal_connect('key_press_event' => \&event_for_key);
  $window_dict->signal_connect('focus_in_event' => sub {
				 $dict_has_focus = 1;
				 $main_has_focus = 0;
			       });

  $vbox_main = new Gtk::VBox(0, 1);
  $window_dict->add($vbox_main);
  $vbox_main->set_border_width(5);
  $vbox_main->show();

  $hbox_buttons = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_buttons, 0, 0, 5);
  $hbox_buttons->show();
  
  $button_close = new Gtk::Button("(C)lose Dictionary");
  $button_close->signal_connect('clicked' => sub { 
				  $window_dict->hide();
				  $window_main->grab_focus();
				});
  $hbox_buttons->pack_start($button_close, 0, 0, 5);
  $button_close->show();
  
  $label_dict_search = new Gtk::Label("Search for word:");
  $hbox_buttons->pack_start($label_dict_search, 0, 0, 5);
  $label_dict_search->show();
  
  $entry_dict_search = new Gtk::Entry();
  $entry_dict_search->set_usize(200, 0);
  $entry_dict_search->signal_connect('focus_in_event' => sub {
				       $search_has_focus = 1;
				     });
  $entry_dict_search->signal_connect('focus_out_event' => sub {
					$search_has_focus = 0;
				      });
  $hbox_buttons->pack_start($entry_dict_search, 0, 0, 5);
  $entry_dict_search->show();
  
#  $hbox_alt = new Gtk::HBox(0, 1);
#  $vbox_main->pack_start($hbox_alt, 0, 0, 5);
#  $hbox_alt->show();

#  $button_get = new Gtk::Button("Go To Defintion:");
#  $button_get->signal_connect('clicked' => sub { 
#				$window_dict->hide();
#				$window_main->grab_focus();
#			      });
#  $hbox_alt->pack_start($button_get, 0, 0, 5);
#  $button_get->show();

  $hbox_words = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_words, 0, 0, 5);
  $hbox_words->show();
  
  $scrolled_window_words = new Gtk::ScrolledWindow(undef, undef);
  $scrolled_window_words->set_policy('automatic', 'automatic');
  $hbox_words->pack_start($scrolled_window_words, 1, 1, 5);
  $scrolled_window_words->show();
  
  $clist_words = new Gtk::CList(2);
  $clist_words->set_selection_mode('single');
  $clist_words->set_column_width(0, 100);
  $clist_words->set_column_width(1, 50);
  $clist_words->set_column_visibility(1, 0);
  $clist_words->signal_connect('select_row' => \&select_word);
  $scrolled_window_words->add($clist_words);
  $clist_words->show();

  $hbox_definition = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_definition, 1, 1, 5);
  $hbox_definition->show();

  $scrolled_window = new Gtk::ScrolledWindow(undef, undef);
  $scrolled_window->set_policy('automatic', 'automatic');
  $hbox_definition->pack_start($scrolled_window, 1, 1, 5);
  $scrolled_window->show();
  
  $text_definition = new Gtk::Text;
  $text_definition->set_word_wrap(1);
  $scrolled_window->add($text_definition);
  $text_definition->show();

  $window_dict->show();
}

sub create_browser_window() {
  my $vbox_main;
  my $hbox_buttons;
  my $button_close;
  my $button_select;
  my $label_title_search;
  my $hbox_message;
  
  if (defined($window_browse)) {
    $window_browse->show();
    $window_browse->grab_focus();
    #	$clist_library->grab_focus();
    return();
  }
  else {
    %scrolled_window_hash = ("A - E" => undef, 
			     "F - J" => undef,
			     "K - O" => undef,
			     "P - R" => undef,
			     "S - T" => undef,
			     "U - Z" => undef);
  }
  
  # use this session's index if it exists:  poor man's caching
  if (!defined($index)) {
    $index = new PGB::Index();
    $index_hash = $index->library_hash();
  }
  
  $window_browse = new Gtk::Window('toplevel');
  $window_browse->signal_connect('destroy' => sub { 
				   undef($window_browse);
				   $window_main->grab_focus();
				 });
  $window_browse->set_title("Gutenberg Library");
  $window_browse->set_name('gutenbook.library');
  $window_browse->set_wmclass('gutenbook', 'Gutenbook');
  $window_browse->set_uposition(100, 200);
  $window_browse->set_usize(680, 500);
  $window_browse->signal_connect('key_press_event' => \&event_for_key);
  $window_browse->signal_connect('focus_in_event' => sub {
				   $browse_has_focus = 1;
				   $main_has_focus = 0;
				 });
  
  $vbox_main = new Gtk::VBox(0, 1);
  $window_browse->add($vbox_main);
  $vbox_main->set_border_width(5);
  $vbox_main->show();
  
  $hbox_buttons = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_buttons, 0, 0, 5);
  $hbox_buttons->show();
  
  $button_close = new Gtk::Button("(C)lose Gutenberg Library");
  $button_close->signal_connect('clicked' => sub { 
				  $window_browse->hide();
				  $window_main->grab_focus();
				});
  $hbox_buttons->pack_start($button_close, 0, 0, 5);
  $button_close->show();
  
  $button_select = new Gtk::Button("(R)ead Selected Etext");
  $button_select->signal_connect('clicked' => \&view_selected_title);
  $hbox_buttons->pack_start($button_select, 0, 0, 5);
  $button_select->show();
  
  $label_title_search = new Gtk::Label("Search in Title/Author:");
  $hbox_buttons->pack_start($label_title_search, 0, 0, 5);
  $label_title_search->show();
  
  $entry_title_search = new Gtk::Entry();
  $entry_title_search->set_usize(200, 0);
  $entry_title_search->signal_connect('focus_in_event' => sub {
					$search_has_focus = 1;
				      });
  $entry_title_search->signal_connect('focus_out_event' => sub {
					$search_has_focus = 0;
				      });
  $hbox_buttons->pack_start($entry_title_search, 0, 0, 5);
  $entry_title_search->show();
  
  $hbox_message = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_message, 0, 0, 5);
  $hbox_message->show();
  
  $label_totals = new Gtk::Label("");
  $hbox_message->pack_start($label_totals, 1, 1, 0);
  $label_totals->show();

  $label_browse_message = new Gtk::Label("");
  $hbox_message->pack_start($label_browse_message, 1, 1, 0);
  $label_browse_message->show();
  
  $notebook_library = new Gtk::Notebook();
  $vbox_main->pack_start($notebook_library, 1, 1, 5);
  $notebook_library->show();
  
  my $i = 0;
  foreach my $key (sort(keys(%scrolled_window_hash))) {
    $scrolled_window_hash{$key}[0] = new Gtk::ScrolledWindow(undef, undef);
    $scrolled_window_hash{$key}[0]->set_policy('automatic', 'automatic');
    $notebook_library->append_page_menu($scrolled_window_hash{$key}[0], 
					new Gtk::Label($key), 
					new Gtk::Label($key));
    $scrolled_window_hash{$key}[0]->show();
    
    $scrolled_window_hash{$key}[1] = new_with_titles Gtk::CList(('Number', 
								 'Title/Author', 
								 'Year', 
								 'File', 
								 'Local Copy',
								 '(c)',
								 'Key'));
    $scrolled_window_hash{$key}[1]->set_selection_mode('single');
    $scrolled_window_hash{$key}[1]->set_column_width(0, 50);
    $scrolled_window_hash{$key}[1]->set_column_width(1, 330);
    $scrolled_window_hash{$key}[1]->set_column_width(2, 40);
    $scrolled_window_hash{$key}[1]->set_column_width(3, 80);
    $scrolled_window_hash{$key}[1]->set_column_width(4, 80);
    $scrolled_window_hash{$key}[1]->set_column_width(5, 20);
    $scrolled_window_hash{$key}[1]->set_column_width(6, 20);
    $scrolled_window_hash{$key}[1]->set_column_visibility(6, 0);
    $scrolled_window_hash{$key}[1]->set_sort_column($sort_by_holder);
    $scrolled_window_hash{$key}[1]->set_sort_type($sort_order_holder);
    $scrolled_window_hash{$key}[1]->set_auto_sort(1);
    $scrolled_window_hash{$key}[1]->signal_connect('select_row' => \&select_row);
    $scrolled_window_hash{$key}[1]->signal_connect('click_column' => \&click_column);
    $scrolled_window_hash{$key}[0]->add($scrolled_window_hash{$key}[1]);
    $scrolled_window_hash{$key}[1]->show();
    populate_library(0, $i);
    $scrolled_window_hash{$key}[1]->sort();
    $i++;
  }
  
  $window_browse->show();
  $window_browse->grab_focus();

  # this connect now rather than earlier because it relies 
  # on the existance of the CList widgets inside $scrolled_window_hash
  $notebook_library->signal_connect('switch_page' => \&switch_page);
  $scrolled_window_hash{"A - E"}[1]->grab_focus();
  $label_totals->set_text("Showing $total_hash{0} titles out of " . $index->total_titles() . " total");

  

  return();
}

sub create_main_window() {
  my $vbox_main;
  my $menubar_main;
  my $menu_file;
  my $menuitem_file;
  my $menuitem_open;
  my $menuitem_save;
  my $menuitem_quit;
  my $menu_options;
  my $menuitem_options;
  my $menuitem_prefs;
  my $menu_dictionary;
  my $menuitem_dictionary;
  my $menuitem_dict;
  my $menu_help;
  my $menuitem_help;
  my $menuitem_about;
  my $menuitem_manual;
  
  my $hbox_buttons;
  my $button_ibrowse;
  my $button_prev;
  my $button_next;

  my $hbox_fields;
  my $label_page;
  my $label_search;
  
  my $hbox_title;
  my $hbox_text;
  my $hbox_labels;
  
  $really_destroy = 1;

  $window_main = new Gtk::Window('toplevel');
  $window_main->signal_connect('destroy' => sub { Gtk::main_quit($window_main) if ($really_destroy) });
  $window_main->signal_connect('delete_event' => \&Gtk::false);
  $window_main->signal_connect('key_press_event' => \&event_for_key);
  # $window_main->signal_connect('key_release_event' => \&event_for_key);
  $window_main->signal_connect('focus_in_event' => sub {
				 $main_has_focus = 1;
				 $browse_has_focus = 0;
			       });
  $window_main->set_title("Gutenbook $VERSION");
  $window_main->set_name("Gutenbook");
  $window_main->set_wmclass("gutenbook", "Gutenbook");
  $window_main->set_uposition(0, 0);
  $window_main->set_usize(640, 480) if $plist_hash->{"main_window_small"};
  $window_main->set_usize(800, 600) if $plist_hash->{"main_window_medium"};
  $window_main->set_usize(1024, 768) if $plist_hash->{"main_window_large"};
  $window_main->set_policy(0, 1, 0);

  $vbox_main = new Gtk::VBox(0, 1);
  $window_main->add($vbox_main);
  $vbox_main->set_border_width(0);
  $vbox_main->show();
  
  $menubar_main = new Gtk::MenuBar;
  $vbox_main->pack_start($menubar_main, 0, 0, 0);
  $menubar_main->show();
  
  $menuitem_file = new Gtk::MenuItem('File');
  $menubar_main->append($menuitem_file);
  $menuitem_file->show();
  
  $menu_file = new Gtk::Menu;
  $menuitem_file->set_submenu($menu_file);
  
  $menuitem_open = new Gtk::MenuItem('Open Etext File...');
  $menuitem_open->signal_connect('activate' => sub { select_save_file("select") });
  $menu_file->append($menuitem_open);
  $menuitem_open->show();
  
  $menuitem_save = new Gtk::MenuItem('Save Etext As File...');
  $menuitem_save->signal_connect('activate' => sub { select_save_file("save") });
  $menu_file->append($menuitem_save);
  $menuitem_save->show();
  
  $menuitem_quit = new Gtk::MenuItem('Quit Gutenbook');
  $menuitem_quit->signal_connect('activate' => \&Gtk::main_quit);
  $menu_file->append($menuitem_quit);
  $menuitem_quit->show();
  
  $menuitem_options = new Gtk::MenuItem('Edit');
  $menubar_main->append($menuitem_options);
  $menuitem_options->show();
  
  $menu_options = new Gtk::Menu;
  $menuitem_options->set_submenu($menu_options);

  $menuitem_prefs = new Gtk::MenuItem('User Preferences...');
  $menuitem_prefs->signal_connect('activate' => sub { create_prefs_window() });
  $menu_options->append($menuitem_prefs);
  $menuitem_prefs->show();
  
  $menuitem_dictionary = new Gtk::MenuItem('Dictionary');
  $menubar_main->append($menuitem_dictionary);
  $menuitem_dictionary->show();
  
  $menu_dictionary = new Gtk::Menu;
  $menuitem_dictionary->set_submenu($menu_dictionary);
  
  $menuitem_dict = new Gtk::MenuItem('Look Up Word...');
  $menuitem_dict->signal_connect('activate' => sub { create_dict_window()  });
  $menu_dictionary->append($menuitem_dict);
  $menuitem_dict->show();
  
  $menuitem_help = new Gtk::MenuItem('Help');
  $menuitem_help->right_justify();
  $menubar_main->append($menuitem_help);
  $menuitem_help->show();
  
  $menu_help = new Gtk::Menu;
  $menuitem_help->set_submenu($menu_help);
  
  $menuitem_about = new Gtk::MenuItem('About...');
  #  $menuitem_about->signal_connect('activate' => sub { create_dict_window()  });
  $menu_help->append($menuitem_about);
  $menuitem_about->show();
  
  $menuitem_manual = new Gtk::MenuItem('Help...');
  #  $menuitem_manual->signal_connect('activate' => sub { create_dict_window()  });
  $menu_help->append($menuitem_manual);
  $menuitem_manual->show();
  
  $hbox_buttons = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_buttons, 0, 0, 5);
  $hbox_buttons->show();
  
  $button_ibrowse = new Gtk::Button('Browse Gutenberg (L)ibrary');
  $button_ibrowse->signal_connect('clicked' => sub { 
				    create_browser_window();
				  });
  $hbox_buttons->pack_start($button_ibrowse, 0, 0, 5);
  $button_ibrowse->show();
  
  $button_prev = new Gtk::Button('Page (B)ack');
  $button_prev->signal_connect('clicked' => sub { 
				 $etext->directional_page(0, undef, $text_left, $text_right, $label_left, $label_right) 
				   if defined($etext);
			       });
  $hbox_buttons->pack_start($button_prev, 0, 0, 5);
  $button_prev->show();
  
  $button_next= new Gtk::Button('Page (F)orward');
  $button_next->signal_connect('clicked' => sub { 
				 $etext->directional_page(1, undef, $text_left, $text_right, $label_left, $label_right) 
				   if defined($etext);
			       });
  $hbox_buttons->pack_start($button_next, 0, 0, 5);
  $button_next->show();
  
  $label_message = new Gtk::Label("");
  $hbox_buttons->pack_start($label_message, 0, 0, 5);
  $label_message->show();
  
  $hbox_fields = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_fields, 0, 0, 5);
  $hbox_fields->show();

  $label_page = new Gtk::Label("Go to page:");
  $hbox_fields->pack_start($label_page, 0, 0, 5);
  $label_page->show();
  
  $entry_page = new_with_max_length Gtk::Entry(5);
  $entry_page->set_usize(50, 0);
  $entry_page->signal_connect('focus_in_event' => sub {
				$page_has_focus = 1;
			      });
  $entry_page->signal_connect('focus_out_event' => sub {
				$page_has_focus = 0;
			      });
  $hbox_fields->pack_start($entry_page, 0, 0, 5);
  $entry_page->show();
  
  $label_search = new Gtk::Label("Search in Etext:");
  $hbox_fields->pack_start($label_search, 0, 0, 5);
  $label_search->show();
  
  $entry_search = new Gtk::Entry();
  $entry_search->set_usize(200, 0);
  $entry_search->signal_connect('focus_in_event' => sub {
				  $search_has_focus = 1;
				});
  $entry_search->signal_connect('focus_out_event' => sub {
				  # TODO: These two lines cause a deep recursion and a seg fault: 
				  # find a better way
				  # $etext->stop_search($entry_search, $label_message) if defined($etext);
				  # $text_left->grab_focus();
				  $search_has_focus = 0;
				});
  $hbox_fields->pack_start($entry_search, 0, 0, 5);
  $entry_search->show();
  
  $hbox_title = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_title, 0, 0, 5);
  $hbox_title->show();
  
  $label_title = new Gtk::Label("Welcome to Gutenbook");
  $hbox_title->pack_start($label_title, 1, 1, 0);
  $label_title->show();
  
  $hbox_text = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_text, 1, 1, 5);
  $hbox_text->show();
  
  $text_left = new Gtk::Text;
  $text_left->set_word_wrap(1);
  $hbox_text->pack_start($text_left, 1, 1, 5);
  $text_left->show();
  
  $text_right = new Gtk::Text;
  $text_right->set_word_wrap(1);
  $hbox_text->pack_start($text_right, 1, 1, 5);
  $text_right->show();
  
  $hbox_labels = new Gtk::HBox(0, 1);
  $vbox_main->pack_start($hbox_labels, 0, 0, 0);
  $hbox_labels->show();
  
  $label_left = new Gtk::Label(" ");
  $hbox_labels->pack_start($label_left, 1, 1, 0);
  $label_left->show();
  
  $label_right = new Gtk::Label(" ");
  $hbox_labels->pack_start($label_right, 1, 1, 0);
  $label_right->show();
  
  $window_main->show();
  
  return();
}
