Merge branch '4524_cleanup'
[midnight-commander.git] / lib / widget / quick.c
blob3a6d165b85c3da25764ca3ea3ff7d330dc6be37c
1 /*
2 Widget based utility functions.
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Authors:
8 Miguel de Icaza, 1994, 1995, 1996
9 Radek Doulik, 1994, 1995
10 Jakub Jelinek, 1995
11 Andrej Borsenkow, 1995
12 Andrew Borodin <aborodin@vmail.ru>, 2009-2022
14 This file is part of the Midnight Commander.
16 The Midnight Commander is free software: you can redistribute it
17 and/or modify it under the terms of the GNU General Public License as
18 published by the Free Software Foundation, either version 3 of the License,
19 or (at your option) any later version.
21 The Midnight Commander is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
26 You should have received a copy of the GNU General Public License
27 along with this program. If not, see <http://www.gnu.org/licenses/>.
30 /** \file quick.c
31 * \brief Source: quick dialog engine
34 #include <config.h>
36 #include <stdlib.h>
37 #include <stdio.h> /* fprintf() */
39 #include "lib/global.h"
40 #include "lib/strutil.h" /* str_term_width1() */
41 #include "lib/util.h" /* tilde_expand() */
42 #include "lib/widget.h"
44 /*** global variables ****************************************************************************/
46 /*** file scope macro definitions ****************************************************************/
48 #ifdef ENABLE_NLS
49 #define I18N(x) (x = x != NULL && *x != '\0' ? _(x) : x)
50 #else
51 #define I18N(x) (x = x)
52 #endif
54 /*** file scope type declarations ****************************************************************/
56 typedef struct
58 Widget *widget;
59 quick_widget_t *quick_widget;
60 } quick_widget_item_t;
62 /*** forward declarations (file scope functions) *************************************************/
64 /*** file scope variables ************************************************************************/
66 /* --------------------------------------------------------------------------------------------- */
67 /*** file scope functions ************************************************************************/
68 /* --------------------------------------------------------------------------------------------- */
70 static WInput *
71 quick_create_input (int y, int x, const quick_widget_t * qw)
73 WInput *in;
75 in = input_new (y, x, input_colors, 8, qw->u.input.text, qw->u.input.histname,
76 qw->u.input.completion_flags);
78 in->is_password = qw->u.input.is_passwd;
79 in->strip_password = qw->u.input.strip_passwd;
81 return in;
84 /* --------------------------------------------------------------------------------------------- */
86 static void
87 quick_create_labeled_input (GArray * widgets, int *y, int x, quick_widget_t * quick_widget,
88 int *width)
90 quick_widget_item_t in, label;
92 label.quick_widget = g_new0 (quick_widget_t, 1);
93 label.quick_widget->widget_type = quick_label;
94 label.quick_widget->options = quick_widget->options;
95 label.quick_widget->state = quick_widget->state;
96 /* FIXME: this should be turned in depend of label_location */
97 label.quick_widget->pos_flags = quick_widget->pos_flags;
99 switch (quick_widget->u.input.label_location)
101 case input_label_above:
102 label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
103 *y += label.widget->rect.lines - 1;
104 g_array_append_val (widgets, label);
106 in.widget = WIDGET (quick_create_input (++(*y), x, quick_widget));
107 in.quick_widget = quick_widget;
108 g_array_append_val (widgets, in);
110 *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
111 break;
113 case input_label_left:
114 label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
115 g_array_append_val (widgets, label);
117 in.widget = WIDGET (quick_create_input (*y, x + label.widget->rect.cols + 1, quick_widget));
118 in.quick_widget = quick_widget;
119 g_array_append_val (widgets, in);
121 *width = label.widget->rect.cols + in.widget->rect.cols + 1;
122 break;
124 case input_label_right:
125 in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
126 in.quick_widget = quick_widget;
127 g_array_append_val (widgets, in);
129 label.widget =
130 WIDGET (label_new
131 (*y, x + in.widget->rect.cols + 1, I18N (quick_widget->u.input.label_text)));
132 g_array_append_val (widgets, label);
134 *width = label.widget->rect.cols + in.widget->rect.cols + 1;
135 break;
137 case input_label_below:
138 in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
139 in.quick_widget = quick_widget;
140 g_array_append_val (widgets, in);
142 label.widget = WIDGET (label_new (++(*y), x, I18N (quick_widget->u.input.label_text)));
143 *y += label.widget->rect.lines - 1;
144 g_array_append_val (widgets, label);
146 *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
147 break;
149 default:
150 return;
153 INPUT (in.widget)->label = LABEL (label.widget);
154 /* cross references */
155 label.quick_widget->u.label.input = in.quick_widget;
156 in.quick_widget->u.input.label = label.quick_widget;
159 /* --------------------------------------------------------------------------------------------- */
160 /*** public functions ****************************************************************************/
161 /* --------------------------------------------------------------------------------------------- */
164 quick_dialog_skip (quick_dialog_t * quick_dlg, int nskip)
166 int len;
167 int blen = 0;
168 int x, y; /* current positions */
169 int y1 = 0; /* bottom of 1st column in case of two columns */
170 int y2 = -1; /* start of two columns */
171 int width1 = 0; /* width of single column */
172 int width2 = 0; /* width of each of two columns */
173 gboolean have_groupbox = FALSE;
174 gboolean two_columns = FALSE;
175 gboolean put_buttons = FALSE;
177 /* x position of 1st column is 3 */
178 const int x1 = 3;
179 /* x position of 2nd column is 4 and it will be fixed later, after creation of all widgets */
180 int x2 = 4;
182 GArray *widgets;
183 size_t i;
184 quick_widget_t *quick_widget;
185 WGroupbox *g = NULL;
186 WDialog *dd;
187 GList *input_labels = NULL; /* Widgets not directly requested by the user. */
188 int return_val;
190 len = str_term_width1 (I18N (quick_dlg->title)) + 6;
191 quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
193 y = 1;
194 x = x1;
196 /* create widgets */
197 widgets = g_array_sized_new (FALSE, FALSE, sizeof (quick_widget_item_t), 8);
199 for (quick_widget = quick_dlg->widgets; quick_widget->widget_type != quick_end; quick_widget++)
201 quick_widget_item_t item = { NULL, quick_widget };
202 int width = 0;
204 switch (quick_widget->widget_type)
206 case quick_checkbox:
207 item.widget =
208 WIDGET (check_new
209 (++y, x, *quick_widget->u.checkbox.state,
210 I18N (quick_widget->u.checkbox.text)));
211 g_array_append_val (widgets, item);
212 width = item.widget->rect.cols;
213 if (g != NULL)
214 width += 2;
215 if (two_columns)
216 width2 = MAX (width2, width);
217 else
218 width1 = MAX (width1, width);
219 break;
221 case quick_button:
222 /* single button */
223 item.widget = WIDGET (button_new (++y, x, quick_widget->u.button.action,
224 quick_widget->u.button.action == B_ENTER ?
225 DEFPUSH_BUTTON : NORMAL_BUTTON,
226 I18N (quick_widget->u.button.text),
227 quick_widget->u.button.callback));
228 g_array_append_val (widgets, item);
229 width = item.widget->rect.cols;
230 if (g != NULL)
231 width += 2;
232 if (two_columns)
233 width2 = MAX (width2, width);
234 else
235 width1 = MAX (width1, width);
236 break;
238 case quick_input:
239 *quick_widget->u.input.result = NULL;
240 y++;
241 if (quick_widget->u.input.label_location != input_label_none)
243 quick_create_labeled_input (widgets, &y, x, quick_widget, &width);
244 input_labels = g_list_prepend (input_labels, quick_widget->u.input.label);
246 else
248 item.widget = WIDGET (quick_create_input (y, x, quick_widget));
249 g_array_append_val (widgets, item);
250 width = item.widget->rect.cols;
252 if (g != NULL)
253 width += 2;
254 if (two_columns)
255 width2 = MAX (width2, width);
256 else
257 width1 = MAX (width1, width);
258 break;
260 case quick_label:
261 item.widget = WIDGET (label_new (++y, x, I18N (quick_widget->u.label.text)));
262 g_array_append_val (widgets, item);
263 y += item.widget->rect.lines - 1;
264 width = item.widget->rect.cols;
265 if (g != NULL)
266 width += 2;
267 if (two_columns)
268 width2 = MAX (width2, width);
269 else
270 width1 = MAX (width1, width);
271 break;
273 case quick_radio:
275 WRadio *r;
276 char **items = NULL;
278 /* create the copy of radio_items to avoid mwmory leak */
279 items = g_new (char *, quick_widget->u.radio.count + 1);
280 for (i = 0; i < (size_t) quick_widget->u.radio.count; i++)
281 items[i] = g_strdup (_(quick_widget->u.radio.items[i]));
282 items[i] = NULL;
284 r = radio_new (++y, x, quick_widget->u.radio.count, (const char **) items);
285 r->pos = r->sel = *quick_widget->u.radio.value;
286 g_strfreev (items);
287 item.widget = WIDGET (r);
288 g_array_append_val (widgets, item);
289 y += item.widget->rect.lines - 1;
290 width = item.widget->rect.cols;
291 if (g != NULL)
292 width += 2;
293 if (two_columns)
294 width2 = MAX (width2, width);
295 else
296 width1 = MAX (width1, width);
298 break;
300 case quick_start_groupbox:
301 I18N (quick_widget->u.groupbox.title);
302 len = str_term_width1 (quick_widget->u.groupbox.title);
303 g = groupbox_new (++y, x, 1, len + 4, quick_widget->u.groupbox.title);
304 item.widget = WIDGET (g);
305 g_array_append_val (widgets, item);
306 have_groupbox = TRUE;
307 break;
309 case quick_stop_groupbox:
310 if (g != NULL)
312 Widget *w = WIDGET (g);
314 y++;
315 w->rect.lines = y + 1 - w->rect.y;
316 g = NULL;
318 g_array_append_val (widgets, item);
320 break;
322 case quick_separator:
323 y++;
324 if (quick_widget->u.separator.line)
326 item.widget = WIDGET (hline_new (y, x, 1));
327 g_array_append_val (widgets, item);
329 break;
331 case quick_start_columns:
332 y2 = y;
333 g_array_append_val (widgets, item);
334 two_columns = TRUE;
335 break;
337 case quick_next_column:
338 x = x2;
339 y1 = y;
340 y = y2;
341 break;
343 case quick_stop_columns:
344 x = x1;
345 y = MAX (y1, y);
346 g_array_append_val (widgets, item);
347 two_columns = FALSE;
348 break;
350 case quick_buttons:
351 /* start put several buttons in bottom line */
352 if (quick_widget->u.separator.space)
354 y++;
356 if (quick_widget->u.separator.line)
357 item.widget = WIDGET (hline_new (y, 1, -1));
360 g_array_append_val (widgets, item);
362 /* several buttons in bottom line */
363 y++;
364 quick_widget++;
365 for (; quick_widget->widget_type == quick_button; quick_widget++)
367 item.widget = WIDGET (button_new (y, x++, quick_widget->u.button.action,
368 quick_widget->u.button.action == B_ENTER ?
369 DEFPUSH_BUTTON : NORMAL_BUTTON,
370 I18N (quick_widget->u.button.text),
371 quick_widget->u.button.callback));
372 item.quick_widget = quick_widget;
373 g_array_append_val (widgets, item);
374 blen += item.widget->rect.cols + 1;
377 /* stop dialog build here */
378 blen--;
379 quick_widget->widget_type = quick_end;
380 quick_widget--;
381 break;
383 default:
384 break;
388 /* adjust dialog width */
389 quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, blen + 6);
390 if (have_groupbox)
392 if (width1 != 0)
393 width1 += 2;
394 if (width2 != 0)
395 width2 += 2;
397 if (width2 == 0)
398 len = width1 + 6;
399 else
401 len = width2 * 2 + 7;
402 if (width1 != 0)
403 len = MAX (len, width1 + 6);
406 quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
407 width1 = quick_dlg->rect.cols - 6;
408 width2 = (quick_dlg->rect.cols - 7) / 2;
410 if (quick_dlg->rect.x == -1 || quick_dlg->rect.y == -1)
411 dd = dlg_create (TRUE, 0, 0, y + 3, quick_dlg->rect.cols, WPOS_CENTER | WPOS_TRYUP, FALSE,
412 dialog_colors, quick_dlg->callback, quick_dlg->mouse_callback,
413 quick_dlg->help, quick_dlg->title);
414 else
415 dd = dlg_create (TRUE, quick_dlg->rect.y, quick_dlg->rect.x, y + 3, quick_dlg->rect.cols,
416 WPOS_KEEP_DEFAULT, FALSE, dialog_colors, quick_dlg->callback,
417 quick_dlg->mouse_callback, quick_dlg->help, quick_dlg->title);
419 /* add widgets into the dialog */
420 x2 = x1 + width2 + 1;
421 g = NULL;
422 two_columns = FALSE;
423 x = (WIDGET (dd)->rect.cols - blen) / 2;
425 for (i = 0; i < widgets->len; i++)
427 quick_widget_item_t *item;
428 int column_width;
429 WRect *r;
431 item = &g_array_index (widgets, quick_widget_item_t, i);
432 r = &item->widget->rect;
433 column_width = two_columns ? width2 : width1;
435 /* adjust widget width and x position */
436 switch (item->quick_widget->widget_type)
438 case quick_label:
440 quick_widget_t *input = item->quick_widget->u.label.input;
442 if (input != NULL && input->u.input.label_location == input_label_right)
444 /* location of this label will be adjusted later */
445 break;
448 MC_FALLTHROUGH;
449 case quick_checkbox:
450 case quick_radio:
451 if (r->x != x1)
452 r->x = x2;
453 if (g != NULL)
454 r->x += 2;
455 break;
457 case quick_button:
458 if (!put_buttons)
460 if (r->x != x1)
461 r->x = x2;
462 if (g != NULL)
463 r->x += 2;
465 else
467 r->x = x;
468 x += r->cols + 1;
470 break;
472 case quick_input:
474 Widget *label = WIDGET (INPUT (item->widget)->label);
475 int width = column_width;
477 if (g != NULL)
478 width -= 4;
480 switch (item->quick_widget->u.input.label_location)
482 case input_label_left:
483 /* label was adjusted before; adjust input line */
484 r->x = label->rect.x + label->rect.cols + 1 - WIDGET (label->owner)->rect.x;
485 r->cols = width - label->rect.cols - 1;
486 break;
488 case input_label_right:
489 if (r->x != x1)
490 r->x = x2;
491 if (g != NULL)
492 r->x += 2;
493 r->cols = width - label->rect.cols - 1;
494 label->rect.x = r->x + r->cols + 1;
495 break;
497 default:
498 if (r->x != x1)
499 r->x = x2;
500 if (g != NULL)
501 r->x += 2;
502 r->cols = width;
503 break;
506 /* forced update internal variables of input line */
507 r->lines = 1;
508 widget_set_size_rect (item->widget, r);
510 break;
512 case quick_start_groupbox:
513 g = GROUPBOX (item->widget);
514 if (r->x != x1)
515 r->x = x2;
516 r->cols = column_width;
517 break;
519 case quick_stop_groupbox:
520 g = NULL;
521 break;
523 case quick_separator:
524 if (item->widget != NULL)
526 if (g != NULL)
528 Widget *wg = WIDGET (g);
530 HLINE (item->widget)->auto_adjust_cols = FALSE;
531 r->x = wg->rect.x + 1 - WIDGET (wg->owner)->rect.x;
532 r->cols = wg->rect.cols;
534 else if (two_columns)
536 HLINE (item->widget)->auto_adjust_cols = FALSE;
537 if (r->x != x1)
538 r->x = x2;
539 r->x--;
540 r->cols = column_width + 2;
542 else
543 HLINE (item->widget)->auto_adjust_cols = TRUE;
545 break;
547 case quick_start_columns:
548 two_columns = TRUE;
549 break;
551 case quick_stop_columns:
552 two_columns = FALSE;
553 break;
555 case quick_buttons:
556 /* several buttons in bottom line */
557 put_buttons = TRUE;
558 break;
560 default:
561 break;
564 if (item->widget != NULL)
566 unsigned long id;
568 /* add widget into dialog */
569 item->widget->options |= item->quick_widget->options; /* FIXME: cannot reset flags, setup only */
570 item->widget->state |= item->quick_widget->state; /* FIXME: cannot reset flags, setup only */
571 id = group_add_widget_autopos (GROUP (dd), item->widget, item->quick_widget->pos_flags,
572 NULL);
573 if (item->quick_widget->id != NULL)
574 *item->quick_widget->id = id;
578 /* skip frame widget */
579 if (dd->bg != NULL)
580 nskip++;
582 while (nskip-- != 0)
583 group_set_current_widget_next (GROUP (dd));
585 return_val = dlg_run (dd);
587 /* Get the data if we found something interesting */
588 if (return_val != B_CANCEL)
589 for (i = 0; i < widgets->len; i++)
591 quick_widget_item_t *item;
593 item = &g_array_index (widgets, quick_widget_item_t, i);
595 switch (item->quick_widget->widget_type)
597 case quick_checkbox:
598 *item->quick_widget->u.checkbox.state = CHECK (item->widget)->state;
599 break;
601 case quick_input:
602 if ((item->quick_widget->u.input.completion_flags & INPUT_COMPLETE_CD) != 0)
603 *item->quick_widget->u.input.result =
604 tilde_expand (input_get_ctext (INPUT (item->widget)));
605 else
606 *item->quick_widget->u.input.result = input_get_text (INPUT (item->widget));
607 break;
609 case quick_radio:
610 *item->quick_widget->u.radio.value = RADIO (item->widget)->sel;
611 break;
613 default:
614 break;
618 widget_destroy (WIDGET (dd));
620 g_list_free_full (input_labels, g_free); /* destroy input labels created before */
621 g_array_free (widgets, TRUE);
623 return return_val;
626 /* --------------------------------------------------------------------------------------------- */