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