Original patch as attached on the bugreport
[midnight-commander.git] / src / widget.c
blob83f31690a70c4ebf52e7ddd2199a4107c67cebb0
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>
36 #include <sys/types.h>
38 #include "global.h"
39 #include "tty.h"
40 #include "color.h"
41 #include "mouse.h"
42 #include "dialog.h"
43 #include "widget.h"
44 #include "win.h"
45 #include "complete.h"
46 #include "key.h" /* XCTRL and ALT macros */
47 #include "profile.h" /* for history loading and saving */
48 #include "wtools.h" /* For common_dialog_repaint() */
49 #include "main.h" /* for `slow_terminal' */
51 #define HISTORY_FILE_NAME ".mc/history"
53 struct WButtonBar {
54 Widget widget;
55 int visible; /* Is it visible? */
56 struct {
57 char *text;
58 enum { BBFUNC_NONE, BBFUNC_VOID, BBFUNC_PTR } tag;
59 union {
60 voidfn fn_void;
61 buttonbarfn fn_ptr;
62 } u;
63 void *data;
64 } labels [10];
67 static int button_event (Gpm_Event *event, void *);
69 int quote = 0;
71 static void
72 widget_selectcolor (Widget *w, gboolean focused, gboolean hotkey)
74 Dlg_head *h = w->parent;
76 attrset (hotkey
77 ? (focused
78 ? DLG_HOT_FOCUSC (h)
79 : DLG_HOT_NORMALC (h))
80 : (focused
81 ? DLG_FOCUSC (h)
82 : DLG_NORMALC (h)));
85 static cb_ret_t
86 button_callback (Widget *w, widget_msg_t msg, int parm)
88 WButton *b = (WButton *) w;
89 char buf[BUF_SMALL];
90 int stop = 0;
91 int off = 0;
92 Dlg_head *h = b->widget.parent;
94 switch (msg) {
95 case WIDGET_HOTKEY:
97 * Don't let the default button steal Enter from the current
98 * button. This is a workaround for the flawed event model
99 * when hotkeys are sent to all widgets before the key is
100 * handled by the current widget.
102 if (parm == '\n' && h->current == &b->widget) {
103 button_callback (w, WIDGET_KEY, ' ');
104 return MSG_HANDLED;
107 if (parm == '\n' && b->flags == DEFPUSH_BUTTON) {
108 button_callback (w, WIDGET_KEY, ' ');
109 return MSG_HANDLED;
112 if (b->hotkey == tolower (parm)) {
113 button_callback (w, WIDGET_KEY, ' ');
114 return MSG_HANDLED;
117 return MSG_NOT_HANDLED;
119 case WIDGET_KEY:
120 if (parm != ' ' && parm != '\n')
121 return MSG_NOT_HANDLED;
123 if (b->callback)
124 stop = (*b->callback) (b->action);
125 if (!b->callback || stop) {
126 h->ret_value = b->action;
127 dlg_stop (h);
129 return MSG_HANDLED;
131 case WIDGET_CURSOR:
132 switch (b->flags) {
133 case DEFPUSH_BUTTON:
134 off = 3;
135 break;
136 case NORMAL_BUTTON:
137 off = 2;
138 break;
139 case NARROW_BUTTON:
140 off = 1;
141 break;
142 case HIDDEN_BUTTON:
143 default:
144 off = 0;
145 break;
147 widget_move (&b->widget, 0, b->hotpos + off);
148 return MSG_HANDLED;
150 case WIDGET_UNFOCUS:
151 case WIDGET_FOCUS:
152 case WIDGET_DRAW:
153 if (msg == WIDGET_UNFOCUS)
154 b->selected = 0;
155 else if (msg == WIDGET_FOCUS)
156 b->selected = 1;
158 switch (b->flags) {
159 case DEFPUSH_BUTTON:
160 g_snprintf (buf, sizeof (buf), "[< %s >]", b->text);
161 off = 3;
162 break;
163 case NORMAL_BUTTON:
164 g_snprintf (buf, sizeof (buf), "[ %s ]", b->text);
165 off = 2;
166 break;
167 case NARROW_BUTTON:
168 g_snprintf (buf, sizeof (buf), "[%s]", b->text);
169 off = 1;
170 break;
171 case HIDDEN_BUTTON:
172 default:
173 buf[0] = '\0';
174 off = 0;
175 break;
178 widget_selectcolor (w, b->selected, FALSE);
179 widget_move (w, 0, 0);
181 addstr (buf);
183 if (b->hotpos >= 0) {
184 widget_selectcolor (w, b->selected, TRUE);
185 widget_move (w, 0, b->hotpos + off);
186 addch ((unsigned char) b->text[b->hotpos]);
188 return MSG_HANDLED;
190 case WIDGET_DESTROY:
191 g_free (b->text);
192 return MSG_HANDLED;
194 default:
195 return default_proc (msg, parm);
199 static int
200 button_event (Gpm_Event *event, void *data)
202 WButton *b = data;
204 if (event->type & (GPM_DOWN|GPM_UP)){
205 Dlg_head *h=b->widget.parent;
206 dlg_select_widget (b);
207 if (event->type & GPM_UP){
208 button_callback ((Widget *) data, WIDGET_KEY, ' ');
209 (*h->callback) (h, DLG_POST_KEY, ' ');
210 return MOU_NORMAL;
213 return MOU_NORMAL;
216 static int
217 button_len (const char *text, unsigned int flags)
219 int ret = strlen (text);
220 switch (flags){
221 case DEFPUSH_BUTTON:
222 ret += 6;
223 break;
224 case NORMAL_BUTTON:
225 ret += 4;
226 break;
227 case NARROW_BUTTON:
228 ret += 2;
229 break;
230 case HIDDEN_BUTTON:
231 default:
232 return 0;
234 return ret;
238 * Locate the hotkey and remove it from the button text. Assuming that
239 * the button text is g_malloc()ed, we can safely change and shorten it.
241 static void
242 button_scan_hotkey (WButton *b)
244 char *cp = strchr (b->text, '&');
246 if (cp != NULL && cp[1] != '\0') {
247 g_strlcpy (cp, cp + 1, strlen (cp));
248 b->hotkey = tolower ((unsigned char) *cp);
249 b->hotpos = cp - b->text;
253 WButton *
254 button_new (int y, int x, int action, int flags, const char *text,
255 bcback callback)
257 WButton *b = g_new (WButton, 1);
259 init_widget (&b->widget, y, x, 1, button_len (text, flags),
260 button_callback, button_event);
262 b->action = action;
263 b->flags = flags;
264 b->selected = 0;
265 b->text = g_strdup (text);
266 b->callback = callback;
267 widget_want_hotkey (b->widget, 1);
268 b->hotkey = 0;
269 b->hotpos = -1;
271 button_scan_hotkey(b);
272 return b;
275 const char *
276 button_get_text (WButton *b)
278 return b->text;
281 void
282 button_set_text (WButton *b, const char *text)
284 g_free (b->text);
285 b->text = g_strdup (text);
286 b->widget.cols = button_len (text, b->flags);
287 button_scan_hotkey(b);
288 dlg_redraw (b->widget.parent);
292 /* Radio button widget */
293 static int radio_event (Gpm_Event *event, void *);
295 static cb_ret_t
296 radio_callback (Widget *w, widget_msg_t msg, int parm)
298 WRadio *r = (WRadio *) w;
299 int i;
300 Dlg_head *h = r->widget.parent;
302 switch (msg) {
303 case WIDGET_HOTKEY:
305 int i, lp = tolower (parm);
306 const char *cp;
308 for (i = 0; i < r->count; i++) {
309 cp = strchr (r->texts[i], '&');
310 if (cp != NULL && cp[1] != '\0') {
311 int c = tolower ((unsigned char) cp[1]);
313 if (c != lp)
314 continue;
315 r->pos = i;
317 /* Take action */
318 radio_callback (w, WIDGET_KEY, ' ');
319 return MSG_HANDLED;
323 return MSG_NOT_HANDLED;
325 case WIDGET_KEY:
326 switch (parm) {
327 case ' ':
328 r->sel = r->pos;
329 (*h->callback) (h, DLG_ACTION, 0);
330 radio_callback (w, WIDGET_FOCUS, ' ');
331 return MSG_HANDLED;
333 case KEY_UP:
334 case KEY_LEFT:
335 if (r->pos > 0) {
336 r->pos--;
337 return MSG_HANDLED;
339 return MSG_NOT_HANDLED;
341 case KEY_DOWN:
342 case KEY_RIGHT:
343 if (r->count - 1 > r->pos) {
344 r->pos++;
345 return MSG_HANDLED;
348 return MSG_NOT_HANDLED;
350 case WIDGET_CURSOR:
351 (*h->callback) (h, DLG_ACTION, 0);
352 radio_callback (w, WIDGET_FOCUS, ' ');
353 widget_move (&r->widget, r->pos, 1);
354 return MSG_HANDLED;
356 case WIDGET_UNFOCUS:
357 case WIDGET_FOCUS:
358 case WIDGET_DRAW:
359 for (i = 0; i < r->count; i++) {
360 register const char *cp;
361 const gboolean focused = (i == r->pos && msg == WIDGET_FOCUS);
362 widget_selectcolor (w, focused, FALSE);
363 widget_move (&r->widget, i, 0);
365 tty_printf ("(%c) ", (r->sel == i) ? '*' : ' ');
366 for (cp = r->texts[i]; *cp; cp++) {
367 if (*cp == '&') {
368 widget_selectcolor (w, focused, TRUE);
369 addch (*++cp);
370 widget_selectcolor (w, focused, FALSE);
371 } else
372 addch (*cp);
375 return MSG_HANDLED;
377 default:
378 return default_proc (msg, parm);
382 static int
383 radio_event (Gpm_Event *event, void *data)
385 WRadio *r = data;
386 Widget *w = data;
388 if (event->type & (GPM_DOWN|GPM_UP)){
389 Dlg_head *h = r->widget.parent;
391 r->pos = event->y - 1;
392 dlg_select_widget (r);
393 if (event->type & GPM_UP){
394 radio_callback (w, WIDGET_KEY, ' ');
395 radio_callback (w, WIDGET_FOCUS, 0);
396 (*h->callback) (h, DLG_POST_KEY, ' ');
397 return MOU_NORMAL;
400 return MOU_NORMAL;
403 WRadio *
404 radio_new (int y, int x, int count, const char **texts)
406 WRadio *r = g_new (WRadio, 1);
407 int i, max, m;
409 /* Compute the longest string */
410 max = 0;
411 for (i = 0; i < count; i++){
412 m = strlen (texts [i]);
413 if (m > max)
414 max = m;
417 init_widget (&r->widget, y, x, count, max, radio_callback, radio_event);
418 r->state = 1;
419 r->pos = 0;
420 r->sel = 0;
421 r->count = count;
422 r->texts = texts;
423 widget_want_hotkey (r->widget, 1);
425 return r;
429 /* Checkbutton widget */
431 static int check_event (Gpm_Event *event, void *);
433 static cb_ret_t
434 check_callback (Widget *w, widget_msg_t msg, int parm)
436 WCheck *c = (WCheck *) w;
437 Dlg_head *h = c->widget.parent;
439 switch (msg) {
440 case WIDGET_HOTKEY:
441 if (c->hotkey == parm
442 || (c->hotkey >= 'a' && c->hotkey <= 'z'
443 && c->hotkey - 32 == parm)) {
444 check_callback (w, WIDGET_KEY, ' '); /* make action */
445 return MSG_HANDLED;
447 return MSG_NOT_HANDLED;
449 case WIDGET_KEY:
450 if (parm != ' ')
451 return MSG_NOT_HANDLED;
452 c->state ^= C_BOOL;
453 c->state ^= C_CHANGE;
454 (*h->callback) (h, DLG_ACTION, 0);
455 check_callback (w, WIDGET_FOCUS, ' ');
456 return MSG_HANDLED;
458 case WIDGET_CURSOR:
459 widget_move (&c->widget, 0, 1);
460 return MSG_HANDLED;
462 case WIDGET_FOCUS:
463 case WIDGET_UNFOCUS:
464 case WIDGET_DRAW:
465 widget_selectcolor (w, msg == WIDGET_FOCUS, FALSE);
466 widget_move (&c->widget, 0, 0);
467 tty_printf ("[%c] %s", (c->state & C_BOOL) ? 'x' : ' ', c->text);
469 if (c->hotpos >= 0) {
470 widget_selectcolor (w, msg == WIDGET_FOCUS, TRUE);
471 widget_move (&c->widget, 0, +c->hotpos + 4);
472 addch ((unsigned char) c->text[c->hotpos]);
474 return MSG_HANDLED;
476 case WIDGET_DESTROY:
477 g_free (c->text);
478 return MSG_HANDLED;
480 default:
481 return default_proc (msg, parm);
485 static int
486 check_event (Gpm_Event *event, void *data)
488 WCheck *c = data;
489 Widget *w = data;
491 if (event->type & (GPM_DOWN|GPM_UP)){
492 Dlg_head *h = c->widget.parent;
494 dlg_select_widget (c);
495 if (event->type & GPM_UP){
496 check_callback (w, WIDGET_KEY, ' ');
497 check_callback (w, WIDGET_FOCUS, 0);
498 (*h->callback) (h, DLG_POST_KEY, ' ');
499 return MOU_NORMAL;
502 return MOU_NORMAL;
505 WCheck *
506 check_new (int y, int x, int state, const char *text)
508 WCheck *c = g_new (WCheck, 1);
509 const char *s;
510 char *t;
512 init_widget (&c->widget, y, x, 1, strlen (text),
513 check_callback, check_event);
514 c->state = state ? C_BOOL : 0;
515 c->text = g_strdup (text);
516 c->hotkey = 0;
517 c->hotpos = -1;
518 widget_want_hotkey (c->widget, 1);
520 /* Scan for the hotkey */
521 for (s = text, t = c->text; *s; s++, t++){
522 if (*s != '&'){
523 *t = *s;
524 continue;
526 s++;
527 if (*s){
528 c->hotkey = tolower ((unsigned char) *s);
529 c->hotpos = t - c->text;
531 *t = *s;
533 *t = 0;
534 return c;
538 /* Label widget */
540 static cb_ret_t
541 label_callback (Widget *w, widget_msg_t msg, int parm)
543 WLabel *l = (WLabel *) w;
544 Dlg_head *h = l->widget.parent;
546 switch (msg) {
547 case WIDGET_INIT:
548 return MSG_HANDLED;
550 /* We don't want to get the focus */
551 case WIDGET_FOCUS:
552 return MSG_NOT_HANDLED;
554 case WIDGET_DRAW:
556 char *p = l->text, *q, c = 0;
557 int y = 0;
559 if (!l->text)
560 return MSG_HANDLED;
562 if (l->transparent)
563 attrset (DEFAULT_COLOR);
564 else
565 attrset (DLG_NORMALC (h));
566 for (;;) {
567 int xlen;
569 q = strchr (p, '\n');
570 if (q) {
571 c = *q;
572 *q = 0;
574 widget_move (&l->widget, y, 0);
575 tty_printf ("%s", p);
576 xlen = l->widget.cols - strlen (p);
577 if (xlen > 0)
578 tty_printf ("%*s", xlen, " ");
579 if (!q)
580 break;
581 *q = c;
582 p = q + 1;
583 y++;
585 return MSG_HANDLED;
588 case WIDGET_DESTROY:
589 g_free (l->text);
590 return MSG_HANDLED;
592 default:
593 return default_proc (msg, parm);
597 void
598 label_set_text (WLabel *label, const char *text)
600 int newcols = label->widget.cols;
602 if (label->text && text && !strcmp (label->text, text))
603 return; /* Flickering is not nice */
605 g_free (label->text);
607 if (text){
608 label->text = g_strdup (text);
609 if (label->auto_adjust_cols) {
610 newcols = strlen (text);
611 if (newcols > label->widget.cols)
612 label->widget.cols = newcols;
614 } else
615 label->text = 0;
617 if (label->widget.parent)
618 label_callback ((Widget *) label, WIDGET_DRAW, 0);
620 if (newcols < label->widget.cols)
621 label->widget.cols = newcols;
624 WLabel *
625 label_new (int y, int x, const char *text)
627 WLabel *l;
628 int width;
630 /* Multiline labels are immutable - no need to compute their sizes */
631 if (!text || strchr(text, '\n'))
632 width = 1;
633 else
634 width = strlen (text);
636 l = g_new (WLabel, 1);
637 init_widget (&l->widget, y, x, 1, width, label_callback, NULL);
638 l->text = text ? g_strdup (text) : 0;
639 l->auto_adjust_cols = 1;
640 l->transparent = 0;
641 widget_want_cursor (l->widget, 0);
642 return l;
646 /* Gauge widget (progress indicator) */
647 /* Currently width is hardcoded here for text mode */
648 #define gauge_len 47
650 static cb_ret_t
651 gauge_callback (Widget *w, widget_msg_t msg, int parm)
653 WGauge *g = (WGauge *) w;
654 Dlg_head *h = g->widget.parent;
656 if (msg == WIDGET_INIT)
657 return MSG_HANDLED;
659 /* We don't want to get the focus */
660 if (msg == WIDGET_FOCUS)
661 return MSG_NOT_HANDLED;
663 if (msg == WIDGET_DRAW){
664 widget_move (&g->widget, 0, 0);
665 attrset (DLG_NORMALC (h));
666 if (!g->shown)
667 tty_printf ("%*s", gauge_len, "");
668 else {
669 int percentage, columns;
670 long total = g->max, done = g->current;
672 if (total <= 0 || done < 0) {
673 done = 0;
674 total = 100;
676 if (done > total)
677 done = total;
678 while (total > 65535) {
679 total /= 256;
680 done /= 256;
682 percentage = (200 * done / total + 1) / 2;
683 columns = (2 * (gauge_len - 7) * done / total + 1) / 2;
684 addch ('[');
685 attrset (GAUGE_COLOR);
686 tty_printf ("%*s", (int) columns, "");
687 attrset (DLG_NORMALC (h));
688 tty_printf ("%*s] %3d%%", (int)(gauge_len - 7 - columns), "", (int) percentage);
690 return MSG_HANDLED;
692 return default_proc (msg, parm);
695 void
696 gauge_set_value (WGauge *g, int max, int current)
698 if (g->current == current && g->max == max)
699 return; /* Do not flicker */
700 if (max == 0)
701 max = 1; /* I do not like division by zero :) */
703 g->current = current;
704 g->max = max;
705 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
708 void
709 gauge_show (WGauge *g, int shown)
711 if (g->shown == shown)
712 return;
713 g->shown = shown;
714 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
717 WGauge *
718 gauge_new (int y, int x, int shown, int max, int current)
720 WGauge *g = g_new (WGauge, 1);
722 init_widget (&g->widget, y, x, 1, gauge_len, gauge_callback, NULL);
723 g->shown = shown;
724 if (max == 0)
725 max = 1; /* I do not like division by zero :) */
726 g->max = max;
727 g->current = current;
728 widget_want_cursor (g->widget, 0);
729 return g;
733 /* Input widget */
735 /* {{{ history button */
737 #define LARGE_HISTORY_BUTTON 1
739 #ifdef LARGE_HISTORY_BUTTON
740 # define HISTORY_BUTTON_WIDTH 3
741 #else
742 # define HISTORY_BUTTON_WIDTH 1
743 #endif
745 #define should_show_history_button(in) \
746 (in->history && in->field_len > HISTORY_BUTTON_WIDTH * 2 + 1 && in->widget.parent)
748 static void draw_history_button (WInput * in)
750 char c;
751 c = in->history->next ? (in->history->prev ? '|' : 'v') : '^';
752 widget_move (&in->widget, 0, in->field_len - HISTORY_BUTTON_WIDTH);
753 #ifdef LARGE_HISTORY_BUTTON
755 Dlg_head *h;
756 h = in->widget.parent;
757 #if 0
758 attrset (NORMALC); /* button has the same color as other buttons */
759 addstr ("[ ]");
760 attrset (HOT_NORMALC);
761 #else
762 attrset (NORMAL_COLOR);
763 addstr ("[ ]");
764 /* Too distracting: attrset (MARKED_COLOR); */
765 #endif
766 widget_move (&in->widget, 0, in->field_len - HISTORY_BUTTON_WIDTH + 1);
767 addch (c);
769 #else
770 attrset (MARKED_COLOR);
771 addch (c);
772 #endif
775 /* }}} history button */
778 /* Input widgets now have a global kill ring */
779 /* Pointer to killed data */
780 static char *kill_buffer = 0;
782 void
783 update_input (WInput *in, int clear_first)
785 int has_history = 0;
786 int i, j;
787 unsigned char c;
788 int buf_len = strlen (in->buffer);
790 if (should_show_history_button (in))
791 has_history = HISTORY_BUTTON_WIDTH;
793 if (in->disable_update)
794 return;
796 /* Make the point visible */
797 if ((in->point < in->first_shown) ||
798 (in->point >= in->first_shown+in->field_len - has_history)){
799 in->first_shown = in->point - (in->field_len / 3);
800 if (in->first_shown < 0)
801 in->first_shown = 0;
804 /* Adjust the mark */
805 if (in->mark > buf_len)
806 in->mark = buf_len;
808 if (has_history)
809 draw_history_button (in);
811 attrset (in->color);
813 widget_move (&in->widget, 0, 0);
814 for (i = 0; i < in->field_len - has_history; i++)
815 addch (' ');
816 widget_move (&in->widget, 0, 0);
818 for (i = 0, j = in->first_shown; i < in->field_len - has_history && in->buffer [j]; i++){
819 c = in->buffer [j++];
820 c = is_printable (c) ? c : '.';
821 if (in->is_password)
822 c = '*';
823 addch (c);
825 widget_move (&in->widget, 0, in->point - in->first_shown);
827 if (clear_first)
828 in->first = 0;
831 void
832 winput_set_origin (WInput *in, int x, int field_len)
834 in->widget.x = x;
835 in->field_len = in->widget.cols = field_len;
836 update_input (in, 0);
839 /* {{{ history saving and loading */
841 int num_history_items_recorded = 60;
844 This loads and saves the history of an input line to and from the
845 widget. It is called with the widgets history name on creation of the
846 widget, and returns the GList list. It stores histories in the file
847 ~/.mc/history in using the profile code.
849 If def_text is passed as INPUT_LAST_TEXT (to the input_new()
850 function) then input_new assigns the default text to be the last text
851 entered, or "" if not found.
854 GList *
855 history_get (const char *input_name)
857 int i;
858 GList *hist;
859 char *profile;
861 hist = NULL;
863 if (!num_history_items_recorded) /* this is how to disable */
864 return NULL;
865 if (!input_name)
866 return NULL;
867 if (!*input_name)
868 return NULL;
869 profile = concat_dir_and_file (home_dir, HISTORY_FILE_NAME);
870 for (i = 0;; i++) {
871 char key_name[BUF_TINY];
872 char this_entry[BUF_LARGE];
873 g_snprintf (key_name, sizeof (key_name), "%d", i);
874 GetPrivateProfileString (input_name, key_name, "", this_entry,
875 sizeof (this_entry), profile);
876 if (!*this_entry)
877 break;
879 hist = list_append_unique (hist, g_strdup (this_entry));
881 g_free (profile);
883 /* return pointer to the last entry in the list */
884 hist = g_list_last (hist);
886 return hist;
889 void
890 history_put (const char *input_name, GList *h)
892 int i;
893 char *profile;
895 if (!input_name)
896 return;
898 if (!*input_name)
899 return;
901 if (!h)
902 return;
904 if (!num_history_items_recorded) /* this is how to disable */
905 return;
907 profile = concat_dir_and_file (home_dir, HISTORY_FILE_NAME);
909 if ((i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) != -1)
910 close (i);
912 /* Make sure the history is only readable by the user */
913 if (chmod (profile, S_IRUSR | S_IWUSR) == -1 && errno != ENOENT) {
914 g_free (profile);
915 return;
918 /* go to end of list */
919 h = g_list_last (h);
921 /* go back 60 places */
922 for (i = 0; i < num_history_items_recorded - 1 && h->prev; i++)
923 h = g_list_previous (h);
925 if (input_name)
926 profile_clean_section (input_name, profile);
928 /* dump histories into profile */
929 for (i = 0; h; h = g_list_next (h)) {
930 char *text;
932 text = (char *) h->data;
934 /* We shouldn't have null entries, but let's be sure */
935 if (text && *text) {
936 char key_name[BUF_TINY];
937 g_snprintf (key_name, sizeof (key_name), "%d", i++);
938 WritePrivateProfileString (input_name, key_name, text,
939 profile);
943 g_free (profile);
946 /* }}} history saving and loading */
949 /* {{{ history display */
951 static const char *
952 i18n_htitle (void)
954 static const char *history_title = NULL;
956 if (history_title == NULL)
957 history_title = _(" History ");
958 return history_title;
961 static WLEntry *listbox_select_pos (WListbox *l, WLEntry *base, int
962 pos);
964 static inline cb_ret_t
965 listbox_fwd (WListbox *l)
967 if (l->current != l->list->prev){
968 listbox_select_entry (l, listbox_select_pos (l, l->list, l->pos+1));
969 return MSG_HANDLED;
971 return MSG_NOT_HANDLED;
974 char *
975 show_hist (GList *history, int widget_x, int widget_y)
977 GList *hi, *z;
978 size_t maxlen = strlen (i18n_htitle ()), i, count = 0;
979 int x, y, w, h;
980 char *q, *r = 0;
981 Dlg_head *query_dlg;
982 WListbox *query_list;
984 z = history;
985 if (!z)
986 return NULL;
988 z = g_list_first (history);
989 hi = z;
990 while (hi) {
991 if ((i = strlen ((char *) hi->data)) > maxlen)
992 maxlen = i;
993 count++;
994 hi = g_list_next (hi);
997 y = widget_y;
998 h = count + 2;
999 if (h <= y || y > LINES - 6) {
1000 h = min (h, y - 1);
1001 y -= h;
1002 } else {
1003 y++;
1004 h = min (h, LINES - y);
1007 if (widget_x > 2)
1008 x = widget_x - 2;
1009 else
1010 x = 0;
1011 if ((w = maxlen + 4) + x > COLS) {
1012 w = min (w, COLS);
1013 x = COLS - w;
1016 query_dlg =
1017 create_dlg (y, x, h, w, dialog_colors, NULL, "[History-query]",
1018 i18n_htitle (), DLG_COMPACT);
1019 query_list = listbox_new (1, 1, w - 2, h - 2, 0);
1020 add_widget (query_dlg, query_list);
1021 hi = z;
1022 if (y < widget_y) {
1023 /* traverse */
1024 while (hi) {
1025 listbox_add_item (query_list, 0, 0, (char *) hi->data, NULL);
1026 hi = g_list_next (hi);
1028 while (listbox_fwd (query_list));
1029 } else {
1030 /* traverse backwards */
1031 hi = g_list_last (history);
1032 while (hi) {
1033 listbox_add_item (query_list, 0, 0, (char *) hi->data, NULL);
1034 hi = g_list_previous (hi);
1037 run_dlg (query_dlg);
1038 q = NULL;
1039 if (query_dlg->ret_value != B_CANCEL) {
1040 listbox_get_current (query_list, &q, NULL);
1041 if (q)
1042 r = g_strdup (q);
1044 destroy_dlg (query_dlg);
1045 return r;
1048 static void do_show_hist (WInput * in)
1050 char *r;
1051 r = show_hist (in->history, in->widget.x, in->widget.y);
1052 if (r) {
1053 assign_text (in, r);
1054 g_free (r);
1058 /* }}} history display */
1060 static void
1061 input_destroy (WInput *in)
1063 if (!in){
1064 fprintf (stderr, "Internal error: null Input *\n");
1065 exit (1);
1068 new_input (in);
1070 if (in->history){
1071 if (!in->is_password) /* don't save passwords ;-) */
1072 history_put (in->history_name, in->history);
1074 in->history = g_list_first (in->history);
1075 g_list_foreach (in->history, (GFunc) g_free, NULL);
1076 g_list_free (in->history);
1079 g_free (in->buffer);
1080 free_completions (in);
1081 g_free (in->history_name);
1084 void
1085 input_disable_update (WInput *in)
1087 in->disable_update++;
1090 void
1091 input_enable_update (WInput *in)
1093 in->disable_update--;
1094 update_input (in, 0);
1097 #define ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
1100 push_history (WInput *in, const char *text)
1102 static int i18n;
1103 /* input widget where urls with passwords are entered without any
1104 vfs prefix */
1105 static const char *password_input_fields[] = {
1106 N_(" Link to a remote machine "),
1107 N_(" FTP to machine "),
1108 N_(" SMB link to machine ")
1110 char *t;
1111 const char *p;
1112 size_t i;
1114 if (!i18n) {
1115 i18n = 1;
1116 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1117 password_input_fields[i] = _(password_input_fields[i]);
1120 for (p = text; *p == ' ' || *p == '\t'; p++);
1121 if (!*p)
1122 return 0;
1124 if (in->history) {
1125 /* Avoid duplicated entries */
1126 in->history = g_list_last (in->history);
1127 if (!strcmp ((char *) in->history->data, text))
1128 return 1;
1131 t = g_strdup (text);
1133 if (in->history_name) {
1134 p = in->history_name + 3;
1135 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1136 if (strcmp (p, password_input_fields[i]) == 0)
1137 break;
1138 if (i < ELEMENTS (password_input_fields))
1139 strip_password (t, 0);
1140 else
1141 strip_password (t, 1);
1144 in->history = list_append_unique (in->history, t);
1145 in->need_push = 0;
1147 return 2;
1150 #undef ELEMENTS
1152 /* Cleans the input line and adds the current text to the history */
1153 void
1154 new_input (WInput *in)
1156 if (in->buffer)
1157 push_history (in, in->buffer);
1158 in->need_push = 1;
1159 in->buffer [0] = 0;
1160 in->point = 0;
1161 in->mark = 0;
1162 free_completions (in);
1163 update_input (in, 0);
1166 static cb_ret_t
1167 insert_char (WInput *in, int c_code)
1169 size_t i;
1171 if (c_code == -1)
1172 return MSG_NOT_HANDLED;
1174 in->need_push = 1;
1175 if (strlen (in->buffer)+1 == (size_t) in->current_max_len){
1176 /* Expand the buffer */
1177 char *narea = g_realloc (in->buffer, in->current_max_len + in->field_len);
1178 if (narea){
1179 in->buffer = narea;
1180 in->current_max_len += in->field_len;
1183 if (strlen (in->buffer)+1 < (size_t) in->current_max_len){
1184 size_t l = strlen (&in->buffer [in->point]);
1185 for (i = l+1; i > 0; i--)
1186 in->buffer [in->point+i] = in->buffer [in->point+i-1];
1187 in->buffer [in->point] = c_code;
1188 in->point++;
1190 return MSG_HANDLED;
1193 static void
1194 beginning_of_line (WInput *in)
1196 in->point = 0;
1199 static void
1200 end_of_line (WInput *in)
1202 in->point = strlen (in->buffer);
1205 static void
1206 backward_char (WInput *in)
1208 if (in->point)
1209 in->point--;
1212 static void
1213 forward_char (WInput *in)
1215 if (in->buffer [in->point])
1216 in->point++;
1219 static void
1220 forward_word (WInput * in)
1222 char *p = in->buffer + in->point;
1224 while (*p
1225 && (isspace ((unsigned char) *p)
1226 || ispunct ((unsigned char) *p)))
1227 p++;
1228 while (*p && isalnum ((unsigned char) *p))
1229 p++;
1230 in->point = p - in->buffer;
1233 static void
1234 backward_word (WInput *in)
1236 char *p = in->buffer + in->point;
1238 while (p - 1 > in->buffer - 1 && (isspace ((unsigned char) *(p - 1))
1239 || ispunct ((unsigned char)
1240 *(p - 1))))
1241 p--;
1242 while (p - 1 > in->buffer - 1 && isalnum ((unsigned char) *(p - 1)))
1243 p--;
1244 in->point = p - in->buffer;
1247 static void
1248 key_left (WInput *in)
1250 backward_char (in);
1253 static void
1254 key_ctrl_left (WInput *in)
1256 backward_word (in);
1259 static void
1260 key_right (WInput *in)
1262 forward_char (in);
1265 static void
1266 key_ctrl_right (WInput *in)
1268 forward_word (in);
1270 static void
1271 backward_delete (WInput *in)
1273 int i;
1275 if (!in->point)
1276 return;
1277 for (i = in->point; in->buffer [i-1]; i++)
1278 in->buffer [i-1] = in->buffer [i];
1279 in->need_push = 1;
1280 in->point--;
1283 static void
1284 delete_char (WInput *in)
1286 int i;
1288 for (i = in->point; in->buffer [i]; i++)
1289 in->buffer [i] = in->buffer [i+1];
1290 in->need_push = 1;
1293 static void
1294 copy_region (WInput *in, int x_first, int x_last)
1296 int first = min (x_first, x_last);
1297 int last = max (x_first, x_last);
1299 if (last == first)
1300 return;
1302 g_free (kill_buffer);
1304 kill_buffer = g_strndup(in->buffer+first,last-first);
1307 static void
1308 delete_region (WInput *in, int x_first, int x_last)
1310 int first = min (x_first, x_last);
1311 int last = max (x_first, x_last);
1312 size_t len = strlen (&in->buffer [last]) + 1;
1314 in->point = first;
1315 in->mark = first;
1316 memmove (&in->buffer [first], &in->buffer [last], len);
1317 in->need_push = 1;
1320 static void
1321 kill_word (WInput *in)
1323 int old_point = in->point;
1324 int new_point;
1326 forward_word (in);
1327 new_point = in->point;
1328 in->point = old_point;
1330 copy_region (in, old_point, new_point);
1331 delete_region (in, old_point, new_point);
1332 in->need_push = 1;
1335 static void
1336 back_kill_word (WInput *in)
1338 int old_point = in->point;
1339 int new_point;
1341 backward_word (in);
1342 new_point = in->point;
1343 in->point = old_point;
1345 copy_region (in, old_point, new_point);
1346 delete_region (in, old_point, new_point);
1347 in->need_push = 1;
1350 static void
1351 set_mark (WInput *in)
1353 in->mark = in->point;
1356 static void
1357 kill_save (WInput *in)
1359 copy_region (in, in->mark, in->point);
1362 static void
1363 kill_region (WInput *in)
1365 kill_save (in);
1366 delete_region (in, in->point, in->mark);
1369 static void
1370 yank (WInput *in)
1372 char *p;
1374 if (!kill_buffer)
1375 return;
1376 for (p = kill_buffer; *p; p++)
1377 insert_char (in, *p);
1380 static void
1381 kill_line (WInput *in)
1383 g_free (kill_buffer);
1384 kill_buffer = g_strdup (&in->buffer [in->point]);
1385 in->buffer [in->point] = 0;
1388 void
1389 assign_text (WInput *in, const char *text)
1391 free_completions (in);
1392 g_free (in->buffer);
1393 in->buffer = g_strdup (text); /* was in->buffer->text */
1394 in->current_max_len = strlen (in->buffer) + 1;
1395 in->point = strlen (in->buffer);
1396 in->mark = 0;
1397 in->need_push = 1;
1400 static void
1401 hist_prev (WInput *in)
1403 if (!in->history)
1404 return;
1406 if (in->need_push) {
1407 switch (push_history (in, in->buffer)) {
1408 case 2:
1409 in->history = g_list_previous (in->history);
1410 break;
1411 case 1:
1412 if (in->history->prev)
1413 in->history = g_list_previous (in->history);
1414 break;
1415 case 0:
1416 break;
1418 } else if (in->history->prev)
1419 in->history = g_list_previous (in->history);
1420 else
1421 return;
1422 assign_text (in, (char *) in->history->data);
1423 in->need_push = 0;
1426 static void
1427 hist_next (WInput *in)
1429 if (in->need_push) {
1430 switch (push_history (in, in->buffer)) {
1431 case 2:
1432 assign_text (in, "");
1433 return;
1434 case 0:
1435 return;
1439 if (!in->history)
1440 return;
1442 if (!in->history->next) {
1443 assign_text (in, "");
1444 return;
1447 in->history = g_list_next (in->history);
1448 assign_text (in, (char *) in->history->data);
1449 in->need_push = 0;
1452 static const struct {
1453 int key_code;
1454 void (*fn)(WInput *in);
1455 } input_map [] = {
1456 /* Motion */
1457 { XCTRL('a'), beginning_of_line },
1458 { KEY_HOME, beginning_of_line },
1459 { KEY_A1, beginning_of_line },
1460 { ALT ('<'), beginning_of_line },
1461 { XCTRL('e'), end_of_line },
1462 { KEY_END, end_of_line },
1463 { KEY_C1, end_of_line },
1464 { ALT ('>'), end_of_line },
1465 { KEY_LEFT, key_left },
1466 { KEY_LEFT | KEY_M_CTRL, key_ctrl_left },
1467 { XCTRL('b'), backward_char },
1468 { ALT('b'), backward_word },
1469 { KEY_RIGHT, key_right },
1470 { KEY_RIGHT | KEY_M_CTRL, key_ctrl_right },
1471 { XCTRL('f'), forward_char },
1472 { ALT('f'), forward_word },
1474 /* Editing */
1475 { KEY_BACKSPACE, backward_delete },
1476 { KEY_DC, delete_char },
1477 { ALT('d'), kill_word },
1478 { ALT(KEY_BACKSPACE), back_kill_word },
1480 /* Region manipulation */
1481 { 0, set_mark },
1482 { XCTRL('w'), kill_region },
1483 { ALT('w'), kill_save },
1484 { XCTRL('y'), yank },
1485 { XCTRL('k'), kill_line },
1487 /* History */
1488 { ALT('p'), hist_prev },
1489 { ALT('n'), hist_next },
1490 { ALT('h'), do_show_hist },
1492 /* Completion */
1493 { ALT('\t'), complete },
1495 { 0, 0 }
1498 /* This function is a test for a special input key used in complete.c */
1499 /* Returns 0 if it is not a special key, 1 if it is a non-complete key
1500 and 2 if it is a complete key */
1502 is_in_input_map (WInput *in, int c_code)
1504 int i;
1506 (void) in;
1508 for (i = 0; input_map [i].fn; i++)
1509 if (c_code == input_map [i].key_code) {
1510 if (input_map [i].fn == complete)
1511 return 2;
1512 else
1513 return 1;
1515 return 0;
1518 static void
1519 port_region_marked_for_delete (WInput *in)
1521 *in->buffer = 0;
1522 in->point = 0;
1523 in->first = 0;
1526 cb_ret_t
1527 handle_char (WInput *in, int c_code)
1529 cb_ret_t v;
1530 int i;
1532 v = MSG_NOT_HANDLED;
1534 if (quote){
1535 free_completions (in);
1536 v = insert_char (in, c_code);
1537 update_input (in, 1);
1538 quote = 0;
1539 return v;
1542 for (i = 0; input_map [i].fn; i++){
1543 if (c_code == input_map [i].key_code){
1544 if (input_map [i].fn != complete)
1545 free_completions (in);
1546 (*input_map [i].fn)(in);
1547 v = MSG_HANDLED;
1548 break;
1551 if (!input_map [i].fn){
1552 if (c_code > 255 || !is_printable (c_code))
1553 return MSG_NOT_HANDLED;
1554 if (in->first){
1555 port_region_marked_for_delete (in);
1557 free_completions (in);
1558 v = insert_char (in, c_code);
1560 update_input (in, 1);
1561 return v;
1564 /* Inserts text in input line */
1565 void
1566 stuff (WInput *in, const char *text, int insert_extra_space)
1568 input_disable_update (in);
1569 while (*text)
1570 handle_char (in, *text++);
1571 if (insert_extra_space)
1572 handle_char (in, ' ');
1573 input_enable_update (in);
1574 update_input (in, 1);
1577 void
1578 input_set_point (WInput *in, int pos)
1580 if (pos > in->current_max_len)
1581 pos = in->current_max_len;
1582 if (pos != in->point)
1583 free_completions (in);
1584 in->point = pos;
1585 update_input (in, 1);
1588 cb_ret_t
1589 input_callback (Widget *w, widget_msg_t msg, int parm)
1591 WInput *in = (WInput *) w;
1592 cb_ret_t v;
1594 switch (msg) {
1595 case WIDGET_KEY:
1596 if (parm == XCTRL ('q')) {
1597 quote = 1;
1598 v = handle_char (in, ascii_alpha_to_cntrl (mi_getch ()));
1599 quote = 0;
1600 return v;
1603 /* Keys we want others to handle */
1604 if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1605 || parm == KEY_F (10) || parm == XCTRL ('g') || parm == '\n')
1606 return MSG_NOT_HANDLED;
1608 /* When pasting multiline text, insert literal Enter */
1609 if ((parm & ~KEY_M_MASK) == '\n') {
1610 quote = 1;
1611 v = handle_char (in, '\n');
1612 quote = 0;
1613 return v;
1616 return handle_char (in, parm);
1618 case WIDGET_FOCUS:
1619 case WIDGET_UNFOCUS:
1620 case WIDGET_DRAW:
1621 update_input (in, 0);
1622 return MSG_HANDLED;
1624 case WIDGET_CURSOR:
1625 widget_move (&in->widget, 0, in->point - in->first_shown);
1626 return MSG_HANDLED;
1628 case WIDGET_DESTROY:
1629 input_destroy (in);
1630 return MSG_HANDLED;
1632 default:
1633 return default_proc (msg, parm);
1637 static int
1638 input_event (Gpm_Event * event, void *data)
1640 WInput *in = data;
1642 if (event->type & (GPM_DOWN | GPM_DRAG)) {
1643 dlg_select_widget (in);
1645 if (event->x >= in->field_len - HISTORY_BUTTON_WIDTH + 1
1646 && should_show_history_button (in)) {
1647 do_show_hist (in);
1648 } else {
1649 in->point = strlen (in->buffer);
1650 if (event->x - in->first_shown - 1 < in->point)
1651 in->point = event->x - in->first_shown - 1;
1652 if (in->point < 0)
1653 in->point = 0;
1655 update_input (in, 1);
1657 return MOU_NORMAL;
1660 WInput *
1661 input_new (int y, int x, int color, int len, const char *def_text,
1662 const char *histname)
1664 WInput *in = g_new (WInput, 1);
1665 int initial_buffer_len;
1667 init_widget (&in->widget, y, x, 1, len, input_callback, input_event);
1669 /* history setup */
1670 in->history = NULL;
1671 in->history_name = 0;
1672 if (histname) {
1673 if (*histname) {
1674 in->history_name = g_strdup (histname);
1675 in->history = history_get (histname);
1679 if (!def_text)
1680 def_text = "";
1682 if (def_text == INPUT_LAST_TEXT) {
1683 def_text = "";
1684 if (in->history)
1685 if (in->history->data)
1686 def_text = (char *) in->history->data;
1688 initial_buffer_len = 1 + max ((size_t) len, strlen (def_text));
1689 in->widget.options |= W_IS_INPUT;
1690 in->completions = NULL;
1691 in->completion_flags =
1692 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_HOSTNAMES |
1693 INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES;
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;