--- /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