TTY: minor optimization of button bar draw.
[midnight-commander.git] / src / widget.c
blob8163b4dbe4215a1ec3e6797edeb9e0ac2480219e
1 /* Widgets for the Midnight Commander
3 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
4 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc.
6 Authors: 1994, 1995 Radek Doulik
7 1994, 1995 Miguel de Icaza
8 1995 Jakub Jelinek
9 1996 Andrej Borsenkow
10 1997 Norbert Warmuth
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28 /** \file widget.c
29 * \brief Source: widgets
32 #include <config.h>
34 #include <assert.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <sys/types.h>
43 #include "global.h"
45 #include "../src/tty/tty.h"
46 #include "../src/tty/color.h"
47 #include "../src/tty/mouse.h"
48 #include "../src/tty/key.h" /* XCTRL and ALT macros */
50 #include "dialog.h"
51 #include "widget.h"
52 #include "../src/mcconfig/mcconfig.h" /* for history loading and saving */
53 #include "wtools.h" /* For common_dialog_repaint() */
54 #include "strutil.h"
56 #define HISTORY_FILE_NAME ".mc/history"
58 /* number of bttons in buttonbar */
59 #define BUTTONBAR_LABELS_NUM 10
61 struct WButtonBar {
62 Widget widget;
63 int visible; /* Is it visible? */
64 int btn_width; /* width of one button */
65 struct {
66 char *text;
67 enum { BBFUNC_NONE, BBFUNC_VOID, BBFUNC_PTR } tag;
68 union {
69 voidfn fn_void;
70 buttonbarfn fn_ptr;
71 } u;
72 void *data;
73 } labels [BUTTONBAR_LABELS_NUM];
76 static void
77 widget_selectcolor (Widget *w, gboolean focused, gboolean hotkey)
79 Dlg_head *h = w->parent;
81 tty_setcolor (hotkey
82 ? (focused
83 ? DLG_HOT_FOCUSC (h)
84 : DLG_HOT_NORMALC (h))
85 : (focused
86 ? DLG_FOCUSC (h)
87 : DLG_NORMALC (h)));
90 struct hotkey_t
91 parse_hotkey (const char *text)
93 struct hotkey_t result;
94 const char *cp, *p;
96 /* search for '&', that is not on the of text */
97 cp = strchr (text, '&');
98 if (cp != NULL && cp[1] != '\0') {
99 result.start = g_strndup (text, cp - text);
101 /* skip '&' */
102 cp++;
103 p = str_cget_next_char (cp);
104 result.hotkey = g_strndup (cp, p - cp);
106 cp = p;
107 result.end = g_strdup (cp);
108 } else {
109 result.start = g_strdup (text);
110 result.hotkey = NULL;
111 result.end = NULL;
114 return result;
116 void
117 release_hotkey (const struct hotkey_t hotkey)
119 g_free (hotkey.start);
120 g_free (hotkey.hotkey);
121 g_free (hotkey.end);
125 hotkey_width (const struct hotkey_t hotkey)
127 int result;
129 result = str_term_width1 (hotkey.start);
130 result+= (hotkey.hotkey != NULL) ? str_term_width1 (hotkey.hotkey) : 0;
131 result+= (hotkey.end != NULL) ? str_term_width1 (hotkey.end) : 0;
132 return result;
135 static void
136 draw_hotkey (Widget *w, const struct hotkey_t hotkey, gboolean focused)
138 widget_selectcolor (w, focused, FALSE);
139 tty_print_string (hotkey.start);
141 if (hotkey.hotkey != NULL) {
142 widget_selectcolor (w, focused, TRUE);
143 tty_print_string (hotkey.hotkey);
144 widget_selectcolor (w, focused, FALSE);
147 if (hotkey.end != NULL)
148 tty_print_string (hotkey.end);
151 static int button_event (Gpm_Event *event, void *);
153 int quote = 0;
155 static cb_ret_t
156 button_callback (Widget *w, widget_msg_t msg, int parm)
158 WButton *b = (WButton *) w;
159 int stop = 0;
160 int off = 0;
161 Dlg_head *h = b->widget.parent;
163 switch (msg) {
164 case WIDGET_HOTKEY:
166 * Don't let the default button steal Enter from the current
167 * button. This is a workaround for the flawed event model
168 * when hotkeys are sent to all widgets before the key is
169 * handled by the current widget.
171 if (parm == '\n' && h->current == &b->widget) {
172 button_callback (w, WIDGET_KEY, ' ');
173 return MSG_HANDLED;
176 if (parm == '\n' && b->flags == DEFPUSH_BUTTON) {
177 button_callback (w, WIDGET_KEY, ' ');
178 return MSG_HANDLED;
181 if (b->text.hotkey != NULL) {
182 if (g_ascii_tolower ((gchar)b->text.hotkey[0]) ==
183 g_ascii_tolower ((gchar)parm)) {
184 button_callback (w, WIDGET_KEY, ' ');
185 return MSG_HANDLED;
188 return MSG_NOT_HANDLED;
190 case WIDGET_KEY:
191 if (parm != ' ' && parm != '\n')
192 return MSG_NOT_HANDLED;
194 if (b->callback)
195 stop = (*b->callback) (b->action);
196 if (!b->callback || stop) {
197 h->ret_value = b->action;
198 dlg_stop (h);
200 return MSG_HANDLED;
202 case WIDGET_CURSOR:
203 switch (b->flags) {
204 case DEFPUSH_BUTTON:
205 off = 3;
206 break;
207 case NORMAL_BUTTON:
208 off = 2;
209 break;
210 case NARROW_BUTTON:
211 off = 1;
212 break;
213 case HIDDEN_BUTTON:
214 default:
215 off = 0;
216 break;
218 widget_move (&b->widget, 0, b->hotpos + off);
219 return MSG_HANDLED;
221 case WIDGET_UNFOCUS:
222 case WIDGET_FOCUS:
223 case WIDGET_DRAW:
224 if (msg == WIDGET_UNFOCUS)
225 b->selected = 0;
226 else if (msg == WIDGET_FOCUS)
227 b->selected = 1;
229 widget_selectcolor (w, b->selected, FALSE);
230 widget_move (w, 0, 0);
232 switch (b->flags) {
233 case DEFPUSH_BUTTON:
234 tty_print_string ("[< ");
235 break;
236 case NORMAL_BUTTON:
237 tty_print_string ("[ ");
238 break;
239 case NARROW_BUTTON:
240 tty_print_string ("[");
241 break;
242 case HIDDEN_BUTTON:
243 default:
244 return MSG_HANDLED;
247 draw_hotkey (w, b->text, b->selected);
249 switch (b->flags) {
250 case DEFPUSH_BUTTON:
251 tty_print_string (" >]");
252 break;
253 case NORMAL_BUTTON:
254 tty_print_string (" ]");
255 break;
256 case NARROW_BUTTON:
257 tty_print_string ("]");
258 break;
260 return MSG_HANDLED;
262 case WIDGET_DESTROY:
263 release_hotkey (b->text);
264 return MSG_HANDLED;
266 default:
267 return default_proc (msg, parm);
271 static int
272 button_event (Gpm_Event *event, void *data)
274 WButton *b = data;
276 if (event->type & (GPM_DOWN|GPM_UP)){
277 Dlg_head *h=b->widget.parent;
278 dlg_select_widget (b);
279 if (event->type & GPM_UP){
280 button_callback ((Widget *) data, WIDGET_KEY, ' ');
281 (*h->callback) (h, DLG_POST_KEY, ' ');
282 return MOU_NORMAL;
285 return MOU_NORMAL;
288 static int
289 button_len (const struct hotkey_t text, unsigned int flags)
291 int ret = hotkey_width (text);
292 switch (flags) {
293 case DEFPUSH_BUTTON:
294 ret += 6;
295 break;
296 case NORMAL_BUTTON:
297 ret += 4;
298 break;
299 case NARROW_BUTTON:
300 ret += 2;
301 break;
302 case HIDDEN_BUTTON:
303 default:
304 return 0;
306 return ret;
309 WButton *
310 button_new (int y, int x, int action, int flags, const char *text,
311 bcback callback)
313 WButton *b = g_new (WButton, 1);
315 b->text = parse_hotkey (text);
317 init_widget (&b->widget, y, x, 1, button_len (b->text, flags),
318 button_callback, button_event);
320 b->action = action;
321 b->flags = flags;
322 b->selected = 0;
323 b->callback = callback;
324 widget_want_hotkey (b->widget, 1);
325 b->hotpos = (b->text.hotkey != NULL) ? str_term_width1 (b->text.start) : -1;
327 return b;
330 const char *
331 button_get_text (WButton *b)
333 if (b->text.hotkey != NULL)
334 return g_strconcat (b->text.start, "&", b->text.hotkey,
335 b->text.end, NULL);
336 else
337 return g_strdup (b->text.start);
340 void
341 button_set_text (WButton *b, const char *text)
344 g_free (b->text);
345 b->text = g_strdup (text);
346 b->widget.cols = button_len (text, b->flags);
347 button_scan_hotkey(b);
349 release_hotkey (b->text);
350 b->text = parse_hotkey (text);
351 b->widget.cols = button_len (b->text, b->flags);
352 dlg_redraw (b->widget.parent);
356 /* Radio button widget */
357 static int radio_event (Gpm_Event *event, void *);
359 static cb_ret_t
360 radio_callback (Widget *w, widget_msg_t msg, int parm)
362 WRadio *r = (WRadio *) w;
363 int i;
364 Dlg_head *h = r->widget.parent;
366 switch (msg) {
367 case WIDGET_HOTKEY:
369 int i, lp = g_ascii_tolower ((gchar)parm);
371 for (i = 0; i < r->count; i++) {
372 if (r->texts[i].hotkey != NULL) {
373 int c = g_ascii_tolower ((gchar)r->texts[i].hotkey[0]);
375 if (c != lp)
376 continue;
377 r->pos = i;
379 /* Take action */
380 radio_callback (w, WIDGET_KEY, ' ');
381 return MSG_HANDLED;
385 return MSG_NOT_HANDLED;
387 case WIDGET_KEY:
388 switch (parm) {
389 case ' ':
390 r->sel = r->pos;
391 (*h->callback) (h, DLG_ACTION, 0);
392 radio_callback (w, WIDGET_FOCUS, ' ');
393 return MSG_HANDLED;
395 case KEY_UP:
396 case KEY_LEFT:
397 if (r->pos > 0) {
398 r->pos--;
399 return MSG_HANDLED;
401 return MSG_NOT_HANDLED;
403 case KEY_DOWN:
404 case KEY_RIGHT:
405 if (r->count - 1 > r->pos) {
406 r->pos++;
407 return MSG_HANDLED;
410 return MSG_NOT_HANDLED;
412 case WIDGET_CURSOR:
413 (*h->callback) (h, DLG_ACTION, 0);
414 radio_callback (w, WIDGET_FOCUS, ' ');
415 widget_move (&r->widget, r->pos, 1);
416 return MSG_HANDLED;
418 case WIDGET_UNFOCUS:
419 case WIDGET_FOCUS:
420 case WIDGET_DRAW:
421 for (i = 0; i < r->count; i++) {
422 const gboolean focused = (i == r->pos && msg == WIDGET_FOCUS);
423 widget_selectcolor (w, focused, FALSE);
424 widget_move (&r->widget, i, 0);
425 tty_print_string ((r->sel == i) ? "(*) " : "( ) ");
426 draw_hotkey (w, r->texts[i], focused);
428 return MSG_HANDLED;
430 case WIDGET_DESTROY:
431 for (i = 0; i < r->count; i++) {
432 release_hotkey (r->texts[i]);
434 g_free (r->texts);
435 return MSG_HANDLED;
437 default:
438 return default_proc (msg, parm);
442 static int
443 radio_event (Gpm_Event *event, void *data)
445 WRadio *r = data;
446 Widget *w = data;
448 if (event->type & (GPM_DOWN|GPM_UP)){
449 Dlg_head *h = r->widget.parent;
451 r->pos = event->y - 1;
452 dlg_select_widget (r);
453 if (event->type & GPM_UP){
454 radio_callback (w, WIDGET_KEY, ' ');
455 radio_callback (w, WIDGET_FOCUS, 0);
456 (*h->callback) (h, DLG_POST_KEY, ' ');
457 return MOU_NORMAL;
460 return MOU_NORMAL;
463 WRadio *
464 radio_new (int y, int x, int count, const char **texts)
466 WRadio *result = g_new (WRadio, 1);
467 int i, max, m;
469 /* Compute the longest string */
470 result->texts = g_new (struct hotkey_t, count);
472 max = 0;
473 for (i = 0; i < count; i++){
474 result->texts[i] = parse_hotkey (texts[i]);
475 m = hotkey_width (result->texts[i]);
476 if (m > max)
477 max = m;
480 init_widget (&result->widget, y, x, count, max, radio_callback, radio_event);
481 result->state = 1;
482 result->pos = 0;
483 result->sel = 0;
484 result->count = count;
485 widget_want_hotkey (result->widget, 1);
487 return result;
491 /* Checkbutton widget */
493 static int check_event (Gpm_Event *event, void *);
495 static cb_ret_t
496 check_callback (Widget *w, widget_msg_t msg, int parm)
498 WCheck *c = (WCheck *) w;
499 Dlg_head *h = c->widget.parent;
501 switch (msg) {
502 case WIDGET_HOTKEY:
503 if (c->text.hotkey != NULL) {
504 if (g_ascii_tolower ((gchar)c->text.hotkey[0]) ==
505 g_ascii_tolower ((gchar)parm)) {
507 check_callback (w, WIDGET_KEY, ' '); /* make action */
508 return MSG_HANDLED;
511 return MSG_NOT_HANDLED;
513 case WIDGET_KEY:
514 if (parm != ' ')
515 return MSG_NOT_HANDLED;
516 c->state ^= C_BOOL;
517 c->state ^= C_CHANGE;
518 (*h->callback) (h, DLG_ACTION, 0);
519 check_callback (w, WIDGET_FOCUS, ' ');
520 return MSG_HANDLED;
522 case WIDGET_CURSOR:
523 widget_move (&c->widget, 0, 1);
524 return MSG_HANDLED;
526 case WIDGET_FOCUS:
527 case WIDGET_UNFOCUS:
528 case WIDGET_DRAW:
529 widget_selectcolor (w, msg == WIDGET_FOCUS, FALSE);
530 widget_move (&c->widget, 0, 0);
531 tty_print_string ((c->state & C_BOOL) ? "[x] " : "[ ] ");
532 draw_hotkey (w, c->text, msg == WIDGET_FOCUS);
533 return MSG_HANDLED;
535 case WIDGET_DESTROY:
536 release_hotkey (c->text);
537 return MSG_HANDLED;
539 default:
540 return default_proc (msg, parm);
544 static int
545 check_event (Gpm_Event *event, void *data)
547 WCheck *c = data;
548 Widget *w = data;
550 if (event->type & (GPM_DOWN|GPM_UP)){
551 Dlg_head *h = c->widget.parent;
553 dlg_select_widget (c);
554 if (event->type & GPM_UP){
555 check_callback (w, WIDGET_KEY, ' ');
556 check_callback (w, WIDGET_FOCUS, 0);
557 (*h->callback) (h, DLG_POST_KEY, ' ');
558 return MOU_NORMAL;
561 return MOU_NORMAL;
564 WCheck *
565 check_new (int y, int x, int state, const char *text)
567 WCheck *c = g_new (WCheck, 1);
569 c->text = parse_hotkey (text);
571 init_widget (&c->widget, y, x, 1, hotkey_width (c->text),
572 check_callback, check_event);
573 c->state = state ? C_BOOL : 0;
574 widget_want_hotkey (c->widget, 1);
576 return c;
580 /* Label widget */
582 static cb_ret_t
583 label_callback (Widget *w, widget_msg_t msg, int parm)
585 WLabel *l = (WLabel *) w;
586 Dlg_head *h = l->widget.parent;
588 switch (msg) {
589 case WIDGET_INIT:
590 return MSG_HANDLED;
592 /* We don't want to get the focus */
593 case WIDGET_FOCUS:
594 return MSG_NOT_HANDLED;
596 case WIDGET_DRAW:
598 char *p = l->text, *q, c = 0;
599 int y = 0;
601 if (!l->text)
602 return MSG_HANDLED;
604 if (l->transparent)
605 tty_setcolor (DEFAULT_COLOR);
606 else
607 tty_setcolor (DLG_NORMALC (h));
609 for (;;) {
610 q = strchr (p, '\n');
611 if (q != NULL) {
612 c = q[0];
613 q[0] = '\0';
616 widget_move (&l->widget, y, 0);
617 tty_print_string (str_fit_to_term (p, l->widget.cols, J_LEFT));
619 if (q == NULL)
620 break;
621 q[0] = c;
622 p = q + 1;
623 y++;
625 return MSG_HANDLED;
628 case WIDGET_DESTROY:
629 g_free (l->text);
630 return MSG_HANDLED;
632 default:
633 return default_proc (msg, parm);
637 void
638 label_set_text (WLabel *label, const char *text)
640 int newcols = label->widget.cols;
641 int newlines;
643 if (label->text && text && !strcmp (label->text, text))
644 return; /* Flickering is not nice */
646 g_free (label->text);
648 if (text != NULL) {
649 label->text = g_strdup (text);
650 if (label->auto_adjust_cols) {
651 str_msg_term_size (text, &newlines, &newcols);
652 if (newcols > label->widget.cols)
653 label->widget.cols = newcols;
654 if (newlines > label->widget.lines)
655 label->widget.lines = newlines;
657 } else label->text = NULL;
659 if (label->widget.parent)
660 label_callback ((Widget *) label, WIDGET_DRAW, 0);
662 if (newcols < label->widget.cols)
663 label->widget.cols = newcols;
666 WLabel *
667 label_new (int y, int x, const char *text)
669 WLabel *l;
670 int cols = 1;
671 int lines = 1;
673 if (text != NULL)
674 str_msg_term_size (text, &lines, &cols);
676 l = g_new (WLabel, 1);
677 init_widget (&l->widget, y, x, lines, cols, label_callback, NULL);
678 l->text = (text != NULL) ? g_strdup (text) : NULL;
679 l->auto_adjust_cols = 1;
680 l->transparent = 0;
681 widget_want_cursor (l->widget, 0);
682 return l;
686 /* Gauge widget (progress indicator) */
687 /* Currently width is hardcoded here for text mode */
688 #define gauge_len 47
690 static cb_ret_t
691 gauge_callback (Widget *w, widget_msg_t msg, int parm)
693 WGauge *g = (WGauge *) w;
694 Dlg_head *h = g->widget.parent;
696 if (msg == WIDGET_INIT)
697 return MSG_HANDLED;
699 /* We don't want to get the focus */
700 if (msg == WIDGET_FOCUS)
701 return MSG_NOT_HANDLED;
703 if (msg == WIDGET_DRAW){
704 widget_move (&g->widget, 0, 0);
705 tty_setcolor (DLG_NORMALC (h));
706 if (!g->shown)
707 tty_printf ("%*s", gauge_len, "");
708 else {
709 int percentage, columns;
710 long total = g->max, done = g->current;
712 if (total <= 0 || done < 0) {
713 done = 0;
714 total = 100;
716 if (done > total)
717 done = total;
718 while (total > 65535) {
719 total /= 256;
720 done /= 256;
722 percentage = (200 * done / total + 1) / 2;
723 columns = (2 * (gauge_len - 7) * done / total + 1) / 2;
724 tty_print_char ('[');
725 tty_setcolor (GAUGE_COLOR);
726 tty_printf ("%*s", (int) columns, "");
727 tty_setcolor (DLG_NORMALC (h));
728 tty_printf ("%*s] %3d%%", (int)(gauge_len - 7 - columns), "", (int) percentage);
730 return MSG_HANDLED;
733 return default_proc (msg, parm);
736 void
737 gauge_set_value (WGauge *g, int max, int current)
739 if (g->current == current && g->max == max)
740 return; /* Do not flicker */
741 if (max == 0)
742 max = 1; /* I do not like division by zero :) */
744 g->current = current;
745 g->max = max;
746 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
749 void
750 gauge_show (WGauge *g, int shown)
752 if (g->shown == shown)
753 return;
754 g->shown = shown;
755 gauge_callback ((Widget *) g, WIDGET_DRAW, 0);
758 WGauge *
759 gauge_new (int y, int x, int shown, int max, int current)
761 WGauge *g = g_new (WGauge, 1);
763 init_widget (&g->widget, y, x, 1, gauge_len, gauge_callback, NULL);
764 g->shown = shown;
765 if (max == 0)
766 max = 1; /* I do not like division by zero :) */
767 g->max = max;
768 g->current = current;
769 widget_want_cursor (g->widget, 0);
770 return g;
774 /* Input widget */
776 /* {{{ history button */
778 #define LARGE_HISTORY_BUTTON 1
780 #ifdef LARGE_HISTORY_BUTTON
781 # define HISTORY_BUTTON_WIDTH 3
782 #else
783 # define HISTORY_BUTTON_WIDTH 1
784 #endif
786 #define should_show_history_button(in) \
787 (in->history && in->field_width > HISTORY_BUTTON_WIDTH * 2 + 1 && in->widget.parent)
789 static void draw_history_button (WInput * in)
791 char c;
792 c = in->history->next ? (in->history->prev ? '|' : 'v') : '^';
793 widget_move (&in->widget, 0, in->field_width - HISTORY_BUTTON_WIDTH);
794 #ifdef LARGE_HISTORY_BUTTON
796 Dlg_head *h;
797 h = in->widget.parent;
798 tty_setcolor (NORMAL_COLOR);
799 tty_print_string ("[ ]");
800 /* Too distracting: tty_setcolor (MARKED_COLOR); */
801 widget_move (&in->widget, 0, in->field_width - HISTORY_BUTTON_WIDTH + 1);
802 tty_print_char (c);
804 #else
805 tty_setcolor (MARKED_COLOR);
806 tty_print_char (c);
807 #endif
810 /* }}} history button */
813 /* Input widgets now have a global kill ring */
814 /* Pointer to killed data */
815 static char *kill_buffer = 0;
817 void
818 update_input (WInput *in, int clear_first)
820 int has_history = 0;
821 int i;
822 int buf_len = str_length (in->buffer);
823 const char *cp;
824 int pw;
826 if (should_show_history_button (in))
827 has_history = HISTORY_BUTTON_WIDTH;
829 if (in->disable_update)
830 return;
832 pw = str_term_width2 (in->buffer, in->point);
834 /* Make the point visible */
835 if ((pw < in->term_first_shown) ||
836 (pw >= in->term_first_shown + in->field_width - has_history)) {
838 in->term_first_shown = pw - (in->field_width / 3);
839 if (in->term_first_shown < 0)
840 in->term_first_shown = 0;
843 /* Adjust the mark */
844 if (in->mark > buf_len)
845 in->mark = buf_len;
847 if (has_history)
848 draw_history_button (in);
850 tty_setcolor (in->color);
852 widget_move (&in->widget, 0, 0);
854 if (!in->is_password) {
855 tty_print_string (str_term_substring (in->buffer, in->term_first_shown,
856 in->field_width - has_history));
857 } else {
858 cp = in->buffer;
859 for (i = -in->term_first_shown; i < in->field_width - has_history; i++){
860 if (i >= 0) {
861 tty_print_char ((cp[0] != '\0') ? '*' : ' ');
863 if (cp[0] != '\0') str_cnext_char (&cp);
867 if (clear_first)
868 in->first = 0;
871 void
872 winput_set_origin (WInput *in, int x, int field_width)
874 in->widget.x = x;
875 in->field_width = in->widget.cols = field_width;
876 update_input (in, 0);
879 /* {{{ history saving and loading */
881 int num_history_items_recorded = 60;
884 This loads and saves the history of an input line to and from the
885 widget. It is called with the widgets history name on creation of the
886 widget, and returns the GList list. It stores histories in the file
887 ~/.mc/history in using the profile code.
889 If def_text is passed as INPUT_LAST_TEXT (to the input_new()
890 function) then input_new assigns the default text to be the last text
891 entered, or "" if not found.
894 GList *
895 history_get (const char *input_name)
897 size_t i;
898 GList *hist = NULL;
899 char *profile;
900 mc_config_t *cfg;
901 char **keys;
902 size_t keys_num = 0;
903 char *this_entry;
905 if (!num_history_items_recorded) /* this is how to disable */
906 return NULL;
907 if (!input_name || !*input_name)
908 return NULL;
910 profile = concat_dir_and_file (home_dir, HISTORY_FILE_NAME);
911 cfg = mc_config_init (profile);
913 /* get number of keys */
914 keys = mc_config_get_keys (cfg, input_name, &keys_num);
915 g_strfreev (keys);
917 for (i = 0; i < keys_num; i++) {
918 char key_name[BUF_TINY];
919 g_snprintf (key_name, sizeof (key_name), "%lu", (unsigned long)i);
920 this_entry = mc_config_get_string (cfg, input_name, key_name, "");
922 if (this_entry && *this_entry)
923 hist = list_append_unique (hist, this_entry);
926 mc_config_deinit (cfg);
927 g_free (profile);
929 /* return pointer to the last entry in the list */
930 return g_list_last (hist);
933 void
934 history_put (const char *input_name, GList *h)
936 int i;
937 char *profile;
938 mc_config_t *cfg;
940 if (!input_name)
941 return;
943 if (!*input_name)
944 return;
946 if (!h)
947 return;
949 if (!num_history_items_recorded) /* this is how to disable */
950 return;
952 profile = concat_dir_and_file (home_dir, HISTORY_FILE_NAME);
954 if ((i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) != -1)
955 close (i);
957 /* Make sure the history is only readable by the user */
958 if (chmod (profile, S_IRUSR | S_IWUSR) == -1 && errno != ENOENT) {
959 g_free (profile);
960 return;
963 /* go to end of list */
964 h = g_list_last (h);
966 /* go back 60 places */
967 for (i = 0; i < num_history_items_recorded - 1 && h->prev; i++)
968 h = g_list_previous (h);
970 cfg = mc_config_init(profile);
972 if (input_name)
973 mc_config_del_group(cfg,input_name);
975 /* dump histories into profile */
976 for (i = 0; h; h = g_list_next (h)) {
977 char *text;
979 text = (char *) h->data;
981 /* We shouldn't have null entries, but let's be sure */
982 if (text && *text) {
983 char key_name[BUF_TINY];
984 g_snprintf (key_name, sizeof (key_name), "%d", i++);
985 mc_config_set_string(cfg,input_name, key_name, text);
989 mc_config_save_file (cfg);
990 mc_config_deinit(cfg);
991 g_free (profile);
994 /* }}} history saving and loading */
997 /* {{{ history display */
999 static const char *
1000 i18n_htitle (void)
1002 return _(" History ");
1005 static void
1006 listbox_fwd (WListbox *l)
1008 if (l->current != l->list->prev)
1009 listbox_select_entry (l, l->current->next);
1010 else
1011 listbox_select_first (l);
1014 char *
1015 show_hist (GList *history, int widget_x, int widget_y)
1017 GList *hi, *z;
1018 size_t maxlen = str_term_width1 (i18n_htitle ()), i, count = 0;
1019 int x, y, w, h;
1020 char *q = NULL, *r = NULL;
1021 Dlg_head *query_dlg;
1022 WListbox *query_list;
1024 z = history;
1025 if (!z)
1026 return NULL;
1028 z = g_list_first (history);
1029 hi = z;
1030 while (hi) {
1031 if ((i = str_term_width1 ((char *) hi->data)) > maxlen)
1032 maxlen = i;
1033 count++;
1034 hi = g_list_next (hi);
1037 y = widget_y;
1038 h = count + 2;
1039 if (h <= y || y > LINES - 6) {
1040 h = min (h, y - 1);
1041 y -= h;
1042 } else {
1043 y++;
1044 h = min (h, LINES - y);
1047 if (widget_x > 2)
1048 x = widget_x - 2;
1049 else
1050 x = 0;
1051 if ((w = maxlen + 4) + x > COLS) {
1052 w = min (w, COLS);
1053 x = COLS - w;
1056 query_dlg =
1057 create_dlg (y, x, h, w, dialog_colors, NULL, "[History-query]",
1058 i18n_htitle (), DLG_COMPACT);
1059 query_list = listbox_new (1, 1, h - 2, w - 2, NULL);
1060 add_widget (query_dlg, query_list);
1062 if (y < widget_y) {
1063 /* traverse */
1064 hi = z;
1065 while (hi) {
1066 listbox_add_item (query_list, LISTBOX_APPEND_AT_END,
1067 0, (char *) hi->data, NULL);
1068 hi = g_list_next (hi);
1070 listbox_select_last (query_list);
1071 } else {
1072 /* traverse backwards */
1073 hi = g_list_last (history);
1074 while (hi) {
1075 listbox_add_item (query_list, LISTBOX_APPEND_AT_END,
1076 0, (char *) hi->data, NULL);
1077 hi = g_list_previous (hi);
1081 if (run_dlg (query_dlg) != B_CANCEL) {
1082 listbox_get_current (query_list, &q, NULL);
1083 if (q != NULL)
1084 r = g_strdup (q);
1086 destroy_dlg (query_dlg);
1087 return r;
1090 static void
1091 do_show_hist (WInput * in)
1093 char *r;
1094 r = show_hist (in->history, in->widget.x, in->widget.y);
1095 if (r) {
1096 assign_text (in, r);
1097 g_free (r);
1101 /* }}} history display */
1103 static void
1104 input_destroy (WInput *in)
1106 if (!in){
1107 fprintf (stderr, "Internal error: null Input *\n");
1108 exit (1);
1111 new_input (in);
1113 if (in->history){
1114 if (!in->is_password) /* don't save passwords ;-) */
1115 history_put (in->history_name, in->history);
1117 in->history = g_list_first (in->history);
1118 g_list_foreach (in->history, (GFunc) g_free, NULL);
1119 g_list_free (in->history);
1122 g_free (in->buffer);
1123 free_completions (in);
1124 g_free (in->history_name);
1127 void
1128 input_disable_update (WInput *in)
1130 in->disable_update++;
1133 void
1134 input_enable_update (WInput *in)
1136 in->disable_update--;
1137 update_input (in, 0);
1140 #define ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
1143 push_history (WInput *in, const char *text)
1145 static int i18n;
1146 /* input widget where urls with passwords are entered without any
1147 vfs prefix */
1148 static const char *password_input_fields[] = {
1149 N_(" Link to a remote machine "),
1150 N_(" FTP to machine "),
1151 N_(" SMB link to machine ")
1153 char *t;
1154 const char *p;
1155 size_t i;
1157 if (!i18n) {
1158 i18n = 1;
1159 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1160 password_input_fields[i] = _(password_input_fields[i]);
1163 for (p = text; *p == ' ' || *p == '\t'; p++);
1164 if (!*p)
1165 return 0;
1167 if (in->history) {
1168 /* Avoid duplicated entries */
1169 in->history = g_list_last (in->history);
1170 if (!strcmp ((char *) in->history->data, text))
1171 return 1;
1174 t = g_strdup (text);
1176 if (in->history_name) {
1177 p = in->history_name + 3;
1178 for (i = 0; i < ELEMENTS (password_input_fields); i++)
1179 if (strcmp (p, password_input_fields[i]) == 0)
1180 break;
1181 if (i < ELEMENTS (password_input_fields))
1182 strip_password (t, 0);
1183 else
1184 strip_password (t, 1);
1187 in->history = list_append_unique (in->history, t);
1188 in->need_push = 0;
1190 return 2;
1193 #undef ELEMENTS
1195 /* Cleans the input line and adds the current text to the history */
1196 void
1197 new_input (WInput *in)
1199 if (in->buffer)
1200 push_history (in, in->buffer);
1201 in->need_push = 1;
1202 in->buffer[0] = '\0';
1203 in->point = 0;
1204 in->charpoint = 0;
1205 in->mark = 0;
1206 free_completions (in);
1207 update_input (in, 0);
1210 static void
1211 move_buffer_backward (WInput *in, int start, int end)
1213 int i, pos, len;
1214 int str_len = str_length (in->buffer);
1215 if (start >= str_len || end > str_len + 1) return;
1217 pos = str_offset_to_pos (in->buffer, start);
1218 len = str_offset_to_pos (in->buffer, end) - pos;
1220 for (i = pos; in->buffer[i + len - 1]; i++)
1221 in->buffer[i] = in->buffer[i + len];
1224 static cb_ret_t
1225 insert_char (WInput *in, int c_code)
1227 size_t i;
1228 int res;
1230 if (c_code == -1)
1231 return MSG_NOT_HANDLED;
1233 if (in->charpoint >= MB_LEN_MAX)
1234 return MSG_HANDLED;
1236 in->charbuf[in->charpoint] = c_code;
1237 in->charpoint++;
1239 res = str_is_valid_char (in->charbuf, in->charpoint);
1240 if (res < 0) {
1241 if (res != -2)
1242 in->charpoint = 0; /* broken multibyte char, skip */
1243 return MSG_HANDLED;
1246 in->need_push = 1;
1247 if (strlen (in->buffer) + 1 + in->charpoint >= in->current_max_size){
1248 /* Expand the buffer */
1249 size_t new_length = in->current_max_size +
1250 in->field_width + in->charpoint;
1251 char *narea = g_try_renew (char, in->buffer, new_length);
1252 if (narea){
1253 in->buffer = narea;
1254 in->current_max_size = new_length;
1258 if (strlen (in->buffer) + in->charpoint < in->current_max_size) {
1259 /* bytes from begin */
1260 size_t ins_point = str_offset_to_pos (in->buffer, in->point);
1261 /* move chars */
1262 size_t rest_bytes = strlen (in->buffer + ins_point);
1264 for (i = rest_bytes + 1; i > 0; i--)
1265 in->buffer[ins_point + i + in->charpoint - 1] =
1266 in->buffer[ins_point + i - 1];
1268 memcpy(in->buffer + ins_point, in->charbuf, in->charpoint);
1269 in->point++;
1272 in->charpoint = 0;
1273 return MSG_HANDLED;
1276 static void
1277 beginning_of_line (WInput *in)
1279 in->point = 0;
1280 in->charpoint = 0;
1283 static void
1284 end_of_line (WInput *in)
1286 in->point = str_length (in->buffer);
1287 in->charpoint = 0;
1290 static void
1291 backward_char (WInput *in)
1293 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1295 if (in->point > 0) {
1296 in->point-= str_cprev_noncomb_char (&act, in->buffer);
1298 in->charpoint = 0;
1301 static void
1302 forward_char (WInput *in)
1304 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1305 if (act[0] != '\0') {
1306 in->point+= str_cnext_noncomb_char (&act);
1308 in->charpoint = 0;
1311 static void
1312 forward_word (WInput * in)
1314 const char *p = in->buffer + str_offset_to_pos (in->buffer, in->point);
1316 while (p[0] != '\0' && (str_isspace (p) || str_ispunct (p))) {
1317 str_cnext_char (&p);
1318 in->point++;
1320 while (p[0] != '\0' && !str_isspace (p) && !str_ispunct (p)) {
1321 str_cnext_char (&p);
1322 in->point++;
1326 static void
1327 backward_word (WInput *in)
1329 const char *p = in->buffer + str_offset_to_pos (in->buffer, in->point);
1331 while ((p != in->buffer) && (p[0] == '\0')) {
1332 p--;
1333 in->point--;
1336 while ((p != in->buffer) && (str_isspace (p) || str_ispunct (p))) {
1337 str_cprev_char (&p);
1338 in->point--;
1340 while ((p != in->buffer) && !str_isspace (p) && !str_ispunct (p)) {
1341 str_cprev_char (&p);
1342 in->point--;
1346 static void
1347 key_left (WInput *in)
1349 backward_char (in);
1352 static void
1353 key_ctrl_left (WInput *in)
1355 backward_word (in);
1358 static void
1359 key_right (WInput *in)
1361 forward_char (in);
1364 static void
1365 key_ctrl_right (WInput *in)
1367 forward_word (in);
1369 static void
1370 backward_delete (WInput *in)
1372 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1373 int start;
1375 if (in->point == 0)
1376 return;
1378 start = in->point - str_cprev_noncomb_char (&act, in->buffer);
1379 move_buffer_backward(in, start, in->point);
1380 in->charpoint = 0;
1381 in->need_push = 1;
1382 in->point = start;
1385 static void
1386 delete_char (WInput *in)
1388 const char *act = in->buffer + str_offset_to_pos (in->buffer, in->point);
1389 int end = in->point;
1391 end+= str_cnext_noncomb_char (&act);
1393 move_buffer_backward(in, in->point, end);
1394 in->charpoint = 0;
1395 in->need_push = 1;
1398 static void
1399 copy_region (WInput *in, int x_first, int x_last)
1401 int first = min (x_first, x_last);
1402 int last = max (x_first, x_last);
1404 if (last == first)
1405 return;
1407 g_free (kill_buffer);
1409 first = str_offset_to_pos (in->buffer, first);
1410 last = str_offset_to_pos (in->buffer, last);
1412 kill_buffer = g_strndup(in->buffer + first, last - first);
1415 static void
1416 delete_region (WInput *in, int x_first, int x_last)
1418 int first = min (x_first, x_last);
1419 int last = max (x_first, x_last);
1420 size_t len;
1422 in->point = first;
1423 in->mark = first;
1424 last = str_offset_to_pos (in->buffer, last);
1425 first = str_offset_to_pos (in->buffer, first);
1426 len = strlen (&in->buffer[last]) + 1;
1427 memmove (&in->buffer[first], &in->buffer[last], len);
1428 in->charpoint = 0;
1429 in->need_push = 1;
1432 static void
1433 kill_word (WInput *in)
1435 int old_point = in->point;
1436 int new_point;
1438 forward_word (in);
1439 new_point = in->point;
1440 in->point = old_point;
1442 copy_region (in, old_point, new_point);
1443 delete_region (in, old_point, new_point);
1444 in->need_push = 1;
1445 in->charpoint = 0;
1446 in->charpoint = 0;
1449 static void
1450 back_kill_word (WInput *in)
1452 int old_point = in->point;
1453 int new_point;
1455 backward_word (in);
1456 new_point = in->point;
1457 in->point = old_point;
1459 copy_region (in, old_point, new_point);
1460 delete_region (in, old_point, new_point);
1461 in->need_push = 1;
1464 static void
1465 set_mark (WInput *in)
1467 in->mark = in->point;
1470 static void
1471 kill_save (WInput *in)
1473 copy_region (in, in->mark, in->point);
1476 static void
1477 kill_region (WInput *in)
1479 kill_save (in);
1480 delete_region (in, in->point, in->mark);
1483 static void
1484 yank (WInput *in)
1486 char *p;
1488 if (!kill_buffer)
1489 return;
1490 in->charpoint = 0;
1491 for (p = kill_buffer; *p; p++)
1492 insert_char (in, *p);
1493 in->charpoint = 0;
1496 static void
1497 kill_line (WInput *in)
1499 int chp = str_offset_to_pos (in->buffer, in->point);
1500 g_free (kill_buffer);
1501 kill_buffer = g_strdup (&in->buffer[chp]);
1502 in->buffer[chp] = '\0';
1503 in->charpoint = 0;
1506 void
1507 assign_text (WInput *in, const char *text)
1509 free_completions (in);
1510 g_free (in->buffer);
1511 in->buffer = g_strdup (text); /* was in->buffer->text */
1512 in->current_max_size = strlen (in->buffer) + 1;
1513 in->point = str_length (in->buffer);
1514 in->mark = 0;
1515 in->need_push = 1;
1516 in->charpoint = 0;
1519 static void
1520 hist_prev (WInput *in)
1522 if (!in->history)
1523 return;
1525 if (in->need_push) {
1526 switch (push_history (in, in->buffer)) {
1527 case 2:
1528 in->history = g_list_previous (in->history);
1529 break;
1530 case 1:
1531 if (in->history->prev)
1532 in->history = g_list_previous (in->history);
1533 break;
1534 case 0:
1535 break;
1537 } else if (in->history->prev)
1538 in->history = g_list_previous (in->history);
1539 else
1540 return;
1541 assign_text (in, (char *) in->history->data);
1542 in->need_push = 0;
1545 static void
1546 hist_next (WInput *in)
1548 if (in->need_push) {
1549 switch (push_history (in, in->buffer)) {
1550 case 2:
1551 assign_text (in, "");
1552 return;
1553 case 0:
1554 return;
1558 if (!in->history)
1559 return;
1561 if (!in->history->next) {
1562 assign_text (in, "");
1563 return;
1566 in->history = g_list_next (in->history);
1567 assign_text (in, (char *) in->history->data);
1568 in->need_push = 0;
1571 static const struct {
1572 int key_code;
1573 void (*fn)(WInput *in);
1574 } input_map [] = {
1575 /* Motion */
1576 { XCTRL('a'), beginning_of_line },
1577 { KEY_HOME, beginning_of_line },
1578 { KEY_A1, beginning_of_line },
1579 { ALT ('<'), beginning_of_line },
1580 { XCTRL('e'), end_of_line },
1581 { KEY_END, end_of_line },
1582 { KEY_C1, end_of_line },
1583 { ALT ('>'), end_of_line },
1584 { KEY_LEFT, key_left },
1585 { KEY_LEFT | KEY_M_CTRL, key_ctrl_left },
1586 { XCTRL('b'), backward_char },
1587 { ALT('b'), backward_word },
1588 { KEY_RIGHT, key_right },
1589 { KEY_RIGHT | KEY_M_CTRL, key_ctrl_right },
1590 { XCTRL('f'), forward_char },
1591 { ALT('f'), forward_word },
1593 /* Editing */
1594 { KEY_BACKSPACE, backward_delete },
1595 { KEY_DC, delete_char },
1596 { ALT('d'), kill_word },
1597 { ALT(KEY_BACKSPACE), back_kill_word },
1599 /* Region manipulation */
1600 { 0, set_mark },
1601 { XCTRL('w'), kill_region },
1602 { ALT('w'), kill_save },
1603 { XCTRL('y'), yank },
1604 { XCTRL('k'), kill_line },
1606 /* History */
1607 { ALT('p'), hist_prev },
1608 { ALT('n'), hist_next },
1609 { ALT('h'), do_show_hist },
1611 /* Completion */
1612 { ALT('\t'), complete },
1614 { 0, 0 }
1617 /* This function is a test for a special input key used in complete.c */
1618 /* Returns 0 if it is not a special key, 1 if it is a non-complete key
1619 and 2 if it is a complete key */
1621 is_in_input_map (WInput *in, int c_code)
1623 int i;
1625 (void) in;
1627 for (i = 0; input_map [i].fn; i++)
1628 if (c_code == input_map [i].key_code) {
1629 if (input_map [i].fn == complete)
1630 return 2;
1631 else
1632 return 1;
1634 return 0;
1637 static void
1638 port_region_marked_for_delete (WInput *in)
1640 in->buffer[0] = '\0';
1641 in->point = 0;
1642 in->first = 0;
1643 in->charpoint = 0;
1646 cb_ret_t
1647 handle_char (WInput *in, int c_code)
1649 cb_ret_t v;
1650 int i;
1652 v = MSG_NOT_HANDLED;
1654 if (quote){
1655 free_completions (in);
1656 v = insert_char (in, c_code);
1657 update_input (in, 1);
1658 quote = 0;
1659 return v;
1662 for (i = 0; input_map [i].fn; i++){
1663 if (c_code == input_map [i].key_code){
1664 if (input_map [i].fn != complete)
1665 free_completions (in);
1666 (*input_map [i].fn)(in);
1667 v = MSG_HANDLED;
1668 break;
1671 if (!input_map [i].fn){
1672 if (c_code > 255)
1673 return MSG_NOT_HANDLED;
1674 if (in->first){
1675 port_region_marked_for_delete (in);
1677 free_completions (in);
1678 v = insert_char (in, c_code);
1680 update_input (in, 1);
1681 return v;
1684 /* Inserts text in input line */
1685 void
1686 stuff (WInput *in, const char *text, int insert_extra_space)
1688 input_disable_update (in);
1689 while (*text)
1690 handle_char (in, *text++);
1691 if (insert_extra_space)
1692 handle_char (in, ' ');
1693 input_enable_update (in);
1694 update_input (in, 1);
1697 void
1698 input_set_point (WInput *in, int pos)
1700 int max_pos = str_length (in->buffer);
1702 if (pos > max_pos)
1703 pos = max_pos;
1704 if (pos != in->point)
1705 free_completions (in);
1706 in->point = pos;
1707 in->charpoint = 0;
1708 update_input (in, 1);
1711 cb_ret_t
1712 input_callback (Widget *w, widget_msg_t msg, int parm)
1714 WInput *in = (WInput *) w;
1715 cb_ret_t v;
1717 switch (msg) {
1718 case WIDGET_KEY:
1719 if (parm == XCTRL ('q')) {
1720 quote = 1;
1721 v = handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
1722 quote = 0;
1723 return v;
1726 /* Keys we want others to handle */
1727 if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
1728 || parm == KEY_F (10) || parm == XCTRL ('g') || parm == '\n')
1729 return MSG_NOT_HANDLED;
1731 /* When pasting multiline text, insert literal Enter */
1732 if ((parm & ~KEY_M_MASK) == '\n') {
1733 quote = 1;
1734 v = handle_char (in, '\n');
1735 quote = 0;
1736 return v;
1739 return handle_char (in, parm);
1741 case WIDGET_FOCUS:
1742 case WIDGET_UNFOCUS:
1743 case WIDGET_DRAW:
1744 update_input (in, 0);
1745 return MSG_HANDLED;
1747 case WIDGET_CURSOR:
1748 widget_move (&in->widget, 0, str_term_width2 (in->buffer, in->point)
1749 - in->term_first_shown);
1750 return MSG_HANDLED;
1752 case WIDGET_DESTROY:
1753 input_destroy (in);
1754 return MSG_HANDLED;
1756 default:
1757 return default_proc (msg, parm);
1761 static int
1762 input_event (Gpm_Event * event, void *data)
1764 WInput *in = data;
1766 if (event->type & (GPM_DOWN | GPM_DRAG)) {
1767 dlg_select_widget (in);
1769 if (event->x >= in->field_width - HISTORY_BUTTON_WIDTH + 1
1770 && should_show_history_button (in)) {
1771 do_show_hist (in);
1772 } else {
1773 in->point = str_length (in->buffer);
1774 if (event->x + in->term_first_shown - 1 <
1775 str_term_width1 (in->buffer))
1777 in->point = str_column_to_pos (in->buffer, event->x
1778 + in->term_first_shown - 1);
1781 update_input (in, 1);
1783 return MOU_NORMAL;
1786 WInput *
1787 input_new (int y, int x, int color, int width, const char *def_text,
1788 const char *histname, INPUT_COMPLETE_FLAGS completion_flags)
1790 WInput *in = g_new (WInput, 1);
1791 int initial_buffer_len;
1793 init_widget (&in->widget, y, x, 1, width, input_callback, input_event);
1795 /* history setup */
1796 in->history = NULL;
1797 in->history_name = 0;
1798 if (histname) {
1799 if (*histname) {
1800 in->history_name = g_strdup (histname);
1801 in->history = history_get (histname);
1805 if (def_text == NULL)
1806 def_text = "";
1808 if (def_text == INPUT_LAST_TEXT) {
1809 def_text = "";
1810 if (in->history)
1811 if (in->history->data)
1812 def_text = (char *) in->history->data;
1814 initial_buffer_len = 1 + max ((size_t) width, strlen (def_text));
1815 in->widget.options |= W_IS_INPUT;
1816 in->completions = NULL;
1817 in->completion_flags = completion_flags;
1818 in->current_max_size = initial_buffer_len;
1819 in->buffer = g_new (char, initial_buffer_len);
1820 in->color = color;
1821 in->field_width = width;
1822 in->first = 1;
1823 in->term_first_shown = 0;
1824 in->disable_update = 0;
1825 in->mark = 0;
1826 in->need_push = 1;
1827 in->is_password = 0;
1829 strcpy (in->buffer, def_text);
1830 in->point = str_length (in->buffer);
1831 in->charpoint = 0;
1832 return in;
1835 /* Listbox widget */
1837 /* Should draw the scrollbar, but currently draws only
1838 * indications that there is more information
1840 static int listbox_cdiff (WLEntry *s, WLEntry *e);
1842 static void
1843 listbox_drawscroll (WListbox *l)
1845 int line;
1846 int i, top;
1847 int max_line = l->height-1;
1849 /* Are we at the top? */
1850 widget_move (&l->widget, 0, l->width);
1851 if (l->list == l->top)
1852 tty_print_one_vline ();
1853 else
1854 tty_print_char ('^');
1856 /* Are we at the bottom? */
1857 widget_move (&l->widget, max_line, l->width);
1858 top = listbox_cdiff (l->list, l->top);
1859 if ((top + l->height == l->count) || l->height >= l->count)
1860 tty_print_one_vline ();
1861 else
1862 tty_print_char ('v');
1864 /* Now draw the nice relative pointer */
1865 if (l->count)
1866 line = 1+ ((l->pos * (l->height-2)) / l->count);
1867 else
1868 line = 0;
1870 for (i = 1; i < max_line; i++){
1871 widget_move (&l->widget, i, l->width);
1872 if (i != line)
1873 tty_print_one_vline ();
1874 else
1875 tty_print_char ('*');
1879 static void
1880 listbox_draw (WListbox *l, int focused)
1882 WLEntry *e;
1883 int i;
1884 int sel_line;
1885 Dlg_head *h = l->widget.parent;
1886 int normalc = DLG_NORMALC (h);
1887 int selc;
1888 const char *text;
1890 if (focused){
1891 selc = DLG_FOCUSC (h);
1892 } else {
1893 selc = DLG_HOT_FOCUSC (h);
1895 sel_line = -1;
1897 for (e = l->top, i = 0; (i < l->height); i++){
1899 /* Display the entry */
1900 if (e == l->current && sel_line == -1){
1901 sel_line = i;
1902 tty_setcolor (selc);
1903 } else
1904 tty_setcolor (normalc);
1906 widget_move (&l->widget, i, 0);
1908 if ((i > 0 && e == l->list) || !l->list)
1909 text = "";
1910 else {
1911 text = e->text;
1912 e = e->next;
1914 tty_print_string (str_fit_to_term (text, l->width - 2, J_LEFT_FIT));
1916 l->cursor_y = sel_line;
1918 if (l->scrollbar) {
1919 tty_setcolor (normalc);
1920 listbox_drawscroll (l);
1924 /* Returns the number of items between s and e,
1925 must be on the same linked list */
1926 static int
1927 listbox_cdiff (WLEntry *s, WLEntry *e)
1929 int count;
1931 for (count = 0; s != e; count++)
1932 s = s->next;
1933 return count;
1936 static WLEntry *
1937 listbox_check_hotkey (WListbox *l, int key)
1939 int i;
1940 WLEntry *e;
1942 i = 0;
1943 e = l->list;
1944 if (!e)
1945 return NULL;
1947 while (1) {
1949 /* If we didn't find anything, return */
1950 if (i && e == l->list)
1951 return NULL;
1953 if (e->hotkey == key)
1954 return e;
1956 i++;
1957 e = e->next;
1961 /* Selects the last entry and scrolls the list to the bottom */
1962 void
1963 listbox_select_last (WListbox *l)
1965 unsigned int i;
1966 l->current = l->top = l->list->prev;
1967 for (i = min (l->height - 1, l->count - 1); i; i--)
1968 l->top = l->top->prev;
1969 l->pos = l->count - 1;
1972 /* Selects the first entry and scrolls the list to the top */
1973 void
1974 listbox_select_first (WListbox *l)
1976 l->current = l->top = l->list;
1977 l->pos = 0;
1980 void
1981 listbox_remove_list (WListbox *l)
1983 WLEntry *p, *q;
1985 if (!l->count)
1986 return;
1988 p = l->list;
1990 while (l->count--) {
1991 q = p->next;
1992 g_free (p->text);
1993 g_free (p);
1994 p = q;
1996 l->pos = l->count = 0;
1997 l->list = l->top = l->current = 0;
2001 * bor 30.10.96: added force flag to remove *last* entry as well
2002 * bor 30.10.96: corrected selection bug if last entry was removed
2005 void
2006 listbox_remove_current (WListbox *l, int force)
2008 WLEntry *p;
2010 /* Ok, note: this won't allow for emtpy lists */
2011 if (!force && (!l->count || l->count == 1))
2012 return;
2014 l->count--;
2015 p = l->current;
2017 if (l->count) {
2018 l->current->next->prev = l->current->prev;
2019 l->current->prev->next = l->current->next;
2020 if (p->next == l->list) {
2021 l->current = p->prev;
2022 l->pos--;
2024 else
2025 l->current = p->next;
2027 if (p == l->list)
2028 l->list = l->top = p->next;
2029 } else {
2030 l->pos = 0;
2031 l->list = l->top = l->current = 0;
2034 g_free (p->text);
2035 g_free (p);
2038 /* Makes *e the selected entry (sets current and pos) */
2039 void
2040 listbox_select_entry (WListbox *l, WLEntry *dest)
2042 WLEntry *e;
2043 int pos;
2044 int top_seen;
2046 top_seen = 0;
2048 /* Special case */
2049 for (pos = 0, e = l->list; pos < l->count; e = e->next, pos++){
2051 if (e == l->top)
2052 top_seen = 1;
2054 if (e == dest){
2055 l->current = e;
2056 if (top_seen){
2057 while (listbox_cdiff (l->top, l->current) >= l->height)
2058 l->top = l->top->next;
2059 } else {
2060 l->top = l->current;
2062 l->pos = pos;
2063 return;
2066 /* If we are unable to find it, set decent values */
2067 l->current = l->top = l->list;
2068 l->pos = 0;
2071 /* Selects from base the pos element */
2072 static WLEntry *
2073 listbox_select_pos (WListbox *l, WLEntry *base, int pos)
2075 WLEntry *last = l->list->prev;
2077 if (base == last)
2078 return last;
2079 while (pos--){
2080 base = base->next;
2081 if (base == last)
2082 break;
2084 return base;
2087 static void
2088 listbox_back (WListbox *l)
2090 if (l->pos != 0)
2091 listbox_select_entry (l, l->current->prev);
2092 else
2093 listbox_select_last (l);
2096 /* Return MSG_HANDLED if we want a redraw */
2097 static cb_ret_t
2098 listbox_key (WListbox *l, int key)
2100 int i;
2102 cb_ret_t j = MSG_NOT_HANDLED;
2104 if (!l->list)
2105 return MSG_NOT_HANDLED;
2107 switch (key){
2108 case KEY_HOME:
2109 case KEY_A1:
2110 case ALT ('<'):
2111 listbox_select_first (l);
2112 return MSG_HANDLED;
2114 case KEY_END:
2115 case KEY_C1:
2116 case ALT ('>'):
2117 listbox_select_last (l);
2118 return MSG_HANDLED;
2120 case XCTRL('p'):
2121 case KEY_UP:
2122 listbox_back (l);
2123 return MSG_HANDLED;
2125 case XCTRL('n'):
2126 case KEY_DOWN:
2127 listbox_fwd (l);
2128 return MSG_HANDLED;
2130 case KEY_NPAGE:
2131 case XCTRL('v'):
2132 for (i = 0; ((i < l->height - 1)
2133 && (l->current != l->list->prev)); i++) {
2134 listbox_fwd (l);
2135 j = MSG_HANDLED;
2137 return j;
2139 case KEY_PPAGE:
2140 case ALT('v'):
2141 for (i = 0; ((i < l->height - 1)
2142 && (l->current != l->list)); i++) {
2143 listbox_back (l);
2144 j = MSG_HANDLED;
2146 return j;
2148 return MSG_NOT_HANDLED;
2151 static void
2152 listbox_destroy (WListbox *l)
2154 WLEntry *n, *p = l->list;
2155 int i;
2157 for (i = 0; i < l->count; i++){
2158 n = p->next;
2159 g_free (p->text);
2160 g_free (p);
2161 p = n;
2165 static cb_ret_t
2166 listbox_callback (Widget *w, widget_msg_t msg, int parm)
2168 WListbox *l = (WListbox *) w;
2169 cb_ret_t ret_code;
2170 WLEntry *e;
2171 Dlg_head *h = l->widget.parent;
2173 switch (msg) {
2174 case WIDGET_INIT:
2175 return MSG_HANDLED;
2177 case WIDGET_HOTKEY:
2178 if ((e = listbox_check_hotkey (l, parm)) != NULL) {
2179 int action;
2181 listbox_select_entry (l, e);
2183 (*h->callback) (h, DLG_ACTION, l->pos);
2185 if (l->cback)
2186 action = (*l->cback) (l);
2187 else
2188 action = LISTBOX_DONE;
2190 if (action == LISTBOX_DONE) {
2191 h->ret_value = B_ENTER;
2192 dlg_stop (h);
2194 return MSG_HANDLED;
2195 } else
2196 return MSG_NOT_HANDLED;
2198 case WIDGET_KEY:
2199 if ((ret_code = listbox_key (l, parm)) != MSG_NOT_HANDLED) {
2200 listbox_draw (l, 1);
2201 (*h->callback) (h, DLG_ACTION, l->pos);
2203 return ret_code;
2205 case WIDGET_CURSOR:
2206 widget_move (&l->widget, l->cursor_y, 0);
2207 (*h->callback) (h, DLG_ACTION, l->pos);
2208 return MSG_HANDLED;
2210 case WIDGET_FOCUS:
2211 case WIDGET_UNFOCUS:
2212 case WIDGET_DRAW:
2213 listbox_draw (l, msg != WIDGET_UNFOCUS);
2214 return MSG_HANDLED;
2216 case WIDGET_DESTROY:
2217 listbox_destroy (l);
2218 return MSG_HANDLED;
2220 default:
2221 return default_proc (msg, parm);
2225 static int
2226 listbox_event (Gpm_Event *event, void *data)
2228 WListbox *l = data;
2229 Widget *w = data;
2230 int i;
2232 Dlg_head *h = l->widget.parent;
2234 /* Single click */
2235 if (event->type & GPM_DOWN)
2236 dlg_select_widget (l);
2237 if (!l->list)
2238 return MOU_NORMAL;
2239 if (event->type & (GPM_DOWN | GPM_DRAG)) {
2240 if (event->x < 0 || event->x >= l->width)
2241 return MOU_REPEAT;
2242 if (event->y < 1)
2243 for (i = -event->y; i >= 0; i--)
2244 listbox_back (l);
2245 else if (event->y > l->height)
2246 for (i = event->y - l->height; i > 0; i--)
2247 listbox_fwd (l);
2248 else
2249 listbox_select_entry (l,
2250 listbox_select_pos (l, l->top,
2251 event->y - 1));
2253 /* We need to refresh ourselves since the dialog manager doesn't */
2254 /* know about this event */
2255 listbox_callback (w, WIDGET_DRAW, 0);
2256 tty_refresh ();
2257 return MOU_REPEAT;
2260 /* Double click */
2261 if ((event->type & (GPM_DOUBLE | GPM_UP)) == (GPM_UP | GPM_DOUBLE)) {
2262 int action;
2264 if (event->x < 0 || event->x >= l->width)
2265 return MOU_NORMAL;
2266 if (event->y < 1 || event->y > l->height)
2267 return MOU_NORMAL;
2269 dlg_select_widget (l);
2270 listbox_select_entry (l,
2271 listbox_select_pos (l, l->top,
2272 event->y - 1));
2274 if (l->cback)
2275 action = (*l->cback) (l);
2276 else
2277 action = LISTBOX_DONE;
2279 if (action == LISTBOX_DONE) {
2280 h->ret_value = B_ENTER;
2281 dlg_stop (h);
2282 return MOU_NORMAL;
2285 return MOU_NORMAL;
2288 WListbox *
2289 listbox_new (int y, int x, int height, int width, lcback callback)
2291 WListbox *l = g_new (WListbox, 1);
2293 init_widget (&l->widget, y, x, height, width,
2294 listbox_callback, listbox_event);
2296 l->list = l->top = l->current = 0;
2297 l->pos = 0;
2298 l->width = width;
2299 if (height <= 0)
2300 l->height = 1;
2301 else
2302 l->height = height;
2303 l->count = 0;
2304 l->cback = callback;
2305 l->allow_duplicates = 1;
2306 l->scrollbar = !tty_is_slow ();
2307 widget_want_hotkey (l->widget, 1);
2309 return l;
2312 /* Listbox item adding function. They still lack a lot of functionality */
2313 /* any takers? */
2314 /* 1.11.96 bor: added pos argument to control placement of new entry */
2315 static void
2316 listbox_append_item (WListbox *l, WLEntry *e, enum append_pos pos)
2318 if (!l->list){
2319 l->list = e;
2320 l->top = e;
2321 l->current = e;
2322 e->next = l->list;
2323 e->prev = l->list;
2324 } else if (pos == LISTBOX_APPEND_AT_END) {
2325 e->next = l->list;
2326 e->prev = l->list->prev;
2327 l->list->prev->next = e;
2328 l->list->prev = e;
2329 } else if (pos == LISTBOX_APPEND_BEFORE){
2330 e->next = l->current;
2331 e->prev = l->current->prev;
2332 l->current->prev->next = e;
2333 l->current->prev = e;
2334 if (l->list == l->current) { /* move list one position down */
2335 l->list = e;
2336 l->top = e;
2338 } else if (pos == LISTBOX_APPEND_AFTER) {
2339 e->prev = l->current;
2340 e->next = l->current->next;
2341 l->current->next->prev = e;
2342 l->current->next = e;
2343 } else if (pos == LISTBOX_APPEND_SORTED) {
2344 WLEntry *w = l->list;
2346 while (w->next != l->list && strcmp (e->text, w->text) > 0)
2347 w = w->next;
2348 if (w->next == l->list) {
2349 e->prev = w;
2350 e->next = l->list;
2351 w->next = e;
2352 l->list->prev = e;
2353 } else {
2354 e->next = w;
2355 e->prev = w->prev;
2356 w->prev->next = e;
2357 w->prev = e;
2360 l->count++;
2363 char *
2364 listbox_add_item (WListbox *l, enum append_pos pos, int hotkey,
2365 const char *text, void *data)
2367 WLEntry *entry;
2369 if (!l)
2370 return NULL;
2372 if (!l->allow_duplicates)
2373 if (listbox_search_text (l, text))
2374 return NULL;
2376 entry = g_new (WLEntry, 1);
2377 entry->text = g_strdup (text);
2378 entry->data = data;
2379 entry->hotkey = hotkey;
2381 listbox_append_item (l, entry, pos);
2383 return entry->text;
2386 /* Selects the nth entry in the listbox */
2387 void
2388 listbox_select_by_number (WListbox *l, int n)
2390 if (l->list != NULL)
2391 listbox_select_entry (l, listbox_select_pos (l, l->list, n));
2394 WLEntry *
2395 listbox_search_text (WListbox *l, const char *text)
2397 WLEntry *e;
2399 e = l->list;
2400 if (!e)
2401 return NULL;
2403 do {
2404 if(!strcmp (e->text, text))
2405 return e;
2406 e = e->next;
2407 } while (e!=l->list);
2409 return NULL;
2412 /* Returns the current string text as well as the associated extra data */
2413 void
2414 listbox_get_current (WListbox *l, char **string, char **extra)
2416 if (!l->current){
2417 *string = 0;
2418 *extra = 0;
2420 if (string && l->current)
2421 *string = l->current->text;
2422 if (extra && l->current)
2423 *extra = l->current->data;
2426 /* returns TRUE if a function has been called, FALSE otherwise. */
2427 static gboolean
2428 buttonbar_call (WButtonBar *bb, int i)
2430 switch (bb->labels[i].tag) {
2431 case BBFUNC_NONE:
2432 break;
2433 case BBFUNC_VOID:
2434 bb->labels[i].u.fn_void ();
2435 return TRUE;
2436 case BBFUNC_PTR:
2437 bb->labels[i].u.fn_ptr (bb->labels[i].data);
2438 return TRUE;
2440 return FALSE;
2443 /* calculate width of one button, width is never lesser than 7 */
2444 static int
2445 buttonbat_get_button_width ()
2447 int result = COLS / BUTTONBAR_LABELS_NUM;
2448 return (result >= 7) ? result : 7;
2452 static cb_ret_t
2453 buttonbar_callback (Widget *w, widget_msg_t msg, int parm)
2455 WButtonBar *bb = (WButtonBar *) w;
2456 int i;
2457 char *text;
2459 switch (msg) {
2460 case WIDGET_FOCUS:
2461 return MSG_NOT_HANDLED;
2463 case WIDGET_HOTKEY:
2464 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
2465 if (parm == KEY_F (i + 1) && buttonbar_call (bb, i))
2466 return MSG_HANDLED;
2467 return MSG_NOT_HANDLED;
2469 case WIDGET_DRAW:
2470 if (bb->visible) {
2471 widget_move (&bb->widget, 0, 0);
2472 tty_setcolor (DEFAULT_COLOR);
2473 bb->btn_width = buttonbat_get_button_width ();
2474 tty_printf ("%-*s", bb->widget.cols, "");
2476 for (i = 0; i < COLS / bb->btn_width && i < BUTTONBAR_LABELS_NUM; i++) {
2477 widget_move (&bb->widget, 0, i * bb->btn_width);
2478 tty_setcolor (DEFAULT_COLOR);
2479 tty_printf ("%2d", i + 1);
2480 tty_setcolor (SELECTED_COLOR);
2481 text = (bb->labels[i].text != NULL) ? bb->labels[i].text : "";
2482 tty_print_string (str_fit_to_term (text, bb->btn_width - 2, J_CENTER_LEFT));
2485 return MSG_HANDLED;
2487 case WIDGET_DESTROY:
2488 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
2489 g_free (bb->labels[i].text);
2490 return MSG_HANDLED;
2492 default:
2493 return default_proc (msg, parm);
2497 static int
2498 buttonbar_event (Gpm_Event *event, void *data)
2500 WButtonBar *bb = data;
2501 int button;
2503 if (!(event->type & GPM_UP))
2504 return MOU_NORMAL;
2505 if (event->y == 2)
2506 return MOU_NORMAL;
2507 button = (event->x - 1) / bb->btn_width;
2508 if (button < BUTTONBAR_LABELS_NUM)
2509 buttonbar_call (bb, button);
2510 return MOU_NORMAL;
2513 WButtonBar *
2514 buttonbar_new (int visible)
2516 int i;
2517 WButtonBar *bb = g_new (WButtonBar, 1);
2519 init_widget (&bb->widget, LINES-1, 0, 1, COLS,
2520 buttonbar_callback, buttonbar_event);
2522 bb->visible = visible;
2523 for (i = 0; i < BUTTONBAR_LABELS_NUM; i++){
2524 bb->labels[i].text = NULL;
2525 bb->labels[i].tag = BBFUNC_NONE;
2527 widget_want_hotkey (bb->widget, 1);
2528 widget_want_cursor (bb->widget, 0);
2529 bb->btn_width = buttonbat_get_button_width ();
2531 return bb;
2534 static void
2535 set_label_text (WButtonBar * bb, int index, const char *text)
2537 g_free (bb->labels[index - 1].text);
2539 bb->labels[index - 1].text = g_strdup (text);
2542 /* Find ButtonBar widget in the dialog */
2543 WButtonBar *
2544 find_buttonbar (Dlg_head *h)
2546 WButtonBar *bb;
2548 bb = (WButtonBar *) find_widget_type (h, buttonbar_callback);
2549 return bb;
2552 void
2553 buttonbar_clear_label (Dlg_head *h, int idx)
2555 WButtonBar *bb = find_buttonbar (h);
2557 if (!bb)
2558 return;
2560 set_label_text (bb, idx, "");
2561 bb->labels[idx - 1].tag = BBFUNC_NONE;
2564 void
2565 buttonbar_set_label_data (Dlg_head *h, int idx, const char *text,
2566 buttonbarfn cback, void *data)
2568 WButtonBar *bb = find_buttonbar (h);
2570 if (!bb)
2571 return;
2573 assert (cback != (buttonbarfn) 0);
2574 set_label_text (bb, idx, text);
2575 bb->labels[idx - 1].tag = BBFUNC_PTR;
2576 bb->labels[idx - 1].u.fn_ptr = cback;
2577 bb->labels[idx - 1].data = data;
2580 void
2581 buttonbar_set_label (Dlg_head *h, int idx, const char *text, voidfn cback)
2583 WButtonBar *bb = find_buttonbar (h);
2585 if (!bb)
2586 return;
2588 assert (cback != (voidfn) 0);
2589 set_label_text (bb, idx, text);
2590 bb->labels[idx - 1].tag = BBFUNC_VOID;
2591 bb->labels[idx - 1].u.fn_void = cback;
2594 void
2595 buttonbar_set_visible (WButtonBar *bb, gboolean visible)
2597 bb->visible = visible;
2600 void
2601 buttonbar_redraw (Dlg_head *h)
2603 WButtonBar *bb = find_buttonbar (h);
2605 if (!bb)
2606 return;
2608 send_message ((Widget *) bb, WIDGET_DRAW, 0);
2611 static cb_ret_t
2612 groupbox_callback (Widget *w, widget_msg_t msg, int parm)
2614 WGroupbox *g = (WGroupbox *) w;
2616 switch (msg) {
2617 case WIDGET_INIT:
2618 return MSG_HANDLED;
2620 case WIDGET_FOCUS:
2621 return MSG_NOT_HANDLED;
2623 case WIDGET_DRAW:
2624 tty_setcolor (COLOR_NORMAL);
2625 draw_box (g->widget.parent, g->widget.y - g->widget.parent->y,
2626 g->widget.x - g->widget.parent->x, g->widget.lines,
2627 g->widget.cols);
2629 tty_setcolor (COLOR_HOT_NORMAL);
2630 dlg_move (g->widget.parent, g->widget.y - g->widget.parent->y,
2631 g->widget.x - g->widget.parent->x + 1);
2632 tty_print_string (g->title);
2633 return MSG_HANDLED;
2635 case WIDGET_DESTROY:
2636 g_free (g->title);
2637 return MSG_HANDLED;
2639 default:
2640 return default_proc (msg, parm);
2644 WGroupbox *
2645 groupbox_new (int y, int x, int height, int width, const char *title)
2647 WGroupbox *g = g_new (WGroupbox, 1);
2649 init_widget (&g->widget, y, x, height, width, groupbox_callback, NULL);
2651 g->widget.options &= ~W_WANT_CURSOR;
2652 widget_want_hotkey (g->widget, 0);
2654 /* Strip existing spaces, add one space before and after the title */
2655 if (title) {
2656 char *t;
2657 t = g_strstrip (g_strdup (title));
2658 g->title = g_strconcat (" ", t, " ", (char *) NULL);
2659 g_free (t);
2662 return g;