/*
 *   xmcd - Motif(tm) CD Audio Player
 *
 *   Copyright (C) 1993-1996  Ti Kan
 *   E-mail: ti@amb.org
 *
 *   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.
 *
 */
#ifndef LINT
static char *_dbprog_c_ident_ = "@(#)dbprog.c	6.66 96/03/21";
#endif

#define _XMINC_

#include "common_d/appenv.h"
#include "common_d/patchlevel.h"
#include "common_d/util.h"
#include "xmcd_d/xmcd.h"
#include "xmcd_d/widget.h"
#include "xmcd_d/cdfunc.h"
#include "xmcd_d/dbprog.h"
#include "libdi_d/libdi.h"
#include "cddb_d/cddb.h"


extern widgets_t	widgets;
extern appdata_t	app_data;
extern FILE		*errfp;

STATIC char		timemode;		/* Time display mode flag */
STATIC int		sel_pos = -1,		/* Track list select position */
			ind_pos = -1,		/* Track list highlight pos */
			linksel_pos = -1,	/* Link select position */
			extt_pos = -1,		/* Ext track info position */
			dirsel_mode = DIRSEL_SAVE;
						/* Directory selector mode */
STATIC bool_t		title_edited,		/* Track title edited flag */
			extt_setup,		/* Ext track info setup */
			pgmseq_editing,		/* Pgm seq edited flag */
			dbprog_changed,		/* Flag to indicate change */
			extd_manage,		/* Whether to manage extd */
			extt_manage,		/* Whether to manage extt */
			dbmatch_mode;		/* CDDB match query mode */
STATIC cddb_incore_t	cur_db;			/* Database entry of CD */



/***********************
 *  internal routines  *
 ***********************/


/*
 * dbprog_dpyid
 *	Display the disc ID in the Disc ID Indicator.
 *
 * Args:
 *	Nothing.
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_dpyid(void)
{
	XmString	xs;
	char		str[STR_BUF_SZ];
	static char	prev[STR_BUF_SZ];

	if (cur_db.discid != 0)
		sprintf(str, "%s\n%08x", cur_db.category, cur_db.discid);
	else
		sprintf(str, "\n   --   ");

	if (strcmp(str, prev) == 0)
		/* No change */
		return;

	xs = XmStringCreateLtoR(str, XmSTRING_DEFAULT_CHARSET);

	XtVaSetValues(
		widgets.dbprog.discid_ind,
		XmNlabelString,
		xs,
		NULL
	);

	XmStringFree(xs);

	strcpy(prev, str);
}


/*
 * dbprog_pgm_parse
 *	Parse the program mode play sequence text string, and
 *	update the playorder table in the curstat_t structure.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	TRUE=success, FALSE=error.
 */
STATIC bool_t
dbprog_pgm_parse(curstat_t *s)
{
	int	i,
		j,
		n;
	char	*p,
		*q,
		*tmpbuf;
	bool_t	last = FALSE;

	if (cur_db.playorder == NULL)
		/* Nothing to do */
		return TRUE;

	n = strlen(cur_db.playorder) + 1;
	if ((tmpbuf = (char *) MEM_ALLOC(n)) == NULL) {
		cd_fatal_popup(app_data.str_fatal, app_data.str_nomemory);
		return FALSE;
	}

	strcpy(tmpbuf, cur_db.playorder);

	s->prog_tot = 0;

	for (i = 0, p = q = tmpbuf; i < MAXTRACK; i++, p = ++q) {
		/* Skip p to the next digit */
		for (; !isdigit(*p) && *p != '\0'; p++)
			;

		if (*p == '\0')
			/* No more to do */
			break;

		/* Skip q to the next non-digit */
		for (q = p; isdigit(*q); q++)
			;

		if (*q == PGM_SEPCHAR)
			*q = '\0';
		else if (*q == '\0')
			last = TRUE;
		else {
			MEM_FREE(tmpbuf);
			return FALSE;
		}

		if (q > p) {
			/* Update play sequence */
			for (j = 0; j < MAXTRACK; j++) {
				if (s->trkinfo[j].trkno == atoi(p)) {
					s->playorder[i] = j;
					s->prog_tot++;
					break;
				}
			}

			if (j >= MAXTRACK) {
				MEM_FREE(tmpbuf);
				return FALSE;
			}
		}

		if (last)
			break;
	}

	MEM_FREE(tmpbuf);

	return TRUE;
}


/*
 * dbprog_listupd_ent
 *	Update a track entry in the track list.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *	pos - Track position to update.
 *	title - Track title string.
 *	new - Whether this is a new entry or a replacement entry.
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_listupd_ent(curstat_t *s, int pos, char *title, bool_t new)
{
	int		n,
			secs,
			min,
			sec;
	char		*str;
	XmString	xs;

	if (timemode == TIME_TOTAL) {
		min = s->trkinfo[pos].min;
		sec = s->trkinfo[pos].sec;
	}
	else {
		secs = ((s->trkinfo[pos+1].min * 60 + s->trkinfo[pos+1].sec) - 
			(s->trkinfo[pos].min * 60 + s->trkinfo[pos].sec));
		min = (byte_t) (secs / 60);
		sec = (byte_t) (secs % 60);
	}

	n = strlen(title) + TRKLIST_PFLEN;
	if ((str = (char *) MEM_ALLOC(n)) == NULL) {
		cd_fatal_popup(
			app_data.str_fatal,
			app_data.str_nomemory
		);
		return;
	}

	sprintf(str, TRKLIST_FMT,
		s->trkinfo[pos].trkno,
		min, sec, title,
		(cur_db.extt[pos] != NULL) ?  EXTT_FLGSTR : "");

	if (s->mode != MOD_BUSY && s->mode != MOD_NODISC &&
	    s->cur_trk >= 0 && di_curtrk_pos(s) == pos)
		xs = XmStringCreate(str, CHSET2);
	else
		xs = XmStringCreate(str, CHSET1);

	if (new)
		XmListAddItemUnselected(widgets.dbprog.trk_list, xs, pos+1);
	else
		XmListReplaceItemsPos(widgets.dbprog.trk_list, &xs, 1, pos+1);

	XmStringFree(xs);
	MEM_FREE(str);
}


/*
 * dbprog_listupd_all
 *	Update the track list display to reflect the contents of
 *	the trkinfo table in the curstat_t structure.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *	new - Whether this is a new entry or a replacement entry.
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_listupd_all(curstat_t *s, bool_t new)
{
	int	i;

	if (new)
		XmListDeleteAllItems(widgets.dbprog.trk_list);

	for (i = 0; i < (int) s->tot_trks; i++) {
		/* Update track list entry */
		dbprog_listupd_ent(
			s, i,
			(cur_db.trklist[i] != NULL) ?
				cur_db.trklist[i] : UNDEF_STR,
			new
		);
	}

	/* If this item is previously selected, re-select it */
	if (sel_pos > 0)
		XmListSelectPos(widgets.dbprog.trk_list, sel_pos, False);
	else if (ind_pos > 0)
		XmListSelectPos(widgets.dbprog.trk_list, ind_pos, False);
}


/*
 * dbprog_structupd
 *	Update the state of the various widgets fields in the
 *	database/program window to match that of the cur_db structure.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_structupd(curstat_t *s)
{
	/* Disc title */
	if (cur_db.dtitle != NULL) {
		XmTextSetString(widgets.dbprog.dtitle_txt, cur_db.dtitle);
		XmTextSetInsertionPosition(
			widgets.dbprog.dtitle_txt,
			strlen(cur_db.dtitle)
		);
	}
	else {
		XmTextSetString(widgets.dbprog.dtitle_txt, UNDEF_STR);
		XmTextSetInsertionPosition(widgets.dbprog.dtitle_txt, 2);
	}


	/* Disc extended info popup */
	if (cur_db.extd != NULL)
		XmTextSetString(widgets.dbextd.disc_txt, cur_db.extd);
	else
		XmTextSetString(widgets.dbextd.disc_txt, "");

	/* Number of tracks */
	cur_db.ntrk = (word16_t) s->tot_trks;

	/* Track title list */
	sel_pos = -1;
	dbprog_listupd_all(s, TRUE);

	/* Track extended info popup: This is loaded when the user
	 * pops it up.
	 */
	XmTextSetString(widgets.dbextt.trk_txt, "");

	/* Program sequence */
	if (cur_db.playorder != NULL) {
		XmTextSetString(widgets.dbprog.pgmseq_txt, cur_db.playorder);
		XmTextSetInsertionPosition(
			widgets.dbprog.pgmseq_txt,
			strlen(cur_db.playorder)
		);
		XtSetSensitive(widgets.dbprog.clrpgm_btn, True);
	}
	else {
		XmTextSetString(widgets.dbprog.pgmseq_txt, "");
		XtSetSensitive(widgets.dbprog.clrpgm_btn, False);
	}

	dbprog_changed = FALSE;
	XtSetSensitive(widgets.dbprog.addpgm_btn, False);
	XtSetSensitive(widgets.dbprog.savedb_btn, False);
	XtSetSensitive(widgets.dbprog.extd_btn, True);
	XtSetSensitive(widgets.dbprog.extt_btn, False);

	/* Update display */
	dpy_dbmode(s, FALSE);
}


/*
 * dbprog_extdupd
 *	Update the Extended disc info text field in the cur_db structure
 *	to match the contents shown in the text widget.
 *
 * Args:
 *	Nothing
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_extdupd(void)
{
	/* Update in-core database structure */
	if (cur_db.extd != NULL)
		MEM_FREE(cur_db.extd);

	if ((cur_db.extd = XmTextGetString(widgets.dbextd.disc_txt)) == NULL)
		return;

	if (cur_db.extd[0] == '\0') {
		MEM_FREE(cur_db.extd);
		cur_db.extd = NULL;
	}

	if (dbprog_changed)
		XtSetSensitive(widgets.dbprog.savedb_btn, True);
}


/*
 * dbprog_exttupd
 *	Update the Extended track info text field in the cur_db structure
 *	to match the contents shown in the text widget.
 *
 * Args:
 *	Nothing
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_exttupd(void)
{
	if (extt_pos < 0)
		return;

	/* Update in-core database structure */
	if (cur_db.extt[extt_pos] != NULL)
		MEM_FREE(cur_db.extt[extt_pos]);

	if ((cur_db.extt[extt_pos] =
		XmTextGetString(widgets.dbextt.trk_txt)) == NULL) {
		extt_pos = -1;
		return;
	}

	if (cur_db.extt[extt_pos][0] == '\0') {
		MEM_FREE(cur_db.extt[extt_pos]);
		cur_db.extt[extt_pos] = NULL;
	}

	/* Update track list entry */
	dbprog_listupd_ent(
		curstat_addr(), extt_pos, 
		(cur_db.trklist[extt_pos] != NULL) ?
			cur_db.trklist[extt_pos] : UNDEF_STR,
		FALSE
	);

	if (sel_pos == (extt_pos+1))
		/* This item is previously selected */
		XmListSelectPos(widgets.dbprog.trk_list, sel_pos, False);

	extt_pos = -1;

	if (dbprog_changed)
		XtSetSensitive(widgets.dbprog.savedb_btn, True);
}


/*
 * dbprog_dbput
 *	Write in-core CD database entry to disc file.  This routine
 *	forks a child to do the actual file I/O.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_dbput(curstat_t *s)
{
	cddb_ret_t	ret;
	char		errstr[ERR_BUF_SZ];

	if (cur_db.dbfile == NULL)
		/* Output file undefined */
		return;

	/* Update structures if necessary */
	if (XtIsManaged(widgets.dbextd.form))
		dbprog_extdupd();
	if (XtIsManaged(widgets.dbextt.form))
		dbprog_exttupd();

	/* Change to watch cursor */
	cd_busycurs(TRUE);

	/* Save to database */
	if ((ret = cddb_save(&cur_db, s)) != 0) {
		DBGPRN(errfp, "cddb_save: status=%d arg=%d\n",
			CDDB_GET_STAT(ret), CDDB_GET_ARG(ret));
	}

	/* Change to normal cursor */
	cd_busycurs(FALSE);

	switch (CDDB_GET_STAT(ret)) {
	case 0:
		break;

	case FORK_ERR:
		sprintf(errstr, app_data.str_saverr_fork, CDDB_GET_ARG(ret));
		cd_warning_popup(app_data.str_warning, errstr);
		return;

	case SETUID_ERR:
		sprintf(errstr, app_data.str_saverr_suid,
			get_ouid(), get_ogid());
		cd_warning_popup(app_data.str_warning, errstr);
		return;

	case OPEN_ERR:
		sprintf(errstr, app_data.str_saverr_open);
		cd_warning_popup(app_data.str_warning, errstr);
		return;

	case CLOSE_ERR:
		sprintf(errstr, app_data.str_saverr_close);
		cd_warning_popup(app_data.str_warning, errstr);
		return;

	case KILLED_ERR:
		sprintf(errstr, app_data.str_saverr_killed, CDDB_GET_ARG(ret));
		cd_warning_popup(app_data.str_warning, errstr);
		return;

	case WRITE_ERR:
	default:
		sprintf(errstr, app_data.str_saverr_write);
		cd_warning_popup(app_data.str_warning, errstr);
		return;
	}

	/* Set flag */
	s->cddb = CDDB_LOCAL;

	/* All edits have been saved, so clear flag */
	dbprog_changed = FALSE;

	/* Update display */
	dpy_dbmode(s, FALSE);
	dbprog_dpyid();

	/* Update current disc file */
	cddb_curfileupd(&cur_db);

	XtSetSensitive(widgets.dbprog.send_btn, True);
	XtSetSensitive(widgets.dbprog.linkdb_btn, False);
	XtSetSensitive(widgets.dbprog.savedb_btn, False);
	XmProcessTraversal(
		widgets.dbprog.cancel_btn,
		XmTRAVERSE_CURRENT
	);
}


/*
 * dbprog_dbsend
 *	Send current CD database entry to archive site via e-mail.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
STATIC void
dbprog_dbsend(curstat_t *s)
{
	cddb_ret_t	ret;

	/* Change to the watch cursor */
	cd_busycurs(TRUE);

	/* Send the CDDB entry */
	if ((ret = cddb_send(&cur_db, s)) != 0) {
		DBGPRN(errfp, "cddb_send: status=%d arg=%d\n",
			CDDB_GET_STAT(ret), CDDB_GET_ARG(ret));
	}

	/* Change to the normal cursor */
	cd_busycurs(FALSE);

	switch (CDDB_GET_STAT(ret)) {
	case 0:
		/* Make the send button insensitive */
		XtSetSensitive(widgets.dbprog.send_btn, False);
		break;
	default:
		cd_warning_popup(app_data.str_warning, app_data.str_mailerr);
		break;
	}
}


/*
 * dbprog_dblink
 *	Pop up the search-link popup window, to let the user pick
 *	an existing CD database file entry to link the current disc to.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
/*ARGSUSED*/
STATIC void
dbprog_dblink(curstat_t *s)
{
	int		i;
	cddb_linkopts_t	*p;
	XmString	xs;
	byte_t		min,
			sec,
			frame;
	char		buf[STR_BUF_SZ * 2];

	if (cur_db.dbfile == NULL)
		/* Output file undefined */
		return;

	/* Change to the watch cursor */
	cd_busycurs(TRUE);

	/* Search directory for possible alternatives, and allow
	 * user to select a file to link to.
	 */

	XmListDeleteAllItems(widgets.linksel.link_list);

	/* Build linkopts list */
	if (!cddb_init_linkopts(&cur_db, s)) {
		/* Error: shouldn't get here */
		cd_busycurs(FALSE);
		cd_info_popup(app_data.str_info, app_data.str_nolink);
		return;
	}

	/* Traverse the list and add to list widget */
	for (i = 0, p = cddb_linkopts(); p != NULL; i++, p = p->next) {
		if (p->offset == OFFSET_UNKN) {
			sprintf(buf, "??:??  %0.120s", p->dtitle);
			xs = XmStringCreate(buf, CHSET1);
		}
		else {
			blktomsf(p->offset, &min, &sec, &frame, 0);
			sprintf(buf, "%02u:%02u  %0.120s", min, sec, p->dtitle);
			if (p->offset < OFFSET_THRESH)
				xs = XmStringCreate(buf, CHSET2);
			else
				xs = XmStringCreate(buf, CHSET1);
		}

		XmListAddItemUnselected(widgets.linksel.link_list, xs, i + 1);
		XmStringFree(xs);
	}

	/* Change to the normal cursor */
	cd_busycurs(FALSE);

	if (i == 0) {
		cd_info_popup(app_data.str_info, app_data.str_nolink);
	}
	else if (!XtIsManaged(widgets.linksel.form)) {
		linksel_pos = 0;

		/* Pop up the link selector window */
		XtManageChild(widgets.linksel.form);
	}
}


/***********************
 *   public routines   *
 ***********************/


/*
 * dbprog_curtrkupd
 *	Update the track list display to show the current playing
 *	track entry in bold font.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
void
dbprog_curtrkupd(curstat_t *s)
{
	int			n,
				pos,
				list_pos,
				top_pos,
				bottom_pos,
				vis_cnt;
	char			*str;
	static int		sav_pos = -1;

	if (sav_pos >= 0) {
		n = strlen((cur_db.trklist[sav_pos] == NULL) ?
			   UNDEF_STR : cur_db.trklist[sav_pos]) + TRKLIST_PFLEN;

		if ((str = (char *) MEM_ALLOC(n)) == NULL) {
			cd_fatal_popup(
				app_data.str_fatal,
				app_data.str_nomemory
			);
			return;
		}

		/* Update track list entry: un-highlight previous track */
		dbprog_listupd_ent(
			s, sav_pos, 
			(cur_db.trklist[sav_pos] != NULL) ?
				cur_db.trklist[sav_pos] : UNDEF_STR,
			FALSE
		);

		if (sel_pos == sav_pos + 1)
			/* This item is previously selected */
			XmListSelectPos(widgets.dbprog.trk_list,
					sel_pos, False);
	}

	if (s->cur_trk <= 0 || s->mode == MOD_BUSY || s->mode == MOD_NODISC) {
		sav_pos = -1;
		return;
	}

	sav_pos = pos = di_curtrk_pos(s);
	list_pos = pos + 1;

	/* Update track list entry: highlight current track */
	dbprog_listupd_ent(
		s, sav_pos, 
		(cur_db.trklist[sav_pos] != NULL) ?
			cur_db.trklist[sav_pos] : UNDEF_STR,
		FALSE
	);

	/* If this item is previously selected, re-select it */
	if (sel_pos == list_pos)
		XmListSelectPos(widgets.dbprog.trk_list, sel_pos, False);
	else if (ind_pos == list_pos)
		XmListSelectPos(widgets.dbprog.trk_list, ind_pos, False);

	/* Auto-scroll the track list if the current track is not visible.
	 * No scrolling is done while a track list entry is selected
	 * or if a track title is being edited.
	 */
	if (sel_pos < 0 && ind_pos < 0) {
		/* Determine range of tracks visible in current list */
		XtVaGetValues(widgets.dbprog.trk_list,
			XmNtopItemPosition, &top_pos,
			XmNvisibleItemCount, &vis_cnt,
			NULL
		);

		bottom_pos = top_pos + vis_cnt - 1;

		/* Scroll the list if necessary, and try to keep the
		 * current playing track near the middle of the visible
		 * portion of the list.
		 */
		if (list_pos < top_pos) {
			/* Scroll up */
			n = list_pos - (vis_cnt / 2);
			if (n < 1)
				n = 1;
			XmListSetPos(widgets.dbprog.trk_list, n);
		}
		else if (list_pos > bottom_pos) {
			/* Scroll down */
			n = list_pos + (vis_cnt / 2);
			if (n > (int) cur_db.ntrk)
				n = (int) cur_db.ntrk;
			XmListSetBottomPos(widgets.dbprog.trk_list, n);
		}
	}
}


/*
 * dbprog_progclear
 *	Clear program sequence.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
void
dbprog_progclear(curstat_t *s)
{
	if (cur_db.playorder == NULL)
		/* Nothing to do */
		return;

	MEM_FREE(cur_db.playorder);
	cur_db.playorder = NULL;

	s->prog_tot = 0;
	s->prog_cnt = 0;
	s->program = FALSE;

	/* Update display */
	dpy_progmode(s);
}


/*
 * dbprog_dbclear
 *	Clear in-core CD database entry.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *	reload - Whether we are going to be re-loading the CDDB entry.
 *
 * Return:
 *	Nothing.
 */
void
dbprog_dbclear(curstat_t *s, bool_t reload)
{
	word16_t	flags;
	bool_t		upd_display = FALSE;
	static bool_t	first_time = TRUE;

	/* Pop down the extd and extt popups if necessary */
	if (XtIsManaged(widgets.dbextd.form)) {
		dbprog_extd_cancel(
			widgets.dbextd.cancel_btn,
			(XtPointer) s,
			(XtPointer) NULL
		);
	}
	if (XtIsManaged(widgets.dbextt.form)) {
		dbprog_extt_cancel(
			widgets.dbextt.cancel_btn,
			(XtPointer) s,
			(XtPointer) NULL
		);
	}

	/* Save in-core database to file, if so designated */
	if (s->flags & STAT_SAVE) {
		s->flags |= STAT_CLEAR;
		dbprog_save(widgets.dbprog.savedb_btn, (XtPointer) s, NULL);
		return;
	}

	if (first_time || cur_db.ntrk != 0) {
		first_time = FALSE;
		upd_display = TRUE;
	}

	/* Save flags */
	flags = s->flags;

	/* Clear database entry structure */
	cddb_clear(&cur_db, s, reload);

	/* Clear flags */
	s->flags &= ~(STAT_SAVE | STAT_CLEAR | STAT_EJECT | STAT_EXIT);

	/* Set flag */
	s->cddb = CDDB_INVALID;

	if (upd_display) {
		/* Update main window displays */
		dpy_dtitle(s);
		dpy_ttitle(s);

		/* Update disc ID indicator */
		dbprog_dpyid();

		/* Update current disc file */
		cddb_curfileupd(&cur_db);

		/* Update database/program display */
		XmTextSetString(widgets.dbprog.dtitle_txt, "");
		XmListDeleteAllItems(widgets.dbprog.trk_list);
		XmTextSetString(widgets.dbprog.ttitle_txt, "");
		XmTextSetString(widgets.dbprog.pgmseq_txt, "");
		XmTextSetString(widgets.dbextd.disc_txt, "");
		XmTextSetString(widgets.dbextt.trk_txt, "");

		/* Make some buttons insensitive */
		XtSetSensitive(widgets.dbprog.addpgm_btn, False);
		XtSetSensitive(widgets.dbprog.clrpgm_btn, False);
		XtSetSensitive(widgets.dbprog.send_btn, False);
		XtSetSensitive(widgets.dbprog.savedb_btn, False);
		XtSetSensitive(widgets.dbprog.linkdb_btn, False);
		XtSetSensitive(widgets.dbprog.extd_btn, False);
		XtSetSensitive(widgets.dbprog.extt_btn, False);
	}

	/* Clear changed flag */
	dbprog_changed = FALSE;

	/* Eject the CD, if so specified */
	if (flags & STAT_EJECT) {
		cd_busycurs(TRUE);

		di_load_eject(s);

		cd_busycurs(FALSE);
	}

	/* Quit the application, if so specified */
	if (flags & STAT_EXIT)
		cd_quit(s);
}


/*
 * dbprog_dbget
 *	Read in the CD database file entry pertaining to the
 *	currently loaded disc, if available.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
void
dbprog_dbget(curstat_t *s)
{
	cddb_ret_t	ret;
	cddb_match_t	*p;
	char		*msg;
	XmString	xs;

	if (cur_db.discid == 0) {
		/* Get magic disc identifier */
		if ((cur_db.discid = cddb_discid(s)) == 0)
			/* Invalid identifier */
			return;
	}

	if (s->onetrk_prog && s->mode != MOD_STOP) {
		/* Currently playing a single-track program: clear it first */
		s->onetrk_prog = FALSE;
		dbprog_progclear(s);
	}

	/* Set the cddb indicator */
	s->cddb = CDDB_WAIT;

	/* Update widgets */
	dbprog_structupd(s);
	dpy_dtitle(s);
	dpy_ttitle(s);

	XtSetSensitive(widgets.dbprog.loaddb_btn, False);

	/* Load CD database entry */
	if ((ret = cddb_load(&cur_db, s)) != 0) {
		DBGPRN(errfp, "cddb_load: status=%d arg=%d\n",
			CDDB_GET_STAT(ret), CDDB_GET_ARG(ret));
	}

	switch (CDDB_GET_STAT(ret)) {
	case 0:
		/* Success */
		break;

	case MATCH_ERR:
		/* Inexact match found - ask user */
		p = cur_db.matchlist;
		if (p != NULL && !dbmatch_mode) {
			/* Set flag to prevent recursion */
			dbmatch_mode = TRUE;

			msg = (char *) MEM_ALLOC(
				strlen(app_data.str_rmtmatch) +
				strlen(p->dtitle)
			);
			if (msg == NULL) {
				cd_fatal_popup(
					app_data.str_fatal,
					app_data.str_nomemory
				);
				return;
			}

			/* Set up dialog box message string */
			sprintf(msg, app_data.str_rmtmatch,
				p->category, p->dtitle);

			xs = XmStringCreateLtoR(msg, XmSTRING_DEFAULT_CHARSET);

			XtVaSetValues(widgets.dialog.dbmatch,
				XmNmessageString, xs,
				NULL
			);

			XmStringFree(xs);
			MEM_FREE(msg);

			/* Set pointer */
			cur_db.match_cur = p;

			/* Pop up dialog box */
			XtManageChild(widgets.dialog.dbmatch);
			return;
		}

		cur_db.type = CDDB_INVALID;
		/*FALLTHRU*/

	default:
		/* Cannot find CDDB entry, or other error */

		/* Set the cddb indicator */
		s->cddb = cur_db.type;

		/* Update widgets */
		dbprog_structupd(s);

		XtSetSensitive(widgets.dbprog.linkdb_btn, True);
		XtSetSensitive(widgets.dbprog.loaddb_btn, True);

		/* Update display */
		dbprog_dpyid();

		/* Update current disc file */
		cddb_curfileupd(&cur_db);

		dpy_dtitle(s);
		dpy_ttitle(s);

		return;
	}

	/* Set the cddb indicator */
	s->cddb = cur_db.type;

	/* Update widgets */
	dbprog_structupd(s);

	if (cur_db.type == CDDB_LOCAL) {
		XtSetSensitive(widgets.dbprog.send_btn, True);
		XtSetSensitive(widgets.dbprog.savedb_btn, False);
	}
	else {
		XtSetSensitive(widgets.dbprog.send_btn, False);
		XtSetSensitive(widgets.dbprog.savedb_btn, True);
	}

	XtSetSensitive(widgets.dbprog.linkdb_btn, False);
	XtSetSensitive(widgets.dbprog.loaddb_btn, True);

	/* Parse play order string and set the play order */
	if (!dbprog_pgm_parse(s))
		cd_warning_popup(app_data.str_warning, app_data.str_seqfmterr);

	/* Update display */
	dpy_progmode(s);
	dpy_dtitle(s);
	dpy_ttitle(s);
	dbprog_dpyid();

	/* Update current disc file */
	cddb_curfileupd(&cur_db);
}


/*
 * dbprog_init
 *	Initialize the CD database/program subsystem.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Nothing.
 */
void
dbprog_init(curstat_t *s)
{
	timemode = TIME_TRACK;

	/* Clear the in-core structure */
	dbprog_dbclear(s, FALSE);
}


/*
 * dbprog_chgsave
 *	If in-core CDDB info has been changed, ask user if it should
 *	be saved.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Whether the application should proceed to shut down
 */
bool_t
dbprog_chgsave(curstat_t *s)
{
	char	*msg;

	if (cur_db.discid != 0 && dbprog_changed) {
		if (!XtIsManaged(widgets.dialog.confirm)) {
			if (cur_db.dtitle == NULL) {
				msg = (char *) MEM_ALLOC(
					strlen(app_data.str_chgsave) +
					strlen(app_data.str_unkndisc)
				);
			}
			else {
				msg = (char *) MEM_ALLOC(
					strlen(app_data.str_chgsave) +
					strlen(cur_db.dtitle)
				);
			}

			if (msg == NULL) {
				cd_fatal_popup(
					app_data.str_fatal,
					app_data.str_nomemory
				);
				return FALSE;
			}

			sprintf(msg, app_data.str_chgsave,
				(cur_db.dtitle == NULL) ?
					app_data.str_unkndisc : cur_db.dtitle);

			/* Pop-up the dialog box */
			cd_confirm_popup(
				app_data.str_confirm,
				msg,
				dbprog_do_clear,
				(XtPointer) STAT_SAVE,
				dbprog_do_clear,
				(XtPointer) 0
			);

			MEM_FREE(msg);
		}
		return FALSE;
	}

	return TRUE;
}


/*
 * dbprog_curdtitle
 *	Return the current disc title string.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Disc title text string, or the null string if there
 *	is no title available.
 */
char *
dbprog_curdtitle(curstat_t *s)
{
	if (s->mode == MOD_BUSY || s->mode == MOD_NODISC ||
	    s->cddb == CDDB_WAIT)
		return ("");

	return (cur_db.dtitle == NULL ? app_data.str_unkndisc : cur_db.dtitle);
}


/*
 * dbprog_curttitle
 *	Return the current track title string.
 *
 * Args:
 *	s - Pointer to the curstat_t structure.
 *
 * Return:
 *	Track title text string, or the null string if there
 *	is no title available.
 */
char *
dbprog_curttitle(curstat_t *s)
{
	int	n = di_curtrk_pos(s);

	if (s->mode == MOD_BUSY || s->mode == MOD_NODISC ||
	    (int) s->cur_trk < 0 || s->cddb == CDDB_WAIT)
		return ("");

	if (n < 0 || cur_db.trklist[n] == NULL)
		return (app_data.str_unkntrk);

	return (cur_db.trklist[n]);
}


/**************** vv Callback routines vv ****************/

/*
 * dbprog_popup
 *	Pop up the database/program subsystem window.
 */
void
dbprog_popup(Widget w, XtPointer client_data, XtPointer call_data)
{
	if (!XtIsManaged(widgets.dbprog.form)) {
		/* Pop up the dbprog window */
		XtManageChild(widgets.dbprog.form);

		/* Pop up the extd/extt windows if necessary */
		if (extd_manage)
			dbprog_extd(w, client_data, call_data);

		if (extt_manage)
			dbprog_extt(w, (XtPointer) FALSE, call_data);
	}
}


/*
 * dbprog_dtitle_new
 *	Disc title editor text widget callback function.
 */
/*ARGSUSED*/
void
dbprog_dtitle_new(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
	curstat_t		*s = (curstat_t *)(void *) client_data;
	char			*str;
	XmString		xs;

	if (p->reason != XmCR_ACTIVATE && p->reason != XmCR_VALUE_CHANGED)
		return;

	if ((str = XmTextGetString(w)) == NULL)
		return;

	if (strcmp(str, UNDEF_STR) == 0) {
		if (cur_db.dtitle != NULL) {
			MEM_FREE(cur_db.dtitle);
			cur_db.dtitle = NULL;
		}
		XtFree(str);
		return;
	}

	dbprog_changed = TRUE;
	XtSetSensitive(widgets.dbprog.savedb_btn, True);

	if (cur_db.dtitle != NULL)
		MEM_FREE(cur_db.dtitle);

	if (str[0] == '\0') {
		XtFree(str);
		cur_db.dtitle = NULL;
	}
	else
		cur_db.dtitle = str;

	/* Update the extd window if necessary */
	if (XtIsManaged(widgets.dbextd.form)) {
		if (cur_db.dtitle == NULL)
			xs = XmStringCreateSimple("Untitled");
		else
			xs = XmStringCreateSimple(cur_db.dtitle);

		XtVaSetValues(widgets.dbextd.disc_lbl,
			XmNlabelString, xs,
			NULL
		);

		XmStringFree(xs);
	}

	/* Update main window title display */
	dpy_dtitle(s);
}


/*
 * dbprog_trklist_play
 *	Track list entry selection default action callback.
 */
void
dbprog_trklist_play(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmListCallbackStruct	*p = (XmListCallbackStruct *)(void *) call_data;
	curstat_t		*s = (curstat_t *)(void *) client_data;

	if (p->reason != XmCR_DEFAULT_ACTION)
		return;

	/* Stop current playback */
	if (s->mode != MOD_STOP)
		di_stop(s, FALSE);

	sel_pos = p->item_position;

	/* Clear previous program */
	XmTextSetString(widgets.dbprog.pgmseq_txt, "");
	XtSetSensitive(widgets.dbprog.addpgm_btn, False);
	XtSetSensitive(widgets.dbprog.clrpgm_btn, False);
	dbprog_progclear(s);

	/* This is a single-track program as a result of double clicking
	 * on a track (or pressing return).
	 */
	s->onetrk_prog = TRUE;

	/* Add selected track to program */
	dbprog_addpgm(w, client_data, call_data);

	/* Play selected track */
	cd_play_pause(w, client_data, call_data);

	XmListDeselectPos(w, sel_pos);
	sel_pos = -1;
	XmTextSetString(widgets.dbprog.ttitle_txt, "");

	XtSetSensitive(widgets.dbprog.addpgm_btn, False);
	XtSetSensitive(widgets.dbprog.extt_btn, False);
}


/*
 * dbprog_trklist_select
 *	Track list entry selection callback.
 */
void
dbprog_trklist_select(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmListCallbackStruct	*p = (XmListCallbackStruct *)(void *) call_data;
	curstat_t		*s = (curstat_t *)(void *) client_data;
	int			pos;
	char			*cp;
	XmString		xs;

	if (p->reason != XmCR_BROWSE_SELECT)
		return;

	if (!di_check_disc(s) || s->mode == MOD_BUSY || s->mode == MOD_NODISC)
		return;

	pos = p->item_position - 1;

	if (title_edited) {
		title_edited = FALSE;

		if ((cp = XmTextGetString(widgets.dbprog.ttitle_txt)) == NULL)
			return;

		/* Update track list entry */
		dbprog_listupd_ent(s, pos, cp, FALSE);

		XmListDeselectPos(widgets.dbprog.trk_list, sel_pos);
		sel_pos = -1;
		XmTextSetString(widgets.dbprog.ttitle_txt, "");

		if (cur_db.trklist[pos] != NULL)
			MEM_FREE(cur_db.trklist[pos]);

		cur_db.trklist[pos] = cp;

		dbprog_changed = TRUE;
		XtSetSensitive(widgets.dbprog.savedb_btn, True);
		XtSetSensitive(widgets.dbprog.addpgm_btn, False);
		XtSetSensitive(widgets.dbprog.extt_btn, False);

		/* Update the extt window if necessary */
		if (extt_pos == pos &&
		    XtIsManaged(widgets.dbextt.form)) {
			if (cur_db.trklist[pos] == NULL)
				xs = XmStringCreateSimple("Untitled");
			else
				xs = XmStringCreateSimple(cur_db.trklist[pos]);

			XtVaSetValues(widgets.dbextt.trk_lbl,
				XmNlabelString, xs,
				NULL
			);

			XmStringFree(xs);
		}

		/* Update the main window if necessary */
		if (di_curtrk_pos(s) == pos)
			dpy_ttitle(s);

		/* Return the input focus to the track title editor */
		XmProcessTraversal(
			widgets.dbprog.ttitle_txt,
			XmTRAVERSE_CURRENT
		);
	}
	else if (sel_pos == p->item_position) {
		/* This item is already selected: deselect it */

		XmListDeselectPos(w, p->item_position);
		sel_pos = ind_pos = -1;
		XmTextSetString(widgets.dbprog.ttitle_txt, "");

		XtSetSensitive(widgets.dbprog.addpgm_btn, False);
		XtSetSensitive(widgets.dbprog.extt_btn, False);
	}
	else {
		sel_pos = p->item_position;

		if (sel_pos > 0)
			XmListSelectPos(w, sel_pos, False);

		if (cur_db.trklist[p->item_position-1] == NULL) {
			XmTextSetString(widgets.dbprog.ttitle_txt, UNDEF_STR);
			XmTextSetInsertionPosition(
				widgets.dbprog.ttitle_txt,
				strlen(UNDEF_STR)
			);
		}
		else {
			XmTextSetString(widgets.dbprog.ttitle_txt,
					cur_db.trklist[pos]);
			XmTextSetInsertionPosition(
				widgets.dbprog.ttitle_txt,
				strlen(cur_db.trklist[pos])
			);
		}

		XtSetSensitive(widgets.dbprog.addpgm_btn, True);
		XtSetSensitive(widgets.dbprog.extt_btn, True);

		/* Warp the extt window to the new selected track, if
		 * it is popped up.
		 */
		if (XtIsManaged(widgets.dbextt.form))
			dbprog_extt(w, (XtPointer) FALSE, call_data);
	}
}


/*
 * dbprog_ttitle_focuschg
 *	Track title editor text widget keyboard focus change callback.
 */
/*ARGSUSED*/
void
dbprog_ttitle_focuschg(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
	curstat_t		*s = (curstat_t *)(void *) client_data;
	int			i;

	if (p->reason == XmCR_FOCUS) {
		if (sel_pos < 0) {
			for (i = 0; i < (int) s->tot_trks; i++) {
				if (cur_db.trklist[i] == NULL) {
					ind_pos = i + 1;

					XmListSelectPos(
						widgets.dbprog.trk_list,
						ind_pos,
						False
					);
					XmListSetBottomPos(
						widgets.dbprog.trk_list,
						ind_pos
					);
					break;
				}
			}
		}
	}
	else if (p->reason == XmCR_LOSING_FOCUS) {
		if (ind_pos > 0) {
			XmListDeselectPos(widgets.dbprog.trk_list, ind_pos);
			ind_pos = -1;
		}
	}
}


/*
 * dbprog_ttitle_new
 *	Track title editor text widget callback function.
 */
void
dbprog_ttitle_new(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
	curstat_t		*s = (curstat_t *)(void *) client_data;
	char			*cp;
	int			*pos,
				i;
	XmString		xs;
	XmListCallbackStruct	cb;

	if (p->reason == XmCR_VALUE_CHANGED) {
		if ((cp = XmTextGetString(w)) == NULL)
			return;

		if (cp[0] == '\0')
			title_edited = FALSE;
		else if (sel_pos < 0)
			title_edited = TRUE;

		XtFree(cp);
		return;
	}
	else if (p->reason != XmCR_ACTIVATE)
		return;

	if (sel_pos >= 0 &&
	    XmListGetSelectedPos(widgets.dbprog.trk_list, &pos, &i)) {
		if ((cp = XmTextGetString(w)) == NULL)
			return;

		if (pos == NULL) {
			XtFree(cp);
			return;
		}

		if (i == 1) {
			/* Update track list entry */
			dbprog_listupd_ent(s, (*pos)-1, cp, FALSE);

			XmListDeselectPos(widgets.dbprog.trk_list, sel_pos);
			sel_pos = -1;

			if (cur_db.trklist[(*pos)-1] != NULL)
				MEM_FREE(cur_db.trklist[(*pos)-1]);

			cur_db.trklist[(*pos)-1] = cp;

			dbprog_changed = TRUE;
			XtSetSensitive(widgets.dbprog.savedb_btn, True);
			XtSetSensitive(widgets.dbprog.addpgm_btn, False);
			XtSetSensitive(widgets.dbprog.extt_btn, False);

			/* Update the extt window if necessary */
			if (extt_pos == (*pos)-1 &&
			    XtIsManaged(widgets.dbextt.form)) {
				if (cur_db.trklist[(*pos)-1] == NULL)
					xs = XmStringCreateSimple("Untitled");
				else
					xs = XmStringCreateSimple(
						cur_db.trklist[(*pos)-1]
					);

				XtVaSetValues(widgets.dbextt.trk_lbl,
					XmNlabelString, xs,
					NULL
				);

				XmStringFree(xs);
			}

			/* Update the main window if necessary */
			if (di_curtrk_pos(s) == (*pos)-1)
				dpy_ttitle(s);

		}

		XmTextSetString(w, "");

		XtFree((XtPointer) pos);
	}
	else {
		/* Pressing Return in this case is equivalent to clicking
		 * on the first title-less track on the track list.
		 */
		for (i = 0; i < (int) s->tot_trks; i++) {
			if (cur_db.trklist[i] == NULL) {
				cb.item_position = i + 1;
				cb.reason = XmCR_BROWSE_SELECT;
				cb.event = p->event;

				dbprog_trklist_select(
					widgets.dbprog.trk_list,
					(XtPointer) s,
					(XtPointer) &cb
				);
				break;
			}
		}
	}

	for (i = 0; i < (int) s->tot_trks; i++) {
		if (cur_db.trklist[i] == NULL) {
			ind_pos = i + 1;

			XmListSelectPos(
				widgets.dbprog.trk_list,
				ind_pos,
				False
			);
			XmListSetBottomPos(
				widgets.dbprog.trk_list,
				ind_pos
			);
			break;
		}
	}
}


/*
 * dbprog_pgmseq_verify
 *	Play sequence editor text widget user-input verification callback.
 */
/*ARGSUSED*/
void
dbprog_pgmseq_verify(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmTextVerifyCallbackStruct
		*p = (XmTextVerifyCallbackStruct *)(void *) call_data;
	int	i;

	if (p->reason != XmCR_MODIFYING_TEXT_VALUE)
		return;

	p->doit = True;

	/* If not changing via direct edit in the text widget,
	 * then there is nothing to do here.
	 */
	if (!pgmseq_editing)
		return;

	if (p->startPos != p->endPos)
		return;

	switch (p->text->format) {
	case FMT8BIT:
		if (p->text->length != 1) {
			p->doit = False;
			return;
		}

		if (p->text->ptr[0] == PGM_SEPCHAR) {
			if (cur_db.playorder == NULL ||
			    (i = strlen(cur_db.playorder)) == 0 ||
			    cur_db.playorder[i-1] == PGM_SEPCHAR ||
			    p->currInsert != p->newInsert)
				p->doit = False;
		}
		else if (!isdigit(p->text->ptr[0]))
			p->doit = False;
		break;

	case FMT16BIT:
		/* Don't know how to handle 16-bit character sets yet */
		p->doit = False;
		return;
	}
}


/*
 * dbprog_pgmseq_txtchg
 *	Play sequence editor text widget text changed callback.
 */
/*ARGSUSED*/
void
dbprog_pgmseq_txtchg(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
	curstat_t		*s = (curstat_t *)(void *) client_data;

	if (p->reason != XmCR_VALUE_CHANGED)
		return;

	/* If not changing via direct edit in the text widget,
	 * then there is nothing to do here.
	 */
	if (!pgmseq_editing)
		return;

	if (s->onetrk_prog && s->mode != MOD_STOP) {
		/* Currently playing a single-track program: clear it first */
		s->onetrk_prog = FALSE;
		dbprog_progclear(s);
	}

	/* Disable shuffle mode */
	if (s->shuffle) {
		di_shuffle(s, FALSE);
		set_shuffle_btn(FALSE);
	}

	if (cur_db.playorder != NULL)
		MEM_FREE(cur_db.playorder);
	cur_db.playorder = XmTextGetString(w);

	/* Parse play order string and set the play order */
	if (!dbprog_pgm_parse(s))
		cd_warning_popup(app_data.str_warning, app_data.str_seqfmterr);

	/* Update display */
	dpy_progmode(s);

	dbprog_changed = TRUE;
	XtSetSensitive(widgets.dbprog.savedb_btn, True);
	XtSetSensitive(widgets.dbprog.clrpgm_btn, True);
}


/*
 * dbprog_pgmseq_focuschg
 *	Play sequence editor text widget keyboard focus change callback.
 */
/*ARGSUSED*/
void
dbprog_pgmseq_focuschg(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;

	if (p->reason == XmCR_FOCUS)
		pgmseq_editing = TRUE;
	else if (p->reason == XmCR_LOSING_FOCUS)
		pgmseq_editing = FALSE;
}


/*
 * dbprog_addpgm
 *	Program Add button callback.
 */
/*ARGSUSED*/
void
dbprog_addpgm(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	char		*cp,
			tmpbuf[6];

	if (sel_pos < 0 || !di_check_disc(s) || s->mode == MOD_BUSY ||
	    s->mode == MOD_NODISC) {
		cd_beep();
		return;
	}

	if (s->onetrk_prog && s->mode != MOD_STOP) {
		/* Currently playing a single-track program: clear it first */
		s->onetrk_prog = FALSE;
		dbprog_progclear(s);
	}

	/* Disable shuffle mode */
	if (s->shuffle) {
		di_shuffle(s, FALSE);
		set_shuffle_btn(FALSE);
	}

	if (cur_db.playorder != NULL && cur_db.playorder[0] == '\0') {
		MEM_FREE(cur_db.playorder);
		cur_db.playorder = NULL;
	}

	if (cur_db.playorder == NULL) {
		sprintf(tmpbuf, "%u", s->trkinfo[sel_pos-1].trkno);
		cp = (char *) MEM_ALLOC(strlen(tmpbuf) + 1);
	}
	else {
		sprintf(tmpbuf, "%c%u",
			PGM_SEPCHAR, s->trkinfo[sel_pos-1].trkno);
		cp = (char *) MEM_ALLOC(
			strlen(cur_db.playorder) + strlen(tmpbuf) + 1
		);
	}

	if (cp == NULL) {
		cd_fatal_popup(app_data.str_fatal, app_data.str_nomemory);
		return;
	}

	sprintf(cp, "%s%s",
		(cur_db.playorder == NULL) ? "" : cur_db.playorder,
		tmpbuf);

	MEM_FREE(cur_db.playorder);
	cur_db.playorder = cp;

	if (!s->onetrk_prog) {
		XmTextSetString(widgets.dbprog.pgmseq_txt, cur_db.playorder);
		XmTextSetInsertionPosition(
			widgets.dbprog.pgmseq_txt,
			strlen(cur_db.playorder)
		);
	}

	/* Parse play order string and set the play order */
	if (!dbprog_pgm_parse(s))
		cd_warning_popup(app_data.str_warning, app_data.str_seqfmterr);

	/* Update display */
	dpy_progmode(s);

	if (!s->onetrk_prog) {
		dbprog_changed = TRUE;
		XtSetSensitive(widgets.dbprog.savedb_btn, True);
		XtSetSensitive(widgets.dbprog.clrpgm_btn, True);
	}

	XtSetSensitive(widgets.dbprog.addpgm_btn, False);
	XtSetSensitive(widgets.dbprog.extt_btn, False);

	XmListDeselectPos(widgets.dbprog.trk_list, sel_pos);
	sel_pos = -1;

	XmTextSetString(widgets.dbprog.ttitle_txt, "");
}


/*
 * dbprog_clrpgm
 *	Program Clear button callback.
 */
/*ARGSUSED*/
void
dbprog_clrpgm(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmListDeselectPos(widgets.dbprog.trk_list, sel_pos);
	sel_pos = -1;
	XmTextSetString(widgets.dbprog.ttitle_txt, "");

	XmTextSetString(widgets.dbprog.pgmseq_txt, "");

	dbprog_changed = TRUE;
	XtSetSensitive(widgets.dbprog.savedb_btn, True);
	XtSetSensitive(widgets.dbprog.addpgm_btn, False);
	XtSetSensitive(widgets.dbprog.clrpgm_btn, False);
	XtSetSensitive(widgets.dbprog.extt_btn, False);

	dbprog_progclear((curstat_t *)(void *) client_data);
}


/*
 * dbprog_send
 *	CDDB Send pushbutton callback.
 */
/*ARGSUSED*/
void
dbprog_send(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	char		msg[STR_BUF_SZ * 2];

	if (s->mode == MOD_BUSY || s->mode == MOD_NODISC ||
	    cur_db.discid == 0 || cur_db.dbfile == NULL) {
		/* Nothing to send */
		cd_beep();
		return;
	}

	if (app_data.cddb_mailsite == NULL ||
	    app_data.cddb_mailsite[0] == '\0' ||
	    app_data.cddb_mailcmd == NULL ||
	    app_data.cddb_mailcmd[0] == '\0') {
		/* No mail site or mail command specified */
		cd_beep();
		return;
	}

	sprintf(msg, app_data.str_send, app_data.cddb_mailsite);

	cd_confirm_popup(
		app_data.str_confirm,
		msg,
		(XtCallbackProc) dbprog_dbsend,
		client_data,
		(XtCallbackProc) NULL,
		NULL
	);
}


/*
 * dbprog_save
 *	In-core CD database SAVE button callback.
 */
/*ARGSUSED*/
void
dbprog_save(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	cddb_path_t	*pp;
	bool_t		found_local;

	if (cur_db.discid == 0) {
		cd_beep();

		/* Clear flag */
		s->flags &= ~STAT_SAVE;

		if (s->flags & STAT_CLEAR)
			dbprog_dbclear(s, FALSE);

		return;
	}

	/* Look for local database directories */
	found_local = FALSE;
	for (pp = cddb_pathlist(); pp != NULL; pp = pp->next) {
		if (pp->type == CDDB_LOCAL) {
			found_local = TRUE;
			break;
		}
	}

	if (!found_local) {
		/* No database directory */
		cd_info_popup(app_data.str_info, app_data.str_nodb);

		/* Clear flag */
		s->flags &= ~STAT_SAVE;

		if (s->flags & STAT_CLEAR)
			dbprog_dbclear(s, FALSE);

		return;
	}

	dirsel_mode = DIRSEL_SAVE;

	if (cur_db.dbfile == NULL) {
		/* Pop up the database directory selection popup */
		XmListDeselectAllItems(widgets.dirsel.dir_list);
		XmTextSetString(widgets.dbprog.ttitle_txt, "");

		XtManageChild(widgets.dirsel.form);

		XmProcessTraversal(
			widgets.dirsel.ok_btn,
			XmTRAVERSE_CURRENT
		);
	}
	else {
		/* Save to file directly */
		dbprog_dbput(s);

		/* Clear flag */
		s->flags &= ~STAT_SAVE;

		if (s->flags & STAT_CLEAR)
			dbprog_dbclear(s, FALSE);
	}
}


/*
 * dbprog_load
 *	CD database file LOAD button callback.
 */
/*ARGSUSED*/
void
dbprog_load(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;

	if (!di_check_disc(s) || s->mode == MOD_BUSY || s->mode == MOD_NODISC) {
		cd_beep();
		return;
	}

	/* Clear the in-core entry */
	dbprog_dbclear(s, TRUE);

	/* Re-load from database file */
	dbprog_dbget(s);
}


/*
 * dbprog_link
 *	CD Database file search-link button callback.
 */
/*ARGSUSED*/
void
dbprog_link(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	cddb_path_t	*pp;
	bool_t		found_local;

	if (cur_db.discid == 0) {
		cd_beep();
		return;
	}

	dirsel_mode = DIRSEL_LINK;

	if (cur_db.dbfile != NULL) {
		MEM_FREE(cur_db.dbfile);
		cur_db.dbfile = NULL;
	}

	/* Look for local database directories */
	found_local = FALSE;
	for (pp = cddb_pathlist(); pp != NULL; pp = pp->next) {
		if (pp->type == CDDB_LOCAL) {
			found_local = TRUE;
			break;
		}
	}

	if (!found_local) {
		/* No database directory */
		cd_info_popup(app_data.str_info, app_data.str_nodb);
		return;
	}

	/* Pop up the database directory selection popup */
	XmListDeselectAllItems(widgets.dirsel.dir_list);
	XmTextSetString(widgets.dbprog.ttitle_txt, "");

	XmProcessTraversal(
		widgets.dirsel.ok_btn,
		XmTRAVERSE_CURRENT
	);

	XtManageChild(widgets.dirsel.form);
}


/*
 * dbprog_cancel
 *	Pop down CD database/program window.
 */
/*ARGSUSED*/
void
dbprog_cancel(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmPushButtonCallbackStruct
			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
	curstat_t	*s = (curstat_t *)(void *) client_data;

	if (XtIsManaged(widgets.dbextd.form)) {
		/* Force a popdown of the extd window */
		dbprog_extd_cancel(
			widgets.dbextd.cancel_btn,
			(XtPointer) s,
			(XtPointer) p
		);

		extd_manage = TRUE;
	}
	if (XtIsManaged(widgets.dbextt.form)) {
		/* Force a popdown of the extt window */
		dbprog_extt_cancel(
			widgets.dbextt.cancel_btn,
			(XtPointer) s,
			(XtPointer) p
		);

		extt_manage = TRUE;
	}

	/* Pop down the database/program dialog */
	XtUnmanageChild(widgets.dbprog.form);
}


/*
 * dbprog_do_clear
 *	Changed-save dialog box pushbuttons callback.
 */
/*ARGSUSED*/
void
dbprog_do_clear(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = curstat_addr();
	word32_t	flags = (word32_t) client_data;

	dbprog_changed = FALSE;
	s->flags |= (flags & 0xffff);

	/* Clear in-core database */
	dbprog_dbclear(s, FALSE);
}


/*
 * dbprog_timedpy
 *	Toggle the time display mode in the track list.
 */
/*ARGSUSED*/
void
dbprog_timedpy(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmRowColumnCallbackStruct	*p =
		(XmRowColumnCallbackStruct *)(void *) call_data;
	XmToggleButtonCallbackStruct	*q;
	curstat_t			*s = (curstat_t *)(void *) client_data;

	if (p == NULL)
		return;

	q = (XmToggleButtonCallbackStruct *)(void *) p->callbackstruct;

	if (!q->set)
		return;

	/* Overload the function of these buttons to also
	 * dump the contents of the cddb_incore_t structure
	 * in debug mode
	 */
	if (app_data.debug)
		cddb_dump_incore(&cur_db);

	if (p->widget == widgets.dbprog.tottime_btn) {
		if (timemode == TIME_TOTAL)
			return;	/* No change */

		timemode = TIME_TOTAL;
	}
	else if (p->widget == widgets.dbprog.trktime_btn) {
		if (timemode == TIME_TRACK)
			return;	/* No change */

		timemode = TIME_TRACK;
	}
	else
		return;	/* Invalid widget */

	if (di_check_disc(s) && s->mode != MOD_BUSY && s->mode != MOD_NODISC) {
		/* Change to watch cursor */
		cd_busycurs(TRUE);

		/* Update track list with new time display mode */
		dbprog_listupd_all(s, FALSE);

		/* Change to normal cursor */
		cd_busycurs(FALSE);
	}
}


/*
 * dbprog_extd
 *	Pop up/down the disc extended info window.
 */
void
dbprog_extd(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmString	xs;

	if (XtIsManaged(widgets.dbextd.form)) {
		/* Pop down the Disc Extended Info window */
		dbprog_extd_ok(w, client_data, call_data);
		return;
	}

	if (cur_db.dtitle == NULL)
		xs = XmStringCreateSimple("Untitled");
	else
		xs = XmStringCreateSimple(cur_db.dtitle);

	XtVaSetValues(widgets.dbextd.disc_lbl,
		XmNlabelString, xs,
		NULL
	);

	XmStringFree(xs);

	/* Pop up the Disc Extended Info window */
	XtManageChild(widgets.dbextd.form);

	XmProcessTraversal(
		widgets.dbextd.cancel_btn,
		XmTRAVERSE_CURRENT
	);

	extd_manage = FALSE;
}


/*
 * dbprog_extt
 *	Pop up/down the track extended info window.
 */
void
dbprog_extt(Widget w, XtPointer client_data, XtPointer call_data)
{
	int		i,
			n,
			*pos;
	XmString	xs;
	bool_t		from_main = (bool_t)(int) client_data;

	if (XtIsManaged(widgets.dbextt.form)) {
		if (from_main) {
			/* Pop down the Track Extended Info window */
			dbprog_extt_ok(w, client_data, call_data);

			return;
		}
		else {
			/* Update structures */
			dbprog_exttupd();
		}
	}

	if (sel_pos >= 0 &&
	    XmListGetSelectedPos(widgets.dbprog.trk_list, &pos, &n)) {

		/* Enter extt setup mode */
		extt_setup = TRUE;

		if (n != 1) {
			/* This shouldn't happen error */
			cd_beep();
			return;
		}

		if (cur_db.trklist[(*pos)-1] == NULL)
			xs = XmStringCreateSimple("Untitled");
		else
			xs = XmStringCreateSimple(cur_db.trklist[(*pos)-1]);

		XtVaSetValues(widgets.dbextt.trk_lbl,
			XmNlabelString, xs,
			NULL
		);

		XmStringFree(xs);

		/* Track extended info text */
		if (cur_db.extt[(*pos)-1] != NULL)
			XmTextSetString(widgets.dbextt.trk_txt,
				        cur_db.extt[(*pos)-1]);
		else
			XmTextSetString(widgets.dbextt.trk_txt, "");

		extt_pos = (*pos)-1;

		if (from_main) {
			/* Save a backup copy of the text in case the user
			 * wants to abort.  This code will ensure that the
			 * data is not saved twice while the extended track
			 * info window is popped up.
			 */
			for (i = 0; i < MAXTRACK; i++) {
				if (cur_db.sav_extt[i] == NULL &&
				    cur_db.extt[i] != NULL) {
					cur_db.sav_extt[i] = (char *)
						MEM_ALLOC(
						    strlen(cur_db.extt[i]) + 1
						);
					if (cur_db.sav_extt[i] == NULL) {
						cd_fatal_popup(
							app_data.str_fatal,
							app_data.str_nomemory
						);
						return;
					}
					strcpy(
						cur_db.sav_extt[i],
						cur_db.extt[i]
					);
				}
			}
		}

		/* Pop up the Track Extended Info popup */
		XtManageChild(widgets.dbextt.form);

		XmProcessTraversal(
			widgets.dbextt.cancel_btn,
			XmTRAVERSE_CURRENT
		);

		extt_manage = FALSE;

		/* Exit extt setup mode */
		extt_setup = FALSE;
	}
}


/*
 * dbprog_set_changed
 *	Set the flag indicating that the user has made changes to the
 *	in-core CD database entry.
 */
/*ARGSUSED*/
void
dbprog_set_changed(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;

	if (p->reason != XmCR_VALUE_CHANGED)
		return;

	/* Setup of the extt window is not a user change */
	if (!extt_setup) {
		dbprog_changed = TRUE;
		XtSetSensitive(widgets.dbprog.savedb_btn, True);
	}
}


/*
 * dbprog_extd_ok
 *	Extended disc info window OK button callback.
 */
/*ARGSUSED*/
void
dbprog_extd_ok(Widget w, XtPointer client_data, XtPointer call_data)
{
	/* Pop down the Disc Extended Info popup */
	XtUnmanageChild(widgets.dbextd.form);

	/* Update structures */
	dbprog_extdupd();
}


/*
 * dbprog_extd_clear
 *	Extended disc info window Clear button callback.
 */
/*ARGSUSED*/
void
dbprog_extd_clear(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmTextSetString(widgets.dbextd.disc_txt, "");
}


/*
 * dbprog_extd_cancel
 *	Extended disc info window Cancel button callback.
 */
/*ARGSUSED*/
void
dbprog_extd_cancel(Widget w, XtPointer client_data, XtPointer call_data)
{
	bool_t	sav_chg;

	/* Pop down the Disc Extended Info popup */
	XtUnmanageChild(widgets.dbextd.form);

	sav_chg = dbprog_changed;

	/* Restore original text */
	if (cur_db.extd == NULL)
		XmTextSetString(widgets.dbextd.disc_txt, "");
	else
		XmTextSetString(widgets.dbextd.disc_txt, cur_db.extd);

	dbprog_changed = sav_chg;

	if (!dbprog_changed)
		XtSetSensitive(widgets.dbprog.savedb_btn, False);
}


/*
 * dbprog_extt_ok
 *	Extended track info window OK button callback.
 */
/*ARGSUSED*/
void
dbprog_extt_ok(Widget w, XtPointer client_data, XtPointer call_data)
{
	int	i;

	/* Pop down the Track Extended Info popup */
	XtUnmanageChild(widgets.dbextt.form);

	/* Update structures */
	dbprog_exttupd();

	/* Delete backup text */
	for (i = 0; i < MAXTRACK; i++) {
		if (cur_db.sav_extt[i] != NULL) {
			MEM_FREE(cur_db.sav_extt[i]);
			cur_db.sav_extt[i] = NULL;
		}
	}
}


/*
 * dbprog_extt_clear
 *	Extended track info window Clear button callback.
 */
/*ARGSUSED*/
void
dbprog_extt_clear(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmTextSetString(widgets.dbextt.trk_txt, "");
}


/*
 * dbprog_extt_cancel
 *	Extended track info window Cancel button callback.
 */
/*ARGSUSED*/
void
dbprog_extt_cancel(Widget w, XtPointer client_data, XtPointer call_data)
{
	int	i;

	/* Pop down the Track Extended Info popup */
	XtUnmanageChild(widgets.dbextt.form);

	/* Restore backup text */
	for (i = 0; i < MAXTRACK; i++) {
		if (cur_db.extt[i] != NULL)
			MEM_FREE(cur_db.extt[i]);

		cur_db.extt[i] = cur_db.sav_extt[i];
		cur_db.sav_extt[i] = NULL;
	}
}


/*
 * dbprog_dirsel_select
 *	CD Database directory selection list callback.
 */
/*ARGSUSED*/
void
dbprog_dirsel_select(Widget w, XtPointer client_data, XtPointer call_data)
{
	int			i;
	XmListCallbackStruct	*p = (XmListCallbackStruct *)(void *) call_data;
	cddb_path_t		*pp;

	if (p->reason != XmCR_BROWSE_SELECT)
		return;

	if (cur_db.dbfile != NULL)
		MEM_FREE(cur_db.dbfile);

	/* Look for matching CDDB path entry */
	i = 0;
	for (pp = cddb_pathlist(); pp != NULL; pp = pp->next) {
		if (pp->type != CDDB_LOCAL)
			continue;

		if (++i == p->item_position)
			break;
	}

	if (i != p->item_position) {
		/* Error: this shouldn't happen */
		return;
	}

	cur_db.dbfile = (char *) MEM_ALLOC(strlen(pp->path) + 10);
	if (cur_db.dbfile == NULL) {
		cd_fatal_popup(app_data.str_fatal, app_data.str_nomemory);
		return;
	}

	sprintf(cur_db.dbfile, CDDBFILE_PATH, pp->path, cur_db.discid);
	strcpy(cur_db.category, basename(pp->path));
}


/*
 * dbprog_dirsel_ok
 *	CD Database directory selection window OK button callback.
 */
/*ARGSUSED*/
void
dbprog_dirsel_ok(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;

	if (cur_db.dbfile == NULL) {
		/* User has not selected a directory yet */
		cd_beep();
		return;
	}

	/* Pop down the database directory selector popup dialog */
	XtUnmanageChild(widgets.dirsel.form);

	switch (dirsel_mode) {
	case DIRSEL_SAVE:
		/* Save the database entry to output file */
		dbprog_dbput(s);
		break;

	case DIRSEL_LINK:
		/* Link the database entry to another file */
		dbprog_dblink(s);
		break;

	default:
		/* Shouldn't get here */
		break;
	}

	/* Clear flag */
	s->flags &= ~STAT_SAVE;

	if (s->flags & STAT_CLEAR)
		dbprog_dbclear(s, FALSE);
}


/*
 * dbprog_dirsel_cancel
 *	CD Database directory selection window Cancel button callback.
 */
/*ARGSUSED*/
void
dbprog_dirsel_cancel(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;

	/* Pop down the database directory selector popup dialog */
	XtUnmanageChild(widgets.dirsel.form);

	/* Clear database file path */
	if (cur_db.dbfile != NULL) {
		MEM_FREE(cur_db.dbfile);
		cur_db.dbfile = NULL;
	}

	/* Clear flag */
	s->flags &= ~STAT_SAVE;

	if (s->flags & STAT_CLEAR)
		dbprog_dbclear(s, FALSE);
}


/*
 * dbprog_linksel_select
 *	Search-link selector list user-selection callback.
 */
/*ARGSUSED*/
void
dbprog_linksel_select(Widget w, XtPointer client_data, XtPointer call_data)
{
	XmListCallbackStruct	*p = (XmListCallbackStruct *)(void *) call_data;

	if (p->reason != XmCR_BROWSE_SELECT)
		return;

	linksel_pos = p->item_position;
}


/*
 * dbprog_linksel_ok
 *	Search-link selector window OK button callback.
 */
void
dbprog_linksel_ok(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	char		errstr[ERR_BUF_SZ],
			ltarget[FILE_PATH_SZ];
	int		i;
	cddb_ret_t	ret;
	cddb_linkopts_t	*q;

	if (linksel_pos <= 0) {
		/* User has not selected a link target yet */
		cd_beep();
		return;
	}

	/* Pop down the link selector popup dialog */
	XtUnmanageChild(widgets.linksel.form);

	for (i = 0, q = cddb_linkopts(); q != NULL; i++, q = q->next)
		if (i == linksel_pos - 1)
			break;

	if (q == NULL)
		/* This should not happen */
		return;

	sprintf(ltarget, CONCAT_PATH, dirname(cur_db.dbfile), q->idstr);

	/* Change to watch cursor */
	cd_busycurs(TRUE);

	/* Do the link */
	if ((ret = cddb_link(ltarget, cur_db.dbfile)) != 0) {
		DBGPRN(errfp, "cddb_link: status=%d arg=%d\n",
			CDDB_GET_STAT(ret), CDDB_GET_ARG(ret));
	}

	/* Change to normal cursor */
	cd_busycurs(FALSE);

	/* Free link options list */
	cddb_free_linkopts();

	switch (CDDB_GET_STAT(ret)) {
	case 0:
		break;

	case SETUID_ERR:
		sprintf(errstr, app_data.str_lnkerr_suid,
			get_ouid(), get_ogid());
		cd_warning_popup(app_data.str_warning, errstr);
		return;

	case KILLED_ERR:
		sprintf(errstr, app_data.str_saverr_killed, CDDB_GET_ARG(ret));
		cd_warning_popup(app_data.str_warning, errstr);
		return;

	case LINK_ERR:
	default:
		sprintf(errstr, app_data.str_lnkerr_link);
		cd_warning_popup(app_data.str_warning, errstr);
		return;
	}

	/* Load new database entry */
	dbprog_load(w, client_data, call_data);

	/* All edits have been saved, so clear flag */
	dbprog_changed = FALSE;

	/* Update display */
	dpy_dbmode(s, FALSE);
	dpy_ttitle(s);
	dbprog_dpyid();

	/* Update current disc file */
	cddb_curfileupd(&cur_db);

	XtSetSensitive(widgets.dbprog.send_btn, True);
	XtSetSensitive(widgets.dbprog.linkdb_btn, False);
	XtSetSensitive(widgets.dbprog.savedb_btn, False);
	XmProcessTraversal(
		widgets.dbprog.cancel_btn,
		XmTRAVERSE_CURRENT
	);
}


/*
 * dbprog_linksel_cancel
 *	Search-link selector window Cancel button callback.
 */
/*ARGSUSED*/
void
dbprog_linksel_cancel(Widget w, XtPointer client_data, XtPointer call_data)
{
	/* Pop down the link selector popup dialog */
	XtUnmanageChild(widgets.linksel.form);

	/* Free link options list */
	cddb_free_linkopts();

	/* Clear database file path */
	if (cur_db.dbfile != NULL) {
		MEM_FREE(cur_db.dbfile);
		cur_db.dbfile = NULL;
	}
}


/*
 * dbprog_rmtmatch_yes
 *	Remote CDDB inexact match confirmation window Yes button callback.
 */
/*ARGSUSED*/
void
dbprog_rmtmatch_yes(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	cddb_match_t	*p,
			*q;
	word32_t	discid;

	if (XtIsManaged(widgets.dialog.dbmatch))
		XtUnmanageChild(widgets.dialog.dbmatch);

	if ((p = cur_db.match_cur) == NULL)
		/* Shouldn't get here */
		return;

	/* Save orig disc id and category */
	discid = cur_db.discid;

	/* Set new disc id */
	cur_db.discid = p->discid;
	strcpy(cur_db.category, p->category);

	/* Clear match list */
        for (p = q = cur_db.matchlist; p != NULL; p = q) {
		q = p->next;
		MEM_FREE(p->dtitle);
		MEM_FREE(p);
	}
	cur_db.matchlist = cur_db.match_cur = NULL;

	/* Reload from database */
	dbprog_dbget(s);

	/* Restore disc id */
	cur_db.discid = discid;

	if (cur_db.type == CDDB_INVALID)
		cur_db.category[0] = '\0';

	/* Update display */
	dbprog_dpyid();
	dpy_dbmode(s, FALSE);

	/* Update current disc file */
	cddb_curfileupd(&cur_db);

	/* Clear flag */
	dbmatch_mode = FALSE;
}


/*
 * dbprog_rmtmatch_no
 *	Remote CDDB inexact match confirmation window No button callback.
 */
/*ARGSUSED*/
void
dbprog_rmtmatch_no(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	cddb_match_t	*p;
	char		*msg;
	XmString	xs;

	if (XtIsManaged(widgets.dialog.dbmatch))
		XtUnmanageChild(widgets.dialog.dbmatch);

	if ((p = cur_db.match_cur) == NULL)
		/* Shouldn't get here */
		return;

	if (p->next == NULL) {
		/* This is the last entry */
		dbprog_rmtmatch_stop(w, client_data, call_data);
	}
	else {
		p = p->next;

		msg = (char *) MEM_ALLOC(
			strlen(app_data.str_rmtmatch) +
			strlen(p->dtitle)
		);
		if (msg == NULL) {
			cd_fatal_popup(
				app_data.str_fatal,
				app_data.str_nomemory
			);
			return;
		}

		/* Set up dialog box message string */
		sprintf(msg, app_data.str_rmtmatch, p->category, p->dtitle);

		xs = XmStringCreateLtoR(msg, XmSTRING_DEFAULT_CHARSET);

		XtVaSetValues(widgets.dialog.dbmatch,
			XmNmessageString, xs,
			NULL
		);

		XmStringFree(xs);
		MEM_FREE(msg);

		/* Set pointer */
		cur_db.match_cur = p;

		/* Pop up dialog box */
		XtManageChild(widgets.dialog.dbmatch);
	}
}


/*
 * dbprog_rmtmatch_stop
 *	Remote CDDB inexact match confirmation window Stop button callback.
 */
/*ARGSUSED*/
void
dbprog_rmtmatch_stop(Widget w, XtPointer client_data, XtPointer call_data)
{
	curstat_t	*s = (curstat_t *)(void *) client_data;
	cddb_match_t	*p,
			*q;

	if (XtIsManaged(widgets.dialog.dbmatch))
		XtUnmanageChild(widgets.dialog.dbmatch);

	if ((p = cur_db.match_cur) == NULL)
		/* Shouldn't get here */
		return;

	/* Set flag */
	cur_db.type = CDDB_INVALID;
	s->cddb = CDDB_INVALID;

	/* Clear match list */
        for (p = q = cur_db.matchlist; p != NULL; p = q) {
		q = p->next;
		MEM_FREE(p->dtitle);
		MEM_FREE(p);
	}
	cur_db.matchlist = cur_db.match_cur = NULL;

	/* Update widgets */
	dbprog_structupd(s);

	XtSetSensitive(widgets.dbprog.linkdb_btn, True);
	XtSetSensitive(widgets.dbprog.loaddb_btn, True);

	/* Update display */
	dbprog_dpyid();
	dpy_dtitle(s);
	dpy_ttitle(s);

	/* Update current disc file */
	cddb_curfileupd(&cur_db);

	/* Clear flag */
	dbmatch_mode = FALSE;
}


/**************** ^^ Callback routines ^^ ****************/

