# $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;