root/lib/widget/quick.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. quick_create_input
  2. quick_create_labeled_input
  3. quick_dialog_skip

   1 /*
   2    Widget based utility functions.
   3 
   4    Copyright (C) 1994-2025
   5    Free Software Foundation, Inc.
   6 
   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
  13 
  14    This file is part of the Midnight Commander.
  15 
  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.
  20 
  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.
  25 
  26    You should have received a copy of the GNU General Public License
  27    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  28  */
  29 
  30 /** \file quick.c
  31  *  \brief Source: quick dialog engine
  32  */
  33 
  34 #include <config.h>
  35 
  36 #include <stdlib.h>
  37 #include <stdio.h>  // fprintf()
  38 
  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"
  43 
  44 /*** global variables ****************************************************************************/
  45 
  46 /*** file scope macro definitions ****************************************************************/
  47 
  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
  53 
  54 /*** file scope type declarations ****************************************************************/
  55 
  56 typedef struct
  57 {
  58     Widget *widget;
  59     quick_widget_t *quick_widget;
  60 } quick_widget_item_t;
  61 
  62 /*** forward declarations (file scope functions) *************************************************/
  63 
  64 /*** file scope variables ************************************************************************/
  65 
  66 /* --------------------------------------------------------------------------------------------- */
  67 /*** file scope functions ************************************************************************/
  68 /* --------------------------------------------------------------------------------------------- */
  69 
  70 static WInput *
  71 quick_create_input (int y, int x, const quick_widget_t *qw)
     /* [previous][next][first][last][top][bottom][index][help]  */
  72 {
  73     WInput *in;
  74 
  75     in = input_new (y, x, input_colors, 8, qw->u.input.text, qw->u.input.histname,
  76                     qw->u.input.completion_flags);
  77 
  78     in->is_password = qw->u.input.is_passwd;
  79     in->strip_password = qw->u.input.strip_passwd;
  80 
  81     return in;
  82 }
  83 
  84 /* --------------------------------------------------------------------------------------------- */
  85 
  86 static void
  87 quick_create_labeled_input (GArray *widgets, int *y, int x, quick_widget_t *quick_widget,
     /* [previous][next][first][last][top][bottom][index][help]  */
  88                             int *width)
  89 {
  90     quick_widget_item_t in, label;
  91 
  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;
  98 
  99     switch (quick_widget->u.input.label_location)
 100     {
 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);
 105 
 106         in.widget = WIDGET (quick_create_input (++(*y), x, quick_widget));
 107         in.quick_widget = quick_widget;
 108         g_array_append_val (widgets, in);
 109 
 110         *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
 111         break;
 112 
 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);
 116 
 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);
 120 
 121         *width = label.widget->rect.cols + in.widget->rect.cols + 1;
 122         break;
 123 
 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);
 128 
 129         label.widget = WIDGET (
 130             label_new (*y, x + in.widget->rect.cols + 1, I18N (quick_widget->u.input.label_text)));
 131         g_array_append_val (widgets, label);
 132 
 133         *width = label.widget->rect.cols + in.widget->rect.cols + 1;
 134         break;
 135 
 136     case input_label_below:
 137         in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
 138         in.quick_widget = quick_widget;
 139         g_array_append_val (widgets, in);
 140 
 141         label.widget = WIDGET (label_new (++(*y), x, I18N (quick_widget->u.input.label_text)));
 142         *y += label.widget->rect.lines - 1;
 143         g_array_append_val (widgets, label);
 144 
 145         *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
 146         break;
 147 
 148     default:
 149         g_free (label.quick_widget);
 150         return;
 151     }
 152 
 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;
 157 }
 158 
 159 /* --------------------------------------------------------------------------------------------- */
 160 /*** public functions ****************************************************************************/
 161 /* --------------------------------------------------------------------------------------------- */
 162 
 163 int
 164 quick_dialog_skip (quick_dialog_t *quick_dlg, int nskip)
     /* [previous][next][first][last][top][bottom][index][help]  */
 165 {
 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;
 176 
 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;
 181 
 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;
 189 
 190     len = str_term_width1 (I18N (quick_dlg->title)) + 6;
 191     quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
 192 
 193     y = 1;
 194     x = x1;
 195 
 196     // create widgets
 197     widgets = g_array_sized_new (FALSE, FALSE, sizeof (quick_widget_item_t), 8);
 198 
 199     for (quick_widget = quick_dlg->widgets; quick_widget->widget_type != quick_end; quick_widget++)
 200     {
 201         quick_widget_item_t item = { NULL, quick_widget };
 202         int width = 0;
 203 
 204         switch (quick_widget->widget_type)
 205         {
 206         case quick_checkbox:
 207             item.widget = WIDGET (check_new (++y, x, *quick_widget->u.checkbox.state,
 208                                              I18N (quick_widget->u.checkbox.text)));
 209             g_array_append_val (widgets, item);
 210             width = item.widget->rect.cols;
 211             if (g != NULL)
 212                 width += 2;
 213             if (two_columns)
 214                 width2 = MAX (width2, width);
 215             else
 216                 width1 = MAX (width1, width);
 217             break;
 218 
 219         case quick_button:
 220             // single button
 221             item.widget = WIDGET (button_new (
 222                 ++y, x, quick_widget->u.button.action,
 223                 quick_widget->u.button.action == B_ENTER ? DEFPUSH_BUTTON : NORMAL_BUTTON,
 224                 I18N (quick_widget->u.button.text), quick_widget->u.button.callback));
 225             g_array_append_val (widgets, item);
 226             width = item.widget->rect.cols;
 227             if (g != NULL)
 228                 width += 2;
 229             if (two_columns)
 230                 width2 = MAX (width2, width);
 231             else
 232                 width1 = MAX (width1, width);
 233             break;
 234 
 235         case quick_input:
 236             *quick_widget->u.input.result = NULL;
 237             y++;
 238             if (quick_widget->u.input.label_location != input_label_none)
 239             {
 240                 quick_create_labeled_input (widgets, &y, x, quick_widget, &width);
 241                 input_labels = g_list_prepend (input_labels, quick_widget->u.input.label);
 242             }
 243             else
 244             {
 245                 item.widget = WIDGET (quick_create_input (y, x, quick_widget));
 246                 g_array_append_val (widgets, item);
 247                 width = item.widget->rect.cols;
 248             }
 249             if (g != NULL)
 250                 width += 2;
 251             if (two_columns)
 252                 width2 = MAX (width2, width);
 253             else
 254                 width1 = MAX (width1, width);
 255             break;
 256 
 257         case quick_label:
 258             item.widget = WIDGET (label_new (++y, x, I18N (quick_widget->u.label.text)));
 259             g_array_append_val (widgets, item);
 260             y += item.widget->rect.lines - 1;
 261             width = item.widget->rect.cols;
 262             if (g != NULL)
 263                 width += 2;
 264             if (two_columns)
 265                 width2 = MAX (width2, width);
 266             else
 267                 width1 = MAX (width1, width);
 268             break;
 269 
 270         case quick_radio:
 271         {
 272             WRadio *r;
 273             char **items = NULL;
 274 
 275             // create the copy of radio_items to avoid mwmory leak
 276             items = g_new (char *, quick_widget->u.radio.count + 1);
 277             for (i = 0; i < (size_t) quick_widget->u.radio.count; i++)
 278                 items[i] = g_strdup (_ (quick_widget->u.radio.items[i]));
 279             items[i] = NULL;
 280 
 281             r = radio_new (++y, x, quick_widget->u.radio.count, (const char **) items);
 282             r->pos = r->sel = *quick_widget->u.radio.value;
 283             g_strfreev (items);
 284             item.widget = WIDGET (r);
 285             g_array_append_val (widgets, item);
 286             y += item.widget->rect.lines - 1;
 287             width = item.widget->rect.cols;
 288             if (g != NULL)
 289                 width += 2;
 290             if (two_columns)
 291                 width2 = MAX (width2, width);
 292             else
 293                 width1 = MAX (width1, width);
 294         }
 295         break;
 296 
 297         case quick_start_groupbox:
 298             I18N (quick_widget->u.groupbox.title);
 299             len = str_term_width1 (quick_widget->u.groupbox.title);
 300             g = groupbox_new (++y, x, 1, len + 4, quick_widget->u.groupbox.title);
 301             item.widget = WIDGET (g);
 302             g_array_append_val (widgets, item);
 303             have_groupbox = TRUE;
 304             break;
 305 
 306         case quick_stop_groupbox:
 307             if (g != NULL)
 308             {
 309                 Widget *w = WIDGET (g);
 310 
 311                 y++;
 312                 w->rect.lines = y + 1 - w->rect.y;
 313                 g = NULL;
 314 
 315                 g_array_append_val (widgets, item);
 316             }
 317             break;
 318 
 319         case quick_separator:
 320             y++;
 321             if (quick_widget->u.separator.line)
 322             {
 323                 item.widget = WIDGET (hline_new (y, x, 1));
 324                 g_array_append_val (widgets, item);
 325             }
 326             break;
 327 
 328         case quick_start_columns:
 329             y2 = y;
 330             g_array_append_val (widgets, item);
 331             two_columns = TRUE;
 332             break;
 333 
 334         case quick_next_column:
 335             x = x2;
 336             y1 = y;
 337             y = y2;
 338             break;
 339 
 340         case quick_stop_columns:
 341             x = x1;
 342             y = MAX (y1, y);
 343             g_array_append_val (widgets, item);
 344             two_columns = FALSE;
 345             break;
 346 
 347         case quick_buttons:
 348             // start put several buttons in bottom line
 349             if (quick_widget->u.separator.space)
 350             {
 351                 y++;
 352 
 353                 if (quick_widget->u.separator.line)
 354                     item.widget = WIDGET (hline_new (y, 1, -1));
 355             }
 356 
 357             g_array_append_val (widgets, item);
 358 
 359             // several buttons in bottom line
 360             y++;
 361             quick_widget++;
 362             for (; quick_widget->widget_type == quick_button; quick_widget++)
 363             {
 364                 item.widget = WIDGET (button_new (
 365                     y, x++, quick_widget->u.button.action,
 366                     quick_widget->u.button.action == B_ENTER ? DEFPUSH_BUTTON : NORMAL_BUTTON,
 367                     I18N (quick_widget->u.button.text), quick_widget->u.button.callback));
 368                 item.quick_widget = quick_widget;
 369                 g_array_append_val (widgets, item);
 370                 blen += item.widget->rect.cols + 1;
 371             }
 372 
 373             // stop dialog build here
 374             blen--;
 375             quick_widget->widget_type = quick_end;
 376             quick_widget--;
 377             break;
 378 
 379         default:
 380             break;
 381         }
 382     }
 383 
 384     // adjust dialog width
 385     quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, blen + 6);
 386     if (have_groupbox)
 387     {
 388         if (width1 != 0)
 389             width1 += 2;
 390         if (width2 != 0)
 391             width2 += 2;
 392     }
 393     if (width2 == 0)
 394         len = width1 + 6;
 395     else
 396     {
 397         len = width2 * 2 + 7;
 398         if (width1 != 0)
 399             len = MAX (len, width1 + 6);
 400     }
 401 
 402     quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
 403     width1 = quick_dlg->rect.cols - 6;
 404     width2 = (quick_dlg->rect.cols - 7) / 2;
 405 
 406     if (quick_dlg->rect.x == -1 || quick_dlg->rect.y == -1)
 407         dd = dlg_create (TRUE, 0, 0, y + 3, quick_dlg->rect.cols, WPOS_CENTER | WPOS_TRYUP, FALSE,
 408                          dialog_colors, quick_dlg->callback, quick_dlg->mouse_callback,
 409                          quick_dlg->help, quick_dlg->title);
 410     else
 411         dd = dlg_create (TRUE, quick_dlg->rect.y, quick_dlg->rect.x, y + 3, quick_dlg->rect.cols,
 412                          WPOS_KEEP_DEFAULT, FALSE, dialog_colors, quick_dlg->callback,
 413                          quick_dlg->mouse_callback, quick_dlg->help, quick_dlg->title);
 414 
 415     // add widgets into the dialog
 416     x2 = x1 + width2 + 1;
 417     g = NULL;
 418     two_columns = FALSE;
 419     x = (WIDGET (dd)->rect.cols - blen) / 2;
 420 
 421     for (i = 0; i < widgets->len; i++)
 422     {
 423         quick_widget_item_t *item;
 424         int column_width;
 425         WRect *r;
 426 
 427         item = &g_array_index (widgets, quick_widget_item_t, i);
 428         column_width = two_columns ? width2 : width1;
 429 
 430         // adjust widget width and x position
 431         switch (item->quick_widget->widget_type)
 432         {
 433         case quick_label:
 434         {
 435             quick_widget_t *input = item->quick_widget->u.label.input;
 436 
 437             if (input != NULL && input->u.input.label_location == input_label_right)
 438             {
 439                 // location of this label will be adjusted later
 440                 break;
 441             }
 442         }
 443             MC_FALLTHROUGH;
 444         case quick_checkbox:
 445         case quick_radio:
 446             r = &item->widget->rect;
 447             if (r->x != x1)
 448                 r->x = x2;
 449             if (g != NULL)
 450                 r->x += 2;
 451             break;
 452 
 453         case quick_button:
 454             r = &item->widget->rect;
 455             if (!put_buttons)
 456             {
 457                 if (r->x != x1)
 458                     r->x = x2;
 459                 if (g != NULL)
 460                     r->x += 2;
 461             }
 462             else
 463             {
 464                 r->x = x;
 465                 x += r->cols + 1;
 466             }
 467             break;
 468 
 469         case quick_input:
 470         {
 471             Widget *label = WIDGET (INPUT (item->widget)->label);
 472             int width = column_width;
 473 
 474             if (g != NULL)
 475                 width -= 4;
 476 
 477             r = &item->widget->rect;
 478 
 479             switch (item->quick_widget->u.input.label_location)
 480             {
 481             case input_label_left:
 482                 // label was adjusted before; adjust input line
 483                 r->x = label->rect.x + label->rect.cols + 1 - WIDGET (label->owner)->rect.x;
 484                 r->cols = width - label->rect.cols - 1;
 485                 break;
 486 
 487             case input_label_right:
 488                 if (r->x != x1)
 489                     r->x = x2;
 490                 if (g != NULL)
 491                     r->x += 2;
 492                 r->cols = width - label->rect.cols - 1;
 493                 label->rect.x = r->x + r->cols + 1;
 494                 break;
 495 
 496             default:
 497                 if (r->x != x1)
 498                     r->x = x2;
 499                 if (g != NULL)
 500                     r->x += 2;
 501                 r->cols = width;
 502                 break;
 503             }
 504 
 505             // forced update internal variables of input line
 506             r->lines = 1;
 507             widget_set_size_rect (item->widget, r);
 508         }
 509         break;
 510 
 511         case quick_start_groupbox:
 512             g = GROUPBOX (item->widget);
 513             r = &item->widget->rect;
 514             if (r->x != x1)
 515                 r->x = x2;
 516             r->cols = column_width;
 517             break;
 518 
 519         case quick_stop_groupbox:
 520             g = NULL;
 521             break;
 522 
 523         case quick_separator:
 524             if (item->widget != NULL)
 525             {
 526                 r = &item->widget->rect;
 527 
 528                 if (g != NULL)
 529                 {
 530                     Widget *wg = WIDGET (g);
 531 
 532                     HLINE (item->widget)->auto_adjust_cols = FALSE;
 533                     r->x = wg->rect.x + 1 - WIDGET (wg->owner)->rect.x;
 534                     r->cols = wg->rect.cols;
 535                 }
 536                 else if (two_columns)
 537                 {
 538                     HLINE (item->widget)->auto_adjust_cols = FALSE;
 539                     if (r->x != x1)
 540                         r->x = x2;
 541                     r->x--;
 542                     r->cols = column_width + 2;
 543                 }
 544                 else
 545                     HLINE (item->widget)->auto_adjust_cols = TRUE;
 546             }
 547             break;
 548 
 549         case quick_start_columns:
 550             two_columns = TRUE;
 551             break;
 552 
 553         case quick_stop_columns:
 554             two_columns = FALSE;
 555             break;
 556 
 557         case quick_buttons:
 558             // several buttons in bottom line
 559             put_buttons = TRUE;
 560             break;
 561 
 562         default:
 563             break;
 564         }
 565 
 566         if (item->widget != NULL)
 567         {
 568             unsigned long id;
 569 
 570             // add widget into dialog
 571             item->widget->options |=
 572                 item->quick_widget->options;  // FIXME: cannot reset flags, setup only
 573             item->widget->state |=
 574                 item->quick_widget->state;  // FIXME: cannot reset flags, setup only
 575             id = group_add_widget_autopos (GROUP (dd), item->widget, item->quick_widget->pos_flags,
 576                                            NULL);
 577             if (item->quick_widget->id != NULL)
 578                 *item->quick_widget->id = id;
 579         }
 580     }
 581 
 582     // skip frame widget
 583     if (dd->bg != NULL)
 584         nskip++;
 585 
 586     while (nskip-- != 0)
 587         group_set_current_widget_next (GROUP (dd));
 588 
 589     return_val = dlg_run (dd);
 590 
 591     // Get the data if we found something interesting
 592     if (return_val != B_CANCEL)
 593         for (i = 0; i < widgets->len; i++)
 594         {
 595             quick_widget_item_t *item;
 596 
 597             item = &g_array_index (widgets, quick_widget_item_t, i);
 598 
 599             switch (item->quick_widget->widget_type)
 600             {
 601             case quick_checkbox:
 602                 *item->quick_widget->u.checkbox.state = CHECK (item->widget)->state;
 603                 break;
 604 
 605             case quick_input:
 606                 if ((item->quick_widget->u.input.completion_flags & INPUT_COMPLETE_CD) != 0)
 607                     *item->quick_widget->u.input.result =
 608                         tilde_expand (input_get_ctext (INPUT (item->widget)));
 609                 else
 610                     *item->quick_widget->u.input.result = input_get_text (INPUT (item->widget));
 611                 break;
 612 
 613             case quick_radio:
 614                 *item->quick_widget->u.radio.value = RADIO (item->widget)->sel;
 615                 break;
 616 
 617             default:
 618                 break;
 619             }
 620         }
 621 
 622     widget_destroy (WIDGET (dd));
 623 
 624     g_list_free_full (input_labels, g_free);  // destroy input labels created before
 625     g_array_free (widgets, TRUE);
 626 
 627     return return_val;
 628 }
 629 
 630 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */