/* elmo - ELectronic Mail Operator Copyright (C) 2003, 2004 rzyjontko 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; version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ---------------------------------------------------------------------- This file contains an implementation of an address book. */ /**************************************************************************** * IMPLEMENTATION HEADERS ****************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #ifdef HAVE_HOME_ETC_H # include # define HOMEDIR_RC _HEdir #else # define HOMEDIR_RC getenv("HOME") #endif #include "address.h" #include "raddress.h" #include "error.h" #include "file.h" #include "xmalloc.h" #include "ask.h" #include "cmd.h" #include "mail.h" #include "select.h" #include "folder.h" #include "read.h" #include "gettext.h" #include "memblock.h" #include "confread.h" #include "abook.h" #include "eprintf.h" #include "str.h" #include "sender.h" #include "ecurses.h" #include "color.h" #include "label.h" #include "interface.h" #include "bitarray.h" /**************************************************************************** * IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS ****************************************************************************/ #define INITIAL_BLOCK_SIZE 2048 #define DFL_LOCATION() file_with_dir (HOMEDIR_RC, ".addressbook") #define NONEMPTY do { if (abook->count == 0) return; } while (0) /**************************************************************************** * IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES ****************************************************************************/ /** * There is only one structure of this type. It is used to collect data * about the address being read from the addressbook file. */ typedef struct new_item { address_t *addr; char **s_field; int shift; unsigned mask; int group; } new_item_t; /**************************************************************************** * IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID) ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE DATA ****************************************************************************/ /** * This is to decide if the addressbook should be flushed back to the file. */ static int abook_changed = 0; /** * This is to decide if the addressbook should be sorted before showing * it to the user. */ static int abook_sorted = 0; /** * This is the address book itself. It's just a set of addresses grouped * in a single array. */ static raddress_t *abook = NULL; /* Address window contains all addresses defined in a message, so user can easily add new addresses to abook. It consists of select_t object, optional label, and set of addresses. */ static elabel_t *label_add = NULL; static select_t *add_select = NULL; static raddress_t *add_raddress = NULL; /* Addressbook window constis of select_t object and an optional label. We also have to store the information about which addresses have been selected. */ static elabel_t *label = NULL; static select_t *abook_select = NULL; static bitarray_t *selected = NULL; /* Colors used in abook window. */ static chtype text_color; static chtype picked_color; static chtype picked_bar_color; static chtype hilight_color; /** * This is used when reading configuration. */ static new_item_t new_item = {NULL, NULL, 0, 0, 0}; /* Addresses are usually parsed by address.c module which has its own memory block to allocate all the strings used in addresses. We have to store our data in some other location. */ static memblock_t *addr_block = NULL; /* This string is used to eprintf the address, and display in the window. */ static str_t *str_line = NULL; /* This is used as format in eprintf_addr. */ static char *abook_fmt = "%s %020n %e"; /**************************************************************************** * INTERFACE DATA ****************************************************************************/ /**************************************************************************** * IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES ****************************************************************************/ /* This file is generated by interface.pl script from interface.desc, and inc.in. */ static WINDOW *interface_init (void); #include "abook.inc" static WINDOW *interface_init_2 (void); #include "abook_add.inc" /**************************************************************************** * IMPLEMENTATION PRIVATE FUNCTIONS ****************************************************************************/ static int int_value (const char *s) { if (strcmp (s, "M") == 0) return SEX_MALE; if (strcmp (s, "m") == 0) return SEX_MALE; if (strcmp (s, "male") == 0) return SEX_MALE; if (strcmp (s, "F") == 0) return SEX_FEMALE; if (strcmp (s, "f") == 0) return SEX_FEMALE; if (strcmp (s, "female") == 0) return SEX_FEMALE; if (strcmp (s, "Y") == 0) return YES; if (strcmp (s, "y") == 0) return YES; if (strcmp (s, "yes") == 0) return YES; if (strcmp (s, "YES") == 0) return YES; if (strcmp (s, "1") == 0) return YES; if (strcmp (s, "true") == 0) return YES; if (strcmp (s, "t") == 0) return YES; if (strcmp (s, "T") == 0) return YES; return NO; } static void write_groups (rstring_t *groups, FILE *fp) { int i; for (i = 0; i < groups->count; i++){ fprintf (fp, " group:\t\t%s\n", groups->array[i]); } } static void write_addr (address_t *addr, FILE *fp) { fprintf (fp, "addr {\n"); /** * it won't work if name has both kinds of quotes (single and double) */ if (addr->name){ if (addr->flags.bits.atomic_name) fprintf (fp, " name:\t\t%s\n", addr->name); else if (! addr->flags.bits.quotes_in_name) fprintf (fp, " name:\t\t\"%s\"\n", addr->name); else fprintf (fp, " name:\t\t'%s'\n", addr->name); } if (addr->email) fprintf (fp, " email:\t\t%s\n", addr->email); if (addr->initials) fprintf (fp, " initials:\t%s\n", addr->initials); if (addr->flags.bits.sex == SEX_MALE) fprintf (fp, " sex:\t\tM\n"); else if (addr->flags.bits.sex == SEX_FEMALE) fprintf (fp, " sex:\t\tF\n"); if (addr->flags.bits.official) fprintf (fp, " official:\tY\n"); if (addr->flags.bits.foreign) fprintf (fp, " foreign:\tY\n"); if (addr->groups) write_groups (addr->groups, fp); fprintf (fp, "}\n"); } static int cmp_addr (const void *a, const void *b) { const address_t **aa = (const address_t **) a; const address_t **bb = (const address_t **) b; return address_cmp (*aa, *bb); } static void set_color (WINDOW *win, int index) { if (win == abook_select->win){ if (bitarray_is_set (selected, index)) wattrset (win, picked_color); else wattrset (win, text_color); } else { if (bitarray_is_set (selected, index)) wattrset (win, picked_bar_color); else wattrset (win, select_bar_color ()); } } static void set_hilight_color (WINDOW *win) { if (win == abook_select->win) wattrset (win, hilight_color); else wattrset (win, select_hilight_color ()); } static void unset_hilight_color (WINDOW *win) { if (win == abook_select->win) wattrset (win, text_color); else wattrset (win, select_bar_color ()); } static int add_string (WINDOW *win, char *str, int maxlen, search_t *search) { int pos = -1; int len = 1; if (maxlen <= 0) return 0; if (search) pos = search_perform (search, str); if (pos != -1){ len = search->pattern->len; pos -= len - 1; if (pos < 0) pos = 0; window_addnstr (win, str, pos); set_hilight_color (win); window_addnstr (win, str + pos, len); unset_hilight_color (win); maxlen -= pos + len; } return pos + len + window_addnstr (win, str + pos + len, maxlen - 1); } static void abook_draw_line (WINDOW *win, int maxlen, int index, search_t *search) { if (str_line == NULL) str_line = str_create (); if (index >= 0 && abook && index < abook->count){ eprintf_addr_str (abook_fmt, abook->array[index], str_line); set_color (win, index); maxlen -= add_string (win, str_line->str, maxlen, search); } while (maxlen-- > 0) window_addch (win, ' '); } static int abook_count (select_t *nothing) { return (abook) ? abook->count : 0; } static int match_groups (search_t *search, rstring_t *groups) { int i; for (i = 0; i < groups->count; i++){ if (search_perform (search, groups->array[i]) != -1) return 1; } return 0; } static int match_address (search_t *search, int index) { address_t *addr; if (index < 0 || index >= abook->count) return 0; addr = abook->array[index]; if (addr->name && search_perform (search, addr->name) != -1) return 1; if (addr->email && search_perform (search, addr->email) != -1) return 1; if (addr->groups && match_groups (search, addr->groups)) return 1; return 0; } static void add_draw_line (WINDOW *win, int maxlen, int index, search_t *search) { if (str_line == NULL) str_line = str_create (); if (index >= 0 && add_raddress && index < add_raddress->count){ eprintf_addr_str (abook_fmt, add_raddress->array[index], str_line); maxlen -= window_addnstr (win, str_line->str, maxlen); } while (maxlen-- > 0) window_addch (win, ' '); } static int add_count (select_t *nothing) { return (add_raddress) ? add_raddress->count : 0; } static void redraw_label (void) { if (label == NULL) return; if (str_line == NULL) str_line = str_create (); eprintf_addr_desc (abook_fmt, str_line); label_set_text (label, str_line->str); label_redraw (label); } static void redraw_add_label (void) { if (label_add == NULL) return; if (str_line == NULL) str_line = str_create (); eprintf_addr_desc (abook_fmt, str_line); label_set_text (label_add, str_line->str); label_redraw (label_add); } static void destroy_groups (void) { int i; address_t *addr; for (i = 0; i < abook->count; i++){ addr = abook->array[i]; if (addr->groups) rstring_delete (addr->groups); } } /**************************************************************************** * INTERFACE FUNCTIONS ****************************************************************************/ void abook_init (void) { WINDOW *window; window = interface_init_2 (); add_select = select_open (window, 0, add_draw_line, add_count); window_set_functions (window, abook_add_refresh, abook_add_redraw, abook_add_set_focus, abook_add_unset_focus); window = interface_init (); abook_select = select_open (window, 0, abook_draw_line, abook_count); window_set_functions (window, abook_refresh, abook_redraw, abook_set_focus, abook_unset_focus); abook_load (); } void abook_free_resources (void) { destroy_groups (); if (addr_block) memblock_destroy (addr_block); addr_block = NULL; if (abook) raddress_destroy (abook); abook = NULL; if (str_line) str_destroy (str_line); str_line = NULL; if (abook_select) select_close (abook_select); abook_select = NULL; if (add_select) select_close (add_select); add_select = NULL; if (label) label_destroy (label); label = NULL; if (label_add) label_destroy (label_add); label_add = NULL; if (selected) bitarray_destroy (selected); selected = NULL; if (add_raddress) raddress_destroy (add_raddress); add_raddress = NULL; } void abook_refresh (void) { select_show (abook_select); if (label) label_show (label); } void abook_redraw (void) { select_redraw (abook_select); redraw_label (); } void abook_set_focus (void) { if (label) label_set_focus (label); cmd_state_push (CMD_ABOOK); abook_redraw (); } void abook_unset_focus (void) { if (label){ label_unset_focus (label); redraw_label (); } cmd_state_pop (); } void abook_add_refresh (void) { select_show (add_select); if (label_add) label_show (label_add); } void abook_add_redraw (void) { select_redraw (add_select); redraw_add_label (); } void abook_add_set_focus (void) { if (label_add) label_set_focus (label_add); cmd_state_push (CMD_ABOOK_ADD); abook_add_redraw (); } void abook_add_unset_focus (void) { if (label_add){ label_unset_focus (label_add); redraw_add_label (); } cmd_state_pop (); } void abook_show (void) { window_show (abook_select->win); abook_sort (); } void abook_hide (void) { window_hide (abook_select->win); } void abook_load (void) { char *dfl_location = DFL_LOCATION (); char *fname = ask_for_default ("addressbook", NULL); if (fname == NULL) fname = dfl_location; addr_block = memblock_create (INITIAL_BLOCK_SIZE); abook = raddress_create (); abook_sorted = 0; confread_read_file (fname, fname == dfl_location); selected = bitarray_create (abook->count + 1); bitarray_zeros (selected); xfree (dfl_location); } void abook_save (void) { int i; address_t *addr; char *dfl_location; char *fname; FILE *fp; if (! abook_changed) return; dfl_location = DFL_LOCATION (); fname = ask_for_default ("addressbook", NULL); if (fname == NULL) fname = dfl_location; fp = fopen (fname, "w"); if (fp == NULL){ error_ (errno, "%s", fname); xfree (dfl_location); return; } for (i = 0; i < abook->count; i++){ addr = abook->array[i]; write_addr (addr, fp); } fclose (fp); xfree (dfl_location); } void abook_sort (void) { if (abook == NULL) return; if (abook_sorted) return; qsort (abook->array, abook->count, sizeof (address_t *), cmp_addr); abook_sorted = 1; abook_redraw (); } rstring_t * abook_strings (const char *prefix) { int i; char *str; address_t *addr; rstring_t *result; if (abook == NULL) return NULL; result = rstring_create_size (abook->count + 1); if (*prefix == '"') prefix++; for (i = 0; i < abook->count; i++){ addr = abook->array[i]; if (*addr->full == '"') str = addr->full + 1; else str = addr->full; if (strstr (str, prefix) == str) rstring_add (result, abook->array[i]->full); } return result; } /**************************************************************************** * ADDRESSBOOK WINDOW PRIVATE DATA ****************************************************************************/ /** * This string holds list of addresses that may be a value of To field. */ static str_t *to_str = NULL; /**************************************************************************** * ADDRESSBOOK WINDOW PRIVATE FUNCTIONS ****************************************************************************/ static void action_remove (int index) { address_t *addr = abook->array[index]; addr->flags.bits.abook = NO; abook_changed = 1; raddress_remove (abook, index); } static void action_change_sex (int index) { address_t *addr = abook->array[index]; if (addr->flags.bits.sex == SEX_MALE) addr->flags.bits.sex = SEX_FEMALE; else if (addr->flags.bits.sex == SEX_FEMALE) addr->flags.bits.sex = SEX_MALE; if (addr->flags.bits.sex != SEX_UNKNOWN) abook_changed = 1; } static void action_change_official (int index) { address_t *addr = abook->array[index]; addr->flags.bits.official = ! addr->flags.bits.official; abook_changed = 1; } static void action_change_foreign (int index) { address_t *addr = abook->array[index]; addr->flags.bits.foreign = ! addr->flags.bits.foreign; abook_changed = 1; } static void action_add_to_str (int index) { address_t *addr = abook->array[index]; if (to_str->len == 0) str_sprintf (to_str, "%s", addr->full); else str_sprintf (to_str, ", %s", addr->full); } static void take_action (void (*fun)(int)) { int i; int took = 0; for (i = abook->count - 1; i >= 0; i--){ if (bitarray_is_set (selected, i)){ fun (i); took = 1; } } if (! took && abook->count > 0){ fun (abook_select->bar_pos); } bitarray_zeros (selected); } /**************************************************************************** * ADDRESSBOOK WINDOW INTERFACE FUNCTIONS ****************************************************************************/ void abook_next (void) { select_next (abook_select); } void abook_prev (void) { select_prev (abook_select); } void abook_next_page (void) { select_next_page (abook_select); } void abook_prev_page (void) { select_prev_page (abook_select); } void abook_first (void) { select_first (abook_select); } void abook_last (void) { select_last (abook_select); } void abook_hit (void) { bitarray_change_bit (selected, abook_select->bar_pos); select_redraw (abook_select); } void abook_hit_all (void) { bitarray_neg (selected); select_redraw (abook_select); } void abook_set (void) { bitarray_set (selected, abook_select->bar_pos); select_redraw (abook_select); } void abook_unset (void) { bitarray_unset (selected, abook_select->bar_pos); select_redraw (abook_select); } void abook_set_all (void) { bitarray_ones (selected); select_redraw (abook_select); } void abook_unset_all (void) { bitarray_zeros (selected); select_redraw (abook_select); } void abook_remove (void) { take_action (action_remove); select_redraw (abook_select); } void abook_change_official (void) { take_action (action_change_official); select_redraw (abook_select); } void abook_change_sex (void) { take_action (action_change_sex); select_redraw (abook_select); } void abook_change_foreign (void) { take_action (action_change_foreign); select_redraw (abook_select); } void abook_compose (void) { to_str = str_create (); take_action (action_add_to_str); if (to_str){ sender_open_new_to (to_str->str); str_destroy (to_str); } to_str = NULL; } void abook_insert (void) { address_t *addr; char *name; char *email; name = read_argument (_("Name: "), NULL, COMPLETE_NONE, HIDE_NO); if (name == NULL) return; if (*name == '\0') name = NULL; else name = memblock_strdup (& addr_block, name); email = read_argument (_("Email: "), NULL, COMPLETE_NONE, HIDE_NO); if (email == NULL) return; if (*email == '\0') email = NULL; else email = memblock_strdup (& addr_block, email); if (name == NULL && email == NULL) return; addr = address_empty (); addr->name = name; addr->email = email; addr = address_complete (addr); addr->flags.bits.abook = YES; raddress_add (abook, addr); abook_changed = 1; abook_sorted = 0; bitarray_resize (selected, abook->count); select_redraw (abook_select); } void abook_change_name (void) { address_t *addr; address_t *new; char *name; addr = abook->array[abook_select->bar_pos]; name = read_argument (_("Name: "), addr->name, COMPLETE_NONE, HIDE_NO); if (name == NULL) return; if (*name == '\0') name = NULL; else name = memblock_strdup (& addr_block, name); addr->name = name; addr->full = NULL; new = address_complete (addr); if (new != addr){ new->flags.bits.abook = YES; } abook_changed = 1; select_redraw (abook_select); } void abook_change_email (void) { address_t *addr; address_t *new; char *email; addr = abook->array[abook_select->bar_pos]; email = read_argument (_("Email: "), addr->email, COMPLETE_NONE, HIDE_NO); if (email == NULL) return; if (*email == '\0') email = NULL; else email = memblock_strdup (& addr_block, email); addr->email = email; addr->full = NULL; new = address_complete (addr); if (new != addr){ new->flags.bits.abook = YES; } abook_changed = 1; select_redraw (abook_select); } void abook_change_groups (void) { address_t *addr; char *groups_str; char *oldgroups; rstring_t *groups; addr = abook->array[abook_select->bar_pos]; oldgroups = (addr->groups) ? rstring_flatten (addr->groups, ", ") : NULL; groups_str = read_argument (_("Groups: "), oldgroups, COMPLETE_NONE, HIDE_NO); if (oldgroups) xfree (oldgroups); if (groups_str == NULL) return; if (addr->groups) rstring_delete (addr->groups); addr->groups = NULL; if (*groups_str == '\0') return; groups_str = xstrdup (groups_str); groups = rstring_split_re (groups_str, "[ \t]*,[ \t]*"); groups->allocated_first = 1; addr->groups = groups; abook_changed = 1; select_redraw (abook_select); } void abook_search_forward (void) { select_search_setup_forward (abook_select, match_address); } void abook_search_backward (void) { select_search_setup_backward (abook_select, match_address); } /**************************************************************************** * ADDRESSBOOK ADDING WINDOW FUNCTIONS ****************************************************************************/ void abook_add_show (void) { mail_t *mail = folder_mail_selected (); if (mail == NULL) return; if (add_raddress) raddress_destroy (add_raddress); add_raddress = raddress_create (); if (mail->from && ! mail->from->flags.bits.abook) raddress_add (add_raddress, mail->from); if (mail->reply_to && mail->reply_to != mail->from && ! mail->from->flags.bits.abook) raddress_add (add_raddress, mail->reply_to); if (mail->to) raddress_add_array (add_raddress, mail->to); if (mail->cc) raddress_add_array (add_raddress, mail->cc); window_show (add_select->win); } void abook_add_hide (void) { if (add_raddress){ raddress_destroy (add_raddress); add_raddress = NULL; } window_hide (add_select->win); } void abook_add_next (void) { select_next (add_select); } void abook_add_prev (void) { select_prev (add_select); } void abook_add_next_page (void) { select_next_page (add_select); } void abook_add_prev_page (void) { select_prev_page (add_select); } void abook_add_first (void) { select_first (add_select); } void abook_add_last (void) { select_last (add_select); } void abook_add_hit (void) { address_t *addr = add_raddress->array[add_select->bar_pos]; if (addr == NULL || addr->flags.bits.abook) return; raddress_remove (add_raddress, add_select->bar_pos); addr->flags.bits.abook = YES; raddress_add (abook, addr); abook_changed = 1; abook_sorted = 0; bitarray_resize (selected, abook->count); select_redraw (add_select); } /**************************************************************************** * CONFIG FUNCTIONS ****************************************************************************/ void abook_new_prepare (void) { if (new_item.addr == NULL) new_item.addr = address_empty (); } void abook_new_drop (void) { new_item.s_field = NULL; new_item.shift = 0; new_item.mask = 0; new_item.group = 0; } void abook_new_store (void) { address_t *addr = NULL; if (new_item.addr == NULL) return; if (new_item.addr->name == NULL && new_item.addr->email == NULL){ abook_new_drop (); return; } addr = address_complete (new_item.addr); addr->flags.bits.abook = YES; raddress_add (abook, addr); new_item.addr = NULL; new_item.s_field = NULL; new_item.shift = 0; new_item.mask = 0; new_item.group = 0; } int abook_new_field (const char *field) { new_item.s_field = NULL; new_item.shift = 0; new_item.mask = 0; new_item.group = 0; if (strcmp (field, "name") == 0) new_item.s_field = & new_item.addr->name; else if (strcmp (field, "email") == 0) new_item.s_field = & new_item.addr->email; else if (strcmp (field, "initials") == 0) new_item.s_field = & new_item.addr->initials; else if (strcmp (field, "sex") == 0){ new_item.shift = A_SHIFT_SEX; new_item.mask = A_MASK_SEX; } else if (strcmp (field, "official") == 0){ new_item.shift = A_SHIFT_OFFICIAL; new_item.mask = A_MASK_OFFICIAL; } else if (strcmp (field, "foreign") == 0){ new_item.shift = A_SHIFT_FOREIGN; new_item.mask = A_MASK_FOREIGN; } else if (strcmp (field, "group") == 0){ new_item.group = 1; } if (new_item.s_field == NULL && new_item.shift == 0 && new_item.mask == 0 && new_item.group == 0) return 1; return 0; } void abook_new_value (const char *value) { if (new_item.group){ if (new_item.addr->groups == NULL) new_item.addr->groups = rstring_create (); rstring_add (new_item.addr->groups, memblock_strdup (& addr_block, value)); } else if (new_item.s_field){ *new_item.s_field = memblock_strdup (& addr_block, value); } else if (new_item.mask){ new_item.addr->flags.value |= (int_value (value) << new_item.shift) & new_item.mask; } } /**************************************************************************** * INTERFACE CLASS BODIES ****************************************************************************/ /**************************************************************************** * * END MODULE abook.c * ****************************************************************************/