/*
 * Copyright (c)2004 Cat's Eye Technologies.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 *   Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 *   Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 *   Neither the name of Cat's Eye Technologies nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

/*
 * curses_widget.c
 * $Id: curses_widget.c,v 1.14 2005/08/26 22:44:37 cpressey Exp $
 */

#include <ctype.h>
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>

#ifdef SYSTEM_AURA
#include <aura/mem.h>
#else
#include "mem.h"
#endif

#include "curses_form.h"
#include "curses_widget.h"
#include "curses_bar.h"
#include "curses_util.h"

#ifdef DEBUG
extern FILE *dfui_debug_file;
#endif

extern struct curses_bar *statusbar;

/*** WIDGETS ***/

/*
 * Create a new curses_widget, outside of the context of any particular
 * curses_form.
 */
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)
{
	struct curses_widget *w;

	AURA_MALLOC(w, curses_widget);

	w->form = NULL;

	if (flags & CURSES_WIDGET_WIDEN) {
		switch (type) {
		case CURSES_TEXTBOX:
			width = strlen(text) + 2;
			break;
		case CURSES_BUTTON:
			width = strlen(text) + 4;
			break;
		default:
			width = strlen(text);
			break;
		}
	}

	w->x = x;
	w->y = y;
	w->width = width;
	w->type = type;
	w->next = NULL;
	w->prev = NULL;
	w->flags = flags;
	w->accel = '\0';

	if (w->type == CURSES_TEXTBOX) {
		w->size = size;
		w->text = aura_malloc(size, "w->text");
		strcpy(w->text, text);
	} else {
		w->text = aura_strdup(text);
		w->size = strlen(text) + 1;
		/* size argument is ignored */
	}

	w->curpos = strlen(w->text);
	w->editable = 1;
	w->obscured = 0;
	w->offset = 0;
	w->amount = 0;
	w->spin = 0;
	w->tooltip = NULL;
	w->user_id = 0;
	w->userdata = NULL;

	w->click_cb = NULL;

	return(w);
}

/*
 * Free the memory allocated for a curses_widget.  Note that this does
 * NOT free any allocated memory at the widget's userdata pointer.
 */
void
curses_widget_free(struct curses_widget *w)
{
	if (w->tooltip != NULL)
		free(w->tooltip);
	free(w->text);
	AURA_FREE(w, curses_widget);
}

void
curses_widget_tooltip_set(struct curses_widget *w, const char *tooltip)
{
	if (w->tooltip != NULL)
		free(w->tooltip);
	w->tooltip = aura_strdup(tooltip);
}

/*
 * Draw a curses_widget to the window of its associated curses_form.
 * Note that this does not refresh the screen.
 */
void
curses_widget_draw(struct curses_widget *w)
{
	unsigned int wx, wy, x, len, i, charpos, cutoff, fchpos;
	char p[5];
	char bar_char;
	struct curses_form *cf = w->form;

	wx = w->x + 1 - cf->x_offset;
	wy = w->y + 1 - cf->y_offset;

	/*
	 * If the widget is outside the clipping rectangle of the
	 * form, don't draw it.
	 */
	if (!curses_form_widget_is_visible(w))
		return;

	wmove(cf->win, wy, wx);

	curses_colors_set(cf->win, w == cf->widget_focus ?
	    CURSES_COLORS_FOCUS : CURSES_COLORS_CONTROL);

	switch (w->type) {
	case CURSES_LABEL:
		curses_colors_set(cf->win, CURSES_COLORS_LABEL);	 /* XXX conditional on... */
		waddnstr(cf->win, w->text, w->width);
		curs_set(0);
		break;
	case CURSES_TEXTBOX:
		waddstr(cf->win, "[");
		curses_colors_set(cf->win, w->editable ?
		    CURSES_COLORS_TEXT : CURSES_COLORS_GHOST);		/* XXX focus ? */
		charpos = w->offset;
		len = strlen(w->text);
		for (x = 0; x < w->width - 2; x++) {
			if (charpos < len) {
				waddch(cf->win, w->obscured ?
				    '*' : w->text[charpos]);
				charpos++;
			} else {
				waddch(cf->win, ' ');
			}
		}
		curses_colors_set(cf->win, w == cf->widget_focus ?
		    CURSES_COLORS_FOCUS : CURSES_COLORS_CONTROL);
		waddstr(cf->win, "]");
		/*
		 * Position the cursor to where it's expected.
		 */
		if (w->curpos - w->offset < w->width - 2) {
			wmove(cf->win, w->y + 1 - cf->y_offset,
			      w->x + 2 - cf->x_offset + w->curpos - w->offset);
		} else {
			wmove(cf->win, w->y + 1 - cf->y_offset,
			      w->x + 2 - cf->x_offset + w->width - 2);
		}
		curs_set(1);
		break;
	case CURSES_BUTTON:
		waddstr(cf->win, "< ");
		waddnstr(cf->win, w->text, w->width - 4);
		waddstr(cf->win, " >");
		if (isprint(w->accel)) {
			for (i = 0; w->text[i] != '\0'; i++) {
				if (toupper(w->text[i]) == w->accel) {
					wmove(cf->win, wy, wx + 2 + i);
					curses_colors_set(cf->win, w == cf->widget_focus ?
					    CURSES_COLORS_ACCELFOCUS : CURSES_COLORS_ACCEL);
					waddch(cf->win, w->text[i]);
					break;
				}
			}
		}
		curs_set(0);
		break;
	case CURSES_PROGRESS:
		waddstr(cf->win, "[");
		snprintf(p, 5, "%3d%%", w->amount);
		cutoff = (w->amount * (w->width - 2)) / 100;
		fchpos = ((w->width - 2) / 2) - 2;
		for (x = 0; x < w->width - 2; x++) {
			if (x < cutoff) {
				curses_colors_set(cf->win, CURSES_COLORS_FOCUS);/* XXX status ? */
				bar_char = monochrome ? '=' : ' ';
			} else {
				curses_colors_set(cf->win, CURSES_COLORS_CONTROL);
				bar_char = ' ';
			}
			
			if (x >= fchpos && x <= fchpos + 3)
				waddch(cf->win, p[x - fchpos]);
			else
				waddch(cf->win, bar_char);
		}
		waddstr(cf->win, "]");
		curs_set(0);
		break;
	case CURSES_CHECKBOX:
		waddstr(cf->win, "[");
		waddstr(cf->win, w->amount ? "X" : " ");
		waddstr(cf->win, "]");
		curs_set(0);
		break;
	}
}

void
curses_widget_draw_tooltip(struct curses_widget *w)
{
	if (w->tooltip != NULL)
		curses_bar_set_text(statusbar, w->tooltip);
}

/*
 * Returns non-zero if the given widget can take control focus
 * (i.e. if it is not a label, progress bar, or other inert element.)
 */
int
curses_widget_can_take_focus(struct curses_widget *w)
{
	if (w->type == CURSES_LABEL || w->type == CURSES_PROGRESS)
		return(0);
	return(1);
}

int
curses_widget_set_click_cb(struct curses_widget *w,
			   int (*callback)(struct curses_widget *))
{
	w->click_cb = callback;
	return(1);
}

/*
 * Returns:
 *   -1 to indicate that the widget is not clickable.
 *    0 to indicate that the widget clicked.
 *    1 to indicate that the widget clicked and that its form should close.
 */
int
curses_widget_click(struct curses_widget *w)
{
	if ((w->type != CURSES_BUTTON && w->type != CURSES_TEXTBOX) ||
	    w->click_cb == NULL)
		return(-1);

	return(w->click_cb(w));
}

/*** TEXTBOX WIDGETS ***/

int
curses_textbox_advance_char(struct curses_widget *w)
{
	if (w->text[w->curpos] != '\0') {
		w->curpos++;
		if ((w->curpos - w->offset) >= (w->width - 2))
			w->offset++;
		curses_widget_draw(w);
		curses_form_refresh(w->form);
		return(1);
	} else {
		return(0);
	}
}

int
curses_textbox_retreat_char(struct curses_widget *w)
{
	if (w->curpos > 0) {
		w->curpos--;
		if (w->curpos < w->offset)
			w->offset--;
		curses_widget_draw(w);
		curses_form_refresh(w->form);
		return(1);
	} else {
		return(0);
	}
}

int
curses_textbox_home(struct curses_widget *w)
{
	w->curpos = 0;
	w->offset = 0;
	curses_widget_draw(w);
	curses_form_refresh(w->form);
	return(1);
}

int
curses_textbox_end(struct curses_widget *w)
{
	w->curpos = strlen(w->text);
	while ((w->curpos - w->offset) >= (w->width - 2)) {
		w->offset++;
	}
	curses_widget_draw(w);
	curses_form_refresh(w->form);
	return(1);
}

int
curses_textbox_insert_char(struct curses_widget *w, char key)
{
	unsigned int len, i;

	if (!w->editable)
		return(0);

	len = strlen(w->text);
	if (len == (w->size - 1))
		return(0);

	w->text[len + 1] = '\0';
	for (i = len; i > w->curpos; i--)
		w->text[i] = w->text[i - 1];
	w->text[w->curpos++] = key;
	if ((w->curpos - w->offset) >= (w->width - 2))
		w->offset++;
	curses_widget_draw(w);
	curses_form_refresh(w->form);
	return(1);
}

int
curses_textbox_backspace_char(struct curses_widget *w)
{
	int len, i;
	
	if (!w->editable)
		return(0);

	len = strlen(w->text);
	if (w->curpos == 0)
		return(0);

	for (i = w->curpos; i <= len; i++)
		w->text[i - 1] = w->text[i];
	w->curpos--;
	if (w->curpos < w->offset)
		w->offset--;
	curses_widget_draw(w);
	curses_form_refresh(w->form);
	return(1);
}

int
curses_textbox_delete_char(struct curses_widget *w)
{
	unsigned int len, i;
	
	if (!w->editable)
		return(0);

	len = strlen(w->text);
	if (w->curpos == len)
		return(0);

	for (i = w->curpos + 1; i <= len; i++)
		w->text[i - 1] = w->text[i];
	curses_widget_draw(w);
	curses_form_refresh(w->form);
	return(1);
}

int
curses_textbox_set_text(struct curses_widget *w, const char *text)
{
	strlcpy(w->text, text, w->size);
	w->curpos = strlen(w->text);
	curses_widget_draw(w);
	curses_form_refresh(w->form);
	return(1);
}

/*** CHECKBOX WIDGETS ***/

int
curses_checkbox_toggle(struct curses_widget *w)
{
	if (!w->editable)
		return(0);
	w->amount = !w->amount;
	curses_widget_draw(w);
	curses_form_refresh(w->form);
	return(1);
}

/*** PROGRESS WIDGETS ***/

char spinny[5] = "/-\\|";

int
curses_progress_spin(struct curses_widget *w)
{
	struct curses_form *cf = w->form;
	int wx, wy;

	if (w->type != CURSES_PROGRESS)
		return(0);

	wx = w->x + 1 - cf->x_offset;
	wy = w->y + 1 - cf->y_offset;

	w->spin = (w->spin + 1) % 4;
	curses_colors_set(cf->win, CURSES_COLORS_FOCUS);/* XXX status ? */
	mvwaddch(cf->win, wy, wx + 1, spinny[w->spin]);

	return(1);
}


syntax highlighted by Code2HTML, v. 0.9.1