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.12 2005/02/08 07:49:03 cpressey Exp $
37  */
38 
39 #include <ctype.h>
40 #include <ncurses.h>
41 #include <stdlib.h>
42 #include <string.h>
43 
44 #include "libaura/mem.h"
45 
46 #include "curses_form.h"
47 #include "curses_widget.h"
48 #include "curses_bar.h"
49 #include "curses_util.h"
50 
51 #ifdef DEBUG
52 extern FILE *dfui_debug_file;
53 #endif
54 
55 extern struct curses_bar *statusbar;
56 
57 /*** WIDGETS ***/
58 
59 /*
60  * Create a new curses_widget, outside of the context of any particular
61  * curses_form.
62  */
63 struct curses_widget *
64 curses_widget_new(unsigned int x, unsigned int y,
65 		  unsigned int width, widget_t type,
66 		  const char *text, unsigned int size,
67 		  unsigned int flags)
68 {
69 	struct curses_widget *w;
70 
71 	AURA_MALLOC(w, curses_widget);
72 
73 	w->form = NULL;
74 
75 	if (flags & CURSES_WIDGET_WIDEN) {
76 		switch (type) {
77 		case CURSES_TEXTBOX:
78 			width = strlen(text) + 2;
79 			break;
80 		case CURSES_BUTTON:
81 			width = strlen(text) + 4;
82 			break;
83 		default:
84 			width = strlen(text);
85 			break;
86 		}
87 	}
88 
89 	w->x = x;
90 	w->y = y;
91 	w->width = width;
92 	w->type = type;
93 	w->next = NULL;
94 	w->prev = NULL;
95 	w->flags = flags;
96 	w->accel = '\0';
97 
98 	if (w->type == CURSES_TEXTBOX) {
99 		w->size = size;
100 		w->text = aura_malloc(size, "w->text");
101 		strcpy(w->text, text);
102 	} else {
103 		w->text = aura_strdup(text);
104 		w->size = strlen(text) + 1;
105 		/* size argument is ignored */
106 	}
107 
108 	w->curpos = strlen(w->text);
109 	w->editable = 1;
110 	w->obscured = 0;
111 	w->offset = 0;
112 	w->amount = 0;
113 	w->spin = 0;
114 	w->tooltip = NULL;
115 	w->user_id = 0;
116 	w->userdata = NULL;
117 
118 	w->click_cb = NULL;
119 
120 	return(w);
121 }
122 
123 /*
124  * Free the memory allocated for a curses_widget.  Note that this does
125  * NOT free any allocated memory at the widget's userdata pointer.
126  */
127 void
128 curses_widget_free(struct curses_widget *w)
129 {
130 	if (w->tooltip != NULL)
131 		free(w->tooltip);
132 	free(w->text);
133 	AURA_FREE(w, curses_widget);
134 }
135 
136 void
137 curses_widget_tooltip_set(struct curses_widget *w, const char *tooltip)
138 {
139 	if (w->tooltip != NULL)
140 		free(w->tooltip);
141 	w->tooltip = aura_strdup(tooltip);
142 }
143 
144 /*
145  * Draw a curses_widget to the window of its associated curses_form.
146  * Note that this does not refresh the screen.
147  */
148 void
149 curses_widget_draw(struct curses_widget *w)
150 {
151 	unsigned int wx, wy, x, len, i, charpos, cutoff, fchpos;
152 	char p[5];
153 	char bar_char;
154 	struct curses_form *cf = w->form;
155 
156 	wx = w->x + 1 - cf->x_offset;
157 	wy = w->y + 1 - cf->y_offset;
158 
159 	/*
160 	 * If the widget is outside the clipping rectangle of the
161 	 * form, don't draw it.
162 	 */
163 	if (!curses_form_widget_is_visible(w))
164 		return;
165 
166 	wmove(cf->win, wy, wx);
167 
168 	curses_colors_set(cf->win, w == cf->widget_focus ?
169 	    CURSES_COLORS_FOCUS : CURSES_COLORS_CONTROL);
170 
171 	switch (w->type) {
172 	case CURSES_LABEL:
173 		curses_colors_set(cf->win, CURSES_COLORS_LABEL);	 /* XXX conditional on... */
174 		waddnstr(cf->win, w->text, w->width);
175 		curs_set(0);
176 		break;
177 	case CURSES_TEXTBOX:
178 		waddstr(cf->win, "[");
179 		curses_colors_set(cf->win, CURSES_COLORS_TEXT);		/* XXX focus ? */
180 		charpos = w->offset;
181 		len = strlen(w->text);
182 		for (x = 0; x < w->width - 2; x++) {
183 			if (charpos < len) {
184 				waddch(cf->win, w->obscured ?
185 				    '*' : w->text[charpos]);
186 				charpos++;
187 			} else {
188 				waddch(cf->win, ' ');
189 			}
190 		}
191 		curses_colors_set(cf->win, w == cf->widget_focus ?
192 		    CURSES_COLORS_FOCUS : CURSES_COLORS_CONTROL);
193 		waddstr(cf->win, "]");
194 		/*
195 		 * Position the cursor to where it's expected.
196 		 */
197 		if (w->curpos - w->offset < w->width - 2) {
198 			wmove(cf->win, w->y + 1 - cf->y_offset,
199 			      w->x + 2 - cf->x_offset + w->curpos - w->offset);
200 		} else {
201 			wmove(cf->win, w->y + 1 - cf->y_offset,
202 			      w->x + 2 - cf->x_offset + w->width - 2);
203 		}
204 		curs_set(1);
205 		break;
206 	case CURSES_BUTTON:
207 		waddstr(cf->win, "< ");
208 		waddnstr(cf->win, w->text, w->width - 4);
209 		waddstr(cf->win, " >");
210 		if (isprint(w->accel)) {
211 			for (i = 0; w->text[i] != '\0'; i++) {
212 				if (toupper(w->text[i]) == w->accel) {
213 					wmove(cf->win, wy, wx + 2 + i);
214 					curses_colors_set(cf->win, w == cf->widget_focus ?
215 					    CURSES_COLORS_ACCELFOCUS : CURSES_COLORS_ACCEL);
216 					waddch(cf->win, w->text[i]);
217 					break;
218 				}
219 			}
220 		}
221 		curs_set(0);
222 		break;
223 	case CURSES_PROGRESS:
224 		waddstr(cf->win, "[");
225 		snprintf(p, 5, "%3d%%", w->amount);
226 		cutoff = (w->amount * (w->width - 2)) / 100;
227 		fchpos = ((w->width - 2) / 2) - 2;
228 		for (x = 0; x < w->width - 2; x++) {
229 			if (x < cutoff) {
230 				curses_colors_set(cf->win, CURSES_COLORS_FOCUS);/* XXX status ? */
231 				bar_char = monochrome ? '=' : ' ';
232 			} else {
233 				curses_colors_set(cf->win, CURSES_COLORS_CONTROL);
234 				bar_char = ' ';
235 			}
236 
237 			if (x >= fchpos && x <= fchpos + 3)
238 				waddch(cf->win, p[x - fchpos]);
239 			else
240 				waddch(cf->win, bar_char);
241 		}
242 		waddstr(cf->win, "]");
243 		curs_set(0);
244 		break;
245 	case CURSES_CHECKBOX:
246 		waddstr(cf->win, "[");
247 		waddstr(cf->win, w->amount ? "X" : " ");
248 		waddstr(cf->win, "]");
249 		curs_set(0);
250 		break;
251 	}
252 }
253 
254 void
255 curses_widget_draw_tooltip(struct curses_widget *w)
256 {
257 	if (w->tooltip != NULL)
258 		curses_bar_set_text(statusbar, w->tooltip);
259 }
260 
261 /*
262  * Returns non-zero if the given widget can take control focus
263  * (i.e. if it is not a label, progress bar, or other inert element.)
264  */
265 int
266 curses_widget_can_take_focus(struct curses_widget *w)
267 {
268 	if (w->type == CURSES_LABEL || w->type == CURSES_PROGRESS)
269 		return(0);
270 	return(1);
271 }
272 
273 int
274 curses_widget_set_click_cb(struct curses_widget *w,
275 			   int (*callback)(struct curses_widget *))
276 {
277 	w->click_cb = callback;
278 	return(1);
279 }
280 
281 /*
282  * Returns:
283  *   -1 to indicate that the widget is not clickable.
284  *    0 to indicate that the widget clicked.
285  *    1 to indicate that the widget clicked and that its form should close.
286  */
287 int
288 curses_widget_click(struct curses_widget *w)
289 {
290 	if ((w->type != CURSES_BUTTON && w->type != CURSES_TEXTBOX) ||
291 	    w->click_cb == NULL)
292 		return(-1);
293 
294 	return(w->click_cb(w));
295 }
296 
297 /*** TEXTBOX WIDGETS ***/
298 
299 int
300 curses_textbox_advance_char(struct curses_widget *w)
301 {
302 	if (w->text[w->curpos] != '\0') {
303 		w->curpos++;
304 		if ((w->curpos - w->offset) >= (w->width - 2))
305 			w->offset++;
306 		curses_widget_draw(w);
307 		curses_form_refresh(w->form);
308 		return(1);
309 	} else {
310 		return(0);
311 	}
312 }
313 
314 int
315 curses_textbox_retreat_char(struct curses_widget *w)
316 {
317 	if (w->curpos > 0) {
318 		w->curpos--;
319 		if (w->curpos < w->offset)
320 			w->offset--;
321 		curses_widget_draw(w);
322 		curses_form_refresh(w->form);
323 		return(1);
324 	} else {
325 		return(0);
326 	}
327 }
328 
329 int
330 curses_textbox_home(struct curses_widget *w)
331 {
332 	w->curpos = 0;
333 	w->offset = 0;
334 	curses_widget_draw(w);
335 	curses_form_refresh(w->form);
336 	return(1);
337 }
338 
339 int
340 curses_textbox_end(struct curses_widget *w)
341 {
342 	w->curpos = strlen(w->text);
343 	while ((w->curpos - w->offset) >= (w->width - 2)) {
344 		w->offset++;
345 	}
346 	curses_widget_draw(w);
347 	curses_form_refresh(w->form);
348 	return(1);
349 }
350 
351 int
352 curses_textbox_insert_char(struct curses_widget *w, char key)
353 {
354 	unsigned int len, i;
355 
356 	if (!w->editable)
357 		return(0);
358 
359 	len = strlen(w->text);
360 	if (len == (w->size - 1))
361 		return(0);
362 
363 	w->text[len + 1] = '\0';
364 	for (i = len; i > w->curpos; i--)
365 		w->text[i] = w->text[i - 1];
366 	w->text[w->curpos++] = key;
367 	if ((w->curpos - w->offset) >= (w->width - 2))
368 		w->offset++;
369 	curses_widget_draw(w);
370 	curses_form_refresh(w->form);
371 	return(1);
372 }
373 
374 int
375 curses_textbox_backspace_char(struct curses_widget *w)
376 {
377 	int len, i;
378 
379 	if (!w->editable)
380 		return(0);
381 
382 	len = strlen(w->text);
383 	if (w->curpos == 0)
384 		return(0);
385 
386 	for (i = w->curpos; i <= len; i++)
387 		w->text[i - 1] = w->text[i];
388 	w->curpos--;
389 	if (w->curpos < w->offset)
390 		w->offset--;
391 	curses_widget_draw(w);
392 	curses_form_refresh(w->form);
393 	return(1);
394 }
395 
396 int
397 curses_textbox_delete_char(struct curses_widget *w)
398 {
399 	unsigned int len, i;
400 
401 	if (!w->editable)
402 		return(0);
403 
404 	len = strlen(w->text);
405 	if (w->curpos == len)
406 		return(0);
407 
408 	for (i = w->curpos + 1; i <= len; i++)
409 		w->text[i - 1] = w->text[i];
410 	curses_widget_draw(w);
411 	curses_form_refresh(w->form);
412 	return(1);
413 }
414 
415 int
416 curses_textbox_set_text(struct curses_widget *w, const char *text)
417 {
418 	strlcpy(w->text, text, w->size);
419 	w->curpos = strlen(w->text);
420 	curses_widget_draw(w);
421 	curses_form_refresh(w->form);
422 	return(1);
423 }
424 
425 /*** CHECKBOX WIDGETS ***/
426 
427 int
428 curses_checkbox_toggle(struct curses_widget *w)
429 {
430 	if (!w->editable)
431 		return(0);
432 	w->amount = !w->amount;
433 	curses_widget_draw(w);
434 	curses_form_refresh(w->form);
435 	return(1);
436 }
437 
438 /*** PROGRESS WIDGETS ***/
439 
440 char spinny[5] = "/-\\|";
441 
442 int
443 curses_progress_spin(struct curses_widget *w)
444 {
445 	struct curses_form *cf = w->form;
446 	int wx, wy;
447 
448 	if (w->type != CURSES_PROGRESS)
449 		return(0);
450 
451 	wx = w->x + 1 - cf->x_offset;
452 	wy = w->y + 1 - cf->y_offset;
453 
454 	w->spin = (w->spin + 1) % 4;
455 	curses_colors_set(cf->win, CURSES_COLORS_FOCUS);/* XXX status ? */
456 	mvwaddch(cf->win, wy, wx + 1, spinny[w->spin]);
457 
458 	return(1);
459 }
460