# $Id: AddressBook.pm,v 1.6 2002/05/19 06:20:46 muhri Exp $
# -*- perl -*-
package Pronto::AddressBook;
use strict;
use Data::Dumper;
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);
if ($main::prefs{'AddybookSort'}) {
$ctree->set_sort_type('ascending');
} else {
$ctree->set_sort_type('descending');
}
$ctree->sort_recursive(undef);
return 1;
}
sub ctree_click_column
{
my ($ctree, $column) = @_;
if ($main::prefs{'AddybookSort'} == 0) {
$ctree->set_sort_type('ascending');
$main::prefs{'AddybookSort'} = 1;
} else {
$ctree->set_sort_type('descending');
$main::prefs{'AddybookSort'} = 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;
syntax highlighted by Code2HTML, v. 0.9.1