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

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