use size_t where needed.
[midnight-commander.git] / src / dialog.c
blobf8467b9d6162c02162f149f47a2512a208471f87
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 "global.h"
28 #include "tty.h"
29 #include "mouse.h"
30 #include "help.h" /* interactive_display() */
31 #include "key.h" /* mi_getch() */
32 #include "dialog.h"
33 #include "layout.h" /* winch_flag */
34 #include "execute.h" /* suspend_cmd() */
35 #include "main.h" /* slow_terminal */
37 #define waddc(w,y1,x1,c) move (w->y+y1, w->x+x1); addch (c)
39 /* Primitive way to check if the the current dialog is our dialog */
40 /* This is needed by async routines like load_prompt */
41 Dlg_head *current_dlg = 0;
43 /* A hook list for idle events */
44 Hook *idle_hook = 0;
46 static void dlg_broadcast_msg_to (Dlg_head * h, widget_msg_t message,
47 int reverse, int flags);
49 static void slow_box (Dlg_head *h, int y, int x, int ys, int xs)
51 move (h->y+y, h->x+x);
52 hline (' ', xs);
53 vline (' ', ys);
54 move (h->y+y, h->x+x+xs-1);
55 vline (' ', ys);
56 move (h->y+y+ys-1, h->x+x);
57 hline (' ', xs);
60 /* draw box in window */
61 void draw_box (Dlg_head *h, int y, int x, int ys, int xs)
63 if (slow_terminal){
64 slow_box (h, y, x, ys, xs);
65 return;
68 #ifndef HAVE_SLANG
69 waddc (h, y, x, ACS_ULCORNER);
70 hline (ACS_HLINE, xs - 2);
71 waddc (h, y + ys - 1, x, ACS_LLCORNER);
72 hline (ACS_HLINE, xs - 2);
74 waddc (h, y, x + xs - 1, ACS_URCORNER);
75 waddc (h, y + ys - 1, x + xs - 1, ACS_LRCORNER);
77 move (h->y+y+1, h->x+x);
78 vline (ACS_VLINE, ys - 2);
79 move (h->y+y+1, h->x+x+xs-1);
80 vline (ACS_VLINE, ys - 2);
81 #else
82 SLsmg_draw_box (h->y+y, h->x+x, ys, xs);
83 #endif /* HAVE_SLANG */
86 /* draw box in window */
87 void draw_double_box (Dlg_head *h, int y, int x, int ys, int xs)
89 #ifndef HAVE_SLANG
90 draw_box (h, y, x, ys, xs);
91 #else
92 SLsmg_draw_double_box (h->y+y, h->x+x, ys, xs);
93 #endif /* HAVE_SLANG */
96 void widget_erase (Widget *w)
98 int x, y;
100 for (y = 0; y < w->lines; y++){
101 widget_move (w, y, 0);
102 for (x = 0; x < w->cols; x++)
103 addch (' ');
107 void dlg_erase (Dlg_head *h)
109 int x, y;
111 for (y = 0; y < h->lines; y++){
112 move (y+h->y, h->x); /* FIXME: should test if ERR */
113 for (x = 0; x < h->cols; x++){
114 addch (' ');
119 void
120 init_widget (Widget *w, int y, int x, int lines, int cols,
121 callback_fn callback, mouse_h mouse_handler)
123 w->x = x;
124 w->y = y;
125 w->cols = cols;
126 w->lines = lines;
127 w->callback = callback;
128 w->mouse = mouse_handler;
129 w->parent = 0;
131 /* Almost all widgets want to put the cursor in a suitable place */
132 w->options = W_WANT_CURSOR;
135 /* Default callback for widgets */
136 cb_ret_t
137 default_proc (widget_msg_t msg, int parm)
139 (void) parm;
141 switch (msg) {
142 case WIDGET_INIT:
143 case WIDGET_FOCUS:
144 case WIDGET_UNFOCUS:
145 case WIDGET_DRAW:
146 case WIDGET_DESTROY:
147 case WIDGET_CURSOR:
148 case WIDGET_IDLE:
149 return MSG_HANDLED;
151 default:
152 return MSG_NOT_HANDLED;
156 /* Clean the dialog area, draw the frame and the title */
157 void
158 common_dialog_repaint (struct Dlg_head *h)
160 int space;
162 space = (h->flags & DLG_COMPACT) ? 0 : 1;
164 attrset (DLG_NORMALC (h));
165 dlg_erase (h);
166 draw_box (h, space, space, h->lines - 2 * space, h->cols - 2 * space);
168 if (h->title) {
169 attrset (DLG_HOT_NORMALC (h));
170 dlg_move (h, space, (h->cols - strlen (h->title)) / 2);
171 addstr (h->title);
175 /* Default dialog callback */
176 cb_ret_t default_dlg_callback (Dlg_head *h, dlg_msg_t msg, int parm)
178 (void) parm;
180 if (msg == DLG_DRAW && h->color) {
181 common_dialog_repaint (h);
182 return MSG_HANDLED;
184 if (msg == DLG_IDLE){
185 dlg_broadcast_msg_to (h, WIDGET_IDLE, 0, W_WANT_IDLE);
186 return MSG_HANDLED;
188 return MSG_NOT_HANDLED;
191 Dlg_head *
192 create_dlg (int y1, int x1, int lines, int cols, const int *color_set,
193 dlg_cb_fn callback, const char *help_ctx, const char *title,
194 int flags)
196 Dlg_head *new_d;
198 if (flags & DLG_CENTER) {
199 y1 = (LINES - lines) / 2;
200 x1 = (COLS - cols) / 2;
203 if ((flags & DLG_TRYUP) && (y1 > 3))
204 y1 -= 2;
206 new_d = g_new0 (Dlg_head, 1);
207 new_d->color = color_set;
208 new_d->help_ctx = help_ctx;
209 new_d->callback = callback ? callback : default_dlg_callback;
210 new_d->x = x1;
211 new_d->y = y1;
212 new_d->cols = cols;
213 new_d->lines = lines;
214 new_d->flags = flags;
216 /* Strip existing spaces, add one space before and after the title */
217 if (title) {
218 char *t;
219 t = g_strstrip (g_strdup (title));
220 new_d->title = g_strconcat (" ", t, " ", (char *) NULL);
221 g_free (t);
224 return (new_d);
227 void
228 set_idle_proc (Dlg_head *d, int enable)
230 if (enable)
231 d->flags |= DLG_WANT_IDLE;
232 else
233 d->flags &= ~DLG_WANT_IDLE;
237 * Insert widget to dialog before current widget. For dialogs populated
238 * from the bottom, make the widget current. Return widget number.
241 add_widget (Dlg_head *h, void *w)
243 Widget *widget = (Widget *) w;
245 /* Don't accept 0 widgets, and running dialogs */
246 if (!widget || h->running)
247 abort ();
249 widget->x += h->x;
250 widget->y += h->y;
251 widget->parent = h;
252 widget->dlg_id = h->count++;
254 if (h->current) {
255 widget->next = h->current;
256 widget->prev = h->current->prev;
257 h->current->prev->next = widget;
258 h->current->prev = widget;
259 } else {
260 widget->prev = widget;
261 widget->next = widget;
264 if ((h->flags & DLG_REVERSE) || !h->current)
265 h->current = widget;
267 return widget->dlg_id;
270 enum {
271 REFRESH_COVERS_PART, /* If the refresh fn convers only a part */
272 REFRESH_COVERS_ALL /* If the refresh fn convers all the screen */
275 static void
276 do_complete_refresh (Dlg_head *dlg)
278 if (!dlg->fullscreen && dlg->parent)
279 do_complete_refresh (dlg->parent);
281 dlg_redraw (dlg);
284 void
285 do_refresh (void)
287 if (!current_dlg)
288 return;
290 if (fast_refresh)
291 dlg_redraw (current_dlg);
292 else {
293 do_complete_refresh (current_dlg);
297 /* broadcast a message to all the widgets in a dialog that have
298 * the options set to flags. If flags is zero, the message is sent
299 * to all widgets.
301 static void
302 dlg_broadcast_msg_to (Dlg_head *h, widget_msg_t message, int reverse,
303 int flags)
305 Widget *p, *first, *wi;
307 if (!h->current)
308 return;
310 if (reverse)
311 first = p = h->current->prev;
312 else
313 first = p = h->current->next;
315 do {
316 wi = p;
317 if (reverse)
318 p = p->prev;
319 else
320 p = p->next;
321 if (flags == 0 || (flags & wi->options))
322 send_message (wi, message, 0);
323 } while (first != p);
326 /* broadcast a message to all the widgets in a dialog */
327 void
328 dlg_broadcast_msg (Dlg_head *h, widget_msg_t message, int reverse)
330 dlg_broadcast_msg_to (h, message, reverse, 0);
333 int dlg_focus (Dlg_head *h)
335 if (!h->current)
336 return 0;
338 if (send_message (h->current, WIDGET_FOCUS, 0)){
339 (*h->callback) (h, DLG_FOCUS, 0);
340 return 1;
342 return 0;
345 static int
346 dlg_unfocus (Dlg_head *h)
348 if (!h->current)
349 return 0;
351 if (send_message (h->current, WIDGET_UNFOCUS, 0)){
352 (*h->callback) (h, DLG_UNFOCUS, 0);
353 return 1;
355 return 0;
359 /* Return true if the windows overlap */
360 int dlg_overlap (Widget *a, Widget *b)
362 if ((b->x >= a->x + a->cols)
363 || (a->x >= b->x + b->cols)
364 || (b->y >= a->y + a->lines)
365 || (a->y >= b->y + b->lines))
366 return 0;
367 return 1;
371 /* Find the widget with the given callback in the dialog h */
372 Widget *
373 find_widget_type (Dlg_head *h, callback_fn callback)
375 Widget *w;
376 Widget *item;
377 int i;
379 if (!h)
380 return 0;
381 if (!h->current)
382 return 0;
384 w = 0;
385 for (i = 0, item = h->current; i < h->count; i++, item = item->next) {
386 if (item->callback == callback) {
387 w = item;
388 break;
391 return w;
394 /* Find the widget with the given dialog id in the dialog h and select it */
395 void
396 dlg_select_by_id (Dlg_head *h, int id)
398 Widget *w, *w_found;
400 if (!h->current)
401 return;
403 w = h->current;
404 w_found = NULL;
406 do {
407 if (w->dlg_id == id) {
408 w_found = w;
409 break;
411 w = w->next;
412 } while (w != h->current);
414 if (w_found)
415 dlg_select_widget(w_found);
419 /* What to do if the requested widget doesn't take focus */
420 typedef enum {
421 SELECT_NEXT, /* go the the next widget */
422 SELECT_PREV, /* go the the previous widget */
423 SELECT_EXACT /* use current widget */
424 } select_dir_t;
427 * Try to select another widget. If forward is set, follow tab order.
428 * Otherwise go to the previous widget.
430 static void
431 do_select_widget (Dlg_head *h, Widget *w, select_dir_t dir)
433 Widget *w0 = h->current;
435 if (!dlg_unfocus (h))
436 return;
438 h->current = w;
439 do {
440 if (dlg_focus (h))
441 break;
443 switch (dir) {
444 case SELECT_NEXT:
445 h->current = h->current->next;
446 break;
447 case SELECT_PREV:
448 h->current = h->current->prev;
449 break;
450 case SELECT_EXACT:
451 h->current = w0;
452 dlg_focus (h);
453 return;
455 } while (h->current != w);
457 if (dlg_overlap (w0, h->current)) {
458 send_message (h->current, WIDGET_DRAW, 0);
459 send_message (h->current, WIDGET_FOCUS, 0);
465 * Try to select widget in the dialog.
467 void
468 dlg_select_widget (void *w)
470 do_select_widget (((Widget *) w)->parent, w, SELECT_NEXT);
474 /* Try to select previous widget in the tab order */
475 void
476 dlg_one_up (Dlg_head *h)
478 if (h->current)
479 do_select_widget (h, h->current->prev, SELECT_PREV);
483 /* Try to select next widget in the tab order */
484 void
485 dlg_one_down (Dlg_head *h)
487 if (h->current)
488 do_select_widget (h, h->current->next, SELECT_NEXT);
492 void update_cursor (Dlg_head *h)
494 if (!h->current)
495 return;
496 if (h->current->options & W_WANT_CURSOR)
497 send_message (h->current, WIDGET_CURSOR, 0);
498 else {
499 Widget *p = h->current;
501 do {
502 if (p->options & W_WANT_CURSOR)
503 if ((*p->callback)(p, WIDGET_CURSOR, 0)){
504 break;
506 p = p->next;
507 } while (h->current != p);
511 /* Redraw the widgets in reverse order, leaving the current widget
512 * as the last one
514 void dlg_redraw (Dlg_head *h)
516 (h->callback)(h, DLG_DRAW, 0);
518 dlg_broadcast_msg (h, WIDGET_DRAW, 1);
520 update_cursor (h);
523 void dlg_stop (Dlg_head *h)
525 h->running = 0;
528 static inline void dialog_handle_key (Dlg_head *h, int d_key)
530 switch (d_key){
531 case KEY_LEFT:
532 case KEY_UP:
533 dlg_one_up (h);
534 break;
536 case KEY_RIGHT:
537 case KEY_DOWN:
538 dlg_one_down (h);
539 break;
541 case KEY_F(1):
542 interactive_display (NULL, h->help_ctx);
543 do_refresh ();
544 break;
546 case XCTRL('z'):
547 suspend_cmd ();
548 /* Fall through */
550 case XCTRL('l'):
551 #ifndef HAVE_SLANG
552 /* Use this if the refreshes fail */
553 clr_scr ();
554 do_refresh ();
555 #else
556 touchwin (stdscr);
557 #endif /* HAVE_SLANG */
558 mc_refresh ();
559 doupdate ();
560 break;
562 case '\n':
563 case KEY_ENTER:
564 h->ret_value = B_ENTER;
565 h->running = 0;
566 break;
568 case ESC_CHAR:
569 case KEY_F (10):
570 case XCTRL ('c'):
571 case XCTRL ('g'):
572 h->ret_value = B_CANCEL;
573 dlg_stop (h);
574 break;
578 static int
579 dlg_try_hotkey (Dlg_head *h, int d_key)
581 Widget *hot_cur;
582 int handled, c;
584 if (!h->current)
585 return 0;
588 * Explanation: we don't send letter hotkeys to other widgets if
589 * the currently selected widget is an input line
592 if (h->current->options & W_IS_INPUT) {
593 if (d_key < 255 && isalpha (d_key))
594 return 0;
597 /* If it's an alt key, send the message */
598 c = d_key & ~ALT (0);
599 if (d_key & ALT (0) && c < 255 && isalpha (c))
600 d_key = tolower (c);
602 handled = 0;
603 if (h->current->options & W_WANT_HOTKEY)
604 handled = h->current->callback (h->current, WIDGET_HOTKEY, d_key);
606 /* If not used, send hotkey to other widgets */
607 if (handled)
608 return handled;
610 hot_cur = h->current;
612 /* send it to all widgets */
613 do {
614 if (hot_cur->options & W_WANT_HOTKEY)
615 handled |=
616 (*hot_cur->callback) (hot_cur, WIDGET_HOTKEY, d_key);
618 if (!handled)
619 hot_cur = hot_cur->next;
620 } while (h->current != hot_cur && !handled);
622 if (!handled)
623 return 0;
625 do_select_widget (h, hot_cur, SELECT_EXACT);
626 return handled;
629 static void
630 dlg_key_event (Dlg_head *h, int d_key)
632 int handled;
634 if (!h->current)
635 return;
637 /* TAB used to cycle */
638 if (!(h->flags & DLG_WANT_TAB)) {
639 if (d_key == '\t') {
640 dlg_one_down (h);
641 return;
642 } else if (d_key == KEY_BTAB) {
643 dlg_one_up (h);
644 return;
648 /* first can dlg_callback handle the key */
649 handled = (*h->callback) (h, DLG_KEY, d_key);
651 /* next try the hotkey */
652 if (!handled)
653 handled = dlg_try_hotkey (h, d_key);
655 if (handled)
656 (*h->callback) (h, DLG_HOTKEY_HANDLED, 0);
658 /* not used - then try widget_callback */
659 if (!handled)
660 handled = h->current->callback (h->current, WIDGET_KEY, d_key);
662 /* not used- try to use the unhandled case */
663 if (!handled)
664 handled = (*h->callback) (h, DLG_UNHANDLED_KEY, d_key);
666 if (!handled)
667 dialog_handle_key (h, d_key);
669 (*h->callback) (h, DLG_POST_KEY, d_key);
672 static inline int
673 dlg_mouse_event (Dlg_head * h, Gpm_Event * event)
675 Widget *item;
676 Widget *starting_widget = h->current;
677 Gpm_Event new_event;
678 int x = event->x;
679 int y = event->y;
681 item = starting_widget;
682 do {
683 Widget *widget = item;
685 item = item->next;
687 if (!((x > widget->x) && (x <= widget->x + widget->cols)
688 && (y > widget->y) && (y <= widget->y + widget->lines)))
689 continue;
691 new_event = *event;
692 new_event.x -= widget->x;
693 new_event.y -= widget->y;
695 if (!widget->mouse)
696 return MOU_NORMAL;
698 return (*widget->mouse) (&new_event, widget);
699 } while (item != starting_widget);
701 return MOU_NORMAL;
704 /* Run dialog routines */
706 /* Init the process */
707 void init_dlg (Dlg_head *h)
709 /* Initialize dialog manager and widgets */
710 (*h->callback) (h, DLG_INIT, 0);
711 dlg_broadcast_msg (h, WIDGET_INIT, 0);
713 if (h->x == 0 && h->y == 0 && h->cols == COLS && h->lines == LINES)
714 h->fullscreen = 1;
716 h->parent = current_dlg;
717 current_dlg = h;
719 /* Initialize the mouse status */
720 h->mouse_status = MOU_NORMAL;
722 /* Select the first widget that takes focus */
723 while (!dlg_focus (h) && h->current)
724 h->current = h->current->next;
726 /* Redraw the screen */
727 dlg_redraw (h);
729 h->ret_value = 0;
730 h->running = 1;
733 /* Shutdown the run_dlg */
734 void dlg_run_done (Dlg_head *h)
736 if (h->current)
737 (*h->callback) (h, DLG_END, 0);
739 current_dlg = h->parent;
742 void dlg_process_event (Dlg_head *h, int key, Gpm_Event *event)
744 if (key == EV_NONE){
745 if (got_interrupt ())
746 key = XCTRL('g');
747 else
748 return;
751 if (key == EV_MOUSE)
752 h->mouse_status = dlg_mouse_event (h, event);
753 else
754 dlg_key_event (h, key);
757 static inline void
758 frontend_run_dlg (Dlg_head *h)
760 int d_key;
761 Gpm_Event event;
763 event.x = -1;
764 while (h->running) {
765 if (winch_flag)
766 change_screen_size ();
768 if (is_idle ()) {
769 if (idle_hook)
770 execute_hooks (idle_hook);
772 while ((h->flags & DLG_WANT_IDLE) && is_idle ())
773 (*h->callback) (h, DLG_IDLE, 0);
775 /* Allow terminating the dialog from the idle handler */
776 if (!h->running)
777 break;
780 update_cursor (h);
782 /* Clear interrupt flag */
783 got_interrupt ();
784 d_key = get_event (&event, h->mouse_status == MOU_REPEAT, 1);
786 dlg_process_event (h, d_key, &event);
788 if (!h->running)
789 (*h->callback) (h, DLG_VALIDATE, 0);
793 /* Standard run dialog routine
794 * We have to keep this routine small so that we can duplicate it's
795 * behavior on complex routines like the file routines, this way,
796 * they can call the dlg_process_event without rewriting all the code
798 int run_dlg (Dlg_head *h)
800 init_dlg (h);
801 frontend_run_dlg (h);
802 dlg_run_done (h);
803 return h->ret_value;
806 void
807 destroy_dlg (Dlg_head *h)
809 int i;
810 Widget *c;
812 dlg_broadcast_msg (h, WIDGET_DESTROY, 0);
813 c = h->current;
814 for (i = 0; i < h->count; i++) {
815 c = c->next;
816 g_free (h->current);
817 h->current = c;
819 g_free (h->title);
820 g_free (h);
822 do_refresh ();
825 void widget_set_size (Widget *widget, int y, int x, int lines, int cols)
827 widget->x = x;
828 widget->y = y;
829 widget->cols = cols;
830 widget->lines = lines;
831 send_message (widget, WIDGET_RESIZED, 0 /* unused */);
834 /* Replace widget old_w for widget new_w in the dialog */
835 void
836 dlg_replace_widget (Widget *old_w, Widget *new_w)
838 Dlg_head *h = old_w->parent;
839 int should_focus = 0;
841 if (!h->current)
842 return;
844 if (old_w == h->current)
845 should_focus = 1;
847 new_w->parent = h;
848 new_w->dlg_id = old_w->dlg_id;
850 if (old_w == old_w->next) {
851 /* just one widget */
852 new_w->prev = new_w;
853 new_w->next = new_w;
854 } else {
855 new_w->prev = old_w->prev;
856 new_w->next = old_w->next;
857 old_w->prev->next = new_w;
858 old_w->next->prev = new_w;
861 if (should_focus)
862 h->current = new_w;
864 send_message (old_w, WIDGET_DESTROY, 0);
865 send_message (new_w, WIDGET_INIT, 0);
867 if (should_focus)
868 dlg_select_widget (new_w);
870 send_message (new_w, WIDGET_DRAW, 0);