root/lib/widget/wtools.c

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

DEFINITIONS

This source file includes following definitions.
  1. query_default_callback
  2. bg_message
  3. fg_input_dialog_help
  4. wtools_parent_call
  5. wtools_parent_call_string
  6. query_dialog
  7. query_set_sel
  8. create_message
  9. message
  10. mc_error_message
  11. input_dialog_help
  12. input_dialog
  13. input_expand_dialog
  14. status_msg_create
  15. status_msg_destroy
  16. status_msg_init
  17. status_msg_deinit
  18. status_msg_common_update
  19. simple_status_msg_init_cb

   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 <http://www.gnu.org/licenses/>.
  28  */
  29 
  30 /** \file wtools.c
  31  *  \brief Source: widget based utility functions
  32  */
  33 
  34 #include <config.h>
  35 
  36 #include <stdarg.h>
  37 #include <stdlib.h>
  38 
  39 #include "lib/global.h"
  40 #include "lib/tty/tty.h"
  41 #include "lib/tty/key.h"        /* tty_getch() */
  42 #include "lib/strutil.h"
  43 #include "lib/util.h"           /* tilde_expand() */
  44 #include "lib/widget.h"
  45 #include "lib/event.h"          /* mc_event_raise() */
  46 
  47 /*** global variables ****************************************************************************/
  48 
  49 /*** file scope macro definitions ****************************************************************/
  50 
  51 /*** file scope type declarations ****************************************************************/
  52 
  53 /*** forward declarations (file scope functions) *************************************************/
  54 
  55 /*** file scope variables ************************************************************************/
  56 
  57 static WDialog *last_query_dlg;
  58 
  59 static int sel_pos = 0;
  60 
  61 /* --------------------------------------------------------------------------------------------- */
  62 /*** file scope functions ************************************************************************/
  63 /* --------------------------------------------------------------------------------------------- */
  64 
  65 /** default query callback, used to reposition query */
  66 
  67 static cb_ret_t
  68 query_default_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
  69 {
  70     WDialog *h = DIALOG (w);
  71 
  72     switch (msg)
  73     {
  74     case MSG_RESIZE:
  75         if ((w->pos_flags & WPOS_CENTER) == 0)
  76         {
  77             WDialog *prev_dlg = NULL;
  78             int ypos, xpos;
  79             WRect r;
  80 
  81             /* get dialog under h */
  82             if (top_dlg != NULL)
  83             {
  84                 if (top_dlg->data != (void *) h)
  85                     prev_dlg = DIALOG (top_dlg->data);
  86                 else
  87                 {
  88                     GList *p;
  89 
  90                     /* Top dialog is current if it is visible.
  91                        Get previous dialog in stack */
  92                     p = g_list_next (top_dlg);
  93                     if (p != NULL)
  94                         prev_dlg = DIALOG (p->data);
  95                 }
  96             }
  97 
  98             /* if previous dialog is not fullscreen'd -- overlap it */
  99             if (prev_dlg == NULL || (WIDGET (prev_dlg)->pos_flags & WPOS_FULLSCREEN) != 0)
 100                 ypos = LINES / 3 - (w->rect.lines - 3) / 2;
 101             else
 102                 ypos = WIDGET (prev_dlg)->rect.y + 2;
 103 
 104             /* if dialog is too high, place it centered */
 105             if (ypos + w->rect.lines < LINES / 2)
 106                 w->pos_flags |= WPOS_CENTER;
 107 
 108             xpos = COLS / 2 - w->rect.cols / 2;
 109 
 110             /* set position */
 111             rect_init (&r, ypos, xpos, w->rect.lines, w->rect.cols);
 112 
 113             return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
 114         }
 115         MC_FALLTHROUGH;
 116 
 117     default:
 118         return dlg_default_callback (w, sender, msg, parm, data);
 119     }
 120 }
 121 
 122 /* --------------------------------------------------------------------------------------------- */
 123 /** Show message box from background */
 124 
 125 #ifdef ENABLE_BACKGROUND
 126 static void
 127 bg_message (int dummy, int *flags, char *title, const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 128 {
 129     (void) dummy;
 130     title = g_strconcat (_("Background process:"), " ", title, (char *) NULL);
 131     query_dialog (title, text, *flags, 1, _("&OK"));
 132     g_free (title);
 133 }
 134 #endif /* ENABLE_BACKGROUND */
 135 
 136 /* --------------------------------------------------------------------------------------------- */
 137 
 138 /**
 139  * Show dialog, not background safe.
 140  *
 141  * If the arguments "header" and "text" should be translated,
 142  * that MUST be done by the caller of fg_input_dialog_help().
 143  *
 144  * The argument "history_name" holds the name of a section
 145  * in the history file. Data entered in the input field of
 146  * the dialog box will be stored there.
 147  *
 148  */
 149 static char *
 150 fg_input_dialog_help (const char *header, const char *text, const char *help,
     /* [previous][next][first][last][top][bottom][index][help]  */
 151                       const char *history_name, const char *def_text, gboolean strip_password,
 152                       input_complete_t completion_flags)
 153 {
 154     char *p_text;
 155     char histname[64] = "inp|";
 156     gboolean is_passwd = FALSE;
 157     char *my_str = NULL;
 158     int ret;
 159 
 160     /* label text */
 161     p_text = g_strstrip (g_strdup (text));
 162 
 163     /* input history */
 164     if (history_name != NULL && *history_name != '\0')
 165         g_strlcpy (histname + 3, history_name, sizeof (histname) - 3);
 166 
 167     /* The special value of def_text is used to identify password boxes
 168        and hide characters with "*".  Don't save passwords in history! */
 169     if (def_text == INPUT_PASSWORD)
 170     {
 171         is_passwd = TRUE;
 172         histname[3] = '\0';
 173         def_text = "";
 174     }
 175 
 176     {
 177         quick_widget_t quick_widgets[] = {
 178             /* *INDENT-OFF* */
 179             QUICK_LABELED_INPUT (p_text, input_label_above, def_text, histname, &my_str,
 180                                  NULL, is_passwd, strip_password, completion_flags),
 181             QUICK_BUTTONS_OK_CANCEL,
 182             QUICK_END
 183             /* *INDENT-ON* */
 184         };
 185 
 186         WRect r = { -1, -1, 0, COLS / 2 };
 187 
 188         quick_dialog_t qdlg = {
 189             r, header, help,
 190             quick_widgets, NULL, NULL
 191         };
 192 
 193         ret = quick_dialog (&qdlg);
 194     }
 195 
 196     g_free (p_text);
 197 
 198     return (ret != B_CANCEL) ? my_str : NULL;
 199 }
 200 
 201 /* --------------------------------------------------------------------------------------------- */
 202 
 203 #ifdef ENABLE_BACKGROUND
 204 static int
 205 wtools_parent_call (void *routine, gpointer ctx, int argc, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 206 {
 207     ev_background_parent_call_t event_data;
 208 
 209     event_data.routine = routine;
 210     event_data.ctx = ctx;
 211     event_data.argc = argc;
 212     va_start (event_data.ap, argc);
 213     mc_event_raise (MCEVENT_GROUP_CORE, "background_parent_call", (gpointer) & event_data);
 214     va_end (event_data.ap);
 215     return event_data.ret.i;
 216 }
 217 
 218 /* --------------------------------------------------------------------------------------------- */
 219 
 220 static char *
 221 wtools_parent_call_string (void *routine, int argc, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 222 {
 223     ev_background_parent_call_t event_data;
 224 
 225     event_data.routine = routine;
 226     event_data.argc = argc;
 227     va_start (event_data.ap, argc);
 228     mc_event_raise (MCEVENT_GROUP_CORE, "background_parent_call_string", (gpointer) & event_data);
 229     va_end (event_data.ap);
 230     return event_data.ret.s;
 231 }
 232 #endif /* ENABLE_BACKGROUND */
 233 
 234 /* --------------------------------------------------------------------------------------------- */
 235 /*** public functions ****************************************************************************/
 236 /* --------------------------------------------------------------------------------------------- */
 237 
 238 /** Used to ask questions to the user */
 239 int
 240 query_dialog (const char *header, const char *text, int flags, int count, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 241 {
 242     va_list ap;
 243     WDialog *query_dlg;
 244     WGroup *g;
 245     WButton *button;
 246     int win_len = 0;
 247     int i;
 248     int result = -1;
 249     int cols, lines;
 250     const int *query_colors = (flags & D_ERROR) != 0 ? alarm_colors : dialog_colors;
 251     widget_pos_flags_t pos_flags =
 252         (flags & D_CENTER) != 0 ? (WPOS_CENTER | WPOS_TRYUP) : WPOS_KEEP_DEFAULT;
 253 
 254     if (header == MSG_ERROR)
 255         header = _("Error");
 256 
 257     if (count > 0)
 258     {
 259         va_start (ap, count);
 260         for (i = 0; i < count; i++)
 261         {
 262             char *cp = va_arg (ap, char *);
 263 
 264             win_len += str_term_width1 (cp) + 6;
 265             if (strchr (cp, '&') != NULL)
 266                 win_len--;
 267         }
 268         va_end (ap);
 269     }
 270 
 271     /* count coordinates */
 272     str_msg_term_size (text, &lines, &cols);
 273     cols = 6 + MAX (win_len, MAX (str_term_width1 (header), cols));
 274     lines += 4 + (count > 0 ? 2 : 0);
 275 
 276     /* prepare dialog */
 277     query_dlg =
 278         dlg_create (TRUE, 0, 0, lines, cols, pos_flags, FALSE, query_colors, query_default_callback,
 279                     NULL, "[QueryBox]", header);
 280     g = GROUP (query_dlg);
 281 
 282     if (count > 0)
 283     {
 284         WButton *defbutton = NULL;
 285 
 286         group_add_widget_autopos (g, label_new (2, 3, text), WPOS_KEEP_TOP | WPOS_CENTER_HORZ,
 287                                   NULL);
 288         group_add_widget (g, hline_new (lines - 4, -1, -1));
 289 
 290         cols = (cols - win_len - 2) / 2 + 2;
 291         va_start (ap, count);
 292         for (i = 0; i < count; i++)
 293         {
 294             int xpos;
 295             char *cur_name;
 296 
 297             cur_name = va_arg (ap, char *);
 298             xpos = str_term_width1 (cur_name) + 6;
 299             if (strchr (cur_name, '&') != NULL)
 300                 xpos--;
 301 
 302             button = button_new (lines - 3, cols, B_USER + i, NORMAL_BUTTON, cur_name, NULL);
 303             group_add_widget (g, button);
 304             cols += xpos;
 305             if (i == sel_pos)
 306                 defbutton = button;
 307         }
 308         va_end (ap);
 309 
 310         /* do resize before running and selecting any widget */
 311         send_message (query_dlg, NULL, MSG_RESIZE, 0, NULL);
 312 
 313         if (defbutton != NULL)
 314             widget_select (WIDGET (defbutton));
 315 
 316         /* run dialog and make result */
 317         switch (dlg_run (query_dlg))
 318         {
 319         case B_CANCEL:
 320             break;
 321         default:
 322             result = query_dlg->ret_value - B_USER;
 323         }
 324 
 325         /* free used memory */
 326         widget_destroy (WIDGET (query_dlg));
 327     }
 328     else
 329     {
 330         group_add_widget_autopos (g, label_new (2, 3, text), WPOS_KEEP_TOP | WPOS_CENTER_HORZ,
 331                                   NULL);
 332         group_add_widget (g, button_new (0, 0, 0, HIDDEN_BUTTON, "-", NULL));
 333         last_query_dlg = query_dlg;
 334     }
 335     sel_pos = 0;
 336     return result;
 337 }
 338 
 339 /* --------------------------------------------------------------------------------------------- */
 340 
 341 void
 342 query_set_sel (int new_sel)
     /* [previous][next][first][last][top][bottom][index][help]  */
 343 {
 344     sel_pos = new_sel;
 345 }
 346 
 347 /* --------------------------------------------------------------------------------------------- */
 348 /**
 349  * Create message dialog.  The caller must call dlg_run_done() and
 350  * widget_destroy() to dismiss it.  Not safe to call from background.
 351  */
 352 
 353 WDialog *
 354 create_message (int flags, const char *title, const char *text, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 355 {
 356     va_list args;
 357     WDialog *d;
 358     char *p;
 359 
 360     va_start (args, text);
 361     p = g_strdup_vprintf (text, args);
 362     va_end (args);
 363 
 364     query_dialog (title, p, flags, 0);
 365     d = last_query_dlg;
 366 
 367     /* do resize before initing and running */
 368     send_message (d, NULL, MSG_RESIZE, 0, NULL);
 369 
 370     dlg_init (d);
 371     g_free (p);
 372 
 373     return d;
 374 }
 375 
 376 /* --------------------------------------------------------------------------------------------- */
 377 /** Show message box, background safe */
 378 
 379 void
 380 message (int flags, const char *title, const char *text, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 381 {
 382     char *p;
 383     va_list ap;
 384 
 385     va_start (ap, text);
 386     p = g_strdup_vprintf (text, ap);
 387     va_end (ap);
 388 
 389     if (title == MSG_ERROR)
 390         title = _("Error");
 391 
 392 #ifdef ENABLE_BACKGROUND
 393     if (mc_global.we_are_background)
 394     {
 395         union
 396         {
 397             void *p;
 398             void (*f) (int, int *, char *, const char *);
 399         } func;
 400 
 401         func.f = bg_message;
 402 
 403         wtools_parent_call (func.p, NULL, 3, sizeof (flags), &flags, strlen (title), title,
 404                             strlen (p), p);
 405     }
 406     else
 407 #endif /* ENABLE_BACKGROUND */
 408         query_dialog (title, p, flags, 1, _("&OK"));
 409 
 410     g_free (p);
 411 }
 412 
 413 /* --------------------------------------------------------------------------------------------- */
 414 /** Show error message box */
 415 
 416 gboolean
 417 mc_error_message (GError **mcerror, int *code)
     /* [previous][next][first][last][top][bottom][index][help]  */
 418 {
 419     if (mcerror == NULL || *mcerror == NULL)
 420         return FALSE;
 421 
 422     if ((*mcerror)->code == 0)
 423         message (D_ERROR, MSG_ERROR, "%s", (*mcerror)->message);
 424     else
 425         message (D_ERROR, MSG_ERROR, _("%s (%d)"), (*mcerror)->message, (*mcerror)->code);
 426 
 427     if (code != NULL)
 428         *code = (*mcerror)->code;
 429 
 430     g_error_free (*mcerror);
 431     *mcerror = NULL;
 432 
 433     return TRUE;
 434 }
 435 
 436 /* --------------------------------------------------------------------------------------------- */
 437 /**
 438  * Show input dialog, background safe.
 439  *
 440  * If the arguments "header" and "text" should be translated,
 441  * that MUST be done by the caller of these wrappers.
 442  */
 443 
 444 char *
 445 input_dialog_help (const char *header, const char *text, const char *help,
     /* [previous][next][first][last][top][bottom][index][help]  */
 446                    const char *history_name, const char *def_text, gboolean strip_password,
 447                    input_complete_t completion_flags)
 448 {
 449 #ifdef ENABLE_BACKGROUND
 450     if (mc_global.we_are_background)
 451     {
 452         union
 453         {
 454             void *p;
 455             char *(*f) (const char *, const char *, const char *, const char *, const char *,
 456                         gboolean, input_complete_t);
 457         } func;
 458         func.f = fg_input_dialog_help;
 459         return wtools_parent_call_string (func.p, 7,
 460                                           strlen (header), header, strlen (text),
 461                                           text, strlen (help), help,
 462                                           strlen (history_name), history_name,
 463                                           strlen (def_text), def_text,
 464                                           sizeof (gboolean), strip_password,
 465                                           sizeof (input_complete_t), completion_flags);
 466     }
 467     else
 468 #endif /* ENABLE_BACKGROUND */
 469         return fg_input_dialog_help (header, text, help, history_name, def_text, strip_password,
 470                                      completion_flags);
 471 }
 472 
 473 /* --------------------------------------------------------------------------------------------- */
 474 /** Show input dialog with default help, background safe */
 475 
 476 char *
 477 input_dialog (const char *header, const char *text, const char *history_name, const char *def_text,
     /* [previous][next][first][last][top][bottom][index][help]  */
 478               input_complete_t completion_flags)
 479 {
 480     return input_dialog_help (header, text, "[Input Line Keys]", history_name, def_text, FALSE,
 481                               completion_flags);
 482 }
 483 
 484 /* --------------------------------------------------------------------------------------------- */
 485 
 486 char *
 487 input_expand_dialog (const char *header, const char *text,
     /* [previous][next][first][last][top][bottom][index][help]  */
 488                      const char *history_name, const char *def_text,
 489                      input_complete_t completion_flags)
 490 {
 491     char *result;
 492 
 493     result = input_dialog (header, text, history_name, def_text, completion_flags);
 494     if (result)
 495     {
 496         char *expanded;
 497 
 498         expanded = tilde_expand (result);
 499         g_free (result);
 500         return expanded;
 501     }
 502     return result;
 503 }
 504 
 505 /* --------------------------------------------------------------------------------------------- */
 506 /**
 507  * Create status message window object and initialize it
 508  *
 509  * @param title window title
 510  * @param delay initial delay to raise window in seconds
 511  * @param init_cb callback to initialize user-defined part of status message
 512  * @param update_cb callback to update of status message
 513  * @param deinit_cb callback to deinitialize user-defined part of status message
 514  *
 515  * @return newly allocate status message window
 516  */
 517 
 518 status_msg_t *
 519 status_msg_create (const char *title, double delay, status_msg_cb init_cb,
     /* [previous][next][first][last][top][bottom][index][help]  */
 520                    status_msg_update_cb update_cb, status_msg_cb deinit_cb)
 521 {
 522     status_msg_t *sm;
 523 
 524     sm = g_try_new (status_msg_t, 1);
 525     status_msg_init (sm, title, delay, init_cb, update_cb, deinit_cb);
 526 
 527     return sm;
 528 }
 529 
 530 /* --------------------------------------------------------------------------------------------- */
 531 /**
 532  * Destroy status message window object
 533  *
 534  * @param sm status message window object
 535  */
 536 
 537 void
 538 status_msg_destroy (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 539 {
 540     status_msg_deinit (sm);
 541     g_free (sm);
 542 }
 543 
 544 /* --------------------------------------------------------------------------------------------- */
 545 /**
 546  * Initialize already created status message window object
 547  *
 548  * @param sm status message window object
 549  * @param title window title
 550  * @param delay initial delay to raise window in seconds
 551  * @param init_cb callback to initialize user-defined part of status message
 552  * @param update_cb callback to update of status message
 553  * @param deinit_cb callback to deinitialize user-defined part of status message
 554  */
 555 
 556 void
 557 status_msg_init (status_msg_t *sm, const char *title, double delay, status_msg_cb init_cb,
     /* [previous][next][first][last][top][bottom][index][help]  */
 558                  status_msg_update_cb update_cb, status_msg_cb deinit_cb)
 559 {
 560     gint64 start;
 561 
 562     /* repaint screen to remove previous finished dialog */
 563     mc_refresh ();
 564 
 565     start = g_get_monotonic_time ();
 566 
 567     sm->dlg = dlg_create (TRUE, 0, 0, 7, MIN (MAX (40, COLS / 2), COLS), WPOS_CENTER, FALSE,
 568                           dialog_colors, NULL, NULL, NULL, title);
 569     sm->start = start;
 570     sm->delay = (gint64) (delay * G_USEC_PER_SEC);
 571     sm->block = FALSE;
 572 
 573     sm->init = init_cb;
 574     sm->update = update_cb;
 575     sm->deinit = deinit_cb;
 576 
 577     if (sm->init != NULL)
 578         sm->init (sm);
 579 
 580     if (mc_time_elapsed (&start, sm->delay))
 581     {
 582         /* We will manage the dialog without any help, that's why we have to call dlg_init */
 583         dlg_init (sm->dlg);
 584     }
 585 }
 586 
 587 /* --------------------------------------------------------------------------------------------- */
 588 /**
 589  * Deinitialize status message window object
 590  *
 591  * @param sm status message window object
 592  */
 593 
 594 void
 595 status_msg_deinit (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 596 {
 597     if (sm == NULL)
 598         return;
 599 
 600     if (sm->deinit != NULL)
 601         sm->deinit (sm);
 602 
 603     /* close and destroy dialog */
 604     dlg_run_done (sm->dlg);
 605     widget_destroy (WIDGET (sm->dlg));
 606 }
 607 
 608 /* --------------------------------------------------------------------------------------------- */
 609 /**
 610  * Update status message window
 611  *
 612  * @param sm status message window object
 613  *
 614  * @return value of pressed key
 615  */
 616 
 617 int
 618 status_msg_common_update (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 619 {
 620     int c;
 621     Gpm_Event event;
 622 
 623     if (sm == NULL)
 624         return B_ENTER;
 625 
 626     /* This should not happen, but... */
 627     if (sm->dlg == NULL)
 628         return B_ENTER;
 629 
 630     if (widget_get_state (WIDGET (sm->dlg), WST_CONSTRUCT))
 631     {
 632         /* dialog is not shown yet */
 633 
 634         /* do not change sm->start */
 635         gint64 start = sm->start;
 636 
 637         if (mc_time_elapsed (&start, sm->delay))
 638             dlg_init (sm->dlg);
 639 
 640         return B_ENTER;
 641     }
 642 
 643     event.x = -1;               /* Don't show the GPM cursor */
 644     c = tty_get_event (&event, FALSE, sm->block);
 645     if (c == EV_NONE)
 646         return B_ENTER;
 647 
 648     /* Reinitialize by non-B_CANCEL value to avoid old values
 649        after events other than selecting a button */
 650     sm->dlg->ret_value = B_ENTER;
 651     dlg_process_event (sm->dlg, c, &event);
 652 
 653     return sm->dlg->ret_value;
 654 }
 655 
 656 /* --------------------------------------------------------------------------------------------- */
 657 /**
 658  * Callback to initialize already created simple status message window object
 659  *
 660  * @param sm status message window object
 661  */
 662 
 663 void
 664 simple_status_msg_init_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 665 {
 666     simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
 667     Widget *wd = WIDGET (sm->dlg);
 668     WGroup *wg = GROUP (sm->dlg);
 669     WRect r;
 670 
 671     const char *b_name = N_("&Abort");
 672     int b_width;
 673     int wd_width, y;
 674     Widget *b;
 675 
 676 #ifdef ENABLE_NLS
 677     b_name = _(b_name);
 678 #endif
 679 
 680     b_width = str_term_width1 (b_name) + 4;
 681     wd_width = MAX (wd->rect.cols, b_width + 6);
 682 
 683     y = 2;
 684     ssm->label = label_new (y++, 3, NULL);
 685     group_add_widget_autopos (wg, ssm->label, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
 686     group_add_widget (wg, hline_new (y++, -1, -1));
 687     b = WIDGET (button_new (y++, 3, B_CANCEL, NORMAL_BUTTON, b_name, NULL));
 688     group_add_widget_autopos (wg, b, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
 689 
 690     r = wd->rect;
 691     r.lines = y + 2;
 692     r.cols = wd_width;
 693     widget_set_size_rect (wd, &r);
 694 }
 695 
 696 /* --------------------------------------------------------------------------------------------- */

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