1 /*
2  *   xmcd - Motif(R) CD Audio Player/Ripper
3  *
4  *   Copyright (C) 1993-2004  Ti Kan
5  *   E-mail: xmcd@amb.org
6  *
7  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  *
21  */
22 #ifndef lint
23 static char *_cdfunc_c_ident_ = "@(#)cdfunc.c	7.279 04/03/11";
24 #endif
25 
26 #include "common_d/appenv.h"
27 #include "common_d/version.h"
28 #include "common_d/util.h"
29 #include "xmcd_d/xmcd.h"
30 #include "xmcd_d/widget.h"
31 #include "xmcd_d/callback.h"
32 #include "libdi_d/libdi.h"
33 #include "cdinfo_d/cdinfo.h"
34 #include "xmcd_d/dbprog.h"
35 #include "xmcd_d/wwwwarp.h"
36 #include "xmcd_d/geom.h"
37 #include "xmcd_d/hotkey.h"
38 #include "xmcd_d/help.h"
39 #include "xmcd_d/cdfunc.h"
40 #include "cdda_d/cdda.h"
41 
42 
43 #define CHGDISC_DELAY	1500			/* Disc chg delay */
44 
45 #define WM_FUDGE_X	10			/* Window manager decoration */
46 #define WM_FUDGE_Y	25
47 #define WIN_MINWIDTH	150			/* Dialog box minimum width */
48 #define WIN_MINHEIGHT	100			/* Dialog box minimum height */
49 
50 
51 /* Macro to detemine a valid VMS file name character */
52 #define IS_LEGAL_VMSFILECHAR(c) \
53 	(isalnum((int) (c)) || (c) == '$' || (c) == '-' || (c) == '_')
54 
55 
56 /* Confirmation/Working dialog box callback info structure */
57 typedef struct {
58 	Widget		widget0;		/* Button 1 */
59 	Widget		widget1;		/* Button 2 */
60 	Widget		widget2;		/* Dialog box */
61 	String		type;			/* Callback type */
62 	XtCallbackProc	func;			/* Callback function */
63 	XtPointer	data;			/* Callback arg */
64 } cbinfo_t;
65 
66 /* Structure to save callback function pointer and its args */
67 typedef struct {
68 	XtCallbackProc	func;			/* Callback function */
69 	Widget		w;			/* Widget */
70 	XtPointer	client_data;		/* Client data */
71 	XtPointer	call_data;		/* Call data */
72 } callback_sav_t;
73 
74 
75 extern widgets_t	widgets;
76 extern pixmaps_t	pixmaps;
77 extern appdata_t	app_data;
78 extern FILE		*errfp;
79 
80 wlist_t			*cd_brhead = NULL,	/* Bitrates menu head */
81 			*cd_minbrhead = NULL,	/* Min bitrate menu head */
82 			*cd_maxbrhead = NULL;	/* Max bitrate menu head */
83 
84 STATIC char		keystr[3];		/* Keypad number string */
85 STATIC int		keypad_mode,		/* Keypad mode */
86 			cdda_fader_mode;	/* CDDA fader mode */
87 STATIC long		tm_blinkid = -1,	/* Time dpy blink timer ID */
88 			ab_blinkid = -1,	/* A->B dpy blink timer ID */
89 			dbmode_blinkid = -1,	/* Dbmode dpy blink timer ID */
90 			chgdisc_dlyid = -1,	/* Disc chg delay timer ID */
91 			cdda_fader_id = -1,	/* CDDA fader timer ID */
92 			infodiag_id = -1,	/* Info dialog timer ID */
93 			tooltip1_id = -1,	/* Tooltip popup timer ID */
94 			tooltip2_id = -1;	/* Tooltip popdown timer ID */
95 STATIC sword32_t	warp_offset = 0;	/* Track warp block offset */
96 STATIC word32_t		cdda_fader_intvl = 0;	/* CDDA fader interval */
97 STATIC bool_t		searching = FALSE,	/* Running REW or FF */
98 			confirm_pend = FALSE,	/* Confirm response pending */
99 			confirm_resp = FALSE,	/* Confirm dialog response */
100 			popup_ok = FALSE,	/* Are popups allowed? */
101 			pseudo_warp = FALSE,	/* Warp slider only flag */
102 			warp_busy = FALSE,	/* Warp function active */
103 			chgdelay = FALSE,	/* Disc change delay */
104 			mode_chg = FALSE,	/* Changing main window mode */
105 			pathexp_active = FALSE,	/* Showing exp file path */
106 			tooltip_active = FALSE,	/* Tooltip is active */
107 			skip_next_tooltip = FALSE;
108 						/* Skip the next tooltip */
109 STATIC cdinfo_client_t	cdinfo_cldata;		/* Client info for libcdinfo */
110 STATIC di_client_t	di_cldata;		/* Client info for libdi */
111 STATIC callback_sav_t	override_sav;		/* Mode override callback */
112 STATIC cbinfo_t		cbinfo0,		/* Dialog box cbinfo structs */
113 			cbinfo1,
114 			cbinfo2;
115 STATIC XmString		xs_qual,		/* Label strings */
116 			xs_algo;
117 
118 
119 #define OPT_CATEGS	9			/* Max options categories */
120 
121 /* Options menu category list entries */
122 STATIC struct {
123 	char		*name;
124 	wlist_t		*widgets;
125 } opt_categ[OPT_CATEGS+1];
126 
127 
128 /* Forward declaration prototypes */
129 STATIC void		cd_pause_blink(curstat_t *, bool_t),
130 			cd_ab_blink(curstat_t *, bool_t),
131 			cd_dbmode_blink(curstat_t *, bool_t);
132 
133 
134 /* Macro used in cd_options() */
135 #define PATHENT_APPEND(i, str, ent, type)				\
136 	{								\
137 	    if ((i) < 2 &&						\
138 		(strcmp((ent), "CDDB") == 0 ||				\
139 		 strcmp((ent), "CDTEXT") == 0)) {			\
140 		(void) strcat(						\
141 		    (str), ((type) == CDINFO_RMT) ? "CDDB" : "CDTEXT"	\
142 		);							\
143 		(i)++;							\
144 	    }								\
145 	    else							\
146 		(void) strcat((str), (ent));				\
147 	}
148 
149 
150 
151 /***********************
152  *  internal routines  *
153  ***********************/
154 
155 
156 /*
157  * filefmt_btn
158  *	Given a file format code, return its menu selection button
159  *	widget.
160  *
161  * Args:
162  *	fmt - file format code
163  *
164  * Return:
165  *	The associated menu button widget
166  */
167 STATIC Widget
filefmt_btn(int fmt)168 filefmt_btn(int fmt)
169 {
170 	switch (fmt) {
171 	case FILEFMT_AU:
172 		return (widgets.options.mode_fmt_au_btn);
173 	case FILEFMT_WAV:
174 		return (widgets.options.mode_fmt_wav_btn);
175 	case FILEFMT_AIFF:
176 		return (widgets.options.mode_fmt_aiff_btn);
177 	case FILEFMT_AIFC:
178 		return (widgets.options.mode_fmt_aifc_btn);
179 	case FILEFMT_MP3:
180 		return (widgets.options.mode_fmt_mp3_btn);
181 	case FILEFMT_OGG:
182 		return (widgets.options.mode_fmt_ogg_btn);
183 	case FILEFMT_FLAC:
184 		return (widgets.options.mode_fmt_flac_btn);
185 	case FILEFMT_AAC:
186 		return (widgets.options.mode_fmt_aac_btn);
187 	case FILEFMT_MP4:
188 		return (widgets.options.mode_fmt_mp4_btn);
189 	case FILEFMT_RAW:
190 	default:
191 		return (widgets.options.mode_fmt_raw_btn);
192 	}
193 	/*NOTREACHED*/
194 }
195 
196 
197 /*
198  * filefmt_code
199  *	Given a file format menu selection button widget, return its
200  *	file format code.
201  *
202  * Args:
203  *	The file format menu button widget
204  *
205  * Return:
206  *	The associated file format code
207  */
208 STATIC int
filefmt_code(Widget w)209 filefmt_code(Widget w)
210 {
211 	if (w == widgets.options.mode_fmt_raw_btn)
212 		return FILEFMT_RAW;
213 	else if (w == widgets.options.mode_fmt_au_btn)
214 		return FILEFMT_AU;
215 	else if (w == widgets.options.mode_fmt_wav_btn)
216 		return FILEFMT_WAV;
217 	else if (w == widgets.options.mode_fmt_aiff_btn)
218 		return FILEFMT_AIFF;
219 	else if (w == widgets.options.mode_fmt_aifc_btn)
220 		return FILEFMT_AIFC;
221 	else if (w == widgets.options.mode_fmt_mp3_btn)
222 		return FILEFMT_MP3;
223 	else if (w == widgets.options.mode_fmt_ogg_btn)
224 		return FILEFMT_OGG;
225 	else if (w == widgets.options.mode_fmt_flac_btn)
226 		return FILEFMT_FLAC;
227 	else if (w == widgets.options.mode_fmt_aac_btn)
228 		return FILEFMT_AAC;
229 	else if (w == widgets.options.mode_fmt_mp4_btn)
230 		return FILEFMT_MP4;
231 
232 	return FILEFMT_RAW;
233 }
234 
235 
236 /*
237  * disc_etime_norm
238  *	Return the elapsed time of the disc in seconds.  This is
239  *	used during normal playback.
240  *
241  * Args:
242  *	s - Pointer to the curstat_t structure.
243  *
244  * Return:
245  *	The disc elapsed time in seconds.
246  */
247 STATIC sword32_t
disc_etime_norm(curstat_t * s)248 disc_etime_norm(curstat_t *s)
249 {
250 	sword32_t	secs;
251 
252 	secs = (s->curpos_tot.min * 60 + s->curpos_tot.sec) -
253 	       (MSF_OFFSET / FRAME_PER_SEC);
254 	return ((secs >= 0) ? secs : 0);
255 }
256 
257 
258 /*
259  * disc_etime_prog
260  *	Return the elapsed time of the disc in seconds.  This is
261  *	used during shuffle or program mode.
262  *
263  * Args:
264  *	s - Pointer to the curstat_t structure.
265  *
266  * Return:
267  *	The disc elapsed time in seconds.
268  */
269 STATIC sword32_t
disc_etime_prog(curstat_t * s)270 disc_etime_prog(curstat_t *s)
271 {
272 	sword32_t	i,
273 			secs = 0;
274 
275 	/* Find the time of all played tracks */
276 	for (i = 0; i < ((int) s->prog_cnt) - 1; i++) {
277 		secs += ((s->trkinfo[s->trkinfo[i].playorder + 1].min * 60 +
278 			 s->trkinfo[s->trkinfo[i].playorder + 1].sec) -
279 		         (s->trkinfo[s->trkinfo[i].playorder].min * 60 +
280 			 s->trkinfo[s->trkinfo[i].playorder].sec));
281 	}
282 
283 	/* Find the elapsed time of the current track */
284 	for (i = 0; i < MAXTRACK; i++) {
285 		if (s->trkinfo[i].trkno == LEAD_OUT_TRACK)
286 			break;
287 
288 		if (s->trkinfo[i].trkno == s->cur_trk) {
289 			secs += (s->curpos_trk.min * 60 + s->curpos_trk.sec);
290 			break;
291 		}
292 	}
293 
294 	return ((secs >= 0) ? secs : 0);
295 }
296 
297 
298 /*
299  * track_rtime
300  *	Return the remaining time of the current playing track in seconds.
301  *
302  * Args:
303  *	s - Pointer to the curstat_t structure.
304  *
305  * Return:
306  *	The track remaining time in seconds.
307  */
308 STATIC sword32_t
track_rtime(curstat_t * s)309 track_rtime(curstat_t *s)
310 {
311 	sword32_t	i,
312 			secs,
313 			tot_sec,
314 			cur_sec;
315 
316 	if ((i = di_curtrk_pos(s)) < 0)
317 		return 0;
318 
319 	tot_sec = (s->trkinfo[i+1].min * 60 + s->trkinfo[i+1].sec) -
320 		  (s->trkinfo[i].min * 60 + s->trkinfo[i].sec);
321 
322 	/* "Enhanced CD" / "CD Extra" hack */
323 	if (s->trkinfo[i+1].addr > s->discpos_tot.addr) {
324 		tot_sec -= ((s->trkinfo[i+1].addr - s->discpos_tot.addr) /
325 			    FRAME_PER_SEC);
326 	}
327 
328 	cur_sec = s->curpos_trk.min * 60 + s->curpos_trk.sec;
329 	secs = tot_sec - cur_sec;
330 
331 	return ((secs >= 0) ? secs : 0);
332 }
333 
334 
335 /*
336  * disc_rtime_norm
337  *	Return the remaining time of the disc in seconds.  This is
338  *	used during normal playback.
339  *
340  * Args:
341  *	s - Pointer to the curstat_t structure.
342  *
343  * Return:
344  *	The disc remaining time in seconds.
345  */
346 STATIC sword32_t
disc_rtime_norm(curstat_t * s)347 disc_rtime_norm(curstat_t *s)
348 {
349 	sword32_t	secs;
350 
351 	secs = (s->discpos_tot.min * 60 + s->discpos_tot.sec) -
352 		(s->curpos_tot.min * 60 + s->curpos_tot.sec);
353 
354 	return ((secs >= 0) ? secs : 0);
355 }
356 
357 
358 /*
359  * disc_rtime_prog
360  *	Return the remaining time of the disc in seconds.  This is
361  *	used during shuffle or program mode.
362  *
363  * Args:
364  *	s - Pointer to the curstat_t structure.
365  *
366  * Return:
367  *	The disc remaining time in seconds.
368  */
369 STATIC sword32_t
disc_rtime_prog(curstat_t * s)370 disc_rtime_prog(curstat_t *s)
371 {
372 	sword32_t	i,
373 			secs = 0;
374 
375 	/* Find the time of all unplayed tracks */
376 	for (i = s->prog_cnt; i < (int) s->prog_tot; i++) {
377 		secs += ((s->trkinfo[s->trkinfo[i].playorder + 1].min * 60 +
378 			 s->trkinfo[s->trkinfo[i].playorder + 1].sec) -
379 		         (s->trkinfo[s->trkinfo[i].playorder].min * 60 +
380 			 s->trkinfo[s->trkinfo[i].playorder].sec));
381 	}
382 
383 	/* Find the remaining time of the current track */
384 	for (i = 0; i < MAXTRACK; i++) {
385 		if (s->trkinfo[i].trkno == LEAD_OUT_TRACK)
386 			break;
387 
388 		if (s->trkinfo[i].trkno == s->cur_trk) {
389 			secs += ((s->trkinfo[i+1].min * 60 +
390 				  s->trkinfo[i+1].sec) -
391 				 (s->curpos_tot.min * 60 + s->curpos_tot.sec));
392 
393 			/* "Enhanced CD" / "CD Extra" hack */
394 			if (s->trkinfo[i+1].addr > s->discpos_tot.addr) {
395 				secs -= ((s->trkinfo[i+1].addr -
396 					  s->discpos_tot.addr) /
397 					 FRAME_PER_SEC);
398 			}
399 
400 			break;
401 		}
402 	}
403 
404 	return ((secs >= 0) ? secs : 0);
405 }
406 
407 
408 /*
409  * dpy_time_blink
410  *	Make the time indicator region of the main window blink.
411  *	This is used when the disc is paused.
412  *
413  * Args:
414  *	s - Pointer to the curstat_t structure.
415  *
416  * Return:
417  *	Nothing
418  */
419 STATIC void
dpy_time_blink(curstat_t * s)420 dpy_time_blink(curstat_t *s)
421 {
422 	static bool_t	bstate = TRUE;
423 
424 	if (bstate) {
425 		tm_blinkid = cd_timeout(
426 			app_data.blinkoff_interval,
427 			dpy_time_blink,
428 			(byte_t *) s
429 		);
430 		dpy_time(s, TRUE);
431 	}
432 	else {
433 		tm_blinkid = cd_timeout(
434 			app_data.blinkon_interval,
435 			dpy_time_blink,
436 			(byte_t *) s
437 		);
438 		dpy_time(s, FALSE);
439 	}
440 	bstate = !bstate;
441 }
442 
443 
444 /*
445  * dpy_ab_blink
446  *	Make the a->b indicator of the main window blink.
447  *
448  * Args:
449  *	s - Pointer to the curstat_t structure.
450  *
451  * Return:
452  *	Nothing
453  */
454 STATIC void
dpy_ab_blink(curstat_t * s)455 dpy_ab_blink(curstat_t *s)
456 {
457 	static bool_t	bstate = TRUE;
458 
459 	if (bstate) {
460 		ab_blinkid = cd_timeout(
461 			app_data.blinkoff_interval,
462 			dpy_ab_blink,
463 			(byte_t *) s
464 		);
465 		dpy_progmode(s, TRUE);
466 	}
467 	else {
468 		ab_blinkid = cd_timeout(
469 			app_data.blinkon_interval,
470 			dpy_ab_blink,
471 			(byte_t *) s
472 		);
473 		dpy_progmode(s, FALSE);
474 	}
475 	bstate = !bstate;
476 }
477 
478 
479 /*
480  * dpy_dbmode_blink
481  *	Make the dbmode indicator of the main window blink.
482  *
483  * Args:
484  *	s - Pointer to the curstat_t structure.
485  *
486  * Return:
487  *	Nothing
488  */
489 STATIC void
dpy_dbmode_blink(curstat_t * s)490 dpy_dbmode_blink(curstat_t *s)
491 {
492 	static bool_t	bstate = TRUE;
493 
494 	if (bstate) {
495 		dbmode_blinkid = cd_timeout(
496 			app_data.blinkoff_interval,
497 			dpy_dbmode_blink,
498 			(byte_t *) s
499 		);
500 		dpy_dbmode(s, TRUE);
501 	}
502 	else {
503 		dbmode_blinkid = cd_timeout(
504 			app_data.blinkon_interval,
505 			dpy_dbmode_blink,
506 			(byte_t *) s
507 		);
508 		dpy_dbmode(s, FALSE);
509 	}
510 	bstate = !bstate;
511 }
512 
513 
514 /*
515  * dpy_keypad_ind
516  *	Update the digital indicator on the keypad window.
517  *
518  * Args:
519  *	s - Pointer to the curstat_t structure.
520  *
521  * Return:
522  *	Nothing.
523  */
524 STATIC void
dpy_keypad_ind(curstat_t * s)525 dpy_keypad_ind(curstat_t *s)
526 {
527 	char		str[24],
528 			trk[16],
529 			time[16];
530 	byte_t		min,
531 			sec,
532 			frame;
533 	XmString	xs;
534 	static char	prevstr[24];
535 
536 	if (!XtIsManaged(widgets.keypad.form))
537 		return;
538 
539 	switch (keypad_mode) {
540 	case KPMODE_DISC:
541 		if (s->mode == MOD_BUSY)
542 			(void) strcpy(str, "disc    -");
543 		else if (keystr[0] == '\0')
544 			(void) sprintf(str, "disc  %3d", s->cur_disc);
545 		else
546 			(void) sprintf(str, "( disc  %3d )", atoi(keystr));
547 		break;
548 
549 	case KPMODE_TRACK:
550 		if (keystr[0] == '\0') {
551 			if (warp_busy) {
552 				util_blktomsf(
553 					warp_offset,
554 					&min,
555 					&sec,
556 					&frame,
557 					0
558 				);
559 
560 				(void) sprintf(trk, "%02u", s->cur_trk);
561 
562 				(void) sprintf(time, "+%02u:%02u", min, sec);
563 			}
564 			else if (di_curtrk_pos(s) < 0) {
565 				(void) strcpy(trk, "--");
566 				(void) strcpy(time, " --:--");
567 			}
568 			else if (curtrk_type(s) == TYP_DATA) {
569 				(void) sprintf(trk, "%02u", s->cur_trk);
570 				(void) strcpy(time, " --:--");
571 			}
572 			else if (s->cur_idx == 0 && app_data.subq_lba) {
573 				/* LBA format doesn't have meaningful lead-in
574 				 * time, so just display blank.
575 				 */
576 				(void) sprintf(trk, "%02u", s->cur_trk);
577 				(void) strcpy(time, "   :  ");
578 			}
579 			else {
580 				util_blktomsf(
581 					s->curpos_trk.addr,
582 					&min,
583 					&sec,
584 					&frame,
585 					0
586 				);
587 				(void) sprintf(trk, "%02u", s->cur_trk);
588 				(void) sprintf(time, "%c%02u:%02u",
589 				       (s->cur_idx == 0) ? '-' : '+', min, sec);
590 			}
591 
592 			if (warp_busy)
593 				(void) sprintf(str, "( %s  %s )", trk, time);
594 			else
595 				(void) sprintf(str, "%s  %s", trk, time);
596 		}
597 		else {
598 			util_blktomsf(warp_offset, &min, &sec, &frame, 0);
599 			(void) sprintf(trk, "%02u", atoi(keystr));
600 			(void) sprintf(time, "+%02u:%02u", min, sec);
601 
602 			(void) sprintf(str, "( %s  %s )", trk, time);
603 		}
604 		break;
605 
606 	default:
607 		return;
608 	}
609 
610 	if (strcmp(str, prevstr) == 0) {
611 		/* No change */
612 		return;
613 	}
614 
615 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
616 
617 	XtVaSetValues(
618 		widgets.keypad.keypad_ind,
619 		XmNlabelString,
620 		xs,
621 		NULL
622 	);
623 
624 	XmStringFree(xs);
625 	(void) strcpy(prevstr, str);
626 }
627 
628 
629 /*
630  * dpy_warp
631  *	Update the warp slider position.
632  *
633  * Args:
634  *	s - Pointer to the curstat_t structure.
635  *
636  * Return:
637  *	Nothing.
638  */
639 STATIC void
dpy_warp(curstat_t * s)640 dpy_warp(curstat_t *s)
641 {
642 	int	i;
643 
644 	if (!XtIsManaged(widgets.keypad.form) || warp_busy)
645 		return;
646 
647 	if ((i = di_curtrk_pos(s)) < 0 || s->cur_idx == 0 ||
648 	    curtrk_type(s) == TYP_DATA)
649 		set_warp_slider(0, TRUE);
650 	else
651 		set_warp_slider(unscale_warp(s, i, s->curpos_trk.addr), TRUE);
652 }
653 
654 
655 /*
656  * dpy_cddaperf
657  *	Update CDDA performance monitor display
658  *
659  * Args:
660  *	s - Pointer to the curstat_t structure.
661  *
662  * Return:
663  *	Nothing.
664  */
665 STATIC void
dpy_cddaperf(curstat_t * s)666 dpy_cddaperf(curstat_t *s)
667 {
668 	XmString	xs;
669 	char		speedstr[16],
670 			framesstr[16],
671 			fpsstr[16],
672 			ttcstr[16];
673 	static char	prev_speed[16] = { '-', '\0' },
674 			prev_frames[16] = { '-', '\0' },
675 			prev_fps[16] = { '-', '\0' },
676 			prev_ttc[16] = { '0', 'h', 'r', ' ',
677 					 '-', '-', ':', '-', '-', '\0' };
678 
679 	if (PLAYMODE_IS_STD(app_data.play_mode)) {
680 		(void) strcpy(speedstr, "-");
681 		(void) strcpy(framesstr, "-");
682 		(void) strcpy(fpsstr, "-");
683 		(void) strcpy(ttcstr, "0hr --:--");
684 	}
685 	else if (s->mode == MOD_PLAY || s->mode == MOD_PAUSE ||
686 		 s->mode == MOD_SAMPLE) {
687 		int	fps,
688 			ttc,
689 			hour,
690 			min,
691 			sec;
692 
693 		if (s->tot_frm > 0) {
694 			fps = s->frm_per_sec;
695 
696 			/* Special hack for 1.0x situation to keep the
697 			 * display steady and accurate.  This is to work
698 			 * around sound card sampling rate tolerances.
699 			 */
700 			if (fps >= (FRAME_PER_SEC-1) &&
701 			    fps <= (FRAME_PER_SEC+1)) {
702 				fps = FRAME_PER_SEC;
703 			}
704 
705 			if (fps > 0)
706 				ttc = (int) (s->tot_frm - s->frm_played) / fps;
707 			else
708 				ttc = 0;
709 
710 			hour = ttc / 3600;
711 			ttc %= 3600;
712 			min = ttc / 60;
713 			sec = ttc % 60;
714 
715 			(void) sprintf(speedstr, "%0.1f",
716 				    (float) fps / (float) FRAME_PER_SEC);
717 			(void) sprintf(framesstr, "%d   (%d%%)",
718 				    (int) s->frm_played,
719 				    (int) (s->frm_played * 100 / s->tot_frm));
720 			(void) sprintf(fpsstr, "%d", fps);
721 			(void) sprintf(ttcstr, "%dhr %02d:%02d",
722 				    hour, min, sec);
723 		}
724 		else {
725 			(void) strcpy(speedstr, "0.0");
726 			(void) strcpy(framesstr, "0   (0%)");
727 			(void) strcpy(fpsstr, "-");
728 			(void) strcpy(ttcstr, "0hr --:--");
729 		}
730 	}
731 	else {
732 		(void) strcpy(speedstr, "-");
733 		(void) strcpy(framesstr, "-");
734 		(void) strcpy(fpsstr, "-");
735 		(void) strcpy(ttcstr, "0hr --:--");
736 	}
737 
738 	if (!XtIsManaged(widgets.perfmon.form))
739 		return;
740 
741 	if (strcmp(prev_speed, speedstr) != 0) {
742 		xs = create_xmstring(
743 			speedstr, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
744 		);
745 		XtVaSetValues(widgets.perfmon.speed_ind,
746 			XmNlabelString, xs,
747 			NULL
748 		);
749 		XmStringFree(xs);
750 		(void) strcpy(prev_speed, speedstr);
751 	}
752 
753 	if (strcmp(prev_frames, framesstr) != 0) {
754 		xs = create_xmstring(
755 			framesstr, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
756 		);
757 		XtVaSetValues(widgets.perfmon.frames_ind,
758 			XmNlabelString, xs,
759 			NULL
760 		);
761 		XmStringFree(xs);
762 		(void) strcpy(prev_frames, framesstr);
763 	}
764 
765 	if (strcmp(prev_fps, fpsstr) != 0) {
766 		xs = create_xmstring(
767 			fpsstr, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
768 		);
769 		XtVaSetValues(widgets.perfmon.fps_ind,
770 			XmNlabelString, xs,
771 			NULL
772 		);
773 		XmStringFree(xs);
774 		(void) strcpy(prev_fps, fpsstr);
775 	}
776 
777 	if (strcmp(prev_ttc, ttcstr) != 0) {
778 		xs = create_xmstring(
779 			ttcstr, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
780 		);
781 		XtVaSetValues(widgets.perfmon.ttc_ind,
782 			XmNlabelString, xs,
783 			NULL
784 		);
785 		XmStringFree(xs);
786 		(void) strcpy(prev_ttc, ttcstr);
787 	}
788 }
789 
790 
791 /*
792  * set_btn_color
793  *	Set the label color of a pushbutton widget
794  *
795  * Args:
796  *	w - The pushbutton widget.
797  *	px - The label pixmap, if applicable.
798  *	color - The pixel value of the desired color.
799  *
800  * Return:
801  *	Nothing.
802  */
803 STATIC void
set_btn_color(Widget w,Pixmap px,Pixel color)804 set_btn_color(Widget w, Pixmap px, Pixel color)
805 {
806 	unsigned char	labtype;
807 
808 	XtVaGetValues(w, XmNlabelType, &labtype, NULL);
809 
810 	if (labtype == XmPIXMAP)
811 		XtVaSetValues(w, XmNlabelPixmap, px, NULL);
812 	else
813 		XtVaSetValues(w, XmNforeground, color, NULL);
814 }
815 
816 
817 /*
818  * set_scale_color
819  *	Set the indicator color of a scale widget
820  *
821  * Args:
822  *	w - The scale widget.
823  *	pixmap - not used.
824  *	color - The pixel value of the desired color.
825  *
826  * Return:
827  *	Nothing.
828  */
829 /*ARGSUSED*/
830 STATIC void
set_scale_color(Widget w,Pixmap px,Pixel color)831 set_scale_color(Widget w, Pixmap px, Pixel color)
832 {
833 	XtVaSetValues(w, XmNforeground, color, NULL);
834 }
835 
836 
837 /*
838  * cd_pause_blink
839  *	Disable or enable the time indicator blinking.
840  *
841  * Args:
842  *	s - Pointer to the curstat_t structure.
843  *	enable - TRUE: start blink, FALSE: stop blink
844  *
845  * Return:
846  *	Nothing.
847  */
848 STATIC void
cd_pause_blink(curstat_t * s,bool_t enable)849 cd_pause_blink(curstat_t *s, bool_t enable)
850 {
851 	static bool_t	blinking = FALSE;
852 
853 	if (enable) {
854 		if (!blinking) {
855 			/* Start time display blink */
856 			blinking = TRUE;
857 			dpy_time_blink(s);
858 		}
859 	}
860 	else if (blinking) {
861 		/* Stop time display blink */
862 		cd_untimeout(tm_blinkid);
863 
864 		tm_blinkid = -1;
865 		blinking = FALSE;
866 	}
867 }
868 
869 
870 /*
871  * cd_ab_blink
872  *	Disable or enable the a->b indicator blinking.
873  *
874  * Args:
875  *	s - Pointer to the curstat_t structure.
876  *	enable - TRUE: start blink, FALSE: stop blink
877  *
878  * Return:
879  *	Nothing.
880  */
881 STATIC void
cd_ab_blink(curstat_t * s,bool_t enable)882 cd_ab_blink(curstat_t *s, bool_t enable)
883 {
884 	static bool_t	blinking = FALSE;
885 
886 	if (enable) {
887 		if (!blinking) {
888 			/* Start A->B display blink */
889 			blinking = TRUE;
890 			dpy_ab_blink(s);
891 		}
892 	}
893 	else if (blinking) {
894 		/* Stop A->B display blink */
895 		cd_untimeout(ab_blinkid);
896 
897 		ab_blinkid = -1;
898 		blinking = FALSE;
899 	}
900 }
901 
902 
903 /*
904  * cd_dbmode_blink
905  *	Disable or enable the dbmode indicator blinking.
906  *
907  * Args:
908  *	s - Pointer to the curstat_t structure.
909  *	enable - TRUE: start blink, FALSE: stop blink
910  *
911  * Return:
912  *	Nothing.
913  */
914 STATIC void
cd_dbmode_blink(curstat_t * s,bool_t enable)915 cd_dbmode_blink(curstat_t *s, bool_t enable)
916 {
917 	static bool_t	blinking = FALSE;
918 
919 	if (enable) {
920 		if (!blinking) {
921 			/* Start dbmode display blink */
922 			blinking = TRUE;
923 			dpy_dbmode_blink(s);
924 		}
925 	}
926 	else if (blinking) {
927 		/* Stop A->B display blink */
928 		cd_untimeout(dbmode_blinkid);
929 
930 		dbmode_blinkid = -1;
931 		blinking = FALSE;
932 	}
933 }
934 
935 
936 /*
937  * do_chgdisc
938  *	Timer function to change discs on a multi-CD changer.
939  *
940  * Args:
941  *	s - Pointer to the curstat_t structure.
942  *
943  * Return:
944  *	Nothing.
945  */
946 STATIC void
do_chgdisc(curstat_t * s)947 do_chgdisc(curstat_t *s)
948 {
949 	/* Reset timer ID */
950 	chgdisc_dlyid = -1;
951 	chgdelay = FALSE;
952 
953 	/* Change to watch cursor */
954 	cd_busycurs(TRUE, CURS_ALL);
955 
956 	/* Do the disc change */
957 	di_chgdisc(s);
958 
959 	/* Update display */
960 	dpy_dbmode(s, FALSE);
961 	dpy_playmode(s, FALSE);
962 	dpy_progmode(s, FALSE);
963 
964 	/* Change to normal cursor */
965 	cd_busycurs(FALSE, CURS_ALL);
966 }
967 
968 
969 /*
970  * cdda_fadeout
971  *	Timer function to perform CDDA fade-out
972  *
973  * Args:
974  *	s - Pointer to the curstat_t structure.
975  *
976  * Return:
977  *	Nothing.
978  */
979 STATIC void
cdda_fadeout(curstat_t * s)980 cdda_fadeout(curstat_t *s)
981 {
982 	if (s->cdda_att == 0) {
983 		cdda_fader_mode = CDDA_FADER_NONE;
984 		return;
985 	}
986 
987 	if (s->cdda_att > 50)
988 		s->cdda_att -= 10;
989 	else if (s->cdda_att > 30)
990 		s->cdda_att -= 5;
991 	else if (s->cdda_att > 10)
992 		s->cdda_att -= 2;
993 	else if (s->cdda_att > 4)
994 		s->cdda_att -= 1;
995 	else if (s->cdda_att >= 1)
996 		s->cdda_att -= 1;
997 
998 	set_att_slider(s->cdda_att);
999 	cdda_att(s);
1000 
1001 	if (s->cdda_att > 0) {
1002 		cdda_fader_id = cd_timeout(
1003 			cdda_fader_intvl,
1004 			cdda_fadeout,
1005 			(byte_t *) s
1006 		);
1007 	}
1008 	else {
1009 		cdda_fader_id = -1;
1010 		cdda_fader_mode = CDDA_FADER_NONE;
1011 	}
1012 }
1013 
1014 
1015 /*
1016  * cdda_fadein
1017  *	Timer function to perform CDDA fade-in
1018  *
1019  * Args:
1020  *	s - Pointer to the curstat_t structure.
1021  *
1022  * Return:
1023  *	Nothing.
1024  */
1025 STATIC void
cdda_fadein(curstat_t * s)1026 cdda_fadein(curstat_t *s)
1027 {
1028 	if (s->cdda_att == 100) {
1029 		cdda_fader_mode = CDDA_FADER_NONE;
1030 		return;
1031 	}
1032 
1033 	if (s->cdda_att < 10)
1034 		s->cdda_att += 1;
1035 	else if (s->cdda_att < 30)
1036 		s->cdda_att += 2;
1037 	else if (s->cdda_att < 50)
1038 		s->cdda_att += 5;
1039 	else if (s->cdda_att < 90)
1040 		s->cdda_att += 10;
1041 	else
1042 		s->cdda_att = 100;
1043 
1044 	set_att_slider(s->cdda_att);
1045 	cdda_att(s);
1046 
1047 	if (s->cdda_att < 100) {
1048 		cdda_fader_id = cd_timeout(
1049 			cdda_fader_intvl,
1050 			cdda_fadein,
1051 			(byte_t *) s
1052 		);
1053 	}
1054 	else {
1055 		cdda_fader_id = -1;
1056 		cdda_fader_mode = CDDA_FADER_NONE;
1057 	}
1058 }
1059 
1060 
1061 /*
1062  * cd_tooltip_popdown
1063  *	Pop-down the tool-tip.
1064  *
1065  * Args:
1066  *	w - The tooltip shell.
1067  *
1068  * Return:
1069  *	Nothing.
1070  */
1071 STATIC void
cd_tooltip_popdown(Widget w)1072 cd_tooltip_popdown(Widget w)
1073 {
1074 	/* Cancel pending timers */
1075 	if (tooltip1_id >= 0) {
1076 		cd_untimeout(tooltip1_id);
1077 		tooltip1_id = -1;
1078 	}
1079 	if (tooltip2_id >= 0) {
1080 		cd_untimeout(tooltip2_id);
1081 		tooltip2_id = -1;
1082 	}
1083 
1084 	/* Pop down the tooltip */
1085 	if (tooltip_active) {
1086 		tooltip_active = FALSE;
1087 		XtPopdown(w);
1088 	}
1089 }
1090 
1091 
1092 /*
1093  * cd_tooltip_sphandler
1094  *	Special widget-specific handler for tool-tips
1095  *
1096  * Args:
1097  *	w - The associated control widget
1098  *	ret_tlbl - the return XmString label that will be displayed on the
1099  *		   tool-tip.  The caller should XmStringFree() this when
1100  *		   done.
1101  *
1102  * Return:
1103  *	0: Don't pop-up tool-tip
1104  *	1: Pop-up tooltip with the returned ret_tlbl
1105  *	2: This is not a special widget: use the default handler.
1106  */
1107 STATIC int
cd_tooltip_sphandler(Widget w,XmString * ret_tlbl)1108 cd_tooltip_sphandler(Widget w, XmString *ret_tlbl)
1109 {
1110 	char		*ttitle,
1111 			*artist,
1112 			*title,
1113 			dtitle[TITLEIND_LEN];
1114 	XmString	xs1,
1115 			xs2,
1116 			xs3;
1117 	curstat_t	*s = curstat_addr();
1118 
1119 	if (w == widgets.main.disc_ind) {
1120 		/* Disc display */
1121 		xs1 = create_xmstring(
1122 			"Disc ", NULL, XmSTRING_DEFAULT_CHARSET, FALSE
1123 		);
1124 		XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1125 		*ret_tlbl = XmStringConcat(xs1, xs2);
1126 		XmStringFree(xs1);
1127 		XmStringFree(xs2);
1128 	}
1129 	else if (w == widgets.main.track_ind) {
1130 		/* Track display */
1131 		xs1 = create_xmstring(
1132 			"Track ", NULL, XmSTRING_DEFAULT_CHARSET, FALSE
1133 		);
1134 		XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1135 		*ret_tlbl = XmStringConcat(xs1, xs2);
1136 		XmStringFree(xs1);
1137 		XmStringFree(xs2);
1138 	}
1139 	else if (w == widgets.main.index_ind) {
1140 		/* Index display */
1141 		xs1 = create_xmstring(
1142 			"Index ", NULL, XmSTRING_DEFAULT_CHARSET, FALSE
1143 		);
1144 		XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1145 		*ret_tlbl = XmStringConcat(xs1, xs2);
1146 		XmStringFree(xs1);
1147 		XmStringFree(xs2);
1148 	}
1149 	else if (w == widgets.main.time_ind) {
1150 		/* Time display */
1151 		xs1 = create_xmstring(
1152 			"Time ", NULL, XmSTRING_DEFAULT_CHARSET, FALSE
1153 		);
1154 		XtVaGetValues(widgets.main.timemode_ind,
1155 			XmNlabelString, &xs2,
1156 			NULL
1157 		);
1158 		xs3 = XmStringConcat(xs1, xs2);
1159 		XmStringFree(xs1);
1160 		XmStringFree(xs2);
1161 
1162 		switch (geom_main_getmode()) {
1163 		case MAIN_NORMAL:
1164 			/* Normal mode */
1165 			*ret_tlbl = xs3;
1166 			break;
1167 
1168 		case MAIN_BASIC:
1169 			/* Basic mode */
1170 
1171 			/* Add disc title */
1172 			artist = dbprog_curartist(s);
1173 			title = dbprog_curtitle(s);
1174 			if (artist == NULL && title == NULL) {
1175 				/* No disc artist and title */
1176 				*ret_tlbl = xs3;
1177 				break;
1178 			}
1179 
1180 			dtitle[0] = '\0';
1181 			if (artist != NULL && artist[0] != '\0') {
1182 				(void) sprintf(dtitle, "%.127s", artist);
1183 				if (title != NULL && title[0] != '\0')
1184 					(void) sprintf(dtitle, "%s / %.127s",
1185 							dtitle, title);
1186 			}
1187 			else if (title != NULL && title[0] != '\0') {
1188 				(void) sprintf(dtitle, "%.127s", title);
1189 			}
1190 			dtitle[TITLEIND_LEN - 1] = '\0';
1191 
1192 			xs1 = XmStringSeparatorCreate();
1193 			xs2 = XmStringConcat(xs3, xs1);
1194 			XmStringFree(xs1);
1195 			XmStringFree(xs3);
1196 
1197 			xs1 = create_xmstring(
1198 				dtitle,
1199 				NULL,
1200 				XmSTRING_DEFAULT_CHARSET,
1201 				TRUE
1202 			);
1203 			xs3 = XmStringConcat(xs2, xs1);
1204 			XmStringFree(xs1);
1205 			XmStringFree(xs2);
1206 
1207 			/* Add track title */
1208 			ttitle = dbprog_curttitle(s);
1209 			if (ttitle[0] == '\0') {
1210 				/* No track title */
1211 				*ret_tlbl = xs3;
1212 			}
1213 			else {
1214 				xs1 = XmStringSeparatorCreate();
1215 				xs2 = XmStringConcat(xs3, xs1);
1216 				XmStringFree(xs1);
1217 				XmStringFree(xs3);
1218 
1219 				xs1 = create_xmstring(
1220 					ttitle,
1221 					NULL,
1222 					XmSTRING_DEFAULT_CHARSET,
1223 					TRUE
1224 				);
1225 				xs3 = XmStringConcat(xs2, xs1);
1226 				XmStringFree(xs1);
1227 				XmStringFree(xs2);
1228 
1229 				*ret_tlbl = xs3;
1230 			}
1231 			break;
1232 		}
1233 	}
1234 	else if (w == widgets.main.rptcnt_ind) {
1235 		xs1 = create_xmstring(
1236 			"Repeat count: ", NULL, XmSTRING_DEFAULT_CHARSET, FALSE
1237 		);
1238 		XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1239 		*ret_tlbl = XmStringConcat(xs1, xs2);
1240 		XmStringFree(xs1);
1241 		XmStringFree(xs2);
1242 	}
1243 	else if (w == widgets.main.dbmode_ind) {
1244 		if (s->qmode == QMODE_MATCH) {
1245 			xs1 = create_xmstring(
1246 				"CD info source: ", NULL,
1247 				XmSTRING_DEFAULT_CHARSET, FALSE
1248 			);
1249 			XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1250 			*ret_tlbl = XmStringConcat(xs1, xs2);
1251 			XmStringFree(xs1);
1252 			XmStringFree(xs2);
1253 		}
1254 		else if (s->qmode == QMODE_NONE) {
1255 			*ret_tlbl = create_xmstring(
1256 				"CD info: none", NULL,
1257 				XmSTRING_DEFAULT_CHARSET, FALSE
1258 			);
1259 		}
1260 		else {
1261 			xs1 = create_xmstring(
1262 				"CD info status: ", NULL,
1263 				XmSTRING_DEFAULT_CHARSET, FALSE
1264 			);
1265 			XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1266 			*ret_tlbl = XmStringConcat(xs1, xs2);
1267 			XmStringFree(xs1);
1268 			XmStringFree(xs2);
1269 		}
1270 	}
1271 	else if (w == widgets.main.progmode_ind) {
1272 		char	*str;
1273 
1274 		if (s->program && !s->onetrk_prog && !s->shuffle)
1275 			str = "Mode: program on";
1276 		else if (s->segplay == SEGP_A)
1277 			str = "Mode: a->?";
1278 		else if (s->segplay == SEGP_AB)
1279 			str = "Mode: a->b";
1280 		else
1281 			str = "Mode: program off";
1282 
1283 		*ret_tlbl = create_xmstring(
1284 			str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
1285 		);
1286 	}
1287 	else if (w == widgets.main.timemode_ind) {
1288 		xs1 = create_xmstring(
1289 			"Time display mode: ", NULL,
1290 			XmSTRING_DEFAULT_CHARSET, FALSE
1291 		);
1292 		XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1293 		*ret_tlbl = XmStringConcat(xs1, xs2);
1294 		XmStringFree(xs1);
1295 		XmStringFree(xs2);
1296 	}
1297 	else if (w == widgets.main.playmode_ind) {
1298 		char	str[STR_BUF_SZ];
1299 
1300 		(void) strcpy(str, "Playback mode ");
1301 
1302 		if (PLAYMODE_IS_STD(app_data.play_mode)) {
1303 			(void) strcat(str, "(Standard): ");
1304 		}
1305 		else {
1306 			(void) strcat(str, "(CDDA ");
1307 			if ((app_data.play_mode & PLAYMODE_CDDA) != 0)
1308 			    (void) strcat(str, "play");
1309 			if ((app_data.play_mode & PLAYMODE_FILE) != 0) {
1310 			    if ((app_data.play_mode & PLAYMODE_CDDA) != 0)
1311 				(void) strcat(str, "/save");
1312 			    else
1313 				(void) strcat(str, "save");
1314 			}
1315 			if ((app_data.play_mode & PLAYMODE_PIPE) != 0) {
1316 			    if ((app_data.play_mode & PLAYMODE_CDDA) != 0 ||
1317 				(app_data.play_mode & PLAYMODE_FILE) != 0)
1318 				(void) strcat(str, "/pipe");
1319 			    else
1320 				(void) strcat(str, "pipe");
1321 			}
1322 			(void) strcat(str, "): ");
1323 		}
1324 
1325 		xs1 = create_xmstring(
1326 			str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
1327 		);
1328 		XtVaGetValues(w, XmNlabelString, &xs2, NULL);
1329 		*ret_tlbl = XmStringConcat(xs1, xs2);
1330 		XmStringFree(xs1);
1331 		XmStringFree(xs2);
1332 	}
1333 	else if (w == widgets.main.dtitle_ind) {
1334 		artist = dbprog_curartist(s);
1335 		title = dbprog_curtitle(s);
1336 		if (artist == NULL && title == NULL) {
1337 			/* No artist / title: don't popup tooltip */
1338 			return 0;
1339 		}
1340 		/* Use label string */
1341 		XtVaGetValues(w, XmNlabelString, ret_tlbl, NULL);
1342 	}
1343 	else if (w == widgets.main.ttitle_ind) {
1344 		ttitle = dbprog_curttitle(s);
1345 		if (ttitle[0] == '\0') {
1346 			/* No track title: don't popup tooltip */
1347 			return 0;
1348 		}
1349 		/* Use label string */
1350 		XtVaGetValues(w, XmNlabelString, ret_tlbl, NULL);
1351 	}
1352 	else
1353 		return 2;
1354 
1355 	return 1;
1356 }
1357 
1358 
1359 /*
1360  * cd_tooltip_popup
1361  *	Timer function to pop-up the tool-tip.
1362  *
1363  * Args:
1364  *	w - The associated control widget
1365  *
1366  * Return:
1367  *	Nothing.
1368  */
1369 STATIC void
cd_tooltip_popup(Widget w)1370 cd_tooltip_popup(Widget w)
1371 {
1372 	Display			*display;
1373 	int			screen;
1374 	Position		end_x,
1375 				end_y,
1376 				abs_x,
1377 				abs_y,
1378 				x,
1379 				y;
1380 	Dimension		width,
1381 				height,
1382 				off_x,
1383 				off_y,
1384 				swidth,
1385 				sheight;
1386 	WidgetClass		wc;
1387 	XtWidgetGeometry	geom;
1388 	XmString		tlbl;
1389 
1390 	tooltip1_id = -1;
1391 	tooltip_active = TRUE;
1392 	display = XtDisplay(widgets.toplevel);
1393 	screen = DefaultScreen(display);
1394 	swidth = (Dimension) XDisplayWidth(display, screen);
1395 	sheight = (Dimension) XDisplayHeight(display, screen);
1396 
1397 	XtVaGetValues(w,
1398 		XmNwidth, &width,
1399 		XmNheight, &height,
1400 		NULL
1401 	);
1402 
1403 	/* Perform widget-specific handling */
1404 	switch (cd_tooltip_sphandler(w, &tlbl)) {
1405 	case 0:
1406 		/* tooltip popup refused by cd_tooltip_sphandler */
1407 		tooltip_active = FALSE;
1408 		return;
1409 	case 1:
1410 		/* cd_tooltip_sphandler handled it */
1411 		break;
1412 	default:
1413 		wc = XtClass(w);
1414 
1415 		if (wc == xmPushButtonWidgetClass ||
1416 		    wc == xmCascadeButtonWidgetClass ||
1417 		    wc == xmToggleButtonWidgetClass) {
1418 			/* Use label string */
1419 			XtVaGetValues(w, XmNlabelString, &tlbl, NULL);
1420 		}
1421 		else if (wc == xmScaleWidgetClass) {
1422 			/* Use title string */
1423 			XtVaGetValues(w, XmNtitleString, &tlbl, NULL);
1424 		}
1425 		else {
1426 			/* Use widget name */
1427 			tlbl = create_xmstring(
1428 				XtName(w), NULL,
1429 				XmSTRING_DEFAULT_CHARSET, FALSE
1430 			);
1431 		}
1432 		break;
1433 	}
1434 
1435 	/* Set the tooltip label string and hotkey mnemonic, if any. */
1436 	XtVaSetValues(widgets.tooltip.tooltip_lbl, XmNlabelString, tlbl, NULL);
1437 	hotkey_tooltip_mnemonic(w);
1438 
1439 	/* Translate to screen absolute coordinates, and add desired offsets */
1440 	XtTranslateCoords(w, 0, 0, &abs_x, &abs_y);
1441 
1442 	/* Make sure that the tooltip window doesn't go beyond
1443 	 * screen boundaries.
1444 	 */
1445 	(void) XtQueryGeometry(widgets.tooltip.tooltip_lbl, NULL, &geom);
1446 
1447 	off_x = width / 2;
1448 	end_x = abs_x + geom.width + off_x + 2;
1449 	if (end_x > (Position) swidth)
1450 		off_x = -(end_x - swidth - off_x);
1451 	x = abs_x + off_x;
1452 	if (x < 0)
1453 		x = 2;
1454 
1455 	off_y = height + 5;
1456 	end_y = abs_y + geom.height + off_y + 2;
1457 	if (end_y > (Position) sheight)
1458 		off_y = -(geom.height + 5);
1459 	y = abs_y + off_y;
1460 
1461 	/* Move tooltip widget to desired location and pop it up */
1462 	XtVaSetValues(widgets.tooltip.shell, XmNx, x, XmNy, y, NULL);
1463 	XtPopup(widgets.tooltip.shell, XtGrabNone);
1464 
1465 	/* Set timer for auto-popdown */
1466 	if (app_data.tooltip_time > 0) {
1467 		int	n;
1468 
1469 		/* If the tooltip label is longer than 40 characters,
1470 		 * add 50mS to the tooltip time for each additional
1471 		 * character.
1472 		 */
1473 		n = XmStringLength(tlbl);
1474 		if (n <= 40)
1475 			n = 0;
1476 		else
1477 			n = ((n - 40) + 1) * 50;
1478 
1479 		tooltip2_id = cd_timeout(
1480 			app_data.tooltip_time + n,
1481 			cd_tooltip_popdown,
1482 			(byte_t *) widgets.tooltip.shell
1483 		);
1484 	}
1485 
1486 	XmStringFree(tlbl);
1487 }
1488 
1489 
1490 /*
1491  * cd_keypad_ask_dsbl
1492  *	Prompt the user whether to disable the shuffle or program modes
1493  *	if the user tries to use the keypad to change the track/disc.
1494  *
1495  * Args:
1496  *	s - Pointer to the curstat_t structure.
1497  *	func - The callback function to call if the user answers yes, and
1498  *             after the shuffle or program mode is disabled.
1499  *	w - The widget that normally activates the specified callback function
1500  *	call_data - The callback structure pointer
1501  *	call_data_len - The callback structure size
1502  *
1503  * Return:
1504  *	Nothing.
1505  */
1506 STATIC void
cd_keypad_ask_dsbl(curstat_t * s,XtCallbackProc func,Widget w,XtPointer call_data,int call_data_len)1507 cd_keypad_ask_dsbl(
1508 	curstat_t	*s,
1509 	XtCallbackProc	func,
1510 	Widget		w,
1511 	XtPointer	call_data,
1512 	int		call_data_len
1513 )
1514 {
1515 	char	*str;
1516 
1517 	override_sav.func = func;
1518 	override_sav.w = w;
1519 	override_sav.client_data = (XtPointer) s;
1520 	override_sav.call_data = (XtPointer) MEM_ALLOC(
1521 		"override_sav.call_data",
1522 		call_data_len
1523 	);
1524 	if (override_sav.call_data == NULL) {
1525 		CD_FATAL(app_data.str_nomemory);
1526 		return;
1527 	}
1528 	memcpy(override_sav.call_data, call_data, call_data_len);
1529 
1530 	str = (char *) MEM_ALLOC(
1531 		"ask_dsbl_str",
1532 		strlen(app_data.str_kpmodedsbl) +
1533 		strlen(app_data.str_askproceed) + 20
1534 	);
1535 	if (str == NULL) {
1536 		CD_FATAL(app_data.str_nomemory);
1537 		return;
1538 	}
1539 	(void) sprintf(str, app_data.str_kpmodedsbl,
1540 		       s->shuffle ? "shuffle" : "program");
1541 	(void) sprintf(str, "%s\n%s", str, app_data.str_askproceed);
1542 
1543 	(void) cd_confirm_popup(
1544 		app_data.str_confirm,
1545 		str,
1546 		(XtCallbackProc) cd_keypad_dsbl_modes_yes,
1547 		(XtPointer) s,
1548 		(XtCallbackProc) cd_keypad_dsbl_modes_no,
1549 		(XtPointer) s
1550 	);
1551 
1552 	MEM_FREE(str);
1553 }
1554 
1555 
1556 /*
1557  * cd_show_path
1558  *	Pop up a dialog box and display the title and file path.  On
1559  *	UNIX, if the path is a relative path, it will be converted into
1560  *	an absolute path for display.
1561  *
1562  * Args:
1563  *	title - Title string
1564  *	path  - Path string
1565  *
1566  * Return:
1567  *	Nothing.
1568  */
1569 STATIC void
cd_show_path(char * title,char * path)1570 cd_show_path(char *title, char *path)
1571 {
1572 	char		*cp = NULL,
1573 			*dpypath,
1574 			cwd[FILE_PATH_SZ * 2] = { '\0' };
1575 	static char	*prevpath = NULL;
1576 
1577 	if (!util_newstr(&cp, path)) {
1578 		CD_FATAL(app_data.str_nomemory);
1579 		return;
1580 	}
1581 
1582 #ifndef __VMS
1583 	/* Convert to absolute path if appropriate */
1584 	if (*cp != '/') {
1585 		if (strcmp(cp, CUR_DIR) == 0)
1586 			*cp = '\0';
1587 
1588 #if defined(BSDCOMPAT) && defined(USE_GETWD)
1589 		if (getwd(cwd) == NULL)
1590 #else
1591 		if (getcwd(cwd, FILE_PATH_SZ * 2) == NULL)
1592 #endif
1593 		{
1594 			MEM_FREE(cp);
1595 			CD_FATAL(app_data.str_longpatherr);
1596 			return;
1597 		}
1598 	}
1599 #endif
1600 
1601 	dpypath = (char *) MEM_ALLOC("dpypath", strlen(cp) + strlen(cwd) + 8);
1602 	if (dpypath == NULL) {
1603 		MEM_FREE(cp);
1604 		CD_FATAL(app_data.str_nomemory);
1605 		return;
1606 	}
1607 #ifndef __VMS
1608 	(void) sprintf(dpypath, "%s%s%s",
1609 			cwd, (*cp == '/' || *cp == '\0') ? "" : "/", cp);
1610 #else
1611 	(void) strcpy(dpypath, cp);
1612 #endif
1613 
1614 	MEM_FREE(cp);
1615 
1616 	if (prevpath != NULL && strcmp(prevpath, dpypath) == 0) {
1617 		/* No change */
1618 		MEM_FREE(dpypath);
1619 		return;
1620 	}
1621 
1622 	DBGPRN(DBG_GEN)(errfp, "\n%s\n%s\n", title, dpypath);
1623 	CD_INFO2(title, dpypath);
1624 
1625 	prevpath = dpypath;
1626 }
1627 
1628 
1629 /*
1630  * cd_mkdirs
1631  *	Called at startup time to create some needed directories, if
1632  *	they aren't already there.
1633  *	Currently these are:
1634  *		$HOME/.xmcdcfg
1635  *		$HOME/.xmcdcfg/prog
1636  *		/tmp/.cdaudio
1637  *
1638  * Args:
1639  *	None.
1640  *
1641  * Return:
1642  *	Nothing.
1643  */
1644 STATIC void
cd_mkdirs(void)1645 cd_mkdirs(void)
1646 {
1647 	char		*errmsg,
1648 			*homepath,
1649 			path[FILE_PATH_SZ + 16];
1650 	struct stat	stbuf;
1651 #ifndef __VMS
1652 	pid_t		cpid;
1653 	waitret_t	wstat;
1654 #endif
1655 
1656 #ifndef NOMKTMPDIR
1657 	errmsg = (char *) MEM_ALLOC(
1658 		"errmsg",
1659 		strlen(app_data.str_tmpdirerr) + strlen(TEMP_DIR)
1660 	);
1661 	if (errmsg == NULL) {
1662 		CD_FATAL(app_data.str_nomemory);
1663 		return;
1664 	}
1665 
1666 	/* Make temporary directory, if needed */
1667 	(void) sprintf(errmsg, app_data.str_tmpdirerr, TEMP_DIR);
1668 	if (util_dirstat(TEMP_DIR, &stbuf, TRUE) < 0) {
1669 		if (!util_mkdir(TEMP_DIR, 0777)) {
1670 			CD_FATAL(errmsg);
1671 			return;
1672 		}
1673 	}
1674 	else if (!S_ISDIR(stbuf.st_mode)) {
1675 		CD_FATAL(errmsg);
1676 		return;
1677 	}
1678 
1679 	MEM_FREE(errmsg);
1680 #endif	/* NOMKTMPDIR */
1681 
1682 #ifndef __VMS
1683 	switch (cpid = FORK()) {
1684 	case -1:
1685 		/* fork failed */
1686 		DBGPRN(DBG_GEN)(errfp,
1687 				"cd_mkdirs: fork failed (errno=%d)\n",
1688 				errno);
1689 		return;
1690 
1691 	case 0:
1692 		/* Child process */
1693 		popup_ok = FALSE;
1694 
1695 		/* Force uid and gid to original setting */
1696 		if (!util_set_ougid())
1697 			_exit(1);
1698 
1699 		break;
1700 
1701 	default:
1702 		/* Parent: wait for child to finish */
1703 		(void) util_waitchild(cpid, app_data.srv_timeout + 5,
1704 				      NULL, 0, FALSE, &wstat);
1705 		return;
1706 	}
1707 #endif
1708 
1709 	homepath = util_homedir(util_get_ouid());
1710 	if ((int) strlen(homepath) >= FILE_PATH_SZ) {
1711 		CD_FATAL(app_data.str_longpatherr);
1712 		return;
1713 	}
1714 
1715 	/* Create the per-user config directory */
1716 	(void) sprintf(path, USR_CFG_PATH, homepath);
1717 	if (util_dirstat(path, &stbuf, TRUE) < 0) {
1718 		if (errno == ENOENT) {
1719 			if (util_mkdir(path, 0755)) {
1720 				DBGPRN(DBG_GEN)(errfp,
1721 					"cd_mkdirs: created directory %s\n",
1722 					path
1723 				);
1724 			}
1725 			else {
1726 				DBGPRN(DBG_GEN)(errfp,
1727 					"cd_mkdirs: cannot mkdir %s\n",
1728 					path
1729 				);
1730 			}
1731 		}
1732 		else {
1733 			DBGPRN(DBG_GEN)(errfp,
1734 				"cd_mkdirs: cannot stat %s\n", path);
1735 		}
1736 	}
1737 	else if (!S_ISDIR(stbuf.st_mode)) {
1738 		DBGPRN(DBG_GEN)(errfp,
1739 			"cd_mkdirs: %s is not a directory.\n", path);
1740 	}
1741 
1742 	/* Create the per-user track program directory */
1743 	(void) sprintf(path, USR_PROG_PATH, homepath);
1744 #ifdef __VMS
1745 	(void) sprintf(path, "%s%c", path, DIR_END);
1746 #endif
1747 	if (util_dirstat(path, &stbuf, TRUE) < 0) {
1748 		if (errno == ENOENT) {
1749 			if (util_mkdir(path, 0755)) {
1750 				DBGPRN(DBG_GEN)(errfp,
1751 					"cd_mkdirs: created directory %s\n",
1752 					path
1753 				);
1754 			}
1755 			else {
1756 				DBGPRN(DBG_GEN)(errfp,
1757 					"cd_mkdirs: cannot mkdir %s\n",
1758 					path
1759 				);
1760 			}
1761 		}
1762 		else {
1763 			DBGPRN(DBG_GEN)(errfp,
1764 				"cd_mkdirs: cannot stat %s\n", path);
1765 		}
1766 	}
1767 	else if (!S_ISDIR(stbuf.st_mode)) {
1768 		DBGPRN(DBG_GEN)(errfp,
1769 			"cd_mkdirs: %s is not a directory.\n", path);
1770 	}
1771 
1772 #ifndef __VMS
1773 	_exit(0);
1774 	/*NOTREACHED*/
1775 #endif
1776 }
1777 
1778 
1779 /*
1780  * cd_strcpy
1781  *	Similar to strcpy(3), but skips some punctuation characters.
1782  *
1783  * Args:
1784  *	tgt - Target string buffer
1785  *	src - Source string buffer
1786  *
1787  * Return:
1788  *	The number of characters copied
1789  */
1790 STATIC size_t
cd_strcpy(char * tgt,char * src)1791 cd_strcpy(char *tgt, char *src)
1792 {
1793 	size_t	n = 0;
1794 	bool_t	prev_space = FALSE;
1795 
1796 	if (src == NULL)
1797 		return 0;
1798 
1799 	for (; *src != '\0'; src++) {
1800 		switch (*src) {
1801 		case '\'':
1802 		case '"':
1803 		case ',':
1804 		case ':':
1805 		case ';':
1806 		case '|':
1807 			/* Skip some punctuation characters */
1808 			break;
1809 
1810 		case ' ':
1811 		case '\t':
1812 		case '/':
1813 		case '\\':
1814 #ifdef __VMS
1815 		case '[':
1816 		case ']':
1817 		case '<':
1818 		case '>':
1819 		case '(':
1820 		case ')':
1821 		case '{':
1822 		case '}':
1823 #endif
1824 			/* Substitute these with underscores */
1825 			if (!prev_space) {
1826 				*tgt++ = app_data.subst_underscore ? '_' : ' ';
1827 				n++;
1828 				prev_space = TRUE;
1829 			}
1830 			break;
1831 
1832 		default:
1833 #ifdef __VMS
1834 			if (!IS_LEGAL_VMSFILECHAR(*src) && *src != '.') {
1835 				/* Skip illegal characters */
1836 				break;
1837 			}
1838 #else
1839 			if (!isprint((int) *src)) {
1840 				/* Skip unprintable characters */
1841 				break;
1842 			}
1843 #endif
1844 
1845 			*tgt++ = *src;
1846 			n++;
1847 			prev_space = FALSE;
1848 			break;
1849 		}
1850 	}
1851 	*tgt = '\0';
1852 	return (n);
1853 }
1854 
1855 
1856 /*
1857  * cd_unlink
1858  *	Unlink the specified file
1859  *
1860  * Args:
1861  *	path - The file path to unlink
1862  *
1863  * Return:
1864  *	TRUE  - success
1865  *	FALSE - failure
1866  */
1867 STATIC bool_t
cd_unlink(char * path)1868 cd_unlink(char *path)
1869 {
1870 #ifdef __VMS
1871 	if (path == NULL)
1872 		return FALSE;
1873 
1874 	if (UNLINK(path) < 0 && errno != ENOENT) {
1875 		DBGPRN(DBG_GEN)(errfp, "unlink failed (errno=%d)\n", errno);
1876 		return FALSE;
1877 	}
1878 	return TRUE;
1879 #else
1880 	pid_t	cpid;
1881 	int	ret,
1882 		wstat;
1883 	bool_t	success;
1884 
1885 	if (path == NULL)
1886 		return FALSE;
1887 
1888 	switch (cpid = FORK()) {
1889 	case -1:
1890 		DBGPRN(DBG_GEN)(errfp,
1891 				"cd_unlink: fork failed (errno=%d)\n",
1892 				errno);
1893 		success = FALSE;
1894 		break;
1895 
1896 	case 0:
1897 		/* Child process */
1898 		popup_ok = FALSE;
1899 
1900 		/* Force uid and gid to original setting */
1901 		if (!util_set_ougid())
1902 			_exit(1);
1903 
1904 		DBGPRN(DBG_GEN)(errfp, "Unlinking [%s]\n", path);
1905 
1906 		if ((ret = UNLINK(path)) != 0 && errno == ENOENT)
1907 			ret = 0;
1908 
1909 		if (ret != 0) {
1910 			DBGPRN(DBG_GEN)(errfp,
1911 					"unlink failed (errno=%d)\n",
1912 					errno);
1913 		}
1914 		_exit((int) (ret != 0));
1915 		/*NOTREACHED*/
1916 
1917 	default:
1918 		/* Parent: wait for child to finish */
1919 		ret = util_waitchild(cpid, app_data.srv_timeout + 5,
1920 			       NULL, 0, FALSE, &wstat);
1921 		if (ret < 0) {
1922 			DBGPRN(DBG_GEN)(errfp,
1923 					"waitpid failed (errno=%d)\n", errno);
1924 			success = FALSE;
1925 		}
1926 		else if (WIFEXITED(wstat)) {
1927 			if ((ret = WEXITSTATUS(wstat)) != 0) {
1928 				DBGPRN(DBG_GEN)(errfp,
1929 					"unlink child exited (status=%d)\n",
1930 					ret);
1931 			}
1932 			success = (bool_t) (ret == 0);
1933 		}
1934 		else if (WIFSIGNALED(wstat)) {
1935 			DBGPRN(DBG_GEN)(errfp,
1936 					"unlink child killed (signal=%d)\n",
1937 					WTERMSIG(wstat));
1938 			success = FALSE;
1939 		}
1940 		else {
1941 			DBGPRN(DBG_GEN)(errfp, "unlink child error\n");
1942 			success = FALSE;
1943 		}
1944 		break;
1945 	}
1946 
1947 	return (success);
1948 #endif
1949 }
1950 
1951 
1952 #ifdef __VMS
1953 
1954 #define VMS_MAX_FILECOMP	39	/* Max component length in VMS */
1955 
1956 /*
1957  * cd_vms_fixfname
1958  *	VMS only allows a max of 39 characters per file path name
1959  *	component, so truncate things if needed.
1960  *
1961  * Args:
1962  *	buf - File path name buffer
1963  *
1964  * Return:
1965  *	Nothing.
1966  */
1967 STATIC void
cd_vms_fixfname(char * buf)1968 cd_vms_fixfname(char *buf)
1969 {
1970 	char	*tmpbuf,
1971 		*p,
1972 		*q;
1973 	int	i;
1974 
1975 	if ((tmpbuf = (char *) MEM_ALLOC("tmpbuf", strlen(buf) + 1)) == NULL) {
1976 		CD_FATAL(app_data.str_nomemory);
1977 		return;
1978 	}
1979 
1980 	for (i = 0, p = buf, q = tmpbuf; *p != '\0'; i++, p++, q++) {
1981 		if (i < VMS_MAX_FILECOMP) {
1982 			*q = *p;
1983 			if (!IS_LEGAL_VMSFILECHAR(*p))
1984 				i = -1;
1985 		}
1986 		else {
1987 			/*
1988 			 * File name component truncation:
1989 			 * Advance to the next component separator
1990 			 */
1991 			while (*p != '\0' && IS_LEGAL_VMSFILECHAR(*p))
1992 				p++;
1993 
1994 			if (*p == '\0')
1995 				break;
1996 			*q = *p;
1997 			i = -1;
1998 		}
1999 	}
2000 	*q = '\0';
2001 
2002 	(void) strcpy(buf, tmpbuf);
2003 
2004 	MEM_FREE(tmpbuf);
2005 }
2006 #endif	/* __VMS */
2007 
2008 
2009 /*
2010  * cd_mkfname
2011  *	Construct a file name based on the supplied template, and append
2012  *	it to the target string.
2013  *
2014  * Args:
2015  *	s      - Pointer to the curstat_t structure
2016  *	trkidx - Track index number (0-based), or -1 if not applicable
2017  *	tgt    - The target string
2018  *	tmpl   - The template string
2019  *	tgtlen - Target string buffer size
2020  *
2021  * Returns:
2022  *	TRUE  - success
2023  *	FALSE - failure
2024  */
2025 STATIC bool_t
cd_mkfname(curstat_t * s,int trkidx,char * tgt,char * tmpl,int tgtlen)2026 cd_mkfname(curstat_t *s, int trkidx, char *tgt, char *tmpl, int tgtlen)
2027 {
2028 	int		len,
2029 			n,
2030 			remain;
2031 	char		*cp,
2032 			*cp2,
2033 			*p,
2034 			tmp[(STR_BUF_SZ * 2) + 4];
2035 	bool_t		err;
2036 	cdinfo_incore_t	*dbp;
2037 
2038 	dbp = dbprog_curdb(s);
2039 
2040 	err = FALSE;
2041 	n = 0;
2042 	len = strlen(tgt);
2043 	cp = tgt + len;
2044 
2045 	for (cp2 = tmpl; *cp2 != '\0'; cp2++, cp += n, len += n) {
2046 		if ((remain = (tgtlen - len)) <= 0)
2047 			break;
2048 
2049 		switch (*cp2) {
2050 		case '%':
2051 			switch (*(++cp2)) {
2052 			case 'X':
2053 				n = strlen(PROGNAME);
2054 				if (n < remain)
2055 					(void) strcpy(cp, PROGNAME);
2056 				else
2057 					err = TRUE;
2058 				break;
2059 
2060 			case 'V':
2061 				n = strlen(VERSION_MAJ) +
2062 				    strlen(VERSION_MAJ) + 1;
2063 				if (n < remain)
2064 					(void) sprintf(cp, "%s.%s",
2065 							VERSION_MAJ,
2066 							VERSION_MIN);
2067 				else
2068 					err = TRUE;
2069 				break;
2070 
2071 			case 'N':
2072 				p = util_loginname();
2073 				n = strlen(p);
2074 				if (n < remain)
2075 					(void) strcpy(cp, p);
2076 				else
2077 					err = TRUE;
2078 				break;
2079 
2080 			case '~':
2081 				p = util_homedir(util_get_ouid());
2082 				n = strlen(p);
2083 				if (n < remain)
2084 					(void) strcpy(cp, p);
2085 				else
2086 					err = TRUE;
2087 				break;
2088 
2089 			case 'H':
2090 				p = util_get_uname()->nodename;
2091 				n = strlen(p);
2092 				if (n < remain)
2093 					(void) strcpy(cp, p);
2094 				else
2095 					err = TRUE;
2096 				break;
2097 
2098 			case 'L':
2099 				p = app_data.libdir;
2100 				n = strlen(p);
2101 				if (n < remain)
2102 					(void) strcpy(cp, p);
2103 				else
2104 					err = TRUE;
2105 				break;
2106 
2107 			case 'S':
2108 				p = app_data.libdir;
2109 				n = strlen(p) + strlen("discog") + 1;
2110 				if (n < remain) {
2111 					(void) strcpy(cp, p);
2112 #ifdef __VMS
2113 					(void) strcat(cp, ".discog");
2114 #else
2115 					(void) strcat(cp, "/discog");
2116 #endif
2117 				}
2118 				else
2119 					err = TRUE;
2120 				break;
2121 
2122 			case 'C':
2123 				p = cdinfo_genre_path(dbp->disc.genre);
2124 				n = strlen(p);
2125 				if (n < remain)
2126 					(void) strcpy(cp, p);
2127 				else
2128 					err = TRUE;
2129 				break;
2130 
2131 			case 'I':
2132 				n = 8;
2133 				if (n < remain)
2134 					(void) sprintf(cp, "%08x",
2135 							dbp->discid);
2136 				else
2137 					err = TRUE;
2138 				break;
2139 
2140 			case 'A':
2141 			case 'a':
2142 				p = dbp->disc.artist;
2143 				if (p == NULL)
2144 					p = "artist";
2145 				if (*cp2 == 'a') {
2146 					p = util_text_reduce(p);
2147 					if (p == NULL) {
2148 					    CD_FATAL(app_data.str_nomemory);
2149 					    return FALSE;
2150 					}
2151 				}
2152 
2153 				n = strlen(p);
2154 				if (n < remain)
2155 					n = cd_strcpy(cp, p);
2156 				else
2157 					err = TRUE;
2158 
2159 				if (*cp2 == 'a')
2160 					MEM_FREE(p);
2161 				break;
2162 
2163 			case 'D':
2164 			case 'd':
2165 				p = dbp->disc.title;
2166 				if (p == NULL)
2167 					p = "title";
2168 				if (*cp2 == 'd') {
2169 					p = util_text_reduce(p);
2170 					if (p == NULL) {
2171 					    CD_FATAL(app_data.str_nomemory);
2172 					    return FALSE;
2173 					}
2174 				}
2175 
2176 				n = strlen(p);
2177 				if (n < remain)
2178 					n = cd_strcpy(cp, p);
2179 				else
2180 					err = TRUE;
2181 
2182 				if (*cp2 == 'd')
2183 					MEM_FREE(p);
2184 				break;
2185 
2186 			case 'R':
2187 			case 'r':
2188 				p = dbp->track[trkidx].artist;
2189 				if (p == NULL)
2190 					p = "trackartist";
2191 				if (*cp2 == 'r') {
2192 					p = util_text_reduce(p);
2193 					if (p == NULL) {
2194 					    CD_FATAL(app_data.str_nomemory);
2195 					    return FALSE;
2196 					}
2197 				}
2198 
2199 				n = strlen(p);
2200 				if (n < remain)
2201 					n = cd_strcpy(cp, p);
2202 				else
2203 					err = TRUE;
2204 
2205 				if (*cp2 == 'r')
2206 					MEM_FREE(p);
2207 				break;
2208 
2209 			case 'T':
2210 			case 't':
2211 				p = dbp->track[trkidx].title;
2212 				if (p == NULL)
2213 					p = "track";
2214 				if (*cp2 == 't') {
2215 					p = util_text_reduce(p);
2216 					if (p == NULL) {
2217 					    CD_FATAL(app_data.str_nomemory);
2218 					    return FALSE;
2219 					}
2220 				}
2221 
2222 				n = strlen(p);
2223 				if (n < remain)
2224 					n = cd_strcpy(cp, p);
2225 				else
2226 					err = TRUE;
2227 
2228 				if (*cp2 == 't')
2229 					MEM_FREE(p);
2230 				break;
2231 
2232 			case 'B':
2233 			case 'b':
2234 				(void) sprintf(tmp, "%.127s%s%.127s",
2235 					(dbp->disc.artist == NULL) ?
2236 						"artist" : dbp->disc.artist,
2237 					(dbp->disc.artist != NULL &&
2238 					 dbp->disc.title != NULL) ?
2239 						"-" : "",
2240 					(dbp->disc.title == NULL) ?
2241 						"title" : dbp->disc.title);
2242 				p = tmp;
2243 				if (*cp2 == 'b') {
2244 					p = util_text_reduce(p);
2245 					if (p == NULL) {
2246 					    CD_FATAL(app_data.str_nomemory);
2247 					    return FALSE;
2248 					}
2249 				}
2250 
2251 				n = strlen(p);
2252 				if (n < remain)
2253 					n = cd_strcpy(cp, p);
2254 				else
2255 					err = TRUE;
2256 
2257 				if (*cp2 == 'b')
2258 					MEM_FREE(p);
2259 				break;
2260 
2261 			case '#':
2262 				n = 2;
2263 				if (n < remain)
2264 					(void) sprintf(cp, "%02d",
2265 						s->trkinfo[trkidx].trkno);
2266 				else
2267 					err = TRUE;
2268 				break;
2269 
2270 			case '%':
2271 			case ' ':
2272 			case '\t':
2273 			case '\'':
2274 			case '"':
2275 			case ',':
2276 			case ';':
2277 			case ':':
2278 			case '|':
2279 			case '\\':
2280 				/* Skip some punctuation characters */
2281 				n = 1;
2282 				if (n < remain)
2283 					*cp = '%';
2284 				else
2285 					err = TRUE;
2286 				break;
2287 
2288 			default:
2289 				n = 2;
2290 				if (n < remain)
2291 					*cp = '%';
2292 				else
2293 					err = TRUE;
2294 				break;
2295 			}
2296 			break;
2297 
2298 		case ' ':
2299 		case '\t':
2300 #ifdef __VMS
2301 		case '<':
2302 		case '>':
2303 		case '(':
2304 		case ')':
2305 		case '{':
2306 		case '}':
2307 #endif
2308 			n = 1;
2309 			if (n < remain) {
2310 				if (app_data.subst_underscore)
2311 					*cp = '_';
2312 				else
2313 					*cp = ' ';
2314 			}
2315 			else
2316 				err = TRUE;
2317 			break;
2318 
2319 #ifndef __VMS
2320 		case ';':
2321 #endif
2322 		case '\'':
2323 		case '"':
2324 		case ',':
2325 		case '|':
2326 		case '\\':
2327 			/* Skip some punctuation characters */
2328 			n = 0;
2329 			break;
2330 
2331 #ifdef __VMS
2332 		/* Allow these because they are special in VMS file names */
2333 		case '$':
2334 		case '[':
2335 		case ']':
2336 		case '.':
2337 		case ':':
2338 		case ';':
2339 		case '-':
2340 		case '_':
2341 			n = 1;
2342 			if (n < remain)
2343 				*cp = *cp2;
2344 			else
2345 				err = TRUE;
2346 			break;
2347 #endif
2348 
2349 		default:
2350 #ifdef __VMS
2351 			if (!isalnum((int) *cp2)) {
2352 				/* Skip illegal characters */
2353 				n = 0;
2354 				break;
2355 			}
2356 #else
2357 			if (!isprint((int) *cp2)) {
2358 				/* Skip unprintable characters */
2359 				n = 0;
2360 				break;
2361 			}
2362 #endif
2363 
2364 			n = 1;
2365 			if (n < remain)
2366 				*cp = *cp2;
2367 			else
2368 				err = TRUE;
2369 			break;
2370 		}
2371 
2372 		if (err)
2373 			break;
2374 	}
2375 	*cp = '\0';
2376 
2377 	if (err) {
2378 		CD_INFO(app_data.str_longpatherr);
2379 		return FALSE;
2380 	}
2381 
2382 #ifdef __VMS
2383 	/* Check file path and truncate its components if needed */
2384 	cd_vms_fixfname(tgt);
2385 #endif
2386 
2387 	return TRUE;
2388 }
2389 
2390 
2391 /*
2392  * cd_mkoutpath
2393  *	Construct audio output file names and check for collision
2394  *
2395  * Args:
2396  *	s - Pointer to the curstat_t structure
2397  *
2398  * Return
2399  *	TRUE  - passed
2400  *	FALSE - failed
2401  */
2402 STATIC bool_t
cd_mkoutpath(curstat_t * s)2403 cd_mkoutpath(curstat_t *s)
2404 {
2405 	char			*str,
2406 				*dpath,
2407 				*path;
2408 	int			i,
2409 				j;
2410 	struct stat		stbuf;
2411 	bool_t			ret;
2412 
2413 	if (s->outf_tmpl == NULL) {
2414 		CD_INFO(app_data.str_noaudiopath);
2415 		return FALSE;
2416 	}
2417 
2418 	dpath = NULL;
2419 
2420 	if (app_data.cdda_trkfile) {
2421 		/* Normal play, program, shuffle, sample and
2422 		 * segment play modes
2423 		 */
2424 		for (i = 0; i < (int) s->tot_trks; i++) {
2425 			if (s->program) {
2426 				bool_t	inprog;
2427 
2428 				inprog = FALSE;
2429 				for (j = 0; j < (int) s->prog_tot; j++) {
2430 					if (i == s->trkinfo[j].playorder) {
2431 						inprog = TRUE;
2432 						break;
2433 					}
2434 				}
2435 
2436 				if (!inprog)
2437 					/* This track is not in the program */
2438 					continue;
2439 			}
2440 			else if (s->segplay == SEGP_AB) {
2441 				bool_t		inseg;
2442 				sword32_t	sot,
2443 						eot;
2444 
2445 				inseg = FALSE;
2446 				sot = s->trkinfo[i].addr;
2447 				eot = s->trkinfo[i+1].addr;
2448 
2449 				/* "Enhanced CD" / "CD Extra" hack */
2450 				if (eot > s->discpos_tot.addr)
2451 					eot = s->discpos_tot.addr;
2452 
2453 				if (s->bp_startpos_tot.addr >= sot &&
2454 				    s->bp_startpos_tot.addr < eot) {
2455 					/* The segment spans the beginning
2456 					 * of this track
2457 					 */
2458 					inseg = TRUE;
2459 				}
2460 				else if (s->bp_endpos_tot.addr >= sot &&
2461 					 s->bp_endpos_tot.addr < eot) {
2462 					/* The segment spans the end
2463 					 * of this track
2464 					 */
2465 					inseg = TRUE;
2466 				}
2467 				else if (s->bp_startpos_tot.addr >= sot &&
2468 					 s->bp_endpos_tot.addr < eot) {
2469 					/* The segment is contained within
2470 					 * this track
2471 					 */
2472 					inseg = TRUE;
2473 				}
2474 
2475 				if (!inseg)
2476 					continue;
2477 			}
2478 			else if (!s->shuffle &&
2479 				 s->cur_trk > s->trkinfo[i].trkno) {
2480 				continue;
2481 			}
2482 
2483 			path = (char *) MEM_ALLOC("path", FILE_PATH_SZ);
2484 			if (path == NULL) {
2485 				CD_FATAL(app_data.str_nomemory);
2486 				return FALSE;
2487 			}
2488 			path[0] = '\0';
2489 
2490 			if (!cd_mkfname(s, i, path, s->outf_tmpl,
2491 					FILE_PATH_SZ)) {
2492 				MEM_FREE(path);
2493 				return FALSE;
2494 			}
2495 
2496 			if (!util_newstr(&s->trkinfo[i].outfile, path)) {
2497 				MEM_FREE(path);
2498 				CD_FATAL(app_data.str_nomemory);
2499 				return FALSE;
2500 			}
2501 
2502 			if (dpath == NULL &&
2503 			    (dpath = util_dirname(path)) == NULL) {
2504 				MEM_FREE(path);
2505 				CD_FATAL("File dirname error");
2506 				return FALSE;
2507 			}
2508 
2509 			if (LSTAT(path, &stbuf) < 0) {
2510 				MEM_FREE(path);
2511 				continue;
2512 			}
2513 
2514 			if (S_ISREG(stbuf.st_mode)) {
2515 				char	*fname;
2516 
2517 				if ((fname = util_basename(path)) == NULL) {
2518 					CD_FATAL(app_data.str_nomemory);
2519 					return FALSE;
2520 				}
2521 
2522 				str = (char *) MEM_ALLOC("str",
2523 					strlen(app_data.str_overwrite) +
2524 					strlen(fname) + 4
2525 				);
2526 				if (str == NULL) {
2527 					MEM_FREE(fname);
2528 					CD_FATAL(app_data.str_nomemory);
2529 					return FALSE;
2530 				}
2531 
2532 				(void) sprintf(str, "%s\n\n%s",
2533 						fname,
2534 						app_data.str_overwrite);
2535 
2536 				ret = cd_confirm_popup(
2537 					app_data.str_confirm,
2538 					str,
2539 					(XtCallbackProc) NULL,
2540 					NULL,
2541 					(XtCallbackProc) NULL,
2542 					NULL
2543 				);
2544 
2545 				MEM_FREE(str);
2546 				MEM_FREE(fname);
2547 
2548 				if (ret)
2549 					ret = cd_unlink(path);
2550 
2551 				if (!ret) {
2552 					if (s->onetrk_prog) {
2553 						s->onetrk_prog = FALSE;
2554 						dbprog_progclear(s);
2555 					}
2556 
2557 					MEM_FREE(path);
2558 					return FALSE;
2559 				}
2560 			}
2561 			else {
2562 				str = (char *) MEM_ALLOC("str",
2563 					strlen(app_data.str_audiopathexists) +
2564 					strlen(path) + 4
2565 				);
2566 				if (str == NULL) {
2567 					CD_FATAL(app_data.str_nomemory);
2568 					return FALSE;
2569 				}
2570 
2571 				(void) sprintf(str, "%s\n\n%s",
2572 						path,
2573 						app_data.str_audiopathexists);
2574 
2575 				CD_INFO(str);
2576 
2577 				MEM_FREE(str);
2578 				MEM_FREE(path);
2579 				return FALSE;
2580 			}
2581 
2582 			MEM_FREE(path);
2583 		}
2584 	}
2585 	else {
2586 		path = (char *) MEM_ALLOC("path", FILE_PATH_SZ);
2587 		if (path == NULL) {
2588 			CD_FATAL(app_data.str_nomemory);
2589 			return FALSE;
2590 		}
2591 		path[0] = '\0';
2592 
2593 		if (!cd_mkfname(s, -1, path, s->outf_tmpl, FILE_PATH_SZ)) {
2594 			MEM_FREE(path);
2595 			return FALSE;
2596 		}
2597 
2598 		if (!util_newstr(&s->trkinfo[0].outfile, path)) {
2599 			MEM_FREE(path);
2600 			CD_FATAL(app_data.str_nomemory);
2601 			return FALSE;
2602 		}
2603 
2604 		if (LSTAT(path, &stbuf) == 0) {
2605 			if (S_ISREG(stbuf.st_mode)) {
2606 				char	*fname;
2607 
2608 				if ((fname = util_basename(path)) == NULL) {
2609 					CD_FATAL(app_data.str_nomemory);
2610 					return FALSE;
2611 				}
2612 
2613 				str = (char *) MEM_ALLOC("str",
2614 					strlen(app_data.str_overwrite) +
2615 					strlen(fname) + 4
2616 				);
2617 				if (str == NULL) {
2618 					MEM_FREE(fname);
2619 					CD_FATAL(app_data.str_nomemory);
2620 					return FALSE;
2621 				}
2622 
2623 				(void) sprintf(str, "%s\n\n%s",
2624 					       fname, app_data.str_overwrite);
2625 
2626 				ret = cd_confirm_popup(
2627 					app_data.str_confirm,
2628 					str,
2629 					(XtCallbackProc) NULL,
2630 					NULL,
2631 					(XtCallbackProc) NULL,
2632 					NULL
2633 				);
2634 
2635 				MEM_FREE(str);
2636 				MEM_FREE(fname);
2637 
2638 				if (ret)
2639 					ret = cd_unlink(path);
2640 
2641 				if (!ret) {
2642 					if (s->onetrk_prog) {
2643 						s->onetrk_prog = FALSE;
2644 						dbprog_progclear(s);
2645 					}
2646 
2647 					MEM_FREE(path);
2648 					return FALSE;
2649 				}
2650 			}
2651 			else {
2652 				str = (char *) MEM_ALLOC("str",
2653 					strlen(app_data.str_audiopathexists) +
2654 					strlen(path) + 4
2655 				);
2656 				if (str == NULL) {
2657 					CD_FATAL(app_data.str_nomemory);
2658 					return FALSE;
2659 				}
2660 
2661 				(void) sprintf(str, "%s\n\n%s",
2662 					       path,
2663 					       app_data.str_audiopathexists);
2664 
2665 				CD_INFO(str);
2666 
2667 				MEM_FREE(str);
2668 				MEM_FREE(path);
2669 				return FALSE;
2670 			}
2671 		}
2672 
2673 		if ((dpath = util_dirname(path)) == NULL) {
2674 			MEM_FREE(path);
2675 			CD_FATAL("File dirname error");
2676 			return FALSE;
2677 		}
2678 
2679 		MEM_FREE(path);
2680 	}
2681 
2682 	/* Show output directory path in pop-up info window */
2683 	cd_show_path(app_data.str_outdir, dpath);
2684 
2685 	MEM_FREE(dpath);
2686 
2687 	return TRUE;
2688 }
2689 
2690 
2691 /*
2692  * cd_ckpipeprog
2693  *	Construct pipe to program path and check for validity
2694  *
2695  * Args:
2696  *	s - Pointer to the curstat_t structure
2697  *
2698  * Return
2699  *	TRUE  - passed
2700  *	FALSE - failed
2701  */
2702 /*ARGSUSED*/
2703 STATIC bool_t
cd_ckpipeprog(curstat_t * s)2704 cd_ckpipeprog(curstat_t *s)
2705 {
2706 	char	*str;
2707 
2708 	if (app_data.pipeprog == NULL) {
2709 		CD_INFO(app_data.str_noprog);
2710 		return FALSE;
2711 	}
2712 
2713 	if (!util_checkcmd(app_data.pipeprog)) {
2714 		str = (char *) MEM_ALLOC("str",
2715 			strlen(app_data.pipeprog) +
2716 			strlen(app_data.str_cannotinvoke) + 8
2717 		);
2718 		if (str == NULL) {
2719 			CD_FATAL(app_data.str_nomemory);
2720 			return FALSE;
2721 		}
2722 
2723 		(void) sprintf(str, app_data.str_cannotinvoke,
2724 				app_data.pipeprog);
2725 		CD_INFO(str);
2726 
2727 		MEM_FREE(str);
2728 		return FALSE;
2729 	}
2730 
2731 	return TRUE;
2732 }
2733 
2734 
2735 /***********************
2736  *   public routines   *
2737  ***********************/
2738 
2739 
2740 /*
2741  * fix_outfile_path
2742  *	Fix the CDDA audio output file path to make sure there is a proper
2743  *	suffix based on the file format.  Also, replace white spaces in
2744  *	the file path string with underscores.
2745  *
2746  * Args:
2747  *	s - Pointer to the curstat_t structure.
2748  *
2749  * Return:
2750  *	Nothing.
2751  */
2752 void
fix_outfile_path(curstat_t * s)2753 fix_outfile_path(curstat_t *s)
2754 {
2755 	filefmt_t	*fmp;
2756 	char		*suf,
2757 			*cp,
2758 			tmp[FILE_PATH_SZ * 2 + 8];
2759 
2760 	/* File suffix */
2761 	if ((fmp = cdda_filefmt(app_data.cdda_filefmt)) == NULL)
2762 		suf = "";
2763 	else
2764 		suf = fmp->suf;
2765 
2766 	if (s->outf_tmpl == NULL) {
2767 		/* No default outfile, set it */
2768 		(void) sprintf(tmp,
2769 #ifdef __VMS
2770 			"%%S.%%C.%%I]%s%s",
2771 #else
2772 			"%%S/%%C/%%I/%s%s",
2773 #endif
2774 			app_data.cdda_trkfile ? FILEPATH_TRACK : FILEPATH_DISC,
2775 			suf
2776 		);
2777 
2778 		if (!util_newstr(&s->outf_tmpl, tmp)) {
2779 			CD_FATAL(app_data.str_nomemory);
2780 			return;
2781 		}
2782 	}
2783 	else if ((cp = strrchr(s->outf_tmpl, '.')) != NULL) {
2784 		char	*cp2 = strrchr(s->outf_tmpl, DIR_END);
2785 
2786 		if (cp2 == NULL || cp > cp2) {
2787 			/* Change suffix if necessary */
2788 			if (strcmp(cp, suf) != 0) {
2789 				*cp = '\0';
2790 				(void) strncpy(tmp, s->outf_tmpl,
2791 					    FILE_PATH_SZ - strlen(suf) - 1);
2792 				(void) strcat(tmp, suf);
2793 
2794 				if (!util_newstr(&s->outf_tmpl, tmp)) {
2795 					CD_FATAL(app_data.str_nomemory);
2796 					return;
2797 				}
2798 			}
2799 		}
2800 		else {
2801 			/* No suffix, add one */
2802 			(void) strncpy(tmp, s->outf_tmpl,
2803 					FILE_PATH_SZ - strlen(suf) - 1);
2804 			(void) strcat(tmp, suf);
2805 
2806 			if (!util_newstr(&s->outf_tmpl, tmp)) {
2807 				CD_FATAL(app_data.str_nomemory);
2808 				return;
2809 			}
2810 		}
2811 	}
2812 	else {
2813 		/* No suffix, add one */
2814 		(void) strncpy(tmp, s->outf_tmpl,
2815 				FILE_PATH_SZ - strlen(suf) - 1);
2816 		(void) strcat(tmp, suf);
2817 
2818 		if (!util_newstr(&s->outf_tmpl, tmp)) {
2819 			CD_FATAL(app_data.str_nomemory);
2820 			return;
2821 		}
2822 	}
2823 }
2824 
2825 
2826 /*
2827  * curtrk_type
2828  *	Return the track type of the currently playing track.
2829  *
2830  * Args:
2831  *	s - Pointer to the curstat_t structure.
2832  *
2833  * Return:
2834  *	TYP_AUDIO or TYP_DATA.
2835  */
2836 byte_t
curtrk_type(curstat_t * s)2837 curtrk_type(curstat_t *s)
2838 {
2839 	sword32_t	i;
2840 
2841 	if ((i = di_curtrk_pos(s)) >= 0)
2842 		return (s->trkinfo[i].type);
2843 
2844 	return TYP_AUDIO;
2845 }
2846 
2847 
2848 /*
2849  * dpy_disc
2850  *	Update the disc number display region of the main window.
2851  *
2852  * Args:
2853  *	s - Pointer to the curstat_t structure.
2854  *
2855  * Return:
2856  *	Nothing
2857  */
2858 void
dpy_disc(curstat_t * s)2859 dpy_disc(curstat_t *s)
2860 {
2861 	XmString	xs;
2862 	char		str[8];
2863 	static char	prev[8] = { '\0' };
2864 
2865 	(void) sprintf(str, "%u", s->cur_disc);
2866 
2867 	if (strcmp(str, prev) == 0)
2868 		/* No change, just return */
2869 		return;
2870 
2871 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
2872 
2873 	XtVaSetValues(
2874 		widgets.main.disc_ind,
2875 		XmNlabelString, xs,
2876 		NULL
2877 	);
2878 
2879 	XmStringFree(xs);
2880 
2881 	/* Update the keypad indicator */
2882 	dpy_keypad_ind(s);
2883 
2884 	(void) strcpy(prev, str);
2885 }
2886 
2887 
2888 /*
2889  * dpy_track
2890  *	Update the track number display region of the main window.
2891  *
2892  * Args:
2893  *	s - Pointer to the curstat_t structure.
2894  *
2895  * Return:
2896  *	Nothing
2897  */
2898 void
dpy_track(curstat_t * s)2899 dpy_track(curstat_t *s)
2900 {
2901 	XmString	xs;
2902 	char		str[4];
2903 	static char	prev[4] = { '\0' };
2904 	static int	sav_trk = -1;
2905 
2906 
2907 	if (s->cur_trk != sav_trk) {
2908 		/* Update CD Info/program window current track display */
2909 		dbprog_curtrkupd(s);
2910 		/* Update main window track title display */
2911 		dpy_ttitle(s);
2912 		/* Update the keypad indicator */
2913 		dpy_keypad_ind(s);
2914 		/* Update warp slider */
2915 		dpy_warp(s);
2916 	}
2917 
2918 	sav_trk = s->cur_trk;
2919 
2920 	if (s->cur_trk < 0 || s->mode == MOD_BUSY ||
2921 		 s->mode == MOD_NODISC)
2922 		(void) strcpy(str, "--");
2923 	else if (s->time_dpy == T_REMAIN_DISC) {
2924 		if (s->shuffle || s->program) {
2925 			if (s->prog_tot >= s->prog_cnt)
2926 				(void) sprintf(str, "-%u",
2927 					       s->prog_tot - s->prog_cnt);
2928 			else
2929 				(void) strcpy(str, "-0");
2930 		}
2931 		else
2932 			(void) sprintf(str, "-%u",
2933 				       s->tot_trks - di_curtrk_pos(s) - 1);
2934 	}
2935 	else
2936 		(void) sprintf(str, "%02u", s->cur_trk);
2937 
2938 	if (strcmp(str, prev) == 0 && !mode_chg)
2939 		/* No change, just return */
2940 		return;
2941 
2942 	xs = create_xmstring(
2943 		str, NULL,
2944 		(geom_main_getmode() == MAIN_NORMAL) ? CHSET1 : CHSET2,
2945 		FALSE
2946 	);
2947 
2948 	XtVaSetValues(
2949 		widgets.main.track_ind,
2950 		XmNlabelString, xs,
2951 		NULL
2952 	);
2953 
2954 	XmStringFree(xs);
2955 
2956 	(void) strcpy(prev, str);
2957 
2958 	/* Update CDDA save-to-file expanded path display if appropriate */
2959 	if (pathexp_active && app_data.cdda_trkfile) {
2960 		cd_filepath_exp(
2961 			widgets.options.mode_pathexp_btn,
2962 			(XtPointer) s,
2963 			NULL
2964 		);
2965 	}
2966 }
2967 
2968 
2969 /*
2970  * dpy_index
2971  *	Update the index number display region of the main window.
2972  *
2973  * Args:
2974  *	s - Pointer to the curstat_t structure.
2975  *
2976  * Return:
2977  *	Nothing
2978  */
2979 void
dpy_index(curstat_t * s)2980 dpy_index(curstat_t *s)
2981 {
2982 	XmString	xs;
2983 	char		str[4];
2984 	static char	prev[4] = { '\0' };
2985 
2986 	if (s->cur_idx <= 0 || s->mode == MOD_BUSY ||
2987 	    s->mode == MOD_NODISC || s->mode == MOD_STOP)
2988 		(void) strcpy(str, "--");
2989 	else
2990 		(void) sprintf(str, "%02u", s->cur_idx);
2991 
2992 	if (strcmp(str, prev) == 0)
2993 		/* No change, just return */
2994 		return;
2995 
2996 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
2997 
2998 	XtVaSetValues(
2999 		widgets.main.index_ind,
3000 		XmNlabelString, xs,
3001 		NULL
3002 	);
3003 
3004 	XmStringFree(xs);
3005 
3006 	(void) strcpy(prev, str);
3007 }
3008 
3009 
3010 /*
3011  * dpy_time
3012  *	Update the time display region of the main window.
3013  *
3014  * Args:
3015  *	s - Pointer to the curstat_t structure.
3016  *	blank - Whether the display region should be blanked.
3017  *
3018  * Return:
3019  *	Nothing
3020  */
3021 void
dpy_time(curstat_t * s,bool_t blank)3022 dpy_time(curstat_t *s, bool_t blank)
3023 {
3024 	sword32_t	time_sec;
3025 	XmString	xs;
3026 	char		str[16];
3027 	static char	prev[16] = { 'j', 'u', 'n', 'k', '\0' };
3028 
3029 	if (blank)
3030 		str[0] = '\0';
3031 	else if (s->mode == MOD_BUSY)
3032 		(void) strcpy(str, app_data.str_busy);
3033 	else if (s->mode == MOD_NODISC)
3034 		(void) strcpy(str, app_data.str_nodisc);
3035 	else if (curtrk_type(s) == TYP_DATA)
3036 		(void) strcpy(str, app_data.str_data);
3037 	else if (s->mode == MOD_STOP)
3038 		(void) strcpy(str, " --:--");
3039 	else {
3040 		switch (s->time_dpy) {
3041 		case T_ELAPSED_TRACK:
3042 			if (app_data.subq_lba && s->cur_idx == 0)
3043 				/* LBA format doesn't have meaningful lead-in
3044 				 * time, so just display blank.
3045 				 */
3046 				(void) strcpy(str, "   :  ");
3047 			else
3048 				(void) sprintf(str, "%s%02u:%02u",
3049 					       (s->cur_idx == 0) ? "-" : "+",
3050 					       s->curpos_trk.min,
3051 					       s->curpos_trk.sec);
3052 			break;
3053 
3054 		case T_ELAPSED_SEG:
3055 			if (s->segplay != SEGP_AB) {
3056 				(void) strcpy(str, "   :  ");
3057 				break;
3058 			}
3059 			time_sec = (s->curpos_tot.addr -
3060 				    s->bp_startpos_tot.addr) / FRAME_PER_SEC;
3061 			(void) sprintf(str, "+%02u:%02u",
3062 				       time_sec / 60, time_sec % 60);
3063 			break;
3064 
3065 		case T_ELAPSED_DISC:
3066 			if (s->shuffle || s->program) {
3067 				if (s->cur_idx == 0) {
3068 					(void) strcpy(str, "   :  ");
3069 					break;
3070 				}
3071 				else
3072 					time_sec = disc_etime_prog(s);
3073 			}
3074 			else
3075 				time_sec = disc_etime_norm(s);
3076 
3077 			(void) sprintf(str, "+%02u:%02u",
3078 				       time_sec / 60, time_sec % 60);
3079 			break;
3080 
3081 		case T_REMAIN_TRACK:
3082 			if (s->cur_idx == 0)
3083 				(void) strcpy(str, "   :  ");
3084 			else {
3085 				time_sec = track_rtime(s);
3086 				(void) sprintf(str, "-%02u:%02u",
3087 					       time_sec / 60, time_sec % 60);
3088 			}
3089 			break;
3090 
3091 		case T_REMAIN_SEG:
3092 			if (s->segplay != SEGP_AB) {
3093 				(void) strcpy(str, "   :  ");
3094 				break;
3095 			}
3096 			time_sec = (s->bp_endpos_tot.addr -
3097 				    s->curpos_tot.addr) / FRAME_PER_SEC;
3098 			(void) sprintf(str, "-%02u:%02u",
3099 				       time_sec / 60, time_sec % 60);
3100 			break;
3101 
3102 		case T_REMAIN_DISC:
3103 			if (s->shuffle || s->program) {
3104 				if (s->cur_idx == 0) {
3105 					(void) strcpy(str, "   :  ");
3106 					break;
3107 				}
3108 				else
3109 					time_sec = disc_rtime_prog(s);
3110 			}
3111 			else
3112 				time_sec = disc_rtime_norm(s);
3113 
3114 			(void) sprintf(str, "-%02u:%02u",
3115 				       time_sec / 60, time_sec % 60);
3116 			break;
3117 
3118 		default:
3119 			(void) strcpy(str, "??:??");
3120 			break;
3121 		}
3122 	}
3123 
3124 	if (s->mode == MOD_PAUSE)
3125 		cd_pause_blink(s, TRUE);
3126 	else
3127 		cd_pause_blink(s, FALSE);
3128 
3129 	/* Update the keypad indicator */
3130 	dpy_keypad_ind(s);
3131 
3132 	/* Update warp slider */
3133 	dpy_warp(s);
3134 
3135 	/* Display CDDA performance */
3136 	dpy_cddaperf(s);
3137 
3138 	if (strcmp(str, prev) == 0 && !mode_chg)
3139 		/* No change: just return */
3140 		return;
3141 
3142 	xs = create_xmstring(
3143 		str, NULL,
3144 		(geom_main_getmode() == MAIN_NORMAL) ? CHSET1 : CHSET2,
3145 		FALSE
3146 	);
3147 
3148 	XtVaSetValues(
3149 		widgets.main.time_ind,
3150 		XmNlabelString, xs,
3151 		NULL
3152 	);
3153 
3154 	XmStringFree(xs);
3155 	(void) strcpy(prev, str);
3156 }
3157 
3158 
3159 /*
3160  * dpy_dtitle
3161  *	Update the disc title display region of the main window.
3162  *
3163  * Args:
3164  *	s - Pointer to the curstat_t structure.
3165  *
3166  * Return:
3167  *	Nothing
3168  */
3169 void
dpy_dtitle(curstat_t * s)3170 dpy_dtitle(curstat_t *s)
3171 {
3172 	XmString	xs;
3173 	char		*artist,
3174 			*title,
3175 			str[TITLEIND_LEN],
3176 			icontitle[TITLEIND_LEN + 16];
3177 	static char	prev[TITLEIND_LEN];
3178 
3179 	str[0] = '\0';
3180 	if (!chgdelay) {
3181 		artist = dbprog_curartist(s);
3182 		title = dbprog_curtitle(s);
3183 		if (artist != NULL) {
3184 			(void) sprintf(str, "%.127s", artist);
3185 			if (title != NULL)
3186 				(void) sprintf(str, "%s / %.127s", str, title);
3187 		}
3188 		else if (title != NULL)
3189 			(void) sprintf(str, "%.127s", title);
3190 
3191 		str[TITLEIND_LEN - 1] = '\0';
3192 	}
3193 
3194 	if (strcmp(str, prev) == 0)
3195 		/* No change: just return */
3196 		return;
3197 
3198 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, TRUE);
3199 
3200 	XtVaSetValues(
3201 		widgets.main.dtitle_ind,
3202 		XmNlabelString, xs,
3203 		NULL
3204 	);
3205 
3206 	XmStringFree(xs);
3207 
3208 	(void) strcpy(prev, str);
3209 
3210 	/* Update icon title */
3211 	if (str[0] == '\0')
3212 		(void) strcpy(icontitle, PROGNAME);
3213 	else {
3214 		chset_conv_t	*up;
3215 		char		*a = NULL;
3216 
3217 		/* Convert from UTF-8 to local charset if possible */
3218 		if ((up = util_chset_open(CHSET_UTF8_TO_LOCALE)) == NULL)
3219 			return;
3220 
3221 		if (!util_chset_conv(up, str, &a, FALSE) &&
3222 		    !util_newstr(&a, str)) {
3223 			util_chset_close(up);
3224 			return;
3225 		}
3226 
3227 		util_chset_close(up);
3228 
3229 #ifdef USE_SGI_DESKTOP
3230 		(void) strcpy(icontitle, a);
3231 #else
3232 		(void) sprintf(icontitle, "%s: %s", PROGNAME, a);
3233 #endif
3234 
3235 		MEM_FREE(a);
3236 	}
3237 
3238 	XtVaSetValues(widgets.toplevel,
3239 		XmNiconName, icontitle,
3240 		NULL
3241 	);
3242 }
3243 
3244 
3245 /*
3246  * dpy_ttitle
3247  *	Update the track title display region of the main window.
3248  *
3249  * Args:
3250  *	s - Pointer to the curstat_t structure.
3251  *
3252  * Return:
3253  *	Nothing
3254  */
3255 void
dpy_ttitle(curstat_t * s)3256 dpy_ttitle(curstat_t *s)
3257 {
3258 	XmString	xs;
3259 	char		str[TITLEIND_LEN];
3260 	static char	prev[TITLEIND_LEN];
3261 
3262 	if (chgdelay)
3263 		str[0] = '\0';
3264 	else {
3265 		(void) strncpy(str, dbprog_curttitle(s), TITLEIND_LEN - 1);
3266 		str[TITLEIND_LEN - 1] = '\0';
3267 	}
3268 
3269 	if (strcmp(str, prev) == 0)
3270 		/* No change: just return */
3271 		return;
3272 
3273 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, TRUE);
3274 
3275 	XtVaSetValues(
3276 		widgets.main.ttitle_ind,
3277 		XmNlabelString, xs,
3278 		NULL
3279 	);
3280 
3281 	XmStringFree(xs);
3282 
3283 	(void) strcpy(prev, str);
3284 }
3285 
3286 
3287 /*
3288  * dpy_rptcnt
3289  *	Update the repeat count indicator of the main window.
3290  *
3291  * Args:
3292  *	s - Pointer to the curstat_t structure.
3293  *
3294  * Return:
3295  *	Nothing
3296  */
3297 void
dpy_rptcnt(curstat_t * s)3298 dpy_rptcnt(curstat_t *s)
3299 {
3300 	XmString		xs;
3301 	char			str[16];
3302 	static char		prevstr[16];
3303 
3304 	if (s->repeat && (s->mode == MOD_PLAY || s->mode == MOD_PAUSE))
3305 		(void) sprintf(str, "%u", s->rptcnt);
3306 	else
3307 		(void) strcpy(str, "-");
3308 
3309 	if (strcmp(str, prevstr) == 0)
3310 		/* No change */
3311 		return;
3312 
3313 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
3314 
3315 	XtVaSetValues(
3316 		widgets.main.rptcnt_ind,
3317 		XmNlabelString, xs,
3318 		NULL
3319 	);
3320 
3321 	XmStringFree(xs);
3322 
3323 	(void) strcpy(prevstr, str);
3324 }
3325 
3326 
3327 /*
3328  * dpy_dbmode
3329  *	Update the cdinfo indicator of the main window.
3330  *
3331  * Args:
3332  *	s - Pointer to the curstat_t structure.
3333  *	blank - Whether the indicator should be blanked
3334  *
3335  * Return:
3336  *	Nothing
3337  */
3338 void
dpy_dbmode(curstat_t * s,bool_t blank)3339 dpy_dbmode(curstat_t *s, bool_t blank)
3340 {
3341 	cdinfo_incore_t	*dbp;
3342 	char		*str;
3343 	XmString	xs;
3344 	static char	prev[16] = { 'j', 'u', 'n', 'k', '\0' };
3345 
3346 	dbp = dbprog_curdb(s);
3347 
3348 	if (blank)
3349 		str = "";
3350 	else {
3351 		switch (s->qmode) {
3352 		case QMODE_MATCH:
3353 			if (dbp->flags & CDINFO_FROMLOC)
3354 				str = app_data.str_local;
3355 			else if (dbp->flags & CDINFO_FROMCDT)
3356 				str = app_data.str_cdtext;
3357 			else
3358 				str = app_data.str_cddb;
3359 			break;
3360 		case QMODE_WAIT:
3361 			str = app_data.str_query;
3362 			break;
3363 		case QMODE_ERR:
3364 			str = app_data.str_error;
3365 			break;
3366 		case QMODE_NONE:
3367 		default:
3368 			str = "";
3369 			break;
3370 		}
3371 	}
3372 
3373 	if (strcmp(prev, str) == 0)
3374 		/* No change: just return */
3375 		return;
3376 
3377 	if (str == app_data.str_cddb) {
3378 		char		cddb_str[8];
3379 		XmString	xs1,
3380 				xs2;
3381 
3382 		(void) strcpy(cddb_str, "CDDB");
3383 		if (cdinfo_cddb_ver() == 2) {
3384 			xs1 = create_xmstring(cddb_str, NULL, CHSET1, FALSE);
3385 			cddb_str[0] = (char) '\262';	/* like <SUP>2</SUP> */
3386 			cddb_str[1] = '\0';
3387 			xs2 = create_xmstring(cddb_str, NULL, CHSET2, FALSE);
3388 			xs = XmStringConcat(xs1, xs2);
3389 			XmStringFree(xs1);
3390 			XmStringFree(xs2);
3391 		}
3392 		else
3393 			xs = create_xmstring(cddb_str, NULL, CHSET1, FALSE);
3394 	}
3395 	else if (str == app_data.str_cdtext)
3396 		xs = create_xmstring(str, NULL, CHSET1, FALSE);
3397 	else
3398 		xs = create_xmstring(str, NULL, CHSET2, FALSE);
3399 
3400 	XtVaSetValues(
3401 		widgets.main.dbmode_ind,
3402 		XmNlabelString, xs,
3403 		NULL
3404 	);
3405 
3406 	XmStringFree(xs);
3407 
3408 	(void) strncpy(prev, str, sizeof(prev) - 1);
3409 	prev[sizeof(prev) - 1] = '\0';
3410 
3411 	if (s->qmode == QMODE_WAIT)
3412 		cd_dbmode_blink(s, TRUE);
3413 	else
3414 		cd_dbmode_blink(s, FALSE);
3415 }
3416 
3417 
3418 /*
3419  * dpy_progmode
3420  *	Update the prog indicator of the main window.
3421  *
3422  * Args:
3423  *	s - Pointer to the curstat_t structure.
3424  *	blank - Whether the indicator should be blanked
3425  *
3426  * Return:
3427  *	Nothing
3428  */
3429 void
dpy_progmode(curstat_t * s,bool_t blank)3430 dpy_progmode(curstat_t *s, bool_t blank)
3431 {
3432 	XmString	xs;
3433 	char		*str;
3434 	bool_t		state;
3435 	static char	prev[16] = { 'j', 'u', 'n', 'k', '\0' };
3436 
3437 	state = (bool_t) (s->program && !s->onetrk_prog && !s->shuffle);
3438 
3439 	if (blank)
3440 		str = "";
3441 	else {
3442 		if (s->segplay == SEGP_A)
3443 			str = "a->?";
3444 		else if (s->segplay == SEGP_AB)
3445 			str = "a->b";
3446 		else if (state)
3447 			str = app_data.str_progmode;
3448 		else
3449 			str = "";
3450 	}
3451 
3452 	if (strcmp(prev, str) == 0)
3453 		/* No change: just return */
3454 		return;
3455 
3456 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
3457 
3458 	XtVaSetValues(
3459 		widgets.main.progmode_ind,
3460 		XmNlabelString, xs,
3461 		NULL
3462 	);
3463 
3464 	XmStringFree(xs);
3465 
3466 	(void) strncpy(prev, str, sizeof(prev) - 1);
3467 	prev[sizeof(prev) - 1] = '\0';
3468 
3469 	if (s->segplay == SEGP_NONE && strcmp(prev, "a->b") == 0)
3470 		/* Cancel a->b mode in segments window */
3471 		dbprog_segments_cancel(s);
3472 
3473 	/* Set segments window set/clear button sensitivity */
3474 	dbprog_segments_setmode(s);
3475 
3476 	if (s->segplay == SEGP_A)
3477 		cd_ab_blink(s, TRUE);
3478 	else
3479 		cd_ab_blink(s, FALSE);
3480 
3481 }
3482 
3483 
3484 /*
3485  * dpy_timemode
3486  *	Update the time mode indicator of the main window.
3487  *
3488  * Args:
3489  *	s - Pointer to the curstat_t structure.
3490  *
3491  * Return:
3492  *	Nothing
3493  */
3494 void
dpy_timemode(curstat_t * s)3495 dpy_timemode(curstat_t *s)
3496 {
3497 	String		str;
3498 	XmString	xs;
3499 	static byte_t	prev = 0xff;
3500 
3501 	if (prev == s->time_dpy)
3502 		/* No change: just return */
3503 		return;
3504 
3505 	switch (s->time_dpy) {
3506 	case T_ELAPSED_TRACK:
3507 		str = app_data.str_elapse;
3508 		break;
3509 
3510 	case T_ELAPSED_SEG:
3511 		str = app_data.str_elapseseg;
3512 		break;
3513 
3514 	case T_ELAPSED_DISC:
3515 		str = app_data.str_elapsedisc;
3516 		break;
3517 
3518 	case T_REMAIN_TRACK:
3519 		str = app_data.str_remaintrk;
3520 		break;
3521 
3522 	case T_REMAIN_SEG:
3523 		str = app_data.str_remainseg;
3524 		break;
3525 
3526 	case T_REMAIN_DISC:
3527 		str = app_data.str_remaindisc;
3528 		break;
3529 	default:
3530 		/* Invalid mode */
3531 		return;
3532 	}
3533 
3534 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
3535 
3536 	XtVaSetValues(
3537 		widgets.main.timemode_ind,
3538 		XmNlabelString, xs,
3539 		NULL
3540 	);
3541 
3542 	XmStringFree(xs);
3543 
3544 	prev = s->time_dpy;
3545 }
3546 
3547 
3548 /*
3549  * dpy_playmode
3550  *	Update the play mode indicator of the main window.
3551  *
3552  * Args:
3553  *	s - Pointer to the curstat_t structure.
3554  *	blank - Whether the indicator should be blanked
3555  *
3556  * Return:
3557  *	Nothing
3558  */
3559 void
dpy_playmode(curstat_t * s,bool_t blank)3560 dpy_playmode(curstat_t *s, bool_t blank)
3561 {
3562 	char		*str;
3563 	XmString	xs;
3564 	bool_t		expbtn_sens = FALSE;
3565 	static char	prev[64] = { 'j', 'u', 'n', 'k', '\0' };
3566 
3567 	if (blank)
3568 		str = "";
3569 	else {
3570 		switch (s->mode) {
3571 		case MOD_PLAY:
3572 			str = app_data.str_play;
3573 			if ((app_data.play_mode & PLAYMODE_FILE) != 0)
3574 				expbtn_sens = TRUE;
3575 			break;
3576 		case MOD_PAUSE:
3577 			str = app_data.str_pause;
3578 			if ((app_data.play_mode & PLAYMODE_FILE) != 0)
3579 				expbtn_sens = TRUE;
3580 			break;
3581 		case MOD_SAMPLE:
3582 			str = app_data.str_sample;
3583 			if ((app_data.play_mode & PLAYMODE_FILE) != 0)
3584 				expbtn_sens = TRUE;
3585 			break;
3586 		case MOD_STOP:
3587 			str = app_data.str_ready;
3588 			break;
3589 		default:
3590 			str = "";
3591 			break;
3592 		}
3593 	}
3594 
3595 	if (strcmp(prev, str) == 0)
3596 		/* No change: just return */
3597 		return;
3598 
3599 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
3600 
3601 	XtVaSetValues(
3602 		widgets.main.playmode_ind,
3603 		XmNlabelString, xs,
3604 		NULL
3605 	);
3606 
3607 	XmStringFree(xs);
3608 
3609 	(void) strncpy(prev, str, sizeof(prev) - 1);
3610 	prev[sizeof(prev) - 1] = '\0';
3611 
3612 	/* Set segments window set button sensitivity */
3613 	dbprog_segments_setmode(s);
3614 
3615 	/* Set CDDA file path expand button sensitivity */
3616 	XtSetSensitive(
3617 		widgets.options.mode_pathexp_btn,
3618 		(Boolean) expbtn_sens
3619 	);
3620 
3621 	/* Pop down CDDA file path expand dialog if appropriate */
3622 	if (pathexp_active && !expbtn_sens)
3623 		cd_info2_popdown(NULL);
3624 }
3625 
3626 
3627 /*
3628  * dpy_all
3629  *	Update all indicator of the main window.
3630  *
3631  * Args:
3632  *	s - Pointer to the curstat_t structure.
3633  *
3634  * Return:
3635  *	Nothing
3636  */
3637 void
dpy_all(curstat_t * s)3638 dpy_all(curstat_t *s)
3639 {
3640 	dpy_disc(s);
3641 	dpy_track(s);
3642 	dpy_index(s);
3643 	dpy_time(s, FALSE);
3644 	dpy_dtitle(s);
3645 	dpy_rptcnt(s);
3646 	dpy_dbmode(s, FALSE);
3647 	dpy_progmode(s, FALSE);
3648 	dpy_timemode(s);
3649 	dpy_playmode(s, FALSE);
3650 }
3651 
3652 
3653 /*
3654  * set_lock_btn
3655  *	Set the lock button state
3656  *
3657  * Args:
3658  *	state - TRUE=in, FALSE=out
3659  *
3660  * Return:
3661  *	Nothing.
3662  */
3663 void
set_lock_btn(bool_t state)3664 set_lock_btn(bool_t state)
3665 {
3666 	XmToggleButtonSetState(
3667 		widgets.main.lock_btn, (Boolean) state, False
3668 	);
3669 }
3670 
3671 
3672 /*
3673  * set_repeat_btn
3674  *	Set the repeat button state
3675  *
3676  * Args:
3677  *	state - TRUE=in, FALSE=out
3678  *
3679  * Return:
3680  *	Nothing.
3681  */
3682 void
set_repeat_btn(bool_t state)3683 set_repeat_btn(bool_t state)
3684 {
3685 	XmToggleButtonSetState(
3686 		widgets.main.repeat_btn, (Boolean) state, False
3687 	);
3688 }
3689 
3690 
3691 /*
3692  * set_shuffle_btn
3693  *	Set the shuffle button state
3694  *
3695  * Args:
3696  *	state - TRUE=in, FALSE=out
3697  *
3698  * Return:
3699  *	Nothing.
3700  */
3701 void
set_shuffle_btn(bool_t state)3702 set_shuffle_btn(bool_t state)
3703 {
3704 	XmToggleButtonSetState(
3705 		widgets.main.shuffle_btn, (Boolean) state, False
3706 	);
3707 }
3708 
3709 
3710 /*
3711  * set_vol_slider
3712  *	Set the volume control slider position
3713  *
3714  * Args:
3715  *	val - The value setting.
3716  *
3717  * Return:
3718  *	Nothing.
3719  */
3720 void
set_vol_slider(int val)3721 set_vol_slider(int val)
3722 {
3723 	/* Check bounds */
3724 	if (val > 100)
3725 		val = 100;
3726 	if (val < 0)
3727 		val = 0;
3728 
3729 	XmScaleSetValue(widgets.main.level_scale, val);
3730 }
3731 
3732 
3733 /*
3734  * set_warp_slider
3735  *	Set the track warp slider position
3736  *
3737  * Args:
3738  *	val - The value setting.
3739  *	autoupd - This is an auto-update.
3740  *
3741  * Return:
3742  *	Nothing.
3743  */
3744 void
set_warp_slider(int val,bool_t autoupd)3745 set_warp_slider(int val, bool_t autoupd)
3746 {
3747 	if (autoupd && (keypad_mode != KPMODE_TRACK || keystr[0] != '\0')) {
3748 		/* User using keypad: no updates */
3749 		return;
3750 	}
3751 
3752 	/* Check bounds */
3753 	if (val > 255)
3754 		val = 255;
3755 	if (val < 0)
3756 		val = 0;
3757 
3758 	pseudo_warp = TRUE;
3759 	XmScaleSetValue(widgets.keypad.warp_scale, val);
3760 	pseudo_warp = FALSE;
3761 }
3762 
3763 
3764 /*
3765  * set_bal_slider
3766  *	Set the balance control slider position
3767  *
3768  * Args:
3769  *	val - The value setting.
3770  *
3771  * Return:
3772  *	Nothing.
3773  */
3774 void
set_bal_slider(int val)3775 set_bal_slider(int val)
3776 {
3777 	/* Check bounds */
3778 	if (val > 50)
3779 		val = 50;
3780 	if (val < -50)
3781 		val = -50;
3782 
3783 	XmScaleSetValue(widgets.options.bal_scale, val);
3784 }
3785 
3786 
3787 /*
3788  * set_qualval_slider
3789  *	Set the VBR quality factor slider position
3790  *
3791  * Args:
3792  *	val - The value setting.
3793  *
3794  * Return:
3795  *	Nothing.
3796  */
3797 void
set_qualval_slider(int val)3798 set_qualval_slider(int val)
3799 {
3800 	XmString	xs,
3801 			xs2;
3802 	char		str[16];
3803 
3804 	/* Check bounds */
3805 	if (val > 10)
3806 		val = 10;
3807 	if (val < 1)
3808 		val = 1;
3809 
3810 	(void) sprintf(str, ": %d", val);
3811 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
3812 	xs2 = XmStringConcat(xs_qual, xs);
3813 	XtVaSetValues(widgets.options.qualval_lbl,
3814 		XmNlabelString, xs2,
3815 		NULL
3816 	);
3817 	XmStringFree(xs2);
3818 	XmStringFree(xs);
3819 
3820 	XmScaleSetValue(widgets.options.qualval_scl, val);
3821 }
3822 
3823 
3824 /*
3825  * set_algo_slider
3826  *	Set the compression algorithm slider position
3827  *
3828  * Args:
3829  *	val - The value setting.
3830  *
3831  * Return:
3832  *	Nothing.
3833  */
3834 void
set_algo_slider(int val)3835 set_algo_slider(int val)
3836 {
3837 	XmString	xs,
3838 			xs2;
3839 	char		str[16];
3840 
3841 	/* Check bounds */
3842 	if (val > 10)
3843 		val = 10;
3844 	if (val < 1)
3845 		val = 1;
3846 
3847 	(void) sprintf(str, ": %d", val);
3848 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
3849 	xs2 = XmStringConcat(xs_algo, xs);
3850 	XtVaSetValues(widgets.options.compalgo_lbl,
3851 		XmNlabelString, xs2,
3852 		NULL
3853 	);
3854 	XmStringFree(xs2);
3855 	XmStringFree(xs);
3856 
3857 	XmScaleSetValue(widgets.options.compalgo_scl, val);
3858 }
3859 
3860 
3861 /*
3862  * set_att_slider
3863  *	Set the CDDA attenuator slider position
3864  *
3865  * Args:
3866  *	val - The value setting.
3867  *
3868  * Return:
3869  *	Nothing.
3870  */
3871 void
set_att_slider(int val)3872 set_att_slider(int val)
3873 {
3874 	/* Check bounds */
3875 	if (val > 100)
3876 		val = 100;
3877 	if (val < 0)
3878 		val = 0;
3879 
3880 	XmScaleSetValue(widgets.options.cdda_att_scale, val);
3881 }
3882 
3883 
3884 /*
3885  * set_text_string
3886  *	Set the text widget string.
3887  *
3888  * Args:
3889  *	w    - The text widget
3890  *	str  - The input text string
3891  *	utf8 - If TRUE, the input string is UTF-8 encoded and will be
3892  *	       converted to a local charset before setting the widget
3893  *
3894  * Return:
3895  *	Nothing.
3896  */
3897 void
set_text_string(Widget w,char * str,bool_t utf8)3898 set_text_string(Widget w, char *str, bool_t utf8)
3899 {
3900 	chset_conv_t	*up;
3901 	char		*a = NULL;
3902 
3903 	if (w == (Widget) NULL)
3904 		return;
3905 
3906 	if (str == NULL || *str == '\0' || !utf8) {
3907 		XmTextSetString(w, str);
3908 		return;
3909 	}
3910 
3911 	/* Convert from UTF-8 encoding */
3912 	if ((up = util_chset_open(CHSET_UTF8_TO_LOCALE)) == NULL)
3913 		return;
3914 
3915 	/* We set the roe flag of the util_chset_conv() function to
3916 	 * TRUE here because we don't want to cause a failed chset
3917 	 * conversion to corrupt the string, in case the user submits
3918 	 * the entry back to CDDB without editing these fields.
3919 	 * This also avoids a bogus CDINFO_CHANGED flag from being set.
3920 	 */
3921 	if (!util_chset_conv(up, str, &a, TRUE)) {
3922 		util_chset_close(up);
3923 		return;
3924 	}
3925 
3926 	util_chset_close(up);
3927 
3928 	if (a != NULL) {
3929 		XmTextSetString(w, a);
3930 		MEM_FREE(a);
3931 	}
3932 }
3933 
3934 
3935 /*
3936  * get_text_string
3937  *	Get the text widget string.
3938  *
3939  * Args:
3940  *	w    - The text widget
3941  *	utf8 - If TRUE, the output string should be UTF-8 encoded and
3942  *	       a conversion from local charset will be made.
3943  *
3944  * Return:
3945  *	The return string.  This is internally allocated and should be
3946  *	freed by the caller via MEM_FREE when done.
3947  */
3948 char *
get_text_string(Widget w,bool_t utf8)3949 get_text_string(Widget w, bool_t utf8)
3950 {
3951 	chset_conv_t	*up;
3952 	char		*str,
3953 			*a = NULL;
3954 
3955 	if ((str = XmTextGetString(w)) == NULL)
3956 		return NULL;
3957 
3958 	if (*str == '\0' || !utf8) {
3959 		if (!util_newstr(&a, str))
3960 			return NULL;
3961 		XtFree(str);
3962 		return (a);
3963 	}
3964 
3965 	/* Convert to UTF-8 encoding */
3966 	if ((up = util_chset_open(CHSET_LOCALE_TO_UTF8)) == NULL) {
3967 		if (!util_newstr(&a, str))
3968 			return NULL;
3969 		XtFree(str);
3970 		return (a);
3971 	}
3972 
3973 	if (!util_chset_conv(up, str, &a, TRUE)) {
3974 		util_chset_close(up);
3975 		if (!util_newstr(&a, str))
3976 			return NULL;
3977 		XtFree(str);
3978 		return (a);
3979 	}
3980 
3981 	util_chset_close(up);
3982 	XtFree(str);
3983 
3984 	return (a);
3985 }
3986 
3987 
3988 /*
3989  * create_xmstring
3990  *	Create a Motif compound string from the input text string.
3991  *
3992  * Args:
3993  *	in_text  - The input UTF-8 text string
3994  *	def_text - The fallback string to use if in_text is NULL or empty
3995  *	tag      - The tag component to be associated with the text
3996  *	utf8     - If TRUE, the input string is UTF-8 encoded and will be
3997  *		   converted to a local charset.
3998  *
3999  * Return:
4000  *	The compound string.  The caller should call XmStringFree on
4001  *	this when done using it.
4002  */
4003 XmString
create_xmstring(char * in_text,char * def_text,char * tag,bool_t utf8)4004 create_xmstring(char *in_text, char *def_text, char *tag, bool_t utf8)
4005 {
4006 	chset_conv_t	*up;
4007 	char		*a = NULL;
4008 	XmString	xs;
4009 
4010 	if (in_text == NULL || *in_text == '\0') {
4011 		if (def_text != NULL)
4012 			xs = XmStringCreateSimple(def_text);
4013 		else
4014 			xs = XmStringCreateSimple("");
4015 
4016 		return (xs);
4017 	}
4018 
4019 	if (!utf8) {
4020 		xs = XmStringCreateLtoR(in_text, tag);
4021 		return (xs);
4022 	}
4023 
4024 	/* Convert from UTF-8 to local charset if possible */
4025 	if ((up = util_chset_open(CHSET_UTF8_TO_LOCALE)) == NULL) {
4026 		xs = XmStringCreateLtoR(in_text, tag);
4027 		return (xs);
4028 	}
4029 	if (!util_chset_conv(up, in_text, &a, FALSE) &&
4030 	    !util_newstr(&a, in_text)) {
4031 		util_chset_close(up);
4032 		xs = XmStringCreateLtoR(in_text, tag);
4033 		return (xs);
4034 	}
4035 	util_chset_close(up);
4036 
4037 	xs = XmStringCreateLtoR(a, tag);
4038 	MEM_FREE(a);
4039 
4040 	return (xs);
4041 }
4042 
4043 
4044 /*
4045  * scale_warp
4046  *	Scale track warp value (0-255) to the number of CD logical audio
4047  *	blocks.
4048  *
4049  * Args:
4050  *	s - Pointer to the curstat_t structure.
4051  *	pos - Track position.
4052  *	val - Warp value.
4053  *
4054  * Return:
4055  *	The number of CD logical audio blocks
4056  */
4057 int
scale_warp(curstat_t * s,int pos,int val)4058 scale_warp(curstat_t *s, int pos, int val)
4059 {
4060 	int		n;
4061 	sword32_t	eot;
4062 
4063 	eot = s->trkinfo[pos+1].addr;
4064 
4065 	/* "Enhanced CD" / "CD Extra" hack */
4066 	if (eot > s->discpos_tot.addr)
4067 		eot = s->discpos_tot.addr;
4068 
4069 	n = val * (eot - s->trkinfo[pos].addr) / 0xff;
4070 	return ((n > 0) ? (n - 1) : n);
4071 }
4072 
4073 
4074 /*
4075  * unscale_warp
4076  *	Scale the number of CD logical audio blocks to track warp
4077  *	value (0-255).
4078  * Args:
4079  *	s - Pointer to the curstat_t structure.
4080  *	pos - Track position.
4081  *	val - Number of logical audio blocks.
4082  *
4083  * Return:
4084  *	The warp value
4085  */
4086 int
unscale_warp(curstat_t * s,int pos,int val)4087 unscale_warp(curstat_t *s, int pos, int val)
4088 {
4089 	sword32_t	eot;
4090 
4091 	eot = s->trkinfo[pos+1].addr;
4092 
4093 	/* "Enhanced CD" / "CD Extra" hack */
4094 	if (eot > s->discpos_tot.addr)
4095 		eot = s->discpos_tot.addr;
4096 
4097 	return (val * 0xff / (eot - s->trkinfo[pos].addr));
4098 }
4099 
4100 
4101 /*
4102  * cd_timeout
4103  *	Alarm clock callback facility
4104  *
4105  * Args:
4106  *	msec - When msec milliseconds has elapsed, the callback
4107  *		occurs.
4108  *	handler - Pointer to the callback function.
4109  *	arg - An argument passed to the callback function.
4110  *
4111  * Return:
4112  *	Timeout ID.
4113  */
4114 long
cd_timeout(word32_t msec,void (* handler)(),byte_t * arg)4115 cd_timeout(word32_t msec, void (*handler)(), byte_t *arg)
4116 {
4117 	/* Note: This code assumes that sizeof(XtIntervalId) <= sizeof(long)
4118 	 * If this is not true then cd_timeout/cd_untimeout will not work
4119 	 * correctly.
4120 	 */
4121 	return ((long)
4122 		XtAppAddTimeOut(
4123 			XtWidgetToApplicationContext(widgets.toplevel),
4124 			(unsigned long) msec,
4125 			(XtTimerCallbackProc) handler,
4126 			(XtPointer) arg
4127 		)
4128 	);
4129 }
4130 
4131 
4132 /*
4133  * cd_untimeout
4134  *	Cancel a pending alarm configured with cd_timeout.
4135  *
4136  * Args:
4137  *	id - The timeout ID
4138  *
4139  * Return:
4140  *	Nothing.
4141  */
4142 void
cd_untimeout(long id)4143 cd_untimeout(long id)
4144 {
4145 	/* Note: This code assumes that sizeof(XtIntervalId) <= sizeof(long)
4146 	 * If this is not true then cd_timeout/cd_untimeout will not work
4147 	 * correctly.
4148 	 */
4149 	XtRemoveTimeOut((XtIntervalId) id);
4150 }
4151 
4152 
4153 /*
4154  * cd_beep
4155  *	Beep the workstation speaker.
4156  *
4157  * Args:
4158  *	Nothing.
4159  *
4160  * Return:
4161  *	Nothing.
4162  */
4163 void
cd_beep(void)4164 cd_beep(void)
4165 {
4166 	XBell(XtDisplay(widgets.toplevel), 50);
4167 }
4168 
4169 
4170 /*
4171  * cd_dialog_setpos
4172  *	Set up a dialog box's position to be near the pointer cursor.
4173  *	This is used before popping up the dialog boxes.
4174  *
4175  * Args:
4176  *	w - The dialog box shell widget to set up
4177  *
4178  * Return:
4179  *	Nothing.
4180  */
4181 void
cd_dialog_setpos(Widget w)4182 cd_dialog_setpos(Widget w)
4183 {
4184 	Display			*display;
4185 	int			screen,
4186 				junk,
4187 				ptr_x,
4188 				ptr_y;
4189 	unsigned int		keybtn;
4190 	Position		win_x,
4191 				win_y;
4192 	Dimension		win_width,
4193 				win_height,
4194 				swidth,
4195 				sheight;
4196 	Window			rootwin,
4197 				childwin;
4198 
4199 	display = XtDisplay(widgets.toplevel);
4200 	screen = DefaultScreen(display);
4201 	swidth = (Dimension) XDisplayWidth(display, screen);
4202 	sheight = (Dimension) XDisplayHeight(display, screen);
4203 
4204 	/* Get current pointer location */
4205 	XQueryPointer(
4206 		display,
4207 		XtWindow(widgets.toplevel),
4208 		&rootwin, &childwin,
4209 		&ptr_x, &ptr_y,
4210 		&junk, &junk,
4211 		&keybtn
4212 	);
4213 
4214 	/* Get dialog window location and dimensions */
4215 	XtVaGetValues(w,
4216 		XmNx, &win_x,
4217 		XmNy, &win_y,
4218 		XmNwidth, &win_width,
4219 		XmNheight, &win_height,
4220 		NULL
4221 	);
4222 
4223 	if (win_width < WIN_MINWIDTH)
4224 		win_width = WIN_MINWIDTH;
4225 	if (win_height < WIN_MINHEIGHT)
4226 		win_height = WIN_MINHEIGHT;
4227 
4228 	if ((Position) ptr_x > (Position) win_x &&
4229 	    (Position) ptr_x < (Position) (win_x + win_width) &&
4230 	    (Position) ptr_y > (Position) win_y &&
4231 	    (Position) ptr_y < (Position) (win_y + win_height)) {
4232 		/* Pointer cursor is already within the dialog window */
4233 		return;
4234 	}
4235 
4236 	/* Set new position for dialog window */
4237 	win_x = (Position) (ptr_x - (win_width / 2));
4238 	win_y = (Position) (ptr_y - (win_height / 2));
4239 
4240 	if (win_x < 0)
4241 		win_x = 0;
4242 	else if ((Position) (win_x + win_width + WM_FUDGE_X) >
4243 		 (Position) swidth)
4244 		win_x = (Position) (swidth - win_width - WM_FUDGE_X);
4245 
4246 	if (win_y < 0)
4247 		win_y = 0;
4248 	else if ((Position) (win_y + win_height + WM_FUDGE_Y) >
4249 		 (Position) sheight)
4250 		win_y = (Position) (sheight - win_height - WM_FUDGE_Y);
4251 
4252 	XtVaSetValues(w, XmNx, win_x, XmNy, win_y, NULL);
4253 }
4254 
4255 
4256 /*
4257  * cd_info_popup
4258  *	Pop up the information message dialog box.
4259  *
4260  * Args:
4261  *	title - The title bar text string.
4262  *	msg - The information message text string.
4263  *
4264  * Return:
4265  *	Nothing.
4266  */
4267 void
cd_info_popup(char * title,char * msg)4268 cd_info_popup(char *title, char *msg)
4269 {
4270 	XmString	xs;
4271 
4272 	if (!popup_ok) {
4273 		(void) fprintf(errfp, "%s %s:\n%s\n", PROGNAME, title, msg);
4274 		return;
4275 	}
4276 
4277 	/* Remove pending popdown timeout, if any */
4278 	if (infodiag_id >= 0) {
4279 		cd_untimeout(infodiag_id);
4280 		infodiag_id = -1;
4281 	}
4282 
4283 	/* Set the dialog box title */
4284 	xs = create_xmstring(title, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
4285 	XtVaSetValues(widgets.dialog.info, XmNdialogTitle, xs, NULL);
4286 	XmStringFree(xs);
4287 
4288 	/* Set the dialog box message */
4289 	xs = create_xmstring(msg, NULL, XmSTRING_DEFAULT_CHARSET, TRUE);
4290 	XtVaSetValues(widgets.dialog.info, XmNmessageString, xs, NULL);
4291 	XmStringFree(xs);
4292 
4293 	if (!XtIsManaged(widgets.dialog.info)) {
4294 		/* Set up dialog box position */
4295 		cd_dialog_setpos(XtParent(widgets.dialog.info));
4296 
4297 		/* Pop up the info dialog */
4298 		XtManageChild(widgets.dialog.info);
4299 	}
4300 	else {
4301 		/* Raise the window to top of stack */
4302 		XRaiseWindow(
4303 			XtDisplay(widgets.dialog.info),
4304 			XtWindow(XtParent(widgets.dialog.info))
4305 		);
4306 	}
4307 }
4308 
4309 
4310 /*
4311  * cd_info_popup_auto
4312  *	Pop up the information message dialog box, which will auto-popdown
4313  *	in 5 seconds.
4314  *
4315  * Args:
4316  *	title - The title bar text string.
4317  *	msg - The information message text string.
4318  *
4319  * Return:
4320  *	Nothing.
4321  */
4322 void
cd_info_popup_auto(char * title,char * msg)4323 cd_info_popup_auto(char *title, char *msg)
4324 {
4325 	cd_info_popup(title, msg);
4326 
4327 	infodiag_id = cd_timeout(
4328 		5000,	/* popup interval */
4329 		cd_info_popdown,
4330 		NULL
4331 	);
4332 }
4333 
4334 
4335 /*
4336  * cd_info_popdown
4337  *	Pop down the information message dialog box.
4338  *
4339  * Args:
4340  *	p - Not used at this time.
4341  *
4342  * Return:
4343  *	Nothing.
4344  */
4345 /*ARGSUSED*/
4346 void
cd_info_popdown(byte_t * p)4347 cd_info_popdown(byte_t *p)
4348 {
4349 	if (XtIsManaged(widgets.dialog.info))
4350 		XtUnmanageChild(widgets.dialog.info);
4351 
4352 	infodiag_id = -1;
4353 }
4354 
4355 
4356 /*
4357  * cd_info2_popup
4358  *	Pop up the information message dialog box.  This dialog box
4359  *	differs from the cd_info_popup in that it contains an extra
4360  *	single line text widget.
4361  *
4362  * Args:
4363  *	title - The title bar text string.
4364  *	msg - The information message text string.
4365  *	textmsg - The information message string to put in the text widget.
4366  *
4367  * Return:
4368  *	Nothing.
4369  */
4370 void
cd_info2_popup(char * title,char * msg,char * textmsg)4371 cd_info2_popup(char *title, char *msg, char *textmsg)
4372 {
4373 	XmString	xs;
4374 
4375 	if (!popup_ok) {
4376 		(void) fprintf(errfp, "%s %s:\n%s\n%s\n",
4377 				PROGNAME, title, msg, textmsg);
4378 		return;
4379 	}
4380 
4381 	/* Set the dialog box title */
4382 	xs = create_xmstring(title, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
4383 	XtVaSetValues(widgets.dialog.info2, XmNdialogTitle, xs, NULL);
4384 	XmStringFree(xs);
4385 
4386 	/* Set the dialog box message */
4387 	xs = create_xmstring(msg, NULL, XmSTRING_DEFAULT_CHARSET, TRUE);
4388 #if XmVersion > 1001
4389 	XtVaSetValues(widgets.dialog.info2, XmNmessageString, xs, NULL);
4390 #else
4391 	XtVaSetValues(widgets.dialog.info2, XmNselectionLabelString, xs, NULL);
4392 #endif
4393 	XmStringFree(xs);
4394 
4395 	/* Set the text field message */
4396 	set_text_string(widgets.dialog.info2_txt, textmsg, TRUE);
4397 
4398 	if (!XtIsManaged(widgets.dialog.info2)) {
4399 		/* Set up dialog box position */
4400 		cd_dialog_setpos(XtParent(widgets.dialog.info2));
4401 
4402 		/* Pop up the info2 dialog */
4403 		XtManageChild(widgets.dialog.info2);
4404 	}
4405 	else {
4406 		/* Raise the window to top of stack */
4407 		XRaiseWindow(
4408 			XtDisplay(widgets.dialog.info2),
4409 			XtWindow(XtParent(widgets.dialog.info2))
4410 		);
4411 	}
4412 }
4413 
4414 
4415 /*
4416  * cd_info2_popdown
4417  *	Pop down the information message dialog box.
4418  *
4419  * Args:
4420  *	p - Not used at this time.
4421  *
4422  * Return:
4423  *	Nothing.
4424  */
4425 /*ARGSUSED*/
4426 void
cd_info2_popdown(byte_t * p)4427 cd_info2_popdown(byte_t *p)
4428 {
4429 	if (XtIsManaged(widgets.dialog.info2))
4430 		XtUnmanageChild(widgets.dialog.info2);
4431 
4432 	pathexp_active = FALSE;
4433 }
4434 
4435 
4436 /*
4437  * cd_warning_popup
4438  *	Pop up the warning message dialog box.
4439  *
4440  * Args:
4441  *	title - The title bar text string.
4442  *	msg - The warning message text string.
4443  *
4444  * Return:
4445  *	Nothing.
4446  */
4447 void
cd_warning_popup(char * title,char * msg)4448 cd_warning_popup(char *title, char *msg)
4449 {
4450 	XmString	xs;
4451 
4452 	if (!popup_ok) {
4453 		(void) fprintf(errfp, "%s %s:\n%s\n", PROGNAME, title, msg);
4454 		return;
4455 	}
4456 
4457 	/* Set the dialog box title */
4458 	xs = create_xmstring(title, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
4459 	XtVaSetValues(widgets.dialog.warning, XmNdialogTitle, xs, NULL);
4460 	XmStringFree(xs);
4461 
4462 	/* Set the dialog box message */
4463 	xs = create_xmstring(msg, NULL, XmSTRING_DEFAULT_CHARSET, TRUE);
4464 	XtVaSetValues(widgets.dialog.warning, XmNmessageString, xs, NULL);
4465 	XmStringFree(xs);
4466 
4467 	if (!XtIsManaged(widgets.dialog.warning)) {
4468 		/* Set up dialog box position */
4469 		cd_dialog_setpos(XtParent(widgets.dialog.warning));
4470 
4471 		/* Pop up the warning dialog */
4472 		XtManageChild(widgets.dialog.warning);
4473 	}
4474 	else {
4475 		/* Raise the window to top of stack */
4476 		XRaiseWindow(
4477 			XtDisplay(widgets.dialog.warning),
4478 			XtWindow(XtParent(widgets.dialog.warning))
4479 		);
4480 	}
4481 }
4482 
4483 
4484 /*
4485  * cd_fatal_popup
4486  *	Pop up the fatal error message dialog box.
4487  *
4488  * Args:
4489  *	title - The title bar text string.
4490  *	msg - The fatal error message text string.
4491  *
4492  * Return:
4493  *	Nothing.
4494  */
4495 void
cd_fatal_popup(char * title,char * msg)4496 cd_fatal_popup(char *title, char *msg)
4497 {
4498 	XmString	xs;
4499 
4500 	if (!popup_ok) {
4501 		(void) fprintf(errfp, "%s %s:\n%s\n", PROGNAME, title, msg);
4502 		exit(1);
4503 	}
4504 
4505 	/* Make sure that the cursor is normal */
4506 	cd_busycurs(FALSE, CURS_ALL);
4507 
4508 	if (!XtIsManaged(widgets.dialog.fatal)) {
4509 		/* Set the dialog box title */
4510 		xs = create_xmstring(
4511 			title, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
4512 		);
4513 		XtVaSetValues(widgets.dialog.fatal, XmNdialogTitle, xs, NULL);
4514 		XmStringFree(xs);
4515 
4516 		/* Set the dialog box message */
4517 		xs = create_xmstring(
4518 			msg, NULL, XmSTRING_DEFAULT_CHARSET, TRUE
4519 		);
4520 		XtVaSetValues(widgets.dialog.fatal, XmNmessageString, xs, NULL);
4521 		XmStringFree(xs);
4522 
4523 		/* Set up dialog box position */
4524 		cd_dialog_setpos(XtParent(widgets.dialog.fatal));
4525 
4526 		/* Pop up the error dialog */
4527 		XtManageChild(widgets.dialog.fatal);
4528 	}
4529 }
4530 
4531 
4532 /*
4533  * cd_confirm_popup
4534  *	Pop up the user-confirmation message dialog box.  Note that this
4535  *	facility is not re-entrant, so that the 'yes' and 'no' pushbutton
4536  *	callback funcs cannot themselves call cd_confirm_popup.  Note that
4537  *	this function does not return to the caller until a user response
4538  *	is received.
4539  *
4540  * Args:
4541  *	title - The title bar text string.
4542  *	msg - The message text string.
4543  *	f_yes - Pointer to the callback function if user selects OK
4544  *	a_yes - Argument passed to f_yes
4545  *	f_no - Pointer to the callback function if user selects Cancel
4546  *	a_no - Argument passed to f_no
4547  *
4548  * Return:
4549  *	FALSE - The user selected 'No' or simply closed the dialog
4550  *	TRUE  - The user selected 'Yes'
4551  */
4552 bool_t
cd_confirm_popup(char * title,char * msg,XtCallbackProc f_yes,XtPointer a_yes,XtCallbackProc f_no,XtPointer a_no)4553 cd_confirm_popup(
4554 	char		*title,
4555 	char		*msg,
4556 	XtCallbackProc	f_yes,
4557 	XtPointer	a_yes,
4558 	XtCallbackProc	f_no,
4559 	XtPointer	a_no
4560 )
4561 {
4562 	Widget		yes_btn,
4563 			no_btn;
4564 	XmString	xs;
4565 	cbinfo_t	*yes_cbinfo,
4566 			*no_cbinfo;
4567 	static bool_t	first = TRUE;
4568 
4569 	if (!popup_ok)
4570 		/* Not allowed */
4571 		return FALSE;
4572 
4573 	if (XtIsManaged(widgets.dialog.confirm)) {
4574 		/* Already popped up, pop it down and clean up first */
4575 		cd_confirm_popdown();
4576 	}
4577 
4578 	/* Set the dialog box title */
4579 	xs = create_xmstring(title, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
4580 	XtVaSetValues(widgets.dialog.confirm, XmNdialogTitle, xs, NULL);
4581 	XmStringFree(xs);
4582 
4583 	/* Set the dialog box message */
4584 	xs = create_xmstring(msg, NULL, XmSTRING_DEFAULT_CHARSET, TRUE);
4585 	XtVaSetValues(widgets.dialog.confirm, XmNmessageString, xs, NULL);
4586 	XmStringFree(xs);
4587 
4588 	/* Add callbacks */
4589 	yes_btn = XmMessageBoxGetChild(
4590 		widgets.dialog.confirm,
4591 		XmDIALOG_OK_BUTTON
4592 	);
4593 	no_btn = XmMessageBoxGetChild(
4594 		widgets.dialog.confirm,
4595 		XmDIALOG_CANCEL_BUTTON
4596 	);
4597 
4598 	if (f_yes != NULL) {
4599 		yes_cbinfo = &cbinfo0;
4600 		yes_cbinfo->widget0 = yes_btn;
4601 		yes_cbinfo->widget1 = no_btn;
4602 		yes_cbinfo->widget2 = (Widget) NULL;
4603 		yes_cbinfo->type = XmNactivateCallback;
4604 		yes_cbinfo->func = f_yes;
4605 		yes_cbinfo->data = a_yes;
4606 
4607 		register_activate_cb(yes_btn, f_yes, a_yes);
4608 		register_activate_cb(yes_btn, cd_rmcallback, yes_cbinfo);
4609 		register_activate_cb(no_btn, cd_rmcallback, yes_cbinfo);
4610 	}
4611 
4612 	if (f_no != NULL) {
4613 		no_cbinfo = &cbinfo1;
4614 		no_cbinfo->widget0 = no_btn;
4615 		no_cbinfo->widget1 = yes_btn;
4616 		no_cbinfo->widget2 = XtParent(widgets.dialog.confirm);
4617 		no_cbinfo->type = XmNactivateCallback;
4618 		no_cbinfo->func = f_no;
4619 		no_cbinfo->data = a_no;
4620 
4621 		register_activate_cb(no_btn, f_no, a_no);
4622 		register_activate_cb(no_btn, cd_rmcallback, no_cbinfo);
4623 		register_activate_cb(yes_btn, cd_rmcallback, no_cbinfo);
4624 
4625 		/* Install WM_DELETE_WINDOW handler */
4626 		add_delw_callback(
4627 			XtParent(widgets.dialog.confirm),
4628 			f_no,
4629 			a_no
4630 		);
4631 	}
4632 
4633 	if (first) {
4634 		first = FALSE;
4635 		register_activate_cb(yes_btn, cd_confirm_resp, (XtPointer) 1);
4636 		register_activate_cb(no_btn, cd_confirm_resp, NULL);
4637 
4638 		/* Install WM_DELETE_WINDOW handler */
4639 		add_delw_callback(
4640 			XtParent(widgets.dialog.confirm),
4641 			cd_confirm_resp,
4642 			NULL
4643 		);
4644 	}
4645 
4646 	/* Set up dialog box position */
4647 	cd_dialog_setpos(XtParent(widgets.dialog.confirm));
4648 
4649 	/* Pop up the confirm dialog */
4650 	XtManageChild(widgets.dialog.confirm);
4651 
4652 	/* Handle events locally until the confirm window is dismissed.
4653 	 * This is to prevent from returning to the calling function
4654 	 * until a user response to this dialog is received.
4655 	 */
4656 	confirm_pend = TRUE;
4657 	do {
4658 		util_delayms(10);
4659 		event_loop(0);
4660 	} while (confirm_pend);
4661 
4662 	return (confirm_resp);
4663 }
4664 
4665 
4666 /*
4667  * cd_confirm_popdown
4668  *	Pop down the confirm dialog box.
4669  *
4670  * Args:
4671  *	None.
4672  *
4673  * Return:
4674  *	Nothing.
4675  */
4676 void
cd_confirm_popdown(void)4677 cd_confirm_popdown(void)
4678 {
4679 	cbinfo_t	*yes_cbinfo = &cbinfo0,
4680 			*no_cbinfo = &cbinfo1;
4681 
4682 	if (!XtIsManaged(widgets.dialog.confirm))
4683 		/* Not popped up */
4684 		return;
4685 
4686 	XtUnmanageChild(widgets.dialog.confirm);
4687 
4688 	cd_rmcallback(yes_cbinfo->widget0,
4689 		      (XtPointer) yes_cbinfo, (XtPointer) NULL);
4690 	cd_rmcallback(yes_cbinfo->widget0,
4691 		      (XtPointer) no_cbinfo, (XtPointer) NULL);
4692 	cd_rmcallback(no_cbinfo->widget1,
4693 		      (XtPointer) yes_cbinfo, (XtPointer) NULL);
4694 	cd_rmcallback(no_cbinfo->widget1,
4695 		      (XtPointer) no_cbinfo, (XtPointer) NULL);
4696 }
4697 
4698 
4699 /*
4700  * cd_working_popup
4701  *	Pop up the work-in-progress message dialog box.  Note that this
4702  *	facility is not re-entrant, so that the 'stop' pushbutton
4703  *	callback func cannot itself call cd_working_popup.
4704  *
4705  * Args:
4706  *	title - The title bar text string.
4707  *	msg - The message text string.
4708  *	f_stop - Pointer to the callback function if user selects Stop
4709  *	a_stop - Argument passed to f_stop
4710  *
4711  * Return:
4712  *	Nothing.
4713  */
4714 void
cd_working_popup(char * title,char * msg,XtCallbackProc f_stop,XtPointer a_stop)4715 cd_working_popup(
4716 	char		*title,
4717 	char		*msg,
4718 	XtCallbackProc	f_stop,
4719 	XtPointer	a_stop
4720 )
4721 {
4722 	Widget		stop_btn;
4723 	XmString	xs;
4724 	cbinfo_t	*stop_cbinfo;
4725 
4726 	if (!popup_ok)
4727 		/* Not allowed */
4728 		return;
4729 
4730 	if (XtIsManaged(widgets.dialog.working)) {
4731 		/* Already popped up: pop it down and clean up first */
4732 		cd_working_popdown();
4733 	}
4734 
4735 	/* Set the dialog box title */
4736 	xs = create_xmstring(title, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
4737 	XtVaSetValues(widgets.dialog.working, XmNdialogTitle, xs, NULL);
4738 	XmStringFree(xs);
4739 
4740 	/* Set the dialog box message */
4741 	xs = create_xmstring(msg, NULL, XmSTRING_DEFAULT_CHARSET, TRUE);
4742 	XtVaSetValues(widgets.dialog.working, XmNmessageString, xs, NULL);
4743 	XmStringFree(xs);
4744 
4745 	/* Add callbacks */
4746 	stop_btn = XmMessageBoxGetChild(
4747 		widgets.dialog.working,
4748 		XmDIALOG_CANCEL_BUTTON
4749 	);
4750 
4751 	if (f_stop != NULL) {
4752 		stop_cbinfo = &cbinfo2;
4753 		stop_cbinfo->widget0 = stop_btn;
4754 		stop_cbinfo->widget1 = (Widget) NULL;
4755 		stop_cbinfo->widget2 = XtParent(widgets.dialog.working);
4756 		stop_cbinfo->type = XmNactivateCallback;
4757 		stop_cbinfo->func = f_stop;
4758 		stop_cbinfo->data = a_stop;
4759 
4760 		register_activate_cb(stop_btn, f_stop, a_stop);
4761 		register_activate_cb(stop_btn, cd_rmcallback, stop_cbinfo);
4762 
4763 		/* Install WM_DELETE_WINDOW handler */
4764 		add_delw_callback(
4765 			XtParent(widgets.dialog.working),
4766 			f_stop,
4767 			a_stop
4768 		);
4769 	}
4770 
4771 	/* Set up dialog box position */
4772 	cd_dialog_setpos(XtParent(widgets.dialog.working));
4773 
4774 	/* Pop up the working dialog */
4775 	XtManageChild(widgets.dialog.working);
4776 }
4777 
4778 
4779 /*
4780  * cd_working_popdown
4781  *	Pop down the working message dialog box.
4782  *
4783  * Args:
4784  *	None.
4785  *
4786  * Return:
4787  *	Nothing.
4788  */
4789 void
cd_working_popdown(void)4790 cd_working_popdown(void)
4791 {
4792 	cbinfo_t	*p = &cbinfo2;
4793 
4794 	if (!XtIsManaged(widgets.dialog.working))
4795 		/* Not popped up */
4796 		return;
4797 
4798 	XtUnmanageChild(widgets.dialog.working);
4799 	cd_rmcallback(p->widget0, (XtPointer) p, (XtPointer) NULL);
4800 }
4801 
4802 
4803 /*
4804  * cd_init
4805  *	Top level function that initializes all subsystems.  Used on
4806  *	program startup.
4807  *
4808  * Args:
4809  *	s - Pointer to the curstat_t structure.
4810  *
4811  * Return:
4812  *	Nothing.
4813  */
4814 void
cd_init(curstat_t * s)4815 cd_init(curstat_t *s)
4816 {
4817 	int		i,
4818 			j;
4819 	char		*cp,
4820 			*bdevname,
4821 			*hd;
4822 	char		version[STR_BUF_SZ / 2],
4823 			titlestr[STR_BUF_SZ * 2],
4824 			path[FILE_PATH_SZ * 2];
4825 	struct utsname	*up;
4826 
4827 	(void) sprintf(version, "%s.%s", VERSION_MAJ, VERSION_MIN);
4828 	DBGPRN(DBG_ALL)(errfp, "XMCD %s.%s DEBUG MODE\n",
4829 			version, VERSION_TEENY);
4830 
4831 	/* app-defaults file check */
4832 	if (app_data.version == NULL ||
4833 	    strncmp(version, app_data.version, strlen(version)) != 0) {
4834 		CD_FATAL(app_data.str_appdef);
4835 		return;
4836 	}
4837 
4838 	if ((cp = (char *) getenv("XMCD_LIBDIR")) == NULL) {
4839 		/* No library directory specified */
4840 		if (di_isdemo()) {
4841 			/* Demo mode: just fake it */
4842 			app_data.libdir = CUR_DIR;
4843 		}
4844 		else {
4845 			/* Real application: this is a fatal error */
4846 			CD_FATAL(app_data.str_libdirerr);
4847 			return;
4848 		}
4849 	}
4850 	else if (!util_newstr(&app_data.libdir, cp)) {
4851 		CD_FATAL(app_data.str_nomemory);
4852 		return;
4853 	}
4854 
4855 	/* Paranoia: avoid overflowing buffers */
4856 	if ((int) strlen(app_data.libdir) >= FILE_PATH_SZ) {
4857 		CD_FATAL(app_data.str_longpatherr);
4858 		return;
4859 	}
4860 	hd = util_homedir(util_get_ouid());
4861 	if ((int) strlen(hd) >= FILE_PATH_SZ) {
4862 		CD_FATAL(app_data.str_longpatherr);
4863 		return;
4864 	}
4865 
4866 	/* Set some defaults */
4867 	app_data.cdinfo_maxhist = 100;
4868 	app_data.aux = (void *) s;
4869 
4870 	/* Get system common configuration parameters */
4871 	(void) sprintf(path, SYS_CMCFG_PATH, app_data.libdir);
4872 	di_common_parmload(path, TRUE, FALSE);
4873 
4874 	/* Get user common configuration parameters */
4875 	(void) sprintf(path, USR_CMCFG_PATH, hd);
4876 	di_common_parmload(path, FALSE, FALSE);
4877 
4878 	/* Paranoia: avoid overflowing buffers */
4879 	if (app_data.device != NULL &&
4880 	    ((int) strlen(app_data.device) >= FILE_PATH_SZ)) {
4881 		CD_FATAL(app_data.str_longpatherr);
4882 		return;
4883 	}
4884 
4885 #ifdef __VMS
4886 	bdevname = NULL;
4887 	if (!util_newstr(&bdevname, "device.cfg")) {
4888 		CD_FATAL(app_data.str_nomemory);
4889 		return;
4890 	}
4891 #else
4892 	if ((bdevname = util_basename(app_data.device)) == NULL) {
4893 		CD_FATAL("Device path basename error: Aborting.");
4894 		return;
4895 	}
4896 	if ((int) strlen(bdevname) >= FILE_BASE_SZ) {
4897 		MEM_FREE(bdevname);
4898 		CD_FATAL(app_data.str_longpatherr);
4899 		return;
4900 	}
4901 #endif
4902 
4903 	/* Initialize CD Information services */
4904 	(void) strcpy(cdinfo_cldata.prog, PROGNAME);
4905 	(void) strcpy(cdinfo_cldata.user, util_loginname());
4906 	cdinfo_cldata.isdemo = di_isdemo;
4907 	cdinfo_cldata.curstat_addr = curstat_addr;
4908 	cdinfo_cldata.fatal_msg = cd_fatal_popup;
4909 	cdinfo_cldata.warning_msg = cd_warning_popup;
4910 	cdinfo_cldata.info_msg = cd_info_popup;
4911 	cdinfo_cldata.info2_msg = cd_info2_popup;
4912 	cdinfo_cldata.workproc = event_loop;
4913 	cdinfo_cldata.arg = 0;
4914 	cdinfo_init(&cdinfo_cldata);
4915 
4916 
4917 	/* Get system-wide device-specific configuration parameters */
4918 	(void) sprintf(path, SYS_DSCFG_PATH, app_data.libdir, bdevname);
4919 	di_devspec_parmload(path, TRUE, FALSE);
4920 
4921 	/* Get user device-specific configuration parameters */
4922 	(void) sprintf(path, USR_DSCFG_PATH, hd, bdevname);
4923 	di_devspec_parmload(path, FALSE, FALSE);
4924 
4925 	MEM_FREE(bdevname);
4926 
4927 	/* Make some program directories if needed */
4928 	cd_mkdirs();
4929 
4930 	/* Initialize help system */
4931 	help_init();
4932 
4933 	/* Initialize the CD Information/program subsystem */
4934 	dbprog_init(s);
4935 
4936 	/* Initialize the wwwWarp subsystem */
4937 	wwwwarp_init(s);
4938 
4939 	/* Initialize the CD interface subsystem */
4940 	di_cldata.curstat_addr = curstat_addr;
4941 	di_cldata.quit = cd_quit;
4942 	di_cldata.timeout = cd_timeout;
4943 	di_cldata.untimeout = cd_untimeout;
4944 	di_cldata.dbclear = dbprog_dbclear;
4945 	di_cldata.dbget = dbprog_dbget;
4946 	di_cldata.progclear = dbprog_progclear;
4947 	di_cldata.progget = dbprog_progget;
4948 	di_cldata.chgr_scan_stop = dbprog_chgr_scan_stop;
4949 	di_cldata.mkoutpath = cd_mkoutpath;
4950 	di_cldata.ckpipeprog = cd_ckpipeprog;
4951 	di_cldata.fatal_msg = cd_fatal_popup;
4952 	di_cldata.warning_msg = cd_warning_popup;
4953 	di_cldata.info_msg = cd_info_popup;
4954 	di_cldata.info2_msg = cd_info2_popup;
4955 	di_cldata.beep = cd_beep;
4956 	di_cldata.set_lock_btn = set_lock_btn;
4957 	di_cldata.set_shuffle_btn = set_shuffle_btn;
4958 	di_cldata.set_vol_slider = set_vol_slider;
4959 	di_cldata.set_bal_slider = set_bal_slider;
4960 	di_cldata.dpy_all = dpy_all;
4961 	di_cldata.dpy_disc = dpy_disc;
4962 	di_cldata.dpy_track = dpy_track;
4963 	di_cldata.dpy_index = dpy_index;
4964 	di_cldata.dpy_time = dpy_time;
4965 	di_cldata.dpy_progmode = dpy_progmode;
4966 	di_cldata.dpy_playmode = dpy_playmode;
4967 	di_cldata.dpy_rptcnt = dpy_rptcnt;
4968 	di_init(&di_cldata);
4969 
4970 	/* Set default modes */
4971 	di_repeat(s, app_data.repeat_mode);
4972 	set_repeat_btn(s->repeat);
4973 	di_shuffle(s, app_data.shuffle_mode);
4974 	set_shuffle_btn(s->shuffle);
4975 	keypad_mode = KPMODE_TRACK;
4976 	cdda_fader_mode = CDDA_FADER_NONE;
4977 
4978 	/* Set the sensitivity of play mode buttons according to
4979 	 * configuration capabilities
4980 	 */
4981 	if ((di_cldata.capab & CAPAB_RDCDDA) == 0) {
4982 		XtSetSensitive(widgets.options.mode_cdda_btn, False);
4983 		XtSetSensitive(widgets.options.mode_file_btn, False);
4984 		XtSetSensitive(widgets.options.mode_pipe_btn, False);
4985 	}
4986 	else {
4987 		if ((di_cldata.capab & CAPAB_WRDEV) == 0)
4988 			XtSetSensitive(widgets.options.mode_cdda_btn, False);
4989 		if ((di_cldata.capab & CAPAB_WRFILE) == 0)
4990 			XtSetSensitive(widgets.options.mode_file_btn, False);
4991 		if ((di_cldata.capab & CAPAB_WRPIPE) == 0)
4992 			XtSetSensitive(widgets.options.mode_pipe_btn, False);
4993 	}
4994 
4995 	/* Play mode and capabilities */
4996 	if ((di_cldata.capab & CAPAB_PLAYAUDIO) == 0) {
4997 		(void) fprintf(errfp,
4998 			       "No CD playback capability.  Aborting.\n");
4999 		cd_quit(s);
5000 		return;
5001 	}
5002 
5003 	/* Set optional file formats menu entries sensitivity */
5004 	if (!cdda_filefmt_supp(FILEFMT_MP3))
5005 		XtSetSensitive(widgets.options.mode_fmt_mp3_btn, False);
5006 	if (!cdda_filefmt_supp(FILEFMT_OGG))
5007 		XtSetSensitive(widgets.options.mode_fmt_ogg_btn, False);
5008 	if (!cdda_filefmt_supp(FILEFMT_FLAC))
5009 		XtSetSensitive(widgets.options.mode_fmt_flac_btn, False);
5010 	if (!cdda_filefmt_supp(FILEFMT_AAC))
5011 		XtSetSensitive(widgets.options.mode_fmt_aac_btn, False);
5012 	if (!cdda_filefmt_supp(FILEFMT_MP4))
5013 		XtSetSensitive(widgets.options.mode_fmt_mp4_btn, False);
5014 
5015 	/* Get label strings */
5016 	XtVaGetValues(widgets.options.qualval_lbl,
5017 		XmNlabelString, &xs_qual,
5018 		NULL
5019 	);
5020 	XtVaGetValues(widgets.options.compalgo_lbl,
5021 		XmNlabelString, &xs_algo,
5022 		NULL
5023 	);
5024 
5025 	/* Create pushbutton widgets in bitrate menu */
5026 	for (j = (cdda_bitrates() - 1); j >= 0; j--) {
5027 		int		k;
5028 		wlist_t		*l,
5029 				**head;
5030 		Widget		w[3],
5031 				pw;
5032 		Arg		arg[5];
5033 		XtCallbackProc	cbfunc;
5034 
5035 		if (cdda_bitrate_val(j) < 32)
5036 			continue;
5037 
5038 		for (k = 0; k < 3; k++) {
5039 			switch (k) {
5040 			case 1:
5041 				pw = widgets.options.minbrate_menu;
5042 				head = &cd_minbrhead;
5043 				cbfunc = cd_min_bitrate;
5044 				break;
5045 			case 2:
5046 				pw = widgets.options.maxbrate_menu;
5047 				head = &cd_maxbrhead;
5048 				cbfunc = cd_max_bitrate;
5049 				break;
5050 			case 0:
5051 			default:
5052 				pw = widgets.options.bitrate_menu;
5053 				head = &cd_brhead;
5054 				cbfunc = cd_bitrate;
5055 				break;
5056 			}
5057 
5058 			i = 0;
5059 			XtSetArg(arg[i], XmNshadowThickness, 2); i++;
5060 			w[k] = XmCreatePushButton(
5061 				pw, cdda_bitrate_name(j), arg, i
5062 			);
5063 
5064 			XtManageChild(w[k]);
5065 
5066 			/* Register callback */
5067 			register_activate_cb(w[k], cbfunc, s);
5068 
5069 			/* Add to list */
5070 			l = (wlist_t *)(void *) MEM_ALLOC(
5071 				"wlist_t", sizeof(wlist_t)
5072 			);
5073 			if (l == NULL) {
5074 				CD_FATAL(app_data.str_nomemory);
5075 				return;
5076 			}
5077 
5078 			l->w = w[k];
5079 			l->next = *head;
5080 			*head = l;
5081 		}
5082 	}
5083 
5084 	s->time_dpy = (byte_t) app_data.timedpy_mode;
5085 
5086 	/* Force CD-TEXT config to False if it doesn't appear in the
5087 	 * CD info path list.
5088 	 */
5089 	if (!cdinfo_cdtext_iscfg())
5090 		app_data.cdtext_dsbl = TRUE;
5091 
5092 	/* Set default options */
5093 	cd_options_reset(widgets.options.reset_btn, (XtPointer) s, NULL);
5094 
5095 	if (!app_data.mselvol_supp) {
5096 		XtSetSensitive(widgets.options.vol_linear_btn, False);
5097 		XtSetSensitive(widgets.options.vol_square_btn, False);
5098 		XtSetSensitive(widgets.options.vol_invsqr_btn, False);
5099 	}
5100 
5101 	if (!app_data.balance_supp)
5102 		XtSetSensitive(widgets.options.bal_scale, False);
5103 
5104 	if (!app_data.chroute_supp) {
5105 		XtSetSensitive(widgets.options.chroute_rev_btn, False);
5106 		XtSetSensitive(widgets.options.chroute_left_btn, False);
5107 		XtSetSensitive(widgets.options.chroute_right_btn, False);
5108 		XtSetSensitive(widgets.options.chroute_mono_btn, False);
5109 	}
5110 
5111 	/* Add options window category list entries */
5112 	for (i = 0; i < OPT_CATEGS; i++) {
5113 		XmString	xs;
5114 		wlist_t		*wl;
5115 		Widget		*warray;
5116 		int		j;
5117 
5118 		switch (i) {
5119 		case 0:
5120 			/* Playback mode */
5121 			opt_categ[i].name = app_data.str_playbackmode;
5122 			warray = &widgets.options.mode_lbl;
5123 			break;
5124 		case 1:
5125 			/* Encoding parameters */
5126 			opt_categ[i].name = app_data.str_encodeparms;
5127 			warray = &widgets.options.method_opt;
5128 			break;
5129 		case 2:
5130 			/* LAME MP3 options */
5131 			opt_categ[i].name = app_data.str_lameopts;
5132 			warray = &widgets.options.lame_lbl;
5133 			break;
5134 		case 3:
5135 			/* CDDA thread schedling parameters */
5136 			opt_categ[i].name = app_data.str_schedparms;
5137 			warray = &widgets.options.sched_lbl;
5138 			break;
5139 		case 4:
5140 			/* Automation */
5141 			opt_categ[i].name = app_data.str_autofuncs;
5142 			warray = &widgets.options.load_lbl;
5143 			break;
5144 		case 5:
5145 			/* CD Changer */
5146 			opt_categ[i].name = app_data.str_cdchanger;
5147 			warray = &widgets.options.chg_lbl;
5148 			break;
5149 		case 6:
5150 			/* Channel routing */
5151 			opt_categ[i].name = app_data.str_chroute;
5152 			warray = &widgets.options.chroute_lbl;
5153 			break;
5154 		case 7:
5155 			/* Volume / Balance */
5156 			opt_categ[i].name = app_data.str_volbal;
5157 			warray = &widgets.options.vol_lbl;
5158 			break;
5159 		case 8:
5160 			/* CDDB / CD-TEXT */
5161 			opt_categ[i].name = app_data.str_cddb_cdtext;
5162 			warray = &widgets.options.cddb_lbl;
5163 			break;
5164 		default:
5165 			opt_categ[i].name = NULL;
5166 			warray = NULL;
5167 			break;
5168 		}
5169 
5170 		xs = create_xmstring(opt_categ[i].name, NULL, CHSET1, FALSE);
5171 		XmListAddItemUnselected(widgets.options.categ_list, xs, i+1);
5172 		XmStringFree(xs);
5173 
5174 		for (j = 0; warray[j] != (Widget) NULL; j++) {
5175 			wl = (wlist_t *)(void *) MEM_ALLOC(
5176 				"wlist_t", sizeof(wlist_t)
5177 			);
5178 			if (wl == NULL) {
5179 				CD_FATAL(app_data.str_nomemory);
5180 				return;
5181 			}
5182 			wl->next = NULL;
5183 			wl->w = warray[j];
5184 
5185 			if (opt_categ[i].widgets == NULL)
5186 				opt_categ[i].widgets = wl;
5187 			else {
5188 				wl->next = opt_categ[i].widgets;
5189 				opt_categ[i].widgets = wl;
5190 			}
5191 		}
5192 	}
5193 	/* Pre-select the first category */
5194 	XmListSelectPos(widgets.options.categ_list, 1, True);
5195 
5196 	/* Set the main window and icon titles */
5197 	up = util_get_uname();
5198 	(void) sprintf(titlestr, "%.32s: %.80s/%d",
5199 		app_data.main_title,
5200 		up->nodename,
5201 		app_data.devnum);
5202 	XtVaSetValues(widgets.toplevel,
5203 		XmNtitle, titlestr,
5204 		XmNiconName, PROGNAME,
5205 		NULL
5206 	);
5207 }
5208 
5209 
5210 /*
5211  * cd_start
5212  *	Secondary startup functions
5213  *
5214  * Args:
5215  *	s - Pointer to the curstat_t structure.
5216  *
5217  * Return:
5218  *	Nothing.
5219  */
5220 void
cd_start(curstat_t * s)5221 cd_start(curstat_t *s)
5222 {
5223 	/* Allow popup dialogs from here on */
5224 	popup_ok = TRUE;
5225 
5226 	/* Start up libutil */
5227 	util_start();
5228 
5229 	/* Start up I/O interface */
5230 	di_start(s);
5231 
5232 	/* Start up help */
5233 	help_start();
5234 }
5235 
5236 
5237 /*
5238  * cd_icon
5239  *	Main window iconification/deiconification handler.
5240  *
5241  * Args:
5242  *	s - Pointer to the curstat_t structure.
5243  *	iconified - Whether the main window is iconified.
5244  *
5245  * Return:
5246  *	Nothing.
5247  */
5248 void
cd_icon(curstat_t * s,bool_t iconified)5249 cd_icon(curstat_t *s, bool_t iconified)
5250 {
5251 	di_icon(s, iconified);
5252 }
5253 
5254 
5255 /*
5256  * cd_halt
5257  *	Top level function to shut down all subsystems.  Used when
5258  *	closing the application.
5259  *
5260  * Args:
5261  *	s - Pointer to the curstat_t structure.
5262  *
5263  * Return:
5264  *	Nothing.
5265  */
5266 void
cd_halt(curstat_t * s)5267 cd_halt(curstat_t *s)
5268 {
5269 	di_halt(s);
5270 	cdinfo_halt(s);
5271 }
5272 
5273 
5274 /*
5275  * cd_quit
5276  *	Close the application.
5277  *
5278  * Args:
5279  *	s - Pointer to the curstat_t structure.
5280  *
5281  * Return:
5282  *	Nothing.
5283  */
5284 void
cd_quit(curstat_t * s)5285 cd_quit(curstat_t *s)
5286 {
5287 	XmAnyCallbackStruct	p;
5288 
5289 	if (XtIsRealized(widgets.toplevel))
5290 		XtUnmapWidget(widgets.toplevel);
5291 
5292 	/* Cancel asynchronous CDDB load operation, if active */
5293 	cdinfo_load_cancel();
5294 
5295 	/* Shut down all xmcd subsystems */
5296 	cd_halt(s);
5297 
5298 	/* Uninstall current keyboard grabs */
5299 	p.reason = XmCR_FOCUS;
5300 	cd_shell_focus_chg(
5301 		widgets.toplevel,
5302 		(XtPointer) widgets.toplevel,
5303 		(XtPointer) &p
5304 	);
5305 
5306 	/* Let X events drain */
5307 	event_loop(0);
5308 
5309 	/* Shut down GUI */
5310 	shutdown_gui();
5311 
5312 	exit(0);
5313 }
5314 
5315 
5316 /*
5317  * cd_busycurs
5318  *	Enable/disable the watch cursor.
5319  *
5320  * Args:
5321  *	busy   - Boolean value indicating whether to enable or disable the
5322  *		 watch cursor.
5323  *	winmap - Bitmap of form widgets in which the cursor should be
5324  *		 affected
5325  *
5326  * Return:
5327  *	Nothing.
5328  */
5329 void
cd_busycurs(bool_t busy,int winmap)5330 cd_busycurs(bool_t busy, int winmap)
5331 {
5332 	Display		*dpy = XtDisplay(widgets.toplevel);
5333 	Window		win;
5334 	static Cursor	wcur = (Cursor) 0;
5335 
5336 	if (wcur == (Cursor) 0)
5337 		wcur = XCreateFontCursor(dpy, XC_watch);
5338 
5339 	if (winmap == 0)
5340 		return;
5341 
5342 	if (busy) {
5343 		if ((winmap & CURS_MAIN) &&
5344 		    (win = XtWindow(widgets.main.form)) != (Window) 0)
5345 			XDefineCursor(dpy, win, wcur);
5346 		if ((winmap & CURS_KEYPAD) &&
5347 		    (win = XtWindow(widgets.keypad.form)) != (Window) 0)
5348 			XDefineCursor(dpy, win, wcur);
5349 		if ((winmap & CURS_OPTIONS) &&
5350 		    (win = XtWindow(widgets.options.form)) != (Window) 0)
5351 			XDefineCursor(dpy, win, wcur);
5352 		if ((winmap & CURS_DBPROG) &&
5353 		    (win = XtWindow(widgets.dbprog.form)) != (Window) 0)
5354 			XDefineCursor(dpy, win, wcur);
5355 		if ((winmap & CURS_DLIST) &&
5356 		    (win = XtWindow(widgets.dlist.form)) != (Window) 0)
5357 			XDefineCursor(dpy, win, wcur);
5358 		if ((winmap & CURS_DBEXTD) &&
5359 		    (win = XtWindow(widgets.dbextd.form)) != (Window) 0)
5360 			XDefineCursor(dpy, win, wcur);
5361 		if ((winmap & CURS_DBEXTT) &&
5362 		    (win = XtWindow(widgets.dbextt.form)) != (Window) 0)
5363 			XDefineCursor(dpy, win, wcur);
5364 		if ((winmap & CURS_FULLNAME) &&
5365 		    (win = XtWindow(widgets.fullname.form)) != (Window) 0)
5366 			XDefineCursor(dpy, win, wcur);
5367 		if ((winmap & CURS_CREDITS) &&
5368 		    (win = XtWindow(widgets.credits.form)) != (Window) 0)
5369 			XDefineCursor(dpy, win, wcur);
5370 		if ((winmap & CURS_SEGMENTS) &&
5371 		    (win = XtWindow(widgets.segments.form)) != (Window) 0)
5372 			XDefineCursor(dpy, win, wcur);
5373 		if ((winmap & CURS_SUBMITURL) &&
5374 		    (win = XtWindow(widgets.submiturl.form)) != (Window) 0)
5375 			XDefineCursor(dpy, win, wcur);
5376 		if ((winmap & CURS_USERREG) &&
5377 		    (win = XtWindow(widgets.userreg.form)) != (Window) 0)
5378 			XDefineCursor(dpy, win, wcur);
5379 		if ((winmap & CURS_HELP) &&
5380 		    (win = XtWindow(widgets.help.form)) != (Window) 0)
5381 			XDefineCursor(dpy, win, wcur);
5382 	}
5383 	else {
5384 		if ((winmap & CURS_MAIN) &&
5385 		    (win = XtWindow(widgets.main.form)) != (Window) 0)
5386 			XUndefineCursor(dpy, win);
5387 		if ((winmap & CURS_KEYPAD) &&
5388 		    (win = XtWindow(widgets.keypad.form)) != (Window) 0)
5389 			XUndefineCursor(dpy, win);
5390 		if ((winmap & CURS_OPTIONS) &&
5391 		    (win = XtWindow(widgets.options.form)) != (Window) 0)
5392 			XUndefineCursor(dpy, win);
5393 		if ((winmap & CURS_DBPROG) &&
5394 		    (win = XtWindow(widgets.dbprog.form)) != (Window) 0)
5395 			XUndefineCursor(dpy, win);
5396 		if ((winmap & CURS_DLIST) &&
5397 		    (win = XtWindow(widgets.dlist.form)) != (Window) 0)
5398 			XUndefineCursor(dpy, win);
5399 		if ((winmap & CURS_DBEXTD) &&
5400 		    (win = XtWindow(widgets.dbextd.form)) != (Window) 0)
5401 			XUndefineCursor(dpy, win);
5402 		if ((winmap & CURS_DBEXTT) &&
5403 		    (win = XtWindow(widgets.dbextt.form)) != (Window) 0)
5404 			XUndefineCursor(dpy, win);
5405 		if ((winmap & CURS_FULLNAME) &&
5406 		    (win = XtWindow(widgets.fullname.form)) != (Window) 0)
5407 			XUndefineCursor(dpy, win);
5408 		if ((winmap & CURS_CREDITS) &&
5409 		    (win = XtWindow(widgets.credits.form)) != (Window) 0)
5410 			XUndefineCursor(dpy, win);
5411 		if ((winmap & CURS_SEGMENTS) &&
5412 		    (win = XtWindow(widgets.segments.form)) != (Window) 0)
5413 			XUndefineCursor(dpy, win);
5414 		if ((winmap & CURS_SUBMITURL) &&
5415 		    (win = XtWindow(widgets.submiturl.form)) != (Window) 0)
5416 			XUndefineCursor(dpy, win);
5417 		if ((winmap & CURS_USERREG) &&
5418 		    (win = XtWindow(widgets.userreg.form)) != (Window) 0)
5419 			XUndefineCursor(dpy, win);
5420 		if ((winmap & CURS_HELP) &&
5421 		    (win = XtWindow(widgets.help.form)) != (Window) 0)
5422 			XUndefineCursor(dpy, win);
5423 	}
5424 	XFlush(dpy);
5425 }
5426 
5427 
5428 /*
5429  * cd_hostname
5430  *	Return the system's host name (with fully qualified domain name
5431  *	if possible).  This function can only be used after the call
5432  *	to cdinfo_init() is completed.
5433  *
5434  * Args:
5435  *	None.
5436  *
5437  * Return:
5438  *	The host name string.
5439  */
5440 char *
cd_hostname(void)5441 cd_hostname(void)
5442 {
5443 	return (cdinfo_cldata.host);
5444 }
5445 
5446 
5447 /*
5448  * onsig
5449  *	Signal handler.  Causes the application to shut down gracefully.
5450  *
5451  * Args:
5452  *	sig - The signal number received.
5453  *
5454  * Return:
5455  *	Nothing.
5456  */
5457 void
onsig(int sig)5458 onsig(int sig)
5459 {
5460 	(void) util_signal(sig, SIG_IGN);
5461 	cd_quit(curstat_addr());
5462 }
5463 
5464 
5465 /**************** vv Callback routines vv ****************/
5466 
5467 /*
5468  * cd_checkbox
5469  *	Main window checkbox callback function
5470  */
5471 /*ARGSUSED*/
5472 void
cd_checkbox(Widget w,XtPointer client_data,XtPointer call_data)5473 cd_checkbox(Widget w, XtPointer client_data, XtPointer call_data)
5474 {
5475 	XmRowColumnCallbackStruct	*p =
5476 		(XmRowColumnCallbackStruct *)(void *) call_data;
5477 	XmToggleButtonCallbackStruct	*q;
5478 	curstat_t			*s =
5479 		(curstat_t *)(void *) client_data;
5480 	char				*str;
5481 	bool_t				paused = FALSE,
5482 					need_restart = FALSE;
5483 	size_t				len = 0;
5484 
5485 	if (p->reason != XmCR_ACTIVATE)
5486 		return;
5487 
5488 	q = (XmToggleButtonCallbackStruct *)(void *) p->callbackstruct;
5489 	if (q == NULL)
5490 		return;
5491 
5492 	str = q->set ? "Enable" : "Disable";
5493 
5494 	DBGPRN(DBG_UI)(errfp, "\n* CHKBOX: ");
5495 
5496 	if (p->widget == widgets.main.lock_btn) {
5497 		DBGPRN(DBG_UI)(errfp, "lock: %s\n", str);
5498 
5499 		di_lock(s, (bool_t) q->set);
5500 	}
5501 	else if (p->widget == widgets.main.repeat_btn) {
5502 		DBGPRN(DBG_UI)(errfp, "repeat: %s\n", str);
5503 
5504 		di_repeat(s, (bool_t) q->set);
5505 	}
5506 	else if (p->widget == widgets.main.shuffle_btn) {
5507 		DBGPRN(DBG_UI)(errfp, "shuffle: %s\n", str);
5508 
5509 		if (!q->set) {
5510 			/* Clear shuffle mode */
5511 			di_shuffle(s, FALSE);
5512 			return;
5513 		}
5514 
5515 		switch (s->mode) {
5516 		case MOD_PAUSE:
5517 			paused = TRUE;
5518 			/*FALLTHROUGH*/
5519 		case MOD_PLAY:
5520 		case MOD_SAMPLE:
5521 			need_restart = TRUE;
5522 
5523 			if (s->program && !s->onetrk_prog) {
5524 				len = strlen(app_data.str_clrprog) +
5525 				      strlen(app_data.str_restartplay) +
5526 				      strlen(app_data.str_askproceed) + 3;
5527 				str = (char *) MEM_ALLOC("msg", len);
5528 				if (str == NULL) {
5529 					CD_FATAL(app_data.str_nomemory);
5530 					return;
5531 				}
5532 				(void) sprintf(str, "%s\n%s\n%s",
5533 					app_data.str_clrprog,
5534 					app_data.str_restartplay,
5535 					app_data.str_askproceed
5536 				);
5537 			}
5538 			else if (s->segplay == SEGP_AB) {
5539 				len = strlen(app_data.str_cancelseg) +
5540 				      strlen(app_data.str_restartplay) +
5541 				      strlen(app_data.str_askproceed) + 3;
5542 				str = (char *) MEM_ALLOC("msg", len);
5543 				if (str == NULL) {
5544 					CD_FATAL(app_data.str_nomemory);
5545 					return;
5546 				}
5547 				(void) sprintf(str, "%s\n%s\n%s",
5548 					app_data.str_cancelseg,
5549 					app_data.str_restartplay,
5550 					app_data.str_askproceed
5551 				);
5552 			}
5553 			else {
5554 				len = strlen(app_data.str_restartplay) +
5555 				      strlen(app_data.str_askproceed) + 2;
5556 				str = (char *) MEM_ALLOC("msg", len);
5557 				if (str == NULL) {
5558 					CD_FATAL(app_data.str_nomemory);
5559 					return;
5560 				}
5561 				(void) sprintf(str, "%s\n%s",
5562 					app_data.str_restartplay,
5563 					app_data.str_askproceed
5564 				);
5565 			}
5566 			break;
5567 
5568 		default:
5569 			if (s->program && !s->onetrk_prog) {
5570 				len = strlen(app_data.str_clrprog) +
5571 				      strlen(app_data.str_askproceed) + 2;
5572 				str = (char *) MEM_ALLOC("msg", len);
5573 				if (str == NULL) {
5574 					CD_FATAL(app_data.str_nomemory);
5575 					return;
5576 				}
5577 				(void) sprintf(str, "%s\n%s",
5578 					app_data.str_clrprog,
5579 					app_data.str_askproceed
5580 				);
5581 			}
5582 			else if (s->segplay == SEGP_AB) {
5583 				len = strlen(app_data.str_cancelseg) +
5584 				      strlen(app_data.str_askproceed) + 2;
5585 				str = (char *) MEM_ALLOC("msg", len);
5586 				if (str == NULL) {
5587 					CD_FATAL(app_data.str_nomemory);
5588 					return;
5589 				}
5590 				(void) sprintf(str, "%s\n%s",
5591 					app_data.str_cancelseg,
5592 					app_data.str_askproceed
5593 				);
5594 			}
5595 			else
5596 				str = NULL;
5597 			break;
5598 		}
5599 
5600 		if (str == NULL ||
5601 		    cd_confirm_popup(app_data.str_confirm,
5602 					str,
5603 					(XtCallbackProc) NULL, NULL,
5604 					(XtCallbackProc) NULL, NULL)) {
5605 
5606 			if (need_restart) {
5607 				/* Stop playback first */
5608 				di_stop(s, FALSE);
5609 			}
5610 
5611 			if (s->program) {
5612 				/* Cancel program mode */
5613 				dbprog_clrpgm(w, client_data, NULL);
5614 			}
5615 
5616 			if (s->segplay != SEGP_NONE) {
5617 				/* Cancel a->b mode */
5618 				s->segplay = SEGP_NONE;
5619 				dpy_progmode(s, FALSE);
5620 			}
5621 
5622 			/* Set shuffle mode */
5623 			di_shuffle(s, TRUE);
5624 
5625 			if (need_restart) {
5626 				if (paused)
5627 					/* Mute sound */
5628 					di_mute_on(s);
5629 
5630 				/* Restart playback */
5631 				s->cur_trk = -1;
5632 				cd_play_pause(w, client_data, NULL);
5633 
5634 				if (paused) {
5635 					/* This will cause the playback
5636 					 * to pause.
5637 					 */
5638 					cd_play_pause(w, client_data, NULL);
5639 
5640 					/* Restore sound */
5641 					di_mute_off(s);
5642 				}
5643 			}
5644 		}
5645 		else {
5646 			DBGPRN(DBG_UI)(errfp,
5647 			    "\n* SHUFFLE: user aborted mode change\n");
5648 
5649 			/* Restore button state */
5650 			set_shuffle_btn(FALSE);
5651 		}
5652 
5653 		if (str != NULL)
5654 			MEM_FREE(str);
5655 	}
5656 }
5657 
5658 
5659 /*
5660  * cd_mode
5661  *	Main window mode button callback function
5662  */
5663 /*ARGSUSED*/
5664 void
cd_mode(Widget w,XtPointer client_data,XtPointer call_data)5665 cd_mode(Widget w, XtPointer client_data, XtPointer call_data)
5666 {
5667 	curstat_t	*s = (curstat_t *)(void *) client_data;
5668 
5669 	DBGPRN(DBG_UI)(errfp, "\n* WINDOW MODE\n");
5670 
5671 	/* Change the main window arrangement.  Unmap the form while
5672 	 * the change takes place so that the user does not see a
5673 	 * bunch of jumbled widgets while they adjust to the new size.
5674 	 */
5675 	XtUnmapWidget(widgets.main.form);
5676 	geom_main_chgmode(&widgets);
5677 	XtMapWidget(widgets.main.form);
5678 
5679 	/* Force indicator font change */
5680 	mode_chg = TRUE;
5681 	dpy_track(s);
5682 	dpy_time(s, FALSE);
5683 	mode_chg = FALSE;
5684 
5685 	/* This is a hack to prevent the tooltip from popping up due to
5686 	 * the main window reconfiguration.
5687 	 */
5688 	skip_next_tooltip = TRUE;
5689 
5690 	/* Overload the function of this button to also
5691 	 * dump the contents of the curstat_t structure
5692 	 * in debug mode
5693 	 */
5694 	if (app_data.debug & DBG_ALL)
5695 		di_dump_curstat(s);
5696 }
5697 
5698 
5699 /*
5700  * cd_load_eject
5701  *	Main window load/eject button callback function
5702  */
5703 /*ARGSUSED*/
5704 void
cd_load_eject(Widget w,XtPointer client_data,XtPointer call_data)5705 cd_load_eject(Widget w, XtPointer client_data, XtPointer call_data)
5706 {
5707 	curstat_t	*s = (curstat_t *)(void *) client_data;
5708 
5709 	DBGPRN(DBG_UI)(errfp, "\n* LOAD_EJECT\n");
5710 
5711 	if (searching) {
5712 		cd_beep();
5713 		return;
5714 	}
5715 	if (s->mode == MOD_PAUSE)
5716 		dpy_time(s, FALSE);
5717 
5718 	if (s->mode != MOD_BUSY && s->mode != MOD_NODISC) {
5719 		s->flags |= STAT_EJECT;
5720 
5721 		/* Ask the user if the changed CD information should be
5722 		 * submitted to CDDB
5723 		 */
5724 		if (!dbprog_chgsubmit(s))
5725 			return;
5726 	}
5727 
5728 	/* Change to watch cursor */
5729 	cd_busycurs(TRUE, CURS_ALL);
5730 
5731 	/* Cancel asynchronous CDDB load operation, if active */
5732 	cdinfo_load_cancel();
5733 
5734 	/* Load/Eject the CD */
5735 	di_load_eject(s);
5736 
5737 	/* Change to normal cursor */
5738 	cd_busycurs(FALSE, CURS_ALL);
5739 
5740 	s->flags &= ~STAT_EJECT;
5741 }
5742 
5743 
5744 /*
5745  * cd_quit_btn
5746  *	Main window quit button callback function
5747  */
5748 /*ARGSUSED*/
5749 void
cd_quit_btn(Widget w,XtPointer client_data,XtPointer call_data)5750 cd_quit_btn(Widget w, XtPointer client_data, XtPointer call_data)
5751 {
5752 	DBGPRN(DBG_UI)(errfp, "\n* QUIT\n");
5753 
5754 	(void) cd_confirm_popup(
5755 		app_data.str_confirm,
5756 		app_data.str_quit,
5757 		(XtCallbackProc) cd_exit,
5758 		client_data,
5759 		(XtCallbackProc) NULL,
5760 		NULL
5761 	);
5762 }
5763 
5764 
5765 /*
5766  * cd_time
5767  *	Main window time mode button callback function
5768  */
5769 /*ARGSUSED*/
5770 void
cd_time(Widget w,XtPointer client_data,XtPointer call_data)5771 cd_time(Widget w, XtPointer client_data, XtPointer call_data)
5772 {
5773 	curstat_t	*s = (curstat_t *)(void *) client_data;
5774 
5775 	switch (s->time_dpy) {
5776 	case T_ELAPSED_TRACK:
5777 		s->time_dpy = T_ELAPSED_SEG;
5778 		break;
5779 
5780 	case T_ELAPSED_SEG:
5781 		s->time_dpy = T_ELAPSED_DISC;
5782 		break;
5783 
5784 	case T_ELAPSED_DISC:
5785 		s->time_dpy = T_REMAIN_TRACK;
5786 		break;
5787 
5788 	case T_REMAIN_TRACK:
5789 		s->time_dpy = T_REMAIN_SEG;
5790 		break;
5791 
5792 	case T_REMAIN_SEG:
5793 		s->time_dpy = T_REMAIN_DISC;
5794 		break;
5795 
5796 	case T_REMAIN_DISC:
5797 		s->time_dpy = T_ELAPSED_TRACK;
5798 		break;
5799 	}
5800 
5801 	dpy_timemode(s);
5802 	dpy_track(s);
5803 	dpy_time(s, FALSE);
5804 }
5805 
5806 
5807 /*
5808  * cd_ab
5809  *	Main window a->b mode button callback function
5810  */
5811 /*ARGSUSED*/
5812 void
cd_ab(Widget w,XtPointer client_data,XtPointer call_data)5813 cd_ab(Widget w, XtPointer client_data, XtPointer call_data)
5814 {
5815 	curstat_t	*s = (curstat_t *)(void *) client_data;
5816 	char		buf[16];
5817 
5818 	DBGPRN(DBG_UI)(errfp, "\n* A->B\n");
5819 
5820 	if (searching) {
5821 		cd_beep();
5822 		return;
5823 	}
5824 	switch (s->segplay) {
5825 	case SEGP_NONE:
5826 		if (s->mode != MOD_PLAY || s->program || s->shuffle) {
5827 			/* Must be in normal playing mode */
5828 			cd_beep();
5829 			return;
5830 		}
5831 
5832 		/* First click - set start position */
5833 
5834 		s->bp_startpos_tot = s->curpos_tot;	/* Structure copy */
5835 		s->bp_startpos_trk = s->curpos_trk;	/* Structure copy */
5836 		s->segplay = SEGP_A;
5837 
5838 		/* Set the segment start position if the segments window
5839 		 * is popped up
5840 		 */
5841 		if (XtIsManaged(widgets.segments.form)) {
5842 			(void) sprintf(buf, "%u", s->cur_trk);
5843 			set_text_string(
5844 				widgets.segments.starttrk_txt, buf, FALSE
5845 			);
5846 
5847 			(void) sprintf(buf, "%u", s->curpos_trk.addr);
5848 			set_text_string(
5849 				widgets.segments.startfrm_txt, buf, FALSE
5850 			);
5851 		}
5852 		break;
5853 
5854 	case SEGP_A:
5855 		if (s->mode != MOD_PLAY || s->program || s->shuffle) {
5856 			/* Must be in normal playing mode */
5857 			cd_beep();
5858 			return;
5859 		}
5860 
5861 		/* Second click - set end position and stop playback. */
5862 
5863 		/* Get current position */
5864 		di_status_upd(s);
5865 
5866 		s->bp_endpos_tot = s->curpos_tot;	/* Structure copy */
5867 		s->bp_endpos_trk = s->curpos_trk;	/* Structure copy */
5868 
5869 		/* Set the segment end position if the segments window
5870 		 * is popped up
5871 		 */
5872 		if (XtIsManaged(widgets.segments.form)) {
5873 			(void) sprintf(buf, "%u", s->cur_trk);
5874 			set_text_string(
5875 				widgets.segments.endtrk_txt, buf, FALSE
5876 			);
5877 
5878 			(void) sprintf(buf, "%u", s->curpos_trk.addr);
5879 			set_text_string(
5880 				widgets.segments.endfrm_txt, buf, FALSE
5881 			);
5882 		}
5883 
5884 		/* Make sure that the start + min_playblks < end  */
5885 		if ((s->bp_startpos_tot.addr + app_data.min_playblks) >=
5886 		    s->bp_endpos_tot.addr) {
5887 			s->segplay = SEGP_NONE;
5888 			CD_INFO(app_data.str_segposerr);
5889 		}
5890 
5891 		if (w == widgets.main.ab_btn) {
5892 			s->segplay = SEGP_AB;
5893 			di_stop(s, TRUE);
5894 		}
5895 		else
5896 			s->segplay = SEGP_NONE;
5897 
5898 		break;
5899 
5900 	case SEGP_AB:
5901 		/* Third click - Disable a->b mode */
5902 
5903 		/* Clear the segment start/end positions if the segments
5904 		 * window is popped up
5905 		 */
5906 		if (XtIsManaged(widgets.segments.form)) {
5907 			set_text_string(
5908 				widgets.segments.starttrk_txt, "", FALSE
5909 			);
5910 			set_text_string(
5911 				widgets.segments.startfrm_txt, "", FALSE
5912 			);
5913 			set_text_string(
5914 				widgets.segments.endtrk_txt, "", FALSE
5915 			);
5916 			set_text_string(
5917 				widgets.segments.endfrm_txt, "", FALSE
5918 			);
5919 		}
5920 
5921 		s->segplay = SEGP_NONE;
5922 		di_stop(s, TRUE);
5923 		break;
5924 	}
5925 
5926 	/* Set segments window set/clear button sensitivity */
5927 	dbprog_segments_setmode(s);
5928 
5929 	dpy_progmode(s, FALSE);
5930 	dpy_playmode(s, FALSE);
5931 }
5932 
5933 
5934 /*
5935  * cd_sample
5936  *	Main window sample mode button callback function
5937  */
5938 /*ARGSUSED*/
5939 void
cd_sample(Widget w,XtPointer client_data,XtPointer call_data)5940 cd_sample(Widget w, XtPointer client_data, XtPointer call_data)
5941 {
5942 	DBGPRN(DBG_UI)(errfp, "\n* SAMPLE\n");
5943 
5944 	if (searching) {
5945 		cd_beep();
5946 		return;
5947 	}
5948 	di_sample((curstat_t *)(void *) client_data);
5949 }
5950 
5951 
5952 /*
5953  * cd_level
5954  *	Main window volume control slider callback function
5955  */
5956 /*ARGSUSED*/
5957 void
cd_level(Widget w,XtPointer client_data,XtPointer call_data)5958 cd_level(Widget w, XtPointer client_data, XtPointer call_data)
5959 {
5960 	XmScaleCallbackStruct
5961 			*p = (XmScaleCallbackStruct *)(void *) call_data;
5962 
5963 	DBGPRN(DBG_UI)(errfp, "\n* VOL: %d\n", (int) p->value);
5964 
5965 	di_level(
5966 		(curstat_t *)(void *) client_data,
5967 		(byte_t) p->value,
5968 		(bool_t) (p->reason != XmCR_VALUE_CHANGED)
5969 	);
5970 }
5971 
5972 
5973 /*
5974  * cd_play_pause
5975  *	Main window play/pause button callback function
5976  */
5977 /*ARGSUSED*/
5978 void
cd_play_pause(Widget w,XtPointer client_data,XtPointer call_data)5979 cd_play_pause(Widget w, XtPointer client_data, XtPointer call_data)
5980 {
5981 	curstat_t	*s = (curstat_t *)(void *) client_data;
5982 	sword32_t	sav_trk;
5983 
5984 	DBGPRN(DBG_UI)(errfp, "\n* PLAY_PAUSE\n");
5985 
5986 	if (searching) {
5987 		cd_beep();
5988 		return;
5989 	}
5990 
5991 	if (s->mode == MOD_STOP && s->program) {
5992 		if (!dbprog_pgm_parse(s)) {
5993 			cd_beep();
5994 			return;
5995 		}
5996 		s->segplay = SEGP_NONE;	/* Cancel a->b mode */
5997 		dpy_progmode(s, FALSE);
5998 	}
5999 
6000 	sav_trk = s->cur_trk;
6001 
6002 	di_play_pause(s);
6003 
6004 	if (sav_trk >= 0) {
6005 		/* Update curfile */
6006 		dbprog_curfileupd();
6007 	}
6008 
6009 	switch (s->mode) {
6010 	case MOD_PAUSE:
6011 		dpy_time(s, FALSE);
6012 		break;
6013 	case MOD_PLAY:
6014 	case MOD_STOP:
6015 	case MOD_SAMPLE:
6016 		dpy_time(s, FALSE);
6017 
6018 		cd_keypad_clear(w, client_data, NULL);
6019 		warp_busy = FALSE;
6020 		break;
6021 	}
6022 }
6023 
6024 
6025 /*
6026  * cd_stop
6027  *	Main window stop button callback function
6028  */
6029 /*ARGSUSED*/
6030 void
cd_stop(Widget w,XtPointer client_data,XtPointer call_data)6031 cd_stop(Widget w, XtPointer client_data, XtPointer call_data)
6032 {
6033 	curstat_t	*s = (curstat_t *)(void *) client_data;
6034 
6035 	DBGPRN(DBG_UI)(errfp, "\n* STOP\n");
6036 
6037 	if (searching) {
6038 		cd_beep();
6039 		return;
6040 	}
6041 
6042 	/* If the user clicks "stop" while in a->? state, cancel it */
6043 	if (s->segplay == SEGP_A) {
6044 		s->segplay = SEGP_NONE;
6045 		dpy_progmode(s, FALSE);
6046 	}
6047 
6048 	dpy_time(s, FALSE);
6049 
6050 	di_stop(s, TRUE);
6051 }
6052 
6053 
6054 /*
6055  * cd_chgdisc
6056  *	Main window disc change buttons callback function
6057  */
6058 /*ARGSUSED*/
6059 void
cd_chgdisc(Widget w,XtPointer client_data,XtPointer call_data)6060 cd_chgdisc(Widget w, XtPointer client_data, XtPointer call_data)
6061 {
6062 	curstat_t	*s = (curstat_t *)(void *) client_data;
6063 	int		newdisc;
6064 
6065 	if (s->first_disc == s->last_disc) {
6066 		/* Single disc player */
6067 		cd_beep();
6068 		return;
6069 	}
6070 
6071 	newdisc = s->cur_disc;
6072 
6073 	if (w == widgets.main.prevdisc_btn) {
6074 		DBGPRN(DBG_UI)(errfp, "\n* PREV_DISC\n");
6075 		if (newdisc > s->first_disc)
6076 			newdisc--;
6077 	}
6078 	else if (w == widgets.main.nextdisc_btn) {
6079 		DBGPRN(DBG_UI)(errfp, "\n* NEXT_DISC\n");
6080 		if (newdisc < s->last_disc)
6081 			newdisc++;
6082 	}
6083 	else
6084 		return;
6085 
6086 	if (newdisc == s->cur_disc)
6087 		/* No change */
6088 		return;
6089 
6090 	s->prev_disc = s->cur_disc;
6091 	s->cur_disc = newdisc;
6092 	dpy_disc(s);
6093 
6094 	s->flags |= STAT_CHGDISC;
6095 
6096 	/* Ask the user if the changed disc info should be submitted to CDDB */
6097 	if (!dbprog_chgsubmit(s))
6098 		return;
6099 
6100 	s->flags &= ~STAT_CHGDISC;
6101 
6102 	/* Update display: clear disc/track titles on the main window */
6103 	chgdelay = TRUE;
6104 	dpy_dtitle(s);
6105 	dpy_ttitle(s);
6106 
6107 	/* Use a timer callback routine to do the disc change.
6108 	 * This allows the user to click on the disc change buttons
6109 	 * multiple times to advance/reverse to the desired
6110 	 * target disc without causing the changer to actually
6111 	 * switch through all of them.
6112 	 */
6113 	if (chgdisc_dlyid >= 0)
6114 		cd_untimeout(chgdisc_dlyid);
6115 
6116 	chgdisc_dlyid = cd_timeout(CHGDISC_DELAY, do_chgdisc, (byte_t *) s);
6117 }
6118 
6119 
6120 /*
6121  * cd_prevtrk
6122  *	Main window prev track button callback function
6123  */
6124 /*ARGSUSED*/
6125 void
cd_prevtrk(Widget w,XtPointer client_data,XtPointer call_data)6126 cd_prevtrk(Widget w, XtPointer client_data, XtPointer call_data)
6127 {
6128 	DBGPRN(DBG_UI)(errfp, "\n* PREVTRK\n");
6129 
6130 	if (searching) {
6131 		cd_beep();
6132 		return;
6133 	}
6134 	di_prevtrk((curstat_t *)(void *) client_data);
6135 }
6136 
6137 
6138 /*
6139  * cd_nexttrk
6140  *	Main window next track button callback function
6141  */
6142 /*ARGSUSED*/
6143 void
cd_nexttrk(Widget w,XtPointer client_data,XtPointer call_data)6144 cd_nexttrk(Widget w, XtPointer client_data, XtPointer call_data)
6145 {
6146 	DBGPRN(DBG_UI)(errfp, "\n* NEXTTRK\n");
6147 
6148 	if (searching) {
6149 		cd_beep();
6150 		return;
6151 	}
6152 	di_nexttrk((curstat_t *)(void *) client_data);
6153 }
6154 
6155 
6156 /*
6157  * cd_previdx
6158  *	Main window prev index button callback function
6159  */
6160 /*ARGSUSED*/
6161 void
cd_previdx(Widget w,XtPointer client_data,XtPointer call_data)6162 cd_previdx(Widget w, XtPointer client_data, XtPointer call_data)
6163 {
6164 	DBGPRN(DBG_UI)(errfp, "\n* PREVIDX\n");
6165 
6166 	if (searching) {
6167 		cd_beep();
6168 		return;
6169 	}
6170 	di_previdx((curstat_t *)(void *) client_data);
6171 }
6172 
6173 
6174 /*
6175  * cd_previdx
6176  *	Main window next index button callback function
6177  */
6178 /*ARGSUSED*/
6179 void
cd_nextidx(Widget w,XtPointer client_data,XtPointer call_data)6180 cd_nextidx(Widget w, XtPointer client_data, XtPointer call_data)
6181 {
6182 	DBGPRN(DBG_UI)(errfp, "\n* NEXTIDX\n");
6183 
6184 	if (searching) {
6185 		cd_beep();
6186 		return;
6187 	}
6188 	di_nextidx((curstat_t *)(void *) client_data);
6189 }
6190 
6191 
6192 /*
6193  * cd_rew
6194  *	Main window search rewind button callback function
6195  */
6196 /*ARGSUSED*/
6197 void
cd_rew(Widget w,XtPointer client_data,XtPointer call_data)6198 cd_rew(Widget w, XtPointer client_data, XtPointer call_data)
6199 {
6200 	XmPushButtonCallbackStruct
6201 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
6202 	curstat_t	*s = (curstat_t *)(void *) client_data;
6203 	bool_t		start;
6204 	static bool_t	rew_running = FALSE;
6205 
6206 	if (p->reason == XmCR_ARM) {
6207 		DBGPRN(DBG_UI)(errfp, "\n* REW: down\n");
6208 
6209 		if (!rew_running) {
6210 			if (searching) {
6211 				/* Release running FF */
6212 				XtCallActionProc(
6213 					widgets.main.ff_btn,
6214 					"Activate",
6215 					p->event,
6216 					NULL,
6217 					0
6218 				);
6219 				XtCallActionProc(
6220 					widgets.main.ff_btn,
6221 					"Disarm",
6222 					p->event,
6223 					NULL,
6224 					0
6225 				);
6226 			}
6227 
6228 			rew_running = TRUE;
6229 			searching = TRUE;
6230 			start = TRUE;
6231 		}
6232 		else
6233 			/* Already running REW */
6234 			return;
6235 	}
6236 	else {
6237 		DBGPRN(DBG_UI)(errfp, "\n* REW: up\n");
6238 
6239 		if (rew_running) {
6240 			rew_running = FALSE;
6241 			searching = FALSE;
6242 			start = FALSE;
6243 		}
6244 		else
6245 			/* Not running REW */
6246 			return;
6247 	}
6248 
6249 	di_rew(s, start);
6250 
6251 	dpy_time(s, FALSE);
6252 }
6253 
6254 
6255 /*
6256  * cd_ff
6257  *	Main window search fast-forward button callback function
6258  */
6259 /*ARGSUSED*/
6260 void
cd_ff(Widget w,XtPointer client_data,XtPointer call_data)6261 cd_ff(Widget w, XtPointer client_data, XtPointer call_data)
6262 {
6263 	XmPushButtonCallbackStruct
6264 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
6265 	curstat_t	*s = (curstat_t *)(void *) client_data;
6266 	bool_t		start;
6267 	static bool_t	ff_running = FALSE;
6268 
6269 	if (p->reason == XmCR_ARM) {
6270 		DBGPRN(DBG_UI)(errfp, "\n* FF: down\n");
6271 
6272 		if (!ff_running) {
6273 			if (searching) {
6274 				/* Release running REW */
6275 				XtCallActionProc(
6276 					widgets.main.rew_btn,
6277 					"Activate",
6278 					p->event,
6279 					NULL,
6280 					0
6281 				);
6282 				XtCallActionProc(
6283 					widgets.main.rew_btn,
6284 					"Disarm",
6285 					p->event,
6286 					NULL,
6287 					0
6288 				);
6289 			}
6290 
6291 			ff_running = TRUE;
6292 			searching = TRUE;
6293 			start = TRUE;
6294 		}
6295 		else
6296 			/* Already running FF */
6297 			return;
6298 	}
6299 	else {
6300 		DBGPRN(DBG_UI)(errfp, "\n* FF: up\n");
6301 
6302 		if (ff_running) {
6303 			ff_running = FALSE;
6304 			searching = FALSE;
6305 			start = FALSE;
6306 		}
6307 		else
6308 			/* Not running FF */
6309 			return;
6310 	}
6311 
6312 	di_ff(s, start);
6313 
6314 	dpy_time(s, FALSE);
6315 }
6316 
6317 
6318 /*
6319  * cd_keypad_popup
6320  *	Main window keypad button callback function
6321  */
6322 void
cd_keypad_popup(Widget w,XtPointer client_data,XtPointer call_data)6323 cd_keypad_popup(Widget w, XtPointer client_data, XtPointer call_data)
6324 {
6325 	static bool_t	first = TRUE;
6326 
6327 	if (XtIsManaged(widgets.keypad.form)) {
6328 		/* Pop down keypad window */
6329 		cd_keypad_popdown(w, client_data, call_data);
6330 		return;
6331 	}
6332 
6333 	/* Pop up keypad window.
6334 	 * The dialog has mappedWhenManaged set to False,
6335 	 * so we have to map/unmap explicitly.  The reason for this
6336 	 * is we want to avoid a screen glitch when we move the window
6337 	 * in cd_dialog_setpos(), so we map the window afterwards.
6338 	 */
6339 	XtManageChild(widgets.keypad.form);
6340 	if (first) {
6341 		first = FALSE;
6342 		/* Set window position */
6343 		cd_dialog_setpos(XtParent(widgets.keypad.form));
6344 	}
6345 	XtMapWidget(XtParent(widgets.keypad.form));
6346 
6347 	/* Reset keypad */
6348 	cd_keypad_clear(w, client_data, NULL);
6349 
6350 	/* Update warp slider */
6351 	dpy_warp((curstat_t *)(void *) client_data);
6352 
6353 	XmProcessTraversal(
6354 		widgets.keypad.cancel_btn,
6355 		XmTRAVERSE_CURRENT
6356 	);
6357 }
6358 
6359 
6360 /*
6361  * cd_keypad_popdown
6362  *	Keypad window popdown callback function
6363  */
6364 /*ARGSUSED*/
6365 void
cd_keypad_popdown(Widget w,XtPointer client_data,XtPointer call_data)6366 cd_keypad_popdown(Widget w, XtPointer client_data, XtPointer call_data)
6367 {
6368 	/* Pop down keypad window */
6369 	if (XtIsManaged(widgets.keypad.form)) {
6370 		XtUnmapWidget(XtParent(widgets.keypad.form));
6371 		XtUnmanageChild(widgets.keypad.form);
6372 	}
6373 }
6374 
6375 
6376 /*
6377  * cd_keypad_mode
6378  *	Keypad window mode selector callback function
6379  */
6380 void
cd_keypad_mode(Widget w,XtPointer client_data,XtPointer call_data)6381 cd_keypad_mode(Widget w, XtPointer client_data, XtPointer call_data)
6382 {
6383 	XmRowColumnCallbackStruct	*p =
6384 		(XmRowColumnCallbackStruct *)(void *) call_data;
6385 	XmToggleButtonCallbackStruct	*q;
6386 	curstat_t			*s = (curstat_t *)(void *) client_data;
6387 
6388 	if (p == NULL)
6389 		return;
6390 
6391 	q = (XmToggleButtonCallbackStruct *)(void *) p->callbackstruct;
6392 
6393 	if (q == NULL || !q->set)
6394 		return;
6395 
6396 	if (p->widget == widgets.keypad.disc_btn) {
6397 		if (keypad_mode == KPMODE_DISC)
6398 			return;	/* No change */
6399 
6400 		keypad_mode = KPMODE_DISC;
6401 	}
6402 	else if (p->widget == widgets.keypad.track_btn) {
6403 		if (keypad_mode == KPMODE_TRACK)
6404 			return;	/* No change */
6405 
6406 		keypad_mode = KPMODE_TRACK;
6407 	}
6408 	else
6409 		return;	/* Invalid widget */
6410 
6411 	cd_keypad_clear(w, (XtPointer) s, NULL);
6412 	warp_busy = FALSE;
6413 }
6414 
6415 
6416 /*
6417  * cd_keypad_num
6418  *	Keypad window number button callback function
6419  */
6420 /*ARGSUSED*/
6421 void
cd_keypad_num(Widget w,XtPointer client_data,XtPointer call_data)6422 cd_keypad_num(Widget w, XtPointer client_data, XtPointer call_data)
6423 {
6424 	curstat_t	*s = curstat_addr();
6425 	int		n;
6426 	char		tmpstr[2];
6427 
6428 	/* The user entered a digit */
6429 	if (strlen(keystr) >= sizeof(keystr) - 1) {
6430 		cd_beep();
6431 		return;
6432 	}
6433 
6434 	(void) sprintf(tmpstr, "%lu", (unsigned long) client_data);
6435 	(void) strcat(keystr, tmpstr);
6436 
6437 	switch (keypad_mode) {
6438 	case KPMODE_DISC:
6439 		n = atoi(keystr);
6440 
6441 		if (n < s->first_disc || n > s->last_disc) {
6442 			/* Illegal disc entered */
6443 			cd_keypad_clear(w, (XtPointer) s, NULL);
6444 
6445 			cd_beep();
6446 			return;
6447 		}
6448 		break;
6449 
6450 	case KPMODE_TRACK:
6451 		n = s->cur_trk;
6452 		s->cur_trk = (sword32_t) atoi(keystr);
6453 
6454 		if (di_curtrk_pos(s) < 0) {
6455 			/* Illegal track entered */
6456 			cd_keypad_clear(w, (XtPointer) s, NULL);
6457 			s->cur_trk = n;
6458 
6459 			cd_beep();
6460 			return;
6461 		}
6462 		s->cur_trk = n;
6463 		break;
6464 
6465 	default:
6466 		/* Illegal mode */
6467 		return;
6468 	}
6469 
6470 	warp_offset = 0;
6471 	set_warp_slider(0, FALSE);
6472 	dpy_keypad_ind(s);
6473 }
6474 
6475 
6476 /*
6477  * cd_keypad_clear
6478  *	Keypad window clear button callback function
6479  */
6480 /*ARGSUSED*/
6481 void
cd_keypad_clear(Widget w,XtPointer client_data,XtPointer call_data)6482 cd_keypad_clear(Widget w, XtPointer client_data, XtPointer call_data)
6483 {
6484 	curstat_t	*s = (curstat_t *)(void *) client_data;
6485 
6486 	/* Reset keypad */
6487 	keystr[0] = '\0';
6488 
6489 	/* Hack: if the third arg is NULL, then it's an internal
6490 	 * call rather than a callback.  We want to set s->cur_trk
6491 	 * to -1 only for callbacks, so that the keypad indicator
6492 	 * display gets updated correctly.
6493 	 */
6494 	if (call_data != NULL)
6495 		s->cur_trk = -1;
6496 
6497 	warp_offset = 0;
6498 	set_warp_slider(0, FALSE);
6499 	dpy_keypad_ind(s);
6500 }
6501 
6502 
6503 /*
6504  * cd_keypad_dsbl_modes_yes
6505  *	User "yes" confirm callback to cancel shuffle or program modes after
6506  *	activating the keypad.
6507  */
6508 /*ARGSUSED*/
6509 void
cd_keypad_dsbl_modes_yes(Widget w,XtPointer client_data,XtPointer call_data)6510 cd_keypad_dsbl_modes_yes(Widget w, XtPointer client_data, XtPointer call_data)
6511 {
6512 	curstat_t	*s = (curstat_t *)(void *) client_data;
6513 
6514 	if (override_sav.func == NULL) {
6515 		cd_beep();
6516 		return;
6517 	}
6518 
6519 	/* Note: This assumes that shuffle and program modes are
6520 	 * mutually exclusive!
6521 	 */
6522 	if (s->shuffle) {
6523 		/* Disable shuffle mode */
6524 		di_shuffle(s, FALSE);
6525 		set_shuffle_btn(FALSE);
6526 	}
6527 	else if (s->program)
6528 		dbprog_clrpgm(w, client_data, call_data);
6529 
6530 	(*override_sav.func)(
6531 		override_sav.w,
6532 		override_sav.client_data,
6533 		override_sav.call_data
6534 	);
6535 
6536 	override_sav.func = (XtCallbackProc) NULL;
6537 	if (override_sav.call_data != NULL) {
6538 		MEM_FREE(override_sav.call_data);
6539 		override_sav.call_data = NULL;
6540 	}
6541 }
6542 
6543 
6544 /*
6545  * cd_keypad_dsbl_modes_no
6546  *	User "no" confirm callback to cancel shuffle or program modes after
6547  *	activating the keypad.
6548  */
6549 /*ARGSUSED*/
6550 void
cd_keypad_dsbl_modes_no(Widget w,XtPointer client_data,XtPointer call_data)6551 cd_keypad_dsbl_modes_no(Widget w, XtPointer client_data, XtPointer call_data)
6552 {
6553 	warp_busy = FALSE;
6554 	cd_keypad_clear(w, client_data, call_data);
6555 
6556 	override_sav.func = (XtCallbackProc) NULL;
6557 	if (override_sav.call_data != NULL) {
6558 		MEM_FREE(override_sav.call_data);
6559 		override_sav.call_data = NULL;
6560 	}
6561 }
6562 
6563 
6564 /*
6565  * cd_keypad_enter
6566  *	Keypad window enter button callback function
6567  */
6568 void
cd_keypad_enter(Widget w,XtPointer client_data,XtPointer call_data)6569 cd_keypad_enter(Widget w, XtPointer client_data, XtPointer call_data)
6570 {
6571 	curstat_t	*s = (curstat_t *)(void *) client_data;
6572 	int		i;
6573 	sword32_t	curr,
6574 			next,
6575 			sav_cur_trk;
6576 	bool_t		paused = FALSE;
6577 
6578 	/* The user activated the Enter key */
6579 	if (keystr[0] == '\0') {
6580 		/* No numeric input */
6581 		cd_beep();
6582 		return;
6583 	}
6584 
6585 	switch (keypad_mode) {
6586 	case KPMODE_DISC:
6587 		/* Use disc number selected on keypad */
6588 		curr = s->cur_disc;
6589 		next = (sword32_t) atoi(keystr);
6590 
6591 		if (curr == next) {
6592 			/* Nothing to do */
6593 			cd_keypad_clear(w, client_data, NULL);
6594 			break;
6595 		}
6596 
6597 		if (s->program) {
6598 			if (s->onetrk_prog)
6599 				dbprog_clrpgm(w, client_data, call_data);
6600 			else {
6601 				/* Trying to use keypad while in shuffle or
6602 				 * program mode: ask user if shuffle/program
6603 				 * should be disabled.
6604 				 */
6605 				cd_keypad_ask_dsbl(
6606 					s,
6607 					cd_keypad_enter,
6608 					w,
6609 					call_data,
6610 					sizeof(XmPushButtonCallbackStruct)
6611 				);
6612 				return;
6613 			}
6614 		}
6615 
6616 		s->prev_disc = curr;
6617 		s->cur_disc = next;
6618 
6619 		cd_keypad_clear(w, client_data, NULL);
6620 
6621 		s->flags |= STAT_CHGDISC;
6622 
6623 		/* Ask the user if the changed CDDB entry
6624 		 * should be saved to file.
6625 		 */
6626 		if (!dbprog_chgsubmit(s))
6627 			return;
6628 
6629 		s->flags &= ~STAT_CHGDISC;
6630 
6631 		/* Change to watch cursor */
6632 		cd_busycurs(TRUE, CURS_ALL);
6633 
6634 		/* Do the disc change */
6635 		di_chgdisc(s);
6636 
6637 		/* Update display */
6638 		dpy_dbmode(s, FALSE);
6639 		dpy_playmode(s, FALSE);
6640 
6641 		/* Change to normal cursor */
6642 		cd_busycurs(FALSE, CURS_ALL);
6643 
6644 		break;
6645 
6646 	case KPMODE_TRACK:
6647 		if (s->mode == MOD_NODISC || s->mode == MOD_BUSY) {
6648 			/* Cannot go to a track when the disc is not ready */
6649 			cd_keypad_clear(w, client_data, NULL);
6650 			cd_beep();
6651 			return;
6652 		}
6653 
6654 		if (s->shuffle || s->program) {
6655 			if (s->onetrk_prog)
6656 				dbprog_clrpgm(w, client_data, call_data);
6657 			else {
6658 				/* Trying to use keypad while in shuffle or
6659 				 * program mode: ask user if shuffle/program
6660 				 * should be disabled.
6661 				 */
6662 				cd_keypad_ask_dsbl(
6663 					s,
6664 					cd_keypad_enter,
6665 					w,
6666 					call_data,
6667 					sizeof(XmPushButtonCallbackStruct)
6668 				);
6669 				return;
6670 			}
6671 		}
6672 
6673 		/* Use track number selected on keypad */
6674 		sav_cur_trk = s->cur_trk;
6675 		s->cur_trk = (word32_t) atoi(keystr);
6676 
6677 		if ((i = di_curtrk_pos(s)) < 0) {
6678 			s->cur_trk = sav_cur_trk;
6679 			cd_beep();
6680 			return;
6681 		}
6682 
6683 		switch (s->mode) {
6684 		case MOD_PAUSE:
6685 			/* Mute sound */
6686 			di_mute_on(s);
6687 			paused = TRUE;
6688 
6689 			/*FALLTHROUGH*/
6690 		case MOD_PLAY:
6691 		case MOD_SAMPLE:
6692 			if (s->segplay == SEGP_AB) {
6693 				s->segplay = SEGP_NONE;	/* Cancel a->b mode */
6694 				dpy_progmode(s, FALSE);
6695 			}
6696 
6697 			sav_cur_trk = s->cur_trk;
6698 
6699 			/* Set play status to stop */
6700 			di_stop(s, FALSE);
6701 
6702 			/* Restore s->cur_trk because di_stop
6703 			 * resets it
6704 			 */
6705 			s->cur_trk = sav_cur_trk;
6706 
6707 			break;
6708 
6709 		default:
6710 			break;
6711 		}
6712 
6713 		s->curpos_trk.addr = warp_offset;
6714 		util_blktomsf(
6715 			s->curpos_trk.addr,
6716 			&s->curpos_trk.min,
6717 			&s->curpos_trk.sec,
6718 			&s->curpos_trk.frame,
6719 			0
6720 		);
6721 		s->curpos_tot.addr = s->trkinfo[i].addr + warp_offset;
6722 		util_blktomsf(
6723 			s->curpos_tot.addr,
6724 			&s->curpos_tot.min,
6725 			&s->curpos_tot.sec,
6726 			&s->curpos_tot.frame,
6727 			MSF_OFFSET
6728 		);
6729 
6730 		/* Start playback at new position */
6731 		cd_play_pause(w, client_data, call_data);
6732 
6733 		if (paused) {
6734 			/* This will cause the playback to pause */
6735 			cd_play_pause(w, client_data, call_data);
6736 
6737 			/* Restore sound */
6738 			di_mute_off(s);
6739 		}
6740 
6741 		break;
6742 
6743 	default:
6744 		/* Illegal mode */
6745 		break;
6746 	}
6747 }
6748 
6749 
6750 /*
6751  * cd_warp
6752  *	Track warp function
6753  */
6754 void
cd_warp(Widget w,XtPointer client_data,XtPointer call_data)6755 cd_warp(Widget w, XtPointer client_data, XtPointer call_data)
6756 {
6757 	curstat_t	*s = (curstat_t *)(void *) client_data;
6758 	XmScaleCallbackStruct
6759 			*p = (XmScaleCallbackStruct *)(void *) call_data;
6760 	int		i;
6761 	sword32_t	sav_cur_trk;
6762 
6763 	if (pseudo_warp) {
6764 		warp_busy = FALSE;
6765 		return;
6766 	}
6767 
6768 	if (keypad_mode != KPMODE_TRACK ||
6769 	    s->mode == MOD_BUSY || s->mode == MOD_NODISC) {
6770 		warp_offset = 0;
6771 		warp_busy = FALSE;
6772 		set_warp_slider(0, FALSE);
6773 		return;
6774 	}
6775 
6776 	sav_cur_trk = s->cur_trk;
6777 	if (keystr[0] != '\0') {
6778 		/* Use track number selected on keypad */
6779 		s->cur_trk = atoi(keystr);
6780 	}
6781 
6782 	if ((i = di_curtrk_pos(s)) < 0) {
6783 		warp_offset = 0;
6784 		warp_busy = FALSE;
6785 		set_warp_slider(0, FALSE);
6786 		s->cur_trk = sav_cur_trk;
6787 		return;
6788 	}
6789 
6790 	/* Translate slider position to block offset */
6791 	warp_offset = (sword32_t) scale_warp(s, i, p->value);
6792 
6793 	if (p->reason == XmCR_VALUE_CHANGED) {
6794 		if ((s->shuffle || s->program) &&
6795 		    s->mode != MOD_STOP && sav_cur_trk != s->cur_trk) {
6796 			if (s->onetrk_prog)
6797 				dbprog_clrpgm(w, client_data, call_data);
6798 			else {
6799 				/* Trying to warp to a different track while
6800 				 * in shuffle or program mode: ask user if
6801 				 * shuffle/program should be disabled.
6802 				 */
6803 				cd_keypad_ask_dsbl(
6804 					s,
6805 					cd_warp,
6806 					w,
6807 					call_data,
6808 					sizeof(XmScaleCallbackStruct)
6809 				);
6810 				return;
6811 			}
6812 		}
6813 
6814 		DBGPRN(DBG_UI)(errfp, "\n* TRACK WARP\n");
6815 
6816 		s->curpos_trk.addr = warp_offset;
6817 		util_blktomsf(
6818 			s->curpos_trk.addr,
6819 			&s->curpos_trk.min,
6820 			&s->curpos_trk.sec,
6821 			&s->curpos_trk.frame,
6822 			0
6823 		);
6824 		s->curpos_tot.addr = s->trkinfo[i].addr + warp_offset;
6825 		util_blktomsf(
6826 			s->curpos_tot.addr,
6827 			&s->curpos_tot.min,
6828 			&s->curpos_tot.sec,
6829 			&s->curpos_tot.frame,
6830 			MSF_OFFSET
6831 		);
6832 
6833 		if (s->mode == MOD_STOP) {
6834 			warp_busy = TRUE;
6835 			dpy_keypad_ind(s);
6836 			warp_busy = FALSE;
6837 			return;
6838 		}
6839 
6840 		/* Start playback at new position */
6841 		di_warp(s);
6842 
6843 		cd_keypad_clear(w, client_data, NULL);
6844 		warp_offset = 0;
6845 		warp_busy = FALSE;
6846 
6847 		/* Update display */
6848 		dpy_track(s);
6849 		dpy_index(s);
6850 		dpy_time(s, FALSE);
6851 	}
6852 	else {
6853 		warp_busy = TRUE;
6854 	}
6855 
6856 	dpy_keypad_ind(s);
6857 
6858 	/* Restore s->cur_trk to actual */
6859 	s->cur_trk = sav_cur_trk;
6860 }
6861 
6862 
6863 /*
6864  * cd_options_popup
6865  *	Options window popup callback function
6866  */
6867 /*ARGSUSED*/
6868 void
cd_options_popup(Widget w,XtPointer client_data,XtPointer call_data)6869 cd_options_popup(Widget w, XtPointer client_data, XtPointer call_data)
6870 {
6871 	static bool_t	first = TRUE;
6872 
6873 	if (XtIsManaged(widgets.options.form)) {
6874 		/* Pop down options window */
6875 		cd_options_popdown(w, client_data, call_data);
6876 		return;
6877 	}
6878 
6879 	/* Pop up options window.
6880 	 * The dialog has mappedWhenManaged set to False,
6881 	 * so we have to map/unmap explicitly.  The reason for this
6882 	 * is we want to avoid a screen glitch when we move the window
6883 	 * in cd_dialog_setpos(), so we map the window afterwards.
6884 	 */
6885 	XtManageChild(widgets.options.form);
6886 	if (first) {
6887 		first = FALSE;
6888 		/* Set window position */
6889 		cd_dialog_setpos(XtParent(widgets.options.form));
6890 	}
6891 	XtMapWidget(XtParent(widgets.options.form));
6892 
6893 	XmProcessTraversal(
6894 		widgets.options.ok_btn,
6895 		XmTRAVERSE_CURRENT
6896 	);
6897 }
6898 
6899 
6900 /*
6901  * cd_options_popdown
6902  *	Options window popdown callback function
6903  */
6904 /*ARGSUSED*/
6905 void
cd_options_popdown(Widget w,XtPointer client_data,XtPointer call_data)6906 cd_options_popdown(Widget w, XtPointer client_data, XtPointer call_data)
6907 {
6908 	/* Pop down options window */
6909 	if (XtIsManaged(widgets.options.form)) {
6910 		XtUnmapWidget(XtParent(widgets.options.form));
6911 		XtUnmanageChild(widgets.options.form);
6912 	}
6913 }
6914 
6915 
6916 /*
6917  * cd_options_reset
6918  *	Options window reset button callback function
6919  */
6920 /*ARGSUSED*/
6921 void
cd_options_reset(Widget w,XtPointer client_data,XtPointer call_data)6922 cd_options_reset(Widget w, XtPointer client_data, XtPointer call_data)
6923 {
6924 	curstat_t	*s = (curstat_t *)(void *) client_data;
6925 	wlist_t		*l;
6926 	cdinfo_incore_t	*cdp;
6927 	cdinfo_path_t	*pp;
6928 	char		*bdevname,
6929 			*cp,
6930 			str[16],
6931 			path[FILE_PATH_SZ * 2];
6932 	Widget		menuw;
6933 	int		pri;
6934 
6935 	if (call_data != NULL) {
6936 		/* Re-read defaults */
6937 
6938 		DBGPRN(DBG_UI)(errfp, "\n* OPTIONS RESET\n");
6939 
6940 		/* Get system-wide common configuration parameters */
6941 		(void) sprintf(path, SYS_CMCFG_PATH, app_data.libdir);
6942 		di_common_parmload(path, FALSE, TRUE);
6943 
6944 		/* Get user common configuration parameters */
6945 		(void) sprintf(path, USR_CMCFG_PATH,
6946 			       util_homedir(util_get_ouid()));
6947 		di_common_parmload(path, FALSE, TRUE);
6948 
6949 		/* Reinit CD info subsystem */
6950 		cdinfo_reinit();
6951 
6952 #ifdef __VMS
6953 		bdevname = NULL;
6954 		if (!util_newstr(&bdevname, "device.cfg")) {
6955 			CD_FATAL(app_data.str_nomemory);
6956 			return;
6957 		}
6958 #else
6959 		if ((bdevname = util_basename(app_data.device)) == NULL) {
6960 			CD_FATAL("Device path basename error: Aborting.");
6961 			return;
6962 		}
6963 #endif
6964 
6965 		/* Get system-wide device-specific configuration parameters */
6966 		(void) sprintf(path, SYS_DSCFG_PATH, app_data.libdir, bdevname);
6967 		di_devspec_parmload(path, FALSE, TRUE);
6968 
6969 		/* Get user device-specific configuration parameters */
6970 		(void) sprintf(path, USR_DSCFG_PATH,
6971 			       util_homedir(util_get_ouid()), bdevname);
6972 		di_devspec_parmload(path, FALSE, TRUE);
6973 
6974 		MEM_FREE(bdevname);
6975 
6976 		/* Set the channel routing */
6977 		di_route(s);
6978 
6979 		/* Set the volume level */
6980 		di_level(s, (byte_t) s->level, TRUE);
6981 	}
6982 
6983 	/* Set default play mode */
6984 	if (!XtIsSensitive(widgets.options.mode_cdda_btn))
6985 		app_data.play_mode &= ~PLAYMODE_CDDA;
6986 	if (!XtIsSensitive(widgets.options.mode_file_btn))
6987 		app_data.play_mode &= ~PLAYMODE_FILE;
6988 	if (!XtIsSensitive(widgets.options.mode_pipe_btn))
6989 		app_data.play_mode &= ~PLAYMODE_PIPE;
6990 
6991 	if (PLAYMODE_IS_STD(app_data.play_mode)) {
6992 		XmToggleButtonSetState(
6993 			widgets.options.mode_std_btn,
6994 			True,
6995 			True
6996 		);
6997 	}
6998 	else {
6999 		XmToggleButtonSetState(
7000 			widgets.options.mode_cdda_btn,
7001 			(Boolean) ((app_data.play_mode & PLAYMODE_CDDA) != 0),
7002 			True
7003 		);
7004 		XmToggleButtonSetState(
7005 			widgets.options.mode_file_btn,
7006 			(Boolean) ((app_data.play_mode & PLAYMODE_FILE) != 0),
7007 			True
7008 		);
7009 		XmToggleButtonSetState(
7010 			widgets.options.mode_pipe_btn,
7011 			(Boolean) ((app_data.play_mode & PLAYMODE_PIPE) != 0),
7012 			True
7013 		);
7014 	}
7015 
7016 	XmToggleButtonSetState(
7017 		widgets.options.mode_jitter_btn,
7018 		(Boolean) app_data.cdda_jitter_corr,
7019 		(Boolean) (call_data != NULL)
7020 	);
7021 
7022 	XmToggleButtonSetState(
7023 		widgets.options.mode_trkfile_btn,
7024 		(Boolean) app_data.cdda_trkfile,
7025 		(Boolean) (call_data != NULL)
7026 	);
7027 
7028 	XmToggleButtonSetState(
7029 		widgets.options.mode_subst_btn,
7030 		(Boolean) app_data.subst_underscore,
7031 		(Boolean) (call_data != NULL)
7032 	);
7033 
7034 	if (!cdda_filefmt_supp(app_data.cdda_filefmt)) {
7035 		/* Specified format not supported: force to RAW */
7036 		DBGPRN(DBG_GEN)(errfp,
7037 			"\nThe cddaFileFormat (%d) is not supported: "
7038 			"reset to RAW\n", app_data.cdda_filefmt);
7039 		app_data.cdda_filefmt = FILEFMT_RAW;
7040 	}
7041 
7042 	/* Set file format button */
7043 	menuw = filefmt_btn(app_data.cdda_filefmt);
7044 	XtVaSetValues(widgets.options.mode_fmt_opt,
7045 		XmNmenuHistory, menuw,
7046 		NULL
7047 	);
7048 
7049 	/* Set default output file format and path */
7050 	if (app_data.cdda_tmpl != NULL &&
7051 	    !util_newstr(&s->outf_tmpl, app_data.cdda_tmpl)) {
7052 		CD_FATAL(app_data.str_nomemory);
7053 		return;
7054 	}
7055 	fix_outfile_path(s);
7056 	set_text_string(widgets.options.mode_path_txt, s->outf_tmpl, TRUE);
7057 
7058 	/* Set default pipe program path and args */
7059 	set_text_string(widgets.options.mode_prog_txt,
7060 			app_data.pipeprog == NULL ? "" : app_data.pipeprog,
7061 			TRUE);
7062 
7063 	/* Set bitrate & quality factor */
7064 	switch (app_data.comp_mode) {
7065 	case COMPMODE_1:
7066 		menuw = widgets.options.mode1_btn;
7067 		XtSetSensitive(widgets.options.qualval_lbl, True);
7068 		XtSetSensitive(widgets.options.qualval1_lbl, True);
7069 		XtSetSensitive(widgets.options.qualval2_lbl, True);
7070 		XtSetSensitive(widgets.options.qualval_scl, True);
7071 		XtSetSensitive(widgets.options.bitrate_opt, False);
7072 		XtSetSensitive(widgets.options.minbrate_opt, True);
7073 		XtSetSensitive(widgets.options.maxbrate_opt, True);
7074 		break;
7075 	case COMPMODE_2:
7076 		menuw = widgets.options.mode2_btn;
7077 		XtSetSensitive(widgets.options.qualval_lbl, True);
7078 		XtSetSensitive(widgets.options.qualval1_lbl, True);
7079 		XtSetSensitive(widgets.options.qualval2_lbl, True);
7080 		XtSetSensitive(widgets.options.qualval_scl, True);
7081 		XtSetSensitive(widgets.options.bitrate_opt, False);
7082 		XtSetSensitive(widgets.options.minbrate_opt, True);
7083 		XtSetSensitive(widgets.options.maxbrate_opt, True);
7084 		break;
7085 	case COMPMODE_3:
7086 		menuw = widgets.options.mode3_btn;
7087 		XtSetSensitive(widgets.options.qualval_lbl, False);
7088 		XtSetSensitive(widgets.options.qualval1_lbl, False);
7089 		XtSetSensitive(widgets.options.qualval2_lbl, False);
7090 		XtSetSensitive(widgets.options.qualval_scl, False);
7091 		XtSetSensitive(widgets.options.bitrate_opt, True);
7092 		XtSetSensitive(widgets.options.minbrate_opt, True);
7093 		XtSetSensitive(widgets.options.maxbrate_opt, True);
7094 		break;
7095 	case COMPMODE_0:
7096 	default:
7097 		menuw = widgets.options.mode0_btn;
7098 		XtSetSensitive(widgets.options.qualval_lbl, False);
7099 		XtSetSensitive(widgets.options.qualval1_lbl, False);
7100 		XtSetSensitive(widgets.options.qualval2_lbl, False);
7101 		XtSetSensitive(widgets.options.qualval_scl, False);
7102 		XtSetSensitive(widgets.options.bitrate_opt, True);
7103 		XtSetSensitive(widgets.options.minbrate_opt, False);
7104 		XtSetSensitive(widgets.options.maxbrate_opt, False);
7105 		break;
7106 	}
7107 
7108 	/* Set compression method */
7109 	XtVaSetValues(widgets.options.method_opt,
7110 		XmNmenuHistory, menuw,
7111 		NULL
7112 	);
7113 
7114 	/* Set bitrate */
7115 	if (app_data.bitrate == 0) {
7116 		XtVaSetValues(widgets.options.bitrate_opt,
7117 			XmNmenuHistory, widgets.options.bitrate_def_btn,
7118 			NULL
7119 		);
7120 	}
7121 	else for (l = cd_brhead; l != NULL; l = l->next) {
7122 		if (l->w != (Widget) NULL &&
7123 		    atoi(XtName(l->w)) == app_data.bitrate) {
7124 			XtVaSetValues(widgets.options.bitrate_opt,
7125 				XmNmenuHistory, l->w,
7126 				NULL
7127 			);
7128 		}
7129 	}
7130 
7131 	/* Set minimum bitrate */
7132 	if (app_data.bitrate_min == 0) {
7133 		XtVaSetValues(widgets.options.minbrate_opt,
7134 			XmNmenuHistory, widgets.options.minbrate_def_btn,
7135 			NULL
7136 		);
7137 	}
7138 	else for (l = cd_minbrhead; l != NULL; l = l->next) {
7139 		if (l->w != (Widget) NULL &&
7140 		    atoi(XtName(l->w)) == app_data.bitrate_min) {
7141 			XtVaSetValues(widgets.options.minbrate_opt,
7142 				XmNmenuHistory, l->w,
7143 				NULL
7144 			);
7145 		}
7146 	}
7147 
7148 	/* Set maximum bitrate */
7149 	if (app_data.bitrate_max == 0) {
7150 		XtVaSetValues(widgets.options.maxbrate_opt,
7151 			XmNmenuHistory, widgets.options.maxbrate_def_btn,
7152 			NULL
7153 		);
7154 	}
7155 	else for (l = cd_maxbrhead; l != NULL; l = l->next) {
7156 		if (l->w != (Widget) NULL &&
7157 		    atoi(XtName(l->w)) == app_data.bitrate_max) {
7158 			XtVaSetValues(widgets.options.maxbrate_opt,
7159 				XmNmenuHistory, l->w,
7160 				NULL
7161 			);
7162 		}
7163 	}
7164 
7165 	/* Set quality factor */
7166 	set_qualval_slider(app_data.qual_factor);
7167 
7168 	/* Set channel mode */
7169 	switch (app_data.chan_mode) {
7170 	case CH_JSTEREO:
7171 		menuw = widgets.options.chmode_jstereo_btn;
7172 		break;
7173 	case CH_FORCEMS:
7174 		menuw = widgets.options.chmode_forcems_btn;
7175 		break;
7176 	case CH_MONO:
7177 		menuw = widgets.options.chmode_mono_btn;
7178 		break;
7179 	case CH_STEREO:
7180 	default:
7181 		menuw = widgets.options.chmode_stereo_btn;
7182 		break;
7183 	}
7184 	XtVaSetValues(widgets.options.chmode_opt,
7185 		XmNmenuHistory, menuw,
7186 		NULL
7187 	);
7188 
7189 	/* Set compression algorithm value */
7190 	set_algo_slider(app_data.comp_algo);
7191 
7192 	/* Lowpass and highpass filters */
7193 	switch (app_data.lowpass_mode) {
7194 	case FILTER_AUTO:
7195 		menuw = widgets.options.lp_auto_btn;
7196 		break;
7197 	case FILTER_MANUAL:
7198 		menuw = widgets.options.lp_manual_btn;
7199 		break;
7200 	case FILTER_OFF:
7201 	default:
7202 		menuw = widgets.options.lp_off_btn;
7203 		break;
7204 	}
7205 	XtVaSetValues(widgets.options.lp_opt,
7206 		XmNmenuHistory, menuw,
7207 		NULL
7208 	);
7209 
7210 	switch (app_data.highpass_mode) {
7211 	case FILTER_AUTO:
7212 		menuw = widgets.options.hp_auto_btn;
7213 		break;
7214 	case FILTER_MANUAL:
7215 		menuw = widgets.options.hp_manual_btn;
7216 		break;
7217 	case FILTER_OFF:
7218 	default:
7219 		menuw = widgets.options.hp_off_btn;
7220 		break;
7221 	}
7222 	XtVaSetValues(widgets.options.hp_opt,
7223 		XmNmenuHistory, menuw,
7224 		NULL
7225 	);
7226 
7227 	XtSetSensitive(
7228 		widgets.options.lp_freq_txt,
7229 		(Boolean) (app_data.lowpass_mode == FILTER_MANUAL)
7230 	);
7231 	XtSetSensitive(
7232 		widgets.options.lp_width_txt,
7233 		(Boolean) (app_data.lowpass_mode == FILTER_MANUAL)
7234 	);
7235 	XtSetSensitive(
7236 		widgets.options.hp_freq_txt,
7237 		(Boolean) (app_data.highpass_mode == FILTER_MANUAL)
7238 	);
7239 	XtSetSensitive(
7240 		widgets.options.hp_width_txt,
7241 		(Boolean) (app_data.highpass_mode == FILTER_MANUAL)
7242 	);
7243 
7244 	(void) sprintf(str, "%d", app_data.lowpass_freq);
7245 	set_text_string(widgets.options.lp_freq_txt, str, FALSE);
7246 	(void) sprintf(str, "%d", app_data.lowpass_width);
7247 	set_text_string(widgets.options.lp_width_txt, str, FALSE);
7248 	(void) sprintf(str, "%d", app_data.highpass_freq);
7249 	set_text_string(widgets.options.hp_freq_txt, str, FALSE);
7250 	(void) sprintf(str, "%d", app_data.highpass_width);
7251 	set_text_string(widgets.options.hp_width_txt, str, FALSE);
7252 
7253 	/* Set flags button states */
7254 	XmToggleButtonSetState(
7255 		widgets.options.copyrt_btn,
7256 		(Boolean) app_data.copyright,
7257 		False
7258 	);
7259 	XmToggleButtonSetState(
7260 		widgets.options.orig_btn,
7261 		(Boolean) app_data.original,
7262 		False
7263 	);
7264 	XmToggleButtonSetState(
7265 		widgets.options.nores_btn,
7266 		(Boolean) app_data.nores,
7267 		False
7268 	);
7269 	XmToggleButtonSetState(
7270 		widgets.options.chksum_btn,
7271 		(Boolean) app_data.checksum,
7272 		False
7273 	);
7274 
7275 	/* Set add CD info tag button state */
7276 	XmToggleButtonSetState(
7277 		widgets.options.addtag_btn,
7278 		(Boolean) app_data.add_tag,
7279 		False
7280 	);
7281 	XtSetSensitive(
7282 		widgets.options.id3tag_ver_opt,
7283 		(Boolean) app_data.add_tag
7284 	);
7285 
7286 	/* Set ID3 tag mode */
7287 	switch (app_data.id3tag_mode) {
7288 	case ID3TAG_V1:
7289 		menuw = widgets.options.id3tag_v1_btn;
7290 		break;
7291 	case ID3TAG_V2:
7292 		menuw = widgets.options.id3tag_v2_btn;
7293 		break;
7294 	case ID3TAG_BOTH:
7295 	default:
7296 		menuw = widgets.options.id3tag_both_btn;
7297 		break;
7298 	}
7299 	XtVaSetValues(widgets.options.id3tag_ver_opt,
7300 		XmNmenuHistory, menuw,
7301 		NULL
7302 	);
7303 
7304 	/* Set LAME options mode buttons */
7305 	XmToggleButtonSetState(
7306 		widgets.options.lame_disable_btn,
7307 		(Boolean) (app_data.lameopts_mode == LAMEOPTS_DISABLE),
7308 		False
7309 	);
7310 	XmToggleButtonSetState(
7311 		widgets.options.lame_insert_btn,
7312 		(Boolean) (app_data.lameopts_mode == LAMEOPTS_INSERT),
7313 		False
7314 	);
7315 	XmToggleButtonSetState(
7316 		widgets.options.lame_append_btn,
7317 		(Boolean) (app_data.lameopts_mode == LAMEOPTS_APPEND),
7318 		False
7319 	);
7320 	XmToggleButtonSetState(
7321 		widgets.options.lame_replace_btn,
7322 		(Boolean) (app_data.lameopts_mode == LAMEOPTS_REPLACE),
7323 		False
7324 	);
7325 
7326 	/* Set LAME options string */
7327 	set_text_string(
7328 		widgets.options.lame_opts_txt,
7329 		(app_data.lame_opts == NULL) ? "" : app_data.lame_opts,
7330 		FALSE
7331 	);
7332 	XtSetSensitive(
7333 		widgets.options.lame_opts_lbl,
7334 		(Boolean) (app_data.lameopts_mode != LAMEOPTS_DISABLE)
7335 	);
7336 	XtSetSensitive(
7337 		widgets.options.lame_opts_txt,
7338 		(Boolean) (app_data.lameopts_mode != LAMEOPTS_DISABLE)
7339 	);
7340 
7341 	/* Set CDDA read/write scheduling priority buttons */
7342 	XmToggleButtonSetState(
7343 		widgets.options.sched_rd_hipri_btn,
7344 		(Boolean) ((app_data.cdda_sched & 0x01) != 0),
7345 		False
7346 	);
7347 	XmToggleButtonSetState(
7348 		widgets.options.sched_rd_norm_btn,
7349 		(Boolean)
7350 		!XmToggleButtonGetState(widgets.options.sched_rd_hipri_btn),
7351 		False
7352 	);
7353 	XmToggleButtonSetState(
7354 		widgets.options.sched_wr_hipri_btn,
7355 		(Boolean) ((app_data.cdda_sched & 0x02) != 0),
7356 		False
7357 	);
7358 	XmToggleButtonSetState(
7359 		widgets.options.sched_wr_norm_btn,
7360 		(Boolean)
7361 		!XmToggleButtonGetState(widgets.options.sched_wr_hipri_btn),
7362 		False
7363 	);
7364 
7365 	(void) sprintf(str, "%d", app_data.hb_timeout);
7366 	set_text_string(widgets.options.hb_tout_txt, str, FALSE);
7367 
7368 	/* Set automation states buttons */
7369 	XmToggleButtonSetState(
7370 		widgets.options.load_none_btn,
7371 		(Boolean) (!app_data.load_spindown && !app_data.load_play),
7372 		False
7373 	);
7374 	XmToggleButtonSetState(
7375 		widgets.options.load_spdn_btn,
7376 		(Boolean) app_data.load_spindown,
7377 		False
7378 	);
7379 	XmToggleButtonSetState(
7380 		widgets.options.load_play_btn,
7381 		(Boolean) app_data.load_play,
7382 		False
7383 	);
7384 	XmToggleButtonSetState(
7385 		widgets.options.load_lock_btn,
7386 		(Boolean) app_data.caddy_lock,
7387 		False
7388 	);
7389 
7390 	XmToggleButtonSetState(
7391 		widgets.options.exit_none_btn,
7392 		(Boolean) (!app_data.exit_stop && !app_data.exit_eject),
7393 		False
7394 	);
7395 	XmToggleButtonSetState(
7396 		widgets.options.exit_stop_btn,
7397 		(Boolean) app_data.exit_stop,
7398 		False
7399 	);
7400 	XmToggleButtonSetState(
7401 		widgets.options.exit_eject_btn,
7402 		(Boolean) app_data.exit_eject,
7403 		False
7404 	);
7405 
7406 	XmToggleButtonSetState(
7407 		widgets.options.done_eject_btn,
7408 		(Boolean) app_data.done_eject,
7409 		False
7410 	);
7411 
7412 	XmToggleButtonSetState(
7413 		widgets.options.done_exit_btn,
7414 		(Boolean) app_data.done_exit,
7415 		False
7416 	);
7417 
7418 	XmToggleButtonSetState(
7419 		widgets.options.eject_exit_btn,
7420 		(Boolean) app_data.eject_exit,
7421 		False
7422 	);
7423 
7424 	/* Set CD changer options buttons */
7425 	XmToggleButtonSetState(
7426 		widgets.options.chg_multiplay_btn,
7427 		(Boolean) app_data.multi_play,
7428 		False
7429 	);
7430 
7431 	XmToggleButtonSetState(
7432 		widgets.options.chg_reverse_btn,
7433 		(Boolean) app_data.reverse,
7434 		False
7435 	);
7436 
7437 	/* Set volume/balance settings buttons */
7438 	XmToggleButtonSetState(
7439 		widgets.options.vol_linear_btn,
7440 		(Boolean) (app_data.vol_taper == VOLTAPER_LINEAR),
7441 		False
7442 	);
7443 	XmToggleButtonSetState(
7444 		widgets.options.vol_square_btn,
7445 		(Boolean) (app_data.vol_taper == VOLTAPER_SQR),
7446 		False
7447 	);
7448 	XmToggleButtonSetState(
7449 		widgets.options.vol_invsqr_btn,
7450 		(Boolean) (app_data.vol_taper == VOLTAPER_INVSQR),
7451 		False
7452 	);
7453 
7454 	/* Set channel routing buttons */
7455 	XmToggleButtonSetState(
7456 		widgets.options.chroute_stereo_btn,
7457 		(Boolean) (app_data.ch_route == CHROUTE_NORMAL),
7458 		False
7459 	);
7460 	XmToggleButtonSetState(
7461 		widgets.options.chroute_rev_btn,
7462 		(Boolean) (app_data.ch_route == CHROUTE_REVERSE),
7463 		False
7464 	);
7465 	XmToggleButtonSetState(
7466 		widgets.options.chroute_left_btn,
7467 		(Boolean) (app_data.ch_route == CHROUTE_L_MONO),
7468 		False
7469 	);
7470 	XmToggleButtonSetState(
7471 		widgets.options.chroute_right_btn,
7472 		(Boolean) (app_data.ch_route == CHROUTE_R_MONO),
7473 		False
7474 	);
7475 	XmToggleButtonSetState(
7476 		widgets.options.chroute_mono_btn,
7477 		(Boolean) (app_data.ch_route == CHROUTE_MONO),
7478 		False
7479 	);
7480 
7481 	/* Set CDDA playback output port selection buttons */
7482 	XmToggleButtonSetState(
7483 		widgets.options.outport_spkr_btn,
7484 		(Boolean) ((app_data.outport & CDDA_OUT_SPEAKER) != 0),
7485 		False
7486 	);
7487 	XmToggleButtonSetState(
7488 		widgets.options.outport_phone_btn,
7489 		(Boolean) ((app_data.outport & CDDA_OUT_HEADPHONE) != 0),
7490 		False
7491 	);
7492 	XmToggleButtonSetState(
7493 		widgets.options.outport_line_btn,
7494 		(Boolean) ((app_data.outport & CDDA_OUT_LINEOUT) != 0),
7495 		False
7496 	);
7497 
7498 	/* Set CDDB & CD-TEXT config widgets */
7499 	XmToggleButtonSetState(
7500 		widgets.options.autobr_btn,
7501 		(Boolean) app_data.auto_musicbrowser,
7502 		False
7503 	);
7504 	XmToggleButtonSetState(
7505 		widgets.options.manbr_btn,
7506 		(Boolean) !app_data.auto_musicbrowser,
7507 		False
7508 	);
7509 	XtSetSensitive(
7510 		widgets.options.autobr_btn,
7511 		(Boolean) (cdinfo_cddb_ver() == 2)
7512 	);
7513 	XtSetSensitive(
7514 		widgets.options.manbr_btn,
7515 		(Boolean) (cdinfo_cddb_ver() == 2)
7516 	);
7517 
7518 	XtSetSensitive(
7519 		widgets.options.cddb_pri_btn,
7520 		(Boolean) cdinfo_cddb_iscfg()
7521 	);
7522 	XtSetSensitive(
7523 		widgets.options.cdtext_pri_btn,
7524 		(Boolean) (cdinfo_cdtext_iscfg() && !app_data.cdtext_dsbl)
7525 	);
7526 
7527 	/* Find whether cdinfoPath gives CDDB or CD-TEXT priority */
7528 	pri = CDINFO_RMT;
7529 	cdp = cdinfo_addr();
7530 	for (pp = cdp->pathlist; pp != NULL; pp = pp->next) {
7531 		if (pp->type == CDINFO_RMT || pp->type == CDINFO_CDTEXT) {
7532 			pri = pp->type;
7533 			break;
7534 		}
7535 	}
7536 
7537 	/* Override cdinfoPath if necessary */
7538 	if (pri == CDINFO_RMT &&
7539 	    !XtIsSensitive(widgets.options.cddb_pri_btn) &&
7540 	    XtIsSensitive(widgets.options.cdtext_pri_btn)) {
7541 		pri = CDINFO_CDTEXT;
7542 	}
7543 	else if (pri == CDINFO_CDTEXT &&
7544 		 !XtIsSensitive(widgets.options.cdtext_pri_btn) &&
7545 		 XtIsSensitive(widgets.options.cddb_pri_btn)) {
7546 		pri = CDINFO_RMT;
7547 	}
7548 
7549 	/* Set the toggle button states */
7550 	XmToggleButtonSetState(
7551 		widgets.options.cddb_pri_btn,
7552 		(Boolean) (pri == CDINFO_RMT),
7553 		False
7554 	);
7555 	XmToggleButtonSetState(
7556 		widgets.options.cdtext_pri_btn,
7557 		(Boolean) (pri == CDINFO_CDTEXT),
7558 		False
7559 	);
7560 
7561 	(void) sprintf(str, "%d", app_data.srv_timeout);
7562 	set_text_string(widgets.options.serv_tout_txt, str, FALSE);
7563 
7564 	(void) sprintf(str, "%d", app_data.cache_timeout);
7565 	set_text_string(widgets.options.cache_tout_txt, str, FALSE);
7566 
7567 	XmToggleButtonSetState(
7568 		widgets.options.use_proxy_btn,
7569 		(Boolean) app_data.use_proxy,
7570 		False
7571 	);
7572 	XmToggleButtonSetState(
7573 		widgets.options.proxy_auth_btn,
7574 		(Boolean) app_data.proxy_auth,
7575 		False
7576 	);
7577 
7578 	if ((cp = strrchr(app_data.proxy_server, ':')) != NULL) {
7579 		*cp = '\0';
7580 		set_text_string(
7581 			widgets.options.proxy_srvr_txt,
7582 			app_data.proxy_server,
7583 			FALSE
7584 		);
7585 		set_text_string(widgets.options.proxy_port_txt, ++cp, FALSE);
7586 		cp--;
7587 		*cp = ':';
7588 	}
7589 	else {
7590 		set_text_string(
7591 			widgets.options.proxy_srvr_txt,
7592 			app_data.proxy_server,
7593 			FALSE
7594 		);
7595 		set_text_string(widgets.options.proxy_port_txt, "80", FALSE);
7596 	}
7597 
7598 	XtSetSensitive(
7599 		widgets.options.proxy_srvr_lbl,
7600 		(Boolean) app_data.use_proxy
7601 	);
7602 	XtSetSensitive(
7603 		widgets.options.proxy_srvr_txt,
7604 		(Boolean) app_data.use_proxy
7605 	);
7606 	XtSetSensitive(
7607 		widgets.options.proxy_port_lbl,
7608 		(Boolean) app_data.use_proxy
7609 	);
7610 	XtSetSensitive(
7611 		widgets.options.proxy_port_txt,
7612 		(Boolean) app_data.use_proxy
7613 	);
7614 	XtSetSensitive(
7615 		widgets.options.proxy_auth_btn,
7616 		(Boolean) app_data.use_proxy
7617 	);
7618 
7619 	/* Make the Reset and Save buttons insensitive */
7620 	XtSetSensitive(widgets.options.reset_btn, False);
7621 	XtSetSensitive(widgets.options.save_btn, False);
7622 }
7623 
7624 
7625 /*
7626  * cd_options_save
7627  *	Options window save button callback function
7628  */
7629 /*ARGSUSED*/
7630 void
cd_options_save(Widget w,XtPointer client_data,XtPointer call_data)7631 cd_options_save(Widget w, XtPointer client_data, XtPointer call_data)
7632 {
7633 	curstat_t	*s = (curstat_t *)(void *) client_data;
7634 	char		*bdevname,
7635 			path[FILE_PATH_SZ + 2];
7636 
7637 	DBGPRN(DBG_UI)(errfp, "\n* OPTIONS SAVE\n");
7638 
7639 	if (!util_newstr(&app_data.cdda_tmpl, s->outf_tmpl)) {
7640 		CD_FATAL(app_data.str_nomemory);
7641 		return;
7642 	}
7643 
7644 	/* Change to watch cursor */
7645 	cd_busycurs(TRUE, CURS_ALL);
7646 
7647 #ifdef __VMS
7648 	bdevname = NULL;
7649 	if (!util_newstr(&bdevname, "device.cfg")) {
7650 		CD_FATAL(app_data.str_nomemory);
7651 		return;
7652 	}
7653 #else
7654 	if ((bdevname = util_basename(app_data.device)) == NULL) {
7655 		CD_FATAL("Device path basename error: Aborting.");
7656 		return;
7657 	}
7658 #endif
7659 
7660 	/* Save user common configuration parameters */
7661 	(void) sprintf(path, USR_CMCFG_PATH,
7662 		       util_homedir(util_get_ouid()));
7663 	di_common_parmsave(path);
7664 
7665 	/* Save user device-specific configuration parameters */
7666 	(void) sprintf(path, USR_DSCFG_PATH,
7667 		       util_homedir(util_get_ouid()),
7668 		       bdevname);
7669 	di_devspec_parmsave(path);
7670 
7671 	MEM_FREE(bdevname);
7672 
7673 	/* Make the Reset and Save buttons insensitive */
7674 	XtSetSensitive(widgets.options.reset_btn, False);
7675 	XtSetSensitive(widgets.options.save_btn, False);
7676 
7677 	/* Change to normal cursor */
7678 	cd_busycurs(FALSE, CURS_ALL);
7679 }
7680 
7681 
7682 /*
7683  * cd_options
7684  *	Options window toggle button callback function
7685  */
7686 void
cd_options(Widget w,XtPointer client_data,XtPointer call_data)7687 cd_options(Widget w, XtPointer client_data, XtPointer call_data)
7688 {
7689 	XmRowColumnCallbackStruct	*p =
7690 		(XmRowColumnCallbackStruct *)(void *) call_data;
7691 	XmToggleButtonCallbackStruct	*q;
7692 	curstat_t			*s = (curstat_t *)(void *) client_data;
7693 	cdinfo_incore_t			*cdp;
7694 	cdinfo_path_t			*pp;
7695 
7696 	if (p == NULL)
7697 		return;
7698 
7699 	q = (XmToggleButtonCallbackStruct *)(void *) p->callbackstruct;
7700 
7701 	if (w == widgets.options.mode_chkbox) {
7702 		switch (s->mode) {
7703 		case MOD_PLAY:
7704 		case MOD_PAUSE:
7705 		case MOD_SAMPLE:
7706 			/* Disallow changing the mode while playing */
7707 			cd_beep();
7708 
7709 			XmToggleButtonSetState(
7710 				widgets.options.mode_std_btn,
7711 				(Boolean)
7712 				PLAYMODE_IS_STD(app_data.play_mode),
7713 				False
7714 			);
7715 			XmToggleButtonSetState(
7716 				widgets.options.mode_cdda_btn,
7717 				(Boolean)
7718 				((app_data.play_mode & PLAYMODE_CDDA) != 0),
7719 				False
7720 			);
7721 			XmToggleButtonSetState(
7722 				widgets.options.mode_file_btn,
7723 				(Boolean)
7724 				((app_data.play_mode & PLAYMODE_FILE) != 0),
7725 				False
7726 			);
7727 			XmToggleButtonSetState(
7728 				widgets.options.mode_pipe_btn,
7729 				(Boolean)
7730 				((app_data.play_mode & PLAYMODE_PIPE) != 0),
7731 				False
7732 			);
7733 			return;
7734 
7735 		default:
7736 			break;
7737 		}
7738 
7739 		if (p->widget == widgets.options.mode_std_btn) {
7740 			if (!q->set)
7741 				return;
7742 			DBGPRN(DBG_UI)(errfp, "\n* OPTION: playMode=STD\n");
7743 			app_data.play_mode = PLAYMODE_STD;
7744 		}
7745 		else if (p->widget == widgets.options.mode_cdda_btn) {
7746 			if (q->set) {
7747 				DBGPRN(DBG_UI)(errfp,
7748 					"\n* OPTION: playMode+=CDDA\n");
7749 				app_data.play_mode |= PLAYMODE_CDDA;
7750 			}
7751 			else {
7752 				DBGPRN(DBG_UI)(errfp,
7753 					"\n* OPTION: playMode-=CDDA\n");
7754 				app_data.play_mode &= ~PLAYMODE_CDDA;
7755 			}
7756 		}
7757 		else if (p->widget == widgets.options.mode_file_btn) {
7758 			if (q->set) {
7759 				DBGPRN(DBG_UI)(errfp,
7760 					"\n* OPTION: playMode+=FILE\n");
7761 				app_data.play_mode |= PLAYMODE_FILE;
7762 			}
7763 			else {
7764 				DBGPRN(DBG_UI)(errfp,
7765 					"\n* OPTION: playMode-=FILE\n");
7766 				app_data.play_mode &= ~PLAYMODE_FILE;
7767 			}
7768 		}
7769 		else if (p->widget == widgets.options.mode_pipe_btn) {
7770 			if (q->set) {
7771 				DBGPRN(DBG_UI)(errfp,
7772 					"\n* OPTION: playMode+=PIPE\n");
7773 				app_data.play_mode |= PLAYMODE_PIPE;
7774 			}
7775 			else {
7776 				DBGPRN(DBG_UI)(errfp,
7777 					"\n* OPTION: playMode-=PIPE\n");
7778 				app_data.play_mode &= ~PLAYMODE_PIPE;
7779 			}
7780 		}
7781 
7782 		XmToggleButtonSetState(
7783 			widgets.options.mode_std_btn,
7784 			(Boolean) PLAYMODE_IS_STD(app_data.play_mode),
7785 			False
7786 		);
7787 
7788 		if (!di_playmode(s)) {
7789 			cd_beep();
7790 			CD_INFO(app_data.str_cddainit_fail);
7791 			XmToggleButtonSetState(
7792 				p->widget,
7793 				(Boolean) !q->set,
7794 				False
7795 			);
7796 			XmToggleButtonSetState(
7797 				widgets.options.mode_std_btn,
7798 				True,
7799 				False
7800 			);
7801 			app_data.play_mode = PLAYMODE_STD;
7802 		}
7803 
7804 		if (PLAYMODE_IS_STD(app_data.play_mode)) {
7805 		    /* Playback mode related controls */
7806 		    XmToggleButtonSetState(
7807 			widgets.options.mode_cdda_btn, False, False
7808 		    );
7809 		    XmToggleButtonSetState(
7810 			widgets.options.mode_file_btn, False, False
7811 		    );
7812 		    XmToggleButtonSetState(
7813 			widgets.options.mode_pipe_btn, False, False
7814 		    );
7815 		    XtSetSensitive(widgets.options.mode_jitter_btn, False);
7816 		    XtSetSensitive(widgets.options.mode_trkfile_btn, False);
7817 		    XtSetSensitive(widgets.options.mode_subst_btn, False);
7818 		    XtSetSensitive(widgets.options.mode_fmt_opt, False);
7819 		    XtSetSensitive(widgets.options.mode_path_lbl, False);
7820 		    XtSetSensitive(widgets.options.mode_path_txt, False);
7821 		    XtSetSensitive(widgets.options.mode_prog_lbl, False);
7822 		    XtSetSensitive(widgets.options.mode_prog_txt, False);
7823 
7824 		    /* Channel routing controls */
7825 		    XtSetSensitive(
7826 			    widgets.options.chroute_rev_btn,
7827 			    (Boolean) app_data.chroute_supp
7828 		    );
7829 		    XtSetSensitive(
7830 			    widgets.options.chroute_left_btn,
7831 			    (Boolean) app_data.chroute_supp
7832 		    );
7833 		    XtSetSensitive(
7834 			    widgets.options.chroute_right_btn,
7835 			    (Boolean) app_data.chroute_supp
7836 		    );
7837 		    XtSetSensitive(
7838 			    widgets.options.chroute_mono_btn,
7839 			    (Boolean) app_data.chroute_supp
7840 		    );
7841 
7842 		    /* Disable CDDA outport port selector */
7843 		    XtSetSensitive(widgets.options.outport_lbl, False);
7844 		    XtSetSensitive(widgets.options.outport_spkr_btn, False);
7845 		    XtSetSensitive(widgets.options.outport_phone_btn, False);
7846 		    XtSetSensitive(widgets.options.outport_line_btn, False);
7847 
7848 		    /* Enable square/invsqr volume taper buttons */
7849 		    XtSetSensitive(widgets.options.vol_square_btn, True);
7850 		    XtSetSensitive(widgets.options.vol_invsqr_btn, True);
7851 
7852 		    /* Enable balance control */
7853 		    XtSetSensitive(widgets.options.bal_lbl, True);
7854 		    XtSetSensitive(widgets.options.ball_lbl, True);
7855 		    XtSetSensitive(widgets.options.balr_lbl, True);
7856 		    XtSetSensitive(widgets.options.bal_scale, True);
7857 		    XtSetSensitive(widgets.options.balctr_btn, True);
7858 
7859 		    /* Disable the CDDA attenuator slider */
7860 		    XtSetSensitive(widgets.options.cdda_att_lbl, False);
7861 		    XtSetSensitive(widgets.options.cdda_att_scale, False);
7862 		    XtSetSensitive(widgets.options.cdda_fadeout_btn, False);
7863 		    XtSetSensitive(widgets.options.cdda_fadein_btn, False);
7864 		    XtSetSensitive(widgets.options.cdda_fadeout_lbl, False);
7865 		    XtSetSensitive(widgets.options.cdda_fadein_lbl, False);
7866 		}
7867 		else {
7868 		    /* Playback mode related controls */
7869 		    XtSetSensitive(widgets.options.mode_jitter_btn, True);
7870 		    XtSetSensitive(widgets.options.mode_trkfile_btn,
7871 			(Boolean) (app_data.play_mode & PLAYMODE_FILE)
7872 		    );
7873 		    XtSetSensitive(widgets.options.mode_subst_btn,
7874 			(Boolean) (app_data.play_mode & PLAYMODE_FILE)
7875 		    );
7876 		    XtSetSensitive(widgets.options.mode_fmt_opt,
7877 			(Boolean)
7878 			(app_data.play_mode & (PLAYMODE_FILE | PLAYMODE_PIPE))
7879 		    );
7880 		    XtSetSensitive(widgets.options.mode_path_lbl,
7881 			(Boolean) (app_data.play_mode & PLAYMODE_FILE)
7882 		    );
7883 		    XtSetSensitive(widgets.options.mode_path_txt,
7884 			(Boolean) (app_data.play_mode & PLAYMODE_FILE)
7885 		    );
7886 		    XtSetSensitive(widgets.options.mode_prog_lbl,
7887 			(Boolean) (app_data.play_mode & PLAYMODE_PIPE)
7888 		    );
7889 		    XtSetSensitive(widgets.options.mode_prog_txt,
7890 			(Boolean) (app_data.play_mode & PLAYMODE_PIPE)
7891 		    );
7892 
7893 		    /* Enable channel routing controls */
7894 		    XtSetSensitive(widgets.options.chroute_rev_btn, True);
7895 		    XtSetSensitive(widgets.options.chroute_left_btn, True);
7896 		    XtSetSensitive(widgets.options.chroute_right_btn, True);
7897 		    XtSetSensitive(widgets.options.chroute_mono_btn, True);
7898 
7899 		    /* CDDA outport port selector */
7900 		    XtSetSensitive(widgets.options.outport_lbl,
7901 			(Boolean) (app_data.play_mode & PLAYMODE_CDDA)
7902 		    );
7903 		    XtSetSensitive(widgets.options.outport_spkr_btn,
7904 			(Boolean) (app_data.play_mode & PLAYMODE_CDDA)
7905 		    );
7906 		    XtSetSensitive(widgets.options.outport_phone_btn,
7907 			(Boolean) (app_data.play_mode & PLAYMODE_CDDA)
7908 		    );
7909 		    XtSetSensitive(widgets.options.outport_line_btn,
7910 			(Boolean) (app_data.play_mode & PLAYMODE_CDDA)
7911 		    );
7912 
7913 		    /* Only allow linear volume taper in CDDA modes */
7914 		    app_data.vol_taper = VOLTAPER_LINEAR;
7915 		    XmToggleButtonSetState(
7916 			    widgets.options.vol_linear_btn, True, False
7917 		    );
7918 		    XmToggleButtonSetState(
7919 			    widgets.options.vol_square_btn, False, False
7920 		    );
7921 		    XmToggleButtonSetState(
7922 			    widgets.options.vol_invsqr_btn, False, False
7923 		    );
7924 		    XtSetSensitive(widgets.options.vol_square_btn, False);
7925 		    XtSetSensitive(widgets.options.vol_invsqr_btn, False);
7926 
7927 		    /* Only enable balance control for CDDA playback */
7928 		    XtSetSensitive(
7929 			    widgets.options.bal_lbl,
7930 			    (Boolean)
7931 			    ((app_data.play_mode & PLAYMODE_CDDA) != 0)
7932 		    );
7933 		    XtSetSensitive(
7934 			    widgets.options.ball_lbl,
7935 			    (Boolean)
7936 			    ((app_data.play_mode & PLAYMODE_CDDA) != 0)
7937 		    );
7938 		    XtSetSensitive(
7939 			    widgets.options.balr_lbl,
7940 			    (Boolean)
7941 			    ((app_data.play_mode & PLAYMODE_CDDA) != 0)
7942 		    );
7943 		    XtSetSensitive(
7944 			    widgets.options.bal_scale,
7945 			    (Boolean)
7946 			    ((app_data.play_mode & PLAYMODE_CDDA) != 0)
7947 		    );
7948 		    XtSetSensitive(
7949 			    widgets.options.balctr_btn,
7950 			    (Boolean)
7951 			    ((app_data.play_mode & PLAYMODE_CDDA) != 0)
7952 		    );
7953 
7954 		    /* Enable the CDDA attenuator slider */
7955 		    XtSetSensitive(widgets.options.cdda_att_lbl, True);
7956 		    XtSetSensitive(widgets.options.cdda_att_scale, True);
7957 		    XtSetSensitive(widgets.options.cdda_fadeout_btn, True);
7958 		    XtSetSensitive(widgets.options.cdda_fadein_btn, True);
7959 		    XtSetSensitive(widgets.options.cdda_fadeout_lbl, True);
7960 		    XtSetSensitive(widgets.options.cdda_fadein_lbl, True);
7961 		}
7962 	}
7963 	else if (w == widgets.options.lame_radbox) {
7964 		if (!q->set)
7965 			return;
7966 
7967 		if (p->widget == widgets.options.lame_disable_btn) {
7968 			if (app_data.lameopts_mode == LAMEOPTS_DISABLE)
7969 				return;		/* No change */
7970 			app_data.lameopts_mode = LAMEOPTS_DISABLE;
7971 		}
7972 		else if (p->widget == widgets.options.lame_insert_btn) {
7973 			if (app_data.lameopts_mode == LAMEOPTS_INSERT)
7974 				return;		/* No change */
7975 			app_data.lameopts_mode = LAMEOPTS_INSERT;
7976 		}
7977 		else if (p->widget == widgets.options.lame_append_btn) {
7978 			if (app_data.lameopts_mode == LAMEOPTS_APPEND)
7979 				return;		/* No change */
7980 			app_data.lameopts_mode = LAMEOPTS_APPEND;
7981 		}
7982 		else if (p->widget == widgets.options.lame_replace_btn) {
7983 			if (app_data.lameopts_mode == LAMEOPTS_REPLACE)
7984 				return;		/* No change */
7985 			app_data.lameopts_mode = LAMEOPTS_REPLACE;
7986 		}
7987 
7988 		XtSetSensitive(
7989 			widgets.options.lame_opts_lbl,
7990 			(Boolean) (app_data.lameopts_mode != LAMEOPTS_DISABLE)
7991 		);
7992 		XtSetSensitive(
7993 			widgets.options.lame_opts_txt,
7994 			(Boolean) (app_data.lameopts_mode != LAMEOPTS_DISABLE)
7995 		);
7996 		if (app_data.lameopts_mode != LAMEOPTS_DISABLE) {
7997 			XmProcessTraversal(
7998 				widgets.options.lame_opts_txt,
7999 				XmTRAVERSE_CURRENT
8000 			);
8001 		}
8002 
8003 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: lameOptionsMode=%d\n",
8004 			app_data.lameopts_mode);
8005 	}
8006 	else if (w == widgets.options.sched_rd_radbox) {
8007 		if (!q->set)
8008 			return;
8009 
8010 		if (p->widget == widgets.options.sched_rd_norm_btn) {
8011 			if ((app_data.cdda_sched & 0x01) == 0)
8012 				return;		/* No change */
8013 
8014 			app_data.cdda_sched &= ~0x01;
8015 		}
8016 		else if (p->widget == widgets.options.sched_rd_hipri_btn) {
8017 			if ((app_data.cdda_sched & 0x01) != 0)
8018 				return;		/* No change */
8019 
8020 			app_data.cdda_sched |= 0x01;
8021 		}
8022 
8023 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: cddaSchedOptions=%d\n",
8024 			app_data.cdda_sched);
8025 	}
8026 	else if (w == widgets.options.sched_wr_radbox) {
8027 		if (!q->set)
8028 			return;
8029 
8030 		if (p->widget == widgets.options.sched_wr_norm_btn) {
8031 			if ((app_data.cdda_sched & 0x02) == 0)
8032 				return;		/* No change */
8033 
8034 			app_data.cdda_sched &= ~0x02;
8035 		}
8036 		else if (p->widget == widgets.options.sched_wr_hipri_btn) {
8037 			if ((app_data.cdda_sched & 0x02) != 0)
8038 				return;		/* No change */
8039 
8040 			app_data.cdda_sched |= 0x02;
8041 		}
8042 
8043 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: cddaSchedOptions=%d\n",
8044 			app_data.cdda_sched);
8045 	}
8046 	else if (w == widgets.options.load_chkbox) {
8047 		if (p->widget == widgets.options.load_none_btn) {
8048 			if (!q->set) {
8049 				XmToggleButtonSetState(p->widget, True, False);
8050 				return;
8051 			}
8052 			XmToggleButtonSetState(
8053 				widgets.options.load_spdn_btn,
8054 				False, False
8055 			);
8056 			XmToggleButtonSetState(
8057 				widgets.options.load_play_btn,
8058 				False, False
8059 			);
8060 			DBGPRN(DBG_UI)(errfp,
8061 				"\n* OPTION: spinDownOnLoad=0 playOnLoad=0\n");
8062 			app_data.load_spindown = FALSE;
8063 			app_data.load_play = FALSE;
8064 		}
8065 		else if (p->widget == widgets.options.load_spdn_btn) {
8066 			if (!q->set) {
8067 				XmToggleButtonSetState(p->widget, True, False);
8068 				return;
8069 			}
8070 			XmToggleButtonSetState(
8071 				widgets.options.load_none_btn,
8072 				False, False
8073 			);
8074 			XmToggleButtonSetState(
8075 				widgets.options.load_play_btn,
8076 				False, False
8077 			);
8078 			DBGPRN(DBG_UI)(errfp,
8079 				"\n* OPTION: spinDownOnLoad=1 playOnLoad=0\n");
8080 			app_data.load_spindown = TRUE;
8081 			app_data.load_play = FALSE;
8082 		}
8083 		else if (p->widget == widgets.options.load_play_btn) {
8084 			if (!q->set) {
8085 				XmToggleButtonSetState(p->widget, True, False);
8086 				return;
8087 			}
8088 			XmToggleButtonSetState(
8089 				widgets.options.load_none_btn,
8090 				False, False
8091 			);
8092 			XmToggleButtonSetState(
8093 				widgets.options.load_spdn_btn,
8094 				False, False
8095 			);
8096 			DBGPRN(DBG_UI)(errfp,
8097 				"\n* OPTION: spinDownOnLoad=0 playOnLoad=1\n");
8098 			app_data.load_play = TRUE;
8099 			app_data.load_spindown = FALSE;
8100 		}
8101 		else if (p->widget == widgets.options.load_lock_btn) {
8102 			if (app_data.caddylock_supp) {
8103 				DBGPRN(DBG_UI)(errfp,
8104 					"\n* OPTION: caddyLock=%d\n",
8105 				       q->set);
8106 				app_data.caddy_lock = (bool_t) q->set;
8107 			}
8108 			else {
8109 				DBGPRN(DBG_UI)(errfp,
8110 					"\n* OPTION: caddyLock=0\n");
8111 				cd_beep();
8112 				XmToggleButtonSetState(
8113 					p->widget,
8114 					False,
8115 					False
8116 				);
8117 				return;
8118 			}
8119 		}
8120 	}
8121 	else if (w == widgets.options.exit_radbox) {
8122 		if (!q->set)
8123 			return;
8124 
8125 		if (p->widget == widgets.options.exit_none_btn) {
8126 			if (!app_data.exit_stop && !app_data.exit_eject)
8127 				return;		/* No change */
8128 
8129 			DBGPRN(DBG_UI)(errfp,
8130 				"\n* OPTION: stopOnExit=0 ejectOnExit=0\n");
8131 			app_data.exit_stop = app_data.exit_eject = FALSE;
8132 		}
8133 		else if (p->widget == widgets.options.exit_stop_btn) {
8134 			if (app_data.exit_stop)
8135 				return;		/* No change */
8136 
8137 			DBGPRN(DBG_UI)(errfp,
8138 				"\n* OPTION: stopOnExit=1 ejectOnExit=0\n");
8139 			app_data.exit_stop = TRUE;
8140 			app_data.exit_eject = FALSE;
8141 		}
8142 		else if (p->widget == widgets.options.exit_eject_btn) {
8143 			if (app_data.exit_eject)
8144 				return;		/* No change */
8145 
8146 			if (app_data.eject_supp) {
8147 				DBGPRN(DBG_UI)(errfp, "\n* OPTION: "
8148 					"stopOnExit=0 ejectOnExit=1\n");
8149 				app_data.exit_stop = FALSE;
8150 				app_data.exit_eject = TRUE;
8151 			}
8152 			else {
8153 				cd_beep();
8154 				XmToggleButtonSetState(
8155 					p->widget,
8156 					False,
8157 					False
8158 				);
8159 				if (app_data.exit_stop) {
8160 					XmToggleButtonSetState(
8161 						widgets.options.exit_stop_btn,
8162 						True,
8163 						False
8164 					);
8165 				}
8166 				else {
8167 					XmToggleButtonSetState(
8168 						widgets.options.exit_none_btn,
8169 						True,
8170 						False
8171 					);
8172 				}
8173 			}
8174 		}
8175 	}
8176 	else if (w == widgets.options.done_chkbox) {
8177 		if (p->widget == widgets.options.done_eject_btn) {
8178 			if (app_data.eject_supp) {
8179 				DBGPRN(DBG_UI)(errfp,
8180 					"\n* OPTION: ejectOnDone=%d\n",
8181 					q->set);
8182 				app_data.done_eject = (bool_t) q->set;
8183 			}
8184 			else {
8185 				DBGPRN(DBG_UI)(errfp,
8186 					"\n* OPTION: ejectOnDone=0\n");
8187 				cd_beep();
8188 				XmToggleButtonSetState(
8189 					p->widget,
8190 					False,
8191 					False
8192 				);
8193 				return;
8194 			}
8195 		}
8196 		else if (p->widget == widgets.options.done_exit_btn) {
8197 			DBGPRN(DBG_UI)(errfp, "\n* OPTION: exitOnDone=%d\n",
8198 			       q->set);
8199 			app_data.done_exit = (bool_t) q->set;
8200 		}
8201 	}
8202 	else if (w == widgets.options.eject_chkbox) {
8203 		if (p->widget == widgets.options.eject_exit_btn) {
8204 			if (app_data.eject_supp) {
8205 				DBGPRN(DBG_UI)(errfp,
8206 					"\n* OPTION: exitOnEject=%d\n",
8207 					q->set);
8208 				app_data.eject_exit = (bool_t) q->set;
8209 			}
8210 			else {
8211 				DBGPRN(DBG_UI)(errfp,
8212 					"\n* OPTION: exitOnEject=0\n");
8213 				cd_beep();
8214 				XmToggleButtonSetState(
8215 					p->widget,
8216 					False,
8217 					False
8218 				);
8219 				return;
8220 			}
8221 		}
8222 	}
8223 	else if (w == widgets.options.chg_chkbox) {
8224 		if (s->first_disc == s->last_disc) {
8225 			/* Single-disc player: inhibit any change here */
8226 			cd_beep();
8227 			XmToggleButtonSetState(p->widget, False, False);
8228 			return;
8229 		}
8230 
8231 		if (p->widget == widgets.options.chg_multiplay_btn) {
8232 			DBGPRN(DBG_UI)(errfp, "\n* OPTION: multiPlay=%d\n",
8233 			       q->set);
8234 			app_data.multi_play = (bool_t) q->set;
8235 
8236 			if (!app_data.multi_play) {
8237 				app_data.reverse = FALSE;
8238 				DBGPRN(DBG_UI)(errfp,
8239 					"\n* OPTION: reversePlay=0\n");
8240 				XmToggleButtonSetState(
8241 					widgets.options.chg_reverse_btn,
8242 					False,
8243 					False
8244 				);
8245 			}
8246 		}
8247 		else if (p->widget == widgets.options.chg_reverse_btn) {
8248 			DBGPRN(DBG_UI)(errfp, "\n* OPTION: reversePlay=%d\n",
8249 			       q->set);
8250 			app_data.reverse = (bool_t) q->set;
8251 
8252 			if (app_data.reverse) {
8253 				app_data.multi_play = TRUE;
8254 				DBGPRN(DBG_UI)(errfp,
8255 					"\n* OPTION: multiPlay=0\n");
8256 				XmToggleButtonSetState(
8257 					widgets.options.chg_multiplay_btn,
8258 					True,
8259 					False
8260 				);
8261 			}
8262 		}
8263 	}
8264 	else if (w == widgets.options.chroute_radbox) {
8265 		if (!q->set)
8266 			return;
8267 
8268 		if (p->widget == widgets.options.chroute_stereo_btn) {
8269 			if (app_data.ch_route == CHROUTE_NORMAL)
8270 				return;		/* No change */
8271 
8272 			app_data.ch_route = CHROUTE_NORMAL;
8273 		}
8274 		else if (p->widget == widgets.options.chroute_rev_btn) {
8275 			if (app_data.ch_route == CHROUTE_REVERSE)
8276 				return;		/* No change */
8277 
8278 			app_data.ch_route = CHROUTE_REVERSE;
8279 		}
8280 		else if (p->widget == widgets.options.chroute_left_btn) {
8281 			if (app_data.ch_route == CHROUTE_L_MONO)
8282 				return;		/* No change */
8283 
8284 			app_data.ch_route = CHROUTE_L_MONO;
8285 		}
8286 		else if (p->widget == widgets.options.chroute_right_btn) {
8287 			if (app_data.ch_route == CHROUTE_R_MONO)
8288 				return;		/* No change */
8289 
8290 			app_data.ch_route = CHROUTE_R_MONO;
8291 		}
8292 		else if (p->widget == widgets.options.chroute_mono_btn) {
8293 			if (app_data.ch_route == CHROUTE_MONO)
8294 				return;		/* No change */
8295 
8296 			app_data.ch_route = CHROUTE_MONO;
8297 		}
8298 
8299 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: channelRoute=%d\n",
8300 				app_data.ch_route);
8301 		di_route(s);
8302 	}
8303 	else if (w == widgets.options.outport_chkbox) {
8304 		char	*port;
8305 
8306 		if (p->widget == widgets.options.outport_spkr_btn) {
8307 			port = "SPEAKER";
8308 
8309 			if (q->set)
8310 				app_data.outport |= CDDA_OUT_SPEAKER;
8311 			else
8312 				app_data.outport &= ~CDDA_OUT_SPEAKER;
8313 		}
8314 		else if (p->widget == widgets.options.outport_phone_btn) {
8315 			port = "HEADPHONE";
8316 
8317 			if (q->set)
8318 				app_data.outport |= CDDA_OUT_HEADPHONE;
8319 			else
8320 				app_data.outport &= ~CDDA_OUT_HEADPHONE;
8321 		}
8322 		else if (p->widget == widgets.options.outport_line_btn) {
8323 			port = "LINEOUT";
8324 
8325 			if (q->set)
8326 				app_data.outport |= CDDA_OUT_LINEOUT;
8327 			else
8328 				app_data.outport &= ~CDDA_OUT_LINEOUT;
8329 		}
8330 
8331 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: outport%s=%s\n",
8332 		       q->set ? "+" : "-", port);
8333 
8334 		cdda_outport();
8335 	}
8336 	else if (w == widgets.options.vol_radbox) {
8337 		if (!q->set)
8338 			return;
8339 
8340 		if (p->widget == widgets.options.vol_linear_btn) {
8341 			if (app_data.vol_taper == VOLTAPER_LINEAR)
8342 				return;		/* No change */
8343 
8344 			app_data.vol_taper = VOLTAPER_LINEAR;
8345 		}
8346 		else if (p->widget == widgets.options.vol_square_btn) {
8347 			if (app_data.vol_taper == VOLTAPER_SQR)
8348 				return;		/* No change */
8349 
8350 			app_data.vol_taper = VOLTAPER_SQR;
8351 		}
8352 		else if (p->widget == widgets.options.vol_invsqr_btn) {
8353 			if (app_data.vol_taper == VOLTAPER_INVSQR)
8354 				return;		/* No change */
8355 
8356 			app_data.vol_taper = VOLTAPER_INVSQR;
8357 		}
8358 
8359 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: volumeControlTaper=%d\n",
8360 				app_data.vol_taper);
8361 		di_level(s, (byte_t) s->level, TRUE);
8362 	}
8363 	else if (w == widgets.options.mbrowser_radbox) {
8364 		if (!q->set)
8365 			return;
8366 
8367 		if (p->widget == widgets.options.autobr_btn) {
8368 			if (app_data.auto_musicbrowser)
8369 				return;		/* No change */
8370 
8371 			app_data.auto_musicbrowser = TRUE;
8372 		}
8373 		else if (p->widget == widgets.options.manbr_btn) {
8374 			if (!app_data.auto_musicbrowser)
8375 				return;		/* No change */
8376 
8377 			app_data.auto_musicbrowser = FALSE;
8378 		}
8379 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: autoMusicBrowser=%d\n",
8380 				(int) app_data.auto_musicbrowser);
8381 	}
8382 	else if (w == widgets.options.lookup_radbox) {
8383 		char	*path,
8384 			*cp,
8385 			*cp2;
8386 		int	i,
8387 			type[2];
8388 
8389 		if (!q->set)
8390 			return;
8391 
8392 		if (p->widget == widgets.options.cddb_pri_btn) {
8393 			type[0] = CDINFO_RMT;
8394 			type[1] = CDINFO_CDTEXT;
8395 		}
8396 		else if (p->widget == widgets.options.cdtext_pri_btn) {
8397 			type[0] = CDINFO_CDTEXT;
8398 			type[1] = CDINFO_RMT;
8399 		}
8400 
8401 		cdp = cdinfo_addr();
8402 
8403 		i = 0;
8404 		for (pp = cdp->pathlist; pp != NULL; pp = pp->next) {
8405 			if (pp->type != CDINFO_RMT &&
8406 			    pp->type != CDINFO_CDTEXT)
8407 				continue;
8408 
8409 			if (i == 0 && pp->type == type[i])
8410 				return;		/* No change */
8411 
8412 			pp->type = type[i];
8413 			if (++i > 1)
8414 				break;
8415 		}
8416 
8417 		/* Allocate new cdinfo_path string */
8418 		cp = (char *) MEM_ALLOC(
8419 			"cdinfo_path",
8420 			strlen(app_data.cdinfo_path) + 8
8421 		);
8422 		if (cp == NULL) {
8423 			CD_FATAL(app_data.str_nomemory);
8424 			return;
8425 		}
8426 		cp[0] = '\0';
8427 
8428 		i = 0;
8429 		path = app_data.cdinfo_path;
8430 		while ((cp2 = strchr(path, CDINFOPATH_SEPCHAR)) != NULL) {
8431 			*cp2 = '\0';
8432 
8433 			PATHENT_APPEND(i, cp, path, type[i]);
8434 
8435 			(void) sprintf(cp, "%s%c", cp, CDINFOPATH_SEPCHAR);
8436 			path = cp2 + 1;
8437 		}
8438 		PATHENT_APPEND(i, cp, path, type[i]);
8439 
8440 		/* Replace cdinfo_path string */
8441 		MEM_FREE(app_data.cdinfo_path);
8442 		app_data.cdinfo_path = cp;
8443 
8444 		DBGPRN(DBG_UI)(errfp, "\n* OPTION: Lookup priority: %s\n",
8445 			(type[0] == CDINFO_RMT) ? "CDDB" : "CD-TEXT");
8446 	}
8447 
8448 	/* Make the Reset and Save buttons sensitive */
8449 	XtSetSensitive(widgets.options.reset_btn, True);
8450 	XtSetSensitive(widgets.options.save_btn, True);
8451 }
8452 
8453 
8454 /*
8455  * cd_options_categsel
8456  *	Options window category list selection callback function
8457  */
8458 /*ARGSUSED*/
8459 void
cd_options_categsel(Widget w,XtPointer client_data,XtPointer call_data)8460 cd_options_categsel(Widget w, XtPointer client_data, XtPointer call_data)
8461 {
8462 	XmListCallbackStruct	*p =
8463 		(XmListCallbackStruct *)(void *) call_data;
8464 	int			i;
8465 	wlist_t			*wl;
8466 	static int		prev = -1;
8467 
8468 	if (p->reason != XmCR_BROWSE_SELECT && p->reason != XmCR_ACTIVATE)
8469 		return;
8470 
8471 	i = p->item_position - 1;
8472 	if (prev == i)
8473 		return;		/* No change */
8474 
8475 	if (prev != -1) {
8476 		/* Unmanage the previously selected category */
8477 		for (wl = opt_categ[prev].widgets; wl != NULL; wl = wl->next)
8478 			XtUnmanageChild(wl->w);
8479 	}
8480 
8481 	/* Manage the selected category widgets */
8482 	for (wl = opt_categ[i].widgets; wl != NULL; wl = wl->next)
8483 		XtManageChild(wl->w);
8484 
8485 	prev = i;
8486 }
8487 
8488 
8489 /*
8490  * cd_jitter_corr
8491  *	Options window CDDA jitter correction toggle button callback
8492  */
8493 /*ARGSUSED*/
8494 void
cd_jitter_corr(Widget w,XtPointer client_data,XtPointer call_data)8495 cd_jitter_corr(Widget w, XtPointer client_data, XtPointer call_data)
8496 {
8497 	XmToggleButtonCallbackStruct	*p =
8498 		(XmToggleButtonCallbackStruct *)(void *) call_data;
8499 	curstat_t	*s = (curstat_t *)(void *) client_data;
8500 
8501 	if (p->reason != XmCR_VALUE_CHANGED)
8502 		return;
8503 
8504 	if ((bool_t) p->set == app_data.cdda_jitter_corr)
8505 		return;	/* No change */
8506 
8507 	DBGPRN(DBG_UI)(errfp,
8508 		"\n* CDDA Jitter Correction: %s\n", p->set ? "On" : "Off");
8509 
8510 	if (di_isdemo())
8511 		return;	/* Don't actually do anything in demo mode */
8512 
8513 	app_data.cdda_jitter_corr = (bool_t) p->set;
8514 
8515 	/* Notify device interface of the change */
8516 	di_cddajitter(s);
8517 
8518 	/* Make the Reset and Save buttons sensitive */
8519 	XtSetSensitive(widgets.options.reset_btn, True);
8520 	XtSetSensitive(widgets.options.save_btn, True);
8521 }
8522 
8523 
8524 /*
8525  * cd_file_per_trk
8526  *	Options window CDDA file-per-track toggle button callback
8527  */
8528 /*ARGSUSED*/
8529 void
cd_file_per_trk(Widget w,XtPointer client_data,XtPointer call_data)8530 cd_file_per_trk(Widget w, XtPointer client_data, XtPointer call_data)
8531 {
8532 	XmToggleButtonCallbackStruct	*p =
8533 		(XmToggleButtonCallbackStruct *)(void *) call_data;
8534 	curstat_t	*s = (curstat_t *)(void *) client_data;
8535 
8536 	if (p->reason != XmCR_VALUE_CHANGED)
8537 		return;
8538 
8539 	if ((bool_t) p->set == app_data.cdda_trkfile)
8540 		return;	/* No change */
8541 
8542 	DBGPRN(DBG_UI)(errfp,
8543 		"\n* CDDA File-per-track: %s\n", p->set ? "On" : "Off");
8544 
8545 	app_data.cdda_trkfile = (bool_t) p->set;
8546 
8547 	if (s->outf_tmpl != NULL) {
8548 		MEM_FREE(s->outf_tmpl);
8549 		s->outf_tmpl = NULL;
8550 	}
8551 
8552 	/* Set new output file path template */
8553 	fix_outfile_path(s);
8554 
8555 	set_text_string(widgets.options.mode_path_txt, s->outf_tmpl, TRUE);
8556 
8557 	/* Make the Reset and Save buttons sensitive */
8558 	XtSetSensitive(widgets.options.reset_btn, True);
8559 	XtSetSensitive(widgets.options.save_btn, True);
8560 }
8561 
8562 
8563 /*
8564  * cd_subst
8565  *	Options window CDDA file name underscore substitution
8566  *	toggle button callback
8567  */
8568 /*ARGSUSED*/
8569 void
cd_subst(Widget w,XtPointer client_data,XtPointer call_data)8570 cd_subst(Widget w, XtPointer client_data, XtPointer call_data)
8571 {
8572 	XmToggleButtonCallbackStruct	*p =
8573 		(XmToggleButtonCallbackStruct *)(void *) call_data;
8574 
8575 	if (p->reason != XmCR_VALUE_CHANGED)
8576 		return;
8577 
8578 #ifdef __VMS
8579 	/* On VMS, force this toggle to be enabled at all times,
8580 	 * because VMS file names do not allow spaces and tabs.
8581 	 */
8582 	if (!p->set) {
8583 		cd_beep();
8584 		XmToggleButtonSetState(w, True, False);
8585 	}
8586 #else
8587 	if ((bool_t) p->set == app_data.subst_underscore)
8588 		return;	/* No change */
8589 
8590 	DBGPRN(DBG_UI)(errfp,
8591 		"\n* CDDA Underscore Substitution: %s\n",
8592 		p->set ? "On" : "Off");
8593 
8594 	app_data.subst_underscore = (bool_t) p->set;
8595 
8596 	/* Make the Reset and Save buttons sensitive */
8597 	XtSetSensitive(widgets.options.reset_btn, True);
8598 	XtSetSensitive(widgets.options.save_btn, True);
8599 #endif
8600 }
8601 
8602 
8603 /*
8604  * cd_filefmt_mode
8605  *	File format mode selector callback function
8606  */
8607 /*ARGSUSED*/
8608 void
cd_filefmt_mode(Widget w,XtPointer client_data,XtPointer call_data)8609 cd_filefmt_mode(Widget w, XtPointer client_data, XtPointer call_data)
8610 {
8611 	XmPushButtonCallbackStruct
8612 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
8613 	curstat_t	*s = (curstat_t *)(void *) client_data;
8614 	int		newfmt;
8615 
8616 	if (p->reason != XmCR_ACTIVATE)
8617 		return;
8618 
8619 	newfmt = filefmt_code(w);
8620 
8621 	if (newfmt == app_data.cdda_filefmt)
8622 		return;	/* No change */
8623 
8624 	app_data.cdda_filefmt = newfmt;
8625 
8626 	/* Fix output file suffix to match the file type */
8627 	fix_outfile_path(s);
8628 
8629 	set_text_string(widgets.options.mode_path_txt, s->outf_tmpl, TRUE);
8630 
8631 	/* Make the Reset and Save buttons sensitive */
8632 	XtSetSensitive(widgets.options.reset_btn, True);
8633 	XtSetSensitive(widgets.options.save_btn, True);
8634 }
8635 
8636 
8637 /*
8638  * cd_filepath_new
8639  *	Save-to-file path text widget callback function
8640  */
8641 /*ARGSUSED*/
8642 void
cd_filepath_new(Widget w,XtPointer client_data,XtPointer call_data)8643 cd_filepath_new(Widget w, XtPointer client_data, XtPointer call_data)
8644 {
8645 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
8646 	curstat_t		*s = (curstat_t *)(void *) client_data;
8647 	filefmt_t		*fmp,
8648 				*fmp2;
8649 	char			*cp,
8650 				*cp2,
8651 				*cp3,
8652 				*suf,
8653 				tmp[FILE_PATH_SZ * 2 + 8];
8654 	int			i,
8655 				newfmt;
8656 	Widget			menuw;
8657 
8658 	switch (p->reason) {
8659 	case XmCR_VALUE_CHANGED:
8660 	case XmCR_ACTIVATE:
8661 	case XmCR_LOSING_FOCUS:
8662 		if ((cp = get_text_string(w, TRUE)) == NULL)
8663 			return;
8664 
8665 		if (s->outf_tmpl != NULL && strcmp(cp, s->outf_tmpl) == 0) {
8666 			/* Not changed */
8667 			MEM_FREE(cp);
8668 
8669 			if (p->reason == XmCR_ACTIVATE ||
8670 			    p->reason == XmCR_LOSING_FOCUS) {
8671 				/* Set cursor to beginning of text */
8672 				XmTextSetInsertionPosition(w, 0);
8673 			}
8674 			return;
8675 		}
8676 
8677 		newfmt = app_data.cdda_filefmt;
8678 
8679 		/* File suffix */
8680 		if ((fmp = cdda_filefmt(app_data.cdda_filefmt)) == NULL)
8681 			suf = "";
8682 		else
8683 			suf = fmp->suf;
8684 
8685 		/* Check if file suffix indicates a change in file format */
8686 		menuw = (Widget) NULL;
8687 		if ((cp2 = strrchr(cp, '.')) != NULL) {
8688 			if ((cp3 = strrchr(cp, DIR_END)) != NULL &&
8689 			    cp2 > cp3) {
8690 			    if (strcmp(suf, cp2) != 0) {
8691 				for (i = 0; i < MAX_FILEFMTS; i++) {
8692 				    if ((fmp2 = cdda_filefmt(i)) == NULL)
8693 					continue;
8694 
8695 				    if (util_strcasecmp(cp2, fmp2->suf) == 0)
8696 					newfmt = fmp2->fmt;
8697 				}
8698 			    }
8699 			}
8700 			else if (strcmp(suf, cp2) != 0) {
8701 			    for (i = 0; i < MAX_FILEFMTS; i++) {
8702 				if ((fmp2 = cdda_filefmt(i)) == NULL)
8703 				    continue;
8704 
8705 				if (util_strcasecmp(cp2, fmp2->suf) == 0)
8706 				    newfmt = fmp2->fmt;
8707 			    }
8708 			}
8709 
8710 			menuw = filefmt_btn(newfmt);
8711 		}
8712 		else
8713 			cp2 = "";
8714 
8715 		if (!cdda_filefmt_supp(newfmt)) {
8716 			/* Specified format not supported */
8717 			if (p->reason == XmCR_ACTIVATE ||
8718 			    p->reason == XmCR_LOSING_FOCUS) {
8719 				/* Force back to original format */
8720 				fix_outfile_path(s);
8721 				set_text_string(w, s->outf_tmpl, TRUE);
8722 
8723 				/* Set cursor to beginning of text */
8724 				XmTextSetInsertionPosition(w, 0);
8725 			}
8726 			return;
8727 		}
8728 
8729 		if (!util_newstr(&s->outf_tmpl, cp)) {
8730 			MEM_FREE(cp);
8731 			CD_FATAL(app_data.str_nomemory);
8732 			return;
8733 		}
8734 
8735 		MEM_FREE(cp);
8736 
8737 		/* File format changed */
8738 		if (newfmt != app_data.cdda_filefmt) {
8739 			XtVaSetValues(widgets.options.mode_fmt_opt,
8740 				XmNmenuHistory, menuw,
8741 				NULL
8742 			);
8743 			app_data.cdda_filefmt = newfmt;
8744 		}
8745 
8746 		/* Fix output file suffix to match the file type */
8747 		fix_outfile_path(s);
8748 
8749 		if (util_strstr(s->outf_tmpl, "%T") != NULL ||
8750 		    util_strstr(s->outf_tmpl, "%t") != NULL ||
8751 		    util_strstr(s->outf_tmpl, "%R") != NULL ||
8752 		    util_strstr(s->outf_tmpl, "%r") != NULL ||
8753 		    util_strstr(s->outf_tmpl, "%#") != NULL) {
8754 			if (!app_data.cdda_trkfile) {
8755 				XmToggleButtonSetState(
8756 					widgets.options.mode_trkfile_btn,
8757 					True,
8758 					False
8759 				);
8760 				app_data.cdda_trkfile = TRUE;
8761 			}
8762 		}
8763 		else if (app_data.cdda_trkfile) {
8764 			XmToggleButtonSetState(
8765 				widgets.options.mode_trkfile_btn,
8766 				False,
8767 				False
8768 			);
8769 			app_data.cdda_trkfile = FALSE;
8770 		}
8771 
8772 		if (p->reason == XmCR_ACTIVATE ||
8773 		    p->reason == XmCR_LOSING_FOCUS) {
8774 			set_text_string(w, s->outf_tmpl, TRUE);
8775 
8776 			/* Set cursor to beginning of text */
8777 			XmTextSetInsertionPosition(w, 0);
8778 
8779 			DBGPRN(DBG_UI)(errfp, "\n* CDDA file template: %s\n",
8780 				       s->outf_tmpl);
8781 		}
8782 
8783 		break;
8784 
8785 	default:
8786 		cp2 = "";
8787 		break;
8788 	}
8789 
8790 	(void) sprintf(tmp,
8791 #ifdef __VMS
8792 		"%%S.%%C.%%I]%s%s",
8793 #else
8794 		"%%S/%%C/%%I/%s%s",
8795 #endif
8796 		app_data.cdda_trkfile ? FILEPATH_TRACK : FILEPATH_DISC,
8797 		cp2
8798 	);
8799 
8800 	if (strcmp(s->outf_tmpl, tmp) != 0) {
8801 		/* Make the Reset and Save buttons sensitive */
8802 		XtSetSensitive(widgets.options.reset_btn, True);
8803 		XtSetSensitive(widgets.options.save_btn, True);
8804 	}
8805 }
8806 
8807 
8808 /*
8809  * cd_filepath_exp
8810  *	File path expand pushbutton callback function
8811  */
8812 /*ARGSUSED*/
8813 void
cd_filepath_exp(Widget w,XtPointer client_data,XtPointer call_data)8814 cd_filepath_exp(Widget w, XtPointer client_data, XtPointer call_data)
8815 {
8816 	curstat_t	*s = (curstat_t *)(void *) client_data;
8817 	chset_conv_t	*up;
8818 	char		*a = NULL;
8819 	int		i;
8820 	bool_t		ret;
8821 
8822 	/* call_data is NULL when this function is called via
8823 	 * an update rather than a button click.
8824 	 */
8825 	if (call_data != NULL) {
8826 		ret = pathexp_active;
8827 		cd_info2_popdown(NULL);
8828 		if (ret)
8829 			return;
8830 	}
8831 
8832 	switch (s->mode) {
8833 	case MOD_PLAY:
8834 	case MOD_PAUSE:
8835 	case MOD_SAMPLE:
8836 		if ((app_data.play_mode & PLAYMODE_FILE) == 0)
8837 			return;
8838 		break;
8839 	default:
8840 		return;
8841 	}
8842 
8843 	if (app_data.cdda_trkfile) {
8844 		if ((i = di_curtrk_pos(s)) < 0)
8845 			return;
8846 	}
8847 	else
8848 		i = 0;
8849 
8850 	if (s->trkinfo[i].outfile == NULL)
8851 		return;
8852 
8853 	pathexp_active = TRUE;
8854 
8855 	/* Convert text from UTF-8 to local charset if possible */
8856 	if ((up = util_chset_open(CHSET_UTF8_TO_LOCALE)) == NULL)
8857 		return;
8858 
8859 	if (!util_chset_conv(up, s->trkinfo[i].outfile, &a, FALSE) &&
8860 	    !util_newstr(&a, s->trkinfo[i].outfile)) {
8861 		util_chset_close(up);
8862 		return;
8863 	}
8864 	util_chset_close(up);
8865 
8866 	CD_INFO2(app_data.str_pathexp, a);
8867 	MEM_FREE(a);
8868 }
8869 
8870 
8871 /*
8872  * cd_pipeprog_new
8873  *	Pipe-to-program path text widget callback function
8874  */
8875 /*ARGSUSED*/
8876 void
cd_pipeprog_new(Widget w,XtPointer client_data,XtPointer call_data)8877 cd_pipeprog_new(Widget w, XtPointer client_data, XtPointer call_data)
8878 {
8879 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
8880 	char			*cp;
8881 
8882 	switch (p->reason) {
8883 	case XmCR_VALUE_CHANGED:
8884 	case XmCR_ACTIVATE:
8885 	case XmCR_LOSING_FOCUS:
8886 		if ((cp = get_text_string(w, TRUE)) == NULL)
8887 			return;
8888 
8889 		if (app_data.pipeprog != NULL &&
8890 		    strcmp(cp, app_data.pipeprog) == 0) {
8891 			/* Not changed */
8892 			MEM_FREE(cp);
8893 
8894 			if (p->reason == XmCR_ACTIVATE ||
8895 			    p->reason == XmCR_LOSING_FOCUS) {
8896 				/* Set cursor to beginning of text */
8897 				XmTextSetInsertionPosition(w, 0);
8898 			}
8899 			return;
8900 		}
8901 
8902 		if (!util_newstr(&app_data.pipeprog, cp)) {
8903 			MEM_FREE(cp);
8904 			CD_FATAL(app_data.str_nomemory);
8905 			return;
8906 		}
8907 
8908 		MEM_FREE(cp);
8909 
8910 		if (p->reason == XmCR_ACTIVATE ||
8911 		    p->reason == XmCR_LOSING_FOCUS) {
8912 			/* Set cursor to beginning of text */
8913 			XmTextSetInsertionPosition(w, 0);
8914 
8915 			DBGPRN(DBG_UI)(errfp, "\n* CDDA pipe program: %s\n",
8916 				       app_data.pipeprog);
8917 		}
8918 
8919 		/* Make the Reset and Save buttons sensitive */
8920 		XtSetSensitive(widgets.options.reset_btn, True);
8921 		XtSetSensitive(widgets.options.save_btn, True);
8922 
8923 		break;
8924 
8925 	default:
8926 		break;
8927 	}
8928 }
8929 
8930 
8931 /*
8932  * cd_comp_mode
8933  *	Options window encoding method menu entry callback
8934  */
8935 /*ARGSUSED*/
8936 void
cd_comp_mode(Widget w,XtPointer client_data,XtPointer call_data)8937 cd_comp_mode(Widget w, XtPointer client_data, XtPointer call_data)
8938 {
8939 	XmPushButtonCallbackStruct	*p =
8940 		(XmPushButtonCallbackStruct *)(void *) call_data;
8941 	int				newmode;
8942 
8943 	if (p->reason != XmCR_ACTIVATE)
8944 		return;
8945 
8946 	if (w == widgets.options.mode0_btn)
8947 		newmode = COMPMODE_0;
8948 	else if (w == widgets.options.mode1_btn)
8949 		newmode = COMPMODE_1;
8950 	else if (w == widgets.options.mode2_btn)
8951 		newmode = COMPMODE_2;
8952 	else if (w == widgets.options.mode3_btn)
8953 		newmode = COMPMODE_3;
8954 	else
8955 		return;
8956 
8957 	if (app_data.comp_mode == newmode)
8958 		return;	/* No change */
8959 
8960 	DBGPRN(DBG_UI)(errfp, "\n* Compression mode: Mode %d\n", newmode);
8961 	app_data.comp_mode = newmode;
8962 
8963 	switch (newmode) {
8964 	case COMPMODE_1:
8965 	case COMPMODE_2:
8966 		XtSetSensitive(widgets.options.qualval_lbl, True);
8967 		XtSetSensitive(widgets.options.qualval1_lbl, True);
8968 		XtSetSensitive(widgets.options.qualval2_lbl, True);
8969 		XtSetSensitive(widgets.options.qualval_scl, True);
8970 		XtSetSensitive(widgets.options.bitrate_opt, False);
8971 		XtSetSensitive(widgets.options.minbrate_opt, True);
8972 		XtSetSensitive(widgets.options.maxbrate_opt, True);
8973 		break;
8974 
8975 	case COMPMODE_3:
8976 		XtSetSensitive(widgets.options.qualval_lbl, False);
8977 		XtSetSensitive(widgets.options.qualval1_lbl, False);
8978 		XtSetSensitive(widgets.options.qualval2_lbl, False);
8979 		XtSetSensitive(widgets.options.qualval_scl, False);
8980 		XtSetSensitive(widgets.options.bitrate_opt, True);
8981 		XtSetSensitive(widgets.options.minbrate_opt, True);
8982 		XtSetSensitive(widgets.options.maxbrate_opt, True);
8983 		break;
8984 
8985 	case COMPMODE_0:
8986 	default:
8987 		XtSetSensitive(widgets.options.qualval_lbl, False);
8988 		XtSetSensitive(widgets.options.qualval1_lbl, False);
8989 		XtSetSensitive(widgets.options.qualval2_lbl, False);
8990 		XtSetSensitive(widgets.options.qualval_scl, False);
8991 		XtSetSensitive(widgets.options.bitrate_opt, True);
8992 		XtSetSensitive(widgets.options.minbrate_opt, False);
8993 		XtSetSensitive(widgets.options.maxbrate_opt, False);
8994 		break;
8995 	}
8996 
8997 	/* Make the Reset and Save buttons sensitive */
8998 	XtSetSensitive(widgets.options.reset_btn, True);
8999 	XtSetSensitive(widgets.options.save_btn, True);
9000 }
9001 
9002 
9003 /*
9004  * cd_bitrate
9005  *	CBR/ABR bitrate menu callback function
9006  */
9007 /*ARGSUSED*/
9008 void
cd_bitrate(Widget w,XtPointer client_data,XtPointer call_data)9009 cd_bitrate(Widget w, XtPointer client_data, XtPointer call_data)
9010 {
9011 	XmPushButtonCallbackStruct
9012 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
9013 	wlist_t		*l;
9014 	Widget		menuw = (Widget) NULL;
9015 	int		m,
9016 			n;
9017 
9018 	if (p->reason != XmCR_ACTIVATE)
9019 		return;
9020 
9021 	if (w == widgets.options.bitrate_def_btn)
9022 		n = 0;
9023 	else if ((n = atoi(XtName(w))) == 0)
9024 		return;
9025 
9026 	DBGPRN(DBG_UI)(errfp, "\n* Bitrate: %d kb/s\n", n);
9027 	app_data.bitrate = n;
9028 
9029 	if (n == 0) {
9030 		/* Bitrate set to default: set the minimum and maximum to
9031 		 * default too.
9032 		 */
9033 		if (app_data.bitrate_min > 0) {
9034 			XtVaSetValues(widgets.options.minbrate_opt,
9035 				XmNmenuHistory,
9036 				widgets.options.minbrate_def_btn,
9037 				NULL
9038 			);
9039 			app_data.bitrate_min = 0;
9040 			DBGPRN(DBG_UI)(errfp,
9041 					"  Minimum bitrate reset to %d\n",
9042 					app_data.bitrate_min);
9043 		}
9044 		if (app_data.bitrate_max > 0) {
9045 			XtVaSetValues(widgets.options.maxbrate_opt,
9046 				XmNmenuHistory,
9047 				widgets.options.maxbrate_def_btn,
9048 				NULL
9049 			);
9050 			app_data.bitrate_max = 0;
9051 			DBGPRN(DBG_UI)(errfp,
9052 					"  Maximum bitrate reset to %d\n",
9053 					app_data.bitrate_max);
9054 		}
9055 	}
9056 	else {
9057 		/* Adjust minimum and maximum bitrates accordingly */
9058 		if (app_data.bitrate_min > 0 && n <= app_data.bitrate_min) {
9059 			for (l = cd_minbrhead; l != NULL; l = l->next) {
9060 				if (l->w != (Widget) NULL) {
9061 					m = atoi(XtName(l->w));
9062 					if (m < n) {
9063 						menuw = l->w;
9064 						n = m;
9065 					}
9066 				}
9067 			}
9068 			if (menuw != NULL) {
9069 				n = atoi(XtName(menuw));
9070 				if (n != app_data.bitrate_min) {
9071 					XtVaSetValues(
9072 						widgets.options.minbrate_opt,
9073 						XmNmenuHistory, menuw,
9074 						NULL
9075 					);
9076 					app_data.bitrate_min = n;
9077 					DBGPRN(DBG_UI)(errfp,
9078 					    "  Minimum bitrate reset to %d\n",
9079 					    n);
9080 				}
9081 			}
9082 		}
9083 
9084 		n = app_data.bitrate;
9085 		if (app_data.bitrate_max > 0 && n >= app_data.bitrate_max) {
9086 			for (l = cd_maxbrhead; l != NULL; l = l->next) {
9087 				if (l->w != (Widget) NULL) {
9088 					m = atoi(XtName(l->w));
9089 					if (m > n) {
9090 						menuw = l->w;
9091 						n = m;
9092 					}
9093 				}
9094 			}
9095 			if (menuw != NULL) {
9096 				n = atoi(XtName(menuw));
9097 				if (n != app_data.bitrate_max) {
9098 					XtVaSetValues(
9099 						widgets.options.maxbrate_opt,
9100 						XmNmenuHistory, menuw,
9101 						NULL
9102 					);
9103 					app_data.bitrate_max = n;
9104 					DBGPRN(DBG_UI)(errfp,
9105 					    "  Maximum bitrate reset to %d\n",
9106 					    n);
9107 				}
9108 			}
9109 		}
9110 	}
9111 
9112 	/* Make the Reset and Save buttons sensitive */
9113 	XtSetSensitive(widgets.options.reset_btn, True);
9114 	XtSetSensitive(widgets.options.save_btn, True);
9115 }
9116 
9117 
9118 /*
9119  * cd_min_bitrate
9120  *	Minimum bitrate menu callback function
9121  */
9122 /*ARGSUSED*/
9123 void
cd_min_bitrate(Widget w,XtPointer client_data,XtPointer call_data)9124 cd_min_bitrate(Widget w, XtPointer client_data, XtPointer call_data)
9125 {
9126 	XmPushButtonCallbackStruct
9127 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
9128 	wlist_t		*l;
9129 	Widget		menuw = (Widget) NULL;
9130 	int		n;
9131 	bool_t		err = FALSE;
9132 
9133 	if (p->reason != XmCR_ACTIVATE)
9134 		return;
9135 
9136 	if (w == widgets.options.minbrate_def_btn)
9137 		n = 0;
9138 	else if ((n = atoi(XtName(w))) == 0)
9139 		return;
9140 
9141 	if (app_data.comp_mode == COMPMODE_3 &&
9142 	    app_data.bitrate > 0 && n > app_data.bitrate) {
9143 		/* Error: minimum bitrate must be less than
9144 		 * or equal to the average bitrate.
9145 		 */
9146 		CD_INFO(app_data.str_invbr_minmean);
9147 		err = TRUE;
9148 	}
9149 
9150 	if (n > 0 && app_data.bitrate_max > 0 && n > app_data.bitrate_max) {
9151 		/* Error: minimum bitrate must be less than or
9152 		 * equal to the max bitrate.
9153 		 */
9154 		CD_INFO(app_data.str_invbr_minmax);
9155 		err = TRUE;
9156 	}
9157 
9158 	if (err) {
9159 		/* Restore setting */
9160 		if (app_data.bitrate_min == 0) {
9161 			menuw = widgets.options.minbrate_def_btn;
9162 		}
9163 		else for (l = cd_minbrhead; l != NULL; l = l->next) {
9164 			if (l->w != (Widget) NULL &&
9165 			    atoi(XtName(l->w)) == app_data.bitrate_min) {
9166 				menuw = l->w;
9167 				break;
9168 			}
9169 		}
9170 		if (menuw != NULL) {
9171 			XtVaSetValues(widgets.options.minbrate_opt,
9172 				XmNmenuHistory, menuw,
9173 				NULL
9174 			);
9175 		}
9176 		return;
9177 	}
9178 
9179 	DBGPRN(DBG_UI)(errfp, "\n* Minimum bitrate: %d kb/s\n", n);
9180 	app_data.bitrate_min = n;
9181 
9182 	/* Make the Reset and Save buttons sensitive */
9183 	XtSetSensitive(widgets.options.reset_btn, True);
9184 	XtSetSensitive(widgets.options.save_btn, True);
9185 }
9186 
9187 
9188 /*
9189  * cd_max_bitrate
9190  *	Maximum bitrate menu callback function
9191  */
9192 /*ARGSUSED*/
9193 void
cd_max_bitrate(Widget w,XtPointer client_data,XtPointer call_data)9194 cd_max_bitrate(Widget w, XtPointer client_data, XtPointer call_data)
9195 {
9196 	XmPushButtonCallbackStruct
9197 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
9198 	wlist_t		*l;
9199 	Widget		menuw = (Widget) NULL;
9200 	int		n;
9201 	bool_t		err = FALSE;
9202 
9203 	if (p->reason != XmCR_ACTIVATE)
9204 		return;
9205 
9206 	if (w == widgets.options.minbrate_def_btn)
9207 		n = 0;
9208 	else if ((n = atoi(XtName(w))) == 0)
9209 		return;
9210 
9211 	if (app_data.comp_mode == COMPMODE_3 &&
9212 	    app_data.bitrate > 0 && n < app_data.bitrate) {
9213 		/* Error: maximum bitrate must be greater than
9214 		 * or equal to the average bitrate.
9215 		 */
9216 		CD_INFO(app_data.str_invbr_maxmean);
9217 		err = TRUE;
9218 	}
9219 
9220 	if (n > 0 && app_data.bitrate_min > 0 && n < app_data.bitrate_min) {
9221 		/* Error: maximum bitrate must be greater than
9222 		 * or equal to the min bitrate.
9223 		 */
9224 		CD_INFO(app_data.str_invbr_maxmin);
9225 		err = TRUE;
9226 	}
9227 
9228 	if (err) {
9229 		/* Restore setting */
9230 		if (app_data.bitrate_max == 0) {
9231 			menuw = widgets.options.maxbrate_def_btn;
9232 		}
9233 		else for (l = cd_maxbrhead; l != NULL; l = l->next) {
9234 			if (l->w != (Widget) NULL &&
9235 			    atoi(XtName(l->w)) == app_data.bitrate_max) {
9236 				menuw = l->w;
9237 				break;
9238 			}
9239 		}
9240 		if (menuw != NULL) {
9241 			XtVaSetValues(widgets.options.maxbrate_opt,
9242 				XmNmenuHistory, menuw,
9243 				NULL
9244 			);
9245 		}
9246 		return;
9247 	}
9248 
9249 	DBGPRN(DBG_UI)(errfp, "\n* Maximum bitrate: %d kb/s\n", n);
9250 	app_data.bitrate_max = n;
9251 
9252 	/* Make the Reset and Save buttons sensitive */
9253 	XtSetSensitive(widgets.options.reset_btn, True);
9254 	XtSetSensitive(widgets.options.save_btn, True);
9255 }
9256 
9257 
9258 /*
9259  * cd_qualfactor
9260  *	VBR Quality factor slider callback function
9261  */
9262 /*ARGSUSED*/
9263 void
cd_qualfactor(Widget w,XtPointer client_data,XtPointer call_data)9264 cd_qualfactor(Widget w, XtPointer client_data, XtPointer call_data)
9265 {
9266 	XmScaleCallbackStruct
9267 			*p = (XmScaleCallbackStruct *)(void *) call_data;
9268 	XmString	xs,
9269 			xs2;
9270 	char		str[16];
9271 
9272 	if (p->value < 1 || p->value > 10)
9273 		return;
9274 
9275 	if (p->value == app_data.qual_factor)
9276 		return;	/* No change */
9277 
9278 	DBGPRN(DBG_UI)(errfp, "\n* Quality factor: %d\n", p->value);
9279 	app_data.qual_factor = p->value;
9280 
9281 	(void) sprintf(str, ": %d", app_data.qual_factor);
9282 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
9283 	xs2 = XmStringConcat(xs_qual, xs);
9284 	XtVaSetValues(widgets.options.qualval_lbl,
9285 		XmNlabelString, xs2,
9286 		NULL
9287 	);
9288 	XmStringFree(xs2);
9289 	XmStringFree(xs);
9290 
9291 	/* Make the Reset and Save buttons sensitive */
9292 	XtSetSensitive(widgets.options.reset_btn, True);
9293 	XtSetSensitive(widgets.options.save_btn, True);
9294 }
9295 
9296 
9297 /*
9298  * cd_chanmode
9299  *	Channel mode menu callback function
9300  */
9301 /*ARGSUSED*/
9302 void
cd_chanmode(Widget w,XtPointer client_data,XtPointer call_data)9303 cd_chanmode(Widget w, XtPointer client_data, XtPointer call_data)
9304 {
9305 	XmPushButtonCallbackStruct
9306 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
9307 	char		*modestr;
9308 	int		newmode;
9309 
9310 	if (p->reason != XmCR_ACTIVATE)
9311 		return;
9312 
9313 	if (w == widgets.options.chmode_stereo_btn) {
9314 		newmode = CH_STEREO;
9315 		modestr = "stereo";
9316 	}
9317 	else if (w == widgets.options.chmode_jstereo_btn) {
9318 		newmode = CH_JSTEREO;
9319 		modestr = "joint-stereo";
9320 	}
9321 	else if (w == widgets.options.chmode_forcems_btn) {
9322 		newmode = CH_FORCEMS;
9323 		modestr = "force-ms";
9324 	}
9325 	else if (w == widgets.options.chmode_mono_btn) {
9326 		newmode = CH_MONO;
9327 		modestr = "mono";
9328 	}
9329 	else
9330 		return;
9331 
9332 	if (app_data.chan_mode == newmode)
9333 		return;	/* No change */
9334 
9335 	DBGPRN(DBG_UI)(errfp, "\n* Channel mode: %s\n", modestr);
9336 	app_data.chan_mode = newmode;
9337 
9338 	/* Make the Reset and Save buttons sensitive */
9339 	XtSetSensitive(widgets.options.reset_btn, True);
9340 	XtSetSensitive(widgets.options.save_btn, True);
9341 }
9342 
9343 
9344 /*
9345  * cd_compalgo
9346  *	Compression algorithm slider callback function
9347  */
9348 /*ARGSUSED*/
9349 void
cd_compalgo(Widget w,XtPointer client_data,XtPointer call_data)9350 cd_compalgo(Widget w, XtPointer client_data, XtPointer call_data)
9351 {
9352 	XmScaleCallbackStruct
9353 			*p = (XmScaleCallbackStruct *)(void *) call_data;
9354 	XmString	xs,
9355 			xs2;
9356 	char		str[16];
9357 
9358 	if (p->value < 1 || p->value > 10)
9359 		return;
9360 
9361 	if (p->value == app_data.comp_algo)
9362 		return;	/* No change */
9363 
9364 	DBGPRN(DBG_UI)(errfp, "\n* Compression algorithm: %d\n", p->value);
9365 	app_data.comp_algo = p->value;
9366 
9367 	(void) sprintf(str, ": %d", app_data.comp_algo);
9368 	xs = create_xmstring(str, NULL, XmSTRING_DEFAULT_CHARSET, FALSE);
9369 	xs2 = XmStringConcat(xs_algo, xs);
9370 	XtVaSetValues(widgets.options.compalgo_lbl,
9371 		XmNlabelString, xs2,
9372 		NULL
9373 	);
9374 	XmStringFree(xs2);
9375 	XmStringFree(xs);
9376 
9377 	/* Make the Reset and Save buttons sensitive */
9378 	XtSetSensitive(widgets.options.reset_btn, True);
9379 	XtSetSensitive(widgets.options.save_btn, True);
9380 }
9381 
9382 
9383 /*
9384  * cd_lowpass_mode
9385  *	Lowpass filter mode menu callback function
9386  */
9387 /*ARGSUSED*/
9388 void
cd_lowpass_mode(Widget w,XtPointer client_data,XtPointer call_data)9389 cd_lowpass_mode(Widget w, XtPointer client_data, XtPointer call_data)
9390 {
9391 	XmPushButtonCallbackStruct
9392 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
9393 	char		*modestr;
9394 	int		newmode;
9395 
9396 	if (p->reason != XmCR_ACTIVATE)
9397 		return;
9398 
9399 	if (w == widgets.options.lp_off_btn) {
9400 		newmode = FILTER_OFF;
9401 		modestr = "Off";
9402 	}
9403 	else if (w == widgets.options.lp_auto_btn) {
9404 		newmode = FILTER_AUTO;
9405 		modestr = "Auto";
9406 	}
9407 	else if (w == widgets.options.lp_manual_btn) {
9408 		newmode = FILTER_MANUAL;
9409 		modestr = "Manual";
9410 	}
9411 	else
9412 		return;
9413 
9414 	if (app_data.lowpass_mode == newmode)
9415 		return;	/* No change */
9416 
9417 	DBGPRN(DBG_UI)(errfp, "\n* Lowpass filter: %s\n", modestr);
9418 	app_data.lowpass_mode = newmode;
9419 
9420 	XtSetSensitive(
9421 		widgets.options.lp_freq_txt,
9422 		(Boolean) (newmode == FILTER_MANUAL)
9423 	);
9424 	XtSetSensitive(
9425 		widgets.options.lp_width_txt,
9426 		(Boolean) (newmode == FILTER_MANUAL)
9427 	);
9428 
9429 	/* Make the Reset and Save buttons sensitive */
9430 	XtSetSensitive(widgets.options.reset_btn, True);
9431 	XtSetSensitive(widgets.options.save_btn, True);
9432 }
9433 
9434 
9435 /*
9436  * cd_highpass_mode
9437  *	Highpass filter mode menu callback function
9438  */
9439 /*ARGSUSED*/
9440 void
cd_highpass_mode(Widget w,XtPointer client_data,XtPointer call_data)9441 cd_highpass_mode(Widget w, XtPointer client_data, XtPointer call_data)
9442 {
9443 	XmPushButtonCallbackStruct
9444 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
9445 	char		*modestr;
9446 	int		newmode;
9447 
9448 	if (p->reason != XmCR_ACTIVATE)
9449 		return;
9450 
9451 	if (w == widgets.options.hp_off_btn) {
9452 		newmode = FILTER_OFF;
9453 		modestr = "Off";
9454 	}
9455 	else if (w == widgets.options.hp_auto_btn) {
9456 		newmode = FILTER_AUTO;
9457 		modestr = "Auto";
9458 	}
9459 	else if (w == widgets.options.hp_manual_btn) {
9460 		newmode = FILTER_MANUAL;
9461 		modestr = "Manual";
9462 	}
9463 	else
9464 		return;
9465 
9466 	if (app_data.highpass_mode == newmode)
9467 		return;	/* No change */
9468 
9469 	DBGPRN(DBG_UI)(errfp, "\n* Highpass filter: %s\n", modestr);
9470 	app_data.highpass_mode = newmode;
9471 
9472 	XtSetSensitive(
9473 		widgets.options.hp_freq_txt,
9474 		(Boolean) (newmode == FILTER_MANUAL)
9475 	);
9476 	XtSetSensitive(
9477 		widgets.options.hp_width_txt,
9478 		(Boolean) (newmode == FILTER_MANUAL)
9479 	);
9480 
9481 	/* Make the Reset and Save buttons sensitive */
9482 	XtSetSensitive(widgets.options.reset_btn, True);
9483 	XtSetSensitive(widgets.options.save_btn, True);
9484 }
9485 
9486 
9487 /*
9488  * cd_filter_freq
9489  *	Lowpass and highpass filter frequency text field callback
9490  */
9491 /*ARGSUSED*/
9492 void
cd_filter_freq(Widget w,XtPointer client_data,XtPointer call_data)9493 cd_filter_freq(Widget w, XtPointer client_data, XtPointer call_data)
9494 {
9495 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
9496 	char			*cp,
9497 				str[16];
9498 	int			newfreq;
9499 
9500 	if (p->reason == XmCR_VALUE_CHANGED) {
9501 		/* Make the Reset and Save buttons sensitive */
9502 		XtSetSensitive(widgets.options.reset_btn, True);
9503 		XtSetSensitive(widgets.options.save_btn, True);
9504 		return;
9505 	}
9506 	else if (p->reason != XmCR_ACTIVATE && p->reason != XmCR_LOSING_FOCUS)
9507 		return;
9508 
9509 	if ((cp = get_text_string(w, FALSE)) != NULL) {
9510 		newfreq = atoi(cp);
9511 		MEM_FREE(cp);
9512 	}
9513 	else
9514 		newfreq = 0;
9515 
9516 	/* Set cursor to beginning of text */
9517 	XmTextSetInsertionPosition(w, 0);
9518 
9519 	if (w == widgets.options.lp_freq_txt) {
9520 		if (app_data.lowpass_freq == newfreq)
9521 			return;	/* No change */
9522 
9523 		if (newfreq < MIN_LOWPASS_FREQ ||
9524 		    newfreq > MAX_LOWPASS_FREQ) {
9525 			(void) sprintf(str, "%d", app_data.lowpass_freq);
9526 			set_text_string(w, str, FALSE);
9527 
9528 			/* Set cursor to beginning of text */
9529 			XmTextSetInsertionPosition(w, 0);
9530 
9531 			cp = (char *) MEM_ALLOC(
9532 				"str_invfreq_lp",
9533 				strlen(app_data.str_invfreq_lp) + 16
9534 			);
9535 			if (cp == NULL) {
9536 				CD_FATAL(app_data.str_nomemory);
9537 				return;
9538 			}
9539 			(void) sprintf(cp, app_data.str_invfreq_lp,
9540 					MIN_LOWPASS_FREQ, MAX_LOWPASS_FREQ);
9541 			CD_INFO(cp);
9542 			MEM_FREE(cp);
9543 			return;
9544 		}
9545 
9546 		app_data.lowpass_freq = newfreq;
9547 		cp = "Lowpass";
9548 	}
9549 	else if (w == widgets.options.hp_freq_txt) {
9550 		if (app_data.highpass_freq == newfreq)
9551 			return;	/* No change */
9552 
9553 		if (newfreq < MIN_HIGHPASS_FREQ ||
9554 		    newfreq > MAX_HIGHPASS_FREQ) {
9555 			(void) sprintf(str, "%d", app_data.highpass_freq);
9556 			set_text_string(w, str, FALSE);
9557 
9558 			/* Set cursor to beginning of text */
9559 			XmTextSetInsertionPosition(w, 0);
9560 
9561 			cp = (char *) MEM_ALLOC(
9562 				"str_invfreq_hp",
9563 				strlen(app_data.str_invfreq_hp) + 16
9564 			);
9565 			if (cp == NULL) {
9566 				CD_FATAL(app_data.str_nomemory);
9567 				return;
9568 			}
9569 			(void) sprintf(cp, app_data.str_invfreq_hp,
9570 					MIN_HIGHPASS_FREQ, MAX_HIGHPASS_FREQ);
9571 			CD_INFO(cp);
9572 			MEM_FREE(cp);
9573 			return;
9574 		}
9575 
9576 		app_data.highpass_freq = newfreq;
9577 		cp = "Highpass";
9578 	}
9579 
9580 	DBGPRN(DBG_UI)(errfp, "\n* %s filter frequency: %d\n", cp, newfreq);
9581 }
9582 
9583 
9584 /*
9585  * cd_filter_width
9586  *	Lowpass and highpass filter width text field callback
9587  */
9588 /*ARGSUSED*/
9589 void
cd_filter_width(Widget w,XtPointer client_data,XtPointer call_data)9590 cd_filter_width(Widget w, XtPointer client_data, XtPointer call_data)
9591 {
9592 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
9593 	char			*cp;
9594 	int			newwidth;
9595 
9596 	if (p->reason == XmCR_VALUE_CHANGED) {
9597 		/* Make the Reset and Save buttons sensitive */
9598 		XtSetSensitive(widgets.options.reset_btn, True);
9599 		XtSetSensitive(widgets.options.save_btn, True);
9600 		return;
9601 	}
9602 	else if (p->reason != XmCR_ACTIVATE && p->reason != XmCR_LOSING_FOCUS)
9603 		return;
9604 
9605 	if ((cp = get_text_string(w, FALSE)) != NULL) {
9606 		newwidth = atoi(cp);
9607 		MEM_FREE(cp);
9608 	}
9609 	else
9610 		newwidth = 0;
9611 
9612 	/* Set cursor to beginning of text */
9613 	XmTextSetInsertionPosition(w, 0);
9614 
9615 	if (newwidth < 0) {
9616 		set_text_string(w, "0", FALSE);
9617 
9618 		/* Set cursor to beginning of text */
9619 		XmTextSetInsertionPosition(w, 0);
9620 
9621 		XmProcessTraversal(w, XmTRAVERSE_CURRENT);
9622 		return;
9623 	}
9624 
9625 	if (w == widgets.options.lp_width_txt) {
9626 		if (app_data.lowpass_width == newwidth)
9627 			return;	/* No change */
9628 
9629 		app_data.lowpass_width = newwidth;
9630 		cp = "Lowpass";
9631 	}
9632 	else if (w == widgets.options.hp_width_txt) {
9633 		if (app_data.highpass_width == newwidth)
9634 			return;	/* No change */
9635 
9636 		app_data.highpass_width = newwidth;
9637 		cp = "Highpass";
9638 	}
9639 
9640 	DBGPRN(DBG_UI)(errfp, "\n* %s filter width: %d\n", cp, newwidth);
9641 }
9642 
9643 
9644 /*
9645  * cd_addflag
9646  *	Options window Copyright/Original/No res/Checksum flag
9647  *	toggle buttons callback
9648  */
9649 /*ARGSUSED*/
9650 void
cd_addflag(Widget w,XtPointer client_data,XtPointer call_data)9651 cd_addflag(Widget w, XtPointer client_data, XtPointer call_data)
9652 {
9653 	XmToggleButtonCallbackStruct	*p =
9654 		(XmToggleButtonCallbackStruct *)(void *) call_data;
9655 	char				*item;
9656 
9657 	if (p->reason != XmCR_VALUE_CHANGED)
9658 		return;
9659 
9660 	if (w == widgets.options.copyrt_btn) {
9661 		app_data.copyright = (bool_t) p->set;
9662 		item = "Copyright";
9663 	}
9664 	else if (w == widgets.options.orig_btn) {
9665 		app_data.original = (bool_t) p->set;
9666 		item = "Original";
9667 	}
9668 	else if (w == widgets.options.nores_btn) {
9669 		app_data.nores = (bool_t) p->set;
9670 		item = "No bit reservoir";
9671 	}
9672 	else if (w == widgets.options.chksum_btn) {
9673 		app_data.checksum = (bool_t) p->set;
9674 		item = "Checksum";
9675 	}
9676 	else if (w == widgets.options.iso_btn) {
9677 		app_data.strict_iso = (bool_t) p->set;
9678 		item = "Strict-ISO";
9679 	}
9680 	else {
9681 		/* Invalid button */
9682 		return;
9683 	}
9684 
9685 	DBGPRN(DBG_UI)(errfp, "\n* %s flag: %s\n",
9686 			item, p->set ? "Enable" : "Disable");
9687 
9688 	/* Make the Reset and Save buttons sensitive */
9689 	XtSetSensitive(widgets.options.reset_btn, True);
9690 	XtSetSensitive(widgets.options.save_btn, True);
9691 }
9692 
9693 
9694 /*
9695  * cd_addtag
9696  *	Options window Add ID3/comment tag toggle buttons callback
9697  */
9698 /*ARGSUSED*/
9699 void
cd_addtag(Widget w,XtPointer client_data,XtPointer call_data)9700 cd_addtag(Widget w, XtPointer client_data, XtPointer call_data)
9701 {
9702 	XmToggleButtonCallbackStruct	*p =
9703 		(XmToggleButtonCallbackStruct *)(void *) call_data;
9704 
9705 	if (p->reason != XmCR_VALUE_CHANGED)
9706 		return;
9707 
9708 	if (app_data.add_tag == (bool_t) p->set)
9709 		return;	/* No change */
9710 
9711 	DBGPRN(DBG_UI)(errfp, "\n* Add ID3/comment tag: %s\n",
9712 			p->set ? "Enable" : "Disable");
9713 	app_data.add_tag = (bool_t) p->set;
9714 
9715 	/* Change the ID3 tag version option menu sensitivity */
9716 	XtSetSensitive(widgets.options.id3tag_ver_opt, p->set);
9717 
9718 	/* Make the Reset and Save buttons sensitive */
9719 	XtSetSensitive(widgets.options.reset_btn, True);
9720 	XtSetSensitive(widgets.options.save_btn, True);
9721 }
9722 
9723 
9724 /*
9725  * cd_id3tag_mode
9726  *	ID3 tag mode menu callback function
9727  */
9728 /*ARGSUSED*/
9729 void
cd_id3tag_mode(Widget w,XtPointer client_data,XtPointer call_data)9730 cd_id3tag_mode(Widget w, XtPointer client_data, XtPointer call_data)
9731 {
9732 	XmPushButtonCallbackStruct
9733 			*p = (XmPushButtonCallbackStruct *)(void *) call_data;
9734 	char		*verstr;
9735 	int		newver;
9736 
9737 	if (p->reason != XmCR_ACTIVATE)
9738 		return;
9739 
9740 	if (w == widgets.options.id3tag_v1_btn) {
9741 		newver = ID3TAG_V1;
9742 		verstr = "version 1";
9743 	}
9744 	else if (w == widgets.options.id3tag_v2_btn) {
9745 		newver = ID3TAG_V2;
9746 		verstr = "version 2";
9747 	}
9748 	else if (w == widgets.options.id3tag_both_btn) {
9749 		newver = ID3TAG_BOTH;
9750 		verstr = "version 1 and 2";
9751 	}
9752 	else
9753 		return;
9754 
9755 	if (app_data.id3tag_mode == newver)
9756 		return;	/* No change */
9757 
9758 	DBGPRN(DBG_UI)(errfp, "\n* ID3 tag: %s\n", verstr);
9759 	app_data.id3tag_mode = newver;
9760 
9761 	/* Make the Reset and Save buttons sensitive */
9762 	XtSetSensitive(widgets.options.reset_btn, True);
9763 	XtSetSensitive(widgets.options.save_btn, True);
9764 }
9765 
9766 
9767 /*
9768  * cd_set_lameopts
9769  *	LAME options text widget callback function
9770  */
9771 /*ARGSUSED*/
9772 void
cd_set_lameopts(Widget w,XtPointer client_data,XtPointer call_data)9773 cd_set_lameopts(Widget w, XtPointer client_data, XtPointer call_data)
9774 {
9775 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
9776 	char			*cp;
9777 
9778 	if (p->reason == XmCR_VALUE_CHANGED) {
9779 		/* Make the Reset and Save buttons sensitive */
9780 		XtSetSensitive(widgets.options.reset_btn, True);
9781 		XtSetSensitive(widgets.options.save_btn, True);
9782 		return;
9783 	}
9784 	if (p->reason != XmCR_ACTIVATE && p->reason != XmCR_LOSING_FOCUS)
9785 		return;
9786 
9787 	if ((cp = get_text_string(w, FALSE)) == NULL)
9788 		return;
9789 
9790 	/* Set cursor to beginning of text */
9791 	XmTextSetInsertionPosition(w, 0);
9792 
9793 	if (app_data.lame_opts != NULL &&
9794 	    strcmp(app_data.lame_opts, cp) == 0) {
9795 		MEM_FREE(cp);
9796 		return;		/* No change */
9797 	}
9798 
9799 	if (!util_newstr(&app_data.lame_opts, cp)) {
9800 		MEM_FREE(cp);
9801 		CD_FATAL(app_data.str_nomemory);
9802 		return;
9803 	}
9804 
9805 	DBGPRN(DBG_UI)(errfp, "\n* LAME options: %s\n", cp);
9806 
9807 	MEM_FREE(cp);
9808 }
9809 
9810 
9811 /*
9812  * cd_balance
9813  *	Balance control slider callback function
9814  */
9815 /*ARGSUSED*/
9816 void
cd_balance(Widget w,XtPointer client_data,XtPointer call_data)9817 cd_balance(Widget w, XtPointer client_data, XtPointer call_data)
9818 {
9819 	XmScaleCallbackStruct
9820 			*p = (XmScaleCallbackStruct *)(void *) call_data;
9821 	curstat_t	*s = (curstat_t *)(void *) client_data;
9822 
9823 	if (p->value == 0) {
9824 		/* Center setting */
9825 		s->level_left = s->level_right = 100;
9826 	}
9827 	else if (p->value < 0) {
9828 		/* Attenuate the right channel */
9829 		s->level_left = 100;
9830 		s->level_right = 100 + (p->value * 2);
9831 	}
9832 	else {
9833 		/* Attenuate the left channel */
9834 		s->level_left = 100 - (p->value * 2);
9835 		s->level_right = 100;
9836 	}
9837 
9838 	di_level(
9839 		s,
9840 		(byte_t) s->level,
9841 		(bool_t) (p->reason != XmCR_VALUE_CHANGED)
9842 	);
9843 
9844 	DBGPRN(DBG_UI)(errfp, "\n* BALANCE: %d/%d\n",
9845 		       (int) s->level_left, (int) s->level_right);
9846 }
9847 
9848 
9849 /*
9850  * cd_balance_center
9851  *	Balance control center button callback function
9852  */
9853 /*ARGSUSED*/
9854 void
cd_balance_center(Widget w,XtPointer client_data,XtPointer call_data)9855 cd_balance_center(Widget w, XtPointer client_data, XtPointer call_data)
9856 {
9857 	curstat_t	*s = (curstat_t *)(void *) client_data;
9858 
9859 	/* Force the balance control to the center position */
9860 	set_bal_slider(0);
9861 
9862 	s->level_left  = 100;
9863 	s->level_right = 100;
9864 
9865 	di_level(s, (byte_t) s->level, FALSE);
9866 
9867 	DBGPRN(DBG_UI)(errfp, "\n* BALANCE: center\n");
9868 }
9869 
9870 
9871 /*
9872  * cd_cdda_att
9873  *	CDDA attentuator slider callback function
9874  */
9875 /*ARGSUSED*/
9876 void
cd_cdda_att(Widget w,XtPointer client_data,XtPointer call_data)9877 cd_cdda_att(Widget w, XtPointer client_data, XtPointer call_data)
9878 {
9879 	XmScaleCallbackStruct
9880 			*p = (XmScaleCallbackStruct *)(void *) call_data;
9881 	curstat_t	*s = (curstat_t *)(void *) client_data;
9882 
9883 	if (cdda_fader_id > 0) {
9884 		cd_untimeout(cdda_fader_id);
9885 		cdda_fader_mode = CDDA_FADER_NONE;
9886 	}
9887 
9888 	if ((int) s->cdda_att == p->value)
9889 		return;	/* No change */
9890 
9891 	s->cdda_att = (byte_t) p->value;
9892 	cdda_att(s);
9893 
9894 	DBGPRN(DBG_UI)(errfp, "\n* CDDA ATTENUATOR: %d\n", (int) s->cdda_att);
9895 }
9896 
9897 
9898 /*
9899  * cd_cdda_fade
9900  *	CDDA fader buttons callback function
9901  */
9902 /*ARGSUSED*/
9903 void
cd_cdda_fade(Widget w,XtPointer client_data,XtPointer call_data)9904 cd_cdda_fade(Widget w, XtPointer client_data, XtPointer call_data)
9905 {
9906 	curstat_t	*s = (curstat_t *)(void *) client_data;
9907 	bool_t		fadeout = FALSE;
9908 
9909 	fadeout = (bool_t) (w != widgets.options.cdda_fadein_btn);
9910 
9911 	if (fadeout && (int) s->cdda_att == 0)
9912 		return;	/* No change */
9913 	else if (!fadeout && (int) s->cdda_att == 100)
9914 		return;	/* No change */
9915 
9916 	if ((fadeout && cdda_fader_mode == CDDA_FADER_OUT) ||
9917 	    (!fadeout && cdda_fader_mode == CDDA_FADER_IN)) {
9918 		/* Already fading: speed up fader */
9919 		if (cdda_fader_intvl > 20)
9920 			cdda_fader_intvl >>= 1;
9921 		DBGPRN(DBG_UI)(errfp, "\n* CDDA FADE-%s (speedup)\n",
9922 				fadeout ? "OUT" : "IN");
9923 	}
9924 	else {
9925 		/* Start fading at default speed */
9926 		cdda_fader_intvl = CDDA_FADER_INTVL;
9927 		DBGPRN(DBG_UI)(errfp, "\n* CDDA FADE-%s\n",
9928 				fadeout ? "IN" : "OUT");
9929 	}
9930 
9931 	cdda_fader_mode = fadeout ? CDDA_FADER_OUT : CDDA_FADER_IN;
9932 
9933 	if (cdda_fader_id > 0)
9934 		cd_untimeout(cdda_fader_id);
9935 
9936 	cdda_fader_id = cd_timeout(
9937 		cdda_fader_intvl,
9938 		fadeout ? cdda_fadeout : cdda_fadein,
9939 		(byte_t *) s
9940 	);
9941 }
9942 
9943 
9944 /*
9945  * cd_set_timeouts
9946  *	Heartbeat/server/cache timeout text widgets callback
9947  */
9948 /*ARGSUSED*/
9949 void
cd_set_timeouts(Widget w,XtPointer client_data,XtPointer call_data)9950 cd_set_timeouts(Widget w, XtPointer client_data, XtPointer call_data)
9951 {
9952 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
9953 	int			*valp,
9954 				val,
9955 				min_val;
9956 	char			*cp,
9957 				*name,
9958 				str[16];
9959 
9960 	if (p->reason == XmCR_VALUE_CHANGED) {
9961 		/* Make the Reset and Save buttons sensitive */
9962 		XtSetSensitive(widgets.options.reset_btn, True);
9963 		XtSetSensitive(widgets.options.save_btn, True);
9964 		return;
9965 	}
9966 	else if (p->reason != XmCR_ACTIVATE && p->reason != XmCR_LOSING_FOCUS)
9967 		return;
9968 
9969 	if ((cp = get_text_string(w, FALSE)) == NULL)
9970 		return;
9971 
9972 	/* Set cursor to beginning of text */
9973 	XmTextSetInsertionPosition(w, 0);
9974 
9975 	if (w == widgets.options.hb_tout_txt) {
9976 		valp = &app_data.hb_timeout;
9977 		min_val = MIN_HB_TIMEOUT;
9978 		name = "Heartbeat";
9979 	}
9980 	else if (w == widgets.options.serv_tout_txt) {
9981 		valp = &app_data.srv_timeout;
9982 		min_val = 0;
9983 		name = "Service";
9984 	}
9985 	else if (w == widgets.options.cache_tout_txt) {
9986 		valp = &app_data.cache_timeout;
9987 		min_val = 0;
9988 		name = "Cache";
9989 	}
9990 	else {
9991 		MEM_FREE(cp);
9992 		return;
9993 	}
9994 
9995 	if ((val = atoi(cp)) < min_val) {
9996 		cd_beep();
9997 
9998 		(void) sprintf(str, "%d", *valp);
9999 		set_text_string(w, str, FALSE);
10000 
10001 		/* Set cursor to beginning of text */
10002 		XmTextSetInsertionPosition(w, 0);
10003 
10004 		MEM_FREE(cp);
10005 		return;
10006 	}
10007 	else if (val == *valp) {
10008 		MEM_FREE(cp);
10009 		return;		/* No change */
10010 	}
10011 
10012 	DBGPRN(DBG_UI)(errfp, "\n* %s timeout: %d\n", name, val);
10013 	*valp = val;
10014 
10015 	MEM_FREE(cp);
10016 }
10017 
10018 
10019 /*
10020  * cd_set_servers
10021  *	Proxy server/port text widgets callback
10022  */
10023 /*ARGSUSED*/
10024 void
cd_set_servers(Widget w,XtPointer client_data,XtPointer call_data)10025 cd_set_servers(Widget w, XtPointer client_data, XtPointer call_data)
10026 {
10027 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
10028 	char			*cp,
10029 				*cp2 = NULL,
10030 				str[STR_BUF_SZ * 2];
10031 	int			port;
10032 
10033 	if (p->reason == XmCR_VALUE_CHANGED) {
10034 		/* Make the Reset and Save buttons sensitive */
10035 		XtSetSensitive(widgets.options.reset_btn, True);
10036 		XtSetSensitive(widgets.options.save_btn, True);
10037 		return;
10038 	}
10039 	if (p->reason != XmCR_ACTIVATE && p->reason != XmCR_LOSING_FOCUS)
10040 		return;
10041 
10042 	if ((cp = get_text_string(w, FALSE)) == NULL)
10043 		return;
10044 
10045 	if (app_data.proxy_server != NULL)
10046 		cp2 = strrchr(app_data.proxy_server, ':');
10047 
10048 	if (cp2 != NULL)
10049 		*cp2++ = '\0';
10050 	else
10051 		cp2 = "80";
10052 
10053 	/* Set cursor to beginning of text */
10054 	XmTextSetInsertionPosition(w, 0);
10055 
10056 	if (w == widgets.options.proxy_srvr_txt) {
10057 		if ((port = atoi(cp2)) < 0)
10058 			port = 80;
10059 
10060 		(void) sprintf(str, "%.64s:%d", cp, port);
10061 	}
10062 	else if (w == widgets.options.proxy_port_txt) {
10063 		if ((port = atoi(cp)) < 0) {
10064 			cd_beep();
10065 
10066 			set_text_string(w, cp2, FALSE);
10067 
10068 			/* Set cursor to beginning of text */
10069 			XmTextSetInsertionPosition(w, 0);
10070 			return;
10071 		}
10072 
10073 		(void) sprintf(str, "%.64s:%d",
10074 			(app_data.proxy_server == NULL) ?
10075 				"" : app_data.proxy_server,
10076 			port
10077 		);
10078 	}
10079 
10080 	MEM_FREE(cp);
10081 
10082 	if (app_data.proxy_server != NULL &&
10083 	    strcmp(app_data.proxy_server, str) == 0)
10084 		return;		/* No change */
10085 
10086 	if (!util_newstr(&app_data.proxy_server, str)) {
10087 		CD_FATAL(app_data.str_nomemory);
10088 		return;
10089 	}
10090 
10091 	DBGPRN(DBG_UI)(errfp, "\n* Proxy server: %s\n", str);
10092 }
10093 
10094 
10095 /*
10096  * cd_set_proxy
10097  *	CDDB proxy server toggle buttons callback
10098  */
10099 /*ARGSUSED*/
10100 void
cd_set_proxy(Widget w,XtPointer client_data,XtPointer call_data)10101 cd_set_proxy(Widget w, XtPointer client_data, XtPointer call_data)
10102 {
10103 	XmToggleButtonCallbackStruct	*p =
10104 		(XmToggleButtonCallbackStruct *)(void *) call_data;
10105 
10106 	if (p->reason != XmCR_VALUE_CHANGED)
10107 		return;
10108 
10109 	if (w == widgets.options.use_proxy_btn) {
10110 		if (app_data.use_proxy == (bool_t) p->set)
10111 			return;	/* No change */
10112 
10113 		DBGPRN(DBG_UI)(errfp, "\n* Use proxy: %s\n",
10114 				p->set ? "Enable" : "Disable");
10115 		app_data.use_proxy = (bool_t) p->set;
10116 
10117 		/* Set the sensitivity of related widgets */
10118 		XtSetSensitive(widgets.options.proxy_srvr_lbl, p->set);
10119 		XtSetSensitive(widgets.options.proxy_srvr_txt, p->set);
10120 		XtSetSensitive(widgets.options.proxy_port_lbl, p->set);
10121 		XtSetSensitive(widgets.options.proxy_port_txt, p->set);
10122 		XtSetSensitive(widgets.options.proxy_auth_btn, p->set);
10123 	}
10124 	else if (w == widgets.options.proxy_auth_btn) {
10125 		if (app_data.proxy_auth == (bool_t) p->set)
10126 			return;	/* No change */
10127 
10128 		DBGPRN(DBG_UI)(errfp, "\n* Proxy authorization: %s\n",
10129 				p->set ? "Enable" : "Disable");
10130 		app_data.proxy_auth = (bool_t) p->set;
10131 	}
10132 
10133 	/* Make the Reset and Save buttons sensitive */
10134 	XtSetSensitive(widgets.options.reset_btn, True);
10135 	XtSetSensitive(widgets.options.save_btn, True);
10136 }
10137 
10138 
10139 /*
10140  * cd_perfmon
10141  *	CDDA Performance Monitor button callback function
10142  */
10143 /*ARGSUSED*/
10144 void
cd_perfmon_popdown(Widget w,XtPointer client_data,XtPointer call_data)10145 cd_perfmon_popdown(Widget w, XtPointer client_data, XtPointer call_data)
10146 {
10147 	if (XtIsManaged(widgets.perfmon.form)) {
10148 		/* Pop down performance monitor window */
10149 		XtUnmapWidget(XtParent(widgets.perfmon.form));
10150 		XtUnmanageChild(widgets.perfmon.form);
10151 	}
10152 }
10153 
10154 
10155 /*
10156  * cd_perfmon
10157  *	CDDA Performance Monitor button callback function
10158  */
10159 /*ARGSUSED*/
10160 void
cd_perfmon(Widget w,XtPointer client_data,XtPointer call_data)10161 cd_perfmon(Widget w, XtPointer client_data, XtPointer call_data)
10162 {
10163 	static bool_t	first = TRUE;
10164 
10165 	if (XtIsManaged(widgets.perfmon.form)) {
10166 		cd_perfmon_popdown(w, client_data, call_data);
10167 		return;
10168 	}
10169 
10170 	/* Pop up performance monitor window.
10171 	 * The dialog has mappedWhenManaged set to False,
10172 	 * so we have to map/unmap explicitly.  The reason for this
10173 	 * is we want to avoid a screen glitch when we move the window
10174 	 * in cd_dialog_setpos(), so we map the window afterwards.
10175 	 */
10176 	XtManageChild(widgets.perfmon.form);
10177 	if (first) {
10178 		first = FALSE;
10179 		/* Set window position */
10180 		cd_dialog_setpos(XtParent(widgets.perfmon.form));
10181 	}
10182 	XtMapWidget(XtParent(widgets.perfmon.form));
10183 
10184 	XmProcessTraversal(
10185 		widgets.perfmon.cancel_btn,
10186 		XmTRAVERSE_CURRENT
10187 	);
10188 }
10189 
10190 
10191 /*
10192  * cd_txtline_vfy
10193  *	Single-line text widget user-input verification callback.
10194  */
10195 /*ARGSUSED*/
10196 void
cd_txtline_vfy(Widget w,XtPointer client_data,XtPointer call_data)10197 cd_txtline_vfy(Widget w, XtPointer client_data, XtPointer call_data)
10198 {
10199 	XmTextVerifyCallbackStruct
10200 		*p = (XmTextVerifyCallbackStruct *)(void *) call_data;
10201 	int	i;
10202 
10203 	if (p->reason != XmCR_MODIFYING_TEXT_VALUE)
10204 		return;
10205 
10206 	p->doit = True;
10207 
10208 	if (p->startPos != p->endPos)
10209 		/* Deleting text, no verification needed */
10210 		return;
10211 
10212 	switch (p->text->format) {
10213 	case XmFMT_8_BIT:
10214 		for (i = 0; i < p->text->length; i++) {
10215 			/* This is a single-line text widget, so a
10216 			 * newline is not allowed.
10217 			 */
10218 			if (p->text->ptr[i] == '\n' ||
10219 			    p->text->ptr[i] == '\r') {
10220 				p->doit = False;
10221 				break;
10222 			}
10223 		}
10224 		break;
10225 
10226 	case XmFMT_16_BIT:
10227 	default:
10228 		/* Nothing to do here */
10229 		break;
10230 	}
10231 }
10232 
10233 
10234 /*
10235  * cd_txtnline_vfy
10236  *	Single-line text widget numerical user-input verification callback.
10237  */
10238 /*ARGSUSED*/
10239 void
cd_txtnline_vfy(Widget w,XtPointer client_data,XtPointer call_data)10240 cd_txtnline_vfy(Widget w, XtPointer client_data, XtPointer call_data)
10241 {
10242 	XmTextVerifyCallbackStruct
10243 		*p = (XmTextVerifyCallbackStruct *)(void *) call_data;
10244 	int	i;
10245 
10246 	if (p->reason != XmCR_MODIFYING_TEXT_VALUE)
10247 		return;
10248 
10249 	p->doit = True;
10250 
10251 	if (p->startPos != p->endPos)
10252 		/* Deleting text, no verification needed */
10253 		return;
10254 
10255 	switch (p->text->format) {
10256 	case XmFMT_8_BIT:
10257 		for (i = 0; i < p->text->length; i++) {
10258 			/* Only digits are allowed */
10259 			if (!isdigit((int) p->text->ptr[i]) &&
10260 			    p->text->ptr[i] != '+' &&
10261 			    p->text->ptr[i] != '-') {
10262 				p->doit = False;
10263 				break;
10264 			}
10265 		}
10266 		break;
10267 
10268 	case XmFMT_16_BIT:
10269 	default:
10270 		/* Nothing to do here */
10271 		break;
10272 	}
10273 }
10274 
10275 
10276 /*
10277  * cd_about
10278  *	Program information popup callback function
10279  */
10280 /*ARGSUSED*/
10281 void
cd_about(Widget w,XtPointer client_data,XtPointer call_data)10282 cd_about(Widget w, XtPointer client_data, XtPointer call_data)
10283 {
10284 	int		allocsz;
10285 	char		*txt,
10286 			*ctrlver;
10287 	XmString	xs_desc,
10288 			xs_info,
10289 			xs;
10290 	curstat_t	*s = (curstat_t *)(void *) client_data;
10291 
10292 	if (XtIsManaged(widgets.dialog.about)) {
10293 		/* Pop down the about dialog box */
10294 		XtUnmanageChild(widgets.dialog.about);
10295 
10296 		if (w == widgets.help.about_btn)
10297 			return;
10298 	}
10299 
10300 	allocsz = STR_BUF_SZ * 32;
10301 
10302 	if ((txt = (char *) MEM_ALLOC("about_allocsz", allocsz)) == NULL) {
10303 		CD_FATAL(app_data.str_nomemory);
10304 		return;
10305 	}
10306 
10307 	(void) sprintf(txt, "%s version %s.%s.%s\n%s\n\n",
10308 		       PROGNAME, VERSION_MAJ, VERSION_MIN, VERSION_TEENY,
10309 		       "Motif\256 CD Audio Player / Ripper");
10310 
10311 	xs_desc = create_xmstring(txt, NULL, CHSET2, FALSE);
10312 
10313 	(void) sprintf(txt,
10314 			"%s\nURL: %s\nE-mail: %s\n\n%s\n\n%s\n"
10315 			"Drive: %s %s %s%s%s\nDevice: %s\n",
10316 		       COPYRIGHT,
10317 		       XMCD_URL,
10318 		       EMAIL,
10319 		       GNU_BANNER,
10320 		       di_methodstr(),
10321 		       (s->vendor[0] == '\0') ? "??" : s->vendor,
10322 		       s->prod,
10323 		       (s->revnum[0] == '\0') ? "" : "(",
10324 		       s->revnum,
10325 		       (s->revnum[0] == '\0') ? "" : ")",
10326 		       s->curdev
10327 	);
10328 
10329 	ctrlver = cdinfo_cddbctrl_ver();
10330 	(void) sprintf(txt, "%s\nCDDB%s service%s%s\n%s", txt,
10331 		       (cdinfo_cddb_ver() == 2) ? "\262" : " \"classic\"",
10332 		       (ctrlver[0] == '\0') ? "" : ": ",
10333 		       (ctrlver[0] == '\0') ? "\n" : ctrlver,
10334 		       CDDB_BANNER);
10335 
10336 	xs_info = create_xmstring(txt, NULL, CHSET1, FALSE);
10337 
10338 	/* Set the dialog box title */
10339 	xs = create_xmstring(
10340 		app_data.str_about, NULL, XmSTRING_DEFAULT_CHARSET, FALSE
10341 	);
10342 	XtVaSetValues(widgets.dialog.about, XmNdialogTitle, xs, NULL);
10343 	XmStringFree(xs);
10344 
10345 	/* Set the dialog box message */
10346 	xs = XmStringConcat(xs_desc, xs_info);
10347 	XtVaSetValues(widgets.dialog.about, XmNmessageString, xs, NULL);
10348 	XmStringFree(xs_desc);
10349 	XmStringFree(xs_info);
10350 	XmStringFree(xs);
10351 
10352 	MEM_FREE(txt);
10353 
10354 	/* Set up dialog box position */
10355 	cd_dialog_setpos(XtParent(widgets.dialog.about));
10356 
10357 	/* Pop up the about dialog box */
10358 	XtManageChild(widgets.dialog.about);
10359 }
10360 
10361 
10362 /*
10363  * cd_help_popup
10364  *	Program help window popup callback function
10365  */
10366 /*ARGSUSED*/
10367 void
cd_help_popup(Widget w,XtPointer client_data,XtPointer call_data)10368 cd_help_popup(Widget w, XtPointer client_data, XtPointer call_data)
10369 {
10370 	/* Pop up help window */
10371 	help_popup(w);
10372 }
10373 
10374 
10375 /*
10376  * cd_help_cancel
10377  *	Program help window popdown callback function
10378  */
10379 /*ARGSUSED*/
10380 void
cd_help_cancel(Widget w,XtPointer client_data,XtPointer call_data)10381 cd_help_cancel(Widget w, XtPointer client_data, XtPointer call_data)
10382 {
10383 	/* Pop down help window */
10384 	if (help_isactive())
10385 		help_popdown();
10386 }
10387 
10388 
10389 /*
10390  * cd_info_ok
10391  *	Information message dialog box OK button callback function.
10392  */
10393 /*ARGSUSED*/
10394 void
cd_info_ok(Widget w,XtPointer client_data,XtPointer call_data)10395 cd_info_ok(Widget w, XtPointer client_data, XtPointer call_data)
10396 {
10397 	/* Remove pending popdown timeout, if any */
10398 	if (infodiag_id >= 0) {
10399 		cd_untimeout(infodiag_id);
10400 		infodiag_id = -1;
10401 	}
10402 
10403 	/* Pop down the info window */
10404 	cd_info_popdown(NULL);
10405 }
10406 
10407 
10408 /*
10409  * cd_info2_ok
10410  *	Info2 message dialog box OK button callback function.
10411  */
10412 /*ARGSUSED*/
10413 void
cd_info2_ok(Widget w,XtPointer client_data,XtPointer call_data)10414 cd_info2_ok(Widget w, XtPointer client_data, XtPointer call_data)
10415 {
10416 	/* Pop down the info2 window */
10417 	cd_info2_popdown(NULL);
10418 }
10419 
10420 
10421 /*
10422  * cd_warning_ok
10423  *	Warning message dialog box OK button callback function
10424  */
10425 /*ARGSUSED*/
10426 void
cd_warning_ok(Widget w,XtPointer client_data,XtPointer call_data)10427 cd_warning_ok(Widget w, XtPointer client_data, XtPointer call_data)
10428 {
10429 	/* Pop down the warning dialog */
10430 	if (XtIsManaged(widgets.dialog.warning))
10431 		XtUnmanageChild(widgets.dialog.warning);
10432 }
10433 
10434 
10435 /*
10436  * cd_fatal_ok
10437  *	Fatal error message dialog box OK button callback function.
10438  *	This causes the application to terminate.
10439  */
10440 /*ARGSUSED*/
10441 void
cd_fatal_ok(Widget w,XtPointer client_data,XtPointer call_data)10442 cd_fatal_ok(Widget w, XtPointer client_data, XtPointer call_data)
10443 {
10444 	/* Pop down the error dialog */
10445 	if (XtIsManaged(widgets.dialog.fatal))
10446 		XtUnmanageChild(widgets.dialog.fatal);
10447 
10448 	/* Quit */
10449 	cd_quit((curstat_t *)(void *) client_data);
10450 }
10451 
10452 
10453 /*
10454  * cd_confirm_resp
10455  *	Set the confirm dialog user response state
10456  */
10457 /*ARGSUSED*/
10458 void
cd_confirm_resp(Widget w,XtPointer client_data,XtPointer call_data)10459 cd_confirm_resp(Widget w, XtPointer client_data, XtPointer call_data)
10460 {
10461 	confirm_resp = (bool_t) (client_data != NULL);
10462 	confirm_pend = FALSE;
10463 }
10464 
10465 
10466 /*
10467  * cd_rmcallback
10468  *	Remove callback function specified in cdinfo_t.
10469  */
10470 /*ARGSUSED*/
10471 void
cd_rmcallback(Widget w,XtPointer client_data,XtPointer call_data)10472 cd_rmcallback(Widget w, XtPointer client_data, XtPointer call_data)
10473 {
10474 	cbinfo_t	*cb = (cbinfo_t *)(void *) client_data;
10475 
10476 	if (cb == NULL)
10477 		return;
10478 
10479 	if (cb->widget0 != (Widget) NULL) {
10480 		XtRemoveCallback(
10481 			cb->widget0,
10482 			cb->type,
10483 			(XtCallbackProc) cb->func,
10484 			(XtPointer) cb->data
10485 		);
10486 
10487 		XtRemoveCallback(
10488 			cb->widget0,
10489 			cb->type,
10490 			(XtCallbackProc) cd_rmcallback,
10491 			client_data
10492 		);
10493 
10494 		cb->widget0 = (Widget) NULL;
10495 	}
10496 
10497 	if (cb->widget1 != (Widget) NULL) {
10498 		XtRemoveCallback(
10499 			cb->widget1,
10500 			cb->type,
10501 			(XtCallbackProc) cb->func,
10502 			(XtPointer) cb->data
10503 		);
10504 
10505 		XtRemoveCallback(
10506 			cb->widget1,
10507 			cb->type,
10508 			(XtCallbackProc) cd_rmcallback,
10509 			client_data
10510 		);
10511 
10512 		cb->widget1 = (Widget) NULL;
10513 	}
10514 
10515 	/* Remove WM_DELETE_WINDOW handler */
10516 	if (cb->widget2 != (Widget) NULL) {
10517 		rm_delw_callback(
10518 			cb->widget2,
10519 			(XtCallbackProc) cb->func,
10520 			(XtPointer) cb->data
10521 		);
10522 		cb->widget2 = (Widget) NULL;
10523 	}
10524 }
10525 
10526 
10527 /*
10528  * cd_shell_focus_chg
10529  *	Focus change callback.  Used to implement keyboard grabs for
10530  *	hotkey handling.
10531  */
10532 /*ARGSUSED*/
10533 void
cd_shell_focus_chg(Widget w,XtPointer client_data,XtPointer call_data)10534 cd_shell_focus_chg(Widget w, XtPointer client_data, XtPointer call_data)
10535 {
10536 	XmAnyCallbackStruct	*p = (XmAnyCallbackStruct *)(void *) call_data;
10537 	Widget			shell = (Widget) client_data;
10538 	static Widget		prev_shell = (Widget) NULL;
10539 
10540 	if (p->reason != XmCR_FOCUS || shell == (Widget) NULL)
10541 		return;
10542 
10543 	if (prev_shell != NULL) {
10544 		if (shell == prev_shell)
10545 			return;
10546 		else
10547 			hotkey_ungrabkeys(prev_shell);
10548 	}
10549 
10550 	if (shell != widgets.toplevel) {
10551 		hotkey_grabkeys(shell);
10552 		prev_shell = shell;
10553 	}
10554 }
10555 
10556 
10557 /*
10558  * cd_exit
10559  *	Shut down the application gracefully.
10560  */
10561 /*ARGSUSED*/
10562 void
cd_exit(Widget w,XtPointer client_data,XtPointer call_data)10563 cd_exit(Widget w, XtPointer client_data, XtPointer call_data)
10564 {
10565 	curstat_t	*s = (curstat_t *)(void *) client_data;
10566 
10567 	/* Shut down CDDB - if there are more processing needed in the
10568 	 * CDDB, they will call us back.
10569 	 */
10570 	s->flags |= STAT_EXIT;
10571 	if (!dbprog_chgsubmit(s))
10572 		return;
10573 
10574 	cd_quit(s);
10575 }
10576 
10577 
10578 /*
10579  * cd_tooltip_cancel
10580  *	Cancel the tooltip window
10581  */
10582 /*ARGSUSED*/
10583 void
cd_tooltip_cancel(Widget w,XtPointer client_data,XtPointer call_data)10584 cd_tooltip_cancel(Widget w, XtPointer client_data, XtPointer call_data)
10585 {
10586 	cd_tooltip_popdown(widgets.tooltip.shell);
10587 }
10588 
10589 
10590 /*
10591  * cd_not_implemented
10592  *	Pop up a "not yet implemented" message.  The client data
10593  *	may point to a feature name string which will be displayed
10594  *	along with the message.
10595  */
10596 /*ARGSUSED*/
10597 void
cd_not_implemented(Widget w,XtPointer client_data,XtPointer call_data)10598 cd_not_implemented(Widget w, XtPointer client_data, XtPointer call_data)
10599 {
10600 	char	*feature = (char *) client_data;
10601 	char	msg[STR_BUF_SZ * 2];
10602 
10603 	(void) sprintf(msg,
10604 		"%s%sNot yet implemented.",
10605 		feature != NULL ? feature : "",
10606 		feature != NULL ? ": " : ""
10607 	);
10608 
10609 	CD_INFO_AUTO(msg);
10610 }
10611 
10612 
10613 /**************** ^^ Callback routines ^^ ****************/
10614 
10615 /***************** vv Event Handlers vv ******************/
10616 
10617 
10618 /* Mapping table for main window controls and their label color change
10619  * function pointer, and associated label pixmaps.
10620  */
10621 struct wpix_tab {
10622 	Widget	*wptr;
10623 	void	(*set_func)(Widget w, Pixmap px, Pixel color);
10624 	Pixmap	*fpx;
10625 	Pixmap	*hpx;
10626 } wpix_tab[] = {
10627     { &widgets.main.mode_btn, set_btn_color,
10628       &pixmaps.main.mode_pixmap, &pixmaps.main.mode_hlpixmap },
10629     { &widgets.main.eject_btn, set_btn_color,
10630       &pixmaps.main.eject_pixmap, &pixmaps.main.eject_hlpixmap },
10631     { &widgets.main.quit_btn, set_btn_color,
10632       &pixmaps.main.quit_pixmap, &pixmaps.main.quit_hlpixmap },
10633     { &widgets.main.dbprog_btn, set_btn_color,
10634       &pixmaps.main.dbprog_pixmap, &pixmaps.main.dbprog_hlpixmap },
10635     { &widgets.main.wwwwarp_btn, set_btn_color,
10636       &pixmaps.main.world_pixmap, &pixmaps.main.world_hlpixmap },
10637     { &widgets.main.options_btn, set_btn_color,
10638       &pixmaps.main.options_pixmap, &pixmaps.main.options_hlpixmap },
10639     { &widgets.main.time_btn, set_btn_color,
10640       &pixmaps.main.time_pixmap, &pixmaps.main.time_hlpixmap },
10641     { &widgets.main.ab_btn, set_btn_color,
10642       &pixmaps.main.ab_pixmap, &pixmaps.main.ab_hlpixmap },
10643     { &widgets.main.sample_btn, set_btn_color,
10644       &pixmaps.main.sample_pixmap, &pixmaps.main.sample_hlpixmap },
10645     { &widgets.main.keypad_btn, set_btn_color,
10646       &pixmaps.main.keypad_pixmap, &pixmaps.main.keypad_hlpixmap },
10647     { &widgets.main.level_scale, set_scale_color,
10648       NULL, NULL },
10649     { &widgets.main.playpause_btn, set_btn_color,
10650       &pixmaps.main.playpause_pixmap, &pixmaps.main.playpause_hlpixmap },
10651     { &widgets.main.stop_btn, set_btn_color,
10652       &pixmaps.main.stop_pixmap, &pixmaps.main.stop_hlpixmap },
10653     { &widgets.main.prevdisc_btn, set_btn_color,
10654       &pixmaps.main.prevdisc_pixmap, &pixmaps.main.prevdisc_hlpixmap },
10655     { &widgets.main.nextdisc_btn, set_btn_color,
10656       &pixmaps.main.nextdisc_pixmap, &pixmaps.main.nextdisc_hlpixmap },
10657     { &widgets.main.prevtrk_btn, set_btn_color,
10658       &pixmaps.main.prevtrk_pixmap, &pixmaps.main.prevtrk_hlpixmap },
10659     { &widgets.main.nexttrk_btn, set_btn_color,
10660       &pixmaps.main.nexttrk_pixmap, &pixmaps.main.nexttrk_hlpixmap },
10661     { &widgets.main.previdx_btn, set_btn_color,
10662       &pixmaps.main.previdx_pixmap, &pixmaps.main.previdx_hlpixmap },
10663     { &widgets.main.nextidx_btn, set_btn_color,
10664       &pixmaps.main.nextidx_pixmap, &pixmaps.main.nextidx_hlpixmap },
10665     { &widgets.main.rew_btn, set_btn_color,
10666       &pixmaps.main.rew_pixmap, &pixmaps.main.rew_hlpixmap },
10667     { &widgets.main.ff_btn, set_btn_color,
10668       &pixmaps.main.ff_pixmap, &pixmaps.main.ff_hlpixmap },
10669     { NULL, NULL, 0, 0 }
10670 };
10671 
10672 
10673 /*
10674  * cd_focus_chg
10675  *	Widget keyboard focus change event handler.  Used to change
10676  *	the main window controls' label color.
10677  */
10678 /*ARGSUSED*/
10679 void
cd_focus_chg(Widget w,XtPointer client_data,XEvent * ev)10680 cd_focus_chg(Widget w, XtPointer client_data, XEvent *ev)
10681 {
10682 	int		i;
10683 	unsigned char	focuspolicy;
10684 	static bool_t	first = TRUE;
10685 	static int	count = 0;
10686 	static Pixel	fg,
10687 			hl;
10688 
10689 	if (!app_data.main_showfocus)
10690 		return;
10691 
10692 	if (first) {
10693 		first = FALSE;
10694 
10695 		XtVaGetValues(
10696 			widgets.toplevel,
10697 			XmNkeyboardFocusPolicy,
10698 			&focuspolicy,
10699 			NULL
10700 		);
10701 		if (focuspolicy != XmEXPLICIT) {
10702 			app_data.main_showfocus = FALSE;
10703 			return;
10704 		}
10705 
10706 		XtVaGetValues(w, XmNforeground, &fg, NULL);
10707 		XtVaGetValues(w, XmNhighlightColor, &hl, NULL);
10708 	}
10709 
10710 	if (ev->xfocus.mode != NotifyNormal ||
10711 	    ev->xfocus.detail != NotifyAncestor)
10712 		return;
10713 
10714 	if (ev->type == FocusOut) {
10715 		if (count <= 0)
10716 			return;
10717 
10718 		/* Restore original foreground pixmap */
10719 		for (i = 0; wpix_tab[i].set_func != NULL; i++) {
10720 			if (w == *(wpix_tab[i].wptr)) {
10721 				wpix_tab[i].set_func(w,
10722 					(wpix_tab[i].fpx == NULL) ?
10723 					    (Pixmap) 0 : *(wpix_tab[i].fpx),
10724 					fg
10725 				);
10726 				break;
10727 			}
10728 		}
10729 		count--;
10730 	}
10731 	else if (ev->type == FocusIn) {
10732 		if (count >= 1)
10733 			return;
10734 
10735 		/* Set new highlighted foreground pixmap */
10736 		for (i = 0; wpix_tab[i].set_func != NULL; i++) {
10737 			if (w == *(wpix_tab[i].wptr)) {
10738 				wpix_tab[i].set_func(w,
10739 					(wpix_tab[i].fpx == NULL) ?
10740 					    (Pixmap) 0 : *(wpix_tab[i].hpx),
10741 					hl
10742 				);
10743 				break;
10744 			}
10745 		}
10746 		count++;
10747 	}
10748 }
10749 
10750 /*
10751  * cd_xing_chg
10752  *	Widget enter/leave crossing event handler.  Used to manage
10753  *	pop-up tool-tips.
10754  */
10755 /*ARGSUSED*/
10756 void
cd_xing_chg(Widget w,XtPointer client_data,XEvent * ev)10757 cd_xing_chg(Widget w, XtPointer client_data, XEvent *ev)
10758 {
10759 	if (!app_data.tooltip_enable ||
10760 	    ev->xcrossing.mode != NotifyNormal ||
10761 	    ev->xcrossing.detail == NotifyInferior)
10762 		return;
10763 
10764 	if (ev->type == EnterNotify) {
10765 		if (skip_next_tooltip) {
10766 			skip_next_tooltip = FALSE;
10767 			return;
10768 		}
10769 
10770 		if (tooltip1_id < 0) {
10771 			tooltip1_id = cd_timeout(
10772 				app_data.tooltip_delay,
10773 				cd_tooltip_popup,
10774 				(byte_t *) w
10775 			);
10776 		}
10777 	}
10778 	else if (ev->type == LeaveNotify) {
10779 		cd_tooltip_popdown(widgets.tooltip.shell);
10780 	}
10781 }
10782 
10783 
10784 /*
10785  * cd_dbmode_ind
10786  *      Main window dbmode indicator button release event callback function
10787  */
10788 /*ARGSUSED*/
10789 void
cd_dbmode_ind(Widget w,XtPointer client_data,XEvent * ev,Boolean * cont)10790 cd_dbmode_ind(Widget w, XtPointer client_data, XEvent *ev, Boolean *cont)
10791 {
10792 	curstat_t	*s = (curstat_t *)(void *) client_data;
10793 
10794 	*cont = True;
10795 
10796 	if (ev->xany.type != ButtonRelease || ev->xbutton.button != Button1)
10797 		return;
10798 
10799 	if (s->qmode == QMODE_WAIT) {
10800 		(void) dbprog_stopload_active(1, TRUE);
10801 
10802 		(void) cd_confirm_popup(
10803 			app_data.str_confirm,
10804 			app_data.str_stopload,
10805 			(XtCallbackProc) dbprog_stop_load_yes, client_data,
10806 			(XtCallbackProc) dbprog_stop_load_no, client_data
10807 		);
10808 	}
10809 	else if (XtIsSensitive(widgets.dbprog.reload_btn)) {
10810 		(void) cd_confirm_popup(
10811 			app_data.str_confirm,
10812 			app_data.str_reload,
10813 			(XtCallbackProc) dbprog_load, client_data,
10814 			(XtCallbackProc) NULL, NULL
10815 		);
10816 	}
10817 }
10818 
10819 
10820 /***************** ^^ Event Handlers ^^ ******************/
10821 
10822