1 /* dialog.c - draws various useful dialog boxes
2    Copyright (C) 1996-2017 Paul Sheer
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307, USA.
18  */
19 
20 #include <config.h>
21 #include <stdio.h>
22 #include <my_string.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 
29 #include <X11/Intrinsic.h>
30 #include "lkeysym.h"
31 
32 #include "stringtools.h"
33 #include "app_glob.c"
34 
35 #include "coolwidget.h"
36 #include "coollocal.h"
37 #include "loadfile.h"
38 
39 #include "mad.h"
40 #include "edit.h"
41 #include "editcmddef.h"
42 
43 #define MID_X 20
44 #define MID_Y 20
45 
46 extern struct look *look;
47 
48 /* Yuriy Elkin: (from mc/src/util.c) */
get_sys_error(const char * s)49 char *get_sys_error (const char *s)
50 {
51     const char *error_msg;
52     if (errno) {
53 #ifdef HAVE_STRERROR
54 	error_msg = _ (strerror (errno));
55 #else
56 	extern int sys_nerr;
57 	extern char *sys_errlist[];
58 	if ((0 <= errno) && (errno < sys_nerr))
59 	    error_msg = _ (sys_errlist[errno]);
60 	else
61 /* The returned value, 'errno' has an unknown meaning */
62 	    error_msg = _ ("strange errno");
63 #endif
64 	return catstrs (s, "\n [", error_msg, "] ", NULL);
65     }
66     return (char *) s;
67 }
68 
69 /* error messages displayed before the main window is mapped must
70    be displayed on the root window to be seen */
find_mapped_window(Window w)71 Window find_mapped_window (Window w)
72 {
73     CWidget *wdt;
74     if (w == CRoot)
75 	return CRoot;
76     if (!w)
77 	w = CFirstWindow;
78     if ((wdt = CWidgetOfWindow (w)))
79 	if (!wdt->mapped)
80 	    return CRoot;
81     return w;
82 }
83 
CErrorDialog(Window in,int x,int y,const char * heading,const char * fmt,...)84 void CErrorDialog (Window in, int x, int y, const char *heading, const char *fmt,...)
85 {
86     static int inside = 0;
87     va_list pa;
88     char *str;
89     Window win;
90     CEvent cwevent;
91     CState s;
92 
93     if (inside)
94         return;
95 
96     inside = 1;
97 
98     CPushFont ("widget", 0);
99 
100     va_start (pa, fmt);
101     str = vsprintf_alloc (fmt, pa);
102     va_end (pa);
103 
104     if (!in) {
105 	x = MID_X;
106 	y = MID_Y;
107     }
108     in = find_mapped_window (in);
109     CBackupState (&s);
110     CDisable ("*");
111     win = CDrawHeadedDialog ("_error", in, x, y, heading);
112     CGetHintPos (&x, &y);
113     (CDrawText ("", win, x, y, "%s", str))->position = POSITION_CENTRE;
114     free (str);
115     CGetHintPos (0, &y);
116     ((*look->draw_exclam_cancel_button) ("_clickhere", win, -50, y))->position = POSITION_CENTRE;
117     CIdent ("_error")->position = WINDOW_UNMOVEABLE | WINDOW_ALWAYS_RAISED;
118     CSetSizeHintPos ("_error");
119     CMapDialog ("_error");
120     CFocus (CIdent ("_clickhere"));
121     do {
122 	CNextEvent (NULL, &cwevent);
123 	if (!CIdent ("_error"))
124 	    break;
125     } while (strcmp (cwevent.ident, "_clickhere") && cwevent.command != CK_Cancel);
126 
127     CPopFont ();
128 
129     CDestroyWidget ("_error");
130     CRestoreState (&s);
131 
132     inside = 0;
133 }
134 
CMessageDialog(Window in,int x,int y,unsigned long options,const char * heading,const char * fmt,...)135 void CMessageDialog (Window in, int x, int y, unsigned long options, const char *heading, const char *fmt,...)
136 {
137     va_list pa;
138     char *str;
139     Window win;
140     CEvent cwevent;
141     CState s;
142 
143     CPushFont ("widget", 0);
144 
145     va_start (pa, fmt);
146     str = vsprintf_alloc (fmt, pa);
147     va_end (pa);
148 
149     in = find_mapped_window (in);
150 
151     CBackupState (&s);
152     CDisable ("*");
153     win = CDrawHeadedDialog ("_error", in, x, y, heading);
154     CGetHintPos (&x, &y);
155     (CDrawText ("", win, x, y, "%s", str))->options = options;
156     free (str);
157     CGetHintPos (0, &y);
158     ((*look->draw_tick_cancel_button) ("_clickhere", win, -50, y))->position = POSITION_CENTRE;
159     CCentre ("_clickhere");
160     CIdent ("_error")->position = WINDOW_UNMOVEABLE | WINDOW_ALWAYS_RAISED;
161     CSetSizeHintPos ("_error");
162     CMapDialog ("_error");
163     CFocus (CIdent ("_clickhere"));
164     do {
165 	CNextEvent (NULL, &cwevent);
166 	if (!CIdent ("_error"))
167 	    break;
168     } while (strcmp (cwevent.ident, "_clickhere") && cwevent.command != CK_Cancel && cwevent.command != CK_Enter);
169 
170     CPopFont ();
171 
172     CDestroyWidget ("_error");
173     CRestoreState (&s);
174 }
175 
176 /* draws a scrollable text box with a button to clear. Can be used to give long help messages */
CTextboxMessageDialog(Window in,int x,int y,int columns,int lines,const char * heading,const char * text,int line)177 void CTextboxMessageDialog (Window in, int x, int y, int columns, int lines, const char *heading, const char *text, int line)
178 {
179     Window win;
180     CEvent cwevent;
181     CState s;
182     int width, height;
183 
184     CPushFont ("editor", 0);
185     CTextSize (&width, &height, text);
186     width = min (columns * FONT_MEAN_WIDTH, width) + 1 + 6;
187     height = min (lines * FONT_PIX_PER_LINE, height) + 1 + 6;
188     CPopFont ();
189 
190     if (!in) {
191 	x = MID_X;
192 	y = MID_Y;
193     }
194     in = find_mapped_window (in);
195 
196     CBackupState (&s);
197     CDisable ("*");
198     win = CDrawHeadedDialog ("_error", in, x, y, heading);
199     CGetHintPos (&x, &y);
200     CDrawTextbox ("_textmessbox", win, x, y, width, height, line, 0, text, 0);
201     CGetHintPos (0, &y);
202     ((*look->draw_tick_cancel_button) ("_clickhere", win, -50, y))->position = POSITION_CENTRE;
203     CCentre ("_clickhere");
204     CIdent ("_error")->position = WINDOW_UNMOVEABLE | WINDOW_ALWAYS_RAISED;
205     CSetSizeHintPos ("_error");
206     CMapDialog ("_error");
207     CFocus (CIdent ("_clickhere"));
208     do {
209 	CNextEvent (NULL, &cwevent);
210 	if (!CIdent ("_error"))
211 	    break;
212     } while (strcmp (cwevent.ident, "_clickhere") && cwevent.command != CK_Cancel && cwevent.command != CK_Enter);
213     CDestroyWidget ("_error");
214     CRestoreState (&s);
215 }
216 
217 /*
218    Draws a scrollable text box with a button to clear.
219    the text box contains lines of text gotten from get_line().
220    Returns the number of the line the user double clicked on,
221    or pressed enter or space on. get_line() must return a null
222    terminated string without newlines. The string may exist
223    statically within the getline() function and be overwritten
224    with each new call to getline().
225    Returns -1 on cancel.
226    With heading = 0, this behaves like trivialselection below.
227  */
CListboxDialog(Window in,int x,int y,int columns,int lines,const char * heading,int start_line,int cursor_line,int num_lines,char * (* get_line)(void * data,int line),void * data)228 int CListboxDialog (Window in, int x, int y, int columns, int lines,
229      const char *heading, int start_line, int cursor_line, int num_lines,
230 		    char *(*get_line) (void *data, int line), void *data)
231 {
232     Window win;
233     CEvent cwevent;
234     CState s;
235     int width, height, len, i;
236     char *text, *p;
237 
238     CPushFont ("editor", 0);
239     width = columns * FONT_MEAN_WIDTH + 1 + 6;
240     height = lines * FONT_PIX_PER_LINE + 1 + 6;
241     CPopFont ();
242 
243     if (!in) {
244 	x = MID_X;
245 	y = MID_Y;
246     }
247     in = find_mapped_window (in);
248     CBackupState (&s);
249     CDisable ("*");
250 
251     len = 0;
252     for (i = 0; i < num_lines; i++)
253 	len += strlen ((*get_line) (data, i)) + 1;
254     p = text = CMalloc (len + 1);
255     p[0] = '\0';
256     for (i = 0; i < num_lines; i++) {
257 	strcpy (p, (*get_line) (data, i));
258 	p += strlen (p);
259 	*p++ = '\n';
260     }
261     i = -1;
262     if ((unsigned long) p > (unsigned long) text)
263 	*(--p) = 0;
264     if (heading)
265 	win = CDrawHeadedDialog ("_error", in, x, y, heading);
266     else
267 	win = CDrawDialog ("_error", in, x, y);
268     CGetHintPos (&x, &y);
269     (CDrawTextbox ("_textmessbox", win, x, y, width, height, start_line, 0, text, TEXTBOX_MAN_PAGE))->cursor = cursor_line;
270     CGetHintPos (0, &y);
271     if (heading) {
272 	((*look->draw_cross_cancel_button) ("_clickhere", win, -50, y))->position = POSITION_CENTRE;
273 	CCentre ("_clickhere");
274     }
275     CIdent ("_error")->position = WINDOW_ALWAYS_RAISED;
276     CSetSizeHintPos ("_error");
277     CMapDialog ("_error");
278     CFocus (CIdent ("_textmessbox"));
279     do {
280 	CNextEvent (NULL, &cwevent);
281 	if (heading) {
282 	    if (!strcmp (cwevent.ident, "_clickhere"))
283 		break;
284 	} else {
285 	    if (cwevent.key == XK_Tab || cwevent.key == XK_ISO_Left_Tab)
286 		break;
287 	}
288 	if (!strcmp (cwevent.ident, "_textmessbox"))
289 	    if (cwevent.key == XK_space || cwevent.command == CK_Enter ||
290 		cwevent.double_click) {
291 		i = (CIdent ("_textmessbox"))->cursor;
292 		break;
293 	    }
294 	if (!CIdent ("_error"))
295 	    break;
296     } while (cwevent.command != CK_Cancel);
297     CDestroyWidget ("_error");
298     CRestoreState (&s);
299     free (text);
300     return i;
301 }
302 
303 
304 /*
305    Draws a scrollable text box. Returns the line you pressed enter on
306    or double-clicked on. Result must not be free'd. Result must be
307    copied ASAP. Returns 0 if cancelled or clicked elsewhere.
308    Result must be less than 1024 bytes long.
309  */
CTrivialSelectionDialog(Window in,int x,int y,int columns,int lines,const char * text,int line,int cursor_line)310 char *CTrivialSelectionDialog (Window in, int x, int y, int columns, int lines, const char *text, int line, int cursor_line)
311 {
312     Window win;
313     CEvent cwevent;
314     XEvent xevent;
315     CState s;
316     int width, height;
317     char *p = 0;
318     CWidget *w;
319 
320     memset (&xevent, 0, sizeof (xevent));
321 
322     CPushFont ("editor", 0);
323     width = columns * FONT_MEAN_WIDTH + 1 + 6;
324     height = lines * FONT_PIX_PER_LINE + 1 + 6;
325     CPopFont ();
326 
327     CBackupState (&s);
328     CDisable ("*");
329     win = CDrawDialog ("_select", in, x, y);
330     CGetHintPos (&x, &y);
331     w = CDrawTextbox ("_textmessbox", win, x, y, width, height, line, 0, text, 0);
332     w->cursor = cursor_line;
333     CGetHintPos (0, &y);
334     CIdent ("_select")->position = WINDOW_UNMOVEABLE | WINDOW_ALWAYS_RAISED;
335     CSetSizeHintPos ("_select");
336     CMapDialog ("_select");
337     CFocus (CIdent ("_textmessbox"));
338     do {
339 	CNextEvent (&xevent, &cwevent);
340 	if (xevent.xany.window == w->winid) {
341 	    if (!strcmp (cwevent.ident, "_textmessbox") && (cwevent.command == CK_Enter || cwevent.double_click)) {
342 		p = CGetTextBoxLine (w, w->cursor);
343 		break;
344 	    }
345 	} else if (xevent.xany.type == ButtonPress
346 		   && cwevent.kind != C_VERTSCROLL_WIDGET
347 		   && cwevent.kind != C_HORISCROLL_WIDGET
348 		   && cwevent.kind != C_WINDOW_WIDGET) {
349 	    CSendEvent (&xevent);	/* resend this to get processed, here the widget is disabled */
350 	    break;
351 	}
352 	if (!CIdent ("_select"))
353 	    break;
354     } while (cwevent.command != CK_Cancel && cwevent.key != XK_KP_Tab && cwevent.key != XK_Tab);
355     CDestroyWidget ("_select");
356     CRestoreState (&s);
357     return p;
358 }
359 
360 
CFatalErrorDialog(int x,int y,const char * fmt,...)361 void CFatalErrorDialog (int x, int y, const char *fmt,...)
362 {
363     va_list pa;
364     char *str;
365     Window win;
366     CEvent cwevent;
367     CState s;
368     Window in;
369 
370     va_start (pa, fmt);
371     str = vsprintf_alloc (fmt, pa);
372     va_end (pa);
373 
374     fprintf (stderr, "%s: %s\n", CAppName, str);
375     in = find_mapped_window (0);
376 
377     if (CDisplay) {
378 	CBackupState (&s);
379 	CDisable ("*");
380 	win = CDrawHeadedDialog ("fatalerror", in, x, y," Fatal Error ");
381 	CGetHintPos (&x, &y);
382 	CDrawText ("fatalerror.text", win, x, y, "%s", str);
383 	CCentre ("fatalerror.text");
384 	CGetHintPos (0, &y);
385 	((*look->draw_cross_cancel_button) ("clickhere", win, -50, y))->position = POSITION_CENTRE;
386 	CCentre ("clickhere");
387 	CIdent ("fatalerror")->position = WINDOW_UNMOVEABLE | WINDOW_ALWAYS_RAISED;
388 	CSetSizeHintPos ("fatalerror");
389 	CMapDialog ("fatalerror");
390 	CFocus (CIdent ("clickhere"));
391 	do {
392 	    CNextEvent (NULL, &cwevent);
393 	    if (!CIdent ("fatalerror"))
394 		abort ();
395 	} while (strcmp (cwevent.ident, "clickhere"));
396     }
397     abort ();
398 }
399 
400 
401 
402 /* returns a raw XK_key sym, or 0 on cancel */
CRawkeyQuery(Window in,int x,int y,const char * heading,const char * fmt,...)403 XEvent *CRawkeyQuery (Window in, int x, int y, const char *heading, const char *fmt,...)
404 {
405     va_list pa;
406     char *str;
407     XEvent *p = 0;
408     Window win;
409     CEvent cwevent;
410     static XEvent xevent;
411     CState s;
412 
413     va_start (pa, fmt);
414     str = vsprintf_alloc (fmt, pa);
415     va_end (pa);
416 
417     if (!in) {
418 	x = MID_X;
419 	y = MID_Y;
420     }
421     in = find_mapped_window (in);
422 
423     CBackupState (&s);
424     CDisable ("*");
425     win = CDrawHeadedDialog ("_rawkeydlg", in, x, y, heading);
426     CGetHintPos (&x, &y);
427     CDrawText ("_rawkeydlg.text", win, x, y, "%s", str);
428     CGetHintPos (&x, 0);
429     free (str);
430     CDrawTextInput ("_rawkeydlg.input", win, x, y, (FONT_MEAN_WIDTH) * 6, AUTO_HEIGHT, 256, "");
431     CGetHintPos (0, &y);
432     ((*look->draw_cross_cancel_button) ("_rawkeydlg.crosshere", win, -50, y))->position = POSITION_CENTRE;
433     CCentre ("_rawkeydlg.crosshere");
434     CSetSizeHintPos ("_rawkeydlg");
435     CMapDialog ("_rawkeydlg");
436     CFocus (CIdent ("_rawkeydlg.input"));
437     CIdent ("_rawkeydlg")->position = WINDOW_ALWAYS_RAISED;
438 /* handler : */
439     do {
440 	CNextEvent (&xevent, &cwevent);
441 	if (!CIdent ("_rawkeydlg"))
442 	    break;
443 	if (cwevent.command == CK_Cancel || !strcmp (cwevent.ident, "_rawkeydlg.crosshere"))
444 	    break;
445 	if (xevent.type == KeyPress) {
446 	    KeySym k;
447 	    k = CKeySym (&xevent);
448 	    if (k && !mod_type_key (k))
449 		p = &xevent;
450 	}
451     } while (!p);
452 
453     CDestroyWidget ("_rawkeydlg");
454     CRestoreState (&s);
455     return p;
456 }
457 
458 /*
459    def is the default string in the textinput widget.
460    Result must be free'd. Returns 0 on cancel.
461  */
CInputDialog(const char * ident,Window in,int x,int y,int min_width,const char * def,const char * heading,const char * fmt,...)462 char *CInputDialog (const char *ident, Window in, int x, int y, int min_width, const char *def, const char *heading, const char *fmt,...)
463 {
464     va_list pa;
465     char *str, *p = 0;
466     int w, h;
467     Window win;
468     CEvent cwevent;
469     CState s;
470     char inp_name[256];
471     int browse = 0, xf, yf;
472 
473     min_width &= ~INPUT_DIALOG_BROWSE_MASK;
474     browse = min_width & INPUT_DIALOG_BROWSE_MASK;
475 
476     va_start (pa, fmt);
477     str = vsprintf_alloc (fmt, pa);
478     va_end (pa);
479 
480     if (!in) {
481 	x = MID_X;
482 	y = MID_Y;
483     }
484     xf = x + 20;
485     yf = y + 20;
486     in = find_mapped_window (in);
487     CTextSize (&w, &h, str);
488     w = max (max (w, min_width), 130);
489     CBackupState (&s);
490     CDisable ("*");
491     win = CDrawHeadedDialog ("_inputdialog", in, x, y, heading);
492     CGetHintPos (&x, &y);
493     CDrawText ("", win, x, y, "%s", str);
494     CGetHintPos (0, &y);
495     free (str);
496     strcpy (inp_name, ident);
497     inp_name[20] = '\0';
498     strcat (inp_name, ".inpt_dlg");
499     CDrawTextInput (inp_name, win, x, y, w, AUTO_HEIGHT, 256, def);
500     if (browse) {
501 	CGetHintPos (&x, 0);
502 	w += (CDrawButton ("_inputdialog.browse", win, x, y, AUTO_SIZE, _ (" Browse... ")))->width + WIDGET_SPACING;
503     }
504     CGetHintPos (0, &y);
505     (*look->draw_tick_ok_button) ("_inputdialog.clickhere", win, (w + 16) / 4 - 22, y);
506     (*look->draw_cross_cancel_button) ("_inputdialog.crosshere", win, 3 * (w + 16) / 4 - 22, y);
507     CSetSizeHintPos ("_inputdialog");
508     CMapDialog ("_inputdialog");
509     CFocus (CIdent (inp_name));
510     CIdent ("_inputdialog")->position = WINDOW_ALWAYS_RAISED;
511 /* handler : */
512     do {
513 	CNextEvent (NULL, &cwevent);
514 	if (cwevent.command == CK_Cancel || !strcmp (cwevent.ident, "_inputdialog.crosshere"))
515 	    goto fin;
516 	if (cwevent.command == CK_Enter)
517 	    break;
518 	if (!strcmp (cwevent.ident, "_inputdialog.browse")) {
519 	    char *f = 0;
520 /* dialog title */
521 	    switch (browse) {
522 	    case INPUT_DIALOG_BROWSE_DIR:
523 		f = CGetDirectory (in, xf, yf, current_dir, "", _ (" Browse "));
524 		break;
525 	    case INPUT_DIALOG_BROWSE_SAVE:
526 		f = CGetSaveFile (in, xf, yf, current_dir, "", _ (" Browse "));
527 		break;
528 	    case INPUT_DIALOG_BROWSE_LOAD:
529 		f = CGetLoadFile (in, xf, yf, current_dir, "", _ (" Browse "));
530 		break;
531 	    }
532 	    if (f)
533 		if (*f) {
534 		    if (CIdent (inp_name)->text)
535 			free (CIdent (inp_name)->text);
536 		    CIdent (inp_name)->text = f;
537 		    CIdent (inp_name)->cursor = strlen (f);
538 		    CExpose (inp_name);
539 		}
540 	    CFocus (CIdent (inp_name));
541 	}
542 	if (!CIdent ("_inputdialog"))
543 	    goto fin;
544     } while (strcmp (cwevent.ident, "_inputdialog.clickhere"));
545     p = (char *) strdup (CIdent (inp_name)->text);
546 
547   fin:
548     CDestroyWidget ("_inputdialog");
549     CRestoreState (&s);
550     return p;
551 }
552 
553 static char *id[32] =
554 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
555 
free_last_query_buttons(void)556 void free_last_query_buttons (void)
557 {
558     int i;
559     for (i = 0; i < 32; i++)
560 	if (id[i]) {
561 	    free (id[i]);
562 	    id[i] = 0;
563 	}
564 }
565 
566 /* returns -1 on widget-destroyed-without-a-button-pressed or cancel (i.e. Esc) */
CQueryDialog(Window in,int x,int y,const char * heading,const char * first,...)567 int CQueryDialog (Window in, int x, int y, const char *heading, const char *first,...)
568 {
569     va_list pa;
570     int i, buttons = 0, r = -1;
571     Window win;
572     CEvent cwevent;
573     CState s;
574     char *b[32];
575 
576     free_last_query_buttons ();
577     va_start (pa, first);
578     while ((b[buttons] = space_string (va_arg (pa, char *))))
579 	 buttons++;
580     va_end (pa);
581     if (!buttons)
582 	return -1;
583 
584     if (!in) {
585 	x = MID_X;
586 	y = MID_Y;
587     }
588     in = find_mapped_window (in);
589     CBackupState (&s);
590     CDisable ("*");
591     win = CDrawHeadedDialog ("_querydialog", in, x, y, heading);
592     CGetHintPos (&x, &y);
593     CDrawText ("_querydialog.text", win, x, y, "%s", first);
594     CGetHintPos (0, &y);
595 
596     for (i = 0; i < buttons; i++) {
597 	CDrawButton (id[i] = sprintf_alloc ("_query.%.20s", b[i]),
598 		     win, x, y, AUTO_WIDTH, AUTO_HEIGHT, b[i]);
599 	CGetHintPos (&x, 0);
600     }
601 
602     CSetSizeHintPos ("_querydialog");
603     CMapDialog ("_querydialog");
604     CFocus (CIdent (catstrs ("_query.", b[0], NULL)));
605     CIdent ("_querydialog")->position = WINDOW_ALWAYS_RAISED;
606     for (; r < 0;) {
607 	CNextEvent (NULL, &cwevent);
608 	if (!CIdent ("_querydialog"))
609 	    break;
610 	if (!cwevent.handled && cwevent.command == CK_Cancel)
611 	    break;
612 	for (i = 0; i < buttons; i++) {
613 	    if (!strcmp (cwevent.ident, id[i])) {
614 		r = i;
615 		break;
616 	    }
617 	}
618     }
619 
620     for (i = 0; i < buttons; i++)
621 	free (b[i]);
622 
623     CDestroyWidget ("_querydialog");
624     CRestoreState (&s);
625     return r;
626 }
627 
628