Removed type SHELL_ESCAPED_STR in favour of plain char*
[midnight-commander.git] / src / widget.c
blob3b209adc7a996bfa2781ba297fe6fb701b086af0
1 /* Widgets for the Midnight Commander
3 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
4 2004, 2005, 2006, 2007 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 #include <config.h>
30 #include <assert.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/types.h>
37 #include <mhl/types.h>
38 #include <mhl/memory.h>
39 #include <mhl/string.h>
41 #include "global.h"
42 #include "tty.h"
43 #include "color.h"
44 #include "mouse.h"
45 #include "dialog.h"
46 #include "widget.h"
47 #include "win.h"
48 #include "key.h" /* XCTRL and ALT macros */
49 #include "profile.h" /* for history loading and saving */
50 #include "wtools.h" /* For common_dialog_repaint() */
51 #include "main.h" /* for `slow_terminal' */
53 #define HISTORY_FILE_NAME ".mc/history"
55 struct WButtonBar {
56 Widget widget;
57 int visible; /* Is it visible? */
58 struct {
59 char *text;
60 enum { BBFUNC_NONE, BBFUNC_VOID, BBFUNC_PTR } tag;
61 union {
62 voidfn fn_void;
63 buttonbarfn fn_ptr;
64 } u;
65 void *data;
66 } labels [10];
69 static int button_event (Gpm_Event *event, void *);
71 int quote = 0;
73 static void
74 widget_selectcolor (Widget *w, gboolean focused, gboolean hotkey)
76 Dlg_head *h = w->parent;
78 attrset (hotkey
79 ? (focused
80 ? DLG_HOT_FOCUSC (h)
81 : DLG_HOT_NORMALC (h))
82 : (focused
83 ? DLG_FOCUSC (h)
84 : DLG_NORMALC (h)));
87 static cb_ret_t
88 button_callback (Widget *w, widget_msg_t msg, int parm)
90 WButton *b = (WButton *) w;
91 char buf[BUF_SMALL];
92 int stop = 0;
93 int off = 0;
94 Dlg_head *h = b->widget.parent;
96 switch (msg) {
97 case WIDGET_HOTKEY:
99 * Don't let the default button steal Enter from the current
100 * button. This is a workaround for the flawed event model
101 * when hotkeys are sent to all widgets before the key is
102 * handled by the current widget.
104 if (parm == '\n' && h->current == &b->widget) {
105 button_callback (w, WIDGET_KEY, ' ');
106 return MSG_HANDLED;
109 if (parm == '\n' && b->flags == DEFPUSH_BUTTON) {
110 button_callback (w, WIDGET_KEY, ' ');
111 return MSG_HANDLED;
114 if (b->hotkey == tolower (parm)) {
115 button_callback (w, WIDGET_KEY, ' ');
116 return MSG_HANDLED;
119 return MSG_NOT_HANDLED;
121 case WIDGET_KEY:
122 if (parm != ' ' && parm != '\n')
123 return MSG_NOT_HANDLED;
125 if (b->callback)
126 stop = (*b->callback) (b->action);
127 if (!b->callback || stop) {
128 h->ret_value = b->action;
129 dlg_stop (h);
131 return MSG_HANDLED;
133 case WIDGET_CURSOR:
134 switch (b->flags) {
135 case DEFPUSH_BUTTON:
136 off = 3;
137 break;
138 case NORMAL_BUTTON:
139 off = 2;
140 break;
141 case NARROW_BUTTON:
142 off = 1;
143 break;
144 case HIDDEN_BUTTON:
145 default:
146 off = 0;
147 break;
149 widget_move (&b->widget, 0, b->hotpos + off);
150 return MSG_HANDLED;
152 case WIDGET_UNFOCUS:
153 case WIDGET_FOCUS:
154 case WIDGET_DRAW:
155 if (msg == WIDGET_UNFOCUS)
156 b->selected = 0;
157 else if (msg == WIDGET_FOCUS)
158 b->selected = 1;
160 switch (b->flags) {
161 case DEFPUSH_BUTTON:
162 g_snprintf (buf, sizeof (buf), "[< %s >]", b->text);
163 off = 3;
164 break;
165 case NORMAL_BUTTON:
166 g_snprintf (buf, sizeof (buf), "[ %s ]", b->text);
167 off = 2;
168 break;
169 case NARROW_BUTTON:
170 g_snprintf (buf, sizeof (buf), "[%s]", b->text);
171 off = 1;
172 break;
173 case HIDDEN_BUTTON:
174 default:
175 buf[0] = '\0';
176 off = 0;
177 break;
180 widget_selectcolor (w, b->selected, FALSE);
181 widget_move (w, 0, 0);
183 addstr (buf);
185 if (b->hotpos >= 0) {
186 widget_selectcolor (w, b->selected, TRUE);
187 widget_move (w, 0, b->hotpos + off);
188 addch ((unsigned char) b->text[b->hotpos]);
190 return MSG_HANDLED;
192 case WIDGET_DESTROY:
193 g_free (b->text);
194 return MSG_HANDLED;
196 default:
197 return default_proc (msg, parm);
201 static int
202 button_event (Gpm_Event *event, void *data)
204 WButton *b = data;
206 if (event->type & (GPM_DOWN|GPM_UP)){
207 Dlg_head *h=b->widget.parent;
208 dlg_select_widget (b);
209 if (event->type & GPM_UP){
210 button_callback ((Widget *) data, WIDGET_KEY, ' ');
211 (*h->callback) (h, DLG_POST_KEY, ' ');
212 return MOU_NORMAL;
215 return MOU_NORMAL;
218 static int
219 button_len (const char *text, unsigned int flags)
221 int ret = strlen (text);
222 switch (flags){
223 case DEFPUSH_BUTTON:
224 ret += 6;
225 break;
226 case NORMAL_BUTTON:
227 ret += 4;
228 break;
229 case NARROW_BUTTON:
230 ret += 2;
231 break;
232 case HIDDEN_BUTTON:
233 default:
234 return 0;
236 return ret;
240 * Locate the hotkey and remove it from the button text. Assuming that
241 * the button text is g_malloc()ed, we can safely change and shorten it.
243 static void
244 button_scan_hotkey (WButton *b)
246 char *cp = strchr (b->text, '&');
248 if (cp != NULL && cp[1] != '\0') {
249 g_strlcpy (cp, cp + 1, strlen (cp));
250 b->hotkey = tolower ((unsigned char) *cp);
251 b->hotpos = cp - b->text;
255 WButton *
256 button_new (int y, int x, int action, int flags, const char *text,
257 bcback callback)
259 WButton *b = g_new (WButton, 1);
261 init_widget (&b->widget, y, x, 1, button_len (text, flags),
262 button_callback, button_event);
264 b->action = action;
265 b->flags = flags;
266 b->selected = 0;
267 b->text = g_strdup (text);
268 b->callback = callback;
269 widget_want_hotkey (b->widget, 1);
270 b->hotkey = 0;
271 b->hotpos = -1;
273 button_scan_hotkey(b);
274 return b;
277 const char *
278 button_get_text (WButton *b)
280 return b->text;
283 void
284 button_set_text (WButton *b, const char *text)
286 g_free (b->text);
287 b->text = g_strdup (text);
288 b->widget.cols = button_len (text, b->flags);
289 button_scan_hotkey(b);
290 dlg_redraw (b->widget.parent);
294 /* Radio button widget */
295 static int radio_event (Gpm_Event *event, void *);
297 static cb_ret_t
298 radio_callback (Widget *w, widget_msg_t msg, int parm)
300 WRadio *r = (WRadio *) w;
301 int i;
302 Dlg_head *h = r->widget.parent;
304 switch (msg) {
305 case WIDGET_HOTKEY:
307 int i, lp = tolower (parm);
308 const char *cp;
310 for (i = 0; i < r->count; i++) {
311 cp = strchr (r->texts[i], '&');
312 if (cp != NULL && cp[1] != '\0') {
313 int c = tolower ((unsigned char) cp[1]);
315 if (c != lp)
316 continue;
317 r->pos = i;
319 /* Take action */
320 radio_callback (w, WIDGET_KEY, ' ');
321 return MSG_HANDLED;
325 return MSG_NOT_HANDLED;
327 case WIDGET_KEY:
328 switch (parm) {
329 case ' ':
330 r->sel = r->pos;
331 (*h->callback) (h, DLG_ACTION, 0);
332 radio_callback (w, WIDGET_FOCUS, ' ');
333 return MSG_HANDLED;
335 case KEY_UP:
336 case KEY_LEFT:
337 if (r->pos > 0) {
338 r->pos--;
339 return MSG_HANDLED;
341 return MSG_NOT_HANDLED;
343 case KEY_DOWN:
344 case KEY_RIGHT:
345 if (r->count - 1 > r->pos) {
346 r->pos++;
347 return MSG_HANDLED;
350 return MSG_NOT_HANDLED;
352 case WIDGET_CURSOR:
353 (*h->callback) (h, DLG_ACTION, 0);
354 radio_callback (w, WIDGET_FOCUS, ' ');
355 widget_move (&r->widget, r->pos, 1);
356 return MSG_HANDLED;
358 case WIDGET_UNFOCUS:
359 case WIDGET_FOCUS:
360 case WIDGET_DRAW:
361 for (i = 0; i < r->count; i++) {
362 register const char *cp;
363 const gboolean focused = (i == r->pos && msg == WIDGET_FOCUS);
364 widget_selectcolor (w, focused, FALSE);
365 widget_move (&r->widget, i, 0);
367 tty_printf ("(%c) ", (r->sel == i) ? '*' : ' ');
368 for (cp = r->texts[i]; *cp; cp++) {
369 if (*cp == '&') {
370 widget_selectcolor (w, focused, TRUE);
371 addch (*++cp);
372 widget_selectcolor (w, focused, FALSE);
373 } else
374 addch (*cp);
377 return MSG_HANDLED;
379 default:
380 return default_proc (msg, parm);
384 static int
385 radio_event (Gpm_Event *event, void *data)
387 WRadio *r = data;
388 Widget *w = data;
390 if (event->type & (GPM_DOWN|GPM_UP)){
391 Dlg_head *h = r->widget.parent;
393 r->pos = event->y - 1;
394 dlg_select_widget (r);
395 if (event->type & GPM_UP){
396 radio_callback (w, WIDGET_KEY, ' ');
397 radio_callback (w, WIDGET_FOCUS, 0);
398 (*h->callback) (h, DLG_POST_KEY, ' ');
399 return MOU_NORMAL;
402 return MOU_NORMAL;
405 WRadio *
406 radio_new (int y, int x, int count, const char **texts)
408 WRadio *r = g_new (WRadio, 1);
409 int i, max, m;
411 /* Compute the longest string */
412 max = 0;
413 for (i = 0; i < count; i++){
414 m = strlen (texts [i]);
415 if (m > max)
416 max = m;
419 init_widget (&r->widget, y, x, count, max, radio_callback, radio_event);
420 r->state = 1;
421 r->pos = 0;
422 r->sel = 0;
423 r->count = count;
424 r->texts = texts;
425 widget_want_hotkey (r->widget, 1);
427 return r;
431 /* Checkbutton widget */
433 static int check_event (Gpm_Event *event, void *);
435 static cb_ret_t
436 check_callback (Widget *w, widget_msg_t msg, int parm)
438 WCheck *c = (WCheck *) w;
439 Dlg_head *h = c->widget.parent;
441 switch (msg) {
442 case WIDGET_HOTKEY:
443 if (c->hotkey == parm
444 || (c->hotkey >= 'a' && c->hotkey <= 'z'
445 && c->hotkey - 32 == parm)) {
446 check_callback (w, WIDGET_KEY, ' '); /* make action */
447 return MSG_HANDLED;
449 return MSG_NOT_HANDLED;
451 case WIDGET_KEY:
452 if (parm != ' ')
453 return MSG_NOT_HANDLED;
454 c->state ^= C_BOOL;
455 c->state ^= C_CHANGE;
456 (*h->callback) (h, DLG_ACTION, 0);
457 check_callback (w, WIDGET_FOCUS, ' ');
458 return MSG_HANDLED;
460 case WIDGET_CURSOR:
461 widget_move (&c->widget, 0, 1);
462 return MSG_HANDLED;
464 case WIDGET_FOCUS:
465 case WIDGET_UNFOCUS:
466 case WIDGET_DRAW:
467 widget_selectcolor (w, msg == WIDGET_FOCUS, FALSE);
468 widget_move (&c->widget, 0, 0);
469 tty_printf ("[%c] %s", (c->state & C_BOOL) ? 'x' : ' ', c->text);
471 if (c->hotpos >= 0) {
472 widget_selectcolor (w, msg == WIDGET_FOCUS, TRUE);
473 widget_move (&c->widget, 0, +c->hotpos + 4);
474 addch ((unsigned char) c->text[c->hotpos]);
476 return MSG_HANDLED;
478 case WIDGET_DESTROY:
479 g_free (c->text);
480 return MSG_HANDLED;
482 default:
483 return default_proc (msg, parm);
487 static int
488 check_event (Gpm_Event *event, void *data)
490 WCheck *c = data;
491 Widget *w = data;
493 if (event->type & (GPM_DOWN|GPM_UP)){
494 Dlg_head *h = c->widget.parent;
496 dlg_select_widget (c);
497 if (event->type & GPM_UP){
498 check_callback (w, WIDGET_KEY, ' ');
499 check_callback (w, WIDGET_FOCUS, 0);
500 (*h->callback) (h, DLG_POST_KEY, ' ');
501 return MOU_NORMAL;
504 return MOU_NORMAL;
507 WCheck *
508 check_new (int y, int x, int state, const char *text)
510 WCheck *c = g_new (WCheck, 1);
511 const char *s;
512 char *t;
514 init_widget (&c->widget, y, x, 1, strlen (text),
515 check_callback, check_event);
516 c->state = state ? C_BOOL : 0;
517 c->text = g_strdup (text);
518 c->hotkey = 0;
519 c->hotpos = -1;
520 widget_want_hotkey (c->widget, 1);
522 /* Scan for the hotkey */
523 for (s = text, t = c->text; *s; s++, t++){
524 if (*s != '&'){
525 *t = *s;
526 continue;
528 s++;
529 if (*s){
530 c->hotkey = tolower ((unsigned char) *s);
531 c->hotpos = t - c->text;
533 *t = *s;
535 *t = 0;
536 return c;
540 /* Label widget */
542 static cb_ret_t
543 label_callback (Widget *w, widget_msg_t msg, int parm)
545 WLabel *l = (WLabel *) w;
546 Dlg_head *h = l->widget.parent;
548 switch (msg) {
549 case WIDGET_INIT:
550 return MSG_HANDLED;
552 /* We don't want to get the focus */
553 case WIDGET_FOCUS:
554 return MSG_NOT_HANDLED;
556 case WIDGET_DRAW:
558 char *p = l->text, *q, c = 0;
559 int y = 0;
561 if (!l->text)
562 return MSG_HANDLED;
564 if (l->transparent)
565 attrset (DEFAULT_COLOR);
566 else
567 attrset (DLG_NORMALC (h));
568 for (;;) {
569 int xlen;
571 q = strchr (p, '\n');
572 if (q) {
573 c = *q;
574 *q = 0;
576 widget_move (&l->widget, y, 0);
577 tty_printf ("%s", p);
578 xlen = l->widget.cols - strlen (p);
579 if (xlen > 0)
580 tty_printf ("%*s", xlen, " ");
581 if (!q)
582 break;
583 *q = c;
584 p = q + 1;
585 y++;
587 return MSG_HANDLED;
590 case WIDGET_DESTROY:
591 g_free (l->text);
592 return MSG_HANDLED;
594 default:
595 return default_proc (msg, parm);
599 void
600 label_set_text (WLabel *label, const char *text)
602 int newcols = label->widget.cols;
604 if (label->text && text && !strcmp (label->text, text))
605 return; /* Flickering is not nice */
607 g_free (label->text);
609 if (text){
610 label->text = g_strdup (text);
611 if (label->auto_adjust_cols) {
612 newcols = strlen (text);
613 if (newcols > label->widget.cols)
614 label->widget.cols = newcols;
616 } else
617 label->text = 0;
619 if (label->widget.parent)
620 label_callback ((Widget *) label, WIDGET_DRAW, 0);
622 if (newcols < label->widget.cols)
623 label->widget.cols = newcols;
626 WLabel *
627 label_new (int y, int x, const char *text)
629 WLabel *l;
630 int width;
632 /* Multiline labels are immutable - no need to compute their sizes */
633 if (!text || strchr(text, '\n'))
634 width = 1;
635 else
636 width = strlen (text);
638 l = g_new (WLabel, 1);
639 init_widget (&l->widget, y, x, 1, width, label_callback, NULL);
640 l->text = text ? g_strdup (text) : 0;
641 l->auto_adjust_cols = 1;
642 l->transparent = 0;
643 widget_want_cursor (l->widget, 0);
644 return l;
648 /* Gauge widget (progress indicator) */
649 /* Currently width is hardcoded here for text mode */
650 #define gauge_len 47
652 static cb_ret_t
653 gauge_callback (Widget *w, widget_msg_t msg, int parm)
655 WGauge *g = (WGauge *) w;
656 Dlg_head *h = g->widget.parent;
658 if (msg == WIDGET_INIT)
659 return MSG_HANDLED;
661 /* We don't want to get the focus */
662 if (msg == WIDGET_FOCUS)
663 return MSG_NOT_HANDLED;
665 if (msg == WIDGET_DRAW){
666 widget_move (&g->widget, 0, 0);
667 attrset (DLG_NORMALC (h));
668 if (!g->shown)
669 tty_printf ("%*s", gauge_len, "");
670 else {
671 int percentage, columns;
672 long total = g->max, done = g->current;
674 if (total <= 0 || done < 0) {
675 done = 0;
676 total = 100;
678 if (done > total)
679 done = total;
680 while (total > 65535) {
681 total /= 256;
682 done /= 256;
684 percentage = (200 * done / total + 1) / 2;
685 columns = (2 * (gauge_len - 7) * done / total + 1) / 2;
686 addch ('[');
687 attrset (GAUGE_COLOR);
688 tty_printf ("%*s", (int) columns, "");
689 attrset (DLG_NORMALC (h));
690 tty_printf ("%*s] %3d%%", (int)(gauge_len - 7 - columns), "", (int) percentage);
692 return MSG_HANDLED;
694 return default_proc (msg, parm);
697 void
698 gauge_set_value (WGauge *g, int max, int current)
700 if (g->current == current && g->max == max)
701 return; /* Do not flicker */
702 if (max == 0)
703 max = 1; /* I do not like division by zero :) */
705 g->current = current;
706 g->max = max;
707 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
710 void
711 gauge_show (WGauge *g, int shown)
713 if (g->shown == shown)
714 return;
715 g->shown = shown;
716 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
719 WGauge *
720 gauge_new (int y, int x, int shown, int max, int current)
722 WGauge *g = g_new (WGauge, 1);
724 init_widget (&g->widget, y, x, 1, gauge_len, gauge_callback, NULL);
725 g->shown = shown;
726 if (max == 0)
727 max = 1; /* I do not like division by zero :) */
728 g->max = max;
729 g->current = current;
730 widget_want_cursor (g->widget, 0);
731 return g;
735 /* Input widget */
737 /* {{{ history button */
739 #define LARGE_HISTORY_BUTTON 1
741 #ifdef LARGE_HISTORY_BUTTON
742 # define HISTORY_BUTTON_WIDTH 3
743 #else
744 # define HISTORY_BUTTON_WIDTH 1
745 #endif
747 #define should_show_history_button(in) \
748 (in->history && in->field_len > HISTORY_BUTTON_WIDTH * 2 + 1 && in->widget.parent)
750 static void draw_history_button (WInput * in)
752 char c;
753 c = in->history->next ? (in->history->prev ? '|' : 'v') : '^';
754 widget_move (&in->widget, 0, in->field_len - HISTORY_BUTTON_WIDTH);
755 #ifdef LARGE_HISTORY_BUTTON
757 Dlg_head *h;
758 h = in->widget.parent;
759 #if 0
760 attrset (NORMALC); /* button has the same color as other buttons */
761 addstr ("[ ]");
762 attrset (HOT_NORMALC);
763 #else
764 attrset (NORMAL_COLOR);
765 addstr ("[ ]");
766 /* Too distracting: attrset (MARKED_COLOR); */
767 #endif
768 widget_move (&in->widget, 0, in->field_len - HISTORY_BUTTON_WIDTH + 1);
769 addch (c);
771 #else
772 attrset (MARKED_COLOR);
773 addch (c);
774 #endif
777 /* }}} history button */
780 /* Input widgets now have a global kill ring */
781 /* Pointer to killed data */
782 static char *kill_buffer = 0;
784 void
785 update_input (WInput *in, int clear_first)
787 int has_history = 0;
788 int i, j;
789 unsigned char c;
790 int buf_len = strlen (in->buffer);
792 if (should_show_history_button (in))
793 has_history = HISTORY_BUTTON_WIDTH;
795 if (in->disable_update)
796 return;
798 /* Make the point visible */
799 if ((in->point < in->first_shown) ||
800 (in->point >= in->first_shown+in->field_len - has_history)){
801 in->first_shown = in->point - (in->field_len / 3);
802 if (in->first_shown < 0)
803 in->first_shown = 0;
806 /* Adjust the mark */
807 if (in->mark > buf_len)
808 in->mark = buf_len;
810 if (has_history)
811 draw_history_button (in);
813 attrset (in->color);
815 widget_move (&in->widget, 0, 0);
816 for (i = 0; i < in->field_len - has_history; i++)
817 addch (' ');
818 widget_move (&in->widget, 0, 0);
820 for (i = 0, j = in->first_shown; i < in->field_len - has_history && in->buffer [j]; i++){
821 c = in->buffer [j++];
822 c = is_printable (c) ? c : '.';
823 if (in->is_password)
824 c = '*';
825 addch (c);
827 widget_move (&in->widget, 0, in->point - in->first_shown);
829 if (clear_first)
830 in->first = 0;
833 void
834 winput_set_origin (WInput *in, int x, int field_len)
836 in->widget.x = x;
837 in->field_len = in->widget.cols = field_len;
838 update_input (in, 0);
841 /* {{{ history saving and loading */
843 int num_history_items_recorded = 60;
846 This loads and saves the history of an input line to and from the
847 widget. It is called with the widgets history name on creation of the
848 widget, and returns the GList list. It stores histories in the file
849 ~/.mc/history in using the profile code.
851 If def_text is passed as INPUT_LAST_TEXT (to the input_new()
852 function) then input_new assigns the default text to be the last text
853 entered, or "" if not found.
856 GList *
857 history_get (const char *input_name)
859 int i;
860 GList *hist;
861 char *profile;
863 hist = NULL;
865 if (!num_history_items_recorded) /* this is how to disable */
866 return NULL;
867 if (!input_name)
868 return NULL;
869 if (!*input_name)
870 return NULL;
871 profile = mhl_str_dir_plus_file (home_dir, HISTORY_FILE_NAME);
872 for (i = 0;; i++) {
873 char key_name[BUF_TINY];
874 char this_entry[BUF_LARGE];
875 g_snprintf (key_name, sizeof (key_name), "%d", i);
876 GetPrivateProfileString (input_name, key_name, "", this_entry,
877 sizeof (this_entry), profile);
878 if (!*this_entry)
879 break;
881 hist = list_append_unique (hist, g_strdup (this_entry));
883 g_free (profile);
885 /* return pointer to the last entry in the list */
886 hist = g_list_last (hist);
888 return hist;
891 void
892 history_put (const char *input_name, GList *h)
894 int i;
895 char *profile;
897 if (!input_name)
898 return;
900 if (!*input_name)
901 return;
903 if (!h)
904 return;
906 if (!num_history_items_recorded) /* this is how to disable */
907 return;
909 profile = mhl_str_dir_plus_file (home_dir, HISTORY_FILE_NAME);
911 if ((i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) != -1)
912 close (i);
914 /* Make sure the history is only readable by the user */
915 if (chmod (profile, S_IRUSR | S_IWUSR) == -1 && errno != ENOENT) {
916 g_free (profile);
917 return;
920 /* go to end of list */
921 h = g_list_last (h);
923 /* go back 60 places */
924 for (i = 0; i < num_history_items_recorded - 1 && h->prev; i++)
925 h = g_list_previous (h);
927 if (input_name)
928 profile_clean_section (input_name, profile);
930 /* dump histories into profile */
931 for (i = 0; h; h = g_list_next (h)) {
932 char *text;
934 text = (char *) h->data;
936 /* We shouldn't have null entries, but let's be sure */
937 if (text && *text) {
938 char key_name[BUF_TINY];
939 g_snprintf (key_name, sizeof (key_name), "%d", i++);
940 WritePrivateProfileString (input_name, key_name, text,
941 profile);
945 g_free (profile);
948 /* }}} history saving and loading */
951 /* {{{ history display */
953 static const char *
954 i18n_htitle (void)
956 static const char *history_title = NULL;
958 if (history_title == NULL)
959 history_title = _(" History ");
960 return history_title;
963 static WLEntry *listbox_select_pos (WListbox *l, WLEntry *base, int
964 pos);
966 static inline cb_ret_t
967 listbox_fwd (WListbox *l)
969 if (l->current != l->list->prev){
970 listbox_select_entry (l, listbox_select_pos (l, l->list, l->pos+1));
971 return MSG_HANDLED;
973 return MSG_NOT_HANDLED;
976 char *
977 show_hist (GList *history, int widget_x, int widget_y)
979 GList *hi, *z;
980 size_t maxlen = strlen (i18n_htitle ()), i, count = 0;
981 int x, y, w, h;
982 char *q, *r = 0;
983 Dlg_head *query_dlg;
984 WListbox *query_list;
986 z = history;
987 if (!z)
988 return NULL;
990 z = g_list_first (history);
991 hi = z;
992 while (hi) {
993 if ((i = strlen ((char *) hi->data)) > maxlen)
994 maxlen = i;
995 count++;
996 hi = g_list_next (hi);
999 y = widget_y;
1000 h = count + 2;
1001 if (h <= y || y > LINES - 6) {
1002 h = min (h, y - 1);
1003 y -= h;
1004 } else {
1005 y++;
1006 h = min (h, LINES - y);
1009 if (widget_x > 2)
1010 x = widget_x - 2;
1011 else
1012 x = 0;
1013 if ((w = maxlen + 4) + x > COLS) {
1014 w = min (w, COLS);
1015 x = COLS - w;
1018 query_dlg =
1019 create_dlg (y, x, h, w, dialog_colors, NULL, "[History-query]",
1020 i18n_htitle (), DLG_COMPACT);
1021 query_list = listbox_new (1, 1, w - 2, h - 2, 0);
1022 add_widget (query_dlg, query_list);
1023 hi = z;
1024 if (y < widget_y) {
1025 /* traverse */
1026 while (hi) {
1027 listbox_add_item (query_list, 0, 0, (char *) hi->data, NULL);
1028 hi = g_list_next (hi);
1030 while (listbox_fwd (query_list));
1031 } else {
1032 /* traverse backwards */
1033 hi = g_list_last (history);
1034 while (hi) {
1035 listbox_add_item (query_list, 0, 0, (char *) hi->data, NULL);
1036 hi = g_list_previous (hi);
1039 run_dlg (query_dlg);
1040 q = NULL;
1041 if (query_dlg->ret_value != B_CANCEL) {
1042 listbox_get_current (query_list, &q, NULL);
1043 if (q)
1044 r = g_strdup (q);
1046 destroy_dlg (query_dlg);
1047 return r;
1050 static void do_show_hist (WInput * in)
1052 char *r;
1053 r = show_hist (in->history, in->widget.x, in->widget.y);
1054 if (r) {
1055 assign_text (in, r);
1056 g_free (r);
1060 /* }}} history display */
1062 static void
1063 input_destroy (WInput *in)
1065 if (!in){
1066 fprintf (stderr, "Internal error: null Input *\n");
1067 exit (1);
1070 new_input (in);
1072 if (in->history){
1073 if (!in->is_password) /* don't save passwords ;-) */
1074 history_put (in->history_name, in->history);
1076 in->history = g_list_first (in->history);
1077 g_list_foreach (in->history, (GFunc) g_free, NULL);
1078 g_list_free (in->history);
1081 g_free (in->buffer);
1082 free_completions (in);
1083 g_free (in->history_name);
1086 void
1087 input_disable_update (WInput *in)
1089 in->disable_update++;
1092 void
1093 input_enable_update (WInput *in)
1095 in->disable_update--;
1096 update_input (in, 0);
1099 #define ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
1102 push_history (WInput *in, const char *text)
1104 static int i18n;
1105 /* input widget where urls with passwords are entered without any
1106 vfs prefix */
1107 static const char *password_input_fields[] = {
1108 N_(" Link to a remote machine "),
1109 N_(" FTP to machine "),
1110 N_(" SMB link to machine ")
1112 char *t;
1113 const char *p;
1114 size_t i;
1116 if (!i18n) {
1117 i18n = 1;
1118 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1119 password_input_fields[i] = _(password_input_fields[i]);
1122 for (p = text; *p == ' ' || *p == '\t'; p++);
1123 if (!*p)
1124 return 0;
1126 if (in->history) {
1127 /* Avoid duplicated entries */
1128 in->history = g_list_last (in->history);
1129 if (!strcmp ((char *) in->history->data, text))
1130 return 1;
1133 t = g_strdup (text);
1135 if (in->history_name) {
1136 p = in->history_name + 3;
1137 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1138 if (strcmp (p, password_input_fields[i]) == 0)
1139 break;
1140 if (i < ELEMENTS (password_input_fields))
1141 strip_password (t, 0);
1142 else
1143 strip_password (t, 1);
1146 in->history = list_append_unique (in->history, t);
1147 in->need_push = 0;
1149 return 2;
1152 #undef ELEMENTS
1154 /* Cleans the input line and adds the current text to the history */
1155 void
1156 new_input (WInput *in)
1158 if (in->buffer)
1159 push_history (in, in->buffer);
1160 in->need_push = 1;
1161 in->buffer [0] = 0;
1162 in->point = 0;
1163 in->mark = 0;
1164 free_completions (in);
1165 update_input (in, 0);
1168 static cb_ret_t
1169 insert_char (WInput *in, int c_code)
1171 size_t i;
1173 if (c_code == -1)
1174 return MSG_NOT_HANDLED;
1176 in->need_push = 1;
1177 if (strlen (in->buffer)+1 == (size_t) in->current_max_len){
1178 /* Expand the buffer */
1179 char *narea = g_realloc (in->buffer, in->current_max_len + in->field_len);
1180 if (narea){
1181 in->buffer = narea;
1182 in->current_max_len += in->field_len;
1185 if (strlen (in->buffer)+1 < (size_t) in->current_max_len){
1186 size_t l = strlen (&in->buffer [in->point]);
1187 for (i = l+1; i > 0; i--)
1188 in->buffer [in->point+i] = in->buffer [in->point+i-1];
1189 in->buffer [in->point] = c_code;
1190 in->point++;
1192 return MSG_HANDLED;
1195 static void
1196 beginning_of_line (WInput *in)
1198 in->point = 0;
1201 static void
1202 end_of_line (WInput *in)
1204 in->point = strlen (in->buffer);
1207 static void
1208 backward_char (WInput *in)
1210 if (in->point)
1211 in->point--;
1214 static void
1215 forward_char (WInput *in)
1217 if (in->buffer [in->point])
1218 in->point++;
1221 static void
1222 forward_word (WInput * in)
1224 char *p = in->buffer + in->point;
1226 while (*p
1227 && (isspace ((unsigned char) *p)
1228 || ispunct ((unsigned char) *p)))
1229 p++;
1230 while (*p && isalnum ((unsigned char) *p))
1231 p++;
1232 in->point = p - in->buffer;
1235 static void
1236 backward_word (WInput *in)
1238 char *p = in->buffer + in->point;
1240 while (p - 1 > in->buffer - 1 && (isspace ((unsigned char) *(p - 1))
1241 || ispunct ((unsigned char)
1242 *(p - 1))))
1243 p--;
1244 while (p - 1 > in->buffer - 1 && isalnum ((unsigned char) *(p - 1)))
1245 p--;
1246 in->point = p - in->buffer;
1249 static void
1250 key_left (WInput *in)
1252 backward_char (in);
1255 static void
1256 key_ctrl_left (WInput *in)
1258 backward_word (in);
1261 static void
1262 key_right (WInput *in)
1264 forward_char (in);
1267 static void
1268 key_ctrl_right (WInput *in)
1270 forward_word (in);
1272 static void
1273 backward_delete (WInput *in)
1275 int i;
1277 if (!in->point)
1278 return;
1279 for (i = in->point; in->buffer [i-1]; i++)
1280 in->buffer [i-1] = in->buffer [i];
1281 in->need_push = 1;
1282 in->point--;
1285 static void
1286 delete_char (WInput *in)
1288 int i;
1290 for (i = in->point; in->buffer [i]; i++)
1291 in->buffer [i] = in->buffer [i+1];
1292 in->need_push = 1;
1295 static void
1296 copy_region (WInput *in, int x_first, int x_last)
1298 int first = min (x_first, x_last);
1299 int last = max (x_first, x_last);
1301 if (last == first)
1302 return;
1304 g_free (kill_buffer);
1306 kill_buffer = g_strndup(in->buffer+first,last-first);
1309 static void
1310 delete_region (WInput *in, int x_first, int x_last)
1312 int first = min (x_first, x_last);
1313 int last = max (x_first, x_last);
1314 size_t len = strlen (&in->buffer [last]) + 1;
1316 in->point = first;
1317 in->mark = first;
1318 memmove (&in->buffer [first], &in->buffer [last], len);
1319 in->need_push = 1;
1322 static void
1323 kill_word (WInput *in)
1325 int old_point = in->point;
1326 int new_point;
1328 forward_word (in);
1329 new_point = in->point;
1330 in->point = old_point;
1332 copy_region (in, old_point, new_point);
1333 delete_region (in, old_point, new_point);
1334 in->need_push = 1;
1337 static void
1338 back_kill_word (WInput *in)
1340 int old_point = in->point;
1341 int new_point;
1343 backward_word (in);
1344 new_point = in->point;
1345 in->point = old_point;
1347 copy_region (in, old_point, new_point);
1348 delete_region (in, old_point, new_point);
1349 in->need_push = 1;
1352 static void
1353 set_mark (WInput *in)
1355 in->mark = in->point;
1358 static void
1359 kill_save (WInput *in)
1361 copy_region (in, in->mark, in->point);
1364 static void
1365 kill_region (WInput *in)
1367 kill_save (in);
1368 delete_region (in, in->point, in->mark);
1371 static void
1372 yank (WInput *in)
1374 char *p;
1376 if (!kill_buffer)
1377 return;
1378 for (p = kill_buffer; *p; p++)
1379 insert_char (in, *p);
1382 static void
1383 kill_line (WInput *in)
1385 g_free (kill_buffer);
1386 kill_buffer = g_strdup (&in->buffer [in->point]);
1387 in->buffer [in->point] = 0;
1390 void
1391 assign_text (WInput *in, const char *text)
1393 free_completions (in);
1394 g_free (in->buffer);
1395 in->buffer = g_strdup (text); /* was in->buffer->text */
1396 in->current_max_len = strlen (in->buffer) + 1;
1397 in->point = strlen (in->buffer);
1398 in->mark = 0;
1399 in->need_push = 1;
1402 static void
1403 hist_prev (WInput *in)
1405 if (!in->history)
1406 return;
1408 if (in->need_push) {
1409 switch (push_history (in, in->buffer)) {
1410 case 2:
1411 in->history = g_list_previous (in->history);
1412 break;
1413 case 1:
1414 if (in->history->prev)
1415 in->history = g_list_previous (in->history);
1416 break;
1417 case 0:
1418 break;
1420 } else if (in->history->prev)
1421 in->history = g_list_previous (in->history);
1422 else
1423 return;
1424 assign_text (in, (char *) in->history->data);
1425 in->need_push = 0;
1428 static void
1429 hist_next (WInput *in)
1431 if (in->need_push) {
1432 switch (push_history (in, in->buffer)) {
1433 case 2:
1434 assign_text (in, "");
1435 return;
1436 case 0:
1437 return;
1441 if (!in->history)
1442 return;
1444 if (!in->history->next) {
1445 assign_text (in, "");
1446 return;
1449 in->history = g_list_next (in->history);
1450 assign_text (in, (char *) in->history->data);
1451 in->need_push = 0;
1454 static const struct {
1455 int key_code;
1456 void (*fn)(WInput *in);
1457 } input_map [] = {
1458 /* Motion */
1459 { XCTRL('a'), beginning_of_line },
1460 { KEY_HOME, beginning_of_line },
1461 { KEY_A1, beginning_of_line },
1462 { ALT ('<'), beginning_of_line },
1463 { XCTRL('e'), end_of_line },
1464 { KEY_END, end_of_line },
1465 { KEY_C1, end_of_line },
1466 { ALT ('>'), end_of_line },
1467 { KEY_LEFT, key_left },
1468 { KEY_LEFT | KEY_M_CTRL, key_ctrl_left },
1469 { XCTRL('b'), backward_char },
1470 { ALT('b'), backward_word },
1471 { KEY_RIGHT, key_right },
1472 { KEY_RIGHT | KEY_M_CTRL, key_ctrl_right },
1473 { XCTRL('f'), forward_char },
1474 { ALT('f'), forward_word },
1476 /* Editing */
1477 { KEY_BACKSPACE, backward_delete },
1478 { KEY_DC, delete_char },
1479 { ALT('d'), kill_word },
1480 { ALT(KEY_BACKSPACE), back_kill_word },
1482 /* Region manipulation */
1483 { 0, set_mark },
1484 { XCTRL('w'), kill_region },
1485 { ALT('w'), kill_save },
1486 { XCTRL('y'), yank },
1487 { XCTRL('k'), kill_line },
1489 /* History */
1490 { ALT('p'), hist_prev },
1491 { ALT('n'), hist_next },
1492 { ALT('h'), do_show_hist },
1494 /* Completion */
1495 { ALT('\t'), complete },
1497 { 0, 0 }
1500 /* This function is a test for a special input key used in complete.c */
1501 /* Returns 0 if it is not a special key, 1 if it is a non-complete key
1502 and 2 if it is a complete key */
1504 is_in_input_map (WInput *in, int c_code)
1506 int i;
1508 (void) in;
1510 for (i = 0; input_map [i].fn; i++)
1511 if (c_code == input_map [i].key_code) {
1512 if (input_map [i].fn == complete)
1513 return 2;
1514 else
1515 return 1;
1517 return 0;
1520 static void
1521 port_region_marked_for_delete (WInput *in)
1523 *in->buffer = 0;
1524 in->point = 0;
1525 in->first = 0;
1528 cb_ret_t
1529 handle_char (WInput *in, int c_code)
1531 cb_ret_t v;
1532 int i;
1534 v = MSG_NOT_HANDLED;
1536 if (quote){
1537 free_completions (in);
1538 v = insert_char (in, c_code);
1539 update_input (in, 1);
1540 quote = 0;
1541 return v;
1544 for (i = 0; input_map [i].fn; i++){
1545 if (c_code == input_map [i].key_code){
1546 if (input_map [i].fn != complete)
1547 free_completions (in);
1548 (*input_map [i].fn)(in);
1549 v = MSG_HANDLED;
1550 break;
1553 if (!input_map [i].fn){
1554 if (c_code > 255 || !is_printable (c_code))
1555 return MSG_NOT_HANDLED;
1556 if (in->first){
1557 port_region_marked_for_delete (in);
1559 free_completions (in);
1560 v = insert_char (in, c_code);
1562 update_input (in, 1);
1563 return v;
1566 /* Inserts text in input line */
1567 void
1568 stuff (WInput *in, const char *text, int insert_extra_space)
1570 input_disable_update (in);
1571 while (*text)
1572 handle_char (in, *text++);
1573 if (insert_extra_space)
1574 handle_char (in, ' ');
1575 input_enable_update (in);
1576 update_input (in, 1);
1579 void
1580 input_set_point (WInput *in, int pos)
1582 if (pos > in->current_max_len)
1583 pos = in->current_max_len;
1584 if (pos != in->point)
1585 free_completions (in);
1586 in->point = pos;
1587 update_input (in, 1);
1590 cb_ret_t
1591 input_callback (Widget *w, widget_msg_t msg, int parm)
1593 WInput *in = (WInput *) w;
1594 cb_ret_t v;
1596 switch (msg) {
1597 case WIDGET_KEY:
1598 if (parm == XCTRL ('q')) {
1599 quote = 1;
1600 v = handle_char (in, ascii_alpha_to_cntrl (mi_getch ()));
1601 quote = 0;
1602 return v;
1605 /* Keys we want others to handle */
1606 if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1607 || parm == KEY_F (10) || parm == XCTRL ('g') || parm == '\n')
1608 return MSG_NOT_HANDLED;
1610 /* When pasting multiline text, insert literal Enter */
1611 if ((parm & ~KEY_M_MASK) == '\n') {
1612 quote = 1;
1613 v = handle_char (in, '\n');
1614 quote = 0;
1615 return v;
1618 return handle_char (in, parm);
1620 case WIDGET_FOCUS:
1621 case WIDGET_UNFOCUS:
1622 case WIDGET_DRAW:
1623 update_input (in, 0);
1624 return MSG_HANDLED;
1626 case WIDGET_CURSOR:
1627 widget_move (&in->widget, 0, in->point - in->first_shown);
1628 return MSG_HANDLED;
1630 case WIDGET_DESTROY:
1631 input_destroy (in);
1632 return MSG_HANDLED;
1634 default:
1635 return default_proc (msg, parm);
1639 static int
1640 input_event (Gpm_Event * event, void *data)
1642 WInput *in = data;
1644 if (event->type & (GPM_DOWN | GPM_DRAG)) {
1645 dlg_select_widget (in);
1647 if (event->x >= in->field_len - HISTORY_BUTTON_WIDTH + 1
1648 && should_show_history_button (in)) {
1649 do_show_hist (in);
1650 } else {
1651 in->point = strlen (in->buffer);
1652 if (event->x - in->first_shown - 1 < in->point)
1653 in->point = event->x - in->first_shown - 1;
1654 if (in->point < 0)
1655 in->point = 0;
1657 update_input (in, 1);
1659 return MOU_NORMAL;
1662 WInput *
1663 input_new (int y, int x, int color, int len, const char *def_text,
1664 const char *histname, INPUT_COMPLETE_FLAGS completion_flags)
1666 WInput *in = g_new (WInput, 1);
1667 int initial_buffer_len;
1669 init_widget (&in->widget, y, x, 1, len, input_callback, input_event);
1671 /* history setup */
1672 in->history = NULL;
1673 in->history_name = 0;
1674 if (histname) {
1675 if (*histname) {
1676 in->history_name = g_strdup (histname);
1677 in->history = history_get (histname);
1681 if (!def_text)
1682 def_text = "";
1684 if (def_text == INPUT_LAST_TEXT) {
1685 def_text = "";
1686 if (in->history)
1687 if (in->history->data)
1688 def_text = (char *) in->history->data;
1690 initial_buffer_len = 1 + max ((size_t) len, strlen (def_text));
1691 in->widget.options |= W_IS_INPUT;
1692 in->completions = NULL;
1693 in->completion_flags = completion_flags;
1694 in->current_max_len = initial_buffer_len;
1695 in->buffer = g_malloc (initial_buffer_len);
1696 in->color = color;
1697 in->field_len = len;
1698 in->first = 1;
1699 in->first_shown = 0;
1700 in->disable_update = 0;
1701 in->mark = 0;
1702 in->need_push = 1;
1703 in->is_password = 0;
1705 strcpy (in->buffer, def_text);
1706 in->point = strlen (in->buffer);
1707 return in;
1711 /* Listbox widget */
1713 /* Should draw the scrollbar, but currently draws only
1714 * indications that there is more information
1716 static int listbox_cdiff (WLEntry *s, WLEntry *e);
1718 static void
1719 listbox_drawscroll (WListbox *l)
1721 int line;
1722 int i, top;
1723 int max_line = l->height-1;
1725 /* Are we at the top? */
1726 widget_move (&l->widget, 0, l->width);
1727 if (l->list == l->top)
1728 one_vline ();
1729 else
1730 addch ('^');
1732 /* Are we at the bottom? */
1733 widget_move (&l->widget, max_line, l->width);
1734 top = listbox_cdiff (l->list, l->top);
1735 if ((top + l->height == l->count) || l->height >= l->count)
1736 one_vline ();
1737 else
1738 addch ('v');
1740 /* Now draw the nice relative pointer */
1741 if (l->count)
1742 line = 1+ ((l->pos * (l->height-2)) / l->count);
1743 else
1744 line = 0;
1746 for (i = 1; i < max_line; i++){
1747 widget_move (&l->widget, i, l->width);
1748 if (i != line)
1749 one_vline ();
1750 else
1751 addch ('*');
1755 static void
1756 listbox_draw (WListbox *l, int focused)
1758 WLEntry *e;
1759 int i;
1760 int sel_line;
1761 Dlg_head *h = l->widget.parent;
1762 int normalc = DLG_NORMALC (h);
1763 int selc;
1764 const char *text;
1766 if (focused){
1767 selc = DLG_FOCUSC (h);
1768 } else {
1769 selc = DLG_HOT_FOCUSC (h);
1771 sel_line = -1;
1773 for (e = l->top, i = 0; (i < l->height); i++){
1775 /* Display the entry */
1776 if (e == l->current && sel_line == -1){
1777 sel_line = i;
1778 attrset (selc);
1779 } else
1780 attrset (normalc);
1782 widget_move (&l->widget, i, 0);
1784 if ((i > 0 && e == l->list) || !l->list)
1785 text = "";
1786 else {
1787 text = e->text;
1788 e = e->next;
1790 tty_printf (" %-*s ", l->width-2, name_trunc (text, l->width-2));
1792 l->cursor_y = sel_line;
1793 if (!l->scrollbar)
1794 return;
1795 attrset (normalc);
1796 listbox_drawscroll (l);
1799 /* Returns the number of items between s and e,
1800 must be on the same linked list */
1801 static int
1802 listbox_cdiff (WLEntry *s, WLEntry *e)
1804 int count;
1806 for (count = 0; s != e; count++)
1807 s = s->next;
1808 return count;
1811 static WLEntry *
1812 listbox_check_hotkey (WListbox *l, int key)
1814 int i;
1815 WLEntry *e;
1817 i = 0;
1818 e = l->list;
1819 if (!e)
1820 return NULL;
1822 while (1) {
1824 /* If we didn't find anything, return */
1825 if (i && e == l->list)
1826 return NULL;
1828 if (e->hotkey == key)
1829 return e;
1831 i++;
1832 e = e->next;
1836 /* Used only for display updating, for avoiding line at a time scroll */
1837 void
1838 listbox_select_last (WListbox *l, int set_top)
1840 if (l->list){
1841 l->current = l->list->prev;
1842 l->pos = l->count - 1;
1843 if (set_top)
1844 l->top = l->list->prev;
1848 void
1849 listbox_remove_list (WListbox *l)
1851 WLEntry *p, *q;
1853 if (!l->count)
1854 return;
1856 p = l->list;
1858 while (l->count--) {
1859 q = p->next;
1860 g_free (p->text);
1861 g_free (p);
1862 p = q;
1864 l->pos = l->count = 0;
1865 l->list = l->top = l->current = 0;
1869 * bor 30.10.96: added force flag to remove *last* entry as well
1870 * bor 30.10.96: corrected selection bug if last entry was removed
1873 void
1874 listbox_remove_current (WListbox *l, int force)
1876 WLEntry *p;
1878 /* Ok, note: this won't allow for emtpy lists */
1879 if (!force && (!l->count || l->count == 1))
1880 return;
1882 l->count--;
1883 p = l->current;
1885 if (l->count) {
1886 l->current->next->prev = l->current->prev;
1887 l->current->prev->next = l->current->next;
1888 if (p->next == l->list) {
1889 l->current = p->prev;
1890 l->pos--;
1892 else
1893 l->current = p->next;
1895 if (p == l->list)
1896 l->list = l->top = p->next;
1897 } else {
1898 l->pos = 0;
1899 l->list = l->top = l->current = 0;
1902 g_free (p->text);
1903 g_free (p);
1906 /* Makes *e the selected entry (sets current and pos) */
1907 void
1908 listbox_select_entry (WListbox *l, WLEntry *dest)
1910 WLEntry *e;
1911 int pos;
1912 int top_seen;
1914 top_seen = 0;
1916 /* Special case */
1917 for (pos = 0, e = l->list; pos < l->count; e = e->next, pos++){
1919 if (e == l->top)
1920 top_seen = 1;
1922 if (e == dest){
1923 l->current = e;
1924 if (top_seen){
1925 while (listbox_cdiff (l->top, l->current) >= l->height)
1926 l->top = l->top->next;
1927 } else {
1928 l->top = l->current;
1930 l->pos = pos;
1931 return;
1934 /* If we are unable to find it, set decent values */
1935 l->current = l->top = l->list;
1936 l->pos = 0;
1939 /* Selects from base the pos element */
1940 static WLEntry *
1941 listbox_select_pos (WListbox *l, WLEntry *base, int pos)
1943 WLEntry *last = l->list->prev;
1945 if (base == last)
1946 return last;
1947 while (pos--){
1948 base = base->next;
1949 if (base == last)
1950 break;
1952 return base;
1955 static inline cb_ret_t
1956 listbox_back (WListbox *l)
1958 if (l->pos){
1959 listbox_select_entry (l, listbox_select_pos (l, l->list, l->pos-1));
1960 return MSG_HANDLED;
1962 return MSG_NOT_HANDLED;
1965 /* Return MSG_HANDLED if we want a redraw */
1966 static cb_ret_t
1967 listbox_key (WListbox *l, int key)
1969 int i;
1970 int j = 0;
1972 if (!l->list)
1973 return MSG_NOT_HANDLED;
1975 switch (key){
1976 case KEY_HOME:
1977 case KEY_A1:
1978 case ALT ('<'):
1979 l->current = l->top = l->list;
1980 l->pos = 0;
1981 return MSG_HANDLED;
1983 case KEY_END:
1984 case KEY_C1:
1985 case ALT ('>'):
1986 l->current = l->top = l->list->prev;
1987 for (i = min (l->height - 1, l->count - 1); i; i--)
1988 l->top = l->top->prev;
1989 l->pos = l->count - 1;
1990 return MSG_HANDLED;
1992 case XCTRL('p'):
1993 case KEY_UP:
1994 listbox_back (l);
1995 return MSG_HANDLED;
1997 case XCTRL('n'):
1998 case KEY_DOWN:
1999 listbox_fwd (l);
2000 return MSG_HANDLED;
2002 case KEY_NPAGE:
2003 case XCTRL('v'):
2004 for (i = 0; i < l->height-1; i++)
2005 j |= listbox_fwd (l);
2006 return (j > 0) ? MSG_HANDLED : MSG_NOT_HANDLED;
2008 case KEY_PPAGE:
2009 case ALT('v'):
2010 for (i = 0; i < l->height-1; i++)
2011 j |= listbox_back (l);
2012 return (j > 0) ? MSG_HANDLED : MSG_NOT_HANDLED;
2014 return MSG_NOT_HANDLED;
2017 static void
2018 listbox_destroy (WListbox *l)
2020 WLEntry *n, *p = l->list;
2021 int i;
2023 for (i = 0; i < l->count; i++){
2024 n = p->next;
2025 g_free (p->text);
2026 g_free (p);
2027 p = n;
2031 static cb_ret_t
2032 listbox_callback (Widget *w, widget_msg_t msg, int parm)
2034 WListbox *l = (WListbox *) w;
2035 cb_ret_t ret_code;
2036 WLEntry *e;
2037 Dlg_head *h = l->widget.parent;
2039 switch (msg) {
2040 case WIDGET_INIT:
2041 return MSG_HANDLED;
2043 case WIDGET_HOTKEY:
2044 if ((e = listbox_check_hotkey (l, parm)) != NULL) {
2045 int action;
2047 listbox_select_entry (l, e);
2049 if (l->cback)
2050 action = (*l->cback) (l);
2051 else
2052 action = LISTBOX_DONE;
2054 if (action == LISTBOX_DONE) {
2055 h->ret_value = B_ENTER;
2056 dlg_stop (h);
2058 return MSG_HANDLED;
2059 } else
2060 return MSG_NOT_HANDLED;
2062 case WIDGET_KEY:
2063 if ((ret_code = listbox_key (l, parm)))
2064 listbox_draw (l, 1);
2065 return ret_code;
2067 case WIDGET_CURSOR:
2068 widget_move (&l->widget, l->cursor_y, 0);
2069 return MSG_HANDLED;
2071 case WIDGET_FOCUS:
2072 case WIDGET_UNFOCUS:
2073 case WIDGET_DRAW:
2074 listbox_draw (l, msg != WIDGET_UNFOCUS);
2075 return MSG_HANDLED;
2077 case WIDGET_DESTROY:
2078 listbox_destroy (l);
2079 return MSG_HANDLED;
2081 default:
2082 return default_proc (msg, parm);
2086 static int
2087 listbox_event (Gpm_Event *event, void *data)
2089 WListbox *l = data;
2090 Widget *w = data;
2091 int i;
2093 Dlg_head *h = l->widget.parent;
2095 /* Single click */
2096 if (event->type & GPM_DOWN)
2097 dlg_select_widget (l);
2098 if (!l->list)
2099 return MOU_NORMAL;
2100 if (event->type & (GPM_DOWN | GPM_DRAG)) {
2101 if (event->x < 0 || event->x >= l->width)
2102 return MOU_REPEAT;
2103 if (event->y < 1)
2104 for (i = -event->y; i >= 0; i--)
2105 listbox_back (l);
2106 else if (event->y > l->height)
2107 for (i = event->y - l->height; i > 0; i--)
2108 listbox_fwd (l);
2109 else
2110 listbox_select_entry (l,
2111 listbox_select_pos (l, l->top,
2112 event->y - 1));
2114 /* We need to refresh ourselves since the dialog manager doesn't */
2115 /* know about this event */
2116 listbox_callback (w, WIDGET_DRAW, 0);
2117 mc_refresh ();
2118 return MOU_REPEAT;
2121 /* Double click */
2122 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE)) {
2123 int action;
2125 if (event->x < 0 || event->x >= l->width)
2126 return MOU_NORMAL;
2127 if (event->y < 1 || event->y > l->height)
2128 return MOU_NORMAL;
2130 dlg_select_widget (l);
2131 listbox_select_entry (l,
2132 listbox_select_pos (l, l->top,
2133 event->y - 1));
2135 if (l->cback)
2136 action = (*l->cback) (l);
2137 else
2138 action = LISTBOX_DONE;
2140 if (action == LISTBOX_DONE) {
2141 h->ret_value = B_ENTER;
2142 dlg_stop (h);
2143 return MOU_NORMAL;
2146 return MOU_NORMAL;
2149 WListbox *
2150 listbox_new (int y, int x, int width, int height, lcback callback)
2152 WListbox *l = g_new (WListbox, 1);
2154 init_widget (&l->widget, y, x, height, width,
2155 listbox_callback, listbox_event);
2157 l->list = l->top = l->current = 0;
2158 l->pos = 0;
2159 l->width = width;
2160 if (height <= 0)
2161 l->height = 1;
2162 else
2163 l->height = height;
2164 l->count = 0;
2165 l->cback = callback;
2166 l->allow_duplicates = 1;
2167 l->scrollbar = slow_terminal ? 0 : 1;
2168 widget_want_hotkey (l->widget, 1);
2170 return l;
2173 /* Listbox item adding function. They still lack a lot of functionality */
2174 /* any takers? */
2175 /* 1.11.96 bor: added pos argument to control placement of new entry */
2176 static void
2177 listbox_append_item (WListbox *l, WLEntry *e, enum append_pos pos)
2179 if (!l->list){
2180 l->list = e;
2181 l->top = e;
2182 l->current = e;
2183 e->next = l->list;
2184 e->prev = l->list;
2185 } else if (pos == LISTBOX_APPEND_AT_END) {
2186 e->next = l->list;
2187 e->prev = l->list->prev;
2188 l->list->prev->next = e;
2189 l->list->prev = e;
2190 } else if (pos == LISTBOX_APPEND_BEFORE){
2191 e->next = l->current;
2192 e->prev = l->current->prev;
2193 l->current->prev->next = e;
2194 l->current->prev = e;
2195 if (l->list == l->current) { /* move list one position down */
2196 l->list = e;
2197 l->top = e;
2199 } else if (pos == LISTBOX_APPEND_AFTER) {
2200 e->prev = l->current;
2201 e->next = l->current->next;
2202 l->current->next->prev = e;
2203 l->current->next = e;
2204 } else if (pos == LISTBOX_APPEND_SORTED) {
2205 WLEntry *w = l->list;
2207 while (w->next != l->list && strcmp (e->text, w->text) > 0)
2208 w = w->next;
2209 if (w->next == l->list) {
2210 e->prev = w;
2211 e->next = l->list;
2212 w->next = e;
2213 l->list->prev = e;
2214 } else {
2215 e->next = w;
2216 e->prev = w->prev;
2217 w->prev->next = e;
2218 w->prev = e;
2221 l->count++;
2224 char *
2225 listbox_add_item (WListbox *l, enum append_pos pos, int hotkey,
2226 const char *text, void *data)
2228 WLEntry *entry;
2230 if (!l)
2231 return NULL;
2233 if (!l->allow_duplicates)
2234 if (listbox_search_text (l, text))
2235 return NULL;
2237 entry = g_new (WLEntry, 1);
2238 entry->text = g_strdup (text);
2239 entry->data = data;
2240 entry->hotkey = hotkey;
2242 listbox_append_item (l, entry, pos);
2244 return entry->text;
2247 /* Selects the nth entry in the listbox */
2248 void
2249 listbox_select_by_number (WListbox *l, int n)
2251 listbox_select_entry (l, listbox_select_pos (l, l->list, n));
2254 WLEntry *
2255 listbox_search_text (WListbox *l, const char *text)
2257 WLEntry *e;
2259 e = l->list;
2260 if (!e)
2261 return NULL;
2263 do {
2264 if(!strcmp (e->text, text))
2265 return e;
2266 e = e->next;
2267 } while (e!=l->list);
2269 return NULL;
2272 /* Returns the current string text as well as the associated extra data */
2273 void
2274 listbox_get_current (WListbox *l, char **string, char **extra)
2276 if (!l->current){
2277 *string = 0;
2278 *extra = 0;
2280 if (string && l->current)
2281 *string = l->current->text;
2282 if (extra && l->current)
2283 *extra = l->current->data;
2286 /* returns TRUE if a function has been called, FALSE otherwise. */
2287 static gboolean
2288 buttonbar_call (WButtonBar *bb, int i)
2290 switch (bb->labels[i].tag) {
2291 case BBFUNC_NONE:
2292 break;
2293 case BBFUNC_VOID:
2294 bb->labels[i].u.fn_void ();
2295 return TRUE;
2296 case BBFUNC_PTR:
2297 bb->labels[i].u.fn_ptr (bb->labels[i].data);
2298 return TRUE;
2300 return FALSE;
2304 static cb_ret_t
2305 buttonbar_callback (Widget *w, widget_msg_t msg, int parm)
2307 WButtonBar *bb = (WButtonBar *) w;
2308 int i;
2310 switch (msg) {
2311 case WIDGET_FOCUS:
2312 return MSG_NOT_HANDLED;
2314 case WIDGET_HOTKEY:
2315 for (i = 0; i < 10; i++) {
2316 if (parm == KEY_F (i + 1) && buttonbar_call (bb, i))
2317 return MSG_HANDLED;
2319 return MSG_NOT_HANDLED;
2321 case WIDGET_DRAW:
2322 if (!bb->visible)
2323 return MSG_HANDLED;
2324 widget_move (&bb->widget, 0, 0);
2325 attrset (DEFAULT_COLOR);
2326 tty_printf ("%-*s", bb->widget.cols, "");
2327 for (i = 0; i < COLS / 8 && i < 10; i++) {
2328 widget_move (&bb->widget, 0, i * 8);
2329 attrset (DEFAULT_COLOR);
2330 tty_printf ("%d", i + 1);
2331 attrset (SELECTED_COLOR);
2332 tty_printf ("%-*s", ((i + 1) * 8 == COLS ? 5 : 6),
2333 bb->labels[i].text ? bb->labels[i].text : "");
2334 attrset (DEFAULT_COLOR);
2336 attrset (SELECTED_COLOR);
2337 return MSG_HANDLED;
2339 case WIDGET_DESTROY:
2340 for (i = 0; i < 10; i++)
2341 g_free (bb->labels[i].text);
2342 return MSG_HANDLED;
2344 default:
2345 return default_proc (msg, parm);
2349 static int
2350 buttonbar_event (Gpm_Event *event, void *data)
2352 WButtonBar *bb = data;
2353 int button;
2355 if (!(event->type & GPM_UP))
2356 return MOU_NORMAL;
2357 if (event->y == 2)
2358 return MOU_NORMAL;
2359 button = event->x / 8;
2360 if (button < 10)
2361 buttonbar_call (bb, button);
2362 return MOU_NORMAL;
2365 WButtonBar *
2366 buttonbar_new (int visible)
2368 int i;
2369 WButtonBar *bb = g_new (WButtonBar, 1);
2371 init_widget (&bb->widget, LINES-1, 0, 1, COLS,
2372 buttonbar_callback, buttonbar_event);
2374 bb->visible = visible;
2375 for (i = 0; i < 10; i++){
2376 bb->labels[i].text = NULL;
2377 bb->labels[i].tag = BBFUNC_NONE;
2379 widget_want_hotkey (bb->widget, 1);
2380 widget_want_cursor (bb->widget, 0);
2382 return bb;
2385 static void
2386 set_label_text (WButtonBar * bb, int index, const char *text)
2388 g_free (bb->labels[index - 1].text);
2390 bb->labels[index - 1].text = g_strdup (text);
2393 /* Find ButtonBar widget in the dialog */
2394 WButtonBar *
2395 find_buttonbar (Dlg_head *h)
2397 WButtonBar *bb;
2399 bb = (WButtonBar *) find_widget_type (h, buttonbar_callback);
2400 return bb;
2403 void
2404 buttonbar_clear_label (Dlg_head *h, int idx)
2406 WButtonBar *bb = find_buttonbar (h);
2408 if (!bb)
2409 return;
2411 set_label_text (bb, idx, "");
2412 bb->labels[idx - 1].tag = BBFUNC_NONE;
2415 void
2416 buttonbar_set_label_data (Dlg_head *h, int idx, const char *text, buttonbarfn cback,
2417 void *data)
2419 WButtonBar *bb = find_buttonbar (h);
2421 if (!bb)
2422 return;
2424 assert (cback != (buttonbarfn) 0);
2425 set_label_text (bb, idx, text);
2426 bb->labels[idx - 1].tag = BBFUNC_PTR;
2427 bb->labels[idx - 1].u.fn_ptr = cback;
2428 bb->labels[idx - 1].data = data;
2431 void
2432 buttonbar_set_label (Dlg_head *h, int idx, const char *text, voidfn cback)
2434 WButtonBar *bb = find_buttonbar (h);
2436 if (!bb)
2437 return;
2439 assert (cback != (voidfn) 0);
2440 set_label_text (bb, idx, text);
2441 bb->labels[idx - 1].tag = BBFUNC_VOID;
2442 bb->labels[idx - 1].u.fn_void = cback;
2445 void
2446 buttonbar_set_visible (WButtonBar *bb, gboolean visible)
2448 bb->visible = visible;
2451 void
2452 buttonbar_redraw (Dlg_head *h)
2454 WButtonBar *bb = find_buttonbar (h);
2456 if (!bb)
2457 return;
2459 send_message ((Widget *) bb, WIDGET_DRAW, 0);
2462 static cb_ret_t
2463 groupbox_callback (Widget *w, widget_msg_t msg, int parm)
2465 WGroupbox *g = (WGroupbox *) w;
2467 switch (msg) {
2468 case WIDGET_INIT:
2469 return MSG_HANDLED;
2471 case WIDGET_FOCUS:
2472 return MSG_NOT_HANDLED;
2474 case WIDGET_DRAW:
2475 attrset (COLOR_NORMAL);
2476 draw_box (g->widget.parent, g->widget.y - g->widget.parent->y,
2477 g->widget.x - g->widget.parent->x, g->widget.lines,
2478 g->widget.cols);
2480 attrset (COLOR_HOT_NORMAL);
2481 dlg_move (g->widget.parent, g->widget.y - g->widget.parent->y,
2482 g->widget.x - g->widget.parent->x + 1);
2483 addstr (g->title);
2484 return MSG_HANDLED;
2486 case WIDGET_DESTROY:
2487 g_free (g->title);
2488 return MSG_HANDLED;
2490 default:
2491 return default_proc (msg, parm);
2495 WGroupbox *
2496 groupbox_new (int x, int y, int width, int height, const char *title)
2498 WGroupbox *g = g_new (WGroupbox, 1);
2500 init_widget (&g->widget, y, x, height, width, groupbox_callback, NULL);
2502 g->widget.options &= ~W_WANT_CURSOR;
2503 widget_want_hotkey (g->widget, 0);
2505 /* Strip existing spaces, add one space before and after the title */
2506 if (title) {
2507 char *t;
2508 t = g_strstrip (g_strdup (title));
2509 g->title = g_strconcat (" ", t, " ", (char *) NULL);
2510 g_free (t);
2513 return g;