1 /*
2  * Copyright (c) 1993-2009, 2014-2016, 2019 Paul Mattes.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the names of Paul Mattes nor the names of his contributors
13  *       may be used to endorse or promote products derived from this software
14  *       without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  *	idle_gui.c
30  *		This module handles the idle command GUI.
31  */
32 
33 #include "globals.h"
34 #include "xglobals.h"
35 
36 #include <X11/StringDefs.h>
37 #include <X11/Xaw/Toggle.h>
38 #include <X11/Xaw/Command.h>
39 #include <X11/Xaw/Form.h>
40 #include <X11/Shell.h>
41 #include <X11/Xaw/AsciiText.h>
42 #include <X11/Xaw/TextSrc.h>
43 #include <X11/Xaw/TextSink.h>
44 #include <X11/Xaw/AsciiSrc.h>
45 #include <X11/Xaw/AsciiSink.h>
46 
47 #include <errno.h>
48 
49 #include "appres.h"
50 #include "dialog.h"
51 #include "idle.h"
52 #include "idle_gui.h"
53 #include "objects.h"
54 #include "popups.h"
55 #include "xmenubar.h"
56 #include "xpopups.h"
57 
58 /* Macros. */
59 #define FILE_WIDTH	300	/* width of file name widgets */
60 #define MARGIN		3	/* distance from margins to widgets */
61 #define CLOSE_VGAP	0	/* distance between paired toggles */
62 #define FAR_VGAP	10	/* distance between single toggles and groups */
63 #define BUTTON_GAP	5	/* horizontal distance between buttons */
64 
65 /* Globals. */
66 
67 /* Statics. */
68 static enum idle_enum s_disabled = IDLE_DISABLED;
69 static enum idle_enum s_session = IDLE_SESSION;
70 static enum idle_enum s_perm = IDLE_PERM;
71 static char hms = 'm';
72 static bool fuzz = false;
73 static char s_hours = 'h';
74 static char s_minutes = 'm';
75 static char s_seconds = 's';
76 static Widget idle_dialog, idle_shell, command_value, timeout_value;
77 static Widget enable_toggle, enable_perm_toggle, disable_toggle;
78 static Widget hours_toggle, minutes_toggle, seconds_toggle, fuzz_toggle;
79 static sr_t *idle_sr = NULL;
80 
81 static void idle_cancel(Widget w, XtPointer client_data, XtPointer call_data);
82 static void idle_popup_callback(Widget w, XtPointer client_data,
83     XtPointer call_data);
84 static void idle_popup_init(void);
85 static bool idle_start(void);
86 static void okay_callback(Widget w, XtPointer call_parms,
87     XtPointer call_data);
88 static void toggle_enable(Widget w, XtPointer client_data,
89     XtPointer call_data);
90 static void mark_toggle(Widget w, Pixmap p);
91 static void toggle_hms(Widget w, XtPointer client_data,
92     XtPointer call_data);
93 static void toggle_fuzz(Widget w, XtPointer client_data,
94     XtPointer call_data);
95 
96 /* "Idle Command" dialog. */
97 
98 /*
99  * Pop up the "Idle" menu.
100  * Called back from the "Configure Idle Command" option on the Options menu.
101  */
102 void
popup_idle(void)103 popup_idle(void)
104 {
105     char *its;
106     char *s;
107 
108     /* Initialize it. */
109     if (idle_shell == NULL) {
110 	idle_popup_init();
111     }
112 
113     /*
114      * Split the idle_timeout_string (the raw resource value) into fuzz,
115      * a number, and h/m/s.
116      */
117     its = NewString(idle_timeout_string);
118     if (its != NULL) {
119 	if (*its == '~') {
120 	    fuzz = true;
121 	    its++;
122 	} else {
123 	    fuzz = false;
124 	}
125 	s = its;
126 	while (isdigit((unsigned char)*s)) {
127 	    s++;
128 	}
129 	switch (*s) {
130 	case 'h':
131 	case 'H':
132 	    hms = 'h';
133 	    break;
134 	case 'm':
135 	case 'M':
136 	    hms = 'm';
137 	    break;
138 	case 's':
139 	case 'S':
140 	    hms = 's';
141 	    break;
142 	default:
143 	    break;
144 	}
145 	*s = '\0';
146     }
147 
148     /* Set the resource values. */
149     dialog_set(&idle_sr, idle_dialog);
150     XtVaSetValues(command_value,
151 	    XtNstring, idle_command,
152 	    NULL);
153     XtVaSetValues(timeout_value, XtNstring, its, NULL);
154     mark_toggle(enable_toggle, (idle_user_enabled == IDLE_SESSION)?
155 	    diamond : no_diamond);
156     mark_toggle(enable_perm_toggle, (idle_user_enabled == IDLE_PERM)?
157 	    diamond : no_diamond);
158     mark_toggle(disable_toggle, (idle_user_enabled == IDLE_DISABLED)?
159 	    diamond : no_diamond);
160     mark_toggle(hours_toggle, (hms == 'h') ? diamond : no_diamond);
161     mark_toggle(minutes_toggle, (hms == 'm') ? diamond : no_diamond);
162     mark_toggle(seconds_toggle, (hms == 's') ? diamond : no_diamond);
163     mark_toggle(fuzz_toggle, fuzz ? dot : no_dot);
164 
165     /* Pop it up. */
166     popup_popup(idle_shell, XtGrabNone);
167 }
168 
169 /* Initialize the idle pop-up. */
170 static void
idle_popup_init(void)171 idle_popup_init(void)
172 {
173     Widget w;
174     Widget cancel_button;
175     Widget command_label, timeout_label;
176     Widget okay_button;
177 
178     /* Prime the dialog functions. */
179     dialog_set(&idle_sr, idle_dialog);
180 
181     /* Create the menu shell. */
182     idle_shell = XtVaCreatePopupShell(
183 	    "idlePopup", transientShellWidgetClass, toplevel,
184 	    NULL);
185     XtAddCallback(idle_shell, XtNpopupCallback, place_popup,
186 	    (XtPointer)CenterP);
187     XtAddCallback(idle_shell, XtNpopupCallback, idle_popup_callback, NULL);
188 
189     /* Create the form within the shell. */
190     idle_dialog = XtVaCreateManagedWidget(
191 	    ObjDialog, formWidgetClass, idle_shell,
192 	    NULL);
193 
194     /* Create the file name widgets. */
195     command_label = XtVaCreateManagedWidget(
196 	    "command", labelWidgetClass, idle_dialog,
197 	    XtNvertDistance, FAR_VGAP,
198 	    XtNhorizDistance, MARGIN,
199 	    XtNborderWidth, 0,
200 	    NULL);
201     command_value = XtVaCreateManagedWidget(
202 	    "value", asciiTextWidgetClass, idle_dialog,
203 	    XtNeditType, XawtextEdit,
204 	    XtNwidth, FILE_WIDTH,
205 	    XtNvertDistance, FAR_VGAP,
206 	    XtNfromHoriz, command_label,
207 	    XtNhorizDistance, 0,
208 	    NULL);
209     dialog_match_dimension(command_label, command_value, XtNheight);
210     w = XawTextGetSource(command_value);
211     if (w == NULL) {
212 	XtWarning("Cannot find text source in dialog");
213     } else {
214 	XtAddCallback(w, XtNcallback, dialog_text_callback,
215 		(XtPointer)&t_command);
216     }
217     dialog_register_sensitivity(command_value,
218 	    NULL, False,
219 	    NULL, False,
220 	    NULL, False);
221 
222     timeout_label = XtVaCreateManagedWidget(
223 	    "timeout", labelWidgetClass, idle_dialog,
224 	    XtNfromVert, command_label,
225 	    XtNvertDistance, 3,
226 	    XtNhorizDistance, MARGIN,
227 	    XtNborderWidth, 0,
228 	    NULL);
229     timeout_value = XtVaCreateManagedWidget(
230 	    "value", asciiTextWidgetClass, idle_dialog,
231 	    XtNeditType, XawtextEdit,
232 	    XtNwidth, FILE_WIDTH,
233 	    XtNdisplayCaret, False,
234 	    XtNfromVert, command_label,
235 	    XtNvertDistance, 3,
236 	    XtNfromHoriz, timeout_label,
237 	    XtNhorizDistance, 0,
238 	    NULL);
239     dialog_match_dimension(timeout_label, timeout_value, XtNheight);
240     dialog_match_dimension(command_label, timeout_label, XtNwidth);
241     w = XawTextGetSource(timeout_value);
242     if (w == NULL) {
243 	XtWarning("Cannot find text source in dialog");
244     } else {
245 	XtAddCallback(w, XtNcallback, dialog_text_callback,
246 		(XtPointer)&t_numeric);
247     }
248     dialog_register_sensitivity(timeout_value,
249 	    NULL, False,
250 	    NULL, False,
251 	    NULL, False);
252 
253     /* Create the hour/minute/seconds radio buttons. */
254     hours_toggle = XtVaCreateManagedWidget(
255 	    "hours", commandWidgetClass, idle_dialog,
256 	    XtNfromVert, timeout_value,
257 	    XtNvertDistance, CLOSE_VGAP,
258 	    XtNhorizDistance, MARGIN,
259 	    XtNborderWidth, 0,
260 	    XtNsensitive, True,
261 	    NULL);
262     dialog_apply_bitmap(hours_toggle, no_diamond);
263     XtAddCallback(hours_toggle, XtNcallback, toggle_hms,
264 	    (XtPointer)&s_hours);
265     minutes_toggle = XtVaCreateManagedWidget(
266 	    "minutes", commandWidgetClass, idle_dialog,
267 	    XtNfromVert, timeout_value,
268 	    XtNvertDistance, CLOSE_VGAP,
269 	    XtNfromHoriz, hours_toggle,
270 	    XtNhorizDistance, MARGIN,
271 	    XtNborderWidth, 0,
272 	    XtNsensitive, True,
273 	    NULL);
274     dialog_apply_bitmap(minutes_toggle, diamond);
275     XtAddCallback(minutes_toggle, XtNcallback, toggle_hms,
276 	    (XtPointer)&s_minutes);
277     seconds_toggle = XtVaCreateManagedWidget(
278 	    "seconds", commandWidgetClass, idle_dialog,
279 	    XtNfromVert, timeout_value,
280 	    XtNvertDistance, CLOSE_VGAP,
281 	    XtNfromHoriz, minutes_toggle,
282 	    XtNhorizDistance, MARGIN,
283 	    XtNborderWidth, 0,
284 	    XtNsensitive, True,
285 	    NULL);
286     dialog_apply_bitmap(seconds_toggle, no_diamond);
287     XtAddCallback(seconds_toggle, XtNcallback, toggle_hms,
288 	    (XtPointer)&s_seconds);
289 
290     /* Create the fuzz toggle. */
291     fuzz_toggle = XtVaCreateManagedWidget(
292 	    "fuzz", commandWidgetClass, idle_dialog,
293 	    XtNfromVert, hours_toggle,
294 	    XtNvertDistance, CLOSE_VGAP,
295 	    XtNhorizDistance, MARGIN,
296 	    XtNborderWidth, 0,
297 	    XtNsensitive, True,
298 	    NULL);
299     dialog_apply_bitmap(fuzz_toggle, no_dot);
300     XtAddCallback(fuzz_toggle, XtNcallback, toggle_fuzz, NULL);
301 
302     /* Create enable/disable toggles. */
303     enable_toggle = XtVaCreateManagedWidget(
304 	    "enable", commandWidgetClass, idle_dialog,
305 	    XtNfromVert, fuzz_toggle,
306 	    XtNvertDistance, FAR_VGAP,
307 	    XtNhorizDistance, MARGIN,
308 	    XtNborderWidth, 0,
309 	    NULL);
310     dialog_apply_bitmap(enable_toggle,
311 	    (idle_user_enabled == IDLE_SESSION)? diamond: no_diamond);
312     XtAddCallback(enable_toggle, XtNcallback, toggle_enable,
313 	    (XtPointer)&s_session);
314     enable_perm_toggle = XtVaCreateManagedWidget(
315 	    "enablePerm", commandWidgetClass, idle_dialog,
316 	    XtNfromVert, enable_toggle,
317 	    XtNvertDistance, CLOSE_VGAP,
318 	    XtNhorizDistance, MARGIN,
319 	    XtNborderWidth, 0,
320 	    NULL);
321     dialog_apply_bitmap(enable_perm_toggle,
322 	    (idle_user_enabled == IDLE_PERM)? diamond: no_diamond);
323     XtAddCallback(enable_perm_toggle, XtNcallback, toggle_enable,
324 	    (XtPointer)&s_perm);
325     disable_toggle = XtVaCreateManagedWidget(
326 	    "disable", commandWidgetClass, idle_dialog,
327 	    XtNfromVert, enable_perm_toggle,
328 	    XtNvertDistance, CLOSE_VGAP,
329 	    XtNhorizDistance, MARGIN,
330 	    XtNborderWidth, 0,
331 	    NULL);
332     dialog_apply_bitmap(disable_toggle,
333 	    (idle_user_enabled == IDLE_DISABLED)? diamond: no_diamond);
334     XtAddCallback(disable_toggle, XtNcallback, toggle_enable,
335 	    (XtPointer)&s_disabled);
336 
337     /* Set up the buttons at the bottom. */
338     okay_button = XtVaCreateManagedWidget(
339 	    ObjConfirmButton, commandWidgetClass, idle_dialog,
340 	    XtNfromVert, disable_toggle,
341 	    XtNvertDistance, FAR_VGAP,
342 	    XtNhorizDistance, MARGIN,
343 	    NULL);
344     XtAddCallback(okay_button, XtNcallback, okay_callback, NULL);
345 
346     cancel_button = XtVaCreateManagedWidget(
347 	    ObjCancelButton, commandWidgetClass, idle_dialog,
348 	    XtNfromVert, disable_toggle,
349 	    XtNvertDistance, FAR_VGAP,
350 	    XtNfromHoriz, okay_button,
351 	    XtNhorizDistance, BUTTON_GAP,
352 	    NULL);
353     XtAddCallback(cancel_button, XtNcallback, idle_cancel, 0);
354 }
355 
356 /* Callbacks for all the idle widgets. */
357 
358 /* Idle pop-up popping up. */
359 static void
idle_popup_callback(Widget w _is_unused,XtPointer client_data _is_unused,XtPointer call_data _is_unused)360 idle_popup_callback(Widget w _is_unused, XtPointer client_data _is_unused,
361 	XtPointer call_data _is_unused)
362 {
363     /* Set the focus to the command widget. */
364     PA_dialog_focus_xaction(command_value, NULL, NULL, NULL);
365 }
366 
367 /* Cancel button pushed. */
368 static void
idle_cancel(Widget w _is_unused,XtPointer client_data _is_unused,XtPointer call_data _is_unused)369 idle_cancel(Widget w _is_unused, XtPointer client_data _is_unused,
370 	XtPointer call_data _is_unused)
371 {
372     XtPopdown(idle_shell);
373 }
374 
375 /* OK button pushed. */
376 static void
okay_callback(Widget w _is_unused,XtPointer call_parms _is_unused,XtPointer call_data _is_unused)377 okay_callback(Widget w _is_unused, XtPointer call_parms _is_unused,
378 	XtPointer call_data _is_unused)
379 {
380     if (idle_start()) {
381 	idle_changed = true;
382 	XtPopdown(idle_shell);
383     }
384 }
385 
386 /* Mark a toggle. */
387 static void
mark_toggle(Widget w,Pixmap p)388 mark_toggle(Widget w, Pixmap p)
389 {
390     XtVaSetValues(w, XtNleftBitmap, p, NULL);
391 }
392 
393 /* Hour/minute/second options. */
394 static void
toggle_hms(Widget w _is_unused,XtPointer client_data,XtPointer call_data _is_unused)395 toggle_hms(Widget w _is_unused, XtPointer client_data,
396 	XtPointer call_data _is_unused)
397 {
398     /* Toggle the flag */
399     hms = *(char *)client_data;
400 
401     /* Change the widget states. */
402     mark_toggle(hours_toggle, (hms == 'h') ? diamond : no_diamond);
403     mark_toggle(minutes_toggle, (hms == 'm') ? diamond : no_diamond);
404     mark_toggle(seconds_toggle, (hms == 's') ? diamond : no_diamond);
405 }
406 
407 /* Fuzz option. */
408 static void
toggle_fuzz(Widget w _is_unused,XtPointer client_data,XtPointer call_data _is_unused)409 toggle_fuzz(Widget w _is_unused, XtPointer client_data,
410 	XtPointer call_data _is_unused)
411 {
412     /* Toggle the flag */
413     fuzz = !fuzz;
414 
415     /* Change the widget state. */
416     mark_toggle(fuzz_toggle, fuzz ? dot : no_dot);
417 }
418 
419 /* Enable/disable options. */
420 static void
toggle_enable(Widget w _is_unused,XtPointer client_data,XtPointer call_data _is_unused)421 toggle_enable(Widget w _is_unused, XtPointer client_data,
422 	XtPointer call_data _is_unused)
423 {
424     /* Toggle the flag */
425     idle_user_enabled = *(enum idle_enum *)client_data;
426 
427     /* Change the widget states. */
428     mark_toggle(enable_toggle, (idle_user_enabled == IDLE_SESSION)?
429 	    diamond: no_diamond);
430     mark_toggle(enable_perm_toggle, (idle_user_enabled == IDLE_PERM)?
431 	    diamond: no_diamond);
432     mark_toggle(disable_toggle, (idle_user_enabled == IDLE_DISABLED)?
433 	    diamond: no_diamond);
434 }
435 
436 /*
437  * Called when the user presses the OK button on the idle command dialog.
438  * Returns true for success, false otherwise.
439  */
440 static bool
idle_start(void)441 idle_start(void)
442 {
443     char *cmd, *tmo, *its;
444 
445     /* Update the globals, so the dialog has the same values next time. */
446     XtVaGetValues(command_value, XtNstring, &cmd, NULL);
447     Replace(idle_command, NewString(cmd));
448     XtVaGetValues(timeout_value, XtNstring, &tmo, NULL);
449     its = Malloc(strlen(tmo) + 3);
450     sprintf(its, "%s%s%c", fuzz? "~": "", tmo, hms);
451     Replace(idle_timeout_string, its);
452 
453     /* See if they've turned it off. */
454     if (!idle_user_enabled) {
455 	/* If they're turned it off, cancel the timer. */
456 	cancel_idle_timer();
457 	return true;
458     }
459 
460     /* They've turned it on, and possibly reconfigured it. */
461 
462     /* Validate the timeout.  It should work, yes? */
463     if (!process_idle_timeout_value(its)) {
464 	return false;
465     }
466 
467     /* Seems okay.  Reset to the new interval and command. */
468     if (IN_3270) {
469 	    reset_idle_timer();
470     }
471     return true;
472 }
473