--- /dev/null
+This file lists major changes affecting abook's behavior.
+Please read this file carefully when upgrading abook.
+A more comprehensive list of changes can be found in the ChangeLog file.
+
+--
+
+0.6.0pre2:
+  * The four following configuration options have been deprecated and will
+    no longer be accepted by abook:
+      * emailpos
+      * extra_column
+      * extra_alternative
+      * extrapos
+
+    They have been replaced with a single more flexible option:
+    index_format.
+
+    This option is a string defining the format of a line in the main list.
+    It allows displaying of as many fields as desired, with optional
+    width limit, as well as an arbitrary number of alternative fields.
+
+0.6.0pre1 (2006-08-30):
+  * The 'customfield' command has been obsoleted by a more flexible set
+    of commands: 'field' and 'view'.
+
+    Those two commands make it possible to define fields and organize
+    them within tabs as you see fit.
+
+    Not using these commands, the look and feel of previous releases
+    will mostly be kept the same. Also see the related 'preserve_fields'
+    configuration variable.
+
 
 
        signal(SIGTERM, quit_abook_sig);
 
+       init_index();
+
        if(init_ui())
                exit(EXIT_FAILURE);
 
 
 .TP
 .B none
 discards any unknown field.
+.RE
 .IP
 Default is \fIstandard\fP.
-.RE
 
 .TP
-\fBshow_all_emails\fP=[true|false]
-Defines whether all email addresses for a contact are shown in the main list view. Default is true
-
+\fBindex_format\fP=format_string
+Defines the way entries are displayed in the main list. This is a string containing field names enclosed between braces, with an optional width limit specified by a number (right alignment if negative) after the field name and a colon, and an arbitrary number of alternative fields (first with non empty content is to be displayed) separated by vertical bars. For instance:
+.RS
 .TP
-\fBemailpos\fP=column
-Defines the screen column on the main list where the email address is to begin. Default is 25.
-
+\fI{name:22}\fP
+displays the \fIname\fP field with a maximal width of 22 characters.
 .TP
-\fBextra_column\fP=field
-Defines the field to display in the extra (third) column on the main list. Default is "phone" (Home Phone).
+\fI{phone:-13|workphone|mobile}\fP
+displays (right aligned within a width of 13 characters), either the \fIphone\fP, \fIworkphone\fP or \fImobile\fP field, whichever being the first to be non-empty.
+.RE
 .IP
-\fIfield\fP can be any of the following:
-.br
--1                     disabled
-.br
-phone          Home Phone
-.br
-workphone              Work Phone
-.br
-fax                    Fax
-.br
-mobile         Mobile Phone
-.br
-nick                   Nickname/Alias
-.br
-url                    URL
-.br
-notes          Notes
+Default is \fI" {name:22} {email:40} {phone:12|workphone|mobile}"\fP
 
 .TP
-\fBextra_alternative\fP=field
-This is an optional setting that allows you to specify an alternative field to be displayed in the extra (third) column if there is no data for the field specified in extra_column for a particular item. The strings for the fields are the same as above. Please note that the data shown where the alternative field has been used will NOT be marked differently in any way from the rest of the extra column. There is no default.
+\fBshow_all_emails\fP=[true|false]
+Defines whether all email addresses for a contact are shown in the main list view. Default is true.
 
 .TP
-\fBextrapos\fP=column
-Defines the screen column on the main list where the extra field is to begin. Default is 65.
-
+.PD 0
+\fBemailpos\fP
+.TP
+.PD 0
+\fBextra_column\fP
+.TP
+.PD 0
+\fBextra_alternative\fP
 .TP
-\fBmutt_command\fP=command
-Defines the command to start mutt. Default is "mutt".
+.PD
+\fBextrapos\fP
+Obsoleted by \fBindex_format\fP.
 
 .TP
 \fBmutt_return_all_emails\fP=[true|false]
 # Automatically save database on exit
 set autosave=true
 
+# Format of entries lines in list
+set index_format=" {name:22} {email:40} {phone:12|workphone|mobile}"
+
 # Show all email addresses in list
 set show_all_emails=true
 
-# Screen column for email field to start
-set emailpos=25
-
-# Field to be used in the extra column
-set extra_column=phone
-# frequently used values:
-#      -1          disabled
-#      phone       Home Phone
-#      workphone   Work Phone
-#      fax         Fax
-#      mobile      Mobile Phone
-#      nick        Nickname/Alias
-#      url         URL
-
-#
-set extra_alternative=-1
-
-# Screen column for the extra field to start
-set extrapos=65
-
 # Command used to start mutt
 set mutt_command=mutt
 
 # Command used to start the web browser
 set www_command=lynx
 
-# address style [eu|us|uk]
+# Address style [eu|us|uk]
 set address_style=eu
 
-# use ASCII characters only
+# Use ASCII characters only
 set use_ascii_only=false
 
 # Prevent double entry
 set add_email_prevent_duplicates=false
 
-# field to be used with "sort by field" command
+# Field to be used with "sort by field" command
 set sort_field=nick
 
-# show cursor in main display
+# Show cursor in main display
 set show_cursor=false
 
 .fi
 
 
 # Define the identity of the package.
  PACKAGE=abook
- VERSION=0.5.5
+ VERSION=0.6.0pre1
 
 
 cat >>confdefs.h <<_ACEOF
 
 }
 
 void
-get_field_keyname(int i, char **key, char **name)
+get_field_info(int i, char **key, char **name, int *type)
 {
        abook_field_list *cur = fields_list;
        int j;
                *key = (i < 0) ? NULL : cur->field->key;
        if(name)
                *name = (i < 0) ? NULL : cur->field->name;
+       if(type)
+               *type = (i < 0) ? -1 : cur->field->type;
 }
 
 void
 
 #define find_field(key, list)          real_find_field(key, list, NULL)
 #define find_field_number(key, pt_nb)  real_find_field(key, NULL, pt_nb)
 #define find_declared_field(key)       find_field(key,NULL)
-void get_field_keyname(int i, char **key, char **name);
+void get_field_info(int i, char **key, char **name, int *type);
 void add_field(abook_field_list **list, abook_field *f);
 char *declare_new_field(char *key, char *name, char *type, int accept_standard);
 void init_standard_fields();
 
        return is_valid_date(*day, *month, *year);
 }
 
-static int
-is_number(char *s)
-{
-       char *p;
-
-       for(p = s; *p; p++)
-               if(!isdigit(*p))
-                       return FALSE;
-       return TRUE;
-}
-
 static void
 edit_date(int item, int nb)
 {
 
  * html export filter
  */
 
-static void            html_export_write_head(FILE *out, int extra_column);
+static void            html_export_write_head(FILE *out);
 static void            html_export_write_tail(FILE *out);
 
+extern struct index_elem *index_elements;
+
+static void
+html_print_emails(FILE *out, struct list_field *f)
+{
+       abook_list *l = csv_to_abook_list(f->data);
+
+       for(; l; l = l->next) {
+               fprintf(out, "<a href=\"mailto:%s\">%s</a>", l->data, l->data);
+               if(l->next)
+                       fprintf(out, ", ");
+       }
+
+       abook_list_free(&l);
+}
+
 static int
 html_export_database(FILE *out, struct db_enumerator e)
 {
-       char tmp[MAX_EMAILSTR_LEN], *emails;
-       int extra_column;
+       struct list_field f;
+       struct index_elem *cur;
 
        if(list_is_empty())
                return 2;
 
-       extra_column = init_extra_field(STR_EXTRA_COLUMN);
-       html_export_write_head(out, extra_column);
+       init_index();
 
-       db_enumerate_items(e) {
-               get_first_email(tmp, e.item);
-               if (*tmp)
-                   fprintf(out, "<tr>\n<td>"
-                                   "<a href=\"mailto:%s\">%s</a>"
-                                   "</td>\n",
-                           tmp,
-                           db_name_get(e.item));
-               else
-                   fprintf(out, "<tr>\n<td>%s</td>\n", db_name_get(e.item));
+       html_export_write_head(out);
 
-               emails = db_email_get(e.item);
-               fprintf(out, "<td>%s</td>\n", emails);
-               free(emails);
-               if(extra_column >= 0)
-                       fprintf(out, "<td>%s</td>\n",
-                               safe_str(db_fget_byid(e.item, extra_column)));
-               fprintf(out, "</tr>\n\n");
+       db_enumerate_items(e) {
+               fprintf(out, "<tr>");
+               for(cur = index_elements; cur; cur = cur->next) {
+                       if(cur->type != INDEX_FIELD)
+                               continue;
+
+                       get_list_field(e.item, cur, &f);
+
+                       if(f.type == FIELD_EMAILS) {
+                               fprintf(out, "<td>");
+                               html_print_emails(out, &f);
+                               fprintf(out, "</td>");
+                               continue;
+                       } else {
+                               fprintf(out, "<td>%s</td>", safe_str(f.data));
+                       }
+               }
+               fprintf(out, "</tr>\n");
        }
 
        html_export_write_tail(out);
 }
 
 static void
-html_export_write_head(FILE *out, int extra_column)
+html_export_write_head(FILE *out)
 {
-       char *realname = get_real_name(), *extra_column_name = NULL;
+       char *realname = get_real_name(), *str;
+       struct index_elem *cur;
 
        fprintf(out, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");
        fprintf(out, "<html>\n<head>\n <title>%s's addressbook</title>",
        fprintf(out, "\n<h2>%s's addressbook</h2>\n", realname );
        fprintf(out, "<br><br>\n\n");
 
-       fprintf(out, "<table border=\"1\" align=\"center\">\n");
-       fprintf(out, "\n<tr><th>Name</th><th>E-mail address(es)</th>");
-       if(extra_column >= 0) {
-               get_field_keyname(extra_column, NULL, &extra_column_name);
-               fprintf(out, "<th>%s</th>", safe_str(extra_column_name));
+       fprintf(out, "<table border=\"1\" align=\"center\">\n<tr>");
+       for(cur = index_elements; cur; cur = cur->next) {
+               if(cur->type != INDEX_FIELD)
+                       continue;
+
+               get_field_info(cur->d.field.id, NULL, &str, NULL);
+               fprintf(out, "<th>%s</th>", str);
        }
        fprintf(out, "</tr>\n\n");
 
                        fprintf(out, "\n");
                        for(j = PHONE; j <= MOBILEPHONE; j++)
                                if(db_fget(e.item, j)) {
-                                       get_field_keyname(field_id(j),
-                                                       NULL, &str);
+                                       get_field_info(field_id(j),
+                                                       NULL, &str, NULL);
                                        fprintf(out, "%s: %s\n", str,
                                                db_fget(e.item, j));
                                }
 
 int first_list_item = -1;
 char *selected = NULL;
 
-int extra_column = -1;
-int extra_alternative = -1;
-
 extern abook_field_list *fields_list;
+struct index_elem *index_elements = NULL;
 
 static WINDOW *list = NULL;
 
 
-int
-init_extra_field(enum str_opts option)
+static void
+index_elem_add(int type, char *a, char *b)
 {
-       int ret = -1;
-       char *option_str;
+       struct index_elem *tmp = NULL, *cur, *cur2;
+       int field, len = 0;
 
-       option_str = opt_get_str(option);
+       if(!a || !*a)
+               return;
+
+       switch(type) {
+               case INDEX_TEXT:
+                       tmp = xmalloc(sizeof(struct index_elem));
+                       tmp->d.text = xstrdup(a);
+                       break;
+               case INDEX_FIELD: /* fall through */
+               case INDEX_ALT_FIELD:
+                       find_field_number(a, &field);
+                       if(field == -1)
+                               return;
+                       len = (b && *b && is_number(b)) ? atoi(b) : 0;
+                       tmp = xmalloc(sizeof(struct index_elem));
+                       tmp->d.field.id = field;
+                       tmp->d.field.len = len;
+                       break;
+               default:
+                       assert(0);
+       }
+       tmp->type = type;
+       tmp->next = NULL;
+       tmp->d.field.next = NULL;
+
+       if(!index_elements) { /* first element */
+               index_elements = tmp;
+               return;
+       }
 
-       if(option_str && *option_str) {
-               find_field_number(option_str, &ret);
+       for(cur = index_elements; cur->next; cur = cur->next)
+               ;
+       if(type != INDEX_ALT_FIELD)
+               cur->next = tmp;
+       else { /* add as an alternate field */
+               tmp->d.field.len = cur->d.field.len;
+               for(cur2 = cur; cur2->d.field.next; cur2 = cur2->d.field.next)
+                       ;
+               cur2->d.field.next = tmp;
+       }
+}
 
-               if(!strcmp(option_str, "name") || !strcmp(option_str, "email"))
-                       ret = -1;
+static void
+parse_index_format(char *s)
+{
+       char *p, *start, *lstart = NULL;
+       int in_field = 0, in_alternate = 0, in_length = 0, type;
+
+       p = start = s;
+
+       while(*p) {
+               if(*p == '{' && !in_field) {
+                       *p = 0;
+                       index_elem_add(INDEX_TEXT, start, NULL);
+                       start = ++p;
+                       in_field = 1;
+               } else if(*p == ':' && in_field && !in_alternate) {
+                       *p = 0;
+                       lstart = ++p;
+                       in_length = 1;
+               } else if(*p == '|' && in_field) {
+                       *p = 0;
+                       type = in_alternate ? INDEX_ALT_FIELD : INDEX_FIELD;
+                       index_elem_add(type, start, in_length ? lstart : NULL);
+                       start = ++p;
+                       in_length = 0;
+                       in_alternate = 1;
+               } else if(*p == '}' && in_field) {
+                       *p = 0;
+                       type = in_alternate ? INDEX_ALT_FIELD : INDEX_FIELD;
+                       index_elem_add(type, start, in_length ? lstart : NULL);
+                       start = ++p;
+                       in_field = in_alternate = in_length = 0;
+               } else
+                       p++;
        }
+       if(!in_field)
+               index_elem_add(INDEX_TEXT, start, NULL);
+}
 
-       return ret;
+void
+init_index()
+{
+       assert(!index_elements);
+       parse_index_format(opt_get_str(STR_INDEX_FORMAT));
 }
 
 void
 {
        list = newwin(LIST_LINES, LIST_COLS, LIST_TOP, 0);
        scrollok(list, TRUE);
-
-       /*
-        * init extra_column and extra alternative
-        */
-
-       extra_column = init_extra_field(STR_EXTRA_COLUMN);
-       extra_alternative = init_extra_field(STR_EXTRA_ALTERNATIVE);
 }
 
 void
        list = NULL;
 }
 
+void
+get_list_field(int item, struct index_elem *e, struct list_field *res)
+{
+       char *s;
+
+       res->data = s = NULL;
+
+       do { /* find first non-empty field data in the alternate fields list */
+               s = db_fget_byid(item, e->d.field.id);
+       } while(!(s && *s) && ((e = e->d.field.next) != NULL));
+
+       if(!e || !s || !*s)
+               return;
+
+       res->data = s;
+       get_field_info(e->d.field.id, NULL, NULL, &res->type);
+}
+
+static void
+print_list_field(int item, int line, int *x_pos, struct index_elem *e)
+{
+       char *s, *p;
+       int width, x_start, mustfree = FALSE, len = abs(e->d.field.len);
+       struct list_field f;
+
+       get_list_field(item, e, &f);
+       s = f.data;
+
+       if(!s || !*s) {
+               *x_pos += len;
+               return;
+       }
+       
+       if(f.type == FIELD_EMAILS && !opt_get_bool(BOOL_SHOW_ALL_EMAILS))
+               if((p = strchr(s, ',')) != NULL) {
+                       s = xstrndup(s, p - s);
+                       mustfree = TRUE;
+               }
+
+       width = len ? bytes2width(s, len) : strwidth(s);
+       x_start = *x_pos + ((e->d.field.len < 0) ? len - width : 0);
+       if(width + x_start >= COLS)
+               width = COLS - x_start;
+
+       if(width)
+               mvwaddnstr(list, line, x_start, s, width);
+
+       if(mustfree)
+               free(s);
+               
+       *x_pos += len ? len : width;
+}
+
+static void
+print_list_line(int item, int line, int highlight)
+{
+       struct index_elem *cur;
+       int x_pos = 1;
+
+       scrollok(list, FALSE);
+       if(highlight)
+               highlight_line(list, line);
+
+       if(selected[item])
+               mvwaddch(list, line, 0, '*' );
+
+       for(cur = index_elements; cur; cur = cur->next)
+               switch(cur->type) {
+                       case INDEX_TEXT:
+                               mvwaddstr(list, line, x_pos, cur->d.text);
+                               x_pos += strwidth(cur->d.text);
+                               break;
+                       case INDEX_FIELD:
+                               print_list_field(item, line, &x_pos, cur);
+                               break;
+                       default:
+                               assert(0);
+               }
+
+       scrollok(list, TRUE);
+       if(highlight)
+               wstandend(list);
+}
+
 void
 refresh_list()
 {
         wrefresh(list);
 }
 
-void
-print_list_line(int i, int line, int highlight)
-{
-       int extra = extra_column;
-       char tmp[MAX_EMAILSTR_LEN], *emails;
-       int real_emaillen = (extra_column > 0 || extra_alternative > 0) ?
-               EMAILLEN : COLS - EMAILPOS;
-
-       scrollok(list, FALSE);
-       if(highlight)
-               highlight_line(list, line);
-
-       if(selected[i])
-               mvwaddch(list, line, 0, '*' );
-
-       mvwaddnstr(list, line, NAMEPOS, db_name_get(i),
-               bytes2width(db_name_get(i), NAMELEN));
-
-       if(opt_get_bool(BOOL_SHOW_ALL_EMAILS)) {
-               emails = db_email_get(i);
-               mvwaddnstr(list, line, EMAILPOS, emails,
-                               bytes2width(emails, real_emaillen));
-               free(emails);
-       } else {
-               get_first_email(tmp, i);
-               mvwaddnstr(list, line, EMAILPOS, tmp,
-                               bytes2width(tmp, real_emaillen));
-       }
-
-       if(extra < 0 || !db_fget_byid(i, extra))
-               extra = extra_alternative;
-       if(extra >= 0)
-               mvwaddnstr(list, line, EXTRAPOS,
-                       safe_str(db_fget_byid(i, extra)),
-                       bytes2width(safe_str(db_fget_byid(i, extra)),
-                       EXTRALEN));
-
-       scrollok(list, TRUE);
-       if(highlight)
-               wstandend(list);
-}
-
 void
 list_headerline()
 {
+       struct index_elem *e;
+       int x_pos = 1, width;
        char *str = NULL;
 
 #if defined(A_BOLD) && defined(A_NORMAL)
        attrset(A_BOLD);
 #endif
 
-       mvaddstr(2, NAMEPOS, find_field("name", NULL)->name);
-       mvaddstr(2, EMAILPOS, find_field("email", NULL)->name);
-       if(extra_column > 0) {
-               get_field_keyname(extra_column, NULL, &str);
-               mvaddnstr(2, EXTRAPOS, str, COLS - EXTRAPOS);
-       }
+       for(e = index_elements; e; e = e->next)
+               if(e->type == INDEX_TEXT)
+                       x_pos += strwidth(e->d.text);
+               else if(e->type == INDEX_FIELD) {
+                       get_field_info(e->d.field.id, NULL, &str, NULL);
+                       width = e->d.field.len ? abs(e->d.field.len) : strwidth(str);
+                       mvaddnstr(2, x_pos, str, width);
+                       x_pos += width;
+               } else
+                       assert(0);
 
 #if defined(A_BOLD) && defined(A_NORMAL)
        attrset(A_NORMAL);
 
 
 #include "ui.h"
 
+#define INDEX_TEXT  1
+#define INDEX_FIELD 2
+#define INDEX_ALT_FIELD 3
+
+struct index_elem {
+       int type;
+       union {
+               char *text;
+               struct {
+                       int id;
+                       int len;
+                       struct index_elem *next;
+               } field;
+       } d;
+       struct index_elem *next;
+};
+
+struct list_field {
+       char *data;
+       int type;
+};
+
+void           init_index();
 void           init_list();
 int            init_extra_field(enum str_opts option);
 void           close_list();
 void            refresh_list();
-void           print_list_line(int i, int line, int highlight);
+void   get_list_field(int item, struct index_elem *e, struct list_field *res);
 void           list_headerline();
 void            scroll_up();
 void            scroll_down();
 };
 
 #define LIST_TOP        3
-#define LIST_BOTTOM     (LINES-2)
+#define LIST_BOTTOM     (LINES - 2)
 
-#define LIST_LINES     (LIST_BOTTOM-LIST_TOP)
+#define LIST_LINES     (LIST_BOTTOM - LIST_TOP)
 #define LIST_COLS      COLS
 
-#define NAMEPOS                2
-#define EMAILPOS        opt_get_int(INT_EMAILPOS)
-#define EXTRAPOS       opt_get_int(INT_EXTRAPOS)
-
-#define NAMELEN                (EMAILPOS - NAMEPOS - 1)
-#define EMAILLEN        (EXTRAPOS - EMAILPOS - 1)
-#define EXTRALEN       (COLS - EXTRAPOS)
-
 #define LAST_LIST_ITEM (first_list_item + LIST_LINES - 1)
 
 #endif
 
        return s;
 }
 
+int
+is_number(char *p)
+{
+       if(!p || !*p || (*p == '-' && !*++p))
+               return 0;
+
+       for(; *p; p++)
+               if(!isdigit(*p))
+                       return 0;
+
+       return 1;
+}
+
 
 #ifdef HAVE_CONFIG_H
 #      include "config.h"
 
 char           *strlower(char *str);
 char           *strtrim(char *);
 
+int            is_number(char *s);
+
 char           *strdup_printf(const char *format, ... );
 char           *strconcat(const char *str, ...);
 
 
        { "extra_column", OT_STR, STR_EXTRA_COLUMN, UL "phone" },
        { "extra_alternative", OT_STR, STR_EXTRA_ALTERNATIVE, UL "-1" },
        { "extrapos", OT_INT, INT_EXTRAPOS, 65 },
-
+       { "index_format", OT_STR, STR_INDEX_FORMAT, UL " {name:22} {email:40} {phone:12|workphone|mobile}" },
        { "mutt_command", OT_STR, STR_MUTT_COMMAND, UL "mutt" },
        { "mutt_return_all_emails", OT_BOOL, BOOL_MUTT_RETURN_ALL_EMAILS,
                TRUE },
 
 enum str_opts {
        STR_EXTRA_COLUMN,
        STR_EXTRA_ALTERNATIVE,
+       STR_INDEX_FORMAT,
        STR_MUTT_COMMAND,
        STR_PRINT_COMMAND,
        STR_WWW_COMMAND,
 
 # Show all email addresses in list
 set show_all_emails=true
 
-# Screen column for email field to start
-set emailpos=25
-
-# Field to be used in the extra column
-set extra_column=phone
-# frequently used values:
-#      -1              disabled
-#      phone           Home Phone
-#      workphone       Work Phone
-#      fax             Fax
-#      mobile          Mobile Phone
-#      nick            Nick / Alias
-#      url             URL
-
-# Specify an alternative field to be displayed in the extra
-# column if there is no data for the field specified in
-# extra_column for a particular item.  
-set extra_alternative=-1
-
-# Screen column for the extra field to start
-set extrapos=65
+# Format of an entry's line in the main abook screen
+#
+# The below example displays:
+#  * the content of the 'name' field (with a maximum of 22 characters)
+#  * the first of the 'phone', 'workphone' or 'mobile' fields
+#    happening not to be empty (right aligned within 12 characters)
+#  * the 'anniversary' field, with no length limit
+set index_format=" {name:25} {phone:-12|workphone|mobile} {anniversary}"
 
 # Command used to start mutt
 set mutt_command=mutt