Ticket #1703: SKIN: Make own colorpairs for buttonbar widget
[midnight-commander.git] / src / widget.c
blob3b3fee54ca4179ce76a5e4a6548d3ea654fa7247
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"
57 #include "cmddef.h" /* CK_ cmd name const */
58 #include "keybind.h" /* global_keymap_t */
59 #include "fileloc.h"
61 const global_keymap_t *input_map;
63 static void
64 widget_selectcolor (Widget *w, gboolean focused, gboolean hotkey)
66 Dlg_head *h = w->parent;
68 tty_setcolor (hotkey
69 ? (focused
70 ? DLG_HOT_FOCUSC (h)
71 : DLG_HOT_NORMALC (h))
72 : (focused
73 ? DLG_FOCUSC (h)
74 : DLG_NORMALC (h)));
77 struct hotkey_t
78 parse_hotkey (const char *text)
80 struct hotkey_t result;
81 const char *cp, *p;
83 /* search for '&', that is not on the of text */
84 cp = strchr (text, '&');
85 if (cp != NULL && cp[1] != '\0') {
86 result.start = g_strndup (text, cp - text);
88 /* skip '&' */
89 cp++;
90 p = str_cget_next_char (cp);
91 result.hotkey = g_strndup (cp, p - cp);
93 cp = p;
94 result.end = g_strdup (cp);
95 } else {
96 result.start = g_strdup (text);
97 result.hotkey = NULL;
98 result.end = NULL;
101 return result;
103 void
104 release_hotkey (const struct hotkey_t hotkey)
106 g_free (hotkey.start);
107 g_free (hotkey.hotkey);
108 g_free (hotkey.end);
112 hotkey_width (const struct hotkey_t hotkey)
114 int result;
116 result = str_term_width1 (hotkey.start);
117 result+= (hotkey.hotkey != NULL) ? str_term_width1 (hotkey.hotkey) : 0;
118 result+= (hotkey.end != NULL) ? str_term_width1 (hotkey.end) : 0;
119 return result;
122 static void
123 draw_hotkey (Widget *w, const struct hotkey_t hotkey, gboolean focused)
125 widget_selectcolor (w, focused, FALSE);
126 tty_print_string (hotkey.start);
128 if (hotkey.hotkey != NULL) {
129 widget_selectcolor (w, focused, TRUE);
130 tty_print_string (hotkey.hotkey);
131 widget_selectcolor (w, focused, FALSE);
134 if (hotkey.end != NULL)
135 tty_print_string (hotkey.end);
139 /* Default callback for widgets */
140 cb_ret_t
141 default_proc (widget_msg_t msg, int parm)
143 (void) parm;
145 switch (msg) {
146 case WIDGET_INIT:
147 case WIDGET_FOCUS:
148 case WIDGET_UNFOCUS:
149 case WIDGET_DRAW:
150 case WIDGET_DESTROY:
151 case WIDGET_CURSOR:
152 case WIDGET_IDLE:
153 return MSG_HANDLED;
155 default:
156 return MSG_NOT_HANDLED;
160 static int button_event (Gpm_Event *event, void *);
162 int quote = 0;
164 static cb_ret_t
165 button_callback (Widget *w, widget_msg_t msg, int parm)
167 WButton *b = (WButton *) w;
168 int stop = 0;
169 int off = 0;
170 Dlg_head *h = b->widget.parent;
172 switch (msg) {
173 case WIDGET_HOTKEY:
175 * Don't let the default button steal Enter from the current
176 * button. This is a workaround for the flawed event model
177 * when hotkeys are sent to all widgets before the key is
178 * handled by the current widget.
180 if (parm == '\n' && h->current == &b->widget) {
181 button_callback (w, WIDGET_KEY, ' ');
182 return MSG_HANDLED;
185 if (parm == '\n' && b->flags == DEFPUSH_BUTTON) {
186 button_callback (w, WIDGET_KEY, ' ');
187 return MSG_HANDLED;
190 if (b->text.hotkey != NULL) {
191 if (g_ascii_tolower ((gchar)b->text.hotkey[0]) ==
192 g_ascii_tolower ((gchar)parm)) {
193 button_callback (w, WIDGET_KEY, ' ');
194 return MSG_HANDLED;
197 return MSG_NOT_HANDLED;
199 case WIDGET_KEY:
200 if (parm != ' ' && parm != '\n')
201 return MSG_NOT_HANDLED;
203 if (b->callback)
204 stop = (*b->callback) (b->action);
205 if (!b->callback || stop) {
206 h->ret_value = b->action;
207 dlg_stop (h);
209 return MSG_HANDLED;
211 case WIDGET_CURSOR:
212 switch (b->flags) {
213 case DEFPUSH_BUTTON:
214 off = 3;
215 break;
216 case NORMAL_BUTTON:
217 off = 2;
218 break;
219 case NARROW_BUTTON:
220 off = 1;
221 break;
222 case HIDDEN_BUTTON:
223 default:
224 off = 0;
225 break;
227 widget_move (&b->widget, 0, b->hotpos + off);
228 return MSG_HANDLED;
230 case WIDGET_UNFOCUS:
231 case WIDGET_FOCUS:
232 case WIDGET_DRAW:
233 if (msg == WIDGET_UNFOCUS)
234 b->selected = 0;
235 else if (msg == WIDGET_FOCUS)
236 b->selected = 1;
238 widget_selectcolor (w, b->selected, FALSE);
239 widget_move (w, 0, 0);
241 switch (b->flags) {
242 case DEFPUSH_BUTTON:
243 tty_print_string ("[< ");
244 break;
245 case NORMAL_BUTTON:
246 tty_print_string ("[ ");
247 break;
248 case NARROW_BUTTON:
249 tty_print_string ("[");
250 break;
251 case HIDDEN_BUTTON:
252 default:
253 return MSG_HANDLED;
256 draw_hotkey (w, b->text, b->selected);
258 switch (b->flags) {
259 case DEFPUSH_BUTTON:
260 tty_print_string (" >]");
261 break;
262 case NORMAL_BUTTON:
263 tty_print_string (" ]");
264 break;
265 case NARROW_BUTTON:
266 tty_print_string ("]");
267 break;
269 return MSG_HANDLED;
271 case WIDGET_DESTROY:
272 release_hotkey (b->text);
273 return MSG_HANDLED;
275 default:
276 return default_proc (msg, parm);
280 static int
281 button_event (Gpm_Event *event, void *data)
283 WButton *b = data;
285 if (event->type & (GPM_DOWN|GPM_UP)){
286 Dlg_head *h=b->widget.parent;
287 dlg_select_widget (b);
288 if (event->type & GPM_UP){
289 button_callback ((Widget *) data, WIDGET_KEY, ' ');
290 (*h->callback) (h, DLG_POST_KEY, ' ');
291 return MOU_NORMAL;
294 return MOU_NORMAL;
298 button_get_len (const WButton *b)
300 int ret = hotkey_width (b->text);
301 switch (b->flags) {
302 case DEFPUSH_BUTTON:
303 ret += 6;
304 break;
305 case NORMAL_BUTTON:
306 ret += 4;
307 break;
308 case NARROW_BUTTON:
309 ret += 2;
310 break;
311 case HIDDEN_BUTTON:
312 default:
313 return 0;
315 return ret;
318 WButton *
319 button_new (int y, int x, int action, int flags, const char *text,
320 bcback callback)
322 WButton *b = g_new (WButton, 1);
324 b->action = action;
325 b->flags = flags;
326 b->text = parse_hotkey (text);
328 init_widget (&b->widget, y, x, 1, button_get_len (b),
329 button_callback, button_event);
331 b->selected = 0;
332 b->callback = callback;
333 widget_want_hotkey (b->widget, 1);
334 b->hotpos = (b->text.hotkey != NULL) ? str_term_width1 (b->text.start) : -1;
336 return b;
339 const char *
340 button_get_text (const WButton *b)
342 if (b->text.hotkey != NULL)
343 return g_strconcat (b->text.start, "&", b->text.hotkey,
344 b->text.end, NULL);
345 else
346 return g_strdup (b->text.start);
349 void
350 button_set_text (WButton *b, const char *text)
352 release_hotkey (b->text);
353 b->text = parse_hotkey (text);
354 b->widget.cols = button_get_len (b);
355 dlg_redraw (b->widget.parent);
359 /* Radio button widget */
360 static int radio_event (Gpm_Event *event, void *);
362 static cb_ret_t
363 radio_callback (Widget *w, widget_msg_t msg, int parm)
365 WRadio *r = (WRadio *) w;
366 int i;
367 Dlg_head *h = r->widget.parent;
369 switch (msg) {
370 case WIDGET_HOTKEY:
372 int i, lp = g_ascii_tolower ((gchar)parm);
374 for (i = 0; i < r->count; i++) {
375 if (r->texts[i].hotkey != NULL) {
376 int c = g_ascii_tolower ((gchar)r->texts[i].hotkey[0]);
378 if (c != lp)
379 continue;
380 r->pos = i;
382 /* Take action */
383 radio_callback (w, WIDGET_KEY, ' ');
384 return MSG_HANDLED;
388 return MSG_NOT_HANDLED;
390 case WIDGET_KEY:
391 switch (parm) {
392 case ' ':
393 r->sel = r->pos;
394 (*h->callback) (h, DLG_ACTION, 0);
395 radio_callback (w, WIDGET_FOCUS, ' ');
396 return MSG_HANDLED;
398 case KEY_UP:
399 case KEY_LEFT:
400 if (r->pos > 0) {
401 r->pos--;
402 return MSG_HANDLED;
404 return MSG_NOT_HANDLED;
406 case KEY_DOWN:
407 case KEY_RIGHT:
408 if (r->count - 1 > r->pos) {
409 r->pos++;
410 return MSG_HANDLED;
413 return MSG_NOT_HANDLED;
415 case WIDGET_CURSOR:
416 (*h->callback) (h, DLG_ACTION, 0);
417 radio_callback (w, WIDGET_FOCUS, ' ');
418 widget_move (&r->widget, r->pos, 1);
419 return MSG_HANDLED;
421 case WIDGET_UNFOCUS:
422 case WIDGET_FOCUS:
423 case WIDGET_DRAW:
424 for (i = 0; i < r->count; i++) {
425 const gboolean focused = (i == r->pos && msg == WIDGET_FOCUS);
426 widget_selectcolor (w, focused, FALSE);
427 widget_move (&r->widget, i, 0);
428 tty_print_string ((r->sel == i) ? "(*) " : "( ) ");
429 draw_hotkey (w, r->texts[i], focused);
431 return MSG_HANDLED;
433 case WIDGET_DESTROY:
434 for (i = 0; i < r->count; i++) {
435 release_hotkey (r->texts[i]);
437 g_free (r->texts);
438 return MSG_HANDLED;
440 default:
441 return default_proc (msg, parm);
445 static int
446 radio_event (Gpm_Event *event, void *data)
448 WRadio *r = data;
449 Widget *w = data;
451 if (event->type & (GPM_DOWN|GPM_UP)){
452 Dlg_head *h = r->widget.parent;
454 r->pos = event->y - 1;
455 dlg_select_widget (r);
456 if (event->type & GPM_UP){
457 radio_callback (w, WIDGET_KEY, ' ');
458 radio_callback (w, WIDGET_FOCUS, 0);
459 (*h->callback) (h, DLG_POST_KEY, ' ');
460 return MOU_NORMAL;
463 return MOU_NORMAL;
466 WRadio *
467 radio_new (int y, int x, int count, const char **texts)
469 WRadio *result = g_new (WRadio, 1);
470 int i, max, m;
472 /* Compute the longest string */
473 result->texts = g_new (struct hotkey_t, count);
475 max = 0;
476 for (i = 0; i < count; i++){
477 result->texts[i] = parse_hotkey (texts[i]);
478 m = hotkey_width (result->texts[i]);
479 if (m > max)
480 max = m;
483 init_widget (&result->widget, y, x, count, max, radio_callback, radio_event);
484 result->state = 1;
485 result->pos = 0;
486 result->sel = 0;
487 result->count = count;
488 widget_want_hotkey (result->widget, 1);
490 return result;
494 /* Checkbutton widget */
496 static int check_event (Gpm_Event *event, void *);
498 static cb_ret_t
499 check_callback (Widget *w, widget_msg_t msg, int parm)
501 WCheck *c = (WCheck *) w;
502 Dlg_head *h = c->widget.parent;
504 switch (msg) {
505 case WIDGET_HOTKEY:
506 if (c->text.hotkey != NULL) {
507 if (g_ascii_tolower ((gchar)c->text.hotkey[0]) ==
508 g_ascii_tolower ((gchar)parm)) {
510 check_callback (w, WIDGET_KEY, ' '); /* make action */
511 return MSG_HANDLED;
514 return MSG_NOT_HANDLED;
516 case WIDGET_KEY:
517 if (parm != ' ')
518 return MSG_NOT_HANDLED;
519 c->state ^= C_BOOL;
520 c->state ^= C_CHANGE;
521 (*h->callback) (h, DLG_ACTION, 0);
522 check_callback (w, WIDGET_FOCUS, ' ');
523 return MSG_HANDLED;
525 case WIDGET_CURSOR:
526 widget_move (&c->widget, 0, 1);
527 return MSG_HANDLED;
529 case WIDGET_FOCUS:
530 case WIDGET_UNFOCUS:
531 case WIDGET_DRAW:
532 widget_selectcolor (w, msg == WIDGET_FOCUS, FALSE);
533 widget_move (&c->widget, 0, 0);
534 tty_print_string ((c->state & C_BOOL) ? "[x] " : "[ ] ");
535 draw_hotkey (w, c->text, msg == WIDGET_FOCUS);
536 return MSG_HANDLED;
538 case WIDGET_DESTROY:
539 release_hotkey (c->text);
540 return MSG_HANDLED;
542 default:
543 return default_proc (msg, parm);
547 static int
548 check_event (Gpm_Event *event, void *data)
550 WCheck *c = data;
551 Widget *w = data;
553 if (event->type & (GPM_DOWN|GPM_UP)){
554 Dlg_head *h = c->widget.parent;
556 dlg_select_widget (c);
557 if (event->type & GPM_UP){
558 check_callback (w, WIDGET_KEY, ' ');
559 check_callback (w, WIDGET_FOCUS, 0);
560 (*h->callback) (h, DLG_POST_KEY, ' ');
561 return MOU_NORMAL;
564 return MOU_NORMAL;
567 WCheck *
568 check_new (int y, int x, int state, const char *text)
570 WCheck *c = g_new (WCheck, 1);
572 c->text = parse_hotkey (text);
574 init_widget (&c->widget, y, x, 1, hotkey_width (c->text),
575 check_callback, check_event);
576 c->state = state ? C_BOOL : 0;
577 widget_want_hotkey (c->widget, 1);
579 return c;
583 /* Label widget */
585 static cb_ret_t
586 label_callback (Widget *w, widget_msg_t msg, int parm)
588 WLabel *l = (WLabel *) w;
589 Dlg_head *h = l->widget.parent;
591 switch (msg) {
592 case WIDGET_INIT:
593 return MSG_HANDLED;
595 /* We don't want to get the focus */
596 case WIDGET_FOCUS:
597 return MSG_NOT_HANDLED;
599 case WIDGET_DRAW:
601 char *p = l->text, *q, c = 0;
602 int y = 0;
604 if (!l->text)
605 return MSG_HANDLED;
607 if (l->transparent)
608 tty_setcolor (DEFAULT_COLOR);
609 else
610 tty_setcolor (DLG_NORMALC (h));
612 for (;;) {
613 q = strchr (p, '\n');
614 if (q != NULL) {
615 c = q[0];
616 q[0] = '\0';
619 widget_move (&l->widget, y, 0);
620 tty_print_string (str_fit_to_term (p, l->widget.cols, J_LEFT));
622 if (q == NULL)
623 break;
624 q[0] = c;
625 p = q + 1;
626 y++;
628 return MSG_HANDLED;
631 case WIDGET_DESTROY:
632 g_free (l->text);
633 return MSG_HANDLED;
635 default:
636 return default_proc (msg, parm);
640 void
641 label_set_text (WLabel *label, const char *text)
643 int newcols = label->widget.cols;
644 int newlines;
646 if (label->text && text && !strcmp (label->text, text))
647 return; /* Flickering is not nice */
649 g_free (label->text);
651 if (text != NULL) {
652 label->text = g_strdup (text);
653 if (label->auto_adjust_cols) {
654 str_msg_term_size (text, &newlines, &newcols);
655 if (newcols > label->widget.cols)
656 label->widget.cols = newcols;
657 if (newlines > label->widget.lines)
658 label->widget.lines = newlines;
660 } else label->text = NULL;
662 if (label->widget.parent)
663 label_callback ((Widget *) label, WIDGET_DRAW, 0);
665 if (newcols < label->widget.cols)
666 label->widget.cols = newcols;
669 WLabel *
670 label_new (int y, int x, const char *text)
672 WLabel *l;
673 int cols = 1;
674 int lines = 1;
676 if (text != NULL)
677 str_msg_term_size (text, &lines, &cols);
679 l = g_new (WLabel, 1);
680 init_widget (&l->widget, y, x, lines, cols, label_callback, NULL);
681 l->text = (text != NULL) ? g_strdup (text) : NULL;
682 l->auto_adjust_cols = 1;
683 l->transparent = 0;
684 widget_want_cursor (l->widget, 0);
685 return l;
689 /* Gauge widget (progress indicator) */
690 /* Currently width is hardcoded here for text mode */
691 #define gauge_len 47
693 static cb_ret_t
694 gauge_callback (Widget *w, widget_msg_t msg, int parm)
696 WGauge *g = (WGauge *) w;
697 Dlg_head *h = g->widget.parent;
699 if (msg == WIDGET_INIT)
700 return MSG_HANDLED;
702 /* We don't want to get the focus */
703 if (msg == WIDGET_FOCUS)
704 return MSG_NOT_HANDLED;
706 if (msg == WIDGET_DRAW){
707 widget_move (&g->widget, 0, 0);
708 tty_setcolor (DLG_NORMALC (h));
709 if (!g->shown)
710 tty_printf ("%*s", gauge_len, "");
711 else {
712 int percentage, columns;
713 long total = g->max, done = g->current;
715 if (total <= 0 || done < 0) {
716 done = 0;
717 total = 100;
719 if (done > total)
720 done = total;
721 while (total > 65535) {
722 total /= 256;
723 done /= 256;
725 percentage = (200 * done / total + 1) / 2;
726 columns = (2 * (gauge_len - 7) * done / total + 1) / 2;
727 tty_print_char ('[');
728 tty_setcolor (GAUGE_COLOR);
729 tty_printf ("%*s", (int) columns, "");
730 tty_setcolor (DLG_NORMALC (h));
731 tty_printf ("%*s] %3d%%", (int)(gauge_len - 7 - columns), "", (int) percentage);
733 return MSG_HANDLED;
736 return default_proc (msg, parm);
739 void
740 gauge_set_value (WGauge *g, int max, int current)
742 if (g->current == current && g->max == max)
743 return; /* Do not flicker */
744 if (max == 0)
745 max = 1; /* I do not like division by zero :) */
747 g->current = current;
748 g->max = max;
749 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
752 void
753 gauge_show (WGauge *g, int shown)
755 if (g->shown == shown)
756 return;
757 g->shown = shown;
758 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
761 WGauge *
762 gauge_new (int y, int x, int shown, int max, int current)
764 WGauge *g = g_new (WGauge, 1);
766 init_widget (&g->widget, y, x, 1, gauge_len, gauge_callback, NULL);
767 g->shown = shown;
768 if (max == 0)
769 max = 1; /* I do not like division by zero :) */
770 g->max = max;
771 g->current = current;
772 widget_want_cursor (g->widget, 0);
773 return g;
777 /* Input widget */
779 /* {{{ history button */
781 #define LARGE_HISTORY_BUTTON 1
783 #ifdef LARGE_HISTORY_BUTTON
784 # define HISTORY_BUTTON_WIDTH 3
785 #else
786 # define HISTORY_BUTTON_WIDTH 1
787 #endif
789 #define should_show_history_button(in) \
790 (in->history && in->field_width > HISTORY_BUTTON_WIDTH * 2 + 1 && in->widget.parent)
792 static void draw_history_button (WInput * in)
794 char c;
795 c = in->history->next ? (in->history->prev ? '|' : 'v') : '^';
796 widget_move (&in->widget, 0, in->field_width - HISTORY_BUTTON_WIDTH);
797 #ifdef LARGE_HISTORY_BUTTON
799 Dlg_head *h;
800 h = in->widget.parent;
801 tty_setcolor (NORMAL_COLOR);
802 tty_print_string ("[ ]");
803 /* Too distracting: tty_setcolor (MARKED_COLOR); */
804 widget_move (&in->widget, 0, in->field_width - HISTORY_BUTTON_WIDTH + 1);
805 tty_print_char (c);
807 #else
808 tty_setcolor (MARKED_COLOR);
809 tty_print_char (c);
810 #endif
813 /* }}} history button */
816 /* Input widgets now have a global kill ring */
817 /* Pointer to killed data */
818 static char *kill_buffer = 0;
820 void
821 update_input (WInput *in, int clear_first)
823 int has_history = 0;
824 int i;
825 int buf_len = str_length (in->buffer);
826 const char *cp;
827 int pw;
829 if (should_show_history_button (in))
830 has_history = HISTORY_BUTTON_WIDTH;
832 if (in->disable_update)
833 return;
835 pw = str_term_width2 (in->buffer, in->point);
837 /* Make the point visible */
838 if ((pw < in->term_first_shown) ||
839 (pw >= in->term_first_shown + in->field_width - has_history)) {
841 in->term_first_shown = pw - (in->field_width / 3);
842 if (in->term_first_shown < 0)
843 in->term_first_shown = 0;
846 /* Adjust the mark */
847 if (in->mark > buf_len)
848 in->mark = buf_len;
850 if (has_history)
851 draw_history_button (in);
853 tty_setcolor (in->color);
855 widget_move (&in->widget, 0, 0);
857 if (!in->is_password) {
858 tty_print_string (str_term_substring (in->buffer, in->term_first_shown,
859 in->field_width - has_history));
860 } else {
861 cp = in->buffer;
862 for (i = -in->term_first_shown; i < in->field_width - has_history; i++){
863 if (i >= 0) {
864 tty_print_char ((cp[0] != '\0') ? '*' : ' ');
866 if (cp[0] != '\0') str_cnext_char (&cp);
870 if (clear_first)
871 in->first = 0;
874 void
875 winput_set_origin (WInput *in, int x, int field_width)
877 in->widget.x = x;
878 in->field_width = in->widget.cols = field_width;
879 update_input (in, 0);
882 /* {{{ history saving and loading */
884 int num_history_items_recorded = 60;
887 This loads and saves the history of an input line to and from the
888 widget. It is called with the widgets history name on creation of the
889 widget, and returns the GList list. It stores histories in the file
890 ~/.mc/history in using the profile code.
892 If def_text is passed as INPUT_LAST_TEXT (to the input_new()
893 function) then input_new assigns the default text to be the last text
894 entered, or "" if not found.
897 GList *
898 history_get (const char *input_name)
900 size_t i;
901 GList *hist = NULL;
902 char *profile;
903 mc_config_t *cfg;
904 char **keys;
905 size_t keys_num = 0;
906 char *this_entry;
908 if (!num_history_items_recorded) /* this is how to disable */
909 return NULL;
910 if (!input_name || !*input_name)
911 return NULL;
913 profile = g_build_filename (home_dir, MC_USERCONF_DIR, MC_HISTORY_FILE, NULL);
914 cfg = mc_config_init (profile);
916 /* get number of keys */
917 keys = mc_config_get_keys (cfg, input_name, &keys_num);
918 g_strfreev (keys);
920 for (i = 0; i < keys_num; i++) {
921 char key_name[BUF_TINY];
922 g_snprintf (key_name, sizeof (key_name), "%lu", (unsigned long)i);
923 this_entry = mc_config_get_string (cfg, input_name, key_name, "");
925 if (this_entry && *this_entry)
926 hist = list_append_unique (hist, this_entry);
929 mc_config_deinit (cfg);
930 g_free (profile);
932 /* return pointer to the last entry in the list */
933 return g_list_last (hist);
936 void
937 history_put (const char *input_name, GList *h)
939 int i;
940 char *profile;
941 mc_config_t *cfg;
943 if (!input_name)
944 return;
946 if (!*input_name)
947 return;
949 if (!h)
950 return;
952 if (!num_history_items_recorded) /* this is how to disable */
953 return;
955 profile = g_build_filename (home_dir, MC_USERCONF_DIR, MC_HISTORY_FILE, NULL);
957 if ((i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) != -1)
958 close (i);
960 /* Make sure the history is only readable by the user */
961 if (chmod (profile, S_IRUSR | S_IWUSR) == -1 && errno != ENOENT) {
962 g_free (profile);
963 return;
966 /* go to end of list */
967 h = g_list_last (h);
969 /* go back 60 places */
970 for (i = 0; i < num_history_items_recorded - 1 && h->prev; i++)
971 h = g_list_previous (h);
973 cfg = mc_config_init(profile);
975 if (input_name)
976 mc_config_del_group(cfg,input_name);
978 /* dump histories into profile */
979 for (i = 0; h; h = g_list_next (h)) {
980 char *text;
982 text = (char *) h->data;
984 /* We shouldn't have null entries, but let's be sure */
985 if (text && *text) {
986 char key_name[BUF_TINY];
987 g_snprintf (key_name, sizeof (key_name), "%d", i++);
988 mc_config_set_string(cfg,input_name, key_name, text);
992 mc_config_save_file (cfg, NULL);
993 mc_config_deinit(cfg);
994 g_free (profile);
997 /* }}} history saving and loading */
1000 /* {{{ history display */
1002 static const char *
1003 i18n_htitle (void)
1005 return _(" History ");
1008 static void
1009 listbox_fwd (WListbox *l)
1011 if (l->current != l->list->prev)
1012 listbox_select_entry (l, l->current->next);
1013 else
1014 listbox_select_first (l);
1017 typedef struct {
1018 Widget *widget;
1019 int count;
1020 size_t maxlen;
1021 } dlg_hist_data;
1023 static cb_ret_t
1024 dlg_hist_reposition (Dlg_head *dlg_head)
1026 dlg_hist_data *data;
1027 int x = 0, y, he, wi;
1029 /* guard checks */
1030 if ((dlg_head == NULL)
1031 || (dlg_head->data == NULL))
1032 return MSG_NOT_HANDLED;
1034 data = (dlg_hist_data *) dlg_head->data;
1036 y = data->widget->y;
1037 he = data->count + 2;
1039 if (he <= y || y > (LINES - 6)) {
1040 he = min (he, y - 1);
1041 y -= he;
1042 } else {
1043 y++;
1044 he = min (he, LINES - y);
1047 if (data->widget->x > 2)
1048 x = data->widget->x - 2;
1050 wi = data->maxlen + 4;
1052 if ((wi + x) > COLS) {
1053 wi = min (wi, COLS);
1054 x = COLS - wi;
1057 dlg_set_position (dlg_head, y, x, y + he, x + wi);
1059 return MSG_HANDLED;
1062 static cb_ret_t
1063 dlg_hist_callback (Dlg_head *h, dlg_msg_t msg, int parm)
1065 switch (msg) {
1066 case DLG_RESIZE:
1067 return dlg_hist_reposition (h);
1069 default:
1070 return default_dlg_callback (h, msg, parm);
1074 char *
1075 show_hist (GList *history, Widget *widget)
1077 GList *hi, *z;
1078 size_t maxlen, i, count = 0;
1079 char *q, *r = NULL;
1080 Dlg_head *query_dlg;
1081 WListbox *query_list;
1082 dlg_hist_data hist_data;
1084 if (history == NULL)
1085 return NULL;
1087 maxlen = str_term_width1 (i18n_htitle ());
1089 z = g_list_first (history);
1090 hi = z;
1091 while (hi) {
1092 i = str_term_width1 ((char *) hi->data);
1093 maxlen = max (maxlen, i);
1094 count++;
1095 hi = g_list_next (hi);
1098 hist_data.maxlen = maxlen;
1099 hist_data.widget = widget;
1100 hist_data.count = count;
1102 query_dlg =
1103 create_dlg (0, 0, 4, 4, dialog_colors, dlg_hist_callback,
1104 "[History-query]", i18n_htitle (), DLG_COMPACT);
1105 query_dlg->data = &hist_data;
1107 query_list = listbox_new (1, 1, 2, 2, NULL);
1109 /* this call makes list stick to all sides of dialog, effectively make
1110 it be resized with dialog */
1111 add_widget_autopos (query_dlg, query_list, WPOS_KEEP_ALL);
1113 /* to avoid diplicating of (calculating sizes in two places)
1114 code, call dlg_hist_callback function here, to set dialog and
1115 controls positions.
1116 The main idea - create 4x4 dialog and add 2x2 list in
1117 center of it, and let dialog function resize it to needed
1118 size. */
1119 dlg_hist_callback (query_dlg, DLG_RESIZE, 0);
1121 if (query_dlg->y < widget->y) {
1122 /* traverse */
1123 hi = z;
1124 while (hi) {
1125 listbox_add_item (query_list, LISTBOX_APPEND_AT_END,
1126 0, (char *) hi->data, NULL);
1127 hi = g_list_next (hi);
1129 listbox_select_last (query_list);
1130 } else {
1131 /* traverse backwards */
1132 hi = g_list_last (history);
1133 while (hi) {
1134 listbox_add_item (query_list, LISTBOX_APPEND_AT_END,
1135 0, (char *) hi->data, NULL);
1136 hi = g_list_previous (hi);
1140 if (run_dlg (query_dlg) != B_CANCEL) {
1141 listbox_get_current (query_list, &q, NULL);
1142 if (q != NULL)
1143 r = g_strdup (q);
1145 destroy_dlg (query_dlg);
1146 return r;
1149 static void
1150 do_show_hist (WInput *in)
1152 char *r;
1153 r = show_hist (in->history, &in->widget);
1154 if (r) {
1155 assign_text (in, r);
1156 g_free (r);
1160 /* }}} history display */
1162 static void
1163 input_destroy (WInput *in)
1165 if (!in){
1166 fprintf (stderr, "Internal error: null Input *\n");
1167 exit (1);
1170 new_input (in);
1172 if (in->history){
1173 if (!in->is_password) /* don't save passwords ;-) */
1174 history_put (in->history_name, in->history);
1176 in->history = g_list_first (in->history);
1177 g_list_foreach (in->history, (GFunc) g_free, NULL);
1178 g_list_free (in->history);
1181 g_free (in->buffer);
1182 free_completions (in);
1183 g_free (in->history_name);
1186 void
1187 input_disable_update (WInput *in)
1189 in->disable_update++;
1192 void
1193 input_enable_update (WInput *in)
1195 in->disable_update--;
1196 update_input (in, 0);
1199 #define ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
1202 push_history (WInput *in, const char *text)
1204 static int i18n;
1205 /* input widget where urls with passwords are entered without any
1206 vfs prefix */
1207 static const char *password_input_fields[] = {
1208 N_(" Link to a remote machine "),
1209 N_(" FTP to machine "),
1210 N_(" SMB link to machine ")
1212 char *t;
1213 const char *p;
1214 size_t i;
1216 if (!i18n) {
1217 i18n = 1;
1218 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1219 password_input_fields[i] = _(password_input_fields[i]);
1222 for (p = text; *p == ' ' || *p == '\t'; p++);
1223 if (!*p)
1224 return 0;
1226 if (in->history) {
1227 /* Avoid duplicated entries */
1228 in->history = g_list_last (in->history);
1229 if (!strcmp ((char *) in->history->data, text))
1230 return 1;
1233 t = g_strdup (text);
1235 if (in->history_name) {
1236 p = in->history_name + 3;
1237 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1238 if (strcmp (p, password_input_fields[i]) == 0)
1239 break;
1240 if (i < ELEMENTS (password_input_fields))
1241 strip_password (t, 0);
1242 else
1243 strip_password (t, 1);
1246 in->history = list_append_unique (in->history, t);
1247 in->need_push = 0;
1249 return 2;
1252 #undef ELEMENTS
1254 /* Cleans the input line and adds the current text to the history */
1255 void
1256 new_input (WInput *in)
1258 if (in->buffer)
1259 push_history (in, in->buffer);
1260 in->need_push = 1;
1261 in->buffer[0] = '\0';
1262 in->point = 0;
1263 in->charpoint = 0;
1264 in->mark = 0;
1265 free_completions (in);
1266 update_input (in, 0);
1269 static void
1270 move_buffer_backward (WInput *in, int start, int end)
1272 int i, pos, len;
1273 int str_len = str_length (in->buffer);
1274 if (start >= str_len || end > str_len + 1) return;
1276 pos = str_offset_to_pos (in->buffer, start);
1277 len = str_offset_to_pos (in->buffer, end) - pos;
1279 for (i = pos; in->buffer[i + len - 1]; i++)
1280 in->buffer[i] = in->buffer[i + len];
1283 static cb_ret_t
1284 insert_char (WInput *in, int c_code)
1286 size_t i;
1287 int res;
1289 if (c_code == -1)
1290 return MSG_NOT_HANDLED;
1292 if (in->charpoint >= MB_LEN_MAX)
1293 return MSG_HANDLED;
1295 in->charbuf[in->charpoint] = c_code;
1296 in->charpoint++;
1298 res = str_is_valid_char (in->charbuf, in->charpoint);
1299 if (res < 0) {
1300 if (res != -2)
1301 in->charpoint = 0; /* broken multibyte char, skip */
1302 return MSG_HANDLED;
1305 in->need_push = 1;
1306 if (strlen (in->buffer) + 1 + in->charpoint >= in->current_max_size){
1307 /* Expand the buffer */
1308 size_t new_length = in->current_max_size +
1309 in->field_width + in->charpoint;
1310 char *narea = g_try_renew (char, in->buffer, new_length);
1311 if (narea){
1312 in->buffer = narea;
1313 in->current_max_size = new_length;
1317 if (strlen (in->buffer) + in->charpoint < in->current_max_size) {
1318 /* bytes from begin */
1319 size_t ins_point = str_offset_to_pos (in->buffer, in->point);
1320 /* move chars */
1321 size_t rest_bytes = strlen (in->buffer + ins_point);
1323 for (i = rest_bytes + 1; i > 0; i--)
1324 in->buffer[ins_point + i + in->charpoint - 1] =
1325 in->buffer[ins_point + i - 1];
1327 memcpy(in->buffer + ins_point, in->charbuf, in->charpoint);
1328 in->point++;
1331 in->charpoint = 0;
1332 return MSG_HANDLED;
1335 static void
1336 beginning_of_line (WInput *in)
1338 in->point = 0;
1339 in->charpoint = 0;
1342 static void
1343 end_of_line (WInput *in)
1345 in->point = str_length (in->buffer);
1346 in->charpoint = 0;
1349 static void
1350 backward_char (WInput *in)
1352 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1354 if (in->point > 0) {
1355 in->point-= str_cprev_noncomb_char (&act, in->buffer);
1357 in->charpoint = 0;
1360 static void
1361 forward_char (WInput *in)
1363 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1364 if (act[0] != '\0') {
1365 in->point+= str_cnext_noncomb_char (&act);
1367 in->charpoint = 0;
1370 static void
1371 forward_word (WInput * in)
1373 const char *p = in->buffer + str_offset_to_pos (in->buffer, in->point);
1375 while (p[0] != '\0' && (str_isspace (p) || str_ispunct (p))) {
1376 str_cnext_char (&p);
1377 in->point++;
1379 while (p[0] != '\0' && !str_isspace (p) && !str_ispunct (p)) {
1380 str_cnext_char (&p);
1381 in->point++;
1385 static void
1386 backward_word (WInput *in)
1388 const char *p;
1389 const char *p_tmp;
1391 for (
1392 p = in->buffer + str_offset_to_pos (in->buffer, in->point);
1393 (p != in->buffer) && (p[0] == '\0');
1394 p-- , in->point--
1397 while (p != in->buffer) {
1398 p_tmp = p;
1399 str_cprev_char (&p);
1400 if (!str_isspace (p) && !str_ispunct (p)) {
1401 p = p_tmp;
1402 break;
1404 in->point--;
1406 while (p != in->buffer) {
1407 str_cprev_char (&p);
1408 if (str_isspace (p) || str_ispunct (p))
1409 break;
1411 in->point--;
1415 static void
1416 key_left (WInput *in)
1418 backward_char (in);
1421 static void
1422 key_ctrl_left (WInput *in)
1424 backward_word (in);
1427 static void
1428 key_right (WInput *in)
1430 forward_char (in);
1433 static void
1434 key_ctrl_right (WInput *in)
1436 forward_word (in);
1438 static void
1439 backward_delete (WInput *in)
1441 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1442 int start;
1444 if (in->point == 0)
1445 return;
1447 start = in->point - str_cprev_noncomb_char (&act, in->buffer);
1448 move_buffer_backward(in, start, in->point);
1449 in->charpoint = 0;
1450 in->need_push = 1;
1451 in->point = start;
1454 static void
1455 delete_char (WInput *in)
1457 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1458 int end = in->point;
1460 end+= str_cnext_noncomb_char (&act);
1462 move_buffer_backward(in, in->point, end);
1463 in->charpoint = 0;
1464 in->need_push = 1;
1467 static void
1468 copy_region (WInput *in, int x_first, int x_last)
1470 int first = min (x_first, x_last);
1471 int last = max (x_first, x_last);
1473 if (last == first)
1474 return;
1476 g_free (kill_buffer);
1478 first = str_offset_to_pos (in->buffer, first);
1479 last = str_offset_to_pos (in->buffer, last);
1481 kill_buffer = g_strndup(in->buffer + first, last - first);
1484 static void
1485 delete_region (WInput *in, int x_first, int x_last)
1487 int first = min (x_first, x_last);
1488 int last = max (x_first, x_last);
1489 size_t len;
1491 in->point = first;
1492 in->mark = first;
1493 last = str_offset_to_pos (in->buffer, last);
1494 first = str_offset_to_pos (in->buffer, first);
1495 len = strlen (&in->buffer[last]) + 1;
1496 memmove (&in->buffer[first], &in->buffer[last], len);
1497 in->charpoint = 0;
1498 in->need_push = 1;
1501 static void
1502 kill_word (WInput *in)
1504 int old_point = in->point;
1505 int new_point;
1507 forward_word (in);
1508 new_point = in->point;
1509 in->point = old_point;
1511 copy_region (in, old_point, new_point);
1512 delete_region (in, old_point, new_point);
1513 in->need_push = 1;
1514 in->charpoint = 0;
1515 in->charpoint = 0;
1518 static void
1519 back_kill_word (WInput *in)
1521 int old_point = in->point;
1522 int new_point;
1524 backward_word (in);
1525 new_point = in->point;
1526 in->point = old_point;
1528 copy_region (in, old_point, new_point);
1529 delete_region (in, old_point, new_point);
1530 in->need_push = 1;
1533 static void
1534 set_mark (WInput *in)
1536 in->mark = in->point;
1539 static void
1540 kill_save (WInput *in)
1542 copy_region (in, in->mark, in->point);
1545 static void
1546 kill_region (WInput *in)
1548 kill_save (in);
1549 delete_region (in, in->point, in->mark);
1552 static void
1553 yank (WInput *in)
1555 char *p;
1557 if (!kill_buffer)
1558 return;
1559 in->charpoint = 0;
1560 for (p = kill_buffer; *p; p++)
1561 insert_char (in, *p);
1562 in->charpoint = 0;
1565 static void
1566 kill_line (WInput *in)
1568 int chp = str_offset_to_pos (in->buffer, in->point);
1569 g_free (kill_buffer);
1570 kill_buffer = g_strdup (&in->buffer[chp]);
1571 in->buffer[chp] = '\0';
1572 in->charpoint = 0;
1575 void
1576 assign_text (WInput *in, const char *text)
1578 free_completions (in);
1579 g_free (in->buffer);
1580 in->buffer = g_strdup (text); /* was in->buffer->text */
1581 in->current_max_size = strlen (in->buffer) + 1;
1582 in->point = str_length (in->buffer);
1583 in->mark = 0;
1584 in->need_push = 1;
1585 in->charpoint = 0;
1588 static void
1589 hist_prev (WInput *in)
1591 if (!in->history)
1592 return;
1594 if (in->need_push) {
1595 switch (push_history (in, in->buffer)) {
1596 case 2:
1597 in->history = g_list_previous (in->history);
1598 break;
1599 case 1:
1600 if (in->history->prev)
1601 in->history = g_list_previous (in->history);
1602 break;
1603 case 0:
1604 break;
1606 } else if (in->history->prev)
1607 in->history = g_list_previous (in->history);
1608 else
1609 return;
1610 assign_text (in, (char *) in->history->data);
1611 in->need_push = 0;
1614 static void
1615 hist_next (WInput *in)
1617 if (in->need_push) {
1618 switch (push_history (in, in->buffer)) {
1619 case 2:
1620 assign_text (in, "");
1621 return;
1622 case 0:
1623 return;
1627 if (!in->history)
1628 return;
1630 if (!in->history->next) {
1631 assign_text (in, "");
1632 return;
1635 in->history = g_list_next (in->history);
1636 assign_text (in, (char *) in->history->data);
1637 in->need_push = 0;
1640 static void
1641 port_region_marked_for_delete (WInput *in)
1643 in->buffer[0] = '\0';
1644 in->point = 0;
1645 in->first = 0;
1646 in->charpoint = 0;
1649 static cb_ret_t
1650 input_execute_cmd (WInput *in, int command)
1652 cb_ret_t res = MSG_HANDLED;
1654 switch (command) {
1655 case CK_InputBol:
1656 beginning_of_line (in);
1657 break;
1658 case CK_InputEol:
1659 end_of_line (in);
1660 break;
1661 case CK_InputMoveLeft:
1662 key_left (in);
1663 break;
1664 case CK_InputWordLeft:
1665 key_ctrl_left (in);
1666 break;
1667 case CK_InputMoveRight:
1668 key_right (in);
1669 break;
1670 case CK_InputWordRight:
1671 key_ctrl_right (in);
1672 break;
1673 case CK_InputBackwardChar:
1674 backward_char (in);
1675 break;
1676 case CK_InputBackwardWord:
1677 backward_word (in);
1678 break;
1679 case CK_InputForwardChar:
1680 forward_char (in);
1681 break;
1682 case CK_InputForwardWord:
1683 forward_word (in);
1684 break;
1685 case CK_InputBackwardDelete:
1686 backward_delete (in);
1687 break;
1688 case CK_InputDeleteChar:
1689 delete_char (in);
1690 break;
1691 case CK_InputKillWord:
1692 kill_word (in);
1693 break;
1694 case CK_InputBackwardKillWord:
1695 back_kill_word (in);
1696 break;
1697 case CK_InputSetMark:
1698 set_mark (in);
1699 break;
1700 case CK_InputKillRegion:
1701 kill_region (in);
1702 break;
1703 case CK_InputKillSave:
1704 kill_save (in);
1705 break;
1706 case CK_InputYank:
1707 yank (in);
1708 break;
1709 case CK_InputKillLine:
1710 kill_line (in);
1711 break;
1712 case CK_InputHistoryPrev:
1713 hist_prev (in);
1714 break;
1715 case CK_InputHistoryNext:
1716 hist_next (in);
1717 break;
1718 case CK_InputHistoryShow:
1719 do_show_hist (in);
1720 break;
1721 case CK_InputComplete:
1722 complete (in);
1723 break;
1724 default:
1725 res = MSG_NOT_HANDLED;
1728 return res;
1731 /* This function is a test for a special input key used in complete.c */
1732 /* Returns 0 if it is not a special key, 1 if it is a non-complete key
1733 and 2 if it is a complete key */
1735 is_in_input_map (WInput *in, int key)
1737 int i;
1739 for (i = 0; input_map[i].key; i++) {
1740 if (key == input_map[i].key) {
1741 input_execute_cmd (in, input_map[i].command);
1742 if (input_map[i].command == CK_InputComplete)
1743 return 2;
1744 else
1745 return 1;
1748 return 0;
1751 cb_ret_t
1752 handle_char (WInput *in, int key)
1754 cb_ret_t v;
1755 int i;
1757 v = MSG_NOT_HANDLED;
1759 if (quote) {
1760 free_completions (in);
1761 v = insert_char (in, key);
1762 update_input (in, 1);
1763 quote = 0;
1764 return v;
1767 for (i = 0; input_map[i].key; i++) {
1768 if (key == input_map[i].key) {
1769 if (input_map[i].command != CK_InputComplete)
1770 free_completions (in);
1771 input_execute_cmd (in, input_map[i].command);
1772 update_input (in, 1);
1773 v = MSG_HANDLED;
1774 break;
1777 if (input_map[i].command == 0) {
1778 if (key > 255)
1779 return MSG_NOT_HANDLED;
1780 if (in->first)
1781 port_region_marked_for_delete (in);
1782 free_completions (in);
1783 v = insert_char (in, key);
1785 update_input (in, 1);
1786 return v;
1789 /* Inserts text in input line */
1790 void
1791 stuff (WInput *in, const char *text, int insert_extra_space)
1793 input_disable_update (in);
1794 while (*text)
1795 handle_char (in, *text++);
1796 if (insert_extra_space)
1797 handle_char (in, ' ');
1798 input_enable_update (in);
1799 update_input (in, 1);
1802 void
1803 input_set_point (WInput *in, int pos)
1805 int max_pos = str_length (in->buffer);
1807 if (pos > max_pos)
1808 pos = max_pos;
1809 if (pos != in->point)
1810 free_completions (in);
1811 in->point = pos;
1812 in->charpoint = 0;
1813 update_input (in, 1);
1816 cb_ret_t
1817 input_callback (Widget *w, widget_msg_t msg, int parm)
1819 WInput *in = (WInput *) w;
1820 cb_ret_t v;
1822 switch (msg) {
1823 case WIDGET_KEY:
1824 if (parm == XCTRL ('q')) {
1825 quote = 1;
1826 v = handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
1827 quote = 0;
1828 return v;
1831 /* Keys we want others to handle */
1832 if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1833 || parm == KEY_F (10) || parm == XCTRL ('g') || parm == '\n')
1834 return MSG_NOT_HANDLED;
1836 /* When pasting multiline text, insert literal Enter */
1837 if ((parm & ~KEY_M_MASK) == '\n') {
1838 quote = 1;
1839 v = handle_char (in, '\n');
1840 quote = 0;
1841 return v;
1844 return handle_char (in, parm);
1846 case WIDGET_COMMAND:
1847 return input_execute_cmd (in, parm);
1849 case WIDGET_FOCUS:
1850 case WIDGET_UNFOCUS:
1851 case WIDGET_DRAW:
1852 update_input (in, 0);
1853 return MSG_HANDLED;
1855 case WIDGET_CURSOR:
1856 widget_move (&in->widget, 0, str_term_width2 (in->buffer, in->point)
1857 - in->term_first_shown);
1858 return MSG_HANDLED;
1860 case WIDGET_DESTROY:
1861 input_destroy (in);
1862 return MSG_HANDLED;
1864 default:
1865 return default_proc (msg, parm);
1869 static int
1870 input_event (Gpm_Event * event, void *data)
1872 WInput *in = data;
1874 if (event->type & (GPM_DOWN | GPM_DRAG)) {
1875 dlg_select_widget (in);
1877 if (event->x >= in->field_width - HISTORY_BUTTON_WIDTH + 1
1878 && should_show_history_button (in)) {
1879 do_show_hist (in);
1880 } else {
1881 in->point = str_length (in->buffer);
1882 if (event->x + in->term_first_shown - 1 <
1883 str_term_width1 (in->buffer))
1885 in->point = str_column_to_pos (in->buffer, event->x
1886 + in->term_first_shown - 1);
1889 update_input (in, 1);
1891 return MOU_NORMAL;
1894 WInput *
1895 input_new (int y, int x, int color, int width, const char *def_text,
1896 const char *histname, INPUT_COMPLETE_FLAGS completion_flags)
1898 WInput *in = g_new (WInput, 1);
1899 int initial_buffer_len;
1901 init_widget (&in->widget, y, x, 1, width, input_callback, input_event);
1903 /* history setup */
1904 in->history = NULL;
1905 in->history_name = 0;
1906 if (histname) {
1907 if (*histname) {
1908 in->history_name = g_strdup (histname);
1909 in->history = history_get (histname);
1913 if (def_text == NULL)
1914 def_text = "";
1916 if (def_text == INPUT_LAST_TEXT) {
1917 def_text = "";
1918 if (in->history)
1919 if (in->history->data)
1920 def_text = (char *) in->history->data;
1922 initial_buffer_len = 1 + max ((size_t) width, strlen (def_text));
1923 in->widget.options |= W_IS_INPUT;
1924 in->completions = NULL;
1925 in->completion_flags = completion_flags;
1926 in->current_max_size = initial_buffer_len;
1927 in->buffer = g_new (char, initial_buffer_len);
1928 in->color = color;
1929 in->field_width = width;
1930 in->first = 1;
1931 in->term_first_shown = 0;
1932 in->disable_update = 0;
1933 in->mark = 0;
1934 in->need_push = 1;
1935 in->is_password = 0;
1937 strcpy (in->buffer, def_text);
1938 in->point = str_length (in->buffer);
1939 in->charpoint = 0;
1940 return in;
1943 /* Listbox widget */
1945 /* Should draw the scrollbar, but currently draws only
1946 * indications that there is more information
1948 static int listbox_cdiff (WLEntry *s, WLEntry *e);
1950 static void
1951 listbox_drawscroll (WListbox *l)
1953 int line = 0;
1954 int i, top;
1955 int max_line = l->widget.lines - 1;
1957 /* Are we at the top? */
1958 widget_move (&l->widget, 0, l->widget.cols);
1959 if (l->list == l->top)
1960 tty_print_one_vline ();
1961 else
1962 tty_print_char ('^');
1964 /* Are we at the bottom? */
1965 widget_move (&l->widget, max_line, l->widget.cols);
1966 top = listbox_cdiff (l->list, l->top);
1967 if ((top + l->widget.lines == l->count) || l->widget.lines >= l->count)
1968 tty_print_one_vline ();
1969 else
1970 tty_print_char ('v');
1972 /* Now draw the nice relative pointer */
1973 if (l->count != 0)
1974 line = 1+ ((l->pos * (l->widget.lines - 2)) / l->count);
1976 for (i = 1; i < max_line; i++){
1977 widget_move (&l->widget, i, l->widget.cols);
1978 if (i != line)
1979 tty_print_one_vline ();
1980 else
1981 tty_print_char ('*');
1985 static void
1986 listbox_draw (WListbox *l, gboolean focused)
1988 const Dlg_head *h = l->widget.parent;
1989 const int normalc = DLG_NORMALC (h);
1990 int selc = focused ? DLG_HOT_FOCUSC (h) : DLG_FOCUSC (h);
1992 WLEntry *e;
1993 int i;
1994 int sel_line = -1;
1995 const char *text;
1997 for (e = l->top, i = 0; i < l->widget.lines; i++) {
1998 /* Display the entry */
1999 if (e == l->current && sel_line == -1) {
2000 sel_line = i;
2001 tty_setcolor (selc);
2002 } else
2003 tty_setcolor (normalc);
2005 widget_move (&l->widget, i, 1);
2007 if ((i > 0 && e == l->list) || !l->list)
2008 text = "";
2009 else {
2010 text = e->text;
2011 e = e->next;
2013 tty_print_string (str_fit_to_term (text, l->widget.cols - 2, J_LEFT_FIT));
2015 l->cursor_y = sel_line;
2017 if (l->scrollbar && l->count > l->widget.lines) {
2018 tty_setcolor (normalc);
2019 listbox_drawscroll (l);
2023 /* Returns the number of items between s and e,
2024 must be on the same linked list */
2025 static int
2026 listbox_cdiff (WLEntry *s, WLEntry *e)
2028 int count;
2030 for (count = 0; s != e; count++)
2031 s = s->next;
2032 return count;
2035 static WLEntry *
2036 listbox_check_hotkey (WListbox *l, int key)
2038 int i;
2039 WLEntry *e;
2041 i = 0;
2042 e = l->list;
2043 if (!e)
2044 return NULL;
2046 while (1) {
2048 /* If we didn't find anything, return */
2049 if (i && e == l->list)
2050 return NULL;
2052 if (e->hotkey == key)
2053 return e;
2055 i++;
2056 e = e->next;
2060 /* Selects the last entry and scrolls the list to the bottom */
2061 void
2062 listbox_select_last (WListbox *l)
2064 unsigned int i;
2065 l->current = l->top = l->list->prev;
2066 for (i = min (l->widget.lines, l->count) - 1; i; i--)
2067 l->top = l->top->prev;
2068 l->pos = l->count - 1;
2071 /* Selects the first entry and scrolls the list to the top */
2072 void
2073 listbox_select_first (WListbox *l)
2075 l->current = l->top = l->list;
2076 l->pos = 0;
2079 void
2080 listbox_remove_list (WListbox *l)
2082 WLEntry *p, *q;
2084 if (!l->count)
2085 return;
2087 p = l->list;
2089 while (l->count--) {
2090 q = p->next;
2091 g_free (p->text);
2092 g_free (p);
2093 p = q;
2095 l->pos = l->count = 0;
2096 l->list = l->top = l->current = 0;
2100 * bor 30.10.96: added force flag to remove *last* entry as well
2101 * bor 30.10.96: corrected selection bug if last entry was removed
2104 void
2105 listbox_remove_current (WListbox *l, int force)
2107 WLEntry *p;
2109 /* Ok, note: this won't allow for emtpy lists */
2110 if (!force && (!l->count || l->count == 1))
2111 return;
2113 l->count--;
2114 p = l->current;
2116 if (l->count) {
2117 l->current->next->prev = l->current->prev;
2118 l->current->prev->next = l->current->next;
2119 if (p->next == l->list) {
2120 l->current = p->prev;
2121 l->pos--;
2123 else
2124 l->current = p->next;
2126 if (p == l->list)
2127 l->list = l->top = p->next;
2128 } else {
2129 l->pos = 0;
2130 l->list = l->top = l->current = 0;
2133 g_free (p->text);
2134 g_free (p);
2137 /* Makes *e the selected entry (sets current and pos) */
2138 void
2139 listbox_select_entry (WListbox *l, WLEntry *dest)
2141 WLEntry *e;
2142 int pos;
2143 int top_seen;
2145 top_seen = 0;
2147 /* Special case */
2148 for (pos = 0, e = l->list; pos < l->count; e = e->next, pos++){
2150 if (e == l->top)
2151 top_seen = 1;
2153 if (e == dest){
2154 l->current = e;
2155 if (top_seen){
2156 while (listbox_cdiff (l->top, l->current) >= l->widget.lines)
2157 l->top = l->top->next;
2158 } else {
2159 l->top = l->current;
2161 l->pos = pos;
2162 return;
2165 /* If we are unable to find it, set decent values */
2166 l->current = l->top = l->list;
2167 l->pos = 0;
2170 /* Selects from base the pos element */
2171 static WLEntry *
2172 listbox_select_pos (WListbox *l, WLEntry *base, int pos)
2174 WLEntry *last = l->list->prev;
2176 if (base == last)
2177 return last;
2178 while (pos--){
2179 base = base->next;
2180 if (base == last)
2181 break;
2183 return base;
2186 static void
2187 listbox_back (WListbox *l)
2189 if (l->pos != 0)
2190 listbox_select_entry (l, l->current->prev);
2191 else
2192 listbox_select_last (l);
2195 /* Return MSG_HANDLED if we want a redraw */
2196 static cb_ret_t
2197 listbox_key (WListbox *l, int key)
2199 int i;
2201 cb_ret_t j = MSG_NOT_HANDLED;
2203 /* focus on listbox item N by '0'..'9' keys */
2204 if (key >= '0' && key <= '9') {
2205 int oldpos = l->pos;
2206 listbox_select_by_number(l, key - '0');
2208 /* need scroll to item? */
2209 if (abs(oldpos - l->pos) > l->widget.lines)
2210 l->top = l->current;
2212 return MSG_HANDLED;
2215 if (!l->list)
2216 return MSG_NOT_HANDLED;
2218 switch (key){
2219 case KEY_HOME:
2220 case KEY_A1:
2221 case ALT ('<'):
2222 listbox_select_first (l);
2223 return MSG_HANDLED;
2225 case KEY_END:
2226 case KEY_C1:
2227 case ALT ('>'):
2228 listbox_select_last (l);
2229 return MSG_HANDLED;
2231 case XCTRL('p'):
2232 case KEY_UP:
2233 listbox_back (l);
2234 return MSG_HANDLED;
2236 case XCTRL('n'):
2237 case KEY_DOWN:
2238 listbox_fwd (l);
2239 return MSG_HANDLED;
2241 case KEY_NPAGE:
2242 case XCTRL('v'):
2243 for (i = 0; ((i < l->widget.lines - 1)
2244 && (l->current != l->list->prev)); i++) {
2245 listbox_fwd (l);
2246 j = MSG_HANDLED;
2248 return j;
2250 case KEY_PPAGE:
2251 case ALT('v'):
2252 for (i = 0; ((i < l->widget.lines - 1)
2253 && (l->current != l->list)); i++) {
2254 listbox_back (l);
2255 j = MSG_HANDLED;
2257 return j;
2259 return MSG_NOT_HANDLED;
2262 static void
2263 listbox_destroy (WListbox *l)
2265 WLEntry *n, *p = l->list;
2266 int i;
2268 for (i = 0; i < l->count; i++){
2269 n = p->next;
2270 g_free (p->text);
2271 g_free (p);
2272 p = n;
2276 static cb_ret_t
2277 listbox_callback (Widget *w, widget_msg_t msg, int parm)
2279 WListbox *l = (WListbox *) w;
2280 Dlg_head *h = l->widget.parent;
2281 WLEntry *e;
2282 cb_ret_t ret_code;
2284 switch (msg) {
2285 case WIDGET_INIT:
2286 return MSG_HANDLED;
2288 case WIDGET_HOTKEY:
2289 e = listbox_check_hotkey (l, parm);
2290 if (e != NULL) {
2291 int action;
2293 listbox_select_entry (l, e);
2295 (*h->callback) (h, DLG_ACTION, l->pos);
2297 if (l->cback)
2298 action = (*l->cback) (l);
2299 else
2300 action = LISTBOX_DONE;
2302 if (action == LISTBOX_DONE) {
2303 h->ret_value = B_ENTER;
2304 dlg_stop (h);
2306 return MSG_HANDLED;
2308 return MSG_NOT_HANDLED;
2310 case WIDGET_KEY:
2311 ret_code = listbox_key (l, parm);
2312 if (ret_code != MSG_NOT_HANDLED) {
2313 listbox_draw (l, TRUE);
2314 (*h->callback) (h, DLG_ACTION, l->pos);
2316 return ret_code;
2318 case WIDGET_CURSOR:
2319 widget_move (&l->widget, l->cursor_y, 0);
2320 (*h->callback) (h, DLG_ACTION, l->pos);
2321 return MSG_HANDLED;
2323 case WIDGET_FOCUS:
2324 case WIDGET_UNFOCUS:
2325 case WIDGET_DRAW:
2326 listbox_draw (l, msg != WIDGET_UNFOCUS);
2327 return MSG_HANDLED;
2329 case WIDGET_DESTROY:
2330 listbox_destroy (l);
2331 return MSG_HANDLED;
2333 case WIDGET_RESIZED:
2334 return MSG_HANDLED;
2336 default:
2337 return default_proc (msg, parm);
2341 static int
2342 listbox_event (Gpm_Event *event, void *data)
2344 WListbox *l = data;
2345 int i;
2347 Dlg_head *h = l->widget.parent;
2349 /* Single click */
2350 if (event->type & GPM_DOWN)
2351 dlg_select_widget (l);
2353 if (l->list == NULL)
2354 return MOU_NORMAL;
2356 if (event->type & (GPM_DOWN | GPM_DRAG)) {
2357 int ret = MOU_REPEAT;
2359 if (event->x < 0 || event->x > l->widget.cols)
2360 return ret;
2362 if (event->y < 1)
2363 for (i = -event->y; i >= 0; i--)
2364 listbox_back (l);
2365 else if (event->y > l->widget.lines)
2366 for (i = event->y - l->widget.lines; i > 0; i--)
2367 listbox_fwd (l);
2368 else if (event->buttons & GPM_B_UP) {
2369 listbox_back (l);
2370 ret = MOU_NORMAL;
2371 } else if (event->buttons & GPM_B_DOWN) {
2372 listbox_fwd (l);
2373 ret = MOU_NORMAL;
2374 } else
2375 listbox_select_entry (l,
2376 listbox_select_pos (l, l->top,
2377 event->y - 1));
2379 /* We need to refresh ourselves since the dialog manager doesn't */
2380 /* know about this event */
2381 listbox_draw (l, TRUE);
2382 return ret;
2385 /* Double click */
2386 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE)) {
2387 int action;
2389 if (event->x < 0 || event->x >= l->widget.cols
2390 || event->y < 1 || event->y > l->widget.lines)
2391 return MOU_NORMAL;
2393 dlg_select_widget (l);
2394 listbox_select_entry (l,
2395 listbox_select_pos (l, l->top,
2396 event->y - 1));
2398 if (l->cback)
2399 action = (*l->cback) (l);
2400 else
2401 action = LISTBOX_DONE;
2403 if (action == LISTBOX_DONE) {
2404 h->ret_value = B_ENTER;
2405 dlg_stop (h);
2406 return MOU_NORMAL;
2409 return MOU_NORMAL;
2412 WListbox *
2413 listbox_new (int y, int x, int height, int width, lcback callback)
2415 WListbox *l = g_new (WListbox, 1);
2417 if (height <= 0)
2418 height = 1;
2420 init_widget (&l->widget, y, x, height, width,
2421 listbox_callback, listbox_event);
2423 l->list = l->top = l->current = 0;
2424 l->pos = 0;
2425 l->count = 0;
2426 l->cback = callback;
2427 l->allow_duplicates = 1;
2428 l->scrollbar = !tty_is_slow ();
2429 widget_want_hotkey (l->widget, 1);
2431 return l;
2434 /* Listbox item adding function. They still lack a lot of functionality */
2435 /* any takers? */
2436 /* 1.11.96 bor: added pos argument to control placement of new entry */
2437 static void
2438 listbox_append_item (WListbox *l, WLEntry *e, enum append_pos pos)
2440 if (!l->list){
2441 l->list = e;
2442 l->top = e;
2443 l->current = e;
2444 e->next = l->list;
2445 e->prev = l->list;
2446 } else if (pos == LISTBOX_APPEND_AT_END) {
2447 e->next = l->list;
2448 e->prev = l->list->prev;
2449 l->list->prev->next = e;
2450 l->list->prev = e;
2451 } else if (pos == LISTBOX_APPEND_BEFORE){
2452 e->next = l->current;
2453 e->prev = l->current->prev;
2454 l->current->prev->next = e;
2455 l->current->prev = e;
2456 if (l->list == l->current) { /* move list one position down */
2457 l->list = e;
2458 l->top = e;
2460 } else if (pos == LISTBOX_APPEND_AFTER) {
2461 e->prev = l->current;
2462 e->next = l->current->next;
2463 l->current->next->prev = e;
2464 l->current->next = e;
2465 } else if (pos == LISTBOX_APPEND_SORTED) {
2466 WLEntry *w = l->list;
2468 while (w->next != l->list && strcmp (e->text, w->text) > 0)
2469 w = w->next;
2470 if (w->next == l->list) {
2471 e->prev = w;
2472 e->next = l->list;
2473 w->next = e;
2474 l->list->prev = e;
2475 } else {
2476 e->next = w;
2477 e->prev = w->prev;
2478 w->prev->next = e;
2479 w->prev = e;
2482 l->count++;
2485 char *
2486 listbox_add_item (WListbox *l, enum append_pos pos, int hotkey,
2487 const char *text, void *data)
2489 WLEntry *entry;
2491 if (!l)
2492 return NULL;
2494 if (!l->allow_duplicates)
2495 if (listbox_search_text (l, text))
2496 return NULL;
2498 entry = g_new (WLEntry, 1);
2499 entry->text = g_strdup (text);
2500 entry->data = data;
2501 entry->hotkey = hotkey;
2503 listbox_append_item (l, entry, pos);
2505 return entry->text;
2508 /* Selects the nth entry in the listbox */
2509 void
2510 listbox_select_by_number (WListbox *l, int n)
2512 if (l->list != NULL)
2513 listbox_select_entry (l, listbox_select_pos (l, l->list, n));
2516 WLEntry *
2517 listbox_search_text (WListbox *l, const char *text)
2519 WLEntry *e;
2521 e = l->list;
2522 if (!e)
2523 return NULL;
2525 do {
2526 if(!strcmp (e->text, text))
2527 return e;
2528 e = e->next;
2529 } while (e!=l->list);
2531 return NULL;
2534 /* Returns the current string text as well as the associated extra data */
2535 void
2536 listbox_get_current (WListbox *l, char **string, char **extra)
2538 if (!l->current){
2539 *string = 0;
2540 *extra = 0;
2542 if (string && l->current)
2543 *string = l->current->text;
2544 if (extra && l->current)
2545 *extra = l->current->data;
2548 /* returns TRUE if a function has been called, FALSE otherwise. */
2549 static gboolean
2550 buttonbar_call (WButtonBar *bb, int i)
2552 switch (bb->labels[i].tag) {
2553 case BBFUNC_NONE:
2554 break;
2555 case BBFUNC_VOID:
2556 bb->labels[i].u.fn_void ();
2557 return TRUE;
2558 case BBFUNC_PTR:
2559 bb->labels[i].u.fn_ptr (bb->labels[i].data);
2560 return TRUE;
2562 return FALSE;
2565 /* calculate width of one button, width is never lesser than 7 */
2566 static int
2567 buttonbat_get_button_width ()
2569 int result = COLS / BUTTONBAR_LABELS_NUM;
2570 return (result >= 7) ? result : 7;
2574 static cb_ret_t
2575 buttonbar_callback (Widget *w, widget_msg_t msg, int parm)
2577 WButtonBar *bb = (WButtonBar *) w;
2578 int i;
2579 const char *text;
2581 switch (msg) {
2582 case WIDGET_FOCUS:
2583 return MSG_NOT_HANDLED;
2585 case WIDGET_HOTKEY:
2586 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
2587 if (parm == KEY_F (i + 1) && buttonbar_call (bb, i))
2588 return MSG_HANDLED;
2589 return MSG_NOT_HANDLED;
2591 case WIDGET_DRAW:
2592 if (bb->visible) {
2593 widget_move (&bb->widget, 0, 0);
2594 tty_setcolor (DEFAULT_COLOR);
2595 bb->btn_width = buttonbat_get_button_width ();
2596 tty_printf ("%-*s", bb->widget.cols, "");
2598 for (i = 0; i < COLS / bb->btn_width && i < BUTTONBAR_LABELS_NUM; i++) {
2599 widget_move (&bb->widget, 0, i * bb->btn_width);
2600 tty_setcolor (BUTTONBAR_HOTKEY_COLOR);
2601 tty_printf ("%2d", i + 1);
2602 tty_setcolor (BUTTONBAR_BUTTON_COLOR);
2603 text = (bb->labels[i].text != NULL) ? bb->labels[i].text : "";
2604 tty_print_string (str_fit_to_term (text, bb->btn_width - 2, J_CENTER_LEFT));
2607 return MSG_HANDLED;
2609 case WIDGET_DESTROY:
2610 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
2611 g_free (bb->labels[i].text);
2612 return MSG_HANDLED;
2614 default:
2615 return default_proc (msg, parm);
2619 static int
2620 buttonbar_event (Gpm_Event *event, void *data)
2622 WButtonBar *bb = data;
2623 int button;
2625 if (!(event->type & GPM_UP))
2626 return MOU_NORMAL;
2627 if (event->y == 2)
2628 return MOU_NORMAL;
2629 button = (event->x - 1) / bb->btn_width;
2630 if (button < BUTTONBAR_LABELS_NUM)
2631 buttonbar_call (bb, button);
2632 return MOU_NORMAL;
2635 WButtonBar *
2636 buttonbar_new (int visible)
2638 WButtonBar *bb;
2639 int i;
2641 bb = g_new0 (WButtonBar, 1);
2643 init_widget (&bb->widget, LINES - 1, 0, 1, COLS,
2644 buttonbar_callback, buttonbar_event);
2645 bb->widget.pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_BOTTOM;
2646 bb->visible = visible;
2647 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++){
2648 bb->labels[i].text = NULL;
2649 bb->labels[i].tag = BBFUNC_NONE;
2651 widget_want_hotkey (bb->widget, 1);
2652 widget_want_cursor (bb->widget, 0);
2653 bb->btn_width = buttonbat_get_button_width ();
2655 return bb;
2658 static void
2659 set_label_text (WButtonBar * bb, int index, const char *text)
2661 g_free (bb->labels[index - 1].text);
2663 bb->labels[index - 1].text = g_strdup (text);
2666 /* Find ButtonBar widget in the dialog */
2667 WButtonBar *
2668 find_buttonbar (Dlg_head *h)
2670 WButtonBar *bb;
2672 bb = (WButtonBar *) find_widget_type (h, buttonbar_callback);
2673 return bb;
2676 void
2677 buttonbar_clear_label (Dlg_head *h, int idx)
2679 WButtonBar *bb = find_buttonbar (h);
2681 if (!bb)
2682 return;
2684 set_label_text (bb, idx, "");
2685 bb->labels[idx - 1].tag = BBFUNC_NONE;
2688 void
2689 buttonbar_set_label_data (Dlg_head *h, int idx, const char *text,
2690 buttonbarfn cback, void *data)
2692 WButtonBar *bb = find_buttonbar (h);
2694 if (!bb)
2695 return;
2697 assert (cback != (buttonbarfn) 0);
2698 set_label_text (bb, idx, text);
2699 bb->labels[idx - 1].tag = BBFUNC_PTR;
2700 bb->labels[idx - 1].u.fn_ptr = cback;
2701 bb->labels[idx - 1].data = data;
2704 void
2705 buttonbar_set_label (Dlg_head *h, int idx, const char *text, voidfn cback)
2707 WButtonBar *bb = find_buttonbar (h);
2709 if (!bb)
2710 return;
2712 assert (cback != (voidfn) 0);
2713 set_label_text (bb, idx, text);
2714 bb->labels[idx - 1].tag = BBFUNC_VOID;
2715 bb->labels[idx - 1].u.fn_void = cback;
2718 void
2719 buttonbar_set_visible (WButtonBar *bb, gboolean visible)
2721 bb->visible = visible;
2724 void
2725 buttonbar_redraw (Dlg_head *h)
2727 WButtonBar *bb = find_buttonbar (h);
2729 if (!bb)
2730 return;
2732 send_message ((Widget *) bb, WIDGET_DRAW, 0);
2735 static cb_ret_t
2736 groupbox_callback (Widget *w, widget_msg_t msg, int parm)
2738 WGroupbox *g = (WGroupbox *) w;
2740 switch (msg) {
2741 case WIDGET_INIT:
2742 return MSG_HANDLED;
2744 case WIDGET_FOCUS:
2745 return MSG_NOT_HANDLED;
2747 case WIDGET_DRAW:
2748 tty_setcolor (COLOR_NORMAL);
2749 draw_box (g->widget.parent, g->widget.y - g->widget.parent->y,
2750 g->widget.x - g->widget.parent->x, g->widget.lines,
2751 g->widget.cols);
2753 tty_setcolor (COLOR_HOT_NORMAL);
2754 dlg_move (g->widget.parent, g->widget.y - g->widget.parent->y,
2755 g->widget.x - g->widget.parent->x + 1);
2756 tty_print_string (g->title);
2757 return MSG_HANDLED;
2759 case WIDGET_DESTROY:
2760 g_free (g->title);
2761 return MSG_HANDLED;
2763 default:
2764 return default_proc (msg, parm);
2768 WGroupbox *
2769 groupbox_new (int y, int x, int height, int width, const char *title)
2771 WGroupbox *g = g_new (WGroupbox, 1);
2773 init_widget (&g->widget, y, x, height, width, groupbox_callback, NULL);
2775 g->widget.options &= ~W_WANT_CURSOR;
2776 widget_want_hotkey (g->widget, 0);
2778 /* Strip existing spaces, add one space before and after the title */
2779 if (title) {
2780 char *t;
2781 t = g_strstrip (g_strdup (title));
2782 g->title = g_strconcat (" ", t, " ", (char *) NULL);
2783 g_free (t);
2786 return g;