Manual pages: mcmcdiffmceditmcview

root/lib/widget/dialog.c

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

DEFINITIONS

This source file includes following definitions.
  1. dlg_default_get_colors
  2. refresh_cmd
  3. dlg_help
  4. dlg_execute_cmd
  5. dlg_handle_key
  6. dlg_key_event
  7. dlg_handle_mouse_event
  8. frontend_dlg_run
  9. dlg_default_destroy
  10. dlg_default_callback
  11. dlg_default_mouse_callback
  12. dlg_create
  13. dlg_set_default_colors
  14. dlg_close
  15. dlg_init
  16. dlg_process_event
  17. dlg_run_done
  18. dlg_run
  19. dlg_get_title

   1 /*
   2    Dialog box features module for the Midnight Commander
   3 
   4    Copyright (C) 1994-2025
   5    Free Software Foundation, Inc.
   6 
   7    This file is part of the Midnight Commander.
   8 
   9    The Midnight Commander is free software: you can redistribute it
  10    and/or modify it under the terms of the GNU General Public License as
  11    published by the Free Software Foundation, either version 3 of the License,
  12    or (at your option) any later version.
  13 
  14    The Midnight Commander is distributed in the hope that it will be useful,
  15    but WITHOUT ANY WARRANTY; without even the implied warranty of
  16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17    GNU General Public License for more details.
  18 
  19    You should have received a copy of the GNU General Public License
  20    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  21  */
  22 
  23 /** \file dialog.c
  24  *  \brief Source: dialog box features module
  25  */
  26 
  27 #include <config.h>
  28 
  29 #include <stdlib.h>
  30 
  31 #include "lib/global.h"
  32 
  33 #include "lib/tty/tty.h"
  34 #include "lib/skin.h"
  35 #include "lib/tty/key.h"
  36 #include "lib/strutil.h"
  37 #include "lib/event.h"  // mc_event_raise()
  38 #include "lib/util.h"   // MC_PTR_FREE
  39 
  40 #include "lib/widget.h"
  41 #include "lib/widget/mouse.h"
  42 
  43 /*** global variables ****************************************************************************/
  44 
  45 /* Color styles for normal and error dialogs */
  46 dlg_colors_t dialog_colors;
  47 dlg_colors_t alarm_colors;
  48 dlg_colors_t listbox_colors;
  49 dlg_colors_t help_colors;
  50 
  51 /* A hook list for idle events */
  52 hook_t *idle_hook = NULL;
  53 
  54 /* left click outside of dialog closes it */
  55 gboolean mouse_close_dialog = FALSE;
  56 
  57 const global_keymap_t *dialog_map = NULL;
  58 
  59 /*** file scope macro definitions ****************************************************************/
  60 
  61 /*** file scope type declarations ****************************************************************/
  62 
  63 /*** forward declarations (file scope functions) *************************************************/
  64 
  65 /*** file scope variables ************************************************************************/
  66 
  67 /* --------------------------------------------------------------------------------------------- */
  68 /*** file scope functions ************************************************************************/
  69 /* --------------------------------------------------------------------------------------------- */
  70 
  71 static const int *
  72 dlg_default_get_colors (const Widget *w)
     /* [previous][next][first][last][top][bottom][index][help]  */
  73 {
  74     return CONST_DIALOG (w)->colors;
  75 }
  76 
  77 /* --------------------------------------------------------------------------------------------- */
  78 
  79 static void
  80 refresh_cmd (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
  81 {
  82 #ifdef HAVE_SLANG
  83     tty_touch_screen ();
  84     mc_refresh ();
  85 #else
  86     // Use this if the refreshes fail
  87     tty_clear_screen ();
  88     repaint_screen ();
  89 #endif
  90 }
  91 
  92 /* --------------------------------------------------------------------------------------------- */
  93 
  94 static void
  95 dlg_help (const WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
  96 {
  97     ev_help_t event_data = { NULL, h->help_ctx };
  98 
  99     mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
 100 }
 101 
 102 /* --------------------------------------------------------------------------------------------- */
 103 
 104 static cb_ret_t
 105 dlg_execute_cmd (WDialog *h, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 106 {
 107     WGroup *g = GROUP (h);
 108     cb_ret_t ret = MSG_HANDLED;
 109 
 110     if (send_message (h, NULL, MSG_ACTION, command, NULL) == MSG_HANDLED)
 111         return MSG_HANDLED;
 112 
 113     switch (command)
 114     {
 115     case CK_Ok:
 116         h->ret_value = B_ENTER;
 117         dlg_close (h);
 118         break;
 119     case CK_Cancel:
 120         h->ret_value = B_CANCEL;
 121         dlg_close (h);
 122         break;
 123 
 124     case CK_Up:
 125     case CK_Left:
 126         group_select_prev_widget (g);
 127         break;
 128     case CK_Down:
 129     case CK_Right:
 130         group_select_next_widget (g);
 131         break;
 132 
 133     case CK_Help:
 134         dlg_help (h);
 135         break;
 136 
 137     case CK_Suspend:
 138         mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
 139         refresh_cmd ();
 140         break;
 141     case CK_Refresh:
 142         refresh_cmd ();
 143         break;
 144 
 145     case CK_ScreenList:
 146         if (!widget_get_state (WIDGET (h), WST_MODAL))
 147             dialog_switch_list ();
 148         else
 149             ret = MSG_NOT_HANDLED;
 150         break;
 151     case CK_ScreenNext:
 152         if (!widget_get_state (WIDGET (h), WST_MODAL))
 153             dialog_switch_next ();
 154         else
 155             ret = MSG_NOT_HANDLED;
 156         break;
 157     case CK_ScreenPrev:
 158         if (!widget_get_state (WIDGET (h), WST_MODAL))
 159             dialog_switch_prev ();
 160         else
 161             ret = MSG_NOT_HANDLED;
 162         break;
 163 
 164     default:
 165         ret = MSG_NOT_HANDLED;
 166     }
 167 
 168     return ret;
 169 }
 170 
 171 /* --------------------------------------------------------------------------------------------- */
 172 
 173 static cb_ret_t
 174 dlg_handle_key (WDialog *h, int d_key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 175 {
 176     long command;
 177 
 178     command = widget_lookup_key (WIDGET (h), d_key);
 179     if (command == CK_IgnoreKey)
 180         command = keybind_lookup_keymap_command (dialog_map, d_key);
 181     if (command != CK_IgnoreKey)
 182         return dlg_execute_cmd (h, command);
 183 
 184     return MSG_NOT_HANDLED;
 185 }
 186 
 187 /* --------------------------------------------------------------------------------------------- */
 188 
 189 static void
 190 dlg_key_event (WDialog *h, int d_key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 191 {
 192     Widget *w = WIDGET (h);
 193     WGroup *g = GROUP (h);
 194     cb_ret_t handled;
 195 
 196     if (g->widgets == NULL)
 197         return;
 198 
 199     if (g->current == NULL)
 200         g->current = g->widgets;
 201 
 202     // TAB used to cycle
 203     if (!widget_get_options (w, WOP_WANT_TAB))
 204     {
 205         if (d_key == '\t')
 206         {
 207             group_select_next_widget (g);
 208             return;
 209         }
 210         else if ((d_key & ~(KEY_M_SHIFT | KEY_M_CTRL)) == '\t')
 211         {
 212             group_select_prev_widget (g);
 213             return;
 214         }
 215     }
 216 
 217     // first can dlalog handle the key itself
 218     handled = send_message (h, NULL, MSG_KEY, d_key, NULL);
 219 
 220     if (handled == MSG_NOT_HANDLED)
 221         handled = group_default_callback (w, NULL, MSG_KEY, d_key, NULL);
 222 
 223     if (handled == MSG_NOT_HANDLED)
 224         handled = dlg_handle_key (h, d_key);
 225 
 226     (void) handled;
 227     send_message (h, NULL, MSG_POST_KEY, d_key, NULL);
 228 }
 229 
 230 /* --------------------------------------------------------------------------------------------- */
 231 
 232 static int
 233 dlg_handle_mouse_event (Widget *w, Gpm_Event *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 234 {
 235     if (w->mouse_callback != NULL)
 236     {
 237         int mou;
 238 
 239         mou = mouse_handle_event (w, event);
 240         if (mou != MOU_UNHANDLED)
 241             return mou;
 242     }
 243 
 244     return group_handle_mouse_event (w, event);
 245 }
 246 
 247 /* --------------------------------------------------------------------------------------------- */
 248 
 249 static void
 250 frontend_dlg_run (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 251 {
 252     Widget *wh = WIDGET (h);
 253     Gpm_Event event;
 254 
 255     event.x = -1;
 256 
 257     // close opened editors, viewers, etc
 258     if (!widget_get_state (wh, WST_MODAL) && mc_global.midnight_shutdown)
 259     {
 260         send_message (h, NULL, MSG_VALIDATE, 0, NULL);
 261         return;
 262     }
 263 
 264     while (widget_get_state (wh, WST_ACTIVE))
 265     {
 266         int d_key;
 267 
 268         if (tty_got_winch ())
 269             dialog_change_screen_size ();
 270 
 271         if (is_idle ())
 272         {
 273             if (idle_hook)
 274                 execute_hooks (idle_hook);
 275 
 276             while (widget_get_state (wh, WST_IDLE) && is_idle ())
 277                 send_message (wh, NULL, MSG_IDLE, 0, NULL);
 278 
 279             // Allow terminating the dialog from the idle handler
 280             if (!widget_get_state (wh, WST_ACTIVE))
 281                 break;
 282         }
 283 
 284         widget_update_cursor (wh);
 285 
 286         // Clear interrupt flag
 287         tty_got_interrupt ();
 288         d_key = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, TRUE);
 289 
 290         dlg_process_event (h, d_key, &event);
 291 
 292         if (widget_get_state (wh, WST_CLOSED))
 293             send_message (h, NULL, MSG_VALIDATE, 0, NULL);
 294     }
 295 }
 296 
 297 /* --------------------------------------------------------------------------------------------- */
 298 
 299 static void
 300 dlg_default_destroy (Widget *w)
     /* [previous][next][first][last][top][bottom][index][help]  */
 301 {
 302     WDialog *h = DIALOG (w);
 303 
 304     // if some widgets have history, save all histories at one moment here
 305     history_save (h, NULL);
 306     group_default_callback (w, NULL, MSG_DESTROY, 0, NULL);
 307     send_message (w, NULL, MSG_DESTROY, 0, NULL);
 308     mc_event_group_del (h->event_group);
 309     g_free (h->event_group);
 310     g_free (h);
 311 
 312     do_refresh ();
 313 }
 314 
 315 /* --------------------------------------------------------------------------------------------- */
 316 /*** public functions ****************************************************************************/
 317 /* --------------------------------------------------------------------------------------------- */
 318 /** Default dialog callback */
 319 
 320 cb_ret_t
 321 dlg_default_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 322 {
 323     switch (msg)
 324     {
 325     case MSG_INIT:
 326         // nothing to init in dialog itself
 327         return MSG_HANDLED;
 328 
 329     case MSG_IDLE:
 330         // we don't want endless loop
 331         widget_idle (w, FALSE);
 332         return MSG_HANDLED;
 333 
 334     case MSG_DESTROY:
 335         // nothing to deinit in dialog itself
 336         return MSG_HANDLED;
 337 
 338     default:
 339         return group_default_callback (w, sender, msg, parm, data);
 340     }
 341 }
 342 
 343 /* --------------------------------------------------------------------------------------------- */
 344 
 345 void
 346 dlg_default_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 347 {
 348     switch (msg)
 349     {
 350     case MSG_MOUSE_CLICK:
 351         if (event->y < 0 || event->y >= w->rect.lines || event->x < 0 || event->x >= w->rect.cols)
 352         {
 353             DIALOG (w)->ret_value = B_CANCEL;
 354             dlg_close (DIALOG (w));
 355         }
 356         break;
 357 
 358     default:
 359         // return MOU_UNHANDLED
 360         event->result.abort = TRUE;
 361         break;
 362     }
 363 }
 364 
 365 /* --------------------------------------------------------------------------------------------- */
 366 
 367 WDialog *
 368 dlg_create (gboolean modal, int y1, int x1, int lines, int cols, widget_pos_flags_t pos_flags,
     /* [previous][next][first][last][top][bottom][index][help]  */
 369             gboolean compact, const int *colors, widget_cb_fn callback,
 370             widget_mouse_cb_fn mouse_callback, const char *help_ctx, const char *title)
 371 {
 372     WRect r = { y1, x1, lines, cols };
 373     WDialog *new_d;
 374     Widget *w;
 375     WGroup *g;
 376 
 377     new_d = g_new0 (WDialog, 1);
 378     w = WIDGET (new_d);
 379     g = GROUP (new_d);
 380     widget_adjust_position (pos_flags, &r);
 381     group_init (g, &r, callback != NULL ? callback : dlg_default_callback,
 382                 mouse_callback != NULL ? mouse_callback : dlg_default_mouse_callback);
 383 
 384     w->pos_flags = pos_flags;
 385     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
 386     w->state |= WST_FOCUSED;
 387     // Temporary hack: dialog doesn't have an owner, own itself.
 388     w->owner = g;
 389 
 390     w->keymap = dialog_map;
 391 
 392     w->mouse_handler = dlg_handle_mouse_event;
 393     w->mouse.forced_capture = mouse_close_dialog && (w->pos_flags & WPOS_FULLSCREEN) == 0;
 394 
 395     w->destroy = dlg_default_destroy;
 396     w->get_colors = dlg_default_get_colors;
 397 
 398     new_d->colors = colors;
 399     new_d->help_ctx = help_ctx;
 400     new_d->compact = compact;
 401     new_d->data.p = NULL;
 402 
 403     if (modal)
 404     {
 405         w->state |= WST_MODAL;
 406 
 407         new_d->bg =
 408             WIDGET (frame_new (0, 0, w->rect.lines, w->rect.cols, title, FALSE, new_d->compact));
 409         group_add_widget (g, new_d->bg);
 410         frame_set_title (FRAME (new_d->bg), title);
 411     }
 412 
 413     // unique name of event group for this dialog
 414     new_d->event_group = g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG, (void *) new_d);
 415 
 416     return new_d;
 417 }
 418 
 419 /* --------------------------------------------------------------------------------------------- */
 420 
 421 void
 422 dlg_set_default_colors (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 423 {
 424     dialog_colors[DLG_COLOR_NORMAL] = DIALOG_NORMAL_COLOR;
 425     dialog_colors[DLG_COLOR_FOCUS] = DIALOG_FOCUS_COLOR;
 426     dialog_colors[DLG_COLOR_HOT_NORMAL] = DIALOG_HOT_NORMAL_COLOR;
 427     dialog_colors[DLG_COLOR_HOT_FOCUS] = DIALOG_HOT_FOCUS_COLOR;
 428     dialog_colors[DLG_COLOR_SELECTED_NORMAL] = DIALOG_SELECTED_NORMAL_COLOR;
 429     dialog_colors[DLG_COLOR_SELECTED_FOCUS] = DIALOG_SELECTED_FOCUS_COLOR;
 430     dialog_colors[DLG_COLOR_TITLE] = DIALOG_TITLE_COLOR;
 431     dialog_colors[DLG_COLOR_FRAME] = DIALOG_FRAME_COLOR;
 432 
 433     alarm_colors[DLG_COLOR_NORMAL] = ERROR_NORMAL_COLOR;
 434     alarm_colors[DLG_COLOR_FOCUS] = ERROR_FOCUS_COLOR;
 435     alarm_colors[DLG_COLOR_HOT_NORMAL] = ERROR_HOT_NORMAL_COLOR;
 436     alarm_colors[DLG_COLOR_HOT_FOCUS] = ERROR_HOT_FOCUS_COLOR;
 437     alarm_colors[DLG_COLOR_SELECTED_NORMAL] = ERROR_HOT_FOCUS_COLOR;  // unused
 438     alarm_colors[DLG_COLOR_SELECTED_FOCUS] = ERROR_FOCUS_COLOR;       // unused
 439     alarm_colors[DLG_COLOR_TITLE] = ERROR_TITLE_COLOR;
 440     alarm_colors[DLG_COLOR_FRAME] = ERROR_FRAME_COLOR;
 441 
 442     listbox_colors[DLG_COLOR_NORMAL] = PMENU_ENTRY_COLOR;
 443     listbox_colors[DLG_COLOR_FOCUS] = PMENU_SELECTED_COLOR;
 444     listbox_colors[DLG_COLOR_HOT_NORMAL] = PMENU_ENTRY_COLOR;
 445     listbox_colors[DLG_COLOR_HOT_FOCUS] = PMENU_SELECTED_COLOR;
 446     listbox_colors[DLG_COLOR_SELECTED_NORMAL] = PMENU_SELECTED_COLOR;  // unused
 447     listbox_colors[DLG_COLOR_SELECTED_FOCUS] = PMENU_SELECTED_COLOR;   // unused
 448     listbox_colors[DLG_COLOR_TITLE] = PMENU_TITLE_COLOR;
 449     listbox_colors[DLG_COLOR_FRAME] = PMENU_FRAME_COLOR;
 450 
 451     help_colors[DLG_COLOR_NORMAL] = HELP_NORMAL_COLOR;
 452     help_colors[DLG_COLOR_FOCUS] = 0;  // unused
 453     help_colors[DLG_COLOR_HOT_NORMAL] = HELP_BOLD_COLOR;
 454     help_colors[DLG_COLOR_HOT_FOCUS] = 0;        // unused
 455     help_colors[DLG_COLOR_SELECTED_NORMAL] = 0;  // unused
 456     help_colors[DLG_COLOR_SELECTED_FOCUS] = 0;   // unused
 457     help_colors[DLG_COLOR_TITLE] = HELP_TITLE_COLOR;
 458     help_colors[DLG_COLOR_FRAME] = HELP_FRAME_COLOR;
 459 }
 460 
 461 /* --------------------------------------------------------------------------------------------- */
 462 
 463 void
 464 dlg_close (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 465 {
 466     widget_set_state (WIDGET (h), WST_CLOSED, TRUE);
 467 }
 468 
 469 /* --------------------------------------------------------------------------------------------- */
 470 /** Init the process */
 471 
 472 void
 473 dlg_init (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 474 {
 475     WGroup *g = GROUP (h);
 476     Widget *wh = WIDGET (h);
 477 
 478     if (top_dlg != NULL && widget_get_state (WIDGET (top_dlg->data), WST_MODAL))
 479         widget_set_state (wh, WST_MODAL, TRUE);
 480 
 481     // add dialog to the stack
 482     top_dlg = g_list_prepend (top_dlg, h);
 483 
 484     // Initialize dialog manager and widgets
 485     if (widget_get_state (wh, WST_CONSTRUCT))
 486     {
 487         if (!widget_get_state (wh, WST_MODAL))
 488             dialog_switch_add (h);
 489 
 490         send_message (h, NULL, MSG_INIT, 0, NULL);
 491         group_default_callback (wh, NULL, MSG_INIT, 0, NULL);
 492         history_load (h, NULL);
 493     }
 494 
 495     // Select the first widget that takes focus
 496     while (g->current != NULL && !widget_is_focusable (g->current->data))
 497         group_set_current_widget_next (g);
 498 
 499     widget_set_state (wh, WST_ACTIVE, TRUE);
 500     widget_draw (wh);
 501 
 502     h->ret_value = 0;
 503 }
 504 
 505 /* --------------------------------------------------------------------------------------------- */
 506 
 507 void
 508 dlg_process_event (WDialog *h, int key, Gpm_Event *event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 509 {
 510     switch (key)
 511     {
 512     case EV_NONE:
 513         if (tty_got_interrupt ())
 514             dlg_execute_cmd (h, CK_Cancel);
 515         break;
 516 
 517     case EV_MOUSE:
 518     {
 519         Widget *w = WIDGET (h);
 520 
 521         GROUP (h)->mouse_status = w->mouse_handler (w, event);
 522         break;
 523     }
 524 
 525     default:
 526         dlg_key_event (h, key);
 527         break;
 528     }
 529 }
 530 
 531 /* --------------------------------------------------------------------------------------------- */
 532 /** Shutdown the dlg_run */
 533 
 534 void
 535 dlg_run_done (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 536 {
 537     top_dlg = g_list_remove (top_dlg, h);
 538 
 539     if (widget_get_state (WIDGET (h), WST_CLOSED))
 540     {
 541         send_message (h, GROUP (h)->current == NULL ? NULL : WIDGET (GROUP (h)->current->data),
 542                       MSG_END, 0, NULL);
 543         if (!widget_get_state (WIDGET (h), WST_MODAL))
 544             dialog_switch_remove (h);
 545     }
 546 }
 547 
 548 /* --------------------------------------------------------------------------------------------- */
 549 /**
 550  * Standard run dialog routine
 551  * We have to keep this routine small so that we can duplicate it's
 552  * behavior on complex routines like the file routines, this way,
 553  * they can call the dlg_process_event without rewriting all the code
 554  */
 555 
 556 int
 557 dlg_run (WDialog *h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 558 {
 559     dlg_init (h);
 560     frontend_dlg_run (h);
 561     dlg_run_done (h);
 562     return h->ret_value;
 563 }
 564 
 565 /* --------------------------------------------------------------------------------------------- */
 566 
 567 char *
 568 dlg_get_title (const WDialog *h, const ssize_t width)
     /* [previous][next][first][last][top][bottom][index][help]  */
 569 {
 570     char *t;
 571 
 572     if (h == NULL)
 573         abort ();
 574 
 575     if (h->get_title != NULL)
 576         t = h->get_title (h, width);
 577     else
 578         t = g_strdup ("");
 579 
 580     return t;
 581 }
 582 
 583 /* --------------------------------------------------------------------------------------------- */

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