First bunch of mhl_mem_free removal patches
[midnight-commander.git] / src / dialog.c
blob37c6903a4b23cc54e8952a04e399fc6cc93ca568
1 /* Dialog box features module for the Midnight Commander
2 Copyright (C) 1994, 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
3 2005, 2007 Free Software Foundation, Inc.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include <config.h>
22 #include <ctype.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
27 #include <mhl/memory.h>
28 #include <mhl/string.h>
30 #include "global.h"
31 #include "tty.h"
32 #include "mouse.h"
33 #include "help.h" /* interactive_display() */
34 #include "key.h" /* mi_getch() */
35 #include "dialog.h"
36 #include "layout.h" /* winch_flag */
37 #include "execute.h" /* suspend_cmd() */
38 #include "main.h" /* slow_terminal */
40 #define waddc(w,y1,x1,c) move (w->y+y1, w->x+x1); addch (c)
42 /* Primitive way to check if the the current dialog is our dialog */
43 /* This is needed by async routines like load_prompt */
44 Dlg_head *current_dlg = 0;
46 /* A hook list for idle events */
47 Hook *idle_hook = 0;
49 static void dlg_broadcast_msg_to (Dlg_head * h, widget_msg_t message,
50 int reverse, int flags);
52 static void slow_box (Dlg_head *h, int y, int x, int ys, int xs)
54 move (h->y+y, h->x+x);
55 hline (' ', xs);
56 vline (' ', ys);
57 move (h->y+y, h->x+x+xs-1);
58 vline (' ', ys);
59 move (h->y+y+ys-1, h->x+x);
60 hline (' ', xs);
63 /* draw box in window */
64 void draw_box (Dlg_head *h, int y, int x, int ys, int xs)
66 if (slow_terminal){
67 slow_box (h, y, x, ys, xs);
68 return;
71 #ifndef HAVE_SLANG
72 waddc (h, y, x, ACS_ULCORNER);
73 hline (ACS_HLINE, xs - 2);
74 waddc (h, y + ys - 1, x, ACS_LLCORNER);
75 hline (ACS_HLINE, xs - 2);
77 waddc (h, y, x + xs - 1, ACS_URCORNER);
78 waddc (h, y + ys - 1, x + xs - 1, ACS_LRCORNER);
80 move (h->y+y+1, h->x+x);
81 vline (ACS_VLINE, ys - 2);
82 move (h->y+y+1, h->x+x+xs-1);
83 vline (ACS_VLINE, ys - 2);
84 #else
85 SLsmg_draw_box (h->y+y, h->x+x, ys, xs);
86 #endif /* HAVE_SLANG */
89 /* draw box in window */
90 void draw_double_box (Dlg_head *h, int y, int x, int ys, int xs)
92 #ifndef HAVE_SLANG
93 draw_box (h, y, x, ys, xs);
94 #else
95 SLsmg_draw_double_box (h->y+y, h->x+x, ys, xs);
96 #endif /* HAVE_SLANG */
99 void widget_erase (Widget *w)
101 int x, y;
103 for (y = 0; y < w->lines; y++){
104 widget_move (w, y, 0);
105 for (x = 0; x < w->cols; x++)
106 addch (' ');
110 void dlg_erase (Dlg_head *h)
112 int x, y;
114 for (y = 0; y < h->lines; y++){
115 move (y+h->y, h->x); /* FIXME: should test if ERR */
116 for (x = 0; x < h->cols; x++){
117 addch (' ');
122 void
123 init_widget (Widget *w, int y, int x, int lines, int cols,
124 callback_fn callback, mouse_h mouse_handler)
126 w->x = x;
127 w->y = y;
128 w->cols = cols;
129 w->lines = lines;
130 w->callback = callback;
131 w->mouse = mouse_handler;
132 w->parent = 0;
134 /* Almost all widgets want to put the cursor in a suitable place */
135 w->options = W_WANT_CURSOR;
138 /* Default callback for widgets */
139 cb_ret_t
140 default_proc (widget_msg_t msg, int parm)
142 (void) parm;
144 switch (msg) {
145 case WIDGET_INIT:
146 case WIDGET_FOCUS:
147 case WIDGET_UNFOCUS:
148 case WIDGET_DRAW:
149 case WIDGET_DESTROY:
150 case WIDGET_CURSOR:
151 case WIDGET_IDLE:
152 return MSG_HANDLED;
154 default:
155 return MSG_NOT_HANDLED;
159 /* Clean the dialog area, draw the frame and the title */
160 void
161 common_dialog_repaint (struct Dlg_head *h)
163 int space;
165 space = (h->flags & DLG_COMPACT) ? 0 : 1;
167 attrset (DLG_NORMALC (h));
168 dlg_erase (h);
169 draw_box (h, space, space, h->lines - 2 * space, h->cols - 2 * space);
171 if (h->title) {
172 attrset (DLG_HOT_NORMALC (h));
173 dlg_move (h, space, (h->cols - strlen (h->title)) / 2);
174 addstr (h->title);
178 /* Default dialog callback */
179 cb_ret_t default_dlg_callback (Dlg_head *h, dlg_msg_t msg, int parm)
181 (void) parm;
183 if (msg == DLG_DRAW && h->color) {
184 common_dialog_repaint (h);
185 return MSG_HANDLED;
187 if (msg == DLG_IDLE){
188 dlg_broadcast_msg_to (h, WIDGET_IDLE, 0, W_WANT_IDLE);
189 return MSG_HANDLED;
191 return MSG_NOT_HANDLED;
194 Dlg_head *
195 create_dlg (int y1, int x1, int lines, int cols, const int *color_set,
196 dlg_cb_fn callback, const char *help_ctx, const char *title,
197 int flags)
199 Dlg_head *new_d;
201 if (flags & DLG_CENTER) {
202 y1 = (LINES - lines) / 2;
203 x1 = (COLS - cols) / 2;
206 if ((flags & DLG_TRYUP) && (y1 > 3))
207 y1 -= 2;
209 new_d = g_new0 (Dlg_head, 1);
210 new_d->color = color_set;
211 new_d->help_ctx = help_ctx;
212 new_d->callback = callback ? callback : default_dlg_callback;
213 new_d->x = x1;
214 new_d->y = y1;
215 new_d->cols = cols;
216 new_d->lines = lines;
217 new_d->flags = flags;
219 /* Strip existing spaces, add one space before and after the title */
220 if (title) {
221 char *t;
222 t = g_strstrip (mhl_str_dup (title));
223 new_d->title = g_strconcat (" ", t, " ", (char *) NULL);
224 g_free (t);
227 return (new_d);
230 void
231 set_idle_proc (Dlg_head *d, int enable)
233 if (enable)
234 d->flags |= DLG_WANT_IDLE;
235 else
236 d->flags &= ~DLG_WANT_IDLE;
240 * Insert widget to dialog before current widget. For dialogs populated
241 * from the bottom, make the widget current. Return widget number.
244 add_widget (Dlg_head *h, void *w)
246 Widget *widget = (Widget *) w;
248 /* Don't accept 0 widgets, and running dialogs */
249 if (!widget || h->running)
250 abort ();
252 widget->x += h->x;
253 widget->y += h->y;
254 widget->parent = h;
255 widget->dlg_id = h->count++;
257 if (h->current) {
258 widget->next = h->current;
259 widget->prev = h->current->prev;
260 h->current->prev->next = widget;
261 h->current->prev = widget;
262 } else {
263 widget->prev = widget;
264 widget->next = widget;
267 if ((h->flags & DLG_REVERSE) || !h->current)
268 h->current = widget;
270 return widget->dlg_id;
273 enum {
274 REFRESH_COVERS_PART, /* If the refresh fn convers only a part */
275 REFRESH_COVERS_ALL /* If the refresh fn convers all the screen */
278 static void
279 do_complete_refresh (Dlg_head *dlg)
281 if (!dlg->fullscreen && dlg->parent)
282 do_complete_refresh (dlg->parent);
284 dlg_redraw (dlg);
287 void
288 do_refresh (void)
290 if (!current_dlg)
291 return;
293 if (fast_refresh)
294 dlg_redraw (current_dlg);
295 else {
296 do_complete_refresh (current_dlg);
300 /* broadcast a message to all the widgets in a dialog that have
301 * the options set to flags. If flags is zero, the message is sent
302 * to all widgets.
304 static void
305 dlg_broadcast_msg_to (Dlg_head *h, widget_msg_t message, int reverse,
306 int flags)
308 Widget *p, *first, *wi;
310 if (!h->current)
311 return;
313 if (reverse)
314 first = p = h->current->prev;
315 else
316 first = p = h->current->next;
318 do {
319 wi = p;
320 if (reverse)
321 p = p->prev;
322 else
323 p = p->next;
324 if (flags == 0 || (flags & wi->options))
325 send_message (wi, message, 0);
326 } while (first != p);
329 /* broadcast a message to all the widgets in a dialog */
330 void
331 dlg_broadcast_msg (Dlg_head *h, widget_msg_t message, int reverse)
333 dlg_broadcast_msg_to (h, message, reverse, 0);
336 int dlg_focus (Dlg_head *h)
338 if (!h->current)
339 return 0;
341 if (send_message (h->current, WIDGET_FOCUS, 0)){
342 (*h->callback) (h, DLG_FOCUS, 0);
343 return 1;
345 return 0;
348 static int
349 dlg_unfocus (Dlg_head *h)
351 if (!h->current)
352 return 0;
354 if (send_message (h->current, WIDGET_UNFOCUS, 0)){
355 (*h->callback) (h, DLG_UNFOCUS, 0);
356 return 1;
358 return 0;
362 /* Return true if the windows overlap */
363 int dlg_overlap (Widget *a, Widget *b)
365 if ((b->x >= a->x + a->cols)
366 || (a->x >= b->x + b->cols)
367 || (b->y >= a->y + a->lines)
368 || (a->y >= b->y + b->lines))
369 return 0;
370 return 1;
374 /* Find the widget with the given callback in the dialog h */
375 Widget *
376 find_widget_type (Dlg_head *h, callback_fn callback)
378 Widget *w;
379 Widget *item;
380 int i;
382 if (!h)
383 return 0;
384 if (!h->current)
385 return 0;
387 w = 0;
388 for (i = 0, item = h->current; i < h->count; i++, item = item->next) {
389 if (item->callback == callback) {
390 w = item;
391 break;
394 return w;
397 /* Find the widget with the given dialog id in the dialog h and select it */
398 void
399 dlg_select_by_id (Dlg_head *h, int id)
401 Widget *w, *w_found;
403 if (!h->current)
404 return;
406 w = h->current;
407 w_found = NULL;
409 do {
410 if (w->dlg_id == id) {
411 w_found = w;
412 break;
414 w = w->next;
415 } while (w != h->current);
417 if (w_found)
418 dlg_select_widget(w_found);
422 /* What to do if the requested widget doesn't take focus */
423 typedef enum {
424 SELECT_NEXT, /* go the the next widget */
425 SELECT_PREV, /* go the the previous widget */
426 SELECT_EXACT /* use current widget */
427 } select_dir_t;
430 * Try to select another widget. If forward is set, follow tab order.
431 * Otherwise go to the previous widget.
433 static void
434 do_select_widget (Dlg_head *h, Widget *w, select_dir_t dir)
436 Widget *w0 = h->current;
438 if (!dlg_unfocus (h))
439 return;
441 h->current = w;
442 do {
443 if (dlg_focus (h))
444 break;
446 switch (dir) {
447 case SELECT_NEXT:
448 h->current = h->current->next;
449 break;
450 case SELECT_PREV:
451 h->current = h->current->prev;
452 break;
453 case SELECT_EXACT:
454 h->current = w0;
455 dlg_focus (h);
456 return;
458 } while (h->current != w);
460 if (dlg_overlap (w0, h->current)) {
461 send_message (h->current, WIDGET_DRAW, 0);
462 send_message (h->current, WIDGET_FOCUS, 0);
468 * Try to select widget in the dialog.
470 void
471 dlg_select_widget (void *w)
473 do_select_widget (((Widget *) w)->parent, w, SELECT_NEXT);
477 /* Try to select previous widget in the tab order */
478 void
479 dlg_one_up (Dlg_head *h)
481 if (h->current)
482 do_select_widget (h, h->current->prev, SELECT_PREV);
486 /* Try to select next widget in the tab order */
487 void
488 dlg_one_down (Dlg_head *h)
490 if (h->current)
491 do_select_widget (h, h->current->next, SELECT_NEXT);
495 void update_cursor (Dlg_head *h)
497 if (!h->current)
498 return;
499 if (h->current->options & W_WANT_CURSOR)
500 send_message (h->current, WIDGET_CURSOR, 0);
501 else {
502 Widget *p = h->current;
504 do {
505 if (p->options & W_WANT_CURSOR)
506 if ((*p->callback)(p, WIDGET_CURSOR, 0)){
507 break;
509 p = p->next;
510 } while (h->current != p);
514 /* Redraw the widgets in reverse order, leaving the current widget
515 * as the last one
517 void dlg_redraw (Dlg_head *h)
519 (h->callback)(h, DLG_DRAW, 0);
521 dlg_broadcast_msg (h, WIDGET_DRAW, 1);
523 update_cursor (h);
526 void dlg_stop (Dlg_head *h)
528 h->running = 0;
531 static inline void dialog_handle_key (Dlg_head *h, int d_key)
533 switch (d_key){
534 case KEY_LEFT:
535 case KEY_UP:
536 dlg_one_up (h);
537 break;
539 case KEY_RIGHT:
540 case KEY_DOWN:
541 dlg_one_down (h);
542 break;
544 case KEY_F(1):
545 interactive_display (NULL, h->help_ctx);
546 do_refresh ();
547 break;
549 case XCTRL('z'):
550 suspend_cmd ();
551 /* Fall through */
553 case XCTRL('l'):
554 #ifndef HAVE_SLANG
555 /* Use this if the refreshes fail */
556 clr_scr ();
557 do_refresh ();
558 #else
559 touchwin (stdscr);
560 #endif /* HAVE_SLANG */
561 mc_refresh ();
562 doupdate ();
563 break;
565 case '\n':
566 case KEY_ENTER:
567 h->ret_value = B_ENTER;
568 h->running = 0;
569 break;
571 case ESC_CHAR:
572 case KEY_F (10):
573 case XCTRL ('c'):
574 case XCTRL ('g'):
575 h->ret_value = B_CANCEL;
576 dlg_stop (h);
577 break;
581 static int
582 dlg_try_hotkey (Dlg_head *h, int d_key)
584 Widget *hot_cur;
585 int handled, c;
587 if (!h->current)
588 return 0;
591 * Explanation: we don't send letter hotkeys to other widgets if
592 * the currently selected widget is an input line
595 if (h->current->options & W_IS_INPUT) {
596 if (d_key < 255 && isalpha (d_key))
597 return 0;
600 /* If it's an alt key, send the message */
601 c = d_key & ~ALT (0);
602 if (d_key & ALT (0) && c < 255 && isalpha (c))
603 d_key = tolower (c);
605 handled = 0;
606 if (h->current->options & W_WANT_HOTKEY)
607 handled = h->current->callback (h->current, WIDGET_HOTKEY, d_key);
609 /* If not used, send hotkey to other widgets */
610 if (handled)
611 return handled;
613 hot_cur = h->current;
615 /* send it to all widgets */
616 do {
617 if (hot_cur->options & W_WANT_HOTKEY)
618 handled |=
619 (*hot_cur->callback) (hot_cur, WIDGET_HOTKEY, d_key);
621 if (!handled)
622 hot_cur = hot_cur->next;
623 } while (h->current != hot_cur && !handled);
625 if (!handled)
626 return 0;
628 do_select_widget (h, hot_cur, SELECT_EXACT);
629 return handled;
632 static void
633 dlg_key_event (Dlg_head *h, int d_key)
635 int handled;
637 if (!h->current)
638 return;
640 /* TAB used to cycle */
641 if (!(h->flags & DLG_WANT_TAB)) {
642 if (d_key == '\t') {
643 dlg_one_down (h);
644 return;
645 } else if (d_key == KEY_BTAB) {
646 dlg_one_up (h);
647 return;
651 /* first can dlg_callback handle the key */
652 handled = (*h->callback) (h, DLG_KEY, d_key);
654 /* next try the hotkey */
655 if (!handled)
656 handled = dlg_try_hotkey (h, d_key);
658 if (handled)
659 (*h->callback) (h, DLG_HOTKEY_HANDLED, 0);
661 /* not used - then try widget_callback */
662 if (!handled)
663 handled = h->current->callback (h->current, WIDGET_KEY, d_key);
665 /* not used- try to use the unhandled case */
666 if (!handled)
667 handled = (*h->callback) (h, DLG_UNHANDLED_KEY, d_key);
669 if (!handled)
670 dialog_handle_key (h, d_key);
672 (*h->callback) (h, DLG_POST_KEY, d_key);
675 static inline int
676 dlg_mouse_event (Dlg_head * h, Gpm_Event * event)
678 Widget *item;
679 Widget *starting_widget = h->current;
680 Gpm_Event new_event;
681 int x = event->x;
682 int y = event->y;
684 item = starting_widget;
685 do {
686 Widget *widget = item;
688 item = item->next;
690 if (!((x > widget->x) && (x <= widget->x + widget->cols)
691 && (y > widget->y) && (y <= widget->y + widget->lines)))
692 continue;
694 new_event = *event;
695 new_event.x -= widget->x;
696 new_event.y -= widget->y;
698 if (!widget->mouse)
699 return MOU_NORMAL;
701 return (*widget->mouse) (&new_event, widget);
702 } while (item != starting_widget);
704 return MOU_NORMAL;
707 /* Run dialog routines */
709 /* Init the process */
710 void init_dlg (Dlg_head *h)
712 /* Initialize dialog manager and widgets */
713 (*h->callback) (h, DLG_INIT, 0);
714 dlg_broadcast_msg (h, WIDGET_INIT, 0);
716 if (h->x == 0 && h->y == 0 && h->cols == COLS && h->lines == LINES)
717 h->fullscreen = 1;
719 h->parent = current_dlg;
720 current_dlg = h;
722 /* Initialize the mouse status */
723 h->mouse_status = MOU_NORMAL;
725 /* Select the first widget that takes focus */
726 while (!dlg_focus (h) && h->current)
727 h->current = h->current->next;
729 /* Redraw the screen */
730 dlg_redraw (h);
732 h->ret_value = 0;
733 h->running = 1;
736 /* Shutdown the run_dlg */
737 void dlg_run_done (Dlg_head *h)
739 if (h->current)
740 (*h->callback) (h, DLG_END, 0);
742 current_dlg = h->parent;
745 void dlg_process_event (Dlg_head *h, int key, Gpm_Event *event)
747 if (key == EV_NONE){
748 if (got_interrupt ())
749 key = XCTRL('g');
750 else
751 return;
754 if (key == EV_MOUSE)
755 h->mouse_status = dlg_mouse_event (h, event);
756 else
757 dlg_key_event (h, key);
760 static inline void
761 frontend_run_dlg (Dlg_head *h)
763 int d_key;
764 Gpm_Event event;
766 event.x = -1;
767 while (h->running) {
768 if (winch_flag)
769 change_screen_size ();
771 if (is_idle ()) {
772 if (idle_hook)
773 execute_hooks (idle_hook);
775 while ((h->flags & DLG_WANT_IDLE) && is_idle ())
776 (*h->callback) (h, DLG_IDLE, 0);
778 /* Allow terminating the dialog from the idle handler */
779 if (!h->running)
780 break;
783 update_cursor (h);
785 /* Clear interrupt flag */
786 got_interrupt ();
787 d_key = get_event (&event, h->mouse_status == MOU_REPEAT, 1);
789 dlg_process_event (h, d_key, &event);
791 if (!h->running)
792 (*h->callback) (h, DLG_VALIDATE, 0);
796 /* Standard run dialog routine
797 * We have to keep this routine small so that we can duplicate it's
798 * behavior on complex routines like the file routines, this way,
799 * they can call the dlg_process_event without rewriting all the code
801 int run_dlg (Dlg_head *h)
803 init_dlg (h);
804 frontend_run_dlg (h);
805 dlg_run_done (h);
806 return h->ret_value;
809 void
810 destroy_dlg (Dlg_head *h)
812 int i;
813 Widget *c;
815 dlg_broadcast_msg (h, WIDGET_DESTROY, 0);
816 c = h->current;
817 for (i = 0; i < h->count; i++) {
818 c = c->next;
819 g_free (h->current);
820 h->current = c;
822 g_free (h->title);
823 g_free (h);
825 do_refresh ();
828 void widget_set_size (Widget *widget, int y, int x, int lines, int cols)
830 widget->x = x;
831 widget->y = y;
832 widget->cols = cols;
833 widget->lines = lines;
834 send_message (widget, WIDGET_RESIZED, 0 /* unused */);
837 /* Replace widget old_w for widget new_w in the dialog */
838 void
839 dlg_replace_widget (Widget *old_w, Widget *new_w)
841 Dlg_head *h = old_w->parent;
842 int should_focus = 0;
844 if (!h->current)
845 return;
847 if (old_w == h->current)
848 should_focus = 1;
850 new_w->parent = h;
851 new_w->dlg_id = old_w->dlg_id;
853 if (old_w == old_w->next) {
854 /* just one widget */
855 new_w->prev = new_w;
856 new_w->next = new_w;
857 } else {
858 new_w->prev = old_w->prev;
859 new_w->next = old_w->next;
860 old_w->prev->next = new_w;
861 old_w->next->prev = new_w;
864 if (should_focus)
865 h->current = new_w;
867 send_message (old_w, WIDGET_DESTROY, 0);
868 send_message (new_w, WIDGET_INIT, 0);
870 if (should_focus)
871 dlg_select_widget (new_w);
873 send_message (new_w, WIDGET_DRAW, 0);