2 Dialog box features module for the Midnight Commander
4 Copyright (C) 1994-2024
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_help (const WDialog
*h
)
128 ev_help_t event_data
= { NULL
, h
->help_ctx
};
130 mc_event_raise (MCEVENT_GROUP_CORE
, "help", &event_data
);
133 /* --------------------------------------------------------------------------------------------- */
136 dlg_execute_cmd (WDialog
*h
, long command
)
138 WGroup
*g
= GROUP (h
);
139 cb_ret_t ret
= MSG_HANDLED
;
141 if (send_message (h
, NULL
, MSG_ACTION
, command
, NULL
) == MSG_HANDLED
)
147 h
->ret_value
= B_ENTER
;
151 h
->ret_value
= B_CANCEL
;
157 group_select_prev_widget (g
);
161 group_select_next_widget (g
);
169 mc_event_raise (MCEVENT_GROUP_CORE
, "suspend", NULL
);
177 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
178 dialog_switch_list ();
180 ret
= MSG_NOT_HANDLED
;
183 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
184 dialog_switch_next ();
186 ret
= MSG_NOT_HANDLED
;
189 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
190 dialog_switch_prev ();
192 ret
= MSG_NOT_HANDLED
;
196 ret
= MSG_NOT_HANDLED
;
202 /* --------------------------------------------------------------------------------------------- */
205 dlg_handle_key (WDialog
*h
, int d_key
)
209 command
= widget_lookup_key (WIDGET (h
), d_key
);
210 if (command
== CK_IgnoreKey
)
211 command
= keybind_lookup_keymap_command (dialog_map
, d_key
);
212 if (command
!= CK_IgnoreKey
)
213 return dlg_execute_cmd (h
, command
);
215 return MSG_NOT_HANDLED
;
218 /* --------------------------------------------------------------------------------------------- */
221 dlg_key_event (WDialog
*h
, int d_key
)
223 Widget
*w
= WIDGET (h
);
224 WGroup
*g
= GROUP (h
);
227 if (g
->widgets
== NULL
)
230 if (g
->current
== NULL
)
231 g
->current
= g
->widgets
;
233 /* TAB used to cycle */
234 if (!widget_get_options (w
, WOP_WANT_TAB
))
238 group_select_next_widget (g
);
241 else if ((d_key
& ~(KEY_M_SHIFT
| KEY_M_CTRL
)) == '\t')
243 group_select_prev_widget (g
);
248 /* first can dlalog handle the key itself */
249 handled
= send_message (h
, NULL
, MSG_KEY
, d_key
, NULL
);
251 if (handled
== MSG_NOT_HANDLED
)
252 handled
= group_default_callback (w
, NULL
, MSG_KEY
, d_key
, NULL
);
254 if (handled
== MSG_NOT_HANDLED
)
255 handled
= dlg_handle_key (h
, d_key
);
258 send_message (h
, NULL
, MSG_POST_KEY
, d_key
, NULL
);
261 /* --------------------------------------------------------------------------------------------- */
264 dlg_handle_mouse_event (Widget
*w
, Gpm_Event
*event
)
266 if (w
->mouse_callback
!= NULL
)
270 mou
= mouse_handle_event (w
, event
);
271 if (mou
!= MOU_UNHANDLED
)
275 return group_handle_mouse_event (w
, event
);
278 /* --------------------------------------------------------------------------------------------- */
281 frontend_dlg_run (WDialog
*h
)
283 Widget
*wh
= WIDGET (h
);
288 /* close opened editors, viewers, etc */
289 if (!widget_get_state (wh
, WST_MODAL
) && mc_global
.midnight_shutdown
)
291 send_message (h
, NULL
, MSG_VALIDATE
, 0, NULL
);
295 while (widget_get_state (wh
, WST_ACTIVE
))
299 if (tty_got_winch ())
300 dialog_change_screen_size ();
305 execute_hooks (idle_hook
);
307 while (widget_get_state (wh
, WST_IDLE
) && is_idle ())
308 send_message (wh
, NULL
, MSG_IDLE
, 0, NULL
);
310 /* Allow terminating the dialog from the idle handler */
311 if (!widget_get_state (wh
, WST_ACTIVE
))
315 widget_update_cursor (wh
);
317 /* Clear interrupt flag */
318 tty_got_interrupt ();
319 d_key
= tty_get_event (&event
, GROUP (h
)->mouse_status
== MOU_REPEAT
, TRUE
);
321 dlg_process_event (h
, d_key
, &event
);
323 if (widget_get_state (wh
, WST_CLOSED
))
324 send_message (h
, NULL
, MSG_VALIDATE
, 0, NULL
);
328 /* --------------------------------------------------------------------------------------------- */
331 dlg_default_destroy (Widget
*w
)
333 WDialog
*h
= DIALOG (w
);
335 /* if some widgets have history, save all histories at one moment here */
336 dlg_save_history (h
);
337 group_default_callback (w
, NULL
, MSG_DESTROY
, 0, NULL
);
338 send_message (w
, NULL
, MSG_DESTROY
, 0, NULL
);
339 mc_event_group_del (h
->event_group
);
340 g_free (h
->event_group
);
346 /* --------------------------------------------------------------------------------------------- */
347 /*** public functions ****************************************************************************/
348 /* --------------------------------------------------------------------------------------------- */
349 /** Default dialog callback */
352 dlg_default_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
357 /* nothing to init in dialog itself */
361 /* we don't want endless loop */
362 widget_idle (w
, FALSE
);
366 /* nothing to deinit in dialog itself */
370 return group_default_callback (w
, sender
, msg
, parm
, data
);
374 /* --------------------------------------------------------------------------------------------- */
377 dlg_default_mouse_callback (Widget
*w
, mouse_msg_t msg
, mouse_event_t
*event
)
381 case MSG_MOUSE_CLICK
:
382 if (event
->y
< 0 || event
->y
>= w
->rect
.lines
|| event
->x
< 0 || event
->x
>= w
->rect
.cols
)
384 DIALOG (w
)->ret_value
= B_CANCEL
;
385 dlg_close (DIALOG (w
));
390 /* return MOU_UNHANDLED */
391 event
->result
.abort
= TRUE
;
396 /* --------------------------------------------------------------------------------------------- */
399 dlg_create (gboolean modal
, int y1
, int x1
, int lines
, int cols
, widget_pos_flags_t pos_flags
,
400 gboolean compact
, const int *colors
, widget_cb_fn callback
,
401 widget_mouse_cb_fn mouse_callback
, const char *help_ctx
, const char *title
)
403 WRect r
= { y1
, x1
, lines
, cols
};
408 new_d
= g_new0 (WDialog
, 1);
411 widget_adjust_position (pos_flags
, &r
);
412 group_init (g
, &r
, callback
!= NULL
? callback
: dlg_default_callback
,
413 mouse_callback
!= NULL
? mouse_callback
: dlg_default_mouse_callback
);
415 w
->pos_flags
= pos_flags
;
416 w
->options
|= WOP_SELECTABLE
| WOP_TOP_SELECT
;
417 w
->state
|= WST_FOCUSED
;
418 /* Temporary hack: dialog doesn't have an owner, own itself. */
421 w
->keymap
= dialog_map
;
423 w
->mouse_handler
= dlg_handle_mouse_event
;
424 w
->mouse
.forced_capture
= mouse_close_dialog
&& (w
->pos_flags
& WPOS_FULLSCREEN
) == 0;
426 w
->destroy
= dlg_default_destroy
;
427 w
->get_colors
= dlg_default_get_colors
;
429 new_d
->colors
= colors
;
430 new_d
->help_ctx
= help_ctx
;
431 new_d
->compact
= compact
;
432 new_d
->data
.p
= NULL
;
436 w
->state
|= WST_MODAL
;
439 WIDGET (frame_new (0, 0, w
->rect
.lines
, w
->rect
.cols
, title
, FALSE
, new_d
->compact
));
440 group_add_widget (g
, new_d
->bg
);
441 frame_set_title (FRAME (new_d
->bg
), title
);
444 /* unique name of event group for this dialog */
445 new_d
->event_group
= g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG
, (void *) new_d
);
450 /* --------------------------------------------------------------------------------------------- */
453 dlg_set_default_colors (void)
455 dialog_colors
[DLG_COLOR_NORMAL
] = COLOR_NORMAL
;
456 dialog_colors
[DLG_COLOR_FOCUS
] = COLOR_FOCUS
;
457 dialog_colors
[DLG_COLOR_HOT_NORMAL
] = COLOR_HOT_NORMAL
;
458 dialog_colors
[DLG_COLOR_HOT_FOCUS
] = COLOR_HOT_FOCUS
;
459 dialog_colors
[DLG_COLOR_TITLE
] = COLOR_TITLE
;
461 alarm_colors
[DLG_COLOR_NORMAL
] = ERROR_COLOR
;
462 alarm_colors
[DLG_COLOR_FOCUS
] = ERROR_FOCUS
;
463 alarm_colors
[DLG_COLOR_HOT_NORMAL
] = ERROR_HOT_NORMAL
;
464 alarm_colors
[DLG_COLOR_HOT_FOCUS
] = ERROR_HOT_FOCUS
;
465 alarm_colors
[DLG_COLOR_TITLE
] = ERROR_TITLE
;
467 listbox_colors
[DLG_COLOR_NORMAL
] = PMENU_ENTRY_COLOR
;
468 listbox_colors
[DLG_COLOR_FOCUS
] = PMENU_SELECTED_COLOR
;
469 listbox_colors
[DLG_COLOR_HOT_NORMAL
] = PMENU_ENTRY_COLOR
;
470 listbox_colors
[DLG_COLOR_HOT_FOCUS
] = PMENU_SELECTED_COLOR
;
471 listbox_colors
[DLG_COLOR_TITLE
] = PMENU_TITLE_COLOR
;
474 /* --------------------------------------------------------------------------------------------- */
477 dlg_close (WDialog
*h
)
479 widget_set_state (WIDGET (h
), WST_CLOSED
, TRUE
);
482 /* --------------------------------------------------------------------------------------------- */
483 /** Init the process */
486 dlg_init (WDialog
*h
)
488 WGroup
*g
= GROUP (h
);
489 Widget
*wh
= WIDGET (h
);
491 if (top_dlg
!= NULL
&& widget_get_state (WIDGET (top_dlg
->data
), WST_MODAL
))
492 widget_set_state (wh
, WST_MODAL
, TRUE
);
494 /* add dialog to the stack */
495 top_dlg
= g_list_prepend (top_dlg
, h
);
497 /* Initialize dialog manager and widgets */
498 if (widget_get_state (wh
, WST_CONSTRUCT
))
500 if (!widget_get_state (wh
, WST_MODAL
))
501 dialog_switch_add (h
);
503 send_message (h
, NULL
, MSG_INIT
, 0, NULL
);
504 group_default_callback (wh
, NULL
, MSG_INIT
, 0, NULL
);
505 dlg_read_history (h
);
508 /* Select the first widget that takes focus */
509 while (g
->current
!= NULL
&& !widget_is_focusable (g
->current
->data
))
510 group_set_current_widget_next (g
);
512 widget_set_state (wh
, WST_ACTIVE
, TRUE
);
518 /* --------------------------------------------------------------------------------------------- */
521 dlg_process_event (WDialog
*h
, int key
, Gpm_Event
*event
)
526 if (tty_got_interrupt ())
527 dlg_execute_cmd (h
, CK_Cancel
);
532 Widget
*w
= WIDGET (h
);
534 GROUP (h
)->mouse_status
= w
->mouse_handler (w
, event
);
539 dlg_key_event (h
, key
);
544 /* --------------------------------------------------------------------------------------------- */
545 /** Shutdown the dlg_run */
548 dlg_run_done (WDialog
*h
)
550 top_dlg
= g_list_remove (top_dlg
, h
);
552 if (widget_get_state (WIDGET (h
), WST_CLOSED
))
554 send_message (h
, GROUP (h
)->current
== NULL
? NULL
: WIDGET (GROUP (h
)->current
->data
),
556 if (!widget_get_state (WIDGET (h
), WST_MODAL
))
557 dialog_switch_remove (h
);
561 /* --------------------------------------------------------------------------------------------- */
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
573 frontend_dlg_run (h
);
578 /* --------------------------------------------------------------------------------------------- */
581 * Write history to the ${XDG_DATA_HOME}/mc/history file
584 dlg_save_history (WDialog
*h
)
589 if (num_history_items_recorded
== 0) /* this is how to disable */
592 profile
= mc_config_get_full_path (MC_HISTORY_FILE
);
593 i
= open (profile
, O_CREAT
| O_EXCL
, S_IRUSR
| S_IWUSR
);
597 /* Make sure the history is only readable by the user */
598 if (chmod (profile
, S_IRUSR
| S_IWUSR
) != -1 || errno
== ENOENT
)
600 ev_history_load_save_t event_data
;
602 event_data
.cfg
= mc_config_init (profile
, FALSE
);
603 event_data
.receiver
= NULL
;
605 /* get all histories in dialog */
606 mc_event_raise (h
->event_group
, MCEVENT_HISTORY_SAVE
, &event_data
);
608 mc_config_save_file (event_data
.cfg
, NULL
);
609 mc_config_deinit (event_data
.cfg
);
615 /* --------------------------------------------------------------------------------------------- */
618 dlg_get_title (const WDialog
*h
, size_t len
)
625 if (h
->get_title
!= NULL
)
626 t
= h
->get_title (h
, len
);
633 /* --------------------------------------------------------------------------------------------- */