# $Id: AddressBook.pm,v 1.3 2001/04/14 03:44:29 muhri Exp $
# -*- perl -*-
package Pronto::AddressBook;
use strict;
use Data::Dumper;
my $sorttype = 0;
use SelfLoader;
1;
__DATA__


sub new { return bless {}, shift }

sub des_win {
	my ($widget, $win, $data) = @_;
	if (defined $data->{winopen}) {
		undef $data->{winopen};
	}
	$win->destroy;
}

sub init_win {
# ------------------------------------------------------------------
# Creates the initial window for address book.
#
	my ($widget, $data) = @_;
	if ((defined $data->{winopen}) and ($data->{winopen} == 1)) {
		return 1;
	}
	$data->{winopen} = 1;

	my $self = Pronto::AddressBook->new;
	$self->{data} = $data;

	my (
			$win, $hbox, $vbox, $hpane, $ctree, $main_vbox, $swin,
			$main_hbox, $right_vbox, $sql, $sth1, $sth2, $inner_vbox, 
			$close, $new_group, $new_address, $hsep, $label, $hbuttonbox,
			$item, $delete_menu, $delete, $popup, $alignment
		);
    	$win = new Gtk::Window;
	$win->signal_connect ("delete_event" => \&Gtk::false);
	my ($width, $height) = main::get_win_size ('Address Book', 550, 300);
	$win->set_default_size ($width, $height);
	$win->set_policy (1,1,0);
	$win->show;
	$main_hbox = new Gtk::HBox (0, 0);
	$win->add ($main_hbox);
	$main_hbox->show;
	$main_vbox = new Gtk::VBox (0, 0);
	$main_hbox->pack_start ($main_vbox, 1, 1, 0);
	$main_vbox->show;
	
	$hpane = new Gtk::HPaned;
	$hpane->handle_size (10);
	$hpane->gutter_size (6);
	$main_vbox->pack_start ($hpane, 1, 1, 0);
	$hpane->show;

	$swin = new Gtk::ScrolledWindow (undef, undef);
	$swin->set_policy ('automatic', 'automatic');
	my ($swidth, $sheight) = main::get_win_size("addressPane", 200, 250);
	$swin->set_usize ($swidth, $sheight);
	$hpane->add1 ($swin);
	$swin->show;
	
	# Ctree for address view
	$ctree = new Gtk::CTree (1, 0);
 	$ctree->set_expander_style ($main::prefs{'expander'});
	$ctree->set_line_style ($main::prefs{'threadstyle'});
	$ctree->set_selection_mode ('single');
	$ctree->signal_connect("click_column", \&ctree_click_column);
	$ctree->set_border ('in');
	$ctree->can_focus (1);
	$ctree->column_titles_show;
	$ctree->set_column_width (0, 150);
	$swin->add ($ctree);
	$ctree->show;
	
	$self->{widgets}->{tree} = $ctree;
	$self->{widgets}->{inner_vbox} = new Gtk::VBox (0, 0);
	$self->{widgets}->{outer_vbox} = new Gtk::VBox (0, 0);
 
	# Build the address tree
	$self->build_group_tree;

	# Pop up menu for the CTree.
	$popup = new Gtk::Menu;
	$item = new Gtk::MenuItem;
	$item->show;
	$popup->append ($item);
	
	$item = new Gtk::MenuItem (_("Add to To"));
	$item->show;
	$popup->append ($item);
	$item->signal_connect (activate => sub { $self->add_to_field ("to") });
	
	$item = new Gtk::MenuItem (_("Add to CC"));
	$item->show;
	$popup->append ($item);
	$item->signal_connect (activate => sub { $self->add_to_field ("cc") });
	
	$item = new Gtk::MenuItem (_("Add to BCC"));
	$item->show;
	$popup->append ($item);
	$item->signal_connect (activate => sub { $self->add_to_field ("bcc") });
	
	$item = new Gtk::MenuItem;
	$item->show;
	$popup->append ($item);
	
	$delete = new Gtk::MenuItem (_("Delete From"));
	$delete->show;
	$popup->append ($delete);

		$delete_menu = new Gtk::Menu;
		$delete->set_submenu ($delete_menu);
		$delete_menu->show;
	
		$item = new Gtk::MenuItem (_("Group"));
		$item->show;
		$delete_menu->append ($item);
		$item->signal_connect (activate => sub { $self->address_delete ("Group") });

		$item = new Gtk::MenuItem (_("Address Book"));
		$item->show;
		$delete_menu->append ($item);
		$item->signal_connect (activate => sub { $self->address_delete ("Address Book") });
	
	$item = new Gtk::MenuItem;
	$item->show;
	$popup->append ($item);
	
	$ctree->signal_connect (select_row         => sub { $self->refresh_right ('mod') } );
	$ctree->signal_connect (unselect_row       => sub { $self->{widgets}->{inner_vbox}->destroy });
	$ctree->signal_connect (button_press_event => sub {
		my $event = pop;
		return 1 unless ($ctree->selection);
		if ($event->{button} == 3) {
			$popup->popup (undef, undef, $event->{button}, 1);
		}
		elsif ($event->{type} eq "2button_press") {
			$self->add_to_field (undef);
		}
		return 1;
	});

	# Because we depend in the selection in the tree for what we do.
	$ctree->signal_connect (tree_collapse      => sub {
		$ctree->selection or return;
		$ctree->unselect ($ctree->selection); 
		$self->{widgets}->{inner_vbox}->destroy;
		return 1;
	});
	$ctree->signal_connect (tree_expand        => sub {
		$ctree->selection or return;
		$ctree->unselect ($ctree->selection); 
		$self->{widgets}->{inner_vbox}->destroy;
		return 1;
	});

	$label = new Gtk::Label (_("Alias"));
	$label->set_justify ('center');
	$label->set_line_wrap (0);
	$ctree->set_column_widget (0, $label);
	$label->show;
	$label->set_alignment (0.5, 0.5);
	
	# This is the vbox that changes depending on what is click in the left pain.
	# It will initialaly be empty
	$self->{widgets}->{outer_vbox}->add ($self->{widgets}->{inner_vbox});
	$hpane->add2 ($self->{widgets}->{outer_vbox});
	$self->{widgets}->{inner_vbox}->show;
	$self->{widgets}->{outer_vbox}->show;
	$self->{widgets}->{outer_vbox}->border_width (10);

	$hsep = new Gtk::HSeparator;
	$main_vbox->pack_start ($hsep, 0, 0, 0);
	$hsep->show;

	$alignment = new Gtk::Alignment (0.5, 0.5, 0, 0);
	$main_vbox->pack_start ($alignment, 1, 0, 0);
	$alignment->show;

	$hbuttonbox = new Gtk::HButtonBox;
	$alignment->add ($hbuttonbox);
	$hbuttonbox->show;
	$hbuttonbox->set_layout ('default_style');
	$hbuttonbox->set_spacing (0);
	$hbuttonbox->set_child_size (80, 27);
	$hbuttonbox->set_child_ipadding (0, 0);
		
	$new_group = new Gtk::Button (_("New Group"));
	$hbuttonbox->add ($new_group);
	$new_group->show;
	$new_group->can_default (1);
	$new_group->can_focus (1);
	$new_group->signal_connect (clicked => sub { $self->group_new });
	
	$new_address = new Gtk::Button (_("New Address"));
	$hbuttonbox->add ($new_address);
	$new_address->show;
	$new_address->can_default (1);
	$new_address->can_focus (1);
	$new_address->signal_connect (clicked => sub { $self->address_new });
	
	$close = new Gtk::Button (_("Close"));
	$hbuttonbox->add ($close);
	$close->show;
	$close->can_default (1);
	$close->can_focus (1);

	$close->signal_connect ("clicked"    => \&des_win, $win, $data);
	$win->signal_connect ("destroy"      => \&des_win, $win, $data);
	$win->signal_connect ("size-request" => \&main::save_win_size, 'Address Book', $win->window);
	ctree_click_column($ctree);
	return 1;
}

sub ctree_click_column
{
	my ($ctree, $column) = @_;
	if ($sorttype == 0) {
		$ctree->set_sort_type('ascending');
		$sorttype = 1;
	} else {
		$ctree->set_sort_type('descending');
		$sorttype = 0;
	}	
	$ctree->sort_recursive(undef);
	return;
}

sub build_group_tree {
# ------------------------------------------------------------------
# Builds the tree of address groups to addresses
#
	my $self = shift;
	my ($sql, $sth1, $sth2, $sth3, %tree);

	$sql  = q!SELECT name FROM groups WHERE id = ?!;
	$sth1 = $main::conn->prepare ($sql);
	$sql  = q!SELECT alias, id, groups FROM addresses!;
	$sth2 = $main::conn->prepare ($sql);
	$sql  = q!SELECT name, id FROM groups!;
	$sth3 = $main::conn->prepare ($sql);
	$sth2->execute;

	%tree = ();
	$self->{tree} = {};
	$self->{folder} = {};
	$self->{widgets}->{tree}->freeze;
	$self->{widgets}->{tree}->clear;
	$self->{top} = $self->{widgets}->{tree}->insert_node (undef, undef, ["Groups"], 5, undef, undef, undef, undef, 0, 1);
	while (my ($alias, $aid, $groups) = $sth2->fetchrow) {
		if ($groups) {
			my @gid = split (',' => $groups);
			foreach (@gid) {
				$sth1->execute ($_);
				my ($gname) = $sth1->fetchrow;
				unless (exists $tree{$_}) {
					$tree{$_} = $self->add_group_node ($_, $gname);
				}
				my $add = $self->add_address_node ($aid, $alias, $tree{$_}->{obj});
				$self->{folder}->{$_}->{$aid} = $add;
			}
		}
		else {
			my $add = $self->add_address_node ($aid, $alias);
			$self->{folder}->{0}->{$aid} = $add;
		}
	}

	# Groups that do not have any children.
	$sth3->execute;
	while (my ($name, $id) = $sth3->fetchrow) {
		unless (exists $tree{$id}) {
			$tree{$id} = $self->add_group_node ($id, $name);
		}
	}

	$self->{widgets}->{tree}->thaw;
	return 1;
}

sub add_to_field { 	
# ------------------------------------------------------------------
# Adds an address to the compose field. Launches a 
# Compose window if one is not launched.
#
	my ($self, $field) = @_;
 	my ($id, $sql, $sth, $oldval, @fields);

	my $node = $self->get_selected or return;
 	$id = $node->{id};
	my @ids = ();
	if ($node->{type} eq 'address') {
		@ids = ($id);
	}
	else {
		for (keys %{$self->{folder}->{$id}}) {
			push @ids, $_;
		}
	}
	$sql = "SELECT alias, address FROM addresses WHERE id = ?";
	$sth = $main::conn->prepare($sql);
	foreach my $id (@ids) {
 		$sth->execute($id);
 		my ($alias, $address) = $sth->fetchrow;
 		my $string = qq!"$alias" <$address>!;
 		if (defined $self->{data}->{'to'} && !$field) {
			$oldval = $self->{data}->{'focused'}->get_text();
  			if ((defined $oldval) and ($oldval !~ /^\s*$/)) {
   				$string = $oldval . ", " . $string;
  			}
  			$self->{data}->{'focused'}->set_text($string);
 		}  elsif (defined $self->{data}->{'to'} && $field) {
			$oldval = $self->{data}->{$field}->get_text();
			if ((defined $oldval) and ($oldval !~ /^\s*$/)) {
				$string = $oldval . ", ". $string;
			}
			$self->{data}->{$field}->set_text($string);
		}	
		else {
  			if (!$field || $field eq "to") {
   				$fields[0] = $string;
   				$fields[1] = "";
  			} 
			elsif ($field eq "cc") {
   				$fields[0] = "";
   				$fields[1] = $string;
			} 
  			if (!$field || $field ne "bcc") {
				$fields[2] = "";
  				&Pronto::Compose::init_msg_window(0, $self->{data}, \@fields);
 			} else {
				$fields[0] = "";
				$fields[1] = "";
				$fields[2] = "";				
				&Pronto::Compose::init_msg_window(0, $self->{data}, \@fields);
				$self->{data}->{'bcc'}->set_text($string);
			}
		}
	}
	return 1;
}

sub build_address_box {
# ------------------------------------------------------------------
# Builds the box for either adding an address entry or
# modifying an address book entry.
#
	my ($self, $do) = @_;
	my (
			$hbox, $vbox, $ret_vbox, $label, $entry, $alias, $address, $clist, 
			$pub_key, @groups, $groups, $close, $save, $alignment, $hbuttonbox,
			$cancel, $delete, $combo, $node
		);
	if ($do eq 'mod') {
		$node = $self->get_selected or return;
		my ($sql, $sth);
		$sql = q!SELECT alias, address, public_key FROM addresses WHERE id = ?!;
		$sth = $main::conn->prepare ($sql);
		$sth->execute ($node->{id});
		($alias, $address, $pub_key) = $sth->fetchrow;
	}
	defined ($alias)   or $alias   = "";
	defined ($address) or $address = "";
	defined ($pub_key) or $pub_key = "";

	$ret_vbox = new Gtk::VBox (0, 0);
	$ret_vbox->border_width (10);
	$ret_vbox->set_spacing (10);
	$hbox = new Gtk::HBox (0, 0);
	$ret_vbox->pack_start ($hbox, 0, 0, 0);
	$hbox->show;
	$label = new Gtk::Label ('Alias');
	$label->set_justify ('center');
	$label->set_line_wrap (0);
	$hbox->pack_start ($label, 0, 0, 0);
	$label->show;
	$label->set_alignment (0.5, 0.5);
	$entry = new Gtk::Entry;
	$hbox->pack_start ($entry, 1, 1, 0);
	$entry->show;
	$entry->can_focus (1);
	$entry->set_max_length (0);
	$entry->set_visibility (1);
	$entry->set_editable (1);

	$self->{widgets}->{alias} = $entry;
	$self->{widgets}->{alias}->set_text ($alias) if $alias;
	
	$hbox = new Gtk::HBox (0, 0);
	$ret_vbox->pack_start ($hbox, 0, 0, 0);
	$hbox->show;
	$label = new Gtk::Label (_("Address"));
	$label->set_justify ('center');
	$label->set_line_wrap (0);
	$hbox->pack_start ($label, 0, 0, 0);
	$label->show;
	$label->set_alignment (0.5, 0.5);
	$hbox->set_child_packing ($label, 0, 0, 0, 'start');
	$entry = new Gtk::Entry;
	$hbox->pack_start ($entry, 1, 1, 0);
	$entry->show;
	$entry->can_focus (1);
	$entry->set_max_length (0);
	$entry->set_visibility (1);
	$entry->set_editable (1);
	
	$self->{widgets}->{address} = $entry;
	$self->{widgets}->{address}->set_text ($address) if $address;

	$hbox = new Gtk::HBox (0, 0);
	$ret_vbox->pack_start ($hbox, 0, 0, 0);
	$hbox->show;
				
	$label = new Gtk::Label (_("Public Key"));
	$label->set_justify ('center');
	$label->set_line_wrap (0);
	$hbox->pack_start ($label, 0, 0, 0);
	$label->show;
	$label->set_alignment (0.5, 0.5);

	$entry = new Gtk::Entry;
	$hbox->pack_start ($entry, 1, 1, 0);
	$entry->show;
	$entry = $entry;
	$entry->can_focus (1);
	$entry->set_max_length (0);
	$entry->set_visibility (1);
	$entry->set_editable (1);

	$self->{widgets}->{public_key} = $entry;
	$self->{widgets}->{public_key}->set_text ($pub_key) if $pub_key;
	
	$alignment = new Gtk::Alignment (0.5, 0.5, 0, 1);
	$ret_vbox->pack_start ($alignment, 0, 0, 0);
	$alignment->show;
	$hbuttonbox = new Gtk::HButtonBox;
	$alignment->add ($hbuttonbox);
	$hbuttonbox->show;
	$hbuttonbox = $hbuttonbox;
	$hbuttonbox->set_layout ('default_style');
	$hbuttonbox->set_spacing (0);
	$hbuttonbox->set_child_size (80, 27);
	$hbuttonbox->set_child_ipadding (0, 0);
		
	$save = new Gtk::Button (_("Save"));
	$hbuttonbox->add ($save);
	$save->show;
	$save->can_default (1);
	$save->can_focus (1);

	$cancel = new Gtk::Button (_("Cancel"));
	$hbuttonbox->add ($cancel);
	$cancel->show;
	$cancel->can_default (1);
	$cancel->can_focus (1);
	$cancel->signal_connect (clicked => sub { $self->{widgets}->{inner_vbox}->destroy });

	if ($do eq 'mod') {
		$hbox = new Gtk::HBox (0, 0);
		$ret_vbox->pack_start ($hbox, 0, 0, 0);
		$hbox->show;
		$alignment = new Gtk::Alignment (0.5, 1, 0, 1);
		$hbox->pack_start ($alignment, 0, 0, 0);
		$alignment->show;
		$hbuttonbox = new Gtk::HButtonBox;
		$alignment->add ($hbuttonbox);
		$hbuttonbox->show;
		$delete = new Gtk::Button (_("Delete From"));
		$hbuttonbox->add ($delete);
		$delete->show;
		$delete->can_default (1);
		$delete->can_focus (1);
		$delete->signal_connect (clicked => sub { $self->address_delete });

		$alignment = new Gtk::Alignment (0, 0.5, 0, 1);
		$hbox->pack_start ($alignment, 0, 0, 0);
		$alignment->show;
		$combo = new Gtk::Combo;
		my @list = ("Address Book");
		unshift (@list, "Group") if ((exists $node->{father}) and ($node->{father} ne $self->{top}));
		$combo->set_popdown_strings (@list);
		$combo->set_usize (112, 0);
		$alignment->add ($combo);
		$combo->entry->set_editable (0);
		$combo->show;
		$self->{widgets}->{del_from} = $combo->entry;
		$save->signal_connect (clicked => sub { $self->address_save });
	}
	else {
		$save->signal_connect (clicked => sub { $self->address_add });
	}
	return $ret_vbox;
}

sub refresh_right {
# ------------------------------------------------------------------
# Called to either put a group box or an address box
# on the right side of the window.
#
	my ($self, $do) = @_;
	my $new_vbox;
	my $node = $self->get_selected or return;
	
	if ($node->{type} and $node->{type} eq 'group') {
		$new_vbox = $self->build_group_box ($do);
	}
	else {
		$new_vbox = $self->build_address_box ($do);
	}

	$self->{widgets}->{inner_vbox}->destroy;
	$self->{widgets}->{outer_vbox}->add ($new_vbox);
	$new_vbox->show;
	$self->{widgets}->{inner_vbox} = $new_vbox;
	return 1;
}

sub address_add {
# ------------------------------------------------------------------
# Adds and address entry to the address book.
#
	my $self = shift;

	my ($sql, $sth);
	unless ($self->is_valid_address ('add')) {
		return;
	}
	my ($alias, $address, $pub_key) = ($self->{widgets}->{alias}->get_text, $self->{widgets}->{address}->get_text, $self->{widgets}->{public_key}->get_text);

	$sql = "INSERT INTO addresses (id, alias, address, public_key)
			values (?, ?, ?, ?)";
	$sth = $main::conn->prepare($sql);
	my $aid = main::newid('addresses', $main::conn) || 1;
	$sth->execute($aid, $alias, $address, $pub_key || '');

	# Insert the nodes
	my $add = $self->add_address_node ($aid, $alias);
	$self->{folder}->{0}->{$aid} = $add;
	$self->{widgets}->{inner_vbox}->destroy;
	return 1;
}

sub address_save {
# ------------------------------------------------------------------
# Modifies an existing address book entry.
#
	my $self = shift;
	my ($sql, $sth, $groups, @groups, $node);
	
	$node = $self->get_selected;
	unless ($node) {
		return main::err_dialog (_("No address select to save"));
	}
	
	unless ($self->is_valid_address ('mod')) {
		return;
	}
	my ($alias, $address, $pub_key) = ($self->{widgets}->{alias}->get_text, $self->{widgets}->{address}->get_text, $self->{widgets}->{public_key}->get_text);
	if ($alias ne $node->{text}) { $node->{text} = $alias }
	my $id = $node->{id};
	$sql = "UPDATE addresses SET alias = ?, address = ?, public_key = ? WHERE id = ?";
	$sth = $main::conn->prepare ($sql);
	$sth->execute($alias, $address, $pub_key || '', $id);
	$self->{widgets}->{tree}->freeze;
	for my $fid (keys %{$self->{folder}}) {
		next unless exists $self->{folder}->{$fid}->{$id};
		$self->{widgets}->{tree}->node_set_text ($self->{folder}->{$fid}->{$id}->{obj}, 0, $alias);
	}
	$self->{widgets}->{tree}->thaw;
	$self->{widgets}->{inner_vbox}->destroy;
	return 1;
	
}

sub address_delete {
# ------------------------------------------------------------------
# Deletes an address book entry either from the current group
# or from all groups.
#
	my $self = shift;
	my ($del, $groups, %groups, $alias, $fid, $node);
	$node = $self->get_selected;
	unless ($node) {
		return main::err_dialog (_("No address selected for deletion."));
	}
	my $id = $node->{id};
	
	$del = shift;
	if ((!$del) or ($del !~ /^Address Book|Group$/)) {
		if (exists $self->{widgets}->{del_from}) {
			$del = $self->{widgets}->{del_from}->get_text;
		}
		else {
			main::err_dialog (_("I do not know what to do with this input!"));
			return;
		}
	}

	$fid = $self->{tree}->{$node->{father}}->{id};
	if ($del eq "Address Book") {
		my ($sth, $sql);
		$sql = "DELETE FROM addresses WHERE id = ?";
		$sth = $main::conn->prepare ($sql);
		$sth->execute ($id);
		$self->{widgets}->{tree}->freeze;
		for my $fid (keys %{$self->{folder}}) {
			next unless exists $self->{folder}->{$fid}->{$id};
			$self->{widgets}->{tree}->remove_node ($self->{folder}->{$fid}->{$id}->{obj});
			delete $self->{folder}->{$fid}->{$id};
		}
		$self->{widgets}->{tree}->thaw;
	}
	elsif ($del eq 'Group') {
		if (($node->{father} eq $self->{top}) or not $node->{father}) {
			return main::err_dialog (_("This address does not belong to a group."));
		}
		$self->remove_address_from_group ($fid, $id);
		$self->{widgets}->{tree}->freeze;
		my $exists = 0;
		for my $gid (keys %{$self->{folder}}) {
			next if $gid eq $fid;
			if (exists $self->{folder}->{$gid}->{$id}) {
				$exists = 1;
			}
		}
		if (!$exists) {
			$self->{folder}->{0}->{$id} = $self->add_address_node ($id, $self->{folder}->{$fid}->{$id}->{text});
		}
		$self->{widgets}->{tree}->remove_node ($node->{obj});
		delete $self->{folder}->{$fid}->{$id};
		$self->{widgets}->{tree}->thaw;
	}
	else {
		return main::err_dialog (_("This is not an address!"));
	}

	$self->{widgets}->{inner_vbox}->destroy;
	return 1;
}

# $ctree->insert_node (parent,sibling,titles,spacing,pixmap_closed,mask_closed,pixmap_opened,mask_opened,is_leaf,expanded)
sub add_address_node {
# ------------------------------------------------------------------
# Addes an address entry to the tree on the left.
#
	my $self = shift;
	my ($id, $text, $father) = @_;
	$father ||= $self->{top};
	my $node = $self->{widgets}->{tree}->insert_node ($father, undef, [$text], 5, undef, undef, undef, undef, 0, 0);
	$self->{tree}->{$node} = { obj => $node, id => $id, type => 'address', father => $father, text => $text };
	return $self->{tree}->{$node};
}

sub del_address_node {
# ------------------------------------------------------------------
# Deletes and address book entry from the tree on the left.
#
	my $self = shift;
	my $node = shift || $self->get_selected or return;
	delete $self->{tree}->{$node->{obj}};
	$self->{widgets}->{tree}->remove_node ($node->{obj});
	return 1;
}

sub address_new {
# ------------------------------------------------------------------
# Changes the box on the right into an add address box.
#
	my $self = shift;
	my $new_vbox = $self->build_address_box ('add');
	$self->{widgets}->{inner_vbox}->destroy;
	$self->{widgets}->{outer_vbox}->add ($new_vbox);
	$new_vbox->show;
	$self->{widgets}->{inner_vbox} = $new_vbox;
	return 1;
}

sub group_new {
# ------------------------------------------------------------------
# Changes the box on the right into an add group box.
#
	my $self = shift;
	my $new_vbox = $self->build_group_box ('add');
	$self->{widgets}->{inner_vbox}->destroy;
	$self->{widgets}->{outer_vbox}->add ($new_vbox);
	$new_vbox->show;
	$self->{widgets}->{inner_vbox} = $new_vbox;
	return 1;
}

sub is_valid_address {
# ------------------------------------------------------------------
# Verifies that an address entry either being made or modified
# is valid.
#
	my ($self, $do) = @_;
	
	my ($sql, $sth, @row);
	my ($alias, $address, $pub_key) = ($self->{widgets}->{alias}->get_text, $self->{widgets}->{address}->get_text, $self->{widgets}->{public_key}->get_text);
	
	if (!$alias) {
		main::err_dialog (_("You must have an Alias for the address."));
		return;
	}
	if (!$address) {
		main::err_dialog (_("You must have an address."));
		return;
	}
       
	$sql = "SELECT alias FROM addresses WHERE alias = ?";
	$sth = $main::conn->prepare($sql);
	$sth->execute($alias);
	(@row) = $sth->fetchrow_array();
	if (scalar(@row) == 1 and $do eq 'add') {
		main::err_dialog(_("Alias exists, please enter another."));
		return;
	}
	elsif (scalar(@row) > 1 and $do eq 'mod') {
		main::err_dialog(_("Alias exists, please enter another."));
		return;
	}
	
	$sql = "SELECT name FROM groups WHERE name = ?";
	$sth = $main::conn->prepare($sql);
	$sth->execute($alias);
	(@row) = $sth->fetchrow_array();
	if (scalar(@row) > 0) {
		main::err_dialog(_("Alias is the same name as a group."));
		return;
	}
	return 1;
}

sub build_group_box {
# ------------------------------------------------------------------
# Returns either a box to add a group or a box to modify
# a group.
#
	my ($self, $do) = @_;
	my (
			$hbox, $vbox, $ret_vbox, $label, $entry, $alias, $address, $clist, 
			$pub_key, @groups, $groups, $close, $save, $alignment, $hbuttonbox,
			$cancel, $delete, $add, $remove, $name, $vbuttonbox, $scrolledwindow,
			$sql, $sth, $node
		);
	if ($do eq 'mod') {
		$node = $self->get_selected or return;
		($name) = $node->{text};
	}
	
	$ret_vbox = new Gtk::VBox (0, 0);
	$hbox = new Gtk::HBox (0, 0);
	$ret_vbox->pack_start ($hbox, 0, 0, 0);
	$hbox->show;
	$hbox->border_width (20);
	
	$label = new Gtk::Label ('Name');
	$label->set_justify ('center');
	$label->set_line_wrap (0);
	$hbox->pack_start ($label, 0, 0, 0);
	$label->show;
	$label->set_alignment (0.5, 0.5);
	
	$entry = new Gtk::Entry;
	$hbox->pack_start ($entry, 0, 0, 0);
	$entry->show;
	$entry->can_focus (1);
	$entry->set_text ('');
	$entry->set_max_length (0);
	$entry->set_visibility (1);
	$entry->set_editable (1);

	$self->{widgets}->{name} = $entry;
	$self->{widgets}->{name}->set_text ($name) if (defined $name);
	
	$hbox = new Gtk::HBox (0, 0);
	$ret_vbox->pack_start ($hbox, 1, 1, 0);
	$hbox->show;
	
	$scrolledwindow = new Gtk::ScrolledWindow (undef, undef);
	$scrolledwindow->set_policy ('automatic', 'automatic');
	$scrolledwindow->border_width (0);
	$scrolledwindow->hscrollbar->set_update_policy ('continuous');
	$scrolledwindow->vscrollbar->set_update_policy ('continuous');	
	$scrolledwindow->show;
	
	$clist = new Gtk::CList (1);
	$clist->set_selection_mode ('single');
	$clist->set_border ('in');
	$clist->set_column_width (0, 80);
	$scrolledwindow->add ($clist);
	$clist->show;
	$clist->can_focus (1);
	
	my $frame = new Gtk::Frame(_("Addresses"));
	$frame->add($scrolledwindow);
	$frame->set_shadow_type('etched_out');
	$frame->show;
	$hbox->pack_start ($frame, 1, 1, 0);		

	$self->{widgets}->{group1} = $clist;

	$alignment = new Gtk::Alignment (0.5, 0.5, 1, 0);
	$hbox->pack_start ($alignment, 0, 0, 0);
	$alignment->show;
		
	$vbuttonbox = new Gtk::VButtonBox;
	$alignment->add ($vbuttonbox);
	$vbuttonbox->show;
	$vbuttonbox = $vbuttonbox;
	$vbuttonbox->set_layout ('default_style');
	$vbuttonbox->set_spacing (0);
	$vbuttonbox->set_child_size (2, 27);
	$vbuttonbox->set_child_ipadding (0, 0);
	
	$add = new Gtk::Button ('>>');
	$vbuttonbox->add ($add);
	$add->show;
	$add->can_default (1);
	$add->can_focus (1);
	
	$remove = new Gtk::Button ('<<');
	$vbuttonbox->add ($remove);
	$remove->show;
	$remove->can_default (1);
	$remove->can_focus (1);
	
	$scrolledwindow = new Gtk::ScrolledWindow ( undef, undef);
	$scrolledwindow->set_policy ('automatic', 'automatic');
	$scrolledwindow->border_width (0);
	$scrolledwindow->hscrollbar->set_update_policy ('continuous');
	$scrolledwindow->vscrollbar->set_update_policy ('continuous');
	$scrolledwindow->show;
		
	$clist = new Gtk::CList (1);
	$clist->set_selection_mode ('single');
	$clist->set_border ('in');
	$clist->set_column_width (0, 80);
	$scrolledwindow->add ($clist);
	$clist->show;
	$clist->can_focus (1);
	
	$frame = new Gtk::Frame(_("Group"));
	$frame->add($scrolledwindow);
	$frame->show();
	$hbox->pack_start ($frame, 1, 1, 0);
	
	$self->{widgets}->{group2} = $clist;
	my $move_right = sub {
		my @selected = $self->{widgets}->{group1}->selection;
		@selected or return 1;
		my ($id, $text) = map { $self->{widgets}->{group1}->get_row_data ($_), $self->{widgets}->{group1}->get_text ($_, 0) } @selected;
		$self->{widgets}->{group1}->remove ($selected[0]);
		my $row = $self->{widgets}->{group2}->append ($text);
		$self->{widgets}->{group2}->set_row_data ($row, $id);
		my $num_rows = $self->{widgets}->{group1}->rows;
		($selected[0] > $num_rows) and $selected[0] = 0;
		$self->{widgets}->{group1}->select_row ($selected[0], 0) if ($num_rows >= 0);
	};
	my $move_left = sub {
		my @selected = $self->{widgets}->{group2}->selection;
		@selected or return 1;
		my ($id, $text) = map { $self->{widgets}->{group2}->get_row_data ($_), $self->{widgets}->{group2}->get_text ($_, 0) } @selected;
		$self->{widgets}->{group2}->remove ($selected[0]);
		my $row = $self->{widgets}->{group1}->append ($text);
		$self->{widgets}->{group1}->set_row_data ($row, $id);
		my $num_rows = $self->{widgets}->{group2}->rows;
		($selected[0] > $num_rows) and $selected[0] = 0;
		$self->{widgets}->{group2}->select_row ($selected[0], 0) if ($num_rows >= 0);
	};
	$remove->signal_connect (clicked => $move_left);
	$add->signal_connect (clicked => $move_right);
	$self->{widgets}->{group1}->signal_connect ("button_press_event" => sub { 
		my $event = pop;
		if ($event->{type} eq "2button_press") { $move_right->() } 
		return 1;
	});
	$self->{widgets}->{group2}->signal_connect ("button_press_event" => sub { 
		my $event = pop;
		if ($event->{type} eq "2button_press") { $move_left->() } 
		return 1;
	});
	
	$sql = q!SELECT alias, id, groups FROM addresses!;
	$sth = $main::conn->prepare ($sql);
	$sth->execute;

	while (my ($alias, $aid, $groups) = $sth->fetchrow) {
		if ($do eq 'mod') {
			if (defined ($groups) and (grep { $_ eq $node->{id} } split (',' => $groups))) {
				my $row = $self->{widgets}->{group2}->append ($alias);
				$self->{widgets}->{group2}->set_row_data ($row, \$aid);
				next;
			}
		}
		my $row = $self->{widgets}->{group1}->append ($alias);
		$self->{widgets}->{group1}->set_row_data ($row, \$aid);
	}
	
	$alignment = new Gtk::Alignment (0.5, 0.5, 0, 1);
	$ret_vbox->pack_start ($alignment, 0, 0, 0);
	$alignment->show;
	
	$hbuttonbox = new Gtk::HButtonBox;
	$alignment->add ($hbuttonbox);
	$hbuttonbox->show;
	$hbuttonbox->set_layout ('default_style');
	$hbuttonbox->set_spacing (0);
	$hbuttonbox->set_child_size (80, 27);
	$hbuttonbox->set_child_ipadding (0, 0);

	$save = new Gtk::Button ('Save');
	$hbuttonbox->add ($save);
	$save->show;
	$save->can_default (1);
	$save->can_focus (1);

	if ($do eq 'mod') {
		$save->signal_connect (clicked => sub { $self->group_save });
		$delete = new Gtk::Button ("Delete");
		$hbuttonbox->add ($delete);
		$delete->show;
		$delete->can_default (1);
		$delete->can_focus (1);
		$delete->signal_connect (clicked => sub {$self->group_delete });
	}
	else {
		$save->signal_connect (clicked => sub { $self->group_add });
	}
	
	$cancel = new Gtk::Button (_("Cancel"));
	$hbuttonbox->add ($cancel);
	$cancel->show;
	$cancel->can_default (1);
	$cancel->can_focus (1);

	$cancel->signal_connect (clicked => sub { $self->{widgets}->{inner_vbox}->destroy });

	return $ret_vbox;
}

sub group_save {
# ------------------------------------------------------------------
# Saves a group someone just modified.
#
	my $self = shift;
	my ($name, $id, $sql, $sth, @row, $text, $node);
	$node = $self->get_selected or return;

	$name = $self->{widgets}->{name}->get_text;
	$id   = $node->{id};
	$text = $node->{text};
	if (!$name) {
		return main::err_dialog (_("You must specify a name for your group."));
	}

	if ($text ne $name) {
		$sql = "SELECT name FROM groups WHERE name = ?";
		$sth = $main::conn->prepare ($sql);
		$sth->execute ($name);
		(@row) = $sth->fetchrow_array();
		if (scalar(@row) > 0) {
			return main::err_dialog (_("The group name you specified is already in use."));
		}
		$sql = "UPDATE groups SET name = ? WHERE id = ?";
		$sth = $main::conn->prepare ($sql);
		$sth->execute ($name, $id);
		$self->{widgets}->{tree}->node_set_text ($node->{obj}, 0, $name);
		$node->{text} = $name;
	}
	
	my %sel;
	for (0 .. $self->{widgets}->{group2}->rows - 1) {
		my $aid  = ${$self->{widgets}->{group2}->get_row_data ($_)};
		my $text = $self->{widgets}->{group2}->get_text ($_, 0);
		$sel{$aid} = $text;
	}

	# Find out if anything was removed
	for my $aid (keys %{$self->{folder}->{$id}}) {
		if (!exists $sel{$aid}) {
			my $exists = 0;
			for my $fid (keys %{$self->{folder}}) {
				next if $fid eq $id;
				if (exists $self->{folder}->{$fid}->{$aid}) {
					$exists = 1;
				}
			}
			if (!$exists) {
				$self->{folder}->{0}->{$aid} = $self->add_address_node ($aid, $self->{folder}->{$id}->{$aid}->{text});
			}
			$self->del_address_node ($self->{folder}->{$id}->{$aid});
			delete $self->{folder}->{$id}->{$aid};
			$self->remove_address_from_group ($id, $aid);
		}
	}

	# Find out what was added
	for my $aid (keys %sel) {
		
		# It is top level and was added to here
		if (exists $self->{folder}->{0}->{$aid}) {
			$self->del_address_node ($self->{folder}->{0}->{$aid});
			delete $self->{folder}->{0}->{$aid};
		}

		# It was added
		if (!exists $self->{folder}->{$id}->{$aid}) {
			my $add = $self->add_address_node ($aid, $sel{$aid}, $node->{obj});
			$self->{folder}->{$id}->{$aid} = $add;
			$self->add_address_to_group ($id, $aid);
		}
	}
	$self->{widgets}->{inner_vbox}->destroy;
	return 1;
}

sub group_delete {
# ------------------------------------------------------------------
# Deletes a group. The address in the group are not deleted.
# If they belong to anouther group they stay in that one.
# If they belong to no other group they are moved to the
# top level of the tree.
#
	my $self = shift;
	my ($sth, $node, $sql);

	$node = $self->get_selected or return;
	$sql  = "DELETE FROM groups WHERE id = ?";
	$sth  = $main::conn->prepare ($sql);
	$sth->execute ($node->{id});
	my $id = $node->{id};

	for my $aid (keys %{$self->{folder}->{$id}}) {
		my $exists = 0;
		for my $fid (keys %{$self->{folder}}) {
			next if $fid eq $id;
			if (exists $self->{folder}->{$fid}->{$aid}) {
				$exists = 1;
			}
		}
		if (!$exists) {
			$self->{folder}->{0}->{$aid} = $self->add_address_node ($aid, $self->{folder}->{$id}->{$aid}->{text});
		}
		$self->remove_address_from_group ($node->{id}, $aid);
	}
	delete $self->{folder}->{$id};
	$self->{widgets}->{tree}->remove_node ($node->{obj});
	$self->{widgets}->{inner_vbox}->destroy;
}

sub group_add {
# ------------------------------------------------------------------
# Addes a group with any address that were put into it.
# NOTE: Addresses can belong to multiple groups.
#
	my $self = shift;
	my ($name, $id, $sql, $sth, @row, $node);
	
	$name = $self->{widgets}->{name}->get_text;
	if (!$name) {
		return main::err_dialog (_("You must specify a name for your group"));
	}
	$sql = "SELECT name FROM groups WHERE name = ?";
	$sth = $main::conn->prepare ($sql);
	$sth->execute ($name);
	(@row) = $sth->fetchrow_array();
	if (scalar(@row) > 0) {
		return main::err_dialog (_("The group name you specified is already in use."));
	}
	$id  = main::newid('groups', $main::conn) || 1;
	$sql = "INSERT INTO groups (id,name) values (?,?)";
	$sth = $main::conn->prepare ($sql);
	$sth->execute ($id, $name);

	my %sel;
	for (0 .. $self->{widgets}->{group2}->rows - 1) {
		my $aid  = ${$self->{widgets}->{group2}->get_row_data ($_)};
		my $text = $self->{widgets}->{group2}->get_text ($_, 0);
		$sel{$aid} = $text;
	}

	$node = $self->add_group_node ($id, $name);
	
	# Find out what was added
	for my $aid (keys %sel) {
		
		# It is top level and was added to here
		if (exists $self->{folder}->{0}->{$aid}) {
			$self->del_address_node ($self->{folder}->{0}->{$aid});
			delete $self->{folder}->{0}->{$aid};
		}

		my $add = $self->add_address_node ($aid, $sel{$aid}, $node->{obj});
		$self->{folder}->{$id}->{$aid} = $add;
		$self->add_address_to_group ($id, $aid);
	}
	$self->{widgets}->{inner_vbox}->destroy;
	return 1;
}

# $ctree->insert_node (parent,sibling,titles,spacing,pixmap_closed,mask_closed,pixmap_opened,mask_opened,is_leaf,expanded)
sub add_group_node {
# ------------------------------------------------------------------
# Addes a group node to the tree on the left.
#
	my ($self, $id, $text, $top) = @_;
	$top ||= $self->{top};
	my $gnode = $self->{widgets}->{tree}->insert_node ($top, undef, [$text], 5, undef, undef, undef, undef, 0, 0);
	$self->{tree}->{$gnode} = { obj => $gnode, id => $id, type => 'group', text => $text };
	return $self->{tree}->{$gnode};
}

sub remove_address_from_group {
# ------------------------------------------------------------------
# Removes  an address from a group. This is where it removes the 
# entry in the SQL table.
#
	my ($self, $fid, $aid) = @_;
	my $sql;
	$sql = "SELECT groups FROM addresses WHERE id = ?";
	my $grop = $main::conn->prepare ($sql);
	$sql = "UPDATE addresses SET groups = ? WHERE id = ?";
	my $updt = $main::conn->prepare ($sql);
	$grop->execute ($aid);
	my ($groups) = $grop->fetchrow;
	return 1 unless $groups;
	my %groups = map { $_ => 1 } split "," => $groups;
	if (exists $groups{$fid}) {
		delete $groups{$fid};
		my $new = join "," => keys %groups;
		$updt->execute ($new, $aid);
	}
	return 1;
}

sub add_address_to_group {
# ------------------------------------------------------------------
# Addes and address to a group.
#
	my ($self, $fid, $aid) = @_;
	my $sql;
	$sql = "SELECT groups FROM addresses WHERE id = ?";
	my $sel = $main::conn->prepare ($sql);
	$sql = "UPDATE addresses SET groups = ? WHERE id = ?";
	my $upd = $main::conn->prepare ($sql);

	$sel->execute ($aid);
	my ($groups) = $sel->fetchrow;
	$groups ||= '';
	my %groups = map { $_ => 1 } split "," => $groups;
	$groups{$fid} = 1;
	$groups = join "," => keys %groups;
	$upd->execute ($groups, $aid);
	return 1;
}

sub get_selected {
# ------------------------------------------------------------------
# Returns a hash of 
# { obj => node object, text => node text, id => table id, father => father node object }
# for the currently selected object in the tree.
#
	my $self = shift;
	my ($node) = $self->{widgets}->{tree}->selection;
	unless ($node) { $self->{widgets}->{inner_vbox}->destroy; return }
	unless (exists $self->{tree}->{$node}) { $self->{widgets}->{inner_vbox}->destroy; return }
	return $self->{tree}->{$node};
}

1;

