Refactoring of change of current widget.
[midnight-commander.git] / lib / widget / dialog.c
blob2715a7f3d5ba79fdc955a0479db2ec0fe8a10ca0
1 /*
2 Dialog box features module for the Midnight Commander
4 Copyright (C) 1994-2014
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/>.
23 /** \file dialog.c
24 * \brief Source: dialog box features module
27 #include <config.h>
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>
37 #include "lib/global.h"
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() */
46 #include "lib/widget.h"
47 #include "lib/widget/mouse.h"
49 /*** global variables ****************************************************************************/
51 /* Color styles for normal and error dialogs */
52 dlg_colors_t dialog_colors;
53 dlg_colors_t alarm_colors;
54 dlg_colors_t listbox_colors;
56 /* Primitive way to check if the the current dialog is our dialog */
57 /* This is needed by async routines like load_prompt */
58 GList *top_dlg = NULL;
60 /* A hook list for idle events */
61 hook_t *idle_hook = NULL;
63 /* If set then dialogs just clean the screen when refreshing, else */
64 /* they do a complete refresh, refreshing all the parts of the program */
65 int fast_refresh = 0;
67 /* left click outside of dialog closes it */
68 int mouse_close_dialog = 0;
70 const global_keymap_t *dialog_map = NULL;
72 /*** file scope macro definitions ****************************************************************/
74 /*** file scope type declarations ****************************************************************/
76 /** What to do if the requested widget doesn't take focus */
77 typedef enum
79 SELECT_NEXT, /* go the the next widget */
80 SELECT_PREV, /* go the the previous widget */
81 SELECT_EXACT /* use current widget */
82 } select_dir_t;
84 /* Control widget positions in dialog */
85 typedef struct
87 int shift_x;
88 int scale_x;
89 int shift_y;
90 int scale_y;
91 } widget_shift_scale_t;
93 /*** file scope variables ************************************************************************/
95 /* --------------------------------------------------------------------------------------------- */
96 /*** file scope functions ************************************************************************/
97 /* --------------------------------------------------------------------------------------------- */
99 static GList *
100 dlg_get_next_or_prev_of (const GList * list, gboolean next)
102 GList *l = NULL;
104 if (list != NULL && list->data != NULL)
106 const WDialog *owner = CONST_WIDGET (list->data)->owner;
108 if (owner != NULL)
110 if (next)
112 l = g_list_next (list);
113 if (l == NULL)
114 l = owner->widgets;
116 else
118 l = g_list_previous (list);
119 if (l == NULL)
120 l = g_list_last (owner->widgets);
125 return l;
128 /* --------------------------------------------------------------------------------------------- */
130 * broadcast a message to all the widgets in a dialog that have
131 * the options set to flags. If flags is zero, the message is sent
132 * to all widgets.
135 static void
136 dlg_broadcast_msg_to (WDialog * h, widget_msg_t msg, gboolean reverse, widget_options_t flags)
138 GList *p, *first;
140 if (h->widgets == NULL)
141 return;
143 if (h->current == NULL)
144 h->current = h->widgets;
146 p = dlg_get_next_or_prev_of (h->current, !reverse);
147 first = p;
151 Widget *w = WIDGET (p->data);
153 p = dlg_get_next_or_prev_of (p, !reverse);
155 if ((flags == 0) || ((flags & w->options) != 0))
156 send_message (w, NULL, msg, 0, NULL);
158 while (first != p);
161 /* --------------------------------------------------------------------------------------------- */
164 * Read histories from the ${XDG_CACHE_HOME}/mc/history file
166 static void
167 dlg_read_history (WDialog * h)
169 char *profile;
170 ev_history_load_save_t event_data;
172 if (num_history_items_recorded == 0) /* this is how to disable */
173 return;
175 profile = mc_config_get_full_path (MC_HISTORY_FILE);
176 event_data.cfg = mc_config_init (profile, TRUE);
177 event_data.receiver = NULL;
179 /* create all histories in dialog */
180 mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data);
182 mc_config_deinit (event_data.cfg);
183 g_free (profile);
186 /* --------------------------------------------------------------------------------------------- */
188 static gboolean
189 dlg_unfocus (WDialog * h)
191 /* we can unfocus disabled widget */
192 if (h->current != NULL)
194 Widget *wh = WIDGET (h);
196 if ((widget_get_state (wh, WST_CONSTRUCT) || widget_get_state (wh, WST_ACTIVE))
197 && widget_set_state (WIDGET (h->current->data), WST_FOCUSED, FALSE) == MSG_HANDLED)
198 return TRUE;
201 return FALSE;
204 /* --------------------------------------------------------------------------------------------- */
206 static int
207 dlg_find_widget_callback (const void *a, const void *b)
209 const Widget *w = CONST_WIDGET (a);
210 const widget_cb_fn f = b;
212 return (w->callback == f) ? 0 : 1;
215 /* --------------------------------------------------------------------------------------------- */
218 * Put widget on top or bottom of Z-order.
220 static void
221 dlg_reorder_widgets (GList * l, gboolean set_top)
223 WDialog *h = WIDGET (l->data)->owner;
225 h->widgets = g_list_remove_link (h->widgets, l);
226 if (set_top)
227 h->widgets = g_list_concat (h->widgets, l);
228 else
229 h->widgets = g_list_concat (l, h->widgets);
232 /* --------------------------------------------------------------------------------------------- */
234 * Try to select another widget. If forward is set, follow tab order.
235 * Otherwise go to the previous widget.
238 static void
239 do_select_widget (WDialog * h, GList * w, select_dir_t dir)
241 Widget *w0 = WIDGET (h->current->data);
243 if (!dlg_unfocus (h))
244 return;
246 h->current = w;
250 if (dlg_focus (h))
251 break;
253 switch (dir)
255 case SELECT_EXACT:
256 h->current = g_list_find (h->widgets, w0);
257 if (dlg_focus (h))
258 return;
259 /* try find another widget that can take focus */
260 dir = SELECT_NEXT;
261 /* fallthrough */
262 case SELECT_NEXT:
263 dlg_set_current_widget_next (h);
264 break;
265 case SELECT_PREV:
266 dlg_set_current_widget_prev (h);
267 break;
268 default:
269 break;
272 while (h->current != w);
274 if (widget_get_options (WIDGET (h->current->data), WOP_TOP_SELECT))
275 dlg_reorder_widgets (h->current, TRUE);
277 if (widget_overlapped (w0, WIDGET (h->current->data)))
278 widget_set_state (WIDGET (h->current->data), WST_FOCUSED, TRUE);
281 /* --------------------------------------------------------------------------------------------- */
283 static void
284 refresh_cmd (void)
286 #ifdef HAVE_SLANG
287 tty_touch_screen ();
288 mc_refresh ();
289 #else
290 /* Use this if the refreshes fail */
291 clr_scr ();
292 repaint_screen ();
293 #endif /* HAVE_SLANG */
296 /* --------------------------------------------------------------------------------------------- */
298 static cb_ret_t
299 dlg_execute_cmd (WDialog * h, long command)
301 cb_ret_t ret = MSG_HANDLED;
302 switch (command)
304 case CK_Ok:
305 h->ret_value = B_ENTER;
306 dlg_stop (h);
307 break;
308 case CK_Cancel:
309 h->ret_value = B_CANCEL;
310 dlg_stop (h);
311 break;
313 case CK_Up:
314 case CK_Left:
315 dlg_one_up (h);
316 break;
317 case CK_Down:
318 case CK_Right:
319 dlg_one_down (h);
320 break;
322 case CK_Help:
324 ev_help_t event_data = { NULL, h->help_ctx };
325 mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
327 break;
329 case CK_Suspend:
330 mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
331 refresh_cmd ();
332 break;
333 case CK_Refresh:
334 refresh_cmd ();
335 break;
337 case CK_ScreenList:
338 if (!widget_get_state (WIDGET (h), WST_MODAL))
339 dialog_switch_list ();
340 else
341 ret = MSG_NOT_HANDLED;
342 break;
343 case CK_ScreenNext:
344 if (!widget_get_state (WIDGET (h), WST_MODAL))
345 dialog_switch_next ();
346 else
347 ret = MSG_NOT_HANDLED;
348 break;
349 case CK_ScreenPrev:
350 if (!widget_get_state (WIDGET (h), WST_MODAL))
351 dialog_switch_prev ();
352 else
353 ret = MSG_NOT_HANDLED;
354 break;
356 default:
357 ret = MSG_NOT_HANDLED;
360 return ret;
363 /* --------------------------------------------------------------------------------------------- */
365 static cb_ret_t
366 dlg_handle_key (WDialog * h, int d_key)
368 long command;
370 command = keybind_lookup_keymap_command (dialog_map, d_key);
372 if (command == CK_IgnoreKey)
373 return MSG_NOT_HANDLED;
375 if (send_message (h, NULL, MSG_ACTION, command, NULL) == MSG_HANDLED
376 || dlg_execute_cmd (h, command) == MSG_HANDLED)
377 return MSG_HANDLED;
379 return MSG_NOT_HANDLED;
382 /* --------------------------------------------------------------------------------------------- */
384 * This is the low-level mouse handler.
385 * It receives a Gpm_Event event and translates it into a higher level protocol.
387 static int
388 dlg_mouse_translator (Gpm_Event * event, Widget * w)
390 mouse_event_t me;
392 me = mouse_translate_event (w, event);
394 return mouse_process_event (w, &me);
397 /* --------------------------------------------------------------------------------------------- */
399 static int
400 dlg_mouse_event (WDialog * h, Gpm_Event * event)
402 Widget *wh = WIDGET (h);
404 GList *p;
406 /* close the dialog by mouse left click out of dialog area */
407 if (mouse_close_dialog && (wh->pos_flags & WPOS_FULLSCREEN) == 0
408 && ((event->buttons & GPM_B_LEFT) != 0) && ((event->type & GPM_DOWN) != 0)
409 && !mouse_global_in_widget (event, wh))
411 h->ret_value = B_CANCEL;
412 dlg_stop (h);
413 return MOU_NORMAL;
416 if (wh->mouse_callback != NULL)
418 int mou;
420 mou = dlg_mouse_translator (event, wh);
421 if (mou != MOU_UNHANDLED)
422 return mou;
425 /* send the event to widgets in reverse Z-order */
426 p = g_list_last (h->widgets);
429 Widget *w = WIDGET (p->data);
431 if (!widget_get_state (w, WST_DISABLED) && w->mouse_callback != NULL)
433 /* put global cursor position to the widget */
434 int ret;
436 ret = dlg_mouse_translator (event, w);
437 if (ret != MOU_UNHANDLED)
438 return ret;
441 p = g_list_previous (p);
443 while (p != NULL);
445 return MOU_UNHANDLED;
448 /* --------------------------------------------------------------------------------------------- */
450 static cb_ret_t
451 dlg_try_hotkey (WDialog * h, int d_key)
453 GList *hot_cur;
454 Widget *current;
455 cb_ret_t handled;
456 int c;
458 if (h->widgets == NULL)
459 return MSG_NOT_HANDLED;
461 if (h->current == NULL)
462 h->current = h->widgets;
465 * Explanation: we don't send letter hotkeys to other widgets if
466 * the currently selected widget is an input line
469 current = WIDGET (h->current->data);
471 if (widget_get_state (current, WST_DISABLED))
472 return MSG_NOT_HANDLED;
474 if (widget_get_options (current, WOP_IS_INPUT))
476 /* skip ascii control characters, anything else can valid character in
477 * some encoding */
478 if (d_key >= 32 && d_key < 256)
479 return MSG_NOT_HANDLED;
482 /* If it's an alt key, send the message */
483 c = d_key & ~ALT (0);
484 if (d_key & ALT (0) && g_ascii_isalpha (c))
485 d_key = g_ascii_tolower (c);
487 handled = MSG_NOT_HANDLED;
488 if (widget_get_options (current, WOP_WANT_HOTKEY))
489 handled = send_message (current, NULL, MSG_HOTKEY, d_key, NULL);
491 /* If not used, send hotkey to other widgets */
492 if (handled == MSG_HANDLED)
493 return MSG_HANDLED;
495 hot_cur = dlg_get_widget_next_of (h->current);
497 /* send it to all widgets */
498 while (h->current != hot_cur && handled == MSG_NOT_HANDLED)
500 current = WIDGET (hot_cur->data);
502 if (widget_get_options (current, WOP_WANT_HOTKEY)
503 && !widget_get_state (current, WST_DISABLED))
504 handled = send_message (current, NULL, MSG_HOTKEY, d_key, NULL);
506 if (handled == MSG_NOT_HANDLED)
507 hot_cur = dlg_get_widget_next_of (hot_cur);
510 if (handled == MSG_HANDLED)
511 do_select_widget (h, hot_cur, SELECT_EXACT);
513 return handled;
516 /* --------------------------------------------------------------------------------------------- */
518 static void
519 dlg_key_event (WDialog * h, int d_key)
521 cb_ret_t handled;
523 if (h->widgets == NULL)
524 return;
526 if (h->current == NULL)
527 h->current = h->widgets;
529 /* TAB used to cycle */
530 if (!widget_get_options (WIDGET (h), WOP_WANT_TAB))
532 if (d_key == '\t')
534 dlg_one_down (h);
535 return;
537 else if ((d_key & ~(KEY_M_SHIFT | KEY_M_CTRL)) == '\t')
539 dlg_one_up (h);
540 return;
544 /* first can dlg_callback handle the key */
545 handled = send_message (h, NULL, MSG_KEY, d_key, NULL);
547 /* next try the hotkey */
548 if (handled == MSG_NOT_HANDLED)
549 handled = dlg_try_hotkey (h, d_key);
551 if (handled == MSG_HANDLED)
552 send_message (h, NULL, MSG_HOTKEY_HANDLED, 0, NULL);
553 else
554 /* not used - then try widget_callback */
555 handled = send_message (h->current->data, NULL, MSG_KEY, d_key, NULL);
557 /* not used- try to use the unhandled case */
558 if (handled == MSG_NOT_HANDLED)
559 handled = send_message (h, NULL, MSG_UNHANDLED_KEY, d_key, NULL);
561 if (handled == MSG_NOT_HANDLED)
562 handled = dlg_handle_key (h, d_key);
564 (void) handled;
565 send_message (h, NULL, MSG_POST_KEY, d_key, NULL);
568 /* --------------------------------------------------------------------------------------------- */
570 static void
571 frontend_dlg_run (WDialog * h)
573 Widget *wh = WIDGET (h);
574 Gpm_Event event;
576 event.x = -1;
578 /* close opened editors, viewers, etc */
579 if (!widget_get_state (wh, WST_MODAL) && mc_global.midnight_shutdown)
581 send_message (h, NULL, MSG_VALIDATE, 0, NULL);
582 return;
585 while (widget_get_state (wh, WST_ACTIVE))
587 int d_key;
589 if (mc_global.tty.winch_flag != 0)
590 dialog_change_screen_size ();
592 if (is_idle ())
594 if (idle_hook)
595 execute_hooks (idle_hook);
597 while (widget_get_state (wh, WST_IDLE) && is_idle ())
598 send_message (wh, NULL, MSG_IDLE, 0, NULL);
600 /* Allow terminating the dialog from the idle handler */
601 if (!widget_get_state (wh, WST_ACTIVE))
602 break;
605 update_cursor (h);
607 /* Clear interrupt flag */
608 tty_got_interrupt ();
609 d_key = tty_get_event (&event, h->mouse_status == MOU_REPEAT, TRUE);
611 dlg_process_event (h, d_key, &event);
613 if (widget_get_state (wh, WST_CLOSED))
614 send_message (h, NULL, MSG_VALIDATE, 0, NULL);
618 /* --------------------------------------------------------------------------------------------- */
620 static int
621 dlg_find_widget_by_id (gconstpointer a, gconstpointer b)
623 const Widget *w = CONST_WIDGET (a);
624 unsigned long id = GPOINTER_TO_UINT (b);
626 return w->id == id ? 0 : 1;
629 /* --------------------------------------------------------------------------------------------- */
630 static void
631 dlg_widget_set_position (gpointer data, gpointer user_data)
633 /* there are, mainly, 2 generally possible situations:
634 * 1. control sticks to one side - it should be moved
635 * 2. control sticks to two sides of one direction - it should be sized
638 Widget *c = WIDGET (data);
639 Widget *wh = WIDGET (c->owner);
640 const widget_shift_scale_t *wss = (const widget_shift_scale_t *) user_data;
641 int x = c->x;
642 int y = c->y;
643 int cols = c->cols;
644 int lines = c->lines;
646 if ((c->pos_flags & WPOS_CENTER_HORZ) != 0)
647 x = wh->x + (wh->cols - c->cols) / 2;
648 else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0 && (c->pos_flags & WPOS_KEEP_RIGHT) != 0)
650 x += wss->shift_x;
651 cols += wss->scale_x;
653 else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0)
654 x += wss->shift_x;
655 else if ((c->pos_flags & WPOS_KEEP_RIGHT) != 0)
656 x += wss->shift_x + wss->scale_x;
658 if ((c->pos_flags & WPOS_CENTER_VERT) != 0)
659 y = wh->y + (wh->lines - c->lines) / 2;
660 else if ((c->pos_flags & WPOS_KEEP_TOP) != 0 && (c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
662 y += wss->shift_y;
663 lines += wss->scale_y;
665 else if ((c->pos_flags & WPOS_KEEP_TOP) != 0)
666 y += wss->shift_y;
667 else if ((c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
668 y += wss->shift_y + wss->scale_y;
670 widget_set_size (c, y, x, lines, cols);
673 /* --------------------------------------------------------------------------------------------- */
675 static void
676 dlg_adjust_position (widget_pos_flags_t pos_flags, int *y, int *x, int *lines, int *cols)
678 if ((pos_flags & WPOS_FULLSCREEN) != 0)
680 *y = 0;
681 *x = 0;
682 *lines = LINES;
683 *cols = COLS;
685 else
687 if ((pos_flags & WPOS_CENTER_HORZ) != 0)
688 *x = (COLS - *cols) / 2;
690 if ((pos_flags & WPOS_CENTER_VERT) != 0)
691 *y = (LINES - *lines) / 2;
693 if ((pos_flags & WPOS_TRYUP) != 0)
695 if (*y > 3)
696 *y -= 2;
697 else if (*y == 3)
698 *y = 2;
703 /* --------------------------------------------------------------------------------------------- */
704 /*** public functions ****************************************************************************/
705 /* --------------------------------------------------------------------------------------------- */
707 /** Clean the dialog area, draw the frame and the title */
708 void
709 dlg_default_repaint (WDialog * h)
711 Widget *wh = WIDGET (h);
713 int space;
715 if (!widget_get_state (wh, WST_ACTIVE))
716 return;
718 space = h->compact ? 0 : 1;
720 tty_setcolor (h->color[DLG_COLOR_NORMAL]);
721 dlg_erase (h);
722 tty_draw_box (wh->y + space, wh->x + space, wh->lines - 2 * space, wh->cols - 2 * space, FALSE);
724 if (h->title != NULL)
726 tty_setcolor (h->color[DLG_COLOR_TITLE]);
727 widget_move (h, space, (wh->cols - str_term_width1 (h->title)) / 2);
728 tty_print_string (h->title);
732 /* --------------------------------------------------------------------------------------------- */
733 /** this function allows to set dialog position */
735 void
736 dlg_set_position (WDialog * h, int y, int x, int lines, int cols)
738 Widget *wh = WIDGET (h);
739 widget_shift_scale_t wss;
741 /* save old positions, will be used to reposition childs */
742 int ox, oy, oc, ol;
744 /* save old positions, will be used to reposition childs */
745 ox = wh->x;
746 oy = wh->y;
747 oc = wh->cols;
748 ol = wh->lines;
750 wh->x = x;
751 wh->y = y;
752 wh->lines = lines;
753 wh->cols = cols;
755 /* dialog is empty */
756 if (h->widgets == NULL)
757 return;
759 if (h->current == NULL)
760 h->current = h->widgets;
762 /* values by which controls should be moved */
763 wss.shift_x = wh->x - ox;
764 wss.scale_x = wh->cols - oc;
765 wss.shift_y = wh->y - oy;
766 wss.scale_y = wh->lines - ol;
768 if (wss.shift_x != 0 || wss.shift_y != 0 || wss.scale_x != 0 || wss.scale_y != 0)
769 g_list_foreach (h->widgets, dlg_widget_set_position, &wss);
772 /* --------------------------------------------------------------------------------------------- */
773 /** Set dialog size and position */
775 void
776 dlg_set_size (WDialog * h, int lines, int cols)
778 int x = 0, y = 0;
780 dlg_adjust_position (WIDGET (h)->pos_flags, &y, &x, &lines, &cols);
781 dlg_set_position (h, y, x, lines, cols);
784 /* --------------------------------------------------------------------------------------------- */
785 /** Default dialog callback */
787 cb_ret_t
788 dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
790 WDialog *h = DIALOG (w);
792 (void) sender;
793 (void) parm;
794 (void) data;
796 switch (msg)
798 case MSG_DRAW:
799 if (h->color != NULL)
801 dlg_default_repaint (h);
802 return MSG_HANDLED;
804 return MSG_NOT_HANDLED;
806 case MSG_IDLE:
807 /* we don't want endless loop */
808 widget_idle (w, FALSE);
809 return MSG_HANDLED;
811 case MSG_RESIZE:
812 /* this is default resizing mechanism */
813 /* the main idea of this code is to resize dialog
814 according to flags (if any of flags require automatic
815 resizing, like WPOS_CENTER, end after that reposition
816 controls in dialog according to flags of widget) */
817 dlg_set_size (h, w->lines, w->cols);
818 return MSG_HANDLED;
820 default:
821 break;
824 return MSG_NOT_HANDLED;
827 /* --------------------------------------------------------------------------------------------- */
829 WDialog *
830 dlg_create (gboolean modal, int y1, int x1, int lines, int cols, widget_pos_flags_t pos_flags,
831 gboolean compact, const int *colors, widget_cb_fn callback,
832 widget_mouse_cb_fn mouse_callback, const char *help_ctx, const char *title)
834 WDialog *new_d;
835 Widget *w;
837 new_d = g_new0 (WDialog, 1);
838 w = WIDGET (new_d);
839 dlg_adjust_position (pos_flags, &y1, &x1, &lines, &cols);
840 widget_init (w, y1, x1, lines, cols, (callback != NULL) ? callback : dlg_default_callback,
841 mouse_callback);
842 w->pos_flags = pos_flags;
843 w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
845 w->state |= WST_CONSTRUCT | WST_FOCUSED;
846 if (modal)
847 w->state |= WST_MODAL;
849 new_d->color = colors;
850 new_d->help_ctx = help_ctx;
851 new_d->compact = compact;
852 new_d->data = NULL;
854 new_d->mouse_status = MOU_UNHANDLED;
856 /* Strip existing spaces, add one space before and after the title */
857 if (title != NULL && *title != '\0')
859 char *t;
861 t = g_strstrip (g_strdup (title));
862 if (*t != '\0')
863 new_d->title = g_strdup_printf (" %s ", t);
864 g_free (t);
867 /* unique name of event group for this dialog */
868 new_d->event_group = g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG, (void *) new_d);
870 return new_d;
873 /* --------------------------------------------------------------------------------------------- */
875 void
876 dlg_set_default_colors (void)
878 dialog_colors[DLG_COLOR_NORMAL] = COLOR_NORMAL;
879 dialog_colors[DLG_COLOR_FOCUS] = COLOR_FOCUS;
880 dialog_colors[DLG_COLOR_HOT_NORMAL] = COLOR_HOT_NORMAL;
881 dialog_colors[DLG_COLOR_HOT_FOCUS] = COLOR_HOT_FOCUS;
882 dialog_colors[DLG_COLOR_TITLE] = COLOR_TITLE;
884 alarm_colors[DLG_COLOR_NORMAL] = ERROR_COLOR;
885 alarm_colors[DLG_COLOR_FOCUS] = ERROR_FOCUS;
886 alarm_colors[DLG_COLOR_HOT_NORMAL] = ERROR_HOT_NORMAL;
887 alarm_colors[DLG_COLOR_HOT_FOCUS] = ERROR_HOT_FOCUS;
888 alarm_colors[DLG_COLOR_TITLE] = ERROR_TITLE;
890 listbox_colors[DLG_COLOR_NORMAL] = PMENU_ENTRY_COLOR;
891 listbox_colors[DLG_COLOR_FOCUS] = PMENU_SELECTED_COLOR;
892 listbox_colors[DLG_COLOR_HOT_NORMAL] = PMENU_ENTRY_COLOR;
893 listbox_colors[DLG_COLOR_HOT_FOCUS] = PMENU_SELECTED_COLOR;
894 listbox_colors[DLG_COLOR_TITLE] = PMENU_TITLE_COLOR;
897 /* --------------------------------------------------------------------------------------------- */
899 void
900 dlg_erase (WDialog * h)
902 Widget *wh = WIDGET (h);
904 if (wh != NULL && widget_get_state (wh, WST_ACTIVE))
905 tty_fill_region (wh->y, wh->x, wh->lines, wh->cols, ' ');
908 /* --------------------------------------------------------------------------------------------- */
910 * Insert widget to dialog before requested widget. Make the widget current. Return widget ID.
913 unsigned long
914 add_widget_autopos (WDialog * h, void *w, widget_pos_flags_t pos_flags, const void *before)
916 Widget *wh = WIDGET (h);
917 Widget *widget;
919 /* Don't accept 0 widgets */
920 if (w == NULL)
921 abort ();
923 widget = WIDGET (w);
925 if ((pos_flags & WPOS_CENTER_HORZ) != 0)
926 widget->x = (wh->cols - widget->cols) / 2;
927 widget->x += wh->x;
929 if ((pos_flags & WPOS_CENTER_VERT) != 0)
930 widget->y = (wh->lines - widget->lines) / 2;
931 widget->y += wh->y;
933 widget->owner = h;
934 widget->pos_flags = pos_flags;
935 widget->id = h->widget_id++;
937 if (h->widgets == NULL || before == NULL)
939 h->widgets = g_list_append (h->widgets, widget);
940 h->current = g_list_last (h->widgets);
942 else
944 GList *b;
946 b = g_list_find (h->widgets, before);
948 /* don't accept widget not from dialog. This shouldn't happen */
949 if (b == NULL)
950 abort ();
952 b = g_list_next (b);
953 h->widgets = g_list_insert_before (h->widgets, b, widget);
954 if (b != NULL)
955 h->current = g_list_previous (b);
956 else
957 h->current = g_list_last (h->widgets);
960 /* widget has been added in runtime */
961 if (widget_get_state (wh, WST_ACTIVE))
963 send_message (widget, NULL, MSG_INIT, 0, NULL);
964 widget_set_state (widget, WST_FOCUSED, TRUE);
967 return widget->id;
970 /* --------------------------------------------------------------------------------------------- */
971 /** wrapper to simply add lefttop positioned controls */
973 unsigned long
974 add_widget (WDialog * h, void *w)
976 return add_widget_autopos (h, w, WPOS_KEEP_DEFAULT,
977 h->current != NULL ? h->current->data : NULL);
980 /* --------------------------------------------------------------------------------------------- */
982 unsigned long
983 add_widget_before (WDialog * h, void *w, void *before)
985 return add_widget_autopos (h, w, WPOS_KEEP_DEFAULT, before);
988 /* --------------------------------------------------------------------------------------------- */
990 /** delete widget from dialog */
991 void
992 del_widget (void *w)
994 WDialog *h;
995 GList *d;
997 /* Don't accept NULL widget. This shouldn't happen */
998 if (w == NULL)
999 abort ();
1001 h = WIDGET (w)->owner;
1003 d = g_list_find (h->widgets, w);
1004 if (d == h->current)
1005 dlg_set_current_widget_next (h);
1007 h->widgets = g_list_remove_link (h->widgets, d);
1008 send_message (d->data, NULL, MSG_DESTROY, 0, NULL);
1009 g_free (d->data);
1010 g_list_free_1 (d);
1012 /* widget has been deleted in runtime */
1013 if (widget_get_state (WIDGET (h), WST_ACTIVE))
1015 dlg_redraw (h);
1016 dlg_focus (h);
1020 /* --------------------------------------------------------------------------------------------- */
1022 void
1023 do_refresh (void)
1025 GList *d = top_dlg;
1027 if (fast_refresh)
1029 if ((d != NULL) && (d->data != NULL))
1030 dlg_redraw (DIALOG (d->data));
1032 else
1034 /* Search first fullscreen dialog */
1035 for (; d != NULL; d = g_list_next (d))
1036 if (d->data != NULL && (WIDGET (d->data)->pos_flags & WPOS_FULLSCREEN) != 0)
1037 break;
1038 /* back to top dialog */
1039 for (; d != NULL; d = g_list_previous (d))
1040 if (d->data != NULL)
1041 dlg_redraw (DIALOG (d->data));
1045 /* --------------------------------------------------------------------------------------------- */
1046 /** broadcast a message to all the widgets in a dialog */
1048 void
1049 dlg_broadcast_msg (WDialog * h, widget_msg_t msg)
1051 dlg_broadcast_msg_to (h, msg, FALSE, 0);
1054 /* --------------------------------------------------------------------------------------------- */
1056 gboolean
1057 dlg_focus (WDialog * h)
1059 /* cannot focus disabled widget */
1060 if (h->current != NULL)
1062 Widget *wh = WIDGET (h);
1064 if (widget_get_state (wh, WST_CONSTRUCT) || widget_get_state (wh, WST_ACTIVE))
1066 Widget *current = WIDGET (h->current->data);
1068 if (widget_get_options (current, WOP_SELECTABLE)
1069 && !widget_get_state (current, WST_DISABLED)
1070 && widget_set_state (current, WST_FOCUSED, TRUE) == MSG_HANDLED)
1071 return TRUE;
1075 return FALSE;
1078 /* --------------------------------------------------------------------------------------------- */
1079 /** Find the widget with the given callback in the dialog h */
1081 Widget *
1082 find_widget_type (const WDialog * h, widget_cb_fn callback)
1084 GList *w;
1086 w = g_list_find_custom (h->widgets, (gconstpointer) callback, dlg_find_widget_callback);
1088 return (w == NULL) ? NULL : WIDGET (w->data);
1091 /* --------------------------------------------------------------------------------------------- */
1092 /** Find the widget with the given id */
1094 Widget *
1095 dlg_find_by_id (const WDialog * h, unsigned long id)
1097 GList *w;
1099 w = g_list_find_custom (h->widgets, GUINT_TO_POINTER (id), dlg_find_widget_by_id);
1100 return w != NULL ? WIDGET (w->data) : NULL;
1103 /* --------------------------------------------------------------------------------------------- */
1104 /** Find the widget with the given id in the dialog h and select it */
1106 void
1107 dlg_select_by_id (const WDialog * h, unsigned long id)
1109 Widget *w;
1111 w = dlg_find_by_id (h, id);
1112 if (w != NULL)
1113 dlg_select_widget (w);
1116 /* --------------------------------------------------------------------------------------------- */
1118 * Try to select widget in the dialog.
1121 void
1122 dlg_select_widget (void *w)
1124 Widget *widget = WIDGET (w);
1125 WDialog *h = widget->owner;
1127 do_select_widget (h, g_list_find (h->widgets, widget), SELECT_EXACT);
1130 /* --------------------------------------------------------------------------------------------- */
1132 * Set widget at bottom of widget list.
1135 void
1136 dlg_set_bottom_widget (void *w)
1138 Widget *widget = WIDGET (w);
1139 WDialog *h = widget->owner;
1141 dlg_reorder_widgets (g_list_find (h->widgets, widget), FALSE);
1144 /* --------------------------------------------------------------------------------------------- */
1145 /** Try to select previous widget in the tab order */
1147 void
1148 dlg_one_up (WDialog * h)
1150 if (h->widgets != NULL)
1151 do_select_widget (h, dlg_get_widget_prev_of (h->current), SELECT_PREV);
1154 /* --------------------------------------------------------------------------------------------- */
1155 /** Try to select next widget in the tab order */
1157 void
1158 dlg_one_down (WDialog * h)
1160 if (h->widgets != NULL)
1161 do_select_widget (h, dlg_get_widget_next_of (h->current), SELECT_NEXT);
1164 /* --------------------------------------------------------------------------------------------- */
1166 void
1167 update_cursor (WDialog * h)
1169 GList *p = h->current;
1171 if (p != NULL && widget_get_state (WIDGET (h), WST_ACTIVE))
1173 Widget *w;
1175 w = WIDGET (p->data);
1177 if (!widget_get_state (w, WST_DISABLED) && widget_get_options (w, WOP_WANT_CURSOR))
1178 send_message (w, NULL, MSG_CURSOR, 0, NULL);
1179 else
1182 p = dlg_get_widget_next_of (p);
1183 if (p == h->current)
1184 break;
1186 w = WIDGET (p->data);
1188 if (!widget_get_state (w, WST_DISABLED) && widget_get_options (w, WOP_WANT_CURSOR)
1189 && send_message (w, NULL, MSG_CURSOR, 0, NULL) == MSG_HANDLED)
1190 break;
1192 while (TRUE);
1196 /* --------------------------------------------------------------------------------------------- */
1198 * Redraw the widgets in reverse order, leaving the current widget
1199 * as the last one
1202 void
1203 dlg_redraw (WDialog * h)
1205 if (!widget_get_state (WIDGET (h), WST_ACTIVE))
1206 return;
1208 if (h->winch_pending)
1210 h->winch_pending = FALSE;
1211 send_message (h, NULL, MSG_RESIZE, 0, NULL);
1214 send_message (h, NULL, MSG_DRAW, 0, NULL);
1215 dlg_broadcast_msg (h, MSG_DRAW);
1216 update_cursor (h);
1219 /* --------------------------------------------------------------------------------------------- */
1221 void
1222 dlg_stop (WDialog * h)
1224 widget_set_state (WIDGET (h), WST_CLOSED, TRUE);
1227 /* --------------------------------------------------------------------------------------------- */
1228 /** Init the process */
1230 void
1231 dlg_init (WDialog * h)
1233 Widget *wh = WIDGET (h);
1235 if (top_dlg != NULL && widget_get_state (WIDGET (top_dlg->data), WST_MODAL))
1236 widget_set_state (wh, WST_MODAL, TRUE);
1238 /* add dialog to the stack */
1239 top_dlg = g_list_prepend (top_dlg, h);
1241 /* Initialize dialog manager and widgets */
1242 if (widget_get_state (wh, WST_CONSTRUCT))
1244 if (!widget_get_state (wh, WST_MODAL))
1245 dialog_switch_add (h);
1247 send_message (h, NULL, MSG_INIT, 0, NULL);
1248 dlg_broadcast_msg (h, MSG_INIT);
1249 dlg_read_history (h);
1252 widget_set_state (wh, WST_ACTIVE, TRUE);
1254 /* first send MSG_DRAW to dialog itself and all widgets... */
1255 dlg_redraw (h);
1257 /* ...then send MSG_FOCUS to select the first widget that can take focus */
1258 while (h->current != NULL && !dlg_focus (h))
1259 h->current = dlg_get_widget_next_of (h->current);
1262 h->ret_value = 0;
1265 /* --------------------------------------------------------------------------------------------- */
1267 void
1268 dlg_process_event (WDialog * h, int key, Gpm_Event * event)
1270 if (key == EV_NONE)
1272 if (tty_got_interrupt ())
1273 if (send_message (h, NULL, MSG_ACTION, CK_Cancel, NULL) != MSG_HANDLED)
1274 dlg_execute_cmd (h, CK_Cancel);
1276 else if (key == EV_MOUSE)
1277 h->mouse_status = dlg_mouse_event (h, event);
1278 else
1279 dlg_key_event (h, key);
1282 /* --------------------------------------------------------------------------------------------- */
1283 /** Shutdown the dlg_run */
1285 void
1286 dlg_run_done (WDialog * h)
1288 top_dlg = g_list_remove (top_dlg, h);
1290 if (widget_get_state (WIDGET (h), WST_CLOSED))
1292 send_message (h, h->current->data, MSG_END, 0, NULL);
1293 if (!widget_get_state (WIDGET (h), WST_MODAL))
1294 dialog_switch_remove (h);
1298 /* --------------------------------------------------------------------------------------------- */
1300 * Standard run dialog routine
1301 * We have to keep this routine small so that we can duplicate it's
1302 * behavior on complex routines like the file routines, this way,
1303 * they can call the dlg_process_event without rewriting all the code
1307 dlg_run (WDialog * h)
1309 dlg_init (h);
1310 frontend_dlg_run (h);
1311 dlg_run_done (h);
1312 return h->ret_value;
1315 /* --------------------------------------------------------------------------------------------- */
1317 void
1318 dlg_destroy (WDialog * h)
1320 /* if some widgets have history, save all history at one moment here */
1321 dlg_save_history (h);
1322 dlg_broadcast_msg (h, MSG_DESTROY);
1323 g_list_free_full (h->widgets, g_free);
1324 mc_event_group_del (h->event_group);
1325 g_free (h->event_group);
1326 g_free (h->title);
1327 g_free (h);
1329 do_refresh ();
1332 /* --------------------------------------------------------------------------------------------- */
1335 * Write history to the ${XDG_CACHE_HOME}/mc/history file
1337 void
1338 dlg_save_history (WDialog * h)
1340 char *profile;
1341 int i;
1343 if (num_history_items_recorded == 0) /* this is how to disable */
1344 return;
1346 profile = mc_config_get_full_path (MC_HISTORY_FILE);
1347 i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
1348 if (i != -1)
1349 close (i);
1351 /* Make sure the history is only readable by the user */
1352 if (chmod (profile, S_IRUSR | S_IWUSR) != -1 || errno == ENOENT)
1354 ev_history_load_save_t event_data;
1356 event_data.cfg = mc_config_init (profile, FALSE);
1357 event_data.receiver = NULL;
1359 /* get all histories in dialog */
1360 mc_event_raise (h->event_group, MCEVENT_HISTORY_SAVE, &event_data);
1362 mc_config_save_file (event_data.cfg, NULL);
1363 mc_config_deinit (event_data.cfg);
1366 g_free (profile);
1369 /* --------------------------------------------------------------------------------------------- */
1371 char *
1372 dlg_get_title (const WDialog * h, size_t len)
1374 char *t;
1376 if (h == NULL)
1377 abort ();
1379 if (h->get_title != NULL)
1380 t = h->get_title (h, len);
1381 else
1382 t = g_strdup ("");
1384 return t;
1387 /* --------------------------------------------------------------------------------------------- */
1390 * Switch current widget to widget after current in dialog.
1392 * @param h WDialog widget
1395 void
1396 dlg_set_current_widget_next (WDialog * h)
1398 h->current = dlg_get_next_or_prev_of (h->current, TRUE);
1401 /* --------------------------------------------------------------------------------------------- */
1404 * Switch current widget to widget before current in dialog.
1406 * @param h WDialog widget
1409 void
1410 dlg_set_current_widget_prev (WDialog * h)
1412 h->current = dlg_get_next_or_prev_of (h->current, FALSE);
1415 /* --------------------------------------------------------------------------------------------- */
1418 * Get widget that is after specified widget in dialog.
1420 * @param w widget holder
1422 * @return widget that is after "w" or NULL if "w" is NULL or widget doesn't have owner
1425 GList *
1426 dlg_get_widget_next_of (GList * w)
1428 return dlg_get_next_or_prev_of (w, TRUE);
1431 /* --------------------------------------------------------------------------------------------- */
1434 * Get widget that is before specified widget in dialog.
1436 * @param w widget holder
1438 * @return widget that is before "w" or NULL if "w" is NULL or widget doesn't have owner
1441 GList *
1442 dlg_get_widget_prev_of (GList * w)
1444 return dlg_get_next_or_prev_of (w, FALSE);
1447 /* --------------------------------------------------------------------------------------------- */