1/* $XConsortium: popup.c,v 2.38 94/08/26 18:04:22 swick Exp $
2 *
3 *
4 *			  COPYRIGHT 1989
5 *		   DIGITAL EQUIPMENT CORPORATION
6 *		       MAYNARD, MASSACHUSETTS
7 *			ALL RIGHTS RESERVED.
8 *
9 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
10 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
11 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
12 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
13 *
14 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT
15 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN
16 * ADDITION TO THAT SET FORTH ABOVE.
17 *
18 * Permission to use, copy, modify, and distribute this software and its
19 * documentation for any purpose and without fee is hereby granted, provided
20 * that the above copyright notice appear in all copies and that both that
21 * copyright notice and this permission notice appear in supporting
22 * documentation, and that the name of Digital Equipment Corporation not be
23 * used in advertising or publicity pertaining to distribution of the software
24 * without specific, written prior permission.
25 */
26/* $XFree86$ */
27
28/* popup.c -- Handle pop-up widgets. */
29
30#include "xmh.h"
31#include "actions.h"
32
33#include <X11/Xaw/Cardinals.h>
34
35typedef struct _PopupStatus {
36	Widget popup;		/* order of fields same as CommandStatusRec */
37	struct _LastInput lastInput;
38	char*  shell_command;	/* NULL, or contains sh -c command */
39} PopupStatusRec, *PopupStatus;
40
41/* these are just strings which are used more than one place in the code */
42static String XmhNconfirm = "confirm";
43static String XmhNdialog = "dialog";
44static String XmhNerror = "error";
45static String XmhNnotice = "notice";
46static String XmhNokay = "okay";
47static String XmhNprompt = "prompt";
48static String XmhNvalue = "value";
49
50/* The popups were originally parented from toplevel and neglected the
51 * transientFor resource.  In order not to break existing user resource
52 * settings for the popups, transientFor is set independent of the parent,
53 * which remains the toplevel widget.
54 */
55
56static void DeterminePopupPosition(
57    Position	*x_ptr,
58    Position	*y_ptr,
59    Widget	*transFor_return) /* return a suitable top level shell */
60{
61    if (lastInput.win != (Window) -1) {
62	if (transFor_return) {
63	    Widget	source;
64	    source = XtWindowToWidget(XtDisplay(toplevel), lastInput.win);
65	    while (source && !XtIsWMShell(source))
66		source = XtParent(source);
67	    *transFor_return = source;
68	}
69	/* use the site of the last KeyPress or ButtonPress */
70	*x_ptr = lastInput.x;
71	*y_ptr = lastInput.y;
72    } else {
73	Widget	source;
74	int i = 0;
75	Dimension width, height;
76	Arg args[2];
77
78	/* %%% need to keep track of last screen */
79	/* guess which screen and use the the center of it */
80	while (i < numScrns && !scrnList[i]->mapped)
81	    i++;
82	source = ((i < numScrns) ? scrnList[i]->parent : toplevel);
83	XtSetArg(args[0], XtNwidth, &width);
84	XtSetArg(args[1], XtNheight, &height);
85	XtGetValues(source, args, TWO);
86	XtTranslateCoords(source, (Position) (width / 2),
87			  (Position) (height / 2), x_ptr, y_ptr);
88	if (transFor_return) *transFor_return = source;
89    }
90}
91
92static Boolean PositionThePopup(
93    Widget	popup,
94    Position	x,
95    Position	y)
96{
97    /* Hack.  Fix up the position of the popup.  The xmh app defaults file
98     * contains an Xmh*Geometry specification; the effects of that on
99     * popups, and the lack of any user-supplied geometry specification for
100     * popups, are mitigated here, by giving the popup shell a position.
101     * (Xmh*Geometry is needed in case there is no user-supplied default.)
102     * Returns True if an explicit geometry was inferred; false if the
103     * widget was repositioned to (x,y).
104     */
105
106    Arg		args[4];
107    String 	top_geom, pop_geom;
108
109    XtSetArg( args[0], XtNgeometry, &top_geom );
110    XtGetValues( toplevel, args, ONE );
111    XtSetArg( args[0], XtNgeometry, &pop_geom );
112    XtGetValues( popup, args, ONE );
113
114    if (pop_geom == NULL || pop_geom == top_geom) {
115	/* if same db entry, then ... */
116	XtSetArg( args[0], XtNgeometry, (String) NULL);
117	XtSetArg( args[1], XtNx, x);
118	XtSetArg( args[2], XtNy, y);
119	XtSetArg( args[3], XtNwinGravity, SouthWestGravity);
120	XtSetValues( popup, args, FOUR);
121	return False;
122    }
123    return True;
124}
125
126
127static void CenterPopupPosition(
128    Widget	widget,
129    Widget	popup,
130    Position	px,
131    Position	py)
132{
133    Position	x, y;
134    Position	nx, ny;
135    Arg		args[3];
136
137    if (widget == NULL) return;
138    XtSetArg(args[0], XtNx, &x);
139    XtSetArg(args[1], XtNy, &y);
140    XtGetValues(popup, args, TWO);
141    if (x == px && y == py) {
142
143	/* Program sets geometry.  Correct our earlier calculations. */
144
145	nx = (GetWidth(widget) - GetWidth(popup)) / 2;
146	ny = (GetHeight(widget) - GetHeight(popup)) / 2;
147	if (nx < 0) nx = 0;
148	if (ny < 0) ny = 0;
149	XtTranslateCoords(widget, nx, ny, &x, &y);
150	XtSetArg(args[0], XtNx, x);
151	XtSetArg(args[1], XtNy, y);
152	XtSetArg(args[2], XtNwinGravity, CenterGravity);
153	XtSetValues(popup, args, THREE);
154    }
155}
156
157
158/* Insure that the popup is wholly showing on the screen.
159   Optionally center the widget horizontally and/or vertically
160   on current position.
161 */
162
163static void InsureVisibility(
164    Widget	popup,
165    Widget	popup_child,
166    Position	x,		/* assert: current position = (x,y) */
167    Position	y,
168    Boolean	centerX,
169    Boolean	centerY)
170{
171    Position	root_x, root_y;
172    Dimension	width, height, border;
173    Arg		args[3];
174
175
176    XtSetArg( args[0], XtNwidth, &width );
177    XtSetArg( args[1], XtNheight, &height );
178    XtSetArg( args[2], XtNborderWidth, &border );
179    XtGetValues( popup, args, THREE );
180
181    XtTranslateCoords(popup_child, (Position)0, (Position)0, &root_x, &root_y);
182    if (centerX) root_x -= width/2 + border;
183    if (centerY) root_y -= height/2 + border;
184    if (root_x < 0) root_x = 0;
185    if (root_y < 0) root_y = 0;
186    border <<= 1;
187
188    if ((int)(root_x + width + border) > WidthOfScreen(XtScreen(toplevel))) {
189	root_x = WidthOfScreen(XtScreen(toplevel)) - width - border;
190    }
191    if ((int)(root_y + height + border) > HeightOfScreen(XtScreen(toplevel))) {
192	root_y = HeightOfScreen(XtScreen(toplevel)) - height - border;
193    }
194
195    if (root_x != x || root_y != y) {
196	XtSetArg( args[0], XtNx, root_x );
197	XtSetArg( args[1], XtNy, root_y );
198	XtSetValues( popup, args, TWO );
199    }
200}
201
202
203/*ARGSUSED*/
204void DestroyPopup(
205    Widget		widget,		/* unused */
206    XtPointer		client_data,
207    XtPointer		call_data)	/* unused */
208{
209    Widget		popup = (Widget) client_data;
210    XtPopdown(popup);
211    XtDestroyWidget(popup);
212}
213
214void WMDeletePopup(
215    Widget	popup,	/* transient shell */
216    XEvent*	event)
217{
218    String	shellName;
219    String	buttonName;
220    Widget	button;
221
222    shellName = XtName(popup);
223    if (strcmp(shellName, XmhNconfirm) == 0)
224	buttonName = "*no";
225    else if (strcmp(shellName, XmhNprompt) == 0)
226	buttonName = "*cancel";
227    else if (strcmp(shellName, XmhNnotice) == 0)
228	buttonName = "*confirm";
229    else if (strcmp(shellName, XmhNerror) == 0)
230	buttonName = "*OK";
231    else
232	return;		/* WM may kill us */
233
234    button = XtNameToWidget(popup, buttonName);
235    if (! button) return;
236    XtCallActionProc(button, "set", event, (String*)NULL, ZERO);
237    XtCallActionProc(button, "notify", event, (String*)NULL, ZERO);
238    XtCallActionProc(button, "unset", event, (String*)NULL, ZERO);
239}
240
241static void TheUsual(
242    Widget	popup)	/* shell */
243{
244    XtInstallAllAccelerators(popup, popup);
245    XtAugmentTranslations(popup, app_resources.wm_protocols_translations);
246    XtRealizeWidget(popup);
247    XDefineCursor(XtDisplay(popup), XtWindow(popup), app_resources.cursor);
248    (void) XSetWMProtocols(XtDisplay(popup), XtWindow(popup),
249			   protocolList, XtNumber(protocolList));
250}
251
252
253/*ARGSUSED*/
254void XmhPromptOkayAction(
255    Widget	w,		/* the "value" widget in the Dialog box */
256    XEvent	*event,		/* unused */
257    String	*params,	/* unused */
258    Cardinal	*num_params)	/* unused */
259{
260    XtCallCallbacks(XtNameToWidget(XtParent(w), XmhNokay), XtNcallback,
261		    (XtPointer)XtParent(w));
262}
263
264
265void PopupPrompt(
266    Widget		transientFor,	/* required to be a top-level shell */
267    String		question,		/* the prompting string */
268    XtCallbackProc	okayCallback)		/* CreateFolder() */
269{
270    Widget		popup;
271    Widget		dialog;
272    Widget		value;
273    Position		x, y;
274    Boolean		positioned;
275    Arg			args[3];
276    static XtTranslations PromptTextTranslations = NULL;
277
278    DeterminePopupPosition(&x, &y, (Widget*)NULL);
279    XtSetArg(args[0], XtNallowShellResize, True);
280    XtSetArg(args[1], XtNinput, True);
281    XtSetArg(args[2], XtNtransientFor, transientFor);
282    popup = XtCreatePopupShell(XmhNprompt, transientShellWidgetClass, toplevel,
283			       args, THREE);
284    positioned = PositionThePopup(popup, x, y);
285
286    XtSetArg(args[0], XtNlabel, question);
287    XtSetArg(args[1], XtNvalue, "");
288    dialog = XtCreateManagedWidget(XmhNdialog, dialogWidgetClass, popup, args,
289				   TWO);
290    XtSetArg(args[0], XtNresizable, True);
291    XtSetValues( XtNameToWidget(dialog, "label"), args, ONE);
292    value = XtNameToWidget(dialog, XmhNvalue);
293    XtSetValues( value, args, ONE);
294    if (! PromptTextTranslations)
295	PromptTextTranslations = XtParseTranslationTable
296	    ("<Key>Return: XmhPromptOkayAction()\n\
297              Ctrl<Key>R:  no-op(RingBell)\n\
298              Ctrl<Key>S:  no-op(RingBell)\n");
299    XtOverrideTranslations(value, PromptTextTranslations);
300
301    XawDialogAddButton(dialog, XmhNokay, okayCallback, (XtPointer) dialog);
302    XawDialogAddButton(dialog, "cancel", DestroyPopup, (XtPointer) popup);
303    TheUsual(popup);
304    InsureVisibility(popup, dialog, x, y, !positioned, False);
305    XtPopup(popup, XtGrabNone);
306}
307
308
309/* ARGSUSED */
310static void FreePopupStatus(
311    Widget w,			/* unused */
312    XtPointer closure,
313    XtPointer call_data)	/* unused */
314{
315    PopupStatus popup = (PopupStatus)closure;
316    XtPopdown(popup->popup);
317    XtDestroyWidget(popup->popup);
318    if (popup->shell_command)
319	XtFree(popup->shell_command);
320    XtFree((char *) closure);
321}
322
323
324void PopupNotice(
325    String		message,
326    XtCallbackProc	callback,
327    XtPointer		closure)
328{
329    PopupStatus popup_status = (PopupStatus)closure;
330    Widget transientFor;
331    Widget dialog;
332    Widget value;
333    Position x, y;
334    Arg args[3];
335    char command[65], label[128];
336
337    if (popup_status == (PopupStatus)NULL) {
338	popup_status = XtNew(PopupStatusRec);
339	popup_status->lastInput = lastInput;
340	popup_status->shell_command = (char*)NULL;
341    }
342    if (! popup_status->shell_command) {
343	/* MH command */
344	if (sscanf( message, "%64s", command ) != 1)
345	    (void) strcpy( command, "system" );
346	else {
347	    int l = strlen(command);
348	    if (l && command[--l] == ':')
349		command[l] = '\0';
350	}
351	snprintf(label, sizeof(label), "%.64s command returned:", command);
352    } else {
353	/* arbitrary shell command */
354	int len = strlen(popup_status->shell_command);
355	snprintf(label, sizeof(label), "%.88s %s\nshell command returned:",
356                 popup_status->shell_command,
357                 ((len > 88) ? "[truncated]" : ""));
358    }
359
360    DeterminePopupPosition(&x, &y, &transientFor);
361    XtSetArg( args[0], XtNallowShellResize, True );
362    XtSetArg( args[1], XtNinput, True );
363    XtSetArg( args[2], XtNtransientFor, transientFor);
364    popup_status->popup = XtCreatePopupShell(XmhNnotice,
365			     transientShellWidgetClass, toplevel, args, THREE);
366    PositionThePopup(popup_status->popup, x, y);
367
368    XtSetArg( args[0], XtNlabel, label );
369    XtSetArg( args[1], XtNvalue, message );
370    dialog = XtCreateManagedWidget(XmhNdialog, dialogWidgetClass,
371				   popup_status->popup, args, TWO);
372
373    /* The text area of the dialog box will not be editable. */
374    value = XtNameToWidget(dialog, XmhNvalue);
375    XtSetArg( args[0], XtNeditType, XawtextRead);
376    XtSetArg( args[1], XtNdisplayCaret, False);
377    XtSetValues( value, args, TWO);
378    XtOverrideTranslations(value, NoTextSearchAndReplace);
379
380    XawDialogAddButton( dialog, XmhNconfirm,
381		       ((callback != (XtCallbackProc) NULL)
382		          ? callback : (XtCallbackProc) FreePopupStatus),
383		       (XtPointer) popup_status
384		      );
385
386    TheUsual(popup_status->popup);
387    InsureVisibility(popup_status->popup, dialog, x, y, False, False);
388    XtPopup(popup_status->popup, XtGrabNone);
389}
390
391
392void PopupConfirm(
393    Widget		center_widget,	/* where to center; may be NULL */
394    String		question,
395    XtCallbackList	affirm_callbacks,
396    XtCallbackList	negate_callbacks)
397{
398    Widget	popup;
399    Widget	dialog;
400    Widget	button;
401    Widget	transientFor;
402    Position	x, y;
403    Arg		args[3];
404    static XtCallbackRec callbacks[] = {
405	{DestroyPopup,		(XtPointer) NULL},
406	{(XtCallbackProc) NULL,	(XtPointer) NULL}
407    };
408
409    DeterminePopupPosition(&x, &y, &transientFor);
410    XtSetArg(args[0], XtNinput, True);
411    XtSetArg(args[1], XtNallowShellResize, True);
412    XtSetArg(args[2], XtNtransientFor, transientFor);
413    popup = XtCreatePopupShell(XmhNconfirm, transientShellWidgetClass,
414			       toplevel, args, THREE);
415    PositionThePopup(popup, x, y);
416
417    XtSetArg(args[0], XtNlabel, question);
418    dialog = XtCreateManagedWidget(XmhNdialog, dialogWidgetClass, popup, args,
419				   ONE);
420
421    callbacks[0].closure = (XtPointer) popup;
422    XtSetArg(args[0], XtNcallback, callbacks);
423    button = XtCreateManagedWidget("yes", commandWidgetClass, dialog,
424				   args, ONE);
425    if (affirm_callbacks)
426	XtAddCallbacks(button, XtNcallback, affirm_callbacks);
427
428    button = XtCreateManagedWidget("no", commandWidgetClass, dialog,
429				   args, ZERO);
430    XtAddCallback(button, XtNcallback, DestroyPopup, (XtPointer) popup);
431    if (negate_callbacks)
432	XtAddCallbacks(button, XtNcallback, negate_callbacks);
433
434    TheUsual(popup);
435    CenterPopupPosition(center_widget ? center_widget : transientFor,
436			popup, x, y);
437    InsureVisibility(popup, dialog, x, y, False, False);
438    XtPopup(popup, XtGrabNone);
439}
440
441
442void PopupError(
443    Widget	widget,	/* transient for this top-level shell, or NULL */
444    String	message)
445{
446    Widget	transFor, error_popup, dialog;
447    Position	x, y;
448    Boolean	positioned;
449    Arg		args[3];
450    static XtCallbackRec callbacks[] = {
451	{DestroyPopup,		(XtPointer) NULL},
452	{(XtCallbackProc) NULL,	(XtPointer) NULL}
453    };
454
455    transFor = widget;
456    DeterminePopupPosition(&x, &y, transFor ? (Widget*)NULL : &transFor);
457
458    XtSetArg(args[0], XtNallowShellResize, True);
459    XtSetArg(args[1], XtNinput, True);
460    XtSetArg(args[2], XtNtransientFor, transFor);
461    error_popup = XtCreatePopupShell(XmhNerror, transientShellWidgetClass,
462				     toplevel, args, THREE);
463    positioned = PositionThePopup(error_popup, x, y);
464
465    XtSetArg(args[0], XtNlabel, message);
466    dialog = XtCreateManagedWidget(XmhNdialog, dialogWidgetClass, error_popup,
467				   args, ONE);
468    callbacks[0].closure = (XtPointer) error_popup;
469    XtSetArg(args[0], XtNcallback, callbacks);
470    XawDialogAddButton(dialog, "OK", DestroyPopup, (XtPointer) error_popup);
471    TheUsual(error_popup);
472    InsureVisibility(error_popup, dialog, x, y, !positioned, !positioned);
473    XtPopup(error_popup, XtGrabNone);
474}
475
476/*ARGSUSED*/
477void PopupWarningHandler(
478    String name,
479    String type,
480    String class,
481    String msg,
482    String *params,
483    Cardinal *num)
484{
485    char *ptr;
486    int i;
487    String par[10];
488    char message[500];
489    char buffer[500];
490    static Boolean allowPopup = True; /* protect against recursion */
491
492    XtGetErrorDatabaseText(name, type, class, msg, buffer, 500);
493
494    if (params && num && *num) {
495	i = (*num <= 10) ? *num : 10;
496	memmove( (char*)par, (char*)params, i * sizeof(String));
497	bzero( &par[i], (10-i) * sizeof(String));
498	if (*num > 10)
499	    par[9] = "(truncated)";
500	snprintf(message, sizeof(message), buffer, par[0], par[1], par[2],
501                 par[3], par[4], par[5], par[6], par[7], par[8], par[9]);
502	ptr = message;
503    } else {
504	ptr = buffer;
505    }
506    if (allowPopup) {
507	allowPopup = False;
508	PopupError((Widget)NULL, ptr);
509	allowPopup = True;
510    } else {
511	fprintf(stderr, ptr);
512    }
513}
514