/*  MikMod example player
	(c) 1999 Miodrag Vallat and others - see file AUTHORS for
	complete list.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.
 
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
 
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id: mwidget.c,v 1.3 1999/07/05 04:00:02 miod Exp $

  Widget and Dialog creation functions

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "display.h"
#include <mikmod.h>
#include "player.h"
#include "mwindow.h"
#include "mwidget.h"

static void label_free(WID_LABEL *w)
{
	free(w->msg);
	free(w);
}

static void label_paint(DIALOG *d,WID_LABEL *w)
{
	char *start,*pos;
	int y=w->w.y;

	win_attrset(A_REVERSE);
	start=w->msg;
	for (pos=w->msg;*pos;pos++) {
		if (*pos=='\n') {
			*pos='\0';
			win_print(w->w.x,y,start);
			*pos='\n';
			start=pos+1;
			y++;
		}
	}
	win_print(w->w.x,y,start);
	win_attrset(A_NORMAL);
}

static int label_handle_event(DIALOG *d,WID_LABEL *w,WID_EVENT event,int ch)
{
	return 0;
}

static void label_get_size(WID_LABEL *w,int *width,int *height)
{
	char *pos;
	int x=0;
	
	*width=0;
	*height=0;
	for (pos=w->msg;*pos;pos++) {
		if (*pos=='\n') {
			(*height)++;
			if (x>*width) *width=x;
			x=-1;
		}
		x++;
	}
	if (x>*width) *width=x;
	(*height)++;
}

static void str_free(WID_STR *w)
{
	free(w->input);
	free(w);
}

static void str_paint(DIALOG *d,WID_STR *w)
{
	char hl[2]=" ",ch=' ',*pos=&w->input[w->start];
	int dx=0,len;

	if (w->w.has_focus) {
		hl[0]=ch=w->input[w->cur_pos];
		if (!hl[0]) hl[0]=' ';
		w->input[w->cur_pos]='\0';
		if (*pos) win_print(w->w.x,w->w.y,pos);
		dx=strlen(pos);
		win_attrset(A_REVERSE);
		win_print(w->w.x+dx,w->w.y,hl);
		win_attrset(A_NORMAL);
		pos+=dx;
		dx++;
		*pos=ch;
		if (*pos) pos++;
	}
	len=strlen(pos);
	if (len+dx>w->width) {
		ch=w->input[w->width+w->start];
		w->input[w->width+w->start]='\0';
	}
	win_print(w->w.x+dx,w->w.y,pos);
	if (len+dx>w->width)
		w->input[w->width+w->start]=ch;
	else if (len+dx<w->width) {
		dx+=len;
		for (len=0;len<w->width-dx;len++)
			storage[len]=' ';
		storage[len]='\0';
		win_print(w->w.x+dx,w->w.y,storage);
	}
}

static int check_input(DIALOG *d,WID_INT *w,int ret)
{
	if (ret &&(ret!=EVENT_HANDLED)&& w->w.handle_focus) {
		return w->w.handle_focus(d,(WIDGET*)w,ret);
	} else {
		if (ret==FOCUS_ACTIVATE) ret=FOCUS_NEXT;
		return ret;
	}
}

static int input_handle_event(DIALOG *d,WID_STR *w,WID_EVENT event,int ch,BOOL int_input)
{
	char *pos;
	int i;

	if (event==WID_HOTKEY) return 0;
	if (event==WID_GET_FOCUS) return EVENT_HANDLED;
	if ((event==WID_KEY) && w->w.handle_key) {
		i=w->w.handle_key(d,(WIDGET*)w,ch);
		if (i) return i;
	}

	switch (ch) {
		case KEY_UP:
			return check_input(d,(WID_INT*)w,FOCUS_PREV);
		case '\t':
		case KEY_DOWN:
			return check_input(d,(WID_INT*)w,FOCUS_NEXT);
		case KEY_LEFT:
		case CTRL_B:
			if (w->cur_pos>0) w->cur_pos--;
			break;
		case KEY_RIGHT:
		case CTRL_F:
			if (w->cur_pos<strlen(w->input)) w->cur_pos++;
			break;
		case KEY_PPAGE:
		case CTRL_A:
			w->cur_pos=0;
			break;
		case KEY_NPAGE:
		case CTRL_E:
			w->cur_pos=strlen(w->input);
			break;
		case CTRL_K:
			w->input[w->cur_pos]='\0';
			break;
		case CTRL_U:
			w->cur_pos=0;
			w->input[w->cur_pos]='\0';
			break;
		case KEY_DC:
		case CTRL_D:
#if !defined(__OS2__)&&!defined(__EMX__)			
		case KEY_ASCII_DEL:
#endif			
			if (w->cur_pos < strlen(w->input))
				for (pos=&w->input[w->cur_pos];*pos;pos++)
					*pos=*(pos+1);
			break;
		case KEY_BACKSPACE:
#if !defined(__OS2__)&&!defined(__EMX__)			
		case '\b':
#endif
			if (w->cur_pos>0) {
				for (pos=&w->input[w->cur_pos-1];*pos;pos++)
					*pos=*(pos+1);
				w->cur_pos--;
			}
			break;
		case KEY_ENTER:
		case '\r':
			return check_input(d,(WID_INT*)w,FOCUS_ACTIVATE);
		default:
			if (ch>=256 || !isprint(ch)) return 0;

			if ((int_input && isdigit(ch)) ||
				(!int_input && isprint(ch))) {
				i=strlen(w->input);
				if (i<w->length) {
					for (;i>=w->cur_pos;i--)
						w->input[i+1]=w->input[i];
					w->input[w->cur_pos]=ch;
					w->cur_pos++;
				}
			}
	}
	if (w->cur_pos<w->start) w->start=w->cur_pos;
	if (w->cur_pos>=w->start+w->width) w->start=w->cur_pos-w->width+1;
	str_paint(d,w);
	return EVENT_HANDLED;
}

static int str_handle_event(DIALOG *d,WID_STR *w,WID_EVENT event,int ch)
{
	return input_handle_event(d,w,event,ch,0);
}

static void str_get_size(WID_STR *w,int *width,int *height)
{
	*width=w->width;
	*height=1;
}

static void int_free(WID_INT *w)
{
	free(w->input);
	free(w);
}

static void int_paint(DIALOG *d,WID_INT *w)
{
	str_paint(d,(WID_STR*)w);
}

static BOOL int_handle_event(DIALOG *d,WID_INT *w,WID_EVENT event,int ch)
{
	return input_handle_event(d,(WID_STR*)w,event,ch,1);
}

static void int_get_size(WID_INT *w,int *width,int *height)
{
	*width=w->width;
	*height=1;
}

static void button_free(WID_BUTTON *w)
{
	free(w->button);
	free(w);
}

static void button_paint(DIALOG *d,WID_BUTTON *w)
{
	int cur,x,cnt_hl=0;
	char *pos,*hl_pos,*start,hl[2];
	BOOL end;

	for (pos=w->button;*pos;pos++)
		if (*pos=='&') cnt_hl++;
	x=(d->win->width-1-w->w.x-(strlen(w->button)+5*w->cnt-1-cnt_hl))/2;
	cur=0;
	hl_pos=NULL;hl[1]='\0';
	start=w->button;
	end=0;
	for (pos=w->button;!end;pos++) {
		end=!(*pos);
		if ((*pos == '|')||(*pos=='\0')) {
			*pos='\0';
			if ((w->active!=cur)||(!w->w.has_focus))
				win_attrset(A_REVERSE);
			win_print(w->w.x+x,w->w.y,"[ ");
			win_print(w->w.x+x+2,w->w.y,start);
			x+=strlen(start)+2;
			if (hl_pos) {
				if ((w->active!=cur)||(!w->w.has_focus))
					win_attrset(A_NORMAL);
				else
					win_attrset(A_REVERSE);
				win_print(w->w.x+x,w->w.y,hl);
				if ((w->active!=cur)||(!w->w.has_focus))
					win_attrset(A_REVERSE);
				else
					win_attrset(A_NORMAL);
				win_print(w->w.x+x+1,w->w.y,hl_pos);
				*(hl_pos-2)='&';
				x+=strlen(hl_pos)+1;
				hl_pos=NULL;
			}
			win_print(w->w.x+x,w->w.y," ]");
			if ((w->active!=cur)||(!w->w.has_focus))
				win_attrset(A_NORMAL);
			x+=4;
			*pos='|';
			start=pos+1;
			cur++;
		}
		if (*pos=='&') {
			*pos='\0';
			pos++;
			hl_pos=pos+1;
			hl[0]=*pos;
		}
	}
	*(pos-1)='\0';
}

static int focus_button(DIALOG *d,WID_BUTTON *w,int ret)
{
	if (ret &&(ret!=EVENT_HANDLED) && w->w.handle_focus) {
		return w->w.handle_focus(d,(WIDGET*)w,ret);
	} else {
		if (ret==FOCUS_ACTIVATE) {
			dialog_close(d);
			return EVENT_HANDLED;
		}
		return ret;
	}
}

static BOOL button_handle_event(DIALOG *d,WID_BUTTON *w,WID_EVENT event,int ch)
{
	int cur;
	char *pos;

	if (event==WID_GET_FOCUS) {
		if (ch<0)
			w->active=w->cnt-1;
		else
			w->active=0;
		return EVENT_HANDLED;
	}
	if ((event==WID_KEY)&&(w->w.handle_key)) {
		cur=w->w.handle_key(d,(WIDGET*)w,ch);
		if (cur) return cur;
	}
	if ((ch<256) &&(isalpha(ch))) ch=toupper(ch);
	switch (ch) {
		case KEY_UP:
		case KEY_LEFT:
			if (event==WID_KEY) {
				w->active--;
				if (w->active<0) return focus_button(d,w,FOCUS_PREV);
				button_paint(d,w);
			}
			break;
		case KEY_DOWN:
		case KEY_RIGHT:
		case '\t':
			if (event==WID_KEY) {
				w->active++;
				if (w->active>=w->cnt) return focus_button(d,w,FOCUS_NEXT);
				button_paint(d,w);
			}
			break;
		case KEY_ENTER:
		case '\r':
			if (event==WID_KEY) return focus_button(d,w,FOCUS_ACTIVATE);
			break;
		default:
			cur=0;
			for (pos=w->button; *pos; pos++) {
				if (*pos=='|') cur++;
				if (*pos=='&' && (*(pos+1)==ch)) {
					w->active=cur;
					button_paint(d,w);
					return focus_button(d,w,FOCUS_ACTIVATE);
				}
			}
			return 0;
	}
	return EVENT_HANDLED;
}

static void button_get_size(WID_BUTTON *w,int *width,int *height)
{
	char *pos;
	int hl_cnt=0;

	w->cnt=1;
	for (pos=w->button;*pos;pos++) {
		if (*pos=='&') hl_cnt++;
		if (*pos=='|') w->cnt++;
	}
	*width=strlen(w->button)+5*w->cnt-1-hl_cnt;
	*height=1;
}

static void dialog_add(DIALOG *d,WIDGET *w)
{
	d->widget=realloc(d->widget,(d->cnt+1)*sizeof(WIDGET*));
	d->widget[d->cnt]=w;
	d->cnt++;
}

static void widget_init(WIDGET *w,BOOL focus,int spacing)
{
	w->x=1;
	w->y=spacing;
	w->can_focus=focus;
	w->has_focus=0;
	w->handle_key=w->handle_focus=NULL;
	w->data=NULL;
}

WIDGET* wid_label_add(DIALOG *d,int spacing,char *msg)
{
	WID_LABEL *w=malloc(sizeof(WID_LABEL));
	
	widget_init((WIDGET*)w,0,spacing);
	w->w.type=TYPE_LABEL;
	w->w.w_free=(freeFunc)label_free;
	w->w.w_paint=(paintFunc)label_paint;
	w->w.w_handle_event=(handleEventFunc)label_handle_event;
	w->w.w_get_size=(getSizeFunc)label_get_size;

	w->msg=strdup(msg);
	dialog_add(d,(WIDGET*)w);
	return(WIDGET*)w;
}

#define STR_WIDTH 60
WIDGET* wid_str_add(DIALOG *d,int spacing,char *input,int length)
{
	int i;
	WID_STR *w=malloc(sizeof(WID_STR));

	widget_init((WIDGET*)w,1,spacing);
	w->w.type=TYPE_STR;
	w->w.w_free=(freeFunc)str_free;
	w->w.w_paint=(paintFunc)str_paint;
	w->w.w_handle_event=(handleEventFunc)str_handle_event;
	w->w.w_get_size=(getSizeFunc)str_get_size;

	w->width=STR_WIDTH;
	w->length=length;
	if (w->width>w->length) w->width=w->length+1;
	if (w->width<10) w->width=10;

	w->input=malloc(length+1);

	i=MIN(strlen(input),length);
	strncpy(w->input,input,i);
	w->input[i]='\0';
	w->cur_pos=strlen(w->input);

	w->start=w->cur_pos-w->width+1;
	if (w->start<0) w->start=0;

	dialog_add(d,(WIDGET*)w);
	return(WIDGET*)w;
}

#define INT_WIDTH 11
WIDGET* wid_int_add(DIALOG *d,int spacing,int value,int length)
{
	WID_INT *w=malloc(sizeof(WID_INT));
	
	widget_init((WIDGET*)w,1,spacing);
	w->w.type=TYPE_INT;
	w->w.w_free=(freeFunc)int_free;
	w->w.w_paint=(paintFunc)int_paint;
	w->w.w_handle_event=(handleEventFunc)int_handle_event;
	w->w.w_get_size=(getSizeFunc)int_get_size;

	w->width=INT_WIDTH;
	w->start=0;
	w->length=length;

	w->input=malloc(w->length+1);
	sprintf(w->input,"%d",value);
	w->cur_pos=strlen(w->input);

	dialog_add(d,(WIDGET*)w);
	return(WIDGET*)w;
}

WIDGET* wid_button_add(DIALOG *d,int spacing,char *button,int active)
{
	WID_BUTTON *w=malloc(sizeof(WID_BUTTON));
	
	widget_init((WIDGET*)w,1,spacing);
	w->w.type=TYPE_BUTTON;
	w->w.w_free=(freeFunc)button_free;
	w->w.w_paint=(paintFunc)button_paint;
	w->w.w_handle_event=(handleEventFunc)button_handle_event;
	w->w.w_get_size=(getSizeFunc)button_get_size;

	w->button=strdup(button);
	w->active=active;
	dialog_add(d,(WIDGET*)w);
	return(WIDGET*)w;
}

void wid_set_func(WIDGET *w,handleKeyFunc key,handleFocusFunc focus,void *data)
{
	w->handle_key=key;
	w->handle_focus=focus;
	w->data=data;
}

BOOL dialog_repaint(MWINDOW *win)
{
	DIALOG *d=win->data;
	int i=0;

	win_attrset(A_REVERSE);
	win_clear();
	win_attrset(A_NORMAL);
	for (i=0;i<d->cnt;i++)
		d->widget[i]->w_paint(d,d->widget[i]);

	return 1;
}

void dialog_close(DIALOG *d)
{
	int i;
	
	for (i=0;i<d->cnt;i++)
		d->widget[i]->w_free(d->widget[i]);
	if (d->cnt) free(d->widget);
	win_close_win(d->win);
	free(d);
}

static BOOL dialog_handle_key(MWINDOW *win,int ch)
{
	DIALOG *d=win->data;
	int ret,i;

	ret=d->widget[d->active]->w_handle_event(d,d->widget[d->active],WID_KEY,ch);
	if (!ret) {
		for (i=0;i<d->cnt && !ret;i++) {
			ret=d->widget[i]->w_handle_event(d,d->widget[i],WID_HOTKEY,ch);
			if (ret == FOCUS_ACTIVATE) {
				d->widget[d->active]->has_focus=0;
				d->widget[i]->has_focus=1;
				d->active=i;
				d->widget[d->active]->w_handle_event(d,d->widget[d->active],
				                                     WID_GET_FOCUS,ret);
				dialog_repaint(win);
			}
		}
	} else if (ret<EVENT_HANDLED) {
		d->widget[d->active]->has_focus=0;
		do {
			d->active+=ret;
			if (d->active<0) d->active=d->cnt-1;
			else if (d->active>=d->cnt) d->active=0;
		} while (!d->widget[d->active]->can_focus);
		d->widget[d->active]->has_focus=1;
		d->widget[d->active]->w_handle_event(d,d->widget[d->active],
		                                     WID_GET_FOCUS,ret);
		dialog_repaint(win);
	}
	return !!ret;
}

void dialog_open(DIALOG *d,char *title)
{
	int m_y,m_width=0,m_height=0,i,x,y,width=0,height=0;
	BOOL focus=1;

	i=0;
	while (i<d->cnt) { /* Init all widgets(position and focus) */
		m_height+=d->widget[i]->y;
		width=1;
		height=0;
		do { /* widgets in one row */
			d->widget[i]->w_get_size(d->widget[i],&x,&y);
			d->widget[i]->x=width;
			d->widget[i]->y=m_height;
			width += x+1;
			if (y>height) height=y;

			if (d->widget[i]->can_focus) {
				d->widget[i]->has_focus=focus;
				if (focus) d->active=i;
				focus=0;
			} else
				d->widget[i]->has_focus=0;
			i++;
		} while ((i<d->cnt) &&(d->widget[i]->y==-1));

		if (width>m_width) m_width=width;
		m_height+=height;
	}
	width=m_width;
	height=m_height;
	win_get_size_max(&m_y,&m_width,&m_height);
	x=(m_width-width)/2;
	if (x<0) x=0;
	y=(m_height-height)/2+m_y;
	if (y<m_y) y=m_y;

	if (!title) title="Dialog";
	win_open(x,y,width,height,1,title);
	win_set_repaint(dialog_repaint);
	win_set_handle_key(dialog_handle_key);
	win_set_data((void*)d);
	
	d->win=win_get_window();
	dialog_repaint(d->win);
}

DIALOG* dialog_new(void)
{
	DIALOG *d=malloc(sizeof(DIALOG));
	
	d->active=0;
	d->cnt=0;
	d->win=NULL;
	d->widget=NULL;
	return d;
}

/* ex:set ts=4: */
