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. dlg_read_history
  3. refresh_cmd
  4. dlg_execute_cmd
  5. dlg_handle_key
  6. dlg_key_event
  7. dlg_handle_mouse_event
  8. frontend_dlg_run
  9. dlg_default_callback
  10. dlg_default_mouse_callback
  11. dlg_create
  12. dlg_set_default_colors
  13. do_refresh
  14. dlg_stop
  15. dlg_init
  16. dlg_process_event
  17. dlg_run_done
  18. dlg_run
  19. dlg_destroy
  20. dlg_save_history
  21. dlg_get_title

   1 /*
   2    Dialog box features module for the Midnight Commander
   3 
   4    Copyright (C) 1994-2020
   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 <http://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 <ctype.h>
  30 #include <errno.h>
  31 #include <stdlib.h>
  32 #include <stdio.h>
  33 #include <string.h>
  34 #include <sys/types.h>
  35 #include <sys/stat.h>
  36 
  37 #include "lib/global.h"
  38 
  39 #include "lib/tty/tty.h"
  40 #include "lib/skin.h"
  41 #include "lib/tty/key.h"
  42 #include "lib/strutil.h"
  43 #include "lib/fileloc.h"        /* MC_HISTORY_FILE */
  44 #include "lib/event.h"          /* mc_event_raise() */
  45 #include "lib/util.h"           /* MC_PTR_FREE */
  46 #include "lib/mcconfig.h"       /* num_history_items_recorded */
  47 
  48 #include "lib/widget.h"
  49 #include "lib/widget/mouse.h"
  50 
  51 /*** global variables ****************************************************************************/
  52 
  53 /* Color styles for normal and error dialogs */
  54 dlg_colors_t dialog_colors;
  55 dlg_colors_t alarm_colors;
  56 dlg_colors_t listbox_colors;
  57 
  58 /* Primitive way to check if the the current dialog is our dialog */
  59 /* This is needed by async routines like load_prompt */
  60 GList *top_dlg = NULL;
  61 
  62 /* A hook list for idle events */
  63 hook_t *idle_hook = NULL;
  64 
  65 /* If set then dialogs just clean the screen when refreshing, else */
  66 /* they do a complete refresh, refreshing all the parts of the program */
  67 gboolean fast_refresh = FALSE;
  68 
  69 /* left click outside of dialog closes it */
  70 gboolean mouse_close_dialog = FALSE;
  71 
  72 const global_keymap_t *dialog_map = NULL;
  73 
  74 /*** file scope macro definitions ****************************************************************/
  75 
  76 /*** file scope type declarations ****************************************************************/
  77 
  78 /*** file scope variables ************************************************************************/
  79 
  80 /* --------------------------------------------------------------------------------------------- */
  81 /*** file scope functions ************************************************************************/
  82 /* --------------------------------------------------------------------------------------------- */
  83 
  84 static const int *
  85 dlg_default_get_colors (const Widget * w)
     /* [previous][next][first][last][top][bottom][index][help]  */
  86 {
  87     return CONST_DIALOG (w)->colors;
  88 }
  89 
  90 /* --------------------------------------------------------------------------------------------- */
  91 /**
  92   * Read histories from the ${XDG_CACHE_HOME}/mc/history file
  93   */
  94 static void
  95 dlg_read_history (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
  96 {
  97     char *profile;
  98     ev_history_load_save_t event_data;
  99 
 100     if (num_history_items_recorded == 0)        /* this is how to disable */
 101         return;
 102 
 103     profile = mc_config_get_full_path (MC_HISTORY_FILE);
 104     event_data.cfg = mc_config_init (profile, TRUE);
 105     event_data.receiver = NULL;
 106 
 107     /* create all histories in dialog */
 108     mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data);
 109 
 110     mc_config_deinit (event_data.cfg);
 111     g_free (profile);
 112 }
 113 
 114 /* --------------------------------------------------------------------------------------------- */
 115 
 116 static void
 117 refresh_cmd (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 118 {
 119 #ifdef HAVE_SLANG
 120     tty_touch_screen ();
 121     mc_refresh ();
 122 #else
 123     /* Use this if the refreshes fail */
 124     clr_scr ();
 125     repaint_screen ();
 126 #endif /* HAVE_SLANG */
 127 }
 128 
 129 /* --------------------------------------------------------------------------------------------- */
 130 
 131 static cb_ret_t
 132 dlg_execute_cmd (WDialog * h, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 133 {
 134     WGroup *g = GROUP (h);
 135     cb_ret_t ret = MSG_HANDLED;
 136 
 137     if (send_message (h, NULL, MSG_ACTION, command, NULL) == MSG_HANDLED)
 138         return MSG_HANDLED;
 139 
 140     switch (command)
 141     {
 142     case CK_Ok:
 143         h->ret_value = B_ENTER;
 144         dlg_stop (h);
 145         break;
 146     case CK_Cancel:
 147         h->ret_value = B_CANCEL;
 148         dlg_stop (h);
 149         break;
 150 
 151     case CK_Up:
 152     case CK_Left:
 153         group_select_prev_widget (g);
 154         break;
 155     case CK_Down:
 156     case CK_Right:
 157         group_select_next_widget (g);
 158         break;
 159 
 160     case CK_Help:
 161         {
 162             ev_help_t event_data = { NULL, h->help_ctx };
 163             mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
 164         }
 165         break;
 166 
 167     case CK_Suspend:
 168         mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
 169         refresh_cmd ();
 170         break;
 171     case CK_Refresh:
 172         refresh_cmd ();
 173         break;
 174 
 175     case CK_ScreenList:
 176         if (!widget_get_state (WIDGET (h), WST_MODAL))
 177             dialog_switch_list ();
 178         else
 179             ret = MSG_NOT_HANDLED;
 180         break;
 181     case CK_ScreenNext:
 182         if (!widget_get_state (WIDGET (h), WST_MODAL))
 183             dialog_switch_next ();
 184         else
 185             ret = MSG_NOT_HANDLED;
 186         break;
 187     case CK_ScreenPrev:
 188         if (!widget_get_state (WIDGET (h), WST_MODAL))
 189             dialog_switch_prev ();
 190         else
 191             ret = MSG_NOT_HANDLED;
 192         break;
 193 
 194     default:
 195         ret = MSG_NOT_HANDLED;
 196     }
 197 
 198     return ret;
 199 }
 200 
 201 /* --------------------------------------------------------------------------------------------- */
 202 
 203 static cb_ret_t
 204 dlg_handle_key (WDialog * h, int d_key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 205 {
 206     long command;
 207 
 208     command = widget_lookup_key (WIDGET (h), d_key);
 209     if (command != CK_IgnoreKey)
 210         return dlg_execute_cmd (h, command);
 211 
 212     return MSG_NOT_HANDLED;
 213 }
 214 
 215 /* --------------------------------------------------------------------------------------------- */
 216 
 217 static void
 218 dlg_key_event (WDialog * h, int d_key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 219 {
 220     Widget *w = WIDGET (h);
 221     WGroup *g = GROUP (h);
 222     cb_ret_t handled;
 223 
 224     if (g->widgets == NULL)
 225         return;
 226 
 227     if (g->current == NULL)
 228         g->current = g->widgets;
 229 
 230     /* TAB used to cycle */
 231     if (!widget_get_options (w, WOP_WANT_TAB))
 232     {
 233         if (d_key == '\t')
 234         {
 235             group_select_next_widget (g);
 236             return;
 237         }
 238         else if ((d_key & ~(KEY_M_SHIFT | KEY_M_CTRL)) == '\t')
 239         {
 240             group_select_prev_widget (g);
 241             return;
 242         }
 243     }
 244 
 245     /* first can dlalog handle the key itself */
 246     handled = send_message (h, NULL, MSG_KEY, d_key, NULL);
 247 
 248     if (handled == MSG_NOT_HANDLED)
 249         handled = group_default_callback (w, NULL, MSG_KEY, d_key, NULL);
 250 
 251     if (handled == MSG_NOT_HANDLED)
 252         handled = dlg_handle_key (h, d_key);
 253 
 254     (void) handled;
 255     send_message (h, NULL, MSG_POST_KEY, d_key, NULL);
 256 }
 257 
 258 /* --------------------------------------------------------------------------------------------- */
 259 
 260 static int
 261 dlg_handle_mouse_event (Widget * w, Gpm_Event * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 262 {
 263     if (w->mouse_callback != NULL)
 264     {
 265         int mou;
 266 
 267         mou = mouse_handle_event (w, event);
 268         if (mou != MOU_UNHANDLED)
 269             return mou;
 270     }
 271 
 272     return group_handle_mouse_event (w, event);
 273 }
 274 
 275 /* --------------------------------------------------------------------------------------------- */
 276 
 277 static void
 278 frontend_dlg_run (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 279 {
 280     Widget *wh = WIDGET (h);
 281     Gpm_Event event;
 282 
 283     event.x = -1;
 284 
 285     /* close opened editors, viewers, etc */
 286     if (!widget_get_state (wh, WST_MODAL) && mc_global.midnight_shutdown)
 287     {
 288         send_message (h, NULL, MSG_VALIDATE, 0, NULL);
 289         return;
 290     }
 291 
 292     while (widget_get_state (wh, WST_ACTIVE))
 293     {
 294         int d_key;
 295 
 296         if (tty_got_winch ())
 297             dialog_change_screen_size ();
 298 
 299         if (is_idle ())
 300         {
 301             if (idle_hook)
 302                 execute_hooks (idle_hook);
 303 
 304             while (widget_get_state (wh, WST_IDLE) && is_idle ())
 305                 send_message (wh, NULL, MSG_IDLE, 0, NULL);
 306 
 307             /* Allow terminating the dialog from the idle handler */
 308             if (!widget_get_state (wh, WST_ACTIVE))
 309                 break;
 310         }
 311 
 312         widget_update_cursor (wh);
 313 
 314         /* Clear interrupt flag */
 315         tty_got_interrupt ();
 316         d_key = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, TRUE);
 317 
 318         dlg_process_event (h, d_key, &event);
 319 
 320         if (widget_get_state (wh, WST_CLOSED))
 321             send_message (h, NULL, MSG_VALIDATE, 0, NULL);
 322     }
 323 }
 324 
 325 /* --------------------------------------------------------------------------------------------- */
 326 /*** public functions ****************************************************************************/
 327 /* --------------------------------------------------------------------------------------------- */
 328 /** Default dialog callback */
 329 
 330 cb_ret_t
 331 dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 332 {
 333     switch (msg)
 334     {
 335     case MSG_IDLE:
 336         /* we don't want endless loop */
 337         widget_idle (w, FALSE);
 338         return MSG_HANDLED;
 339 
 340     default:
 341         return group_default_callback (w, sender, msg, parm, data);
 342     }
 343 }
 344 
 345 /* --------------------------------------------------------------------------------------------- */
 346 
 347 void
 348 dlg_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 349 {
 350     switch (msg)
 351     {
 352     case MSG_MOUSE_CLICK:
 353         if (event->y < 0 || event->y >= w->lines || event->x < 0 || event->x >= w->cols)
 354         {
 355             DIALOG (w)->ret_value = B_CANCEL;
 356             dlg_stop (DIALOG (w));
 357         }
 358         break;
 359 
 360     default:
 361         /* return MOU_UNHANDLED */
 362         event->result.abort = TRUE;
 363         break;
 364     }
 365 }
 366 
 367 /* --------------------------------------------------------------------------------------------- */
 368 
 369 WDialog *
 370 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]  */
 371             gboolean compact, const int *colors, widget_cb_fn callback,
 372             widget_mouse_cb_fn mouse_callback, const char *help_ctx, const char *title)
 373 {
 374     WDialog *new_d;
 375     Widget *w;
 376     WGroup *g;
 377 
 378     new_d = g_new0 (WDialog, 1);
 379     w = WIDGET (new_d);
 380     g = GROUP (new_d);
 381     widget_adjust_position (pos_flags, &y1, &x1, &lines, &cols);
 382     group_init (g, y1, x1, lines, cols, callback != NULL ? callback : dlg_default_callback,
 383                 mouse_callback != NULL ? mouse_callback : dlg_default_mouse_callback);
 384 
 385     w->pos_flags = pos_flags;
 386     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
 387     w->state |= WST_FOCUSED;
 388     /* Temporary hack: dialog doesn't have an owner, own itself. */
 389     w->owner = g;
 390 
 391     w->keymap = dialog_map;
 392 
 393     w->mouse_handler = dlg_handle_mouse_event;
 394     w->mouse.forced_capture = mouse_close_dialog && (w->pos_flags & WPOS_FULLSCREEN) == 0;
 395 
 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 = NULL;
 402 
 403     if (modal)
 404     {
 405         w->state |= WST_MODAL;
 406 
 407         new_d->bg = WIDGET (frame_new (0, 0, w->lines, w->cols, title, FALSE, new_d->compact));
 408         group_add_widget (g, new_d->bg);
 409         frame_set_title (FRAME (new_d->bg), title);
 410     }
 411 
 412     /* unique name of event group for this dialog */
 413     new_d->event_group = g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG, (void *) new_d);
 414 
 415     return new_d;
 416 }
 417 
 418 /* --------------------------------------------------------------------------------------------- */
 419 
 420 void
 421 dlg_set_default_colors (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 422 {
 423     dialog_colors[DLG_COLOR_NORMAL] = COLOR_NORMAL;
 424     dialog_colors[DLG_COLOR_FOCUS] = COLOR_FOCUS;
 425     dialog_colors[DLG_COLOR_HOT_NORMAL] = COLOR_HOT_NORMAL;
 426     dialog_colors[DLG_COLOR_HOT_FOCUS] = COLOR_HOT_FOCUS;
 427     dialog_colors[DLG_COLOR_TITLE] = COLOR_TITLE;
 428 
 429     alarm_colors[DLG_COLOR_NORMAL] = ERROR_COLOR;
 430     alarm_colors[DLG_COLOR_FOCUS] = ERROR_FOCUS;
 431     alarm_colors[DLG_COLOR_HOT_NORMAL] = ERROR_HOT_NORMAL;
 432     alarm_colors[DLG_COLOR_HOT_FOCUS] = ERROR_HOT_FOCUS;
 433     alarm_colors[DLG_COLOR_TITLE] = ERROR_TITLE;
 434 
 435     listbox_colors[DLG_COLOR_NORMAL] = PMENU_ENTRY_COLOR;
 436     listbox_colors[DLG_COLOR_FOCUS] = PMENU_SELECTED_COLOR;
 437     listbox_colors[DLG_COLOR_HOT_NORMAL] = PMENU_ENTRY_COLOR;
 438     listbox_colors[DLG_COLOR_HOT_FOCUS] = PMENU_SELECTED_COLOR;
 439     listbox_colors[DLG_COLOR_TITLE] = PMENU_TITLE_COLOR;
 440 }
 441 
 442 /* --------------------------------------------------------------------------------------------- */
 443 
 444 void
 445 do_refresh (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 446 {
 447     GList *d = top_dlg;
 448 
 449     if (fast_refresh)
 450     {
 451         if (d != NULL)
 452             widget_draw (WIDGET (d->data));
 453     }
 454     else
 455     {
 456         /* Search first fullscreen dialog */
 457         for (; d != NULL; d = g_list_next (d))
 458             if ((WIDGET (d->data)->pos_flags & WPOS_FULLSCREEN) != 0)
 459                 break;
 460 
 461         /* when small dialog (i.e. error message) is created first,
 462            there is no fullscreen dialog in the stack */
 463         if (d == NULL)
 464             d = g_list_last (top_dlg);
 465 
 466         /* back to top dialog */
 467         for (; d != NULL; d = g_list_previous (d))
 468             widget_draw (WIDGET (d->data));
 469     }
 470 }
 471 
 472 /* --------------------------------------------------------------------------------------------- */
 473 
 474 void
 475 dlg_stop (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 476 {
 477     widget_set_state (WIDGET (h), WST_CLOSED, TRUE);
 478 }
 479 
 480 /* --------------------------------------------------------------------------------------------- */
 481 /** Init the process */
 482 
 483 void
 484 dlg_init (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 485 {
 486     WGroup *g = GROUP (h);
 487     Widget *wh = WIDGET (h);
 488 
 489     if (top_dlg != NULL && widget_get_state (WIDGET (top_dlg->data), WST_MODAL))
 490         widget_set_state (wh, WST_MODAL, TRUE);
 491 
 492     /* add dialog to the stack */
 493     top_dlg = g_list_prepend (top_dlg, h);
 494 
 495     /* Initialize dialog manager and widgets */
 496     if (widget_get_state (wh, WST_CONSTRUCT))
 497     {
 498         if (!widget_get_state (wh, WST_MODAL))
 499             dialog_switch_add (h);
 500 
 501         send_message (h, NULL, MSG_INIT, 0, NULL);
 502         group_default_callback (wh, NULL, MSG_INIT, 0, NULL);
 503         dlg_read_history (h);
 504     }
 505 
 506     /* Select the first widget that takes focus */
 507     while (g->current != NULL && !widget_get_options (WIDGET (g->current->data), WOP_SELECTABLE)
 508            && !widget_get_state (WIDGET (g->current->data), WST_DISABLED))
 509         group_set_current_widget_next (g);
 510 
 511     widget_set_state (wh, WST_ACTIVE, TRUE);
 512     /* draw dialog and focus found widget */
 513     widget_set_state (wh, WST_FOCUSED, TRUE);
 514 
 515     h->ret_value = 0;
 516 }
 517 
 518 /* --------------------------------------------------------------------------------------------- */
 519 
 520 void
 521 dlg_process_event (WDialog * h, int key, Gpm_Event * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 522 {
 523     switch (key)
 524     {
 525     case EV_NONE:
 526         if (tty_got_interrupt ())
 527             dlg_execute_cmd (h, CK_Cancel);
 528         break;
 529 
 530     case EV_MOUSE:
 531         {
 532             Widget *w = WIDGET (h);
 533 
 534             GROUP (h)->mouse_status = w->mouse_handler (w, event);
 535             break;
 536         }
 537 
 538     default:
 539         dlg_key_event (h, key);
 540         break;
 541     }
 542 }
 543 
 544 /* --------------------------------------------------------------------------------------------- */
 545 /** Shutdown the dlg_run */
 546 
 547 void
 548 dlg_run_done (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 549 {
 550     top_dlg = g_list_remove (top_dlg, h);
 551 
 552     if (widget_get_state (WIDGET (h), WST_CLOSED))
 553     {
 554         send_message (h, GROUP (h)->current == NULL ? NULL : WIDGET (GROUP (h)->current->data),
 555                       MSG_END, 0, NULL);
 556         if (!widget_get_state (WIDGET (h), WST_MODAL))
 557             dialog_switch_remove (h);
 558     }
 559 }
 560 
 561 /* --------------------------------------------------------------------------------------------- */
 562 /**
 563  * Standard run dialog routine
 564  * We have to keep this routine small so that we can duplicate it's
 565  * behavior on complex routines like the file routines, this way,
 566  * they can call the dlg_process_event without rewriting all the code
 567  */
 568 
 569 int
 570 dlg_run (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 571 {
 572     dlg_init (h);
 573     frontend_dlg_run (h);
 574     dlg_run_done (h);
 575     return h->ret_value;
 576 }
 577 
 578 /* --------------------------------------------------------------------------------------------- */
 579 
 580 void
 581 dlg_destroy (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 582 {
 583     /* if some widgets have history, save all history at one moment here */
 584     dlg_save_history (h);
 585     group_default_callback (WIDGET (h), NULL, MSG_DESTROY, 0, NULL);
 586     mc_event_group_del (h->event_group);
 587     g_free (h->event_group);
 588     g_free (h);
 589 
 590     do_refresh ();
 591 }
 592 
 593 /* --------------------------------------------------------------------------------------------- */
 594 
 595 /**
 596   * Write history to the ${XDG_CACHE_HOME}/mc/history file
 597   */
 598 void
 599 dlg_save_history (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 600 {
 601     char *profile;
 602     int i;
 603 
 604     if (num_history_items_recorded == 0)        /* this is how to disable */
 605         return;
 606 
 607     profile = mc_config_get_full_path (MC_HISTORY_FILE);
 608     i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
 609     if (i != -1)
 610         close (i);
 611 
 612     /* Make sure the history is only readable by the user */
 613     if (chmod (profile, S_IRUSR | S_IWUSR) != -1 || errno == ENOENT)
 614     {
 615         ev_history_load_save_t event_data;
 616 
 617         event_data.cfg = mc_config_init (profile, FALSE);
 618         event_data.receiver = NULL;
 619 
 620         /* get all histories in dialog */
 621         mc_event_raise (h->event_group, MCEVENT_HISTORY_SAVE, &event_data);
 622 
 623         mc_config_save_file (event_data.cfg, NULL);
 624         mc_config_deinit (event_data.cfg);
 625     }
 626 
 627     g_free (profile);
 628 }
 629 
 630 /* --------------------------------------------------------------------------------------------- */
 631 
 632 char *
 633 dlg_get_title (const WDialog * h, size_t len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 634 {
 635     char *t;
 636 
 637     if (h == NULL)
 638         abort ();
 639 
 640     if (h->get_title != NULL)
 641         t = h->get_title (h, len);
 642     else
 643         t = g_strdup ("");
 644 
 645     return t;
 646 }
 647 
 648 /* --------------------------------------------------------------------------------------------- */

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