#include "links.h"

/* The location of the box in the bookmark manager */
#define	BM_BOX_IND		5


/* The list of bookmarks */
struct list_head bookmarks = {&bookmarks, &bookmarks};

/* The last used id of a bookmark */
bookmark_id next_bookmark_id = 0;

/* Clears the bookmark list */
void free_bookmarks() {
	struct bookmark *bm;

	foreach(bm, bookmarks) {
		mem_free(bm->title);
		mem_free(bm->url);
	}

	free_list(bookmarks);
}

/* Does final cleanup and saving of bookmarks */
void finalize_bookmarks() {
	write_bookmarks();
	free_bookmarks();
}


/* Loads the bookmarks from file */
void read_bookmarks() {
	unsigned char in_buffer[MAX_STR_LEN];	
	unsigned char *file_name;
	unsigned char *title, *url;	/* Pointers to the start of each field (in buffer)*/
	FILE *f;
	
	file_name = stracpy(links_home);
	if (!file_name) return;
	add_to_strn(&file_name, "bookmarks");
	
	f = fopen(file_name, "r");
	mem_free(file_name);
	if (f == NULL)
		return;
	
	title = in_buffer;
	
	while (fgets(in_buffer, MAX_STR_LEN, f)) {
		url = strchr(in_buffer, '|');
		if (url == NULL) 
			continue;
		*url = '\0';
		url++;
		if (strchr(url, '\n')) *( strchr(url, '\n') ) = '\0';
		add_bookmark(title, url);
	}
	
	fclose(f);
}

/* Saves the bookmarks to file */
void write_bookmarks() {
	struct bookmark *bm;
	FILE *out;
	unsigned char *file_name;

	file_name = stracpy(links_home);
	if (!file_name) return;
	add_to_strn(&file_name, "bookmarks");
	
	out = fopen(file_name, "w");
	mem_free(file_name);
	if (!out) return;

	foreachback(bm, bookmarks) {
		unsigned char *p = stracpy(bm->title);
		int i;
		for (i = strlen(p) - 1; i >= 0; i--) if (p[i] < ' '|| p[i] == '|') p[i] = '*';
		fputs(p,out);
		fputc('|', out);
		fputs(bm->url,out);
		fputc('\n',out);
		mem_free(p);
	}

	fclose(out);
}

/* Gets a bookmark by id */
struct bookmark *get_bookmark_by_id(bookmark_id id) {
	struct bookmark *bm;

	if (id == BAD_BOOKMARK_ID) 
		return NULL;

	foreach(bm, bookmarks) {
		if (id == bm->id)
			return bm;
	}

	return NULL;
}


/* Adds a bookmark to the bookmark list. Don't play with new_bm after you're
	done. It would be impolite. */
void add_bookmark(const unsigned char *title, const unsigned char *url) {
	struct bookmark *bm;
	int	title_size;	/* How much mem to allocate for the strings */
	int url_size;

	title_size = strlen(title) + 1;
	url_size = strlen(url) + 1;

	bm = mem_alloc(sizeof(struct bookmark));

	bm->title = mem_alloc( sizeof(unsigned char) * title_size );
	bm->url = mem_alloc( sizeof(unsigned char) * url_size );
	
	strcpy(bm->title, title);
	strcpy(bm->url, url);

	bm->id = next_bookmark_id++;

	/* Actually add it */
	add_to_list(bookmarks, bm);
}


/* Updates an existing bookmark. 
 *
 * If the requested bookmark does not exist, return 0. Otherwise, return 1.
 *
 * If any of the fields are NULL, the value is left unchanged.
 */
int bookmark_update(bookmark_id id, const unsigned char *title, const unsigned char *url) {
	struct bookmark *bm = get_bookmark_by_id(id);

	if (bm == NULL) {
		/* Does not exist. */
		return 0;
	}

	if (title) {
		mem_free(bm->title);
		bm->title = stracpy((unsigned char *)title);
	}

	if (url) {
		mem_free(bm->url);
		bm->url = stracpy((unsigned char *)url);
	}

	return 1;
}

/* Allocates and returns a bookmark */
struct bookmark *create_bookmark(const unsigned char *title, const unsigned char *url) {
	struct bookmark *new_bm = NULL;
	int	title_size;	/* How much mem to allocate for the strings */
	int url_size;

	title_size = strlen(title) + 1;
	url_size = strlen(url) + 1;

	new_bm = mem_alloc(sizeof(struct bookmark));

	new_bm->title = mem_alloc( sizeof(unsigned char) * title_size );
	new_bm->url = mem_alloc( sizeof(unsigned char) * url_size );
	
	strncpy(new_bm->title, title, title_size);
	strncpy(new_bm->url, url, url_size);

	return new_bm;
}


/* Deletes a bookmark, given the id. Returns 0 on failure (no such bm), 1 on 
	success */
int delete_bookmark_by_id(bookmark_id id) {
	struct bookmark *bm;

	bm = get_bookmark_by_id(id);

	if (bm == NULL)
		return 0;

	del_from_list(bm);

	/* Now wipe the bookmark */
	mem_free(bm->title);
	mem_free(bm->url);

	mem_free(bm);
	
	return 1;
}

/****************************************************************************
*
* Bookmark manager stuff.
*
****************************************************************************/

void bookmark_edit_dialog(
		struct terminal *, 
		unsigned char *, 
		const unsigned char *, 
		const unsigned char *, 
		struct session *, 
		struct dialog_data *,
		void when_done(struct dialog *), 
		void *
);

/* Gets the head of the bookmark list kept by the dialog (the one used for 
	display purposes */
/* I really should use this somewhere...
static inline *list_head bookmark_dlg_list_get(struct dialog) {
	return dialog->items[BM_BOX_IND].data;
}
*/

/* Clears the bookmark list from the bookmark_dialog */
static inline void bookmark_dlg_list_clear(struct list_head *bm_list) {
	free_list( *bm_list );
}

/* Updates the bookmark list for a dialog. Returns the number of bookmarks.
	FIXME: Must be changed for hierarchical bookmarks.
*/
int bookmark_dlg_list_update(struct list_head *bm_list) {
	struct bookmark *bm;	/* Iterator over bm list */
	struct box_item	*item;	/* New box item (one per displayed bookmark) */
	unsigned char *text;
	int count = 0;
	bookmark_id id;
	
	/* Empty the list */
	bookmark_dlg_list_clear(bm_list);
	
	/* Copy each bookmark into the display list */
	foreach(bm, bookmarks) {
		/* Deleted in bookmark_dlg_clear_list() */
		item = mem_alloc( sizeof(struct box_item) + strlen(bm->title) 
			+ 1);
		item->text = text = ((unsigned char *)item + sizeof(struct box_item));
		item->data = (void *)(id = bm->id);
	
		/* Note that free_i is left at zero */
	
		strcpy(text, bm->title);

		add_to_list( *bm_list, item);
		count++;
	}
	return count;
}


/* Creates the box display (holds everything EXCEPT the actual rendering data) */
struct dlg_data_item_data_box *bookmark_dlg_box_build(struct dlg_data_item_data_box **box) {
	/* Deleted in abort */
	*box = mem_alloc( sizeof(struct dlg_data_item_data_box) );
	memset(*box, 0, sizeof(struct dlg_data_item_data_box));

	init_list((*box)->items);
	
	(*box)->list_len = bookmark_dlg_list_update(&((*box)->items));
	return *box;
}

/* Get the id of the currently selected bookmark */
bookmark_id bookmark_dlg_box_id_get(struct dlg_data_item_data_box *box) {
	struct box_item *citem;
	int sel;
	
	sel = box->sel;
	
	if (sel == -1) 
		return BAD_BOOKMARK_ID;
	
	/* Sel is an index into the list of bookmarks. Therefore, we spin thru
		until sel equals zero, and return the id at that point */
	foreach(citem, box->items) {
		if (sel == 0) 
			return (bookmark_id)(citem->data);
		sel--;
	}

	return BAD_BOOKMARK_ID;
}



/* Cleans up after the bookmark dialog */
void bookmark_dialog_abort_handler(struct dialog_data *dlg) {
	struct dlg_data_item_data_box *box;
	struct box_item *citem;	/* Item currently being shown */

	box = (struct dlg_data_item_data_box *)(dlg->dlg->items[BM_BOX_IND].data);

	/* Zap the display list */
	bookmark_dlg_list_clear(&(box->items));

	/* Delete the box structure */
	mem_free(box);
}

/* Handles events for a bookmark dialog */
int bookmark_dialog_event_handler(struct dialog_data *dlg, struct event *ev) {

	struct dialog_item_data *di;

	switch (ev->ev) {
		case EV_KBD:
			di = &dlg->items[dlg->selected];
			/* Catch change focus requests */
			if (ev->x == KBD_RIGHT || (ev->x == KBD_TAB && !ev->y)) {
				/* MP: dirty crap!!! this should be done in bfu.c */
				/* Move right */
                display_dlg_item(dlg, &dlg->items[dlg->selected], 0);
				if (++dlg->selected >= BM_BOX_IND) 
					dlg->selected = 0; 
                display_dlg_item(dlg, &dlg->items[dlg->selected], 1);
				
				return EVENT_PROCESSED;
			}
			
			if (ev->x == KBD_LEFT || (ev->x == KBD_TAB && ev->y)) {
				/* Move left */
                display_dlg_item(dlg, &dlg->items[dlg->selected], 0);
				if (--dlg->selected < 0) 
					dlg->selected = BM_BOX_IND - 1; 
                display_dlg_item(dlg, &dlg->items[dlg->selected], 1);
				
				return EVENT_PROCESSED;
			}

			/* Moving the box */
			if (ev->x == KBD_DOWN) {
				box_sel_move(&dlg->items[BM_BOX_IND], 1);
				show_dlg_item_box(dlg, &dlg->items[BM_BOX_IND]);
				
				return EVENT_PROCESSED;
			}
			
			if (ev->x == KBD_UP) {
				box_sel_move(&dlg->items[BM_BOX_IND], -1);
				show_dlg_item_box(dlg, &dlg->items[BM_BOX_IND]);
				
				return EVENT_PROCESSED;
			}
			
			if (ev->x == KBD_PAGE_DOWN) {
				box_sel_move(&dlg->items[BM_BOX_IND], dlg->items[BM_BOX_IND].item->gid / 2);
				show_dlg_item_box(dlg, &dlg->items[BM_BOX_IND]);
				
				return EVENT_PROCESSED;
			}
			
			if (ev->x == KBD_PAGE_UP) {
				box_sel_move(&dlg->items[BM_BOX_IND], (-1) * dlg->items[BM_BOX_IND].item->gid / 2);
				show_dlg_item_box(dlg, &dlg->items[BM_BOX_IND]);
				
				return EVENT_PROCESSED;
			}
			
			/* Selecting a button */
		break;
		case EV_INIT:
		case EV_RESIZE:
		case EV_REDRAW:
		case EV_MOUSE:
		case EV_ABORT:
		break;
		default:
			internal("Unknown event received: %d", ev->ev);
	}

	return EVENT_NOT_PROCESSED;
}


/* The titles to appear in the bookmark add dialog */
unsigned char *bookmark_add_msg[] = {
	TEXT(T_BOOKMARK_TITLE),
	TEXT(T_URL),
};


/* The titles to appear in the bookmark manager */
unsigned char *bookmark_dialog_msg[] = {
	TEXT(T_BOOKMARKS),
};


/* Loads the selected bookmark */
void menu_goto_bookmark(struct terminal *term, void *url, struct session *ses) {
	goto_url(ses, (unsigned char*)url);
}


/* Gets the url of the requested bookmark. 
 * 
 * This returns a pointer to the url's data. Be gentle with it. 
 *
 * Returns a NULL if the bookmark has no url, AND if the passed bookmark id is 
 * invalid. 
 */
const unsigned char *bookmark_get_url(bookmark_id id) {
	struct bookmark *bm = get_bookmark_by_id(id);
	
	if (bm == NULL) {
		return NULL;
	}

	return bm->url;
}

/* Gets the name of the requested bookmark.
 *
 * See bookmark_get_url() for further comments.
 */
const unsigned char *bookmark_get_name(bookmark_id id) {
	struct bookmark *bm = get_bookmark_by_id(id);
	
	if (bm == NULL) {
		return NULL;
	}

	return bm->title;
}
		


/* Goes to the called bookmark */
void bookmark_goto(bookmark_id id, struct session *ses) {
	struct bookmark *bm;
	bm = get_bookmark_by_id(id);
	
	if (bm) 
		goto_url(ses, bm->url);
		
}

/* Shows the bookmark list */
void bookmark_menu(struct terminal *term, void *ddd, struct session *ses)
{
	struct bookmark *bm;
	struct menu_item *mi;
	
	if (!(mi = new_menu(3)))
		return;
		
	foreach(bm, bookmarks) {
		add_to_menu(&mi, stracpy(bm->title), "", 0, MENU_FUNC menu_goto_bookmark, (void *)bm->url, 0);
	}
	
	do_menu(term, mi, ses);
}


/* Called to setup the bookmark dialog */
void layout_bookmark_manager(struct dialog_data *dlg)
{
	int max = 0, min = 0;
	int w, rw;
	int y = -1;
	struct list_head bms;
	struct terminal *term;

	term = dlg->win->term;
	
	/* Find dimensions of dialog */
	max_text_width(term, bookmark_dialog_msg[0], &max);
	min_text_width(term, bookmark_dialog_msg[0], &min);
	max_buttons_width(term, dlg->items + 2, 2, &max);
	min_buttons_width(term, dlg->items + 2, 2, &min);
	
	w = term->x * 9 / 10 - 2 * DIALOG_LB;
	if (w > max) w = max;
	if (w < min) w = min;
	
	if (w > term->x - 2 * DIALOG_LB) 
		w = term->x - 2 * DIALOG_LB;
		
	if (w < 1) 
		w = 1;
		
	w = rw = 50 ;
	
	y += 1;	/* Blankline between top and top of box */
	dlg_format_box(NULL, term, &dlg->items[BM_BOX_IND], dlg->x + DIALOG_LB, &y, w, NULL, AL_LEFT);
	y += 1;	/* Blankline between box and menu */
	dlg_format_buttons(NULL, term, dlg->items, 5, 0, &y, w, &rw, AL_CENTER);
	w = rw;
	dlg->xw = w + 2 * DIALOG_LB;
	dlg->yw = y + 2 * DIALOG_TB;
	center_dlg(dlg);
	draw_dlg(dlg);
	y = dlg->y + DIALOG_TB;
	
	y++;
	dlg_format_box(term, term, &dlg->items[BM_BOX_IND], dlg->x + DIALOG_LB, &y, w, NULL, AL_LEFT);
	y++;
	dlg_format_buttons(term, term, &dlg->items[0], 5, dlg->x + DIALOG_LB, &y, w, NULL, AL_CENTER);
}


void launch_bm_add_doc_dialog(struct terminal *,struct dialog_data *, struct session *);
/* Callback for the "add" button in the bookmark manager */
int push_add_button(struct dialog_data *dlg, struct dialog_item_data *di) {
	launch_bm_add_doc_dialog(dlg->win->term, dlg, (struct session *)dlg->dlg->udata);
	return 0;
}


/* Called when the goto button is pushed */
int push_goto_button(struct dialog_data *dlg, struct dialog_item_data *goto_btn) {
	bookmark_id id;
	struct dlg_data_item_data_box *box;
	
	box = (struct dlg_data_item_data_box*)(dlg->dlg->items[BM_BOX_IND].data);
	
	/* Follow the bookmark */
	id = bookmark_dlg_box_id_get(box);
	if (id != BAD_BOOKMARK_ID) 
		bookmark_goto(id, (struct session*)goto_btn->item->udata);
	/* FIXME There really should be some feedback to the user here */

	/* Close the bookmark dialog */
	delete_window(dlg->win);
	return 0;
}

/* Called when an edit is complete. */
void bookmark_edit_done(struct dialog *d) {
	bookmark_id id = (bookmark_id)d->udata2;
	struct dialog_data *parent;
	
	bookmark_update(id, d->items[0].data, d->items[1].data);

	parent = d->udata;

	/* Tell the bookmark dialog to redraw */
	if (parent) 
		bookmark_dlg_list_update(&(((struct dlg_data_item_data_box*)parent->dlg->items[BM_BOX_IND].data)->items));
}

/* Called when the edit button is pushed */
int push_edit_button(struct dialog_data *dlg, struct dialog_item_data *edit_btn) {
	bookmark_id id;
	struct dlg_data_item_data_box *box;
	
	box = (struct dlg_data_item_data_box*)(dlg->dlg->items[BM_BOX_IND].data);
	
	/* Follow the bookmark */
	id = bookmark_dlg_box_id_get(box);
	if (id != BAD_BOOKMARK_ID) {
		const unsigned char *name = bookmark_get_name(id);
		const unsigned char *url = bookmark_get_url(id);

		bookmark_edit_dialog(dlg->win->term, TEXT(T_EDIT_BOOKMARK), name, url, (struct session*)edit_btn->item->udata, dlg, bookmark_edit_done, (void *)id);
	}
	/* FIXME There really should be some feedback to the user here */
	return 0;
}


/* Used to carry extra info between the push_delete_button() and the really_del_bookmark_ */
struct push_del_button_hop_struct {
	struct dialog *dlg;
	struct dlg_data_item_data_box *box;
	bookmark_id id;
};


/* Called to _really_ delete a bookmark (a confirm in the delete dialog) */
void really_del_bookmark(void *vhop) {
	struct push_del_button_hop_struct *hop;
	struct bookmark *bm;
	int last;

	hop = (struct push_del_button_hop_struct *)vhop;
	
	if (!delete_bookmark_by_id(hop->id)) 
		return;

	last = bookmark_dlg_list_update(&(hop->box->items));
	/* In case we deleted the last bookmark */
	if (hop->box->sel >= (last - 1))
		hop->box->sel = last - 1;

	/* Made in push_delete_button() */
	/*mem_free(vhop);*/
}


/* Callback for the "delete" button in the bookmark manager */
int push_delete_button(struct dialog_data *dlg, struct dialog_item_data *some_useless_delete_button) {
	struct bookmark *bm;
	struct push_del_button_hop_struct *hop;
	struct terminal *term; 
	struct dlg_data_item_data_box *box;

	/* FIXME There's probably a nicer way to do this */
	term = dlg->win->term;

	box = (struct dlg_data_item_data_box*)(dlg->dlg->items[BM_BOX_IND].data);

	bm = get_bookmark_by_id(bookmark_dlg_box_id_get(box));
	
	if (bm == NULL) 
		return 0;


	/* Deleted in really_del_bookmark() */
	hop = mem_alloc(sizeof(struct push_del_button_hop_struct));
		
	hop->id = bm->id;
	hop->dlg = dlg->dlg;
	hop->box = box;
    
	msg_box(term, getml(hop, NULL), TEXT(T_DELETE_BOOKMARK), AL_CENTER | AL_EXTD_TEXT, TEXT(T_DELETE_BOOKMARK), " \"", bm->title, "\" (", TEXT(T_url), ": \"", bm->url, "\")?", NULL, hop, 2, TEXT(T_YES), really_del_bookmark, B_ENTER, TEXT(T_NO), NULL, B_ESC);
	return 0;
}

/* Builds the "Bookmark manager" dialog */
void menu_bookmark_manager(struct terminal *term, void *fcp, struct session *ses)
{
	int fc = (int)fcp;
	struct bookmark *new_bm;

	struct dialog *d;
	
	/* Create the dialog */
	if (!(d = mem_alloc(sizeof(struct dialog) + 7 * sizeof(struct dialog_item) + sizeof(struct bookmark) + 2 * MAX_STR_LEN))) return;
	
	memset(d, 0, sizeof(struct dialog) + 7 * sizeof(struct dialog_item) + sizeof(struct bookmark) + 2 * MAX_STR_LEN);
	
	d->title = TEXT(T_BOOKMARK_MANAGER);
	d->fn = layout_bookmark_manager;
	d->handle_event = bookmark_dialog_event_handler;
	d->abort = bookmark_dialog_abort_handler;
/*	bookmark_build_dlg_list(d);*/	/* Where the currently displayed list goes */
	d->udata = ses;

	d->items[0].type = D_BUTTON;
	d->items[0].gid = B_ENTER;
	d->items[0].fn = push_goto_button;
	d->items[0].udata = ses;
	d->items[0].text = TEXT(T_GOTO);
	
	d->items[1].type = D_BUTTON;
	d->items[1].gid = B_ENTER;
	d->items[1].fn = push_edit_button;
	d->items[1].udata = ses;
	d->items[1].text = TEXT(T_EDIT);
	
	d->items[2].type = D_BUTTON;
	d->items[2].gid = B_ENTER;
	d->items[2].fn = push_delete_button;
	d->items[2].text = TEXT(T_DELETE);
	
	d->items[3].type = D_BUTTON;
	d->items[3].gid = B_ENTER;
	d->items[3].fn = push_add_button;
	d->items[3].text = TEXT(T_ADD);
	
	d->items[4].type = D_BUTTON;
	d->items[4].gid = B_ESC;
	d->items[4].fn = cancel_dialog;
	d->items[4].text = TEXT(T_CLOSE);

	d->items[5].type = D_BOX;	/* MP: D_BOX is nonsence. I tried to remove it, but didn't succeed */
	d->items[5].gid = 12;
	/*d->items[5].data = (void *)bookmark_dlg_box_build();*/	/* Where the currently displayed list goes */
	bookmark_dlg_box_build((struct dlg_data_item_data_box**)&(d->items[5].data));	/* Where the currently displayed list goes */
	
	d->items[6].type = D_END;
	do_dialog(term, d, getml(d, NULL));
}

/****************************************************************************
*
* Bookmark add dialog
*
****************************************************************************/

/* Adds the bookmark */
void bookmark_add_add(struct dialog *d)
{
	struct dialog_data *parent;
	
	add_bookmark(d->items[0].data, d->items[1].data);

	parent = d->udata;

	/* Tell the bookmark dialog to redraw */
	if (parent) 
		bookmark_dlg_list_update(&(((struct dlg_data_item_data_box*)parent->dlg->items[BM_BOX_IND].data)->items));
}


void launch_bm_add_doc_dialog(struct terminal *term,struct dialog_data *parent,struct session *ses) {
			
	bookmark_edit_dialog(term, TEXT(T_ADD_BOOKMARK), NULL, NULL, ses, parent, bookmark_add_add, NULL);
}

/* Called to launch an add dialog on the current link */
void launch_bm_add_link_dialog(struct terminal *term,struct dialog_data *parent,struct session *ses) {
	unsigned char url[MAX_STR_LEN];

	/* FIXME: Logic error -- if there is no current link, 
	 * get_current_link_url() will return NULL, which will cause 
	 * bookmark_add_dialog() to try and use the current document's url. 
	 * Instead, it should use "". 
	 */ 
	bookmark_edit_dialog(term, TEXT(T_ADD_BOOKMARK), NULL, get_current_link_url(ses, url, MAX_STR_LEN), ses, parent, bookmark_add_add, NULL);
}



unsigned char *bm_add_msg[] = {
	TEXT(T_NNAME),
	TEXT(T_URL),
};

/* Called to setup the add bookmark dialog */
void layout_add_dialog(struct dialog_data *dlg)
{
	int max = 0, min = 0;
	int w, rw;
	int x, y = -1;
	struct terminal *term;

	term = dlg->win->term;
	
	max_text_width(term, bm_add_msg[0], &max);
	min_text_width(term, bm_add_msg[0], &min);
	max_text_width(term, bm_add_msg[1], &max);
	min_text_width(term, bm_add_msg[1], &min);
	max_buttons_width(term, dlg->items + 2, 2, &max);
	min_buttons_width(term, dlg->items + 2, 2, &min);
	w = term->x * 9 / 10 - 2 * DIALOG_LB;
	
	if (w > max) w = max;
	if (w < min) w = min;
	if (w > term->x - 2 * DIALOG_LB) w = term->x - 2 * DIALOG_LB;
	if (w < 1) w = 1;

	w = rw = 50;
	
	dlg_format_text(NULL, term, bm_add_msg[0], 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT);
	y += 2;
	dlg_format_text(NULL, term, bm_add_msg[1], 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT);
	y += 2;
	dlg_format_buttons(NULL, term, dlg->items + 2, 2, 0, &y, w, &rw, AL_CENTER);
	w = rw;
	dlg->xw = w + 2 * DIALOG_LB;
	dlg->yw = y + 2 * DIALOG_TB;
	center_dlg(dlg);
	draw_dlg(dlg);
	y = dlg->y + DIALOG_TB;
	dlg_format_text(term, term, bm_add_msg[0], dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	dlg_format_field(NULL, term, &dlg->items[0], dlg->x + DIALOG_LB, &y, w, NULL, AL_LEFT);
	y++;
	dlg_format_text(term, term, bm_add_msg[1], dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT);
	dlg_format_field(term, term, &dlg->items[1], dlg->x + DIALOG_LB, &y, w, NULL, AL_LEFT);
	y++;
	dlg_format_buttons(term, term, &dlg->items[2], 2, dlg->x + DIALOG_LB, &y, w, NULL, AL_CENTER);
	
}

/* Edits a bookmark's fields. 
 * If parent is defined, then that points to a dialog that should be sent 
 * an update when the add is done.
 *
 * If either of src_name or src_url are NULL, try to obtain the name and url 
 * of the current document. If you want to create two null fields, pass in a 
 * pointer to a zero length string ("").
*/
void bookmark_edit_dialog(
		struct terminal *term /* Terminal to write on. */, 
		unsigned char *title /* Title of the dialog. */, 
		const unsigned char *src_name /* Pointer to name to use. (can be null)*/, 
		const unsigned char *src_url /* Url to use. (can be null) */, 
		struct session *ses, 
		struct dialog_data *parent /* The parent window launching this one. */,
		void when_done(struct dialog *) /* Function to execute on 'ok'. */, 
		void *done_data /* Spare data to pass to when_done. Stored in udata2 */
) {
	unsigned char *name, *url;
	struct bookmark *new_bm;

	struct dialog *d;
	
	/* Create the dialog */
	if (!(d = mem_alloc(sizeof(struct dialog) + 5 * sizeof(struct dialog_item) + sizeof(struct extension) + 2 * MAX_STR_LEN))) return;
	memset(d, 0, sizeof(struct dialog) + 5 * sizeof(struct dialog_item) + sizeof(struct extension) + 2 * MAX_STR_LEN);

	name = (unsigned char *)&d->items[5];
	url = name + MAX_STR_LEN;

	/* Get the name */
	if (src_name == NULL) {
		/* Unknown name. */
		get_current_title(ses, name, MAX_STR_LEN);
	} else {
		/* Known name. */
		safe_strncpy(name, src_name, MAX_STR_LEN);
	}

	/* Get the url */
	if (src_url == NULL) {
		/* Unknown . */
		get_current_url(ses, url, MAX_STR_LEN);
	} else {
		/* Known url. */
		safe_strncpy(url, src_url, MAX_STR_LEN);
	}

	d->title = title;
	d->fn = layout_add_dialog;
	d->refresh = (void (*)(void *))when_done;
	d->refresh_data = d;
	d->udata = parent;
	d->udata2 = done_data;

	d->items[0].type = D_FIELD;
	d->items[0].dlen = MAX_STR_LEN;
	d->items[0].data = name;
	d->items[0].fn = check_nonempty;

	d->items[1].type = D_FIELD;
	d->items[1].dlen = MAX_STR_LEN;
	d->items[1].data = url;
	d->items[1].fn = check_nonempty;

	d->items[2].type = D_BUTTON;
	d->items[2].gid = B_ENTER;
	d->items[2].fn = ok_dialog;
	d->items[2].text = TEXT(T_OK);
	
	d->items[3].type = D_BUTTON;
	d->items[3].gid = B_ESC;
	d->items[3].text = TEXT(T_CANCEL);
	d->items[3].fn = cancel_dialog;
	
	d->items[4].type = D_END;
	
	do_dialog(term, d, getml(d, NULL));
}
