2 Dialog box features module for the Midnight Commander
4 Copyright (C) 1994-2023
5 Free Software Foundation, Inc.
7 This file is part of the Midnight Commander.
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.
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.
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/>.
24 * \brief Source: dialog box features module
34 #include <sys/types.h>
37 #include "lib/global.h"
39 #include "lib/tty/tty.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 */
48 #include "lib/widget.h"
49 #include "lib/widget/mouse.h"
51 /*** global variables ****************************************************************************/
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
;
58 /* A hook list for idle events */
59 hook_t
*idle_hook
= NULL
;
61 /* left click outside of dialog closes it */
62 gboolean mouse_close_dialog
= FALSE
;
64 const global_keymap_t
*dialog_map
= NULL
;
66 /*** file scope macro definitions ****************************************************************/
68 /*** file scope type declarations ****************************************************************/
70 /*** forward declarations (file scope functions) *************************************************/
72 /*** file scope variables ************************************************************************/
74 /* --------------------------------------------------------------------------------------------- */
75 /*** file scope functions ************************************************************************/
76 /* --------------------------------------------------------------------------------------------- */
79 dlg_default_get_colors (const Widget
* w
)
81 return CONST_DIALOG (w
)->colors
;
84 /* --------------------------------------------------------------------------------------------- */
86 * Read histories from the ${XDG_DATA_HOME}/mc/history file
89 dlg_read_history (WDialog
* h
)
92 ev_history_load_save_t event_data
;
94 if (num_history_items_recorded
== 0) /* this is how to disable */
97 profile
= mc_config_get_full_path (MC_HISTORY_FILE
);
98 event_data
.cfg
= mc_config_init (profile
, TRUE
);
99 event_data
.receiver
= NULL
;
101 /* create all histories in dialog */
102 mc_event_raise (h
->event_group
, MCEVENT_HISTORY_LOAD
, &event_data
);
104 mc_config_deinit (event_data
.cfg
);
108 /* --------------------------------------------------------------------------------------------- */
117 /* Use this if the refreshes fail */
120 #endif /* HAVE_SLANG */
123 /* --------------------------------------------------------------------------------------------- */
126 dlg_execute_cmd (WDialog
* h
, long command
)
128 WGroup
*g
= GROUP (h
);
129 cb_ret_t ret
= MSG_HANDLED
;
131 if (send_message (h
, NULL
, MSG_ACTION
, command
, NULL
) == MSG_HANDLED
)
137 h
->ret_value
= B_ENTER
;
141 h
->ret_value
= B_CANCEL
;
147 group_select_prev_widget (g
);
151 group_select_next_widget (g
);
156 ev_help_t event_data
= { NULL
, h
->help_ctx
};
157 mc_event_raise (MCEVENT_GROUP_CORE
, "help", &event_data
);
162 mc_event_raise (MCEVENT_GROUP_CORE
, "suspend", NULL
);
170 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
171 dialog_switch_list ();
173 ret
= MSG_NOT_HANDLED
;
176 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
177 dialog_switch_next ();
179 ret
= MSG_NOT_HANDLED
;
182 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
183 dialog_switch_prev ();
185 ret
= MSG_NOT_HANDLED
;
189 ret
= MSG_NOT_HANDLED
;
195 /* --------------------------------------------------------------------------------------------- */
198 dlg_handle_key (WDialog
* h
, int d_key
)
202 command
= widget_lookup_key (WIDGET (h
), d_key
);
203 if (command
== CK_IgnoreKey
)
204 command
= keybind_lookup_keymap_command (dialog_map
, d_key
);
205 if (command
!= CK_IgnoreKey
)
206 return dlg_execute_cmd (h
, command
);
208 return MSG_NOT_HANDLED
;
211 /* --------------------------------------------------------------------------------------------- */
214 dlg_key_event (WDialog
* h
, int d_key
)
216 Widget
*w
= WIDGET (h
);
217 WGroup
*g
= GROUP (h
);
220 if (g
->widgets
== NULL
)
223 if (g
->current
== NULL
)
224 g
->current
= g
->widgets
;
226 /* TAB used to cycle */
227 if (!widget_get_options (w
, WOP_WANT_TAB
))
231 group_select_next_widget (g
);
234 else if ((d_key
& ~(KEY_M_SHIFT
| KEY_M_CTRL
)) == '\t')
236 group_select_prev_widget (g
);
241 /* first can dlalog handle the key itself */
242 handled
= send_message (h
, NULL
, MSG_KEY
, d_key
, NULL
);
244 if (handled
== MSG_NOT_HANDLED
)
245 handled
= group_default_callback (w
, NULL
, MSG_KEY
, d_key
, NULL
);
247 if (handled
== MSG_NOT_HANDLED
)
248 handled
= dlg_handle_key (h
, d_key
);
251 send_message (h
, NULL
, MSG_POST_KEY
, d_key
, NULL
);
254 /* --------------------------------------------------------------------------------------------- */
257 dlg_handle_mouse_event (Widget
* w
, Gpm_Event
* event
)
259 if (w
->mouse_callback
!= NULL
)
263 mou
= mouse_handle_event (w
, event
);
264 if (mou
!= MOU_UNHANDLED
)
268 return group_handle_mouse_event (w
, event
);
271 /* --------------------------------------------------------------------------------------------- */
274 frontend_dlg_run (WDialog
* h
)
276 Widget
*wh
= WIDGET (h
);
281 /* close opened editors, viewers, etc */
282 if (!widget_get_state (wh
, WST_MODAL
) && mc_global
.midnight_shutdown
)
284 send_message (h
, NULL
, MSG_VALIDATE
, 0, NULL
);
288 while (widget_get_state (wh
, WST_ACTIVE
))
292 if (tty_got_winch ())
293 dialog_change_screen_size ();
298 execute_hooks (idle_hook
);
300 while (widget_get_state (wh
, WST_IDLE
) && is_idle ())
301 send_message (wh
, NULL
, MSG_IDLE
, 0, NULL
);
303 /* Allow terminating the dialog from the idle handler */
304 if (!widget_get_state (wh
, WST_ACTIVE
))
308 widget_update_cursor (wh
);
310 /* Clear interrupt flag */
311 tty_got_interrupt ();
312 d_key
= tty_get_event (&event
, GROUP (h
)->mouse_status
== MOU_REPEAT
, TRUE
);
314 dlg_process_event (h
, d_key
, &event
);
316 if (widget_get_state (wh
, WST_CLOSED
))
317 send_message (h
, NULL
, MSG_VALIDATE
, 0, NULL
);
321 /* --------------------------------------------------------------------------------------------- */
324 dlg_default_destroy (Widget
* w
)
326 WDialog
*h
= DIALOG (w
);
328 /* if some widgets have history, save all histories at one moment here */
329 dlg_save_history (h
);
330 group_default_callback (w
, NULL
, MSG_DESTROY
, 0, NULL
);
331 send_message (w
, NULL
, MSG_DESTROY
, 0, NULL
);
332 mc_event_group_del (h
->event_group
);
333 g_free (h
->event_group
);
339 /* --------------------------------------------------------------------------------------------- */
340 /*** public functions ****************************************************************************/
341 /* --------------------------------------------------------------------------------------------- */
342 /** Default dialog callback */
345 dlg_default_callback (Widget
* w
, Widget
* sender
, widget_msg_t msg
, int parm
, void *data
)
350 /* nothing to init in dialog itself */
354 /* we don't want endless loop */
355 widget_idle (w
, FALSE
);
359 /* nothing to deinit in dialog itself */
363 return group_default_callback (w
, sender
, msg
, parm
, data
);
367 /* --------------------------------------------------------------------------------------------- */
370 dlg_default_mouse_callback (Widget
* w
, mouse_msg_t msg
, mouse_event_t
* event
)
374 case MSG_MOUSE_CLICK
:
375 if (event
->y
< 0 || event
->y
>= w
->rect
.lines
|| event
->x
< 0 || event
->x
>= w
->rect
.cols
)
377 DIALOG (w
)->ret_value
= B_CANCEL
;
378 dlg_close (DIALOG (w
));
383 /* return MOU_UNHANDLED */
384 event
->result
.abort
= TRUE
;
389 /* --------------------------------------------------------------------------------------------- */
392 dlg_create (gboolean modal
, int y1
, int x1
, int lines
, int cols
, widget_pos_flags_t pos_flags
,
393 gboolean compact
, const int *colors
, widget_cb_fn callback
,
394 widget_mouse_cb_fn mouse_callback
, const char *help_ctx
, const char *title
)
396 WRect r
= { y1
, x1
, lines
, cols
};
401 new_d
= g_new0 (WDialog
, 1);
404 widget_adjust_position (pos_flags
, &r
);
405 group_init (g
, &r
, callback
!= NULL
? callback
: dlg_default_callback
,
406 mouse_callback
!= NULL
? mouse_callback
: dlg_default_mouse_callback
);
408 w
->pos_flags
= pos_flags
;
409 w
->options
|= WOP_SELECTABLE
| WOP_TOP_SELECT
;
410 w
->state
|= WST_FOCUSED
;
411 /* Temporary hack: dialog doesn't have an owner, own itself. */
414 w
->keymap
= dialog_map
;
416 w
->mouse_handler
= dlg_handle_mouse_event
;
417 w
->mouse
.forced_capture
= mouse_close_dialog
&& (w
->pos_flags
& WPOS_FULLSCREEN
) == 0;
419 w
->destroy
= dlg_default_destroy
;
420 w
->get_colors
= dlg_default_get_colors
;
422 new_d
->colors
= colors
;
423 new_d
->help_ctx
= help_ctx
;
424 new_d
->compact
= compact
;
425 new_d
->data
.p
= NULL
;
429 w
->state
|= WST_MODAL
;
432 WIDGET (frame_new (0, 0, w
->rect
.lines
, w
->rect
.cols
, title
, FALSE
, new_d
->compact
));
433 group_add_widget (g
, new_d
->bg
);
434 frame_set_title (FRAME (new_d
->bg
), title
);
437 /* unique name of event group for this dialog */
438 new_d
->event_group
= g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG
, (void *) new_d
);
443 /* --------------------------------------------------------------------------------------------- */
446 dlg_set_default_colors (void)
448 dialog_colors
[DLG_COLOR_NORMAL
] = COLOR_NORMAL
;
449 dialog_colors
[DLG_COLOR_FOCUS
] = COLOR_FOCUS
;
450 dialog_colors
[DLG_COLOR_HOT_NORMAL
] = COLOR_HOT_NORMAL
;
451 dialog_colors
[DLG_COLOR_HOT_FOCUS
] = COLOR_HOT_FOCUS
;
452 dialog_colors
[DLG_COLOR_TITLE
] = COLOR_TITLE
;
454 alarm_colors
[DLG_COLOR_NORMAL
] = ERROR_COLOR
;
455 alarm_colors
[DLG_COLOR_FOCUS
] = ERROR_FOCUS
;
456 alarm_colors
[DLG_COLOR_HOT_NORMAL
] = ERROR_HOT_NORMAL
;
457 alarm_colors
[DLG_COLOR_HOT_FOCUS
] = ERROR_HOT_FOCUS
;
458 alarm_colors
[DLG_COLOR_TITLE
] = ERROR_TITLE
;
460 listbox_colors
[DLG_COLOR_NORMAL
] = PMENU_ENTRY_COLOR
;
461 listbox_colors
[DLG_COLOR_FOCUS
] = PMENU_SELECTED_COLOR
;
462 listbox_colors
[DLG_COLOR_HOT_NORMAL
] = PMENU_ENTRY_COLOR
;
463 listbox_colors
[DLG_COLOR_HOT_FOCUS
] = PMENU_SELECTED_COLOR
;
464 listbox_colors
[DLG_COLOR_TITLE
] = PMENU_TITLE_COLOR
;
467 /* --------------------------------------------------------------------------------------------- */
470 dlg_close (WDialog
* h
)
472 widget_set_state (WIDGET (h
), WST_CLOSED
, TRUE
);
475 /* --------------------------------------------------------------------------------------------- */
476 /** Init the process */
479 dlg_init (WDialog
* h
)
481 WGroup
*g
= GROUP (h
);
482 Widget
*wh
= WIDGET (h
);
484 if (top_dlg
!= NULL
&& widget_get_state (WIDGET (top_dlg
->data
), WST_MODAL
))
485 widget_set_state (wh
, WST_MODAL
, TRUE
);
487 /* add dialog to the stack */
488 top_dlg
= g_list_prepend (top_dlg
, h
);
490 /* Initialize dialog manager and widgets */
491 if (widget_get_state (wh
, WST_CONSTRUCT
))
493 if (!widget_get_state (wh
, WST_MODAL
))
494 dialog_switch_add (h
);
496 send_message (h
, NULL
, MSG_INIT
, 0, NULL
);
497 group_default_callback (wh
, NULL
, MSG_INIT
, 0, NULL
);
498 dlg_read_history (h
);
501 /* Select the first widget that takes focus */
502 while (g
->current
!= NULL
&& !widget_is_focusable (g
->current
->data
))
503 group_set_current_widget_next (g
);
505 widget_set_state (wh
, WST_ACTIVE
, TRUE
);
511 /* --------------------------------------------------------------------------------------------- */
514 dlg_process_event (WDialog
* h
, int key
, Gpm_Event
* event
)
519 if (tty_got_interrupt ())
520 dlg_execute_cmd (h
, CK_Cancel
);
525 Widget
*w
= WIDGET (h
);
527 GROUP (h
)->mouse_status
= w
->mouse_handler (w
, event
);
532 dlg_key_event (h
, key
);
537 /* --------------------------------------------------------------------------------------------- */
538 /** Shutdown the dlg_run */
541 dlg_run_done (WDialog
* h
)
543 top_dlg
= g_list_remove (top_dlg
, h
);
545 if (widget_get_state (WIDGET (h
), WST_CLOSED
))
547 send_message (h
, GROUP (h
)->current
== NULL
? NULL
: WIDGET (GROUP (h
)->current
->data
),
549 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
550 dialog_switch_remove (h
);
554 /* --------------------------------------------------------------------------------------------- */
556 * Standard run dialog routine
557 * We have to keep this routine small so that we can duplicate it's
558 * behavior on complex routines like the file routines, this way,
559 * they can call the dlg_process_event without rewriting all the code
563 dlg_run (WDialog
* h
)
566 frontend_dlg_run (h
);
571 /* --------------------------------------------------------------------------------------------- */
574 * Write history to the ${XDG_DATA_HOME}/mc/history file
577 dlg_save_history (WDialog
* h
)
582 if (num_history_items_recorded
== 0) /* this is how to disable */
585 profile
= mc_config_get_full_path (MC_HISTORY_FILE
);
586 i
= open (profile
, O_CREAT
| O_EXCL
, S_IRUSR
| S_IWUSR
);
590 /* Make sure the history is only readable by the user */
591 if (chmod (profile
, S_IRUSR
| S_IWUSR
) != -1 || errno
== ENOENT
)
593 ev_history_load_save_t event_data
;
595 event_data
.cfg
= mc_config_init (profile
, FALSE
);
596 event_data
.receiver
= NULL
;
598 /* get all histories in dialog */
599 mc_event_raise (h
->event_group
, MCEVENT_HISTORY_SAVE
, &event_data
);
601 mc_config_save_file (event_data
.cfg
, NULL
);
602 mc_config_deinit (event_data
.cfg
);
608 /* --------------------------------------------------------------------------------------------- */
611 dlg_get_title (const WDialog
* h
, size_t len
)
618 if (h
->get_title
!= NULL
)
619 t
= h
->get_title (h
, len
);
626 /* --------------------------------------------------------------------------------------------- */