[PATCH] Add init and deinit of event system.
[midnight-commander.git] / src / widget.c
blob0a07d9730408c565ed5e04638829e3fadcbc5c22
1 /* Widgets for the Midnight Commander
3 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
4 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
6 Authors: 1994, 1995 Radek Doulik
7 1994, 1995 Miguel de Icaza
8 1995 Jakub Jelinek
9 1996 Andrej Borsenkow
10 1997 Norbert Warmuth
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28 /** \file widget.c
29 * \brief Source: widgets
32 #include <config.h>
34 #include <assert.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <sys/types.h>
43 #include "global.h"
45 #include "../src/tty/tty.h"
46 #include "../src/skin/skin.h"
47 #include "../src/tty/mouse.h"
48 #include "../src/tty/key.h" /* XCTRL and ALT macros */
50 #include "../src/mcconfig/mcconfig.h" /* for history loading and saving */
52 #include "dialog.h"
53 #include "widget.h"
54 #include "wtools.h"
55 #include "strutil.h"
56 #include "../src/event/event.h"
58 #include "cmddef.h" /* CK_ cmd name const */
59 #include "keybind.h" /* global_key_map_t */
61 #define HISTORY_FILE_NAME ".mc/history"
63 const global_key_map_t *input_map;
65 static void
66 widget_selectcolor (Widget *w, gboolean focused, gboolean hotkey)
68 Dlg_head *h = w->parent;
70 tty_setcolor (hotkey
71 ? (focused
72 ? DLG_HOT_FOCUSC (h)
73 : DLG_HOT_NORMALC (h))
74 : (focused
75 ? DLG_FOCUSC (h)
76 : DLG_NORMALC (h)));
79 struct hotkey_t
80 parse_hotkey (const char *text)
82 struct hotkey_t result;
83 const char *cp, *p;
85 /* search for '&', that is not on the of text */
86 cp = strchr (text, '&');
87 if (cp != NULL && cp[1] != '\0') {
88 result.start = g_strndup (text, cp - text);
90 /* skip '&' */
91 cp++;
92 p = str_cget_next_char (cp);
93 result.hotkey = g_strndup (cp, p - cp);
95 cp = p;
96 result.end = g_strdup (cp);
97 } else {
98 result.start = g_strdup (text);
99 result.hotkey = NULL;
100 result.end = NULL;
103 return result;
105 void
106 release_hotkey (const struct hotkey_t hotkey)
108 g_free (hotkey.start);
109 g_free (hotkey.hotkey);
110 g_free (hotkey.end);
114 hotkey_width (const struct hotkey_t hotkey)
116 int result;
118 result = str_term_width1 (hotkey.start);
119 result+= (hotkey.hotkey != NULL) ? str_term_width1 (hotkey.hotkey) : 0;
120 result+= (hotkey.end != NULL) ? str_term_width1 (hotkey.end) : 0;
121 return result;
124 static void
125 draw_hotkey (Widget *w, const struct hotkey_t hotkey, gboolean focused)
127 widget_selectcolor (w, focused, FALSE);
128 tty_print_string (hotkey.start);
130 if (hotkey.hotkey != NULL) {
131 widget_selectcolor (w, focused, TRUE);
132 tty_print_string (hotkey.hotkey);
133 widget_selectcolor (w, focused, FALSE);
136 if (hotkey.end != NULL)
137 tty_print_string (hotkey.end);
141 /* Default callback for widgets */
142 cb_ret_t
143 default_proc (widget_msg_t msg, int parm)
145 (void) parm;
147 switch (msg) {
148 case WIDGET_INIT:
149 case WIDGET_FOCUS:
150 case WIDGET_UNFOCUS:
151 case WIDGET_DRAW:
152 case WIDGET_DESTROY:
153 case WIDGET_CURSOR:
154 case WIDGET_IDLE:
155 return MSG_HANDLED;
157 default:
158 return MSG_NOT_HANDLED;
162 static int button_event (Gpm_Event *event, void *);
164 int quote = 0;
166 static cb_ret_t
167 button_callback (Widget *w, widget_msg_t msg, int parm)
169 WButton *b = (WButton *) w;
170 int stop = 0;
171 int off = 0;
172 Dlg_head *h = b->widget.parent;
174 switch (msg) {
175 case WIDGET_HOTKEY:
177 * Don't let the default button steal Enter from the current
178 * button. This is a workaround for the flawed event model
179 * when hotkeys are sent to all widgets before the key is
180 * handled by the current widget.
182 if (parm == '\n' && h->current == &b->widget) {
183 button_callback (w, WIDGET_KEY, ' ');
184 return MSG_HANDLED;
187 if (parm == '\n' && b->flags == DEFPUSH_BUTTON) {
188 button_callback (w, WIDGET_KEY, ' ');
189 return MSG_HANDLED;
192 if (b->text.hotkey != NULL) {
193 if (g_ascii_tolower ((gchar)b->text.hotkey[0]) ==
194 g_ascii_tolower ((gchar)parm)) {
195 button_callback (w, WIDGET_KEY, ' ');
196 return MSG_HANDLED;
199 return MSG_NOT_HANDLED;
201 case WIDGET_KEY:
202 if (parm != ' ' && parm != '\n')
203 return MSG_NOT_HANDLED;
205 if (b->callback)
206 stop = (*b->callback) (b->action);
207 if (!b->callback || stop) {
208 h->ret_value = b->action;
209 dlg_stop (h);
211 return MSG_HANDLED;
213 case WIDGET_CURSOR:
214 switch (b->flags) {
215 case DEFPUSH_BUTTON:
216 off = 3;
217 break;
218 case NORMAL_BUTTON:
219 off = 2;
220 break;
221 case NARROW_BUTTON:
222 off = 1;
223 break;
224 case HIDDEN_BUTTON:
225 default:
226 off = 0;
227 break;
229 widget_move (&b->widget, 0, b->hotpos + off);
230 return MSG_HANDLED;
232 case WIDGET_UNFOCUS:
233 case WIDGET_FOCUS:
234 case WIDGET_DRAW:
235 if (msg == WIDGET_UNFOCUS)
236 b->selected = 0;
237 else if (msg == WIDGET_FOCUS)
238 b->selected = 1;
240 widget_selectcolor (w, b->selected, FALSE);
241 widget_move (w, 0, 0);
243 switch (b->flags) {
244 case DEFPUSH_BUTTON:
245 tty_print_string ("[< ");
246 break;
247 case NORMAL_BUTTON:
248 tty_print_string ("[ ");
249 break;
250 case NARROW_BUTTON:
251 tty_print_string ("[");
252 break;
253 case HIDDEN_BUTTON:
254 default:
255 return MSG_HANDLED;
258 draw_hotkey (w, b->text, b->selected);
260 switch (b->flags) {
261 case DEFPUSH_BUTTON:
262 tty_print_string (" >]");
263 break;
264 case NORMAL_BUTTON:
265 tty_print_string (" ]");
266 break;
267 case NARROW_BUTTON:
268 tty_print_string ("]");
269 break;
271 return MSG_HANDLED;
273 case WIDGET_DESTROY:
274 release_hotkey (b->text);
275 return MSG_HANDLED;
277 default:
278 return default_proc (msg, parm);
282 static int
283 button_event (Gpm_Event *event, void *data)
285 WButton *b = data;
287 if (event->type & (GPM_DOWN|GPM_UP)){
288 Dlg_head *h=b->widget.parent;
289 dlg_select_widget (b);
290 if (event->type & GPM_UP){
291 button_callback ((Widget *) data, WIDGET_KEY, ' ');
292 (*h->callback) (h, DLG_POST_KEY, ' ');
293 return MOU_NORMAL;
296 return MOU_NORMAL;
300 button_get_len (const WButton *b)
302 int ret = hotkey_width (b->text);
303 switch (b->flags) {
304 case DEFPUSH_BUTTON:
305 ret += 6;
306 break;
307 case NORMAL_BUTTON:
308 ret += 4;
309 break;
310 case NARROW_BUTTON:
311 ret += 2;
312 break;
313 case HIDDEN_BUTTON:
314 default:
315 return 0;
317 return ret;
320 WButton *
321 button_new (int y, int x, int action, int flags, const char *text,
322 bcback callback)
324 WButton *b = g_new (WButton, 1);
326 b->action = action;
327 b->flags = flags;
328 b->text = parse_hotkey (text);
330 init_widget (&b->widget, y, x, 1, button_get_len (b),
331 button_callback, button_event);
333 b->selected = 0;
334 b->callback = callback;
335 widget_want_hotkey (b->widget, 1);
336 b->hotpos = (b->text.hotkey != NULL) ? str_term_width1 (b->text.start) : -1;
338 return b;
341 const char *
342 button_get_text (const WButton *b)
344 if (b->text.hotkey != NULL)
345 return g_strconcat (b->text.start, "&", b->text.hotkey,
346 b->text.end, NULL);
347 else
348 return g_strdup (b->text.start);
351 void
352 button_set_text (WButton *b, const char *text)
354 release_hotkey (b->text);
355 b->text = parse_hotkey (text);
356 b->widget.cols = button_get_len (b);
357 dlg_redraw (b->widget.parent);
361 /* Radio button widget */
362 static int radio_event (Gpm_Event *event, void *);
364 static cb_ret_t
365 radio_callback (Widget *w, widget_msg_t msg, int parm)
367 WRadio *r = (WRadio *) w;
368 int i;
369 Dlg_head *h = r->widget.parent;
371 switch (msg) {
372 case WIDGET_HOTKEY:
374 int i, lp = g_ascii_tolower ((gchar)parm);
376 for (i = 0; i < r->count; i++) {
377 if (r->texts[i].hotkey != NULL) {
378 int c = g_ascii_tolower ((gchar)r->texts[i].hotkey[0]);
380 if (c != lp)
381 continue;
382 r->pos = i;
384 /* Take action */
385 radio_callback (w, WIDGET_KEY, ' ');
386 return MSG_HANDLED;
390 return MSG_NOT_HANDLED;
392 case WIDGET_KEY:
393 switch (parm) {
394 case ' ':
395 r->sel = r->pos;
396 (*h->callback) (h, DLG_ACTION, 0);
397 radio_callback (w, WIDGET_FOCUS, ' ');
398 return MSG_HANDLED;
400 case KEY_UP:
401 case KEY_LEFT:
402 if (r->pos > 0) {
403 r->pos--;
404 return MSG_HANDLED;
406 return MSG_NOT_HANDLED;
408 case KEY_DOWN:
409 case KEY_RIGHT:
410 if (r->count - 1 > r->pos) {
411 r->pos++;
412 return MSG_HANDLED;
415 return MSG_NOT_HANDLED;
417 case WIDGET_CURSOR:
418 (*h->callback) (h, DLG_ACTION, 0);
419 radio_callback (w, WIDGET_FOCUS, ' ');
420 widget_move (&r->widget, r->pos, 1);
421 return MSG_HANDLED;
423 case WIDGET_UNFOCUS:
424 case WIDGET_FOCUS:
425 case WIDGET_DRAW:
426 for (i = 0; i < r->count; i++) {
427 const gboolean focused = (i == r->pos && msg == WIDGET_FOCUS);
428 widget_selectcolor (w, focused, FALSE);
429 widget_move (&r->widget, i, 0);
430 tty_print_string ((r->sel == i) ? "(*) " : "( ) ");
431 draw_hotkey (w, r->texts[i], focused);
433 return MSG_HANDLED;
435 case WIDGET_DESTROY:
436 for (i = 0; i < r->count; i++) {
437 release_hotkey (r->texts[i]);
439 g_free (r->texts);
440 return MSG_HANDLED;
442 default:
443 return default_proc (msg, parm);
447 static int
448 radio_event (Gpm_Event *event, void *data)
450 WRadio *r = data;
451 Widget *w = data;
453 if (event->type & (GPM_DOWN|GPM_UP)){
454 Dlg_head *h = r->widget.parent;
456 r->pos = event->y - 1;
457 dlg_select_widget (r);
458 if (event->type & GPM_UP){
459 radio_callback (w, WIDGET_KEY, ' ');
460 radio_callback (w, WIDGET_FOCUS, 0);
461 (*h->callback) (h, DLG_POST_KEY, ' ');
462 return MOU_NORMAL;
465 return MOU_NORMAL;
468 WRadio *
469 radio_new (int y, int x, int count, const char **texts)
471 WRadio *result = g_new (WRadio, 1);
472 int i, max, m;
474 /* Compute the longest string */
475 result->texts = g_new (struct hotkey_t, count);
477 max = 0;
478 for (i = 0; i < count; i++){
479 result->texts[i] = parse_hotkey (texts[i]);
480 m = hotkey_width (result->texts[i]);
481 if (m > max)
482 max = m;
485 init_widget (&result->widget, y, x, count, max, radio_callback, radio_event);
486 result->state = 1;
487 result->pos = 0;
488 result->sel = 0;
489 result->count = count;
490 widget_want_hotkey (result->widget, 1);
492 return result;
496 /* Checkbutton widget */
498 static int check_event (Gpm_Event *event, void *);
500 static cb_ret_t
501 check_callback (Widget *w, widget_msg_t msg, int parm)
503 WCheck *c = (WCheck *) w;
504 Dlg_head *h = c->widget.parent;
506 switch (msg) {
507 case WIDGET_HOTKEY:
508 if (c->text.hotkey != NULL) {
509 if (g_ascii_tolower ((gchar)c->text.hotkey[0]) ==
510 g_ascii_tolower ((gchar)parm)) {
512 check_callback (w, WIDGET_KEY, ' '); /* make action */
513 return MSG_HANDLED;
516 return MSG_NOT_HANDLED;
518 case WIDGET_KEY:
519 if (parm != ' ')
520 return MSG_NOT_HANDLED;
521 c->state ^= C_BOOL;
522 c->state ^= C_CHANGE;
523 (*h->callback) (h, DLG_ACTION, 0);
524 check_callback (w, WIDGET_FOCUS, ' ');
525 return MSG_HANDLED;
527 case WIDGET_CURSOR:
528 widget_move (&c->widget, 0, 1);
529 return MSG_HANDLED;
531 case WIDGET_FOCUS:
532 case WIDGET_UNFOCUS:
533 case WIDGET_DRAW:
534 widget_selectcolor (w, msg == WIDGET_FOCUS, FALSE);
535 widget_move (&c->widget, 0, 0);
536 tty_print_string ((c->state & C_BOOL) ? "[x] " : "[ ] ");
537 draw_hotkey (w, c->text, msg == WIDGET_FOCUS);
538 return MSG_HANDLED;
540 case WIDGET_DESTROY:
541 release_hotkey (c->text);
542 return MSG_HANDLED;
544 default:
545 return default_proc (msg, parm);
549 static int
550 check_event (Gpm_Event *event, void *data)
552 WCheck *c = data;
553 Widget *w = data;
555 if (event->type & (GPM_DOWN|GPM_UP)){
556 Dlg_head *h = c->widget.parent;
558 dlg_select_widget (c);
559 if (event->type & GPM_UP){
560 check_callback (w, WIDGET_KEY, ' ');
561 check_callback (w, WIDGET_FOCUS, 0);
562 (*h->callback) (h, DLG_POST_KEY, ' ');
563 return MOU_NORMAL;
566 return MOU_NORMAL;
569 WCheck *
570 check_new (int y, int x, int state, const char *text)
572 WCheck *c = g_new (WCheck, 1);
574 c->text = parse_hotkey (text);
576 init_widget (&c->widget, y, x, 1, hotkey_width (c->text),
577 check_callback, check_event);
578 c->state = state ? C_BOOL : 0;
579 widget_want_hotkey (c->widget, 1);
581 return c;
585 /* Label widget */
587 static cb_ret_t
588 label_callback (Widget *w, widget_msg_t msg, int parm)
590 WLabel *l = (WLabel *) w;
591 Dlg_head *h = l->widget.parent;
593 switch (msg) {
594 case WIDGET_INIT:
595 return MSG_HANDLED;
597 /* We don't want to get the focus */
598 case WIDGET_FOCUS:
599 return MSG_NOT_HANDLED;
601 case WIDGET_DRAW:
603 char *p = l->text, *q, c = 0;
604 int y = 0;
606 if (!l->text)
607 return MSG_HANDLED;
609 if (l->transparent)
610 tty_setcolor (DEFAULT_COLOR);
611 else
612 tty_setcolor (DLG_NORMALC (h));
614 for (;;) {
615 q = strchr (p, '\n');
616 if (q != NULL) {
617 c = q[0];
618 q[0] = '\0';
621 widget_move (&l->widget, y, 0);
622 tty_print_string (str_fit_to_term (p, l->widget.cols, J_LEFT));
624 if (q == NULL)
625 break;
626 q[0] = c;
627 p = q + 1;
628 y++;
630 return MSG_HANDLED;
633 case WIDGET_DESTROY:
634 g_free (l->text);
635 return MSG_HANDLED;
637 default:
638 return default_proc (msg, parm);
642 void
643 label_set_text (WLabel *label, const char *text)
645 int newcols = label->widget.cols;
646 int newlines;
648 if (label->text && text && !strcmp (label->text, text))
649 return; /* Flickering is not nice */
651 g_free (label->text);
653 if (text != NULL) {
654 label->text = g_strdup (text);
655 if (label->auto_adjust_cols) {
656 str_msg_term_size (text, &newlines, &newcols);
657 if (newcols > label->widget.cols)
658 label->widget.cols = newcols;
659 if (newlines > label->widget.lines)
660 label->widget.lines = newlines;
662 } else label->text = NULL;
664 if (label->widget.parent)
665 label_callback ((Widget *) label, WIDGET_DRAW, 0);
667 if (newcols < label->widget.cols)
668 label->widget.cols = newcols;
671 WLabel *
672 label_new (int y, int x, const char *text)
674 WLabel *l;
675 int cols = 1;
676 int lines = 1;
678 if (text != NULL)
679 str_msg_term_size (text, &lines, &cols);
681 l = g_new (WLabel, 1);
682 init_widget (&l->widget, y, x, lines, cols, label_callback, NULL);
683 l->text = (text != NULL) ? g_strdup (text) : NULL;
684 l->auto_adjust_cols = 1;
685 l->transparent = 0;
686 widget_want_cursor (l->widget, 0);
687 return l;
691 /* Gauge widget (progress indicator) */
692 /* Currently width is hardcoded here for text mode */
693 #define gauge_len 47
695 static cb_ret_t
696 gauge_callback (Widget *w, widget_msg_t msg, int parm)
698 WGauge *g = (WGauge *) w;
699 Dlg_head *h = g->widget.parent;
701 if (msg == WIDGET_INIT)
702 return MSG_HANDLED;
704 /* We don't want to get the focus */
705 if (msg == WIDGET_FOCUS)
706 return MSG_NOT_HANDLED;
708 if (msg == WIDGET_DRAW){
709 widget_move (&g->widget, 0, 0);
710 tty_setcolor (DLG_NORMALC (h));
711 if (!g->shown)
712 tty_printf ("%*s", gauge_len, "");
713 else {
714 int percentage, columns;
715 long total = g->max, done = g->current;
717 if (total <= 0 || done < 0) {
718 done = 0;
719 total = 100;
721 if (done > total)
722 done = total;
723 while (total > 65535) {
724 total /= 256;
725 done /= 256;
727 percentage = (200 * done / total + 1) / 2;
728 columns = (2 * (gauge_len - 7) * done / total + 1) / 2;
729 tty_print_char ('[');
730 tty_setcolor (GAUGE_COLOR);
731 tty_printf ("%*s", (int) columns, "");
732 tty_setcolor (DLG_NORMALC (h));
733 tty_printf ("%*s] %3d%%", (int)(gauge_len - 7 - columns), "", (int) percentage);
735 return MSG_HANDLED;
738 return default_proc (msg, parm);
741 void
742 gauge_set_value (WGauge *g, int max, int current)
744 if (g->current == current && g->max == max)
745 return; /* Do not flicker */
746 if (max == 0)
747 max = 1; /* I do not like division by zero :) */
749 g->current = current;
750 g->max = max;
751 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
754 void
755 gauge_show (WGauge *g, int shown)
757 if (g->shown == shown)
758 return;
759 g->shown = shown;
760 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
763 WGauge *
764 gauge_new (int y, int x, int shown, int max, int current)
766 WGauge *g = g_new (WGauge, 1);
768 init_widget (&g->widget, y, x, 1, gauge_len, gauge_callback, NULL);
769 g->shown = shown;
770 if (max == 0)
771 max = 1; /* I do not like division by zero :) */
772 g->max = max;
773 g->current = current;
774 widget_want_cursor (g->widget, 0);
775 return g;
779 /* Input widget */
781 /* {{{ history button */
783 #define LARGE_HISTORY_BUTTON 1
785 #ifdef LARGE_HISTORY_BUTTON
786 # define HISTORY_BUTTON_WIDTH 3
787 #else
788 # define HISTORY_BUTTON_WIDTH 1
789 #endif
791 #define should_show_history_button(in) \
792 (in->history && in->field_width > HISTORY_BUTTON_WIDTH * 2 + 1 && in->widget.parent)
794 static void draw_history_button (WInput * in)
796 char c;
797 c = in->history->next ? (in->history->prev ? '|' : 'v') : '^';
798 widget_move (&in->widget, 0, in->field_width - HISTORY_BUTTON_WIDTH);
799 #ifdef LARGE_HISTORY_BUTTON
801 Dlg_head *h;
802 h = in->widget.parent;
803 tty_setcolor (NORMAL_COLOR);
804 tty_print_string ("[ ]");
805 /* Too distracting: tty_setcolor (MARKED_COLOR); */
806 widget_move (&in->widget, 0, in->field_width - HISTORY_BUTTON_WIDTH + 1);
807 tty_print_char (c);
809 #else
810 tty_setcolor (MARKED_COLOR);
811 tty_print_char (c);
812 #endif
815 /* }}} history button */
818 /* Input widgets now have a global kill ring */
819 /* Pointer to killed data */
820 static char *kill_buffer = 0;
822 void
823 update_input (WInput *in, int clear_first)
825 int has_history = 0;
826 int i;
827 int buf_len = str_length (in->buffer);
828 const char *cp;
829 int pw;
831 if (should_show_history_button (in))
832 has_history = HISTORY_BUTTON_WIDTH;
834 if (in->disable_update)
835 return;
837 pw = str_term_width2 (in->buffer, in->point);
839 /* Make the point visible */
840 if ((pw < in->term_first_shown) ||
841 (pw >= in->term_first_shown + in->field_width - has_history)) {
843 in->term_first_shown = pw - (in->field_width / 3);
844 if (in->term_first_shown < 0)
845 in->term_first_shown = 0;
848 /* Adjust the mark */
849 if (in->mark > buf_len)
850 in->mark = buf_len;
852 if (has_history)
853 draw_history_button (in);
855 tty_setcolor (in->color);
857 widget_move (&in->widget, 0, 0);
859 if (!in->is_password) {
860 tty_print_string (str_term_substring (in->buffer, in->term_first_shown,
861 in->field_width - has_history));
862 } else {
863 cp = in->buffer;
864 for (i = -in->term_first_shown; i < in->field_width - has_history; i++){
865 if (i >= 0) {
866 tty_print_char ((cp[0] != '\0') ? '*' : ' ');
868 if (cp[0] != '\0') str_cnext_char (&cp);
872 if (clear_first)
873 in->first = 0;
876 void
877 winput_set_origin (WInput *in, int x, int field_width)
879 in->widget.x = x;
880 in->field_width = in->widget.cols = field_width;
881 update_input (in, 0);
884 /* {{{ history saving and loading */
886 int num_history_items_recorded = 60;
889 This loads and saves the history of an input line to and from the
890 widget. It is called with the widgets history name on creation of the
891 widget, and returns the GList list. It stores histories in the file
892 ~/.mc/history in using the profile code.
894 If def_text is passed as INPUT_LAST_TEXT (to the input_new()
895 function) then input_new assigns the default text to be the last text
896 entered, or "" if not found.
899 GList *
900 history_get (const char *input_name)
902 size_t i;
903 GList *hist = NULL;
904 char *profile;
905 mc_config_t *cfg;
906 char **keys;
907 size_t keys_num = 0;
908 char *this_entry;
910 if (!num_history_items_recorded) /* this is how to disable */
911 return NULL;
912 if (!input_name || !*input_name)
913 return NULL;
915 profile = concat_dir_and_file (home_dir, HISTORY_FILE_NAME);
916 cfg = mc_config_init (profile);
918 /* get number of keys */
919 keys = mc_config_get_keys (cfg, input_name, &keys_num);
920 g_strfreev (keys);
922 for (i = 0; i < keys_num; i++) {
923 char key_name[BUF_TINY];
924 g_snprintf (key_name, sizeof (key_name), "%lu", (unsigned long)i);
925 this_entry = mc_config_get_string (cfg, input_name, key_name, "");
927 if (this_entry && *this_entry)
928 hist = list_append_unique (hist, this_entry);
931 mc_config_deinit (cfg);
932 g_free (profile);
934 /* return pointer to the last entry in the list */
935 return g_list_last (hist);
938 void
939 history_put (const char *input_name, GList *h)
941 int i;
942 char *profile;
943 mc_config_t *cfg;
945 if (!input_name)
946 return;
948 if (!*input_name)
949 return;
951 if (!h)
952 return;
954 if (!num_history_items_recorded) /* this is how to disable */
955 return;
957 profile = concat_dir_and_file (home_dir, HISTORY_FILE_NAME);
959 if ((i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) != -1)
960 close (i);
962 /* Make sure the history is only readable by the user */
963 if (chmod (profile, S_IRUSR | S_IWUSR) == -1 && errno != ENOENT) {
964 g_free (profile);
965 return;
968 /* go to end of list */
969 h = g_list_last (h);
971 /* go back 60 places */
972 for (i = 0; i < num_history_items_recorded - 1 && h->prev; i++)
973 h = g_list_previous (h);
975 cfg = mc_config_init(profile);
977 if (input_name)
978 mc_config_del_group(cfg,input_name);
980 /* dump histories into profile */
981 for (i = 0; h; h = g_list_next (h)) {
982 char *text;
984 text = (char *) h->data;
986 /* We shouldn't have null entries, but let's be sure */
987 if (text && *text) {
988 char key_name[BUF_TINY];
989 g_snprintf (key_name, sizeof (key_name), "%d", i++);
990 mc_config_set_string(cfg,input_name, key_name, text);
994 mc_config_save_file (cfg);
995 mc_config_deinit(cfg);
996 g_free (profile);
999 /* }}} history saving and loading */
1002 /* {{{ history display */
1004 static const char *
1005 i18n_htitle (void)
1007 return _(" History ");
1010 static void
1011 listbox_fwd (WListbox *l)
1013 if (l->current != l->list->prev)
1014 listbox_select_entry (l, l->current->next);
1015 else
1016 listbox_select_first (l);
1019 typedef struct {
1020 Widget *widget;
1021 int count;
1022 size_t maxlen;
1023 } dlg_hist_data;
1025 static cb_ret_t
1026 dlg_hist_reposition (Dlg_head *dlg_head)
1028 dlg_hist_data *data;
1029 int x = 0, y, he, wi;
1031 /* guard checks */
1032 if ((dlg_head == NULL)
1033 || (dlg_head->data == NULL))
1034 return MSG_NOT_HANDLED;
1036 data = (dlg_hist_data *) dlg_head->data;
1038 y = data->widget->y;
1039 he = data->count + 2;
1041 if (he <= y || y > (LINES - 6)) {
1042 he = min (he, y - 1);
1043 y -= he;
1044 } else {
1045 y++;
1046 he = min (he, LINES - y);
1049 if (data->widget->x > 2)
1050 x = data->widget->x - 2;
1052 wi = data->maxlen + 4;
1054 if ((wi + x) > COLS) {
1055 wi = min (wi, COLS);
1056 x = COLS - wi;
1059 dlg_set_position (dlg_head, y, x, y + he, x + wi);
1061 return MSG_HANDLED;
1064 static cb_ret_t
1065 dlg_hist_callback (Dlg_head *h, dlg_msg_t msg, int parm)
1067 switch (msg) {
1068 case DLG_RESIZE:
1069 return dlg_hist_reposition (h);
1071 default:
1072 return default_dlg_callback (h, msg, parm);
1076 char *
1077 show_hist (GList *history, Widget *widget)
1079 GList *hi, *z;
1080 size_t maxlen, i, count = 0;
1081 char *q, *r = NULL;
1082 Dlg_head *query_dlg;
1083 WListbox *query_list;
1084 dlg_hist_data hist_data;
1086 if (history == NULL)
1087 return NULL;
1089 maxlen = str_term_width1 (i18n_htitle ());
1091 z = g_list_first (history);
1092 hi = z;
1093 while (hi) {
1094 i = str_term_width1 ((char *) hi->data);
1095 maxlen = max (maxlen, i);
1096 count++;
1097 hi = g_list_next (hi);
1100 hist_data.maxlen = maxlen;
1101 hist_data.widget = widget;
1102 hist_data.count = count;
1104 query_dlg =
1105 create_dlg (0, 0, 4, 4, dialog_colors, dlg_hist_callback,
1106 "[History-query]", i18n_htitle (), DLG_COMPACT);
1107 query_dlg->data = &hist_data;
1109 query_list = listbox_new (1, 1, 2, 2, NULL);
1111 /* this call makes list stick to all sides of dialog, effectively make
1112 it be resized with dialog */
1113 add_widget_autopos (query_dlg, query_list, WPOS_KEEP_ALL);
1115 /* to avoid diplicating of (calculating sizes in two places)
1116 code, call dlg_hist_callback function here, to set dialog and
1117 controls positions.
1118 The main idea - create 4x4 dialog and add 2x2 list in
1119 center of it, and let dialog function resize it to needed
1120 size. */
1121 dlg_hist_callback (query_dlg, DLG_RESIZE, 0);
1123 if (query_dlg->y < widget->y) {
1124 /* traverse */
1125 hi = z;
1126 while (hi) {
1127 listbox_add_item (query_list, LISTBOX_APPEND_AT_END,
1128 0, (char *) hi->data, NULL);
1129 hi = g_list_next (hi);
1131 listbox_select_last (query_list);
1132 } else {
1133 /* traverse backwards */
1134 hi = g_list_last (history);
1135 while (hi) {
1136 listbox_add_item (query_list, LISTBOX_APPEND_AT_END,
1137 0, (char *) hi->data, NULL);
1138 hi = g_list_previous (hi);
1142 if (run_dlg (query_dlg) != B_CANCEL) {
1143 listbox_get_current (query_list, &q, NULL);
1144 if (q != NULL)
1145 r = g_strdup (q);
1147 destroy_dlg (query_dlg);
1148 return r;
1151 static void
1152 do_show_hist (WInput *in)
1154 char *r;
1155 r = show_hist (in->history, &in->widget);
1156 if (r) {
1157 assign_text (in, r);
1158 g_free (r);
1162 /* }}} history display */
1164 static void
1165 input_destroy (WInput *in)
1167 if (!in){
1168 fprintf (stderr, "Internal error: null Input *\n");
1169 mc_event_deinit();
1170 exit (1);
1173 new_input (in);
1175 if (in->history){
1176 if (!in->is_password) /* don't save passwords ;-) */
1177 history_put (in->history_name, in->history);
1179 in->history = g_list_first (in->history);
1180 g_list_foreach (in->history, (GFunc) g_free, NULL);
1181 g_list_free (in->history);
1184 g_free (in->buffer);
1185 free_completions (in);
1186 g_free (in->history_name);
1189 void
1190 input_disable_update (WInput *in)
1192 in->disable_update++;
1195 void
1196 input_enable_update (WInput *in)
1198 in->disable_update--;
1199 update_input (in, 0);
1202 #define ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
1205 push_history (WInput *in, const char *text)
1207 static int i18n;
1208 /* input widget where urls with passwords are entered without any
1209 vfs prefix */
1210 static const char *password_input_fields[] = {
1211 N_(" Link to a remote machine "),
1212 N_(" FTP to machine "),
1213 N_(" SMB link to machine ")
1215 char *t;
1216 const char *p;
1217 size_t i;
1219 if (!i18n) {
1220 i18n = 1;
1221 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1222 password_input_fields[i] = _(password_input_fields[i]);
1225 for (p = text; *p == ' ' || *p == '\t'; p++);
1226 if (!*p)
1227 return 0;
1229 if (in->history) {
1230 /* Avoid duplicated entries */
1231 in->history = g_list_last (in->history);
1232 if (!strcmp ((char *) in->history->data, text))
1233 return 1;
1236 t = g_strdup (text);
1238 if (in->history_name) {
1239 p = in->history_name + 3;
1240 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1241 if (strcmp (p, password_input_fields[i]) == 0)
1242 break;
1243 if (i < ELEMENTS (password_input_fields))
1244 strip_password (t, 0);
1245 else
1246 strip_password (t, 1);
1249 in->history = list_append_unique (in->history, t);
1250 in->need_push = 0;
1252 return 2;
1255 #undef ELEMENTS
1257 /* Cleans the input line and adds the current text to the history */
1258 void
1259 new_input (WInput *in)
1261 if (in->buffer)
1262 push_history (in, in->buffer);
1263 in->need_push = 1;
1264 in->buffer[0] = '\0';
1265 in->point = 0;
1266 in->charpoint = 0;
1267 in->mark = 0;
1268 free_completions (in);
1269 update_input (in, 0);
1272 static void
1273 move_buffer_backward (WInput *in, int start, int end)
1275 int i, pos, len;
1276 int str_len = str_length (in->buffer);
1277 if (start >= str_len || end > str_len + 1) return;
1279 pos = str_offset_to_pos (in->buffer, start);
1280 len = str_offset_to_pos (in->buffer, end) - pos;
1282 for (i = pos; in->buffer[i + len - 1]; i++)
1283 in->buffer[i] = in->buffer[i + len];
1286 static cb_ret_t
1287 insert_char (WInput *in, int c_code)
1289 size_t i;
1290 int res;
1292 if (c_code == -1)
1293 return MSG_NOT_HANDLED;
1295 if (in->charpoint >= MB_LEN_MAX)
1296 return MSG_HANDLED;
1298 in->charbuf[in->charpoint] = c_code;
1299 in->charpoint++;
1301 res = str_is_valid_char (in->charbuf, in->charpoint);
1302 if (res < 0) {
1303 if (res != -2)
1304 in->charpoint = 0; /* broken multibyte char, skip */
1305 return MSG_HANDLED;
1308 in->need_push = 1;
1309 if (strlen (in->buffer) + 1 + in->charpoint >= in->current_max_size){
1310 /* Expand the buffer */
1311 size_t new_length = in->current_max_size +
1312 in->field_width + in->charpoint;
1313 char *narea = g_try_renew (char, in->buffer, new_length);
1314 if (narea){
1315 in->buffer = narea;
1316 in->current_max_size = new_length;
1320 if (strlen (in->buffer) + in->charpoint < in->current_max_size) {
1321 /* bytes from begin */
1322 size_t ins_point = str_offset_to_pos (in->buffer, in->point);
1323 /* move chars */
1324 size_t rest_bytes = strlen (in->buffer + ins_point);
1326 for (i = rest_bytes + 1; i > 0; i--)
1327 in->buffer[ins_point + i + in->charpoint - 1] =
1328 in->buffer[ins_point + i - 1];
1330 memcpy(in->buffer + ins_point, in->charbuf, in->charpoint);
1331 in->point++;
1334 in->charpoint = 0;
1335 return MSG_HANDLED;
1338 static void
1339 beginning_of_line (WInput *in)
1341 in->point = 0;
1342 in->charpoint = 0;
1345 static void
1346 end_of_line (WInput *in)
1348 in->point = str_length (in->buffer);
1349 in->charpoint = 0;
1352 static void
1353 backward_char (WInput *in)
1355 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1357 if (in->point > 0) {
1358 in->point-= str_cprev_noncomb_char (&act, in->buffer);
1360 in->charpoint = 0;
1363 static void
1364 forward_char (WInput *in)
1366 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1367 if (act[0] != '\0') {
1368 in->point+= str_cnext_noncomb_char (&act);
1370 in->charpoint = 0;
1373 static void
1374 forward_word (WInput * in)
1376 const char *p = in->buffer + str_offset_to_pos (in->buffer, in->point);
1378 while (p[0] != '\0' && (str_isspace (p) || str_ispunct (p))) {
1379 str_cnext_char (&p);
1380 in->point++;
1382 while (p[0] != '\0' && !str_isspace (p) && !str_ispunct (p)) {
1383 str_cnext_char (&p);
1384 in->point++;
1388 static void
1389 backward_word (WInput *in)
1391 const char *p = in->buffer + str_offset_to_pos (in->buffer, in->point);
1393 while ((p != in->buffer) && (p[0] == '\0')) {
1394 p--;
1395 in->point--;
1398 while ((p != in->buffer) && (str_isspace (p) || str_ispunct (p))) {
1399 str_cprev_char (&p);
1400 in->point--;
1402 while ((p != in->buffer) && !str_isspace (p) && !str_ispunct (p)) {
1403 str_cprev_char (&p);
1404 in->point--;
1408 static void
1409 key_left (WInput *in)
1411 backward_char (in);
1414 static void
1415 key_ctrl_left (WInput *in)
1417 backward_word (in);
1420 static void
1421 key_right (WInput *in)
1423 forward_char (in);
1426 static void
1427 key_ctrl_right (WInput *in)
1429 forward_word (in);
1431 static void
1432 backward_delete (WInput *in)
1434 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1435 int start;
1437 if (in->point == 0)
1438 return;
1440 start = in->point - str_cprev_noncomb_char (&act, in->buffer);
1441 move_buffer_backward(in, start, in->point);
1442 in->charpoint = 0;
1443 in->need_push = 1;
1444 in->point = start;
1447 static void
1448 delete_char (WInput *in)
1450 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1451 int end = in->point;
1453 end+= str_cnext_noncomb_char (&act);
1455 move_buffer_backward(in, in->point, end);
1456 in->charpoint = 0;
1457 in->need_push = 1;
1460 static void
1461 copy_region (WInput *in, int x_first, int x_last)
1463 int first = min (x_first, x_last);
1464 int last = max (x_first, x_last);
1466 if (last == first)
1467 return;
1469 g_free (kill_buffer);
1471 first = str_offset_to_pos (in->buffer, first);
1472 last = str_offset_to_pos (in->buffer, last);
1474 kill_buffer = g_strndup(in->buffer + first, last - first);
1477 static void
1478 delete_region (WInput *in, int x_first, int x_last)
1480 int first = min (x_first, x_last);
1481 int last = max (x_first, x_last);
1482 size_t len;
1484 in->point = first;
1485 in->mark = first;
1486 last = str_offset_to_pos (in->buffer, last);
1487 first = str_offset_to_pos (in->buffer, first);
1488 len = strlen (&in->buffer[last]) + 1;
1489 memmove (&in->buffer[first], &in->buffer[last], len);
1490 in->charpoint = 0;
1491 in->need_push = 1;
1494 static void
1495 kill_word (WInput *in)
1497 int old_point = in->point;
1498 int new_point;
1500 forward_word (in);
1501 new_point = in->point;
1502 in->point = old_point;
1504 copy_region (in, old_point, new_point);
1505 delete_region (in, old_point, new_point);
1506 in->need_push = 1;
1507 in->charpoint = 0;
1508 in->charpoint = 0;
1511 static void
1512 back_kill_word (WInput *in)
1514 int old_point = in->point;
1515 int new_point;
1517 backward_word (in);
1518 new_point = in->point;
1519 in->point = old_point;
1521 copy_region (in, old_point, new_point);
1522 delete_region (in, old_point, new_point);
1523 in->need_push = 1;
1526 static void
1527 set_mark (WInput *in)
1529 in->mark = in->point;
1532 static void
1533 kill_save (WInput *in)
1535 copy_region (in, in->mark, in->point);
1538 static void
1539 kill_region (WInput *in)
1541 kill_save (in);
1542 delete_region (in, in->point, in->mark);
1545 static void
1546 yank (WInput *in)
1548 char *p;
1550 if (!kill_buffer)
1551 return;
1552 in->charpoint = 0;
1553 for (p = kill_buffer; *p; p++)
1554 insert_char (in, *p);
1555 in->charpoint = 0;
1558 static void
1559 kill_line (WInput *in)
1561 int chp = str_offset_to_pos (in->buffer, in->point);
1562 g_free (kill_buffer);
1563 kill_buffer = g_strdup (&in->buffer[chp]);
1564 in->buffer[chp] = '\0';
1565 in->charpoint = 0;
1568 void
1569 assign_text (WInput *in, const char *text)
1571 free_completions (in);
1572 g_free (in->buffer);
1573 in->buffer = g_strdup (text); /* was in->buffer->text */
1574 in->current_max_size = strlen (in->buffer) + 1;
1575 in->point = str_length (in->buffer);
1576 in->mark = 0;
1577 in->need_push = 1;
1578 in->charpoint = 0;
1581 static void
1582 hist_prev (WInput *in)
1584 if (!in->history)
1585 return;
1587 if (in->need_push) {
1588 switch (push_history (in, in->buffer)) {
1589 case 2:
1590 in->history = g_list_previous (in->history);
1591 break;
1592 case 1:
1593 if (in->history->prev)
1594 in->history = g_list_previous (in->history);
1595 break;
1596 case 0:
1597 break;
1599 } else if (in->history->prev)
1600 in->history = g_list_previous (in->history);
1601 else
1602 return;
1603 assign_text (in, (char *) in->history->data);
1604 in->need_push = 0;
1607 static void
1608 hist_next (WInput *in)
1610 if (in->need_push) {
1611 switch (push_history (in, in->buffer)) {
1612 case 2:
1613 assign_text (in, "");
1614 return;
1615 case 0:
1616 return;
1620 if (!in->history)
1621 return;
1623 if (!in->history->next) {
1624 assign_text (in, "");
1625 return;
1628 in->history = g_list_next (in->history);
1629 assign_text (in, (char *) in->history->data);
1630 in->need_push = 0;
1633 static void
1634 port_region_marked_for_delete (WInput *in)
1636 in->buffer[0] = '\0';
1637 in->point = 0;
1638 in->first = 0;
1639 in->charpoint = 0;
1642 static cb_ret_t
1643 input_execute_cmd (WInput *in, int command)
1645 cb_ret_t res = MSG_HANDLED;
1647 switch (command) {
1648 case CK_InputBol:
1649 beginning_of_line (in);
1650 break;
1651 case CK_InputEol:
1652 end_of_line (in);
1653 break;
1654 case CK_InputMoveLeft:
1655 key_left (in);
1656 break;
1657 case CK_InputWordLeft:
1658 key_ctrl_left (in);
1659 break;
1660 case CK_InputMoveRight:
1661 key_right (in);
1662 break;
1663 case CK_InputWordRight:
1664 key_ctrl_right (in);
1665 break;
1666 case CK_InputBackwardChar:
1667 backward_char (in);
1668 break;
1669 case CK_InputBackwardWord:
1670 backward_word (in);
1671 break;
1672 case CK_InputForwardChar:
1673 forward_char (in);
1674 break;
1675 case CK_InputForwardWord:
1676 forward_word (in);
1677 break;
1678 case CK_InputBackwardDelete:
1679 backward_delete (in);
1680 break;
1681 case CK_InputDeleteChar:
1682 delete_char (in);
1683 break;
1684 case CK_InputKillWord:
1685 kill_word (in);
1686 break;
1687 case CK_InputBackwardKillWord:
1688 back_kill_word (in);
1689 break;
1690 case CK_InputSetMark:
1691 set_mark (in);
1692 break;
1693 case CK_InputKillRegion:
1694 kill_region (in);
1695 break;
1696 case CK_InputKillSave:
1697 kill_save (in);
1698 break;
1699 case CK_InputYank:
1700 yank (in);
1701 break;
1702 case CK_InputKillLine:
1703 kill_line (in);
1704 break;
1705 case CK_InputHistoryPrev:
1706 hist_prev (in);
1707 break;
1708 case CK_InputHistoryNext:
1709 hist_next (in);
1710 break;
1711 case CK_InputHistoryShow:
1712 do_show_hist (in);
1713 break;
1714 case CK_InputComplete:
1715 complete (in);
1716 break;
1717 default:
1718 res = MSG_NOT_HANDLED;
1721 return res;
1724 /* This function is a test for a special input key used in complete.c */
1725 /* Returns 0 if it is not a special key, 1 if it is a non-complete key
1726 and 2 if it is a complete key */
1728 is_in_input_map (WInput *in, int key)
1730 int i;
1732 for (i = 0; input_map[i].key; i++) {
1733 if (key == input_map[i].key) {
1734 input_execute_cmd (in, input_map[i].command);
1735 if (input_map[i].command == CK_InputComplete)
1736 return 2;
1737 else
1738 return 1;
1741 return 0;
1744 cb_ret_t
1745 handle_char (WInput *in, int key)
1747 cb_ret_t v;
1748 int i;
1750 v = MSG_NOT_HANDLED;
1752 if (quote) {
1753 free_completions (in);
1754 v = insert_char (in, key);
1755 update_input (in, 1);
1756 quote = 0;
1757 return v;
1760 for (i = 0; input_map[i].key; i++) {
1761 if (key == input_map[i].key) {
1762 if (input_map[i].command != CK_InputComplete) {
1763 free_completions (in);
1764 input_execute_cmd (in, input_map[i].command);
1765 update_input (in, 1);
1766 v = MSG_HANDLED;
1767 break;
1771 if (input_map[i].command == 0) {
1772 if (key > 255)
1773 return MSG_NOT_HANDLED;
1774 if (in->first)
1775 port_region_marked_for_delete (in);
1776 free_completions (in);
1777 v = insert_char (in, key);
1779 update_input (in, 1);
1780 return v;
1783 /* Inserts text in input line */
1784 void
1785 stuff (WInput *in, const char *text, int insert_extra_space)
1787 input_disable_update (in);
1788 while (*text)
1789 handle_char (in, *text++);
1790 if (insert_extra_space)
1791 handle_char (in, ' ');
1792 input_enable_update (in);
1793 update_input (in, 1);
1796 void
1797 input_set_point (WInput *in, int pos)
1799 int max_pos = str_length (in->buffer);
1801 if (pos > max_pos)
1802 pos = max_pos;
1803 if (pos != in->point)
1804 free_completions (in);
1805 in->point = pos;
1806 in->charpoint = 0;
1807 update_input (in, 1);
1810 cb_ret_t
1811 input_callback (Widget *w, widget_msg_t msg, int parm)
1813 WInput *in = (WInput *) w;
1814 cb_ret_t v;
1816 switch (msg) {
1817 case WIDGET_KEY:
1818 if (parm == XCTRL ('q')) {
1819 quote = 1;
1820 v = handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
1821 quote = 0;
1822 return v;
1825 /* Keys we want others to handle */
1826 if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1827 || parm == KEY_F (10) || parm == XCTRL ('g') || parm == '\n')
1828 return MSG_NOT_HANDLED;
1830 /* When pasting multiline text, insert literal Enter */
1831 if ((parm & ~KEY_M_MASK) == '\n') {
1832 quote = 1;
1833 v = handle_char (in, '\n');
1834 quote = 0;
1835 return v;
1838 return handle_char (in, parm);
1840 case WIDGET_COMMAND:
1841 return input_execute_cmd (in, parm);
1843 case WIDGET_FOCUS:
1844 case WIDGET_UNFOCUS:
1845 case WIDGET_DRAW:
1846 update_input (in, 0);
1847 return MSG_HANDLED;
1849 case WIDGET_CURSOR:
1850 widget_move (&in->widget, 0, str_term_width2 (in->buffer, in->point)
1851 - in->term_first_shown);
1852 return MSG_HANDLED;
1854 case WIDGET_DESTROY:
1855 input_destroy (in);
1856 return MSG_HANDLED;
1858 default:
1859 return default_proc (msg, parm);
1863 static int
1864 input_event (Gpm_Event * event, void *data)
1866 WInput *in = data;
1868 if (event->type & (GPM_DOWN | GPM_DRAG)) {
1869 dlg_select_widget (in);
1871 if (event->x >= in->field_width - HISTORY_BUTTON_WIDTH + 1
1872 && should_show_history_button (in)) {
1873 do_show_hist (in);
1874 } else {
1875 in->point = str_length (in->buffer);
1876 if (event->x + in->term_first_shown - 1 <
1877 str_term_width1 (in->buffer))
1879 in->point = str_column_to_pos (in->buffer, event->x
1880 + in->term_first_shown - 1);
1883 update_input (in, 1);
1885 return MOU_NORMAL;
1888 WInput *
1889 input_new (int y, int x, int color, int width, const char *def_text,
1890 const char *histname, INPUT_COMPLETE_FLAGS completion_flags)
1892 WInput *in = g_new (WInput, 1);
1893 int initial_buffer_len;
1895 init_widget (&in->widget, y, x, 1, width, input_callback, input_event);
1897 /* history setup */
1898 in->history = NULL;
1899 in->history_name = 0;
1900 if (histname) {
1901 if (*histname) {
1902 in->history_name = g_strdup (histname);
1903 in->history = history_get (histname);
1907 if (def_text == NULL)
1908 def_text = "";
1910 if (def_text == INPUT_LAST_TEXT) {
1911 def_text = "";
1912 if (in->history)
1913 if (in->history->data)
1914 def_text = (char *) in->history->data;
1916 initial_buffer_len = 1 + max ((size_t) width, strlen (def_text));
1917 in->widget.options |= W_IS_INPUT;
1918 in->completions = NULL;
1919 in->completion_flags = completion_flags;
1920 in->current_max_size = initial_buffer_len;
1921 in->buffer = g_new (char, initial_buffer_len);
1922 in->color = color;
1923 in->field_width = width;
1924 in->first = 1;
1925 in->term_first_shown = 0;
1926 in->disable_update = 0;
1927 in->mark = 0;
1928 in->need_push = 1;
1929 in->is_password = 0;
1931 strcpy (in->buffer, def_text);
1932 in->point = str_length (in->buffer);
1933 in->charpoint = 0;
1934 return in;
1937 /* Listbox widget */
1939 /* Should draw the scrollbar, but currently draws only
1940 * indications that there is more information
1942 static int listbox_cdiff (WLEntry *s, WLEntry *e);
1944 static void
1945 listbox_drawscroll (WListbox *l)
1947 int line = 0;
1948 int i, top;
1949 int max_line = l->widget.lines - 1;
1951 /* Are we at the top? */
1952 widget_move (&l->widget, 0, l->widget.cols);
1953 if (l->list == l->top)
1954 tty_print_one_vline ();
1955 else
1956 tty_print_char ('^');
1958 /* Are we at the bottom? */
1959 widget_move (&l->widget, max_line, l->widget.cols);
1960 top = listbox_cdiff (l->list, l->top);
1961 if ((top + l->widget.lines == l->count) || l->widget.lines >= l->count)
1962 tty_print_one_vline ();
1963 else
1964 tty_print_char ('v');
1966 /* Now draw the nice relative pointer */
1967 if (l->count != 0)
1968 line = 1+ ((l->pos * (l->widget.lines - 2)) / l->count);
1970 for (i = 1; i < max_line; i++){
1971 widget_move (&l->widget, i, l->widget.cols);
1972 if (i != line)
1973 tty_print_one_vline ();
1974 else
1975 tty_print_char ('*');
1979 static void
1980 listbox_draw (WListbox *l, gboolean focused)
1982 const Dlg_head *h = l->widget.parent;
1983 const int normalc = DLG_NORMALC (h);
1984 int selc = focused ? DLG_HOT_FOCUSC (h) : DLG_FOCUSC (h);
1986 WLEntry *e;
1987 int i;
1988 int sel_line = -1;
1989 const char *text;
1991 for (e = l->top, i = 0; i < l->widget.lines; i++) {
1992 /* Display the entry */
1993 if (e == l->current && sel_line == -1) {
1994 sel_line = i;
1995 tty_setcolor (selc);
1996 } else
1997 tty_setcolor (normalc);
1999 widget_move (&l->widget, i, 1);
2001 if ((i > 0 && e == l->list) || !l->list)
2002 text = "";
2003 else {
2004 text = e->text;
2005 e = e->next;
2007 tty_print_string (str_fit_to_term (text, l->widget.cols - 2, J_LEFT_FIT));
2009 l->cursor_y = sel_line;
2011 if (l->scrollbar && l->count > l->widget.lines) {
2012 tty_setcolor (normalc);
2013 listbox_drawscroll (l);
2017 /* Returns the number of items between s and e,
2018 must be on the same linked list */
2019 static int
2020 listbox_cdiff (WLEntry *s, WLEntry *e)
2022 int count;
2024 for (count = 0; s != e; count++)
2025 s = s->next;
2026 return count;
2029 static WLEntry *
2030 listbox_check_hotkey (WListbox *l, int key)
2032 int i;
2033 WLEntry *e;
2035 i = 0;
2036 e = l->list;
2037 if (!e)
2038 return NULL;
2040 while (1) {
2042 /* If we didn't find anything, return */
2043 if (i && e == l->list)
2044 return NULL;
2046 if (e->hotkey == key)
2047 return e;
2049 i++;
2050 e = e->next;
2054 /* Selects the last entry and scrolls the list to the bottom */
2055 void
2056 listbox_select_last (WListbox *l)
2058 unsigned int i;
2059 l->current = l->top = l->list->prev;
2060 for (i = min (l->widget.lines, l->count) - 1; i; i--)
2061 l->top = l->top->prev;
2062 l->pos = l->count - 1;
2065 /* Selects the first entry and scrolls the list to the top */
2066 void
2067 listbox_select_first (WListbox *l)
2069 l->current = l->top = l->list;
2070 l->pos = 0;
2073 void
2074 listbox_remove_list (WListbox *l)
2076 WLEntry *p, *q;
2078 if (!l->count)
2079 return;
2081 p = l->list;
2083 while (l->count--) {
2084 q = p->next;
2085 g_free (p->text);
2086 g_free (p);
2087 p = q;
2089 l->pos = l->count = 0;
2090 l->list = l->top = l->current = 0;
2094 * bor 30.10.96: added force flag to remove *last* entry as well
2095 * bor 30.10.96: corrected selection bug if last entry was removed
2098 void
2099 listbox_remove_current (WListbox *l, int force)
2101 WLEntry *p;
2103 /* Ok, note: this won't allow for emtpy lists */
2104 if (!force && (!l->count || l->count == 1))
2105 return;
2107 l->count--;
2108 p = l->current;
2110 if (l->count) {
2111 l->current->next->prev = l->current->prev;
2112 l->current->prev->next = l->current->next;
2113 if (p->next == l->list) {
2114 l->current = p->prev;
2115 l->pos--;
2117 else
2118 l->current = p->next;
2120 if (p == l->list)
2121 l->list = l->top = p->next;
2122 } else {
2123 l->pos = 0;
2124 l->list = l->top = l->current = 0;
2127 g_free (p->text);
2128 g_free (p);
2131 /* Makes *e the selected entry (sets current and pos) */
2132 void
2133 listbox_select_entry (WListbox *l, WLEntry *dest)
2135 WLEntry *e;
2136 int pos;
2137 int top_seen;
2139 top_seen = 0;
2141 /* Special case */
2142 for (pos = 0, e = l->list; pos < l->count; e = e->next, pos++){
2144 if (e == l->top)
2145 top_seen = 1;
2147 if (e == dest){
2148 l->current = e;
2149 if (top_seen){
2150 while (listbox_cdiff (l->top, l->current) >= l->widget.lines)
2151 l->top = l->top->next;
2152 } else {
2153 l->top = l->current;
2155 l->pos = pos;
2156 return;
2159 /* If we are unable to find it, set decent values */
2160 l->current = l->top = l->list;
2161 l->pos = 0;
2164 /* Selects from base the pos element */
2165 static WLEntry *
2166 listbox_select_pos (WListbox *l, WLEntry *base, int pos)
2168 WLEntry *last = l->list->prev;
2170 if (base == last)
2171 return last;
2172 while (pos--){
2173 base = base->next;
2174 if (base == last)
2175 break;
2177 return base;
2180 static void
2181 listbox_back (WListbox *l)
2183 if (l->pos != 0)
2184 listbox_select_entry (l, l->current->prev);
2185 else
2186 listbox_select_last (l);
2189 /* Return MSG_HANDLED if we want a redraw */
2190 static cb_ret_t
2191 listbox_key (WListbox *l, int key)
2193 int i;
2195 cb_ret_t j = MSG_NOT_HANDLED;
2197 /* focus on listbox item N by '0'..'9' keys */
2198 if (key >= '0' && key <= '9') {
2199 int oldpos = l->pos;
2200 listbox_select_by_number(l, key - '0');
2202 /* need scroll to item? */
2203 if (abs(oldpos - l->pos) > l->widget.lines)
2204 l->top = l->current;
2206 return MSG_HANDLED;
2209 if (!l->list)
2210 return MSG_NOT_HANDLED;
2212 switch (key){
2213 case KEY_HOME:
2214 case KEY_A1:
2215 case ALT ('<'):
2216 listbox_select_first (l);
2217 return MSG_HANDLED;
2219 case KEY_END:
2220 case KEY_C1:
2221 case ALT ('>'):
2222 listbox_select_last (l);
2223 return MSG_HANDLED;
2225 case XCTRL('p'):
2226 case KEY_UP:
2227 listbox_back (l);
2228 return MSG_HANDLED;
2230 case XCTRL('n'):
2231 case KEY_DOWN:
2232 listbox_fwd (l);
2233 return MSG_HANDLED;
2235 case KEY_NPAGE:
2236 case XCTRL('v'):
2237 for (i = 0; ((i < l->widget.lines - 1)
2238 && (l->current != l->list->prev)); i++) {
2239 listbox_fwd (l);
2240 j = MSG_HANDLED;
2242 return j;
2244 case KEY_PPAGE:
2245 case ALT('v'):
2246 for (i = 0; ((i < l->widget.lines - 1)
2247 && (l->current != l->list)); i++) {
2248 listbox_back (l);
2249 j = MSG_HANDLED;
2251 return j;
2253 return MSG_NOT_HANDLED;
2256 static void
2257 listbox_destroy (WListbox *l)
2259 WLEntry *n, *p = l->list;
2260 int i;
2262 for (i = 0; i < l->count; i++){
2263 n = p->next;
2264 g_free (p->text);
2265 g_free (p);
2266 p = n;
2270 static cb_ret_t
2271 listbox_callback (Widget *w, widget_msg_t msg, int parm)
2273 WListbox *l = (WListbox *) w;
2274 Dlg_head *h = l->widget.parent;
2275 WLEntry *e;
2276 cb_ret_t ret_code;
2278 switch (msg) {
2279 case WIDGET_INIT:
2280 return MSG_HANDLED;
2282 case WIDGET_HOTKEY:
2283 e = listbox_check_hotkey (l, parm);
2284 if (e != NULL) {
2285 int action;
2287 listbox_select_entry (l, e);
2289 (*h->callback) (h, DLG_ACTION, l->pos);
2291 if (l->cback)
2292 action = (*l->cback) (l);
2293 else
2294 action = LISTBOX_DONE;
2296 if (action == LISTBOX_DONE) {
2297 h->ret_value = B_ENTER;
2298 dlg_stop (h);
2300 return MSG_HANDLED;
2302 return MSG_NOT_HANDLED;
2304 case WIDGET_KEY:
2305 ret_code = listbox_key (l, parm);
2306 if (ret_code != MSG_NOT_HANDLED) {
2307 listbox_draw (l, TRUE);
2308 (*h->callback) (h, DLG_ACTION, l->pos);
2310 return ret_code;
2312 case WIDGET_CURSOR:
2313 widget_move (&l->widget, l->cursor_y, 0);
2314 (*h->callback) (h, DLG_ACTION, l->pos);
2315 return MSG_HANDLED;
2317 case WIDGET_FOCUS:
2318 case WIDGET_UNFOCUS:
2319 case WIDGET_DRAW:
2320 listbox_draw (l, msg != WIDGET_UNFOCUS);
2321 return MSG_HANDLED;
2323 case WIDGET_DESTROY:
2324 listbox_destroy (l);
2325 return MSG_HANDLED;
2327 case WIDGET_RESIZED:
2328 return MSG_HANDLED;
2330 default:
2331 return default_proc (msg, parm);
2335 static int
2336 listbox_event (Gpm_Event *event, void *data)
2338 WListbox *l = data;
2339 int i;
2341 Dlg_head *h = l->widget.parent;
2343 /* Single click */
2344 if (event->type & GPM_DOWN)
2345 dlg_select_widget (l);
2347 if (l->list == NULL)
2348 return MOU_NORMAL;
2350 if (event->type & (GPM_DOWN | GPM_DRAG)) {
2351 int ret = MOU_REPEAT;
2353 if (event->x < 0 || event->x > l->widget.cols)
2354 return ret;
2356 if (event->y < 1)
2357 for (i = -event->y; i >= 0; i--)
2358 listbox_back (l);
2359 else if (event->y > l->widget.lines)
2360 for (i = event->y - l->widget.lines; i > 0; i--)
2361 listbox_fwd (l);
2362 else if (event->buttons & GPM_B_UP) {
2363 listbox_back (l);
2364 ret = MOU_NORMAL;
2365 } else if (event->buttons & GPM_B_DOWN) {
2366 listbox_fwd (l);
2367 ret = MOU_NORMAL;
2368 } else
2369 listbox_select_entry (l,
2370 listbox_select_pos (l, l->top,
2371 event->y - 1));
2373 /* We need to refresh ourselves since the dialog manager doesn't */
2374 /* know about this event */
2375 listbox_draw (l, TRUE);
2376 return ret;
2379 /* Double click */
2380 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE)) {
2381 int action;
2383 if (event->x < 0 || event->x >= l->widget.cols
2384 || event->y < 1 || event->y > l->widget.lines)
2385 return MOU_NORMAL;
2387 dlg_select_widget (l);
2388 listbox_select_entry (l,
2389 listbox_select_pos (l, l->top,
2390 event->y - 1));
2392 if (l->cback)
2393 action = (*l->cback) (l);
2394 else
2395 action = LISTBOX_DONE;
2397 if (action == LISTBOX_DONE) {
2398 h->ret_value = B_ENTER;
2399 dlg_stop (h);
2400 return MOU_NORMAL;
2403 return MOU_NORMAL;
2406 WListbox *
2407 listbox_new (int y, int x, int height, int width, lcback callback)
2409 WListbox *l = g_new (WListbox, 1);
2411 if (height <= 0)
2412 height = 1;
2414 init_widget (&l->widget, y, x, height, width,
2415 listbox_callback, listbox_event);
2417 l->list = l->top = l->current = 0;
2418 l->pos = 0;
2419 l->count = 0;
2420 l->cback = callback;
2421 l->allow_duplicates = 1;
2422 l->scrollbar = !tty_is_slow ();
2423 widget_want_hotkey (l->widget, 1);
2425 return l;
2428 /* Listbox item adding function. They still lack a lot of functionality */
2429 /* any takers? */
2430 /* 1.11.96 bor: added pos argument to control placement of new entry */
2431 static void
2432 listbox_append_item (WListbox *l, WLEntry *e, enum append_pos pos)
2434 if (!l->list){
2435 l->list = e;
2436 l->top = e;
2437 l->current = e;
2438 e->next = l->list;
2439 e->prev = l->list;
2440 } else if (pos == LISTBOX_APPEND_AT_END) {
2441 e->next = l->list;
2442 e->prev = l->list->prev;
2443 l->list->prev->next = e;
2444 l->list->prev = e;
2445 } else if (pos == LISTBOX_APPEND_BEFORE){
2446 e->next = l->current;
2447 e->prev = l->current->prev;
2448 l->current->prev->next = e;
2449 l->current->prev = e;
2450 if (l->list == l->current) { /* move list one position down */
2451 l->list = e;
2452 l->top = e;
2454 } else if (pos == LISTBOX_APPEND_AFTER) {
2455 e->prev = l->current;
2456 e->next = l->current->next;
2457 l->current->next->prev = e;
2458 l->current->next = e;
2459 } else if (pos == LISTBOX_APPEND_SORTED) {
2460 WLEntry *w = l->list;
2462 while (w->next != l->list && strcmp (e->text, w->text) > 0)
2463 w = w->next;
2464 if (w->next == l->list) {
2465 e->prev = w;
2466 e->next = l->list;
2467 w->next = e;
2468 l->list->prev = e;
2469 } else {
2470 e->next = w;
2471 e->prev = w->prev;
2472 w->prev->next = e;
2473 w->prev = e;
2476 l->count++;
2479 char *
2480 listbox_add_item (WListbox *l, enum append_pos pos, int hotkey,
2481 const char *text, void *data)
2483 WLEntry *entry;
2485 if (!l)
2486 return NULL;
2488 if (!l->allow_duplicates)
2489 if (listbox_search_text (l, text))
2490 return NULL;
2492 entry = g_new (WLEntry, 1);
2493 entry->text = g_strdup (text);
2494 entry->data = data;
2495 entry->hotkey = hotkey;
2497 listbox_append_item (l, entry, pos);
2499 return entry->text;
2502 /* Selects the nth entry in the listbox */
2503 void
2504 listbox_select_by_number (WListbox *l, int n)
2506 if (l->list != NULL)
2507 listbox_select_entry (l, listbox_select_pos (l, l->list, n));
2510 WLEntry *
2511 listbox_search_text (WListbox *l, const char *text)
2513 WLEntry *e;
2515 e = l->list;
2516 if (!e)
2517 return NULL;
2519 do {
2520 if(!strcmp (e->text, text))
2521 return e;
2522 e = e->next;
2523 } while (e!=l->list);
2525 return NULL;
2528 /* Returns the current string text as well as the associated extra data */
2529 void
2530 listbox_get_current (WListbox *l, char **string, char **extra)
2532 if (!l->current){
2533 *string = 0;
2534 *extra = 0;
2536 if (string && l->current)
2537 *string = l->current->text;
2538 if (extra && l->current)
2539 *extra = l->current->data;
2542 /* returns TRUE if a function has been called, FALSE otherwise. */
2543 static gboolean
2544 buttonbar_call (WButtonBar *bb, int i)
2546 switch (bb->labels[i].tag) {
2547 case BBFUNC_NONE:
2548 break;
2549 case BBFUNC_VOID:
2550 bb->labels[i].u.fn_void ();
2551 return TRUE;
2552 case BBFUNC_PTR:
2553 bb->labels[i].u.fn_ptr (bb->labels[i].data);
2554 return TRUE;
2556 return FALSE;
2559 /* calculate width of one button, width is never lesser than 7 */
2560 static int
2561 buttonbat_get_button_width ()
2563 int result = COLS / BUTTONBAR_LABELS_NUM;
2564 return (result >= 7) ? result : 7;
2568 static cb_ret_t
2569 buttonbar_callback (Widget *w, widget_msg_t msg, int parm)
2571 WButtonBar *bb = (WButtonBar *) w;
2572 int i;
2573 const char *text;
2575 switch (msg) {
2576 case WIDGET_FOCUS:
2577 return MSG_NOT_HANDLED;
2579 case WIDGET_HOTKEY:
2580 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
2581 if (parm == KEY_F (i + 1) && buttonbar_call (bb, i))
2582 return MSG_HANDLED;
2583 return MSG_NOT_HANDLED;
2585 case WIDGET_DRAW:
2586 if (bb->visible) {
2587 widget_move (&bb->widget, 0, 0);
2588 tty_setcolor (DEFAULT_COLOR);
2589 bb->btn_width = buttonbat_get_button_width ();
2590 tty_printf ("%-*s", bb->widget.cols, "");
2592 for (i = 0; i < COLS / bb->btn_width && i < BUTTONBAR_LABELS_NUM; i++) {
2593 widget_move (&bb->widget, 0, i * bb->btn_width);
2594 tty_setcolor (DEFAULT_COLOR);
2595 tty_printf ("%2d", i + 1);
2596 tty_setcolor (SELECTED_COLOR);
2597 text = (bb->labels[i].text != NULL) ? bb->labels[i].text : "";
2598 tty_print_string (str_fit_to_term (text, bb->btn_width - 2, J_CENTER_LEFT));
2601 return MSG_HANDLED;
2603 case WIDGET_DESTROY:
2604 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
2605 g_free (bb->labels[i].text);
2606 return MSG_HANDLED;
2608 default:
2609 return default_proc (msg, parm);
2613 static int
2614 buttonbar_event (Gpm_Event *event, void *data)
2616 WButtonBar *bb = data;
2617 int button;
2619 if (!(event->type & GPM_UP))
2620 return MOU_NORMAL;
2621 if (event->y == 2)
2622 return MOU_NORMAL;
2623 button = (event->x - 1) / bb->btn_width;
2624 if (button < BUTTONBAR_LABELS_NUM)
2625 buttonbar_call (bb, button);
2626 return MOU_NORMAL;
2629 WButtonBar *
2630 buttonbar_new (int visible)
2632 WButtonBar *bb;
2633 int i;
2635 bb = g_new0 (WButtonBar, 1);
2637 init_widget (&bb->widget, LINES - 1, 0, 1, COLS,
2638 buttonbar_callback, buttonbar_event);
2639 bb->widget.pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_BOTTOM;
2640 bb->visible = visible;
2641 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++){
2642 bb->labels[i].text = NULL;
2643 bb->labels[i].tag = BBFUNC_NONE;
2645 widget_want_hotkey (bb->widget, 1);
2646 widget_want_cursor (bb->widget, 0);
2647 bb->btn_width = buttonbat_get_button_width ();
2649 return bb;
2652 static void
2653 set_label_text (WButtonBar * bb, int index, const char *text)
2655 g_free (bb->labels[index - 1].text);
2657 bb->labels[index - 1].text = g_strdup (text);
2660 /* Find ButtonBar widget in the dialog */
2661 WButtonBar *
2662 find_buttonbar (Dlg_head *h)
2664 WButtonBar *bb;
2666 bb = (WButtonBar *) find_widget_type (h, buttonbar_callback);
2667 return bb;
2670 void
2671 buttonbar_clear_label (Dlg_head *h, int idx)
2673 WButtonBar *bb = find_buttonbar (h);
2675 if (!bb)
2676 return;
2678 set_label_text (bb, idx, "");
2679 bb->labels[idx - 1].tag = BBFUNC_NONE;
2682 void
2683 buttonbar_set_label_data (Dlg_head *h, int idx, const char *text,
2684 buttonbarfn cback, void *data)
2686 WButtonBar *bb = find_buttonbar (h);
2688 if (!bb)
2689 return;
2691 assert (cback != (buttonbarfn) 0);
2692 set_label_text (bb, idx, text);
2693 bb->labels[idx - 1].tag = BBFUNC_PTR;
2694 bb->labels[idx - 1].u.fn_ptr = cback;
2695 bb->labels[idx - 1].data = data;
2698 void
2699 buttonbar_set_label (Dlg_head *h, int idx, const char *text, voidfn cback)
2701 WButtonBar *bb = find_buttonbar (h);
2703 if (!bb)
2704 return;
2706 assert (cback != (voidfn) 0);
2707 set_label_text (bb, idx, text);
2708 bb->labels[idx - 1].tag = BBFUNC_VOID;
2709 bb->labels[idx - 1].u.fn_void = cback;
2712 void
2713 buttonbar_set_visible (WButtonBar *bb, gboolean visible)
2715 bb->visible = visible;
2718 void
2719 buttonbar_redraw (Dlg_head *h)
2721 WButtonBar *bb = find_buttonbar (h);
2723 if (!bb)
2724 return;
2726 send_message ((Widget *) bb, WIDGET_DRAW, 0);
2729 static cb_ret_t
2730 groupbox_callback (Widget *w, widget_msg_t msg, int parm)
2732 WGroupbox *g = (WGroupbox *) w;
2734 switch (msg) {
2735 case WIDGET_INIT:
2736 return MSG_HANDLED;
2738 case WIDGET_FOCUS:
2739 return MSG_NOT_HANDLED;
2741 case WIDGET_DRAW:
2742 tty_setcolor (COLOR_NORMAL);
2743 draw_box (g->widget.parent, g->widget.y - g->widget.parent->y,
2744 g->widget.x - g->widget.parent->x, g->widget.lines,
2745 g->widget.cols);
2747 tty_setcolor (COLOR_HOT_NORMAL);
2748 dlg_move (g->widget.parent, g->widget.y - g->widget.parent->y,
2749 g->widget.x - g->widget.parent->x + 1);
2750 tty_print_string (g->title);
2751 return MSG_HANDLED;
2753 case WIDGET_DESTROY:
2754 g_free (g->title);
2755 return MSG_HANDLED;
2757 default:
2758 return default_proc (msg, parm);
2762 WGroupbox *
2763 groupbox_new (int y, int x, int height, int width, const char *title)
2765 WGroupbox *g = g_new (WGroupbox, 1);
2767 init_widget (&g->widget, y, x, height, width, groupbox_callback, NULL);
2769 g->widget.options &= ~W_WANT_CURSOR;
2770 widget_want_hotkey (g->widget, 0);
2772 /* Strip existing spaces, add one space before and after the title */
2773 if (title) {
2774 char *t;
2775 t = g_strstrip (g_strdup (title));
2776 g->title = g_strconcat (" ", t, " ", (char *) NULL);
2777 g_free (t);
2780 return g;