1 /*
2  * Copyright (c)2004 Cat's Eye Technologies.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  *   Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  *
11  *   Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in
13  *   the documentation and/or other materials provided with the
14  *   distribution.
15  *
16  *   Neither the name of Cat's Eye Technologies nor the names of its
17  *   contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 /*
35  * curses_widget.c
36  * $Id: curses_widget.c,v 1.14 2005/08/26 22:44:37 cpressey Exp $
37  */
38 
39 #include <ctype.h>
40 #include <ncurses.h>
41 #include <stdlib.h>
42 #include <string.h>
43 
44 #ifdef SYSTEM_AURA
45 #include <aura/mem.h>
46 #else
47 #include "mem.h"
48 #endif
49 
50 #include "curses_form.h"
51 #include "curses_widget.h"
52 #include "curses_bar.h"
53 #include "curses_util.h"
54 
55 #ifdef DEBUG
56 extern FILE *dfui_debug_file;
57 #endif
58 
59 extern struct curses_bar *statusbar;
60 
61 /*** WIDGETS ***/
62 
63 /*
64  * Create a new curses_widget, outside of the context of any particular
65  * curses_form.
66  */
67 struct curses_widget *
curses_widget_new(unsigned int x,unsigned int y,unsigned int width,widget_t type,const char * text,unsigned int size,unsigned int flags)68 curses_widget_new(unsigned int x, unsigned int y,
69 		  unsigned int width, widget_t type,
70 		  const char *text, unsigned int size,
71 		  unsigned int flags)
72 {
73 	struct curses_widget *w;
74 
75 	AURA_MALLOC(w, curses_widget);
76 
77 	w->form = NULL;
78 
79 	if (flags & CURSES_WIDGET_WIDEN) {
80 		switch (type) {
81 		case CURSES_TEXTBOX:
82 			width = strlen(text) + 2;
83 			break;
84 		case CURSES_BUTTON:
85 			width = strlen(text) + 4;
86 			break;
87 		default:
88 			width = strlen(text);
89 			break;
90 		}
91 	}
92 
93 	w->x = x;
94 	w->y = y;
95 	w->width = width;
96 	w->type = type;
97 	w->next = NULL;
98 	w->prev = NULL;
99 	w->flags = flags;
100 	w->accel = '\0';
101 
102 	if (w->type == CURSES_TEXTBOX) {
103 		w->size = size;
104 		w->text = aura_malloc(size, "w->text");
105 		strcpy(w->text, text);
106 	} else {
107 		w->text = aura_strdup(text);
108 		w->size = strlen(text) + 1;
109 		/* size argument is ignored */
110 	}
111 
112 	w->curpos = strlen(w->text);
113 	w->editable = 1;
114 	w->obscured = 0;
115 	w->offset = 0;
116 	w->amount = 0;
117 	w->spin = 0;
118 	w->tooltip = NULL;
119 	w->user_id = 0;
120 	w->userdata = NULL;
121 
122 	w->click_cb = NULL;
123 
124 	return(w);
125 }
126 
127 /*
128  * Free the memory allocated for a curses_widget.  Note that this does
129  * NOT free any allocated memory at the widget's userdata pointer.
130  */
131 void
curses_widget_free(struct curses_widget * w)132 curses_widget_free(struct curses_widget *w)
133 {
134 	if (w->tooltip != NULL)
135 		free(w->tooltip);
136 	free(w->text);
137 	AURA_FREE(w, curses_widget);
138 }
139 
140 void
curses_widget_tooltip_set(struct curses_widget * w,const char * tooltip)141 curses_widget_tooltip_set(struct curses_widget *w, const char *tooltip)
142 {
143 	if (w->tooltip != NULL)
144 		free(w->tooltip);
145 	w->tooltip = aura_strdup(tooltip);
146 }
147 
148 /*
149  * Draw a curses_widget to the window of its associated curses_form.
150  * Note that this does not refresh the screen.
151  */
152 void
curses_widget_draw(struct curses_widget * w)153 curses_widget_draw(struct curses_widget *w)
154 {
155 	unsigned int wx, wy, x, len, i, charpos, cutoff, fchpos;
156 	char p[5];
157 	char bar_char;
158 	struct curses_form *cf = w->form;
159 
160 	wx = w->x + 1 - cf->x_offset;
161 	wy = w->y + 1 - cf->y_offset;
162 
163 	/*
164 	 * If the widget is outside the clipping rectangle of the
165 	 * form, don't draw it.
166 	 */
167 	if (!curses_form_widget_is_visible(w))
168 		return;
169 
170 	wmove(cf->win, wy, wx);
171 
172 	curses_colors_set(cf->win, w == cf->widget_focus ?
173 	    CURSES_COLORS_FOCUS : CURSES_COLORS_CONTROL);
174 
175 	switch (w->type) {
176 	case CURSES_LABEL:
177 		curses_colors_set(cf->win, CURSES_COLORS_LABEL);	 /* XXX conditional on... */
178 		waddnstr(cf->win, w->text, w->width);
179 		curs_set(0);
180 		break;
181 	case CURSES_TEXTBOX:
182 		waddstr(cf->win, "[");
183 		curses_colors_set(cf->win, w->editable ?
184 		    CURSES_COLORS_TEXT : CURSES_COLORS_GHOST);		/* XXX focus ? */
185 		charpos = w->offset;
186 		len = strlen(w->text);
187 		for (x = 0; x < w->width - 2; x++) {
188 			if (charpos < len) {
189 				waddch(cf->win, w->obscured ?
190 				    '*' : w->text[charpos]);
191 				charpos++;
192 			} else {
193 				waddch(cf->win, ' ');
194 			}
195 		}
196 		curses_colors_set(cf->win, w == cf->widget_focus ?
197 		    CURSES_COLORS_FOCUS : CURSES_COLORS_CONTROL);
198 		waddstr(cf->win, "]");
199 		/*
200 		 * Position the cursor to where it's expected.
201 		 */
202 		if (w->curpos - w->offset < w->width - 2) {
203 			wmove(cf->win, w->y + 1 - cf->y_offset,
204 			      w->x + 2 - cf->x_offset + w->curpos - w->offset);
205 		} else {
206 			wmove(cf->win, w->y + 1 - cf->y_offset,
207 			      w->x + 2 - cf->x_offset + w->width - 2);
208 		}
209 		curs_set(1);
210 		break;
211 	case CURSES_BUTTON:
212 		waddstr(cf->win, "< ");
213 		waddnstr(cf->win, w->text, w->width - 4);
214 		waddstr(cf->win, " >");
215 		if (isprint(w->accel)) {
216 			for (i = 0; w->text[i] != '\0'; i++) {
217 				if (toupper(w->text[i]) == w->accel) {
218 					wmove(cf->win, wy, wx + 2 + i);
219 					curses_colors_set(cf->win, w == cf->widget_focus ?
220 					    CURSES_COLORS_ACCELFOCUS : CURSES_COLORS_ACCEL);
221 					waddch(cf->win, w->text[i]);
222 					break;
223 				}
224 			}
225 		}
226 		curs_set(0);
227 		break;
228 	case CURSES_PROGRESS:
229 		waddstr(cf->win, "[");
230 		snprintf(p, 5, "%3d%%", w->amount);
231 		cutoff = (w->amount * (w->width - 2)) / 100;
232 		fchpos = ((w->width - 2) / 2) - 2;
233 		for (x = 0; x < w->width - 2; x++) {
234 			if (x < cutoff) {
235 				curses_colors_set(cf->win, CURSES_COLORS_FOCUS);/* XXX status ? */
236 				bar_char = monochrome ? '=' : ' ';
237 			} else {
238 				curses_colors_set(cf->win, CURSES_COLORS_CONTROL);
239 				bar_char = ' ';
240 			}
241 
242 			if (x >= fchpos && x <= fchpos + 3)
243 				waddch(cf->win, p[x - fchpos]);
244 			else
245 				waddch(cf->win, bar_char);
246 		}
247 		waddstr(cf->win, "]");
248 		curs_set(0);
249 		break;
250 	case CURSES_CHECKBOX:
251 		waddstr(cf->win, "[");
252 		waddstr(cf->win, w->amount ? "X" : " ");
253 		waddstr(cf->win, "]");
254 		curs_set(0);
255 		break;
256 	}
257 }
258 
259 void
curses_widget_draw_tooltip(struct curses_widget * w)260 curses_widget_draw_tooltip(struct curses_widget *w)
261 {
262 	if (w->tooltip != NULL)
263 		curses_bar_set_text(statusbar, w->tooltip);
264 }
265 
266 /*
267  * Returns non-zero if the given widget can take control focus
268  * (i.e. if it is not a label, progress bar, or other inert element.)
269  */
270 int
curses_widget_can_take_focus(struct curses_widget * w)271 curses_widget_can_take_focus(struct curses_widget *w)
272 {
273 	if (w->type == CURSES_LABEL || w->type == CURSES_PROGRESS)
274 		return(0);
275 	return(1);
276 }
277 
278 int
curses_widget_set_click_cb(struct curses_widget * w,int (* callback)(struct curses_widget *))279 curses_widget_set_click_cb(struct curses_widget *w,
280 			   int (*callback)(struct curses_widget *))
281 {
282 	w->click_cb = callback;
283 	return(1);
284 }
285 
286 /*
287  * Returns:
288  *   -1 to indicate that the widget is not clickable.
289  *    0 to indicate that the widget clicked.
290  *    1 to indicate that the widget clicked and that its form should close.
291  */
292 int
curses_widget_click(struct curses_widget * w)293 curses_widget_click(struct curses_widget *w)
294 {
295 	if ((w->type != CURSES_BUTTON && w->type != CURSES_TEXTBOX) ||
296 	    w->click_cb == NULL)
297 		return(-1);
298 
299 	return(w->click_cb(w));
300 }
301 
302 /*** TEXTBOX WIDGETS ***/
303 
304 int
curses_textbox_advance_char(struct curses_widget * w)305 curses_textbox_advance_char(struct curses_widget *w)
306 {
307 	if (w->text[w->curpos] != '\0') {
308 		w->curpos++;
309 		if ((w->curpos - w->offset) >= (w->width - 2))
310 			w->offset++;
311 		curses_widget_draw(w);
312 		curses_form_refresh(w->form);
313 		return(1);
314 	} else {
315 		return(0);
316 	}
317 }
318 
319 int
curses_textbox_retreat_char(struct curses_widget * w)320 curses_textbox_retreat_char(struct curses_widget *w)
321 {
322 	if (w->curpos > 0) {
323 		w->curpos--;
324 		if (w->curpos < w->offset)
325 			w->offset--;
326 		curses_widget_draw(w);
327 		curses_form_refresh(w->form);
328 		return(1);
329 	} else {
330 		return(0);
331 	}
332 }
333 
334 int
curses_textbox_home(struct curses_widget * w)335 curses_textbox_home(struct curses_widget *w)
336 {
337 	w->curpos = 0;
338 	w->offset = 0;
339 	curses_widget_draw(w);
340 	curses_form_refresh(w->form);
341 	return(1);
342 }
343 
344 int
curses_textbox_end(struct curses_widget * w)345 curses_textbox_end(struct curses_widget *w)
346 {
347 	w->curpos = strlen(w->text);
348 	while ((w->curpos - w->offset) >= (w->width - 2)) {
349 		w->offset++;
350 	}
351 	curses_widget_draw(w);
352 	curses_form_refresh(w->form);
353 	return(1);
354 }
355 
356 int
curses_textbox_insert_char(struct curses_widget * w,char key)357 curses_textbox_insert_char(struct curses_widget *w, char key)
358 {
359 	unsigned int len, i;
360 
361 	if (!w->editable)
362 		return(0);
363 
364 	len = strlen(w->text);
365 	if (len == (w->size - 1))
366 		return(0);
367 
368 	w->text[len + 1] = '\0';
369 	for (i = len; i > w->curpos; i--)
370 		w->text[i] = w->text[i - 1];
371 	w->text[w->curpos++] = key;
372 	if ((w->curpos - w->offset) >= (w->width - 2))
373 		w->offset++;
374 	curses_widget_draw(w);
375 	curses_form_refresh(w->form);
376 	return(1);
377 }
378 
379 int
curses_textbox_backspace_char(struct curses_widget * w)380 curses_textbox_backspace_char(struct curses_widget *w)
381 {
382 	int len, i;
383 
384 	if (!w->editable)
385 		return(0);
386 
387 	len = strlen(w->text);
388 	if (w->curpos == 0)
389 		return(0);
390 
391 	for (i = w->curpos; i <= len; i++)
392 		w->text[i - 1] = w->text[i];
393 	w->curpos--;
394 	if (w->curpos < w->offset)
395 		w->offset--;
396 	curses_widget_draw(w);
397 	curses_form_refresh(w->form);
398 	return(1);
399 }
400 
401 int
curses_textbox_delete_char(struct curses_widget * w)402 curses_textbox_delete_char(struct curses_widget *w)
403 {
404 	unsigned int len, i;
405 
406 	if (!w->editable)
407 		return(0);
408 
409 	len = strlen(w->text);
410 	if (w->curpos == len)
411 		return(0);
412 
413 	for (i = w->curpos + 1; i <= len; i++)
414 		w->text[i - 1] = w->text[i];
415 	curses_widget_draw(w);
416 	curses_form_refresh(w->form);
417 	return(1);
418 }
419 
420 int
curses_textbox_set_text(struct curses_widget * w,const char * text)421 curses_textbox_set_text(struct curses_widget *w, const char *text)
422 {
423 	strlcpy(w->text, text, w->size);
424 	w->curpos = strlen(w->text);
425 	curses_widget_draw(w);
426 	curses_form_refresh(w->form);
427 	return(1);
428 }
429 
430 /*** CHECKBOX WIDGETS ***/
431 
432 int
curses_checkbox_toggle(struct curses_widget * w)433 curses_checkbox_toggle(struct curses_widget *w)
434 {
435 	if (!w->editable)
436 		return(0);
437 	w->amount = !w->amount;
438 	curses_widget_draw(w);
439 	curses_form_refresh(w->form);
440 	return(1);
441 }
442 
443 /*** PROGRESS WIDGETS ***/
444 
445 char spinny[5] = "/-\\|";
446 
447 int
curses_progress_spin(struct curses_widget * w)448 curses_progress_spin(struct curses_widget *w)
449 {
450 	struct curses_form *cf = w->form;
451 	int wx, wy;
452 
453 	if (w->type != CURSES_PROGRESS)
454 		return(0);
455 
456 	wx = w->x + 1 - cf->x_offset;
457 	wy = w->y + 1 - cf->y_offset;
458 
459 	w->spin = (w->spin + 1) % 4;
460 	curses_colors_set(cf->win, CURSES_COLORS_FOCUS);/* XXX status ? */
461 	mvwaddch(cf->win, wy, wx + 1, spinny[w->spin]);
462 
463 	return(1);
464 }
465