#!/usr/bin/perl -w # # A rolodex like contact management application with vcard support. # # Copiryght (C) 2006 Alessandro Dotti Contra # # Revision: 0.5 #=============================================================================== # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # For any questions related to this software, please write to: # # Alessandro Dotti Contra # v. Verne, 6 40128 Bologna (BO) # ITALY # # or email to: alessandro@hyboria.org #============================================================================== # CONFIGURATION #============================================================================== my $DATADIR = "/home/adotti/infobook"; #============================================================================== # MODULES #============================================================================== use Text::vCard; use Text::vCard::Node; use Text::vCard::Addressbook; use Curses::UI; #============================================================================== # GLOBAL DATA #============================================================================== my %cards = (); my %filed_cards = (); my %browser = (); my %viewer = (); my @categories = (); my $browser = undef; #============================================================================== # FUNCTIONS (prototypes) #============================================================================== sub read_cards($;$); sub load_browser($$); sub view_card(); sub read_card($); sub show_category(); #============================================================================== # # Read cards # read_cards($DATADIR); #============================================================================== # INTERFACE #============================================================================== # # Create the root object. # my $cui = new Curses::UI ( -clear_on_exit => 1, -color_support => 1, ); # # Create the main menu # # File submenu # my $submenu_file = [ { -label => 'Exit ^Q', -value => \&exit_app }, ]; my @menu = ( { -label => 'File', -submenu => $submenu_file }, ); my $menu = $cui->add( 'menu','Menubar', -menu => \@menu, -fg => "green", ); # # Create the main window # my $main = $cui->add( 'main', 'Window', -y => 1, ); # Create the filter popup # my $values = []; my $labels = {}; $values->[0] = 0; $labels->{0} = "All cards"; my $i = 1; foreach my $category (sort @categories) { my $cards = values %{ $filed_cards{$category} }; if($cards) { $values->[$i] = $i; $labels->{$i} = $category; $i++; } } my $filter = $main->add( undef, 'Popupmenu', -y => 1, -values => $values, -labels => $labels, -width => 50, -onchange => \&show_category, ); # Create the main browser # $browser = load_browser("All cards", \%cards); $browser{'All cards'} = $browser; # # Set bindings # $cui->set_binding(sub {$menu->focus()}, "\cX"); $cui->set_binding(sub {$browser->focus()}, "\cB"); $cui->set_binding(sub {$filter->focus()}, "\cF"); $cui->set_binding(sub {view_card()}, "\cV"); $cui->set_binding(sub {exit 0}, "\cQ"); $browser->focus(); $cui->mainloop(); exit 0; #============================================================================== # FUNCTIONS #============================================================================== sub read_cards($;$) { # Read cards from the data directory and file them # my $dir = shift; my $pcat = shift; $pcat = "" unless $pcat; opendir(DIR,"$dir") || die "Can't read directory $dir: $!\n"; chdir $dir; my @items = readdir(DIR); foreach my $item(sort @items) { next if $item =~ /^\./; if(-d $item) { my $category = $pcat . ":$item"; push @categories, $category unless grep(/$category/,@categories); $filed_cards{$category} = (); read_cards($item,$category); } if(-f $item && $item =~ /\.vcf$/) { my $abook = Text::vCard::Addressbook->new( { 'source_file' => $item, } ); foreach my $vcard($abook->vcards()) { my $category = $pcat; my $name = $vcard->get({ 'node_type' => 'name' }); my $vname = ""; if($name) { $vname = $name->[0]->family() . " " . $name->[0]->given(); } else { $vname = $vcard->fullname(); } $cards{$vname} = $vcard; $filed_cards{$category}{$vname} = $vcard; # Read additional categories (if any) and file card again # my $xtags = $vcard->get('X-TAG'); foreach $xtag(@{ $xtags }) { my $tag = $category . ":" . $xtag->value(); push @categories, $tag unless grep(/$tag/,@categories); $filed_cards{$tag}{$vname} = $vcard; } } } } closedir(DIR); chdir ".."; } sub load_browser($$) { my $title = shift; my $hashref = shift; my $values = []; my $labels = {}; my $i = 0; foreach my $name(sort keys %{ $hashref }) { $values->[$i] = $i; $labels->{$i} = $name; $i++; } my $listbox = $main->add( undef, 'Listbox', -y => 2, -values => $values, -labels => $labels, -title => $title, -pad => 1, -border => 1, -bfg => 'green', -vscrollbar => 1, -onchange => \&view_card, ); return $listbox; } sub view_card() { my $listbox; if(@_) { $listbox = shift; } else { $listbox = $browser; } my $selected = $listbox->get(); my $name = $listbox->{-labels}->{$selected}; my $w = undef; if(exists($viewer{$name})){ $w = $viewer{$name}; } else { $w = $main->add( undef, 'Window', -y => 2, -border => 1, -pad => 4, -title => $name, -x => 5, ); my $content = read_card($cards{$name}); $w->add( undef, 'TextViewer', -pad => 1, -vscrollbar => 1, -hscrollbar => 1, -text => $content, ); $viewer{$name} = $w; } $w->focus(); } sub read_card($) { my $vcard = shift; my $content = ""; # # print name and title # my $name = $vcard->get({ 'node_type' => 'name' }); if($name) { $content .= $name->[0]->prefixes() . " " if $name->[0]->prefixes(); $content .= $name->[0]->given() . " "; $content .= $name->[0]->family() . " "; $content .= "[" . $vcard->title() . "]" if $vcard->title(); } else { $content .= $vcard->fullname(); } $content .= "\n\n"; # # print organizations and role # my $organizations = $vcard->get({ 'node_type' => 'ORG' }); foreach my $organization(@{ $organizations }) { $content .= $organization->name(); $content .= "\n"; my $units = $organization->unit(); foreach my $unit(@{ $units }) { $content .= "[$unit]\n"; } } $content .= "(" . $vcard->role() . ")\n" if $vcard->role(); $content .= "\n"; # # print addresses # my $addresses = $vcard->get({ 'node_type' => 'addresses', }); if ($addresses) { $content .= "Addresses\n"; $content .= "---------\n"; foreach my $address(@{ $addresses }) { my @types = $address->types(); foreach my $type(sort @types) { $content .= sprintf("%-10s:", $type); } $content .= $address->po_box() if $address->po_box(); $content .= " " . $address->extended() if $address->extended(); $content .= " " . $address->street() if $address->street(); $content .= " " . $address->city() if $address->city(); $content .= " (" . $address->region() . ")" if $address->region(); $content .= " " . $address->post_code() if $address->post_code(); $content .= " " . $address->country() if $address->country(); $content .= "\n"; } $content .= "\n"; } # # print telephone numbers # my $phones = $vcard->get({ 'node_type' => 'phones', }); if($phones) { $content .= "Phones\n"; $content .= "------\n"; foreach my $phone(@{ $phones }) { my @types = $phone->types(); my $types = ""; foreach my $type(sort @types) { $types .= "$type "; } $content .= sprintf("%-10s: ", $types); $content .= $phone->value(); $content .= "\n"; } $content .= "\n"; } # # print emails # my $emails = $vcard->get({ 'node_type' => 'email' }); if($emails) { $content .= "e-mails\n"; $content .= "-------\n"; foreach my $email(@{ $emails }) { $content .= $email->value() . "\n"; } $content .= "\n"; } # # print urls # my $urls = $vcard->get({ 'node_type' => 'url' }); if($urls) { $content .= "urls\n"; $content .= "----\n"; foreach my $url(@{ $urls }) { $content .= $url->value() . "\n"; } $content .= "\n"; } return $content; } sub show_category() { my $pm = shift; my $val = $pm->get; my $selected = $pm->{-labels}->{$val}; if($browser{$selected}) { $browser = $browser{$selected}; } else { $browser = load_browser($selected, \%{ $filed_cards{$selected} }); $browser{$selected} = $browser; } $browser->focus(); }