/*
	Checkbook - a demo of GIL scrolling list functions
	
	Final version:  This version allows the user to add,
	                edit, and delete checks.  Checks are
	                displayed in a scrolling list.
*/

#include "geosim.h"
#include 
#include 

/* Application parameters */
#define NAME "Checkbook"
#define IFILE "sclist.inf"
#define USE_BSTORE 1

#define NUM_COLUMNS 3
#define MAX_CHECKS  30

#define MIN_NUMBER 100
#define MAX_NUMBER 500

#define MIN_AMOUNT 1
#define MAX_AMOUNT 1000

#define MAX_PAYEE_LEN 30
/*   */


/* Interface element names */
#define LIST_WINDOW "ListWindow"

#define ADD_BTN "Add"
#define EDIT_BTN "Edit"
#define DELETE_BTN "Delete"

#define UP_BTN "list_up"
#define DOWN_BTN "list_down"
#define LIST_DA "list"
#define SCROLLBAR_DA "scroll_bar"

#define CHECK_WINDOW "CheckWindow"

#define NUMBER_FLD "number"
#define PAYEE_FLD "payee"
#define AMOUNT_FLD "amount"

#define NUMBER_INC_BTN "number_inc"
#define NUMBER_DEC_BTN "number_dec"
#define AMOUNT_INC_BTN "amount_inc"
#define AMOUNT_DEC_BTN "amount_dec"

#define CHAR_FUNC "char_func"

#define LIST_UP_BTN "list_up"
#define LIST_DOWN_BTN "list_down"
#define LIST_DA "list"
#define SCROLLBAR_DA "scroll_bar"
#define LIST_FLD "list_fld"
/*   */


/* Typedefs */
typedef struct {
	int number, amount;
	BigString payee;
	} CheckType;
	
typedef enum {
	NOT_SHOWN, ADD_STATE, EDIT_STATE
	} CheckWindState;
/*   */

/* Interface function prototypes */
bool redraw_func();
bool quit();
bool null_func();
bool add();
bool edit();
bool check_ok();
bool check_cancel();
bool char_func(int, int, char);

bool number_inc();
bool number_dec();
bool amount_inc();
bool amount_dec();

char *label_func(int slot);
void text_func(GSlistHandle list, int slot);
void in_func(int slot) { }
void out_func() { }
void click_func(int slot);

bool list(int x, int y, DragStatusType status);
bool scroll_bar(int x, int y, DragStatusType status);
bool list_up();
bool list_down();

/*   */


/* Other prototypes */
void initialize();
void init_data_window();
void display_checkwind();
void display_amount();
void display_payee();
void display_number();
/*   */

/* Globals */
CheckWindState wind_state = NOT_SHOWN;
CheckType edit_check;
int next_number;
int payee_len, ins_point;

GSlistHandle list_handle;
GSsclistHandle sclist_handle;
CheckType *check[MAX_CHECKS];
int num_checks, cur_item;
/*   */

#define NO_ITEM -1

void main(int argc, char *argv[])
{
	GSinterfaceinit(NAME, IFILE, USE_BSTORE, "wc", G640x480x256, argc, argv);
	initialize();
	GSinterface();
}


/* 
	initialize()
*/

void initialize()
{
	int x, y, wd, ht;
	
	GScharfunc(CHAR_FUNC);
	next_number=MIN_NUMBER;
	
	num_checks = 0;
	cur_item = NO_ITEM;
	
	GSsclistsetup(LIST_WINDOW, LIST_DA, /* window, field names */
					NUM_COLUMNS, /* number of columns */
					LIST_FLD, /* picklist field */
					0, 0,     /* row, column gaps */
					JUST_LEFT, /* label justification */
					label_func, /* list functions */
					text_func,
					in_func,
					out_func, 
					click_func,
					SCROLLBAR_DA, /* scroll bar dragarea name */
					GSconvertcolor("sclistcol"), /* colors */
					GSconvertcolor("sclisttl"),
					GSconvertcolor("sclistbr"),
					num_checks, 0, /* number of items, slot # of top item */
					NULL, /* scroll notification */
					LIST_UP_BTN, LIST_DOWN_BTN, /* button names */
					&list_handle, &sclist_handle); /* list handles */
					
	GSgetdragareaspecs(LIST_WINDOW, LIST_DA, &x, &y, &wd, &ht, NULL, NULL);
	GSsetcurrentnamedwindow(LIST_WINDOW);
	
	GSsetcolor(GSconvertcolor("black"));
	GSwdrawline(x-1, y-1, x-1, y+ht-1);
	GSwdrawline(x-1, y-1, x+wd-1, y-1);
	
	GSsetcolor(GSconvertcolor("dgray"));
	GSwdrawline(x+wd, y, x+wd, y+ht);
	GSwdrawline(x, y+ht, x+wd, y+ht);
	
	GSdrawsclist(sclist_handle);
}


/*
	redraw_func() : called when the GIL window needs to be redrawn
*/

bool redraw_func()
{
	int x, y, wd, ht;
	
	GSredrawnamedwindow("root");
	GSredrawnamedwindow(LIST_WINDOW);


	/* redraw list */

	GSgetdragareaspecs(LIST_WINDOW, LIST_DA, &x, &y, &wd, &ht, NULL, NULL);
	GSsetcurrentnamedwindow(LIST_WINDOW);
	
	GSsetcolor(GSconvertcolor("black"));
	GSwdrawline(x-1, y-1, x-1, y+ht-1);
	GSwdrawline(x-1, y-1, x+wd-1, y-1);
	
	GSsetcolor(GSconvertcolor("dgray"));
	GSwdrawline(x+wd, y, x+wd, y+ht);
	GSwdrawline(x, y+ht, x+wd, y+ht);
	
	GSdrawsclist(sclist_handle);
	
	if (wind_state != NOT_SHOWN) {
		GSredrawnamedwindow(CHECK_WINDOW);
		display_checkwind();
		}
}


/*
	quit() : called when the "Quit" button is pressed
*/

bool quit()
{
	GSquit(NULL);
	return TRUE;
}


/*
	add() : called when the "add" button is pressed
	Displays check entry window.
*/

bool add()
{
	edit_check.number = next_number++;
	edit_check.amount = 100;
	sprintf(edit_check.payee, "Payee");
	wind_state = ADD_STATE;
	
	init_data_window();
	
	return TRUE;
}


/*
	edit() : called when the "edit" button is pressed
	Displays check entry window.
*/

bool edit()
{
	if (cur_item == NO_ITEM) {
		/* shouldn't get here, but better safe than sorry... */
		GSdisablebutton("root", EDIT_BTN, TRUE);
		GSelog(0, "Error - edit button not disabled!\n");
		return;
		}
		
	edit_check.number = check[cur_item]->number;
	edit_check.amount = check[cur_item]->amount;
	strcpy(edit_check.payee, check[cur_item]->payee);
	wind_state = EDIT_STATE;
	
	init_data_window();
	
	return TRUE;
}


/* 
	delete_check() : called when the "delete" button is pressed
	Removes current item from list.
*/

bool delete_check()
{
	int i;
	
	if (cur_item == NO_ITEM) {
		/* shouldn't get here, but better safe than sorry... */
		GSdisablebutton("root", DELETE_BTN, TRUE);
		GSelog(0, "Error - delete button not disabled!\n");
		return;
		}
	
	free(check[cur_item]);
	
	for (i=cur_item; i < num_checks; i++)
		check[i] = check[i+1];
		
	num_checks--;
	cur_item = NO_ITEM;
	GSdisablebutton("root", EDIT_BTN, TRUE);
	GSdisablebutton("root", DELETE_BTN, TRUE);

	GSsetcurrentnamedwindow(LIST_WINDOW);

	GSsetsclistnumitems(sclist_handle, num_checks*3, TRUE);
}
	

/*
	init_data_window() : called from add() and edit()
	Initializes "+" and "-" buttons in check entry window,
	sets text entry variables (ins_point and payee_len),
	displays check window and draws contents.
*/

void init_data_window()
{
	if (edit_check.number <= MIN_NUMBER)
		GSdisablebutton(CHECK_WINDOW, NUMBER_DEC_BTN, FALSE);
	else
		GSenablebutton(CHECK_WINDOW, NUMBER_DEC_BTN, FALSE);
		
	if (edit_check.amount <= MIN_AMOUNT)
		GSdisablebutton(CHECK_WINDOW, AMOUNT_DEC_BTN, FALSE);
	else
		GSenablebutton(CHECK_WINDOW, AMOUNT_DEC_BTN, FALSE);
	
	if (edit_check.number >= MAX_NUMBER)
		GSdisablebutton(CHECK_WINDOW, NUMBER_INC_BTN, FALSE);
	else
		GSenablebutton(CHECK_WINDOW, NUMBER_INC_BTN, FALSE);
		
	if (edit_check.amount >= MAX_AMOUNT)
		GSdisablebutton(CHECK_WINDOW, AMOUNT_INC_BTN, FALSE);
	else
		GSenablebutton(CHECK_WINDOW, AMOUNT_INC_BTN, FALSE);
		
	payee_len = strlen(edit_check.payee);
	ins_point = payee_len;
	
	GSdrawnamedwindow(CHECK_WINDOW, POPUP, BSTORE);
	display_checkwind();
}


/*
	check_ok() : called when the "OK" button in the check entry
	             window is pressed
	Removes check entry window.
*/

bool check_ok()
{
	GSremovenamedwindow(CHECK_WINDOW);
	
	if (wind_state == ADD_STATE) {
	
		CheckType *new_check = (CheckType *) malloc(sizeof(CheckType));
		int i;
		
		if (new_check == NULL)
			GSelog(-1, "Out of memory for check %s\n", edit_check.payee);
		
		new_check->number = edit_check.number;
		new_check->amount = edit_check.amount;
		strcpy(new_check->payee, edit_check.payee);
		
		if (cur_item == NO_ITEM) /* add check to end of list */
			check[num_checks] = new_check;
		else { /* insert new check after current item */
			for (i=num_checks-1; i > cur_item; i--)
				check[i+1] = check[i];
			cur_item++;
			check[cur_item] = new_check;
			}
			
		num_checks++;
		
		GSsetsclistnumitems(sclist_handle, num_checks*3, TRUE);
		
		if (num_checks == MAX_CHECKS)
			GSdisablebutton("root", ADD_BTN, TRUE);
		}
	
	else if (wind_state == EDIT_STATE) {
		
		check[cur_item]->number = edit_check.number;
		check[cur_item]->amount = edit_check.amount;
		strcpy(check[cur_item]->payee, edit_check.payee);
		
		GSdrawsclist(sclist_handle);
		}	
		
	wind_state = NOT_SHOWN;
}


/*
	check_cancel() : called when the "Cancel" button in the check
	             entry window is pressed
	Removes check entry window.
*/

bool check_cancel()
{
	GSremovenamedwindow(CHECK_WINDOW);
	
	wind_state = NOT_SHOWN;
}


/*
	null_func() : "do-nothing" function;  fill-in for missing
	              interface functions
*/

bool null_func()
{
	return TRUE;
}


/*
	number_inc() : called when "+" button for check number
	               is pressed
*/

bool number_inc()
{
	if (edit_check.number == MIN_NUMBER)
		GSenablebutton(CHECK_WINDOW, NUMBER_DEC_BTN, TRUE);
		
	edit_check.number++;
	
	if (edit_check.number == MAX_NUMBER)
		GSdisablebutton(CHECK_WINDOW, NUMBER_INC_BTN, TRUE);
		
	display_number();
	
	return TRUE;
}


/*
	number_dec() : called when "-" button for check number
	               is pressed
*/

bool number_dec()
{
	if (edit_check.number == MAX_NUMBER)
		GSenablebutton(CHECK_WINDOW, NUMBER_INC_BTN, TRUE);
		
	edit_check.number--;
	
	if (edit_check.number == MIN_NUMBER)
		GSdisablebutton(CHECK_WINDOW, NUMBER_DEC_BTN, TRUE);
		
	display_number();
	
	return TRUE;
}


/*
	amount_inc() : called when "+" button for check amount
	               is pressed
*/

bool amount_inc()
{
	if (edit_check.amount == MIN_AMOUNT)
		GSenablebutton(CHECK_WINDOW, NUMBER_DEC_BTN, TRUE);
		
	edit_check.amount++;
	
	if (edit_check.amount == MAX_AMOUNT)
		GSdisablebutton(CHECK_WINDOW, NUMBER_INC_BTN, TRUE);
		
	display_amount();
	
	return TRUE;
}


/*
	number_dec() : called when "-" button for check amount
	               is pressed
*/

bool amount_dec()
{
	if (edit_check.amount == MAX_AMOUNT)
		GSenablebutton(CHECK_WINDOW, AMOUNT_INC_BTN, TRUE);
		
	edit_check.amount--;
	
	if (edit_check.amount == MIN_AMOUNT)
		GSdisablebutton(CHECK_WINDOW, AMOUNT_DEC_BTN, TRUE);
		
	display_amount();
	
	return TRUE;
}


/*
	display_amount() : called when check amount needs to be
	                   displayed
*/

void display_amount()
{
	int x, y, wd, ht;
	GSColor col;
	BigString str;
	
	GSsetcurrentnamedwindow(CHECK_WINDOW);
	GSsetfont(SMALL);
	
	GSgetfieldrect(AMOUNT_FLD, &x, &y, &wd, &ht, &col);
	
	GSsetcolor(col);	
	GSwfillrect(x, y, wd, ht);

	GSframefield(CHECK_WINDOW, AMOUNT_FLD, 
			GSconvertcolor("black"), GSconvertcolor("dgray"));

	GSsetcolor(GSconvertcolor("black"));

	sprintf(str, "$%d.%02d", edit_check.amount/100, /*(float)*/ (edit_check.amount%100));
	GSwwritemsg(x+1, y+SMALL+1, str);
}


/*
	display_payee() : called when check payee needs to be
	                  displayed
*/

void display_payee()
{
	int x, y, wd, ht;
	int curs_start, curs_len;
	char curs_char;
	
	GSColor col;
	BigString str;
	
	GSsetcurrentnamedwindow(CHECK_WINDOW);
	GSsetfont(SMALL);
	
	GSgetfieldrect(PAYEE_FLD, &x, &y, &wd, &ht, &col);
	
	GSsetcolor(col);	
	GSwfillrect(x, y, wd, ht);

	GSframefield(CHECK_WINDOW, PAYEE_FLD, 
			GSconvertcolor("black"), GSconvertcolor("dgray"));

	GSsetcolor(GSconvertcolor("black"));

	GSwwritemsg(x+1, y+SMALL+1, edit_check.payee);
	
	/* underline current position */
	
	sprintf(str, "%s", edit_check.payee);
	
	if (ins_point >= payee_len)
		curs_char = ' ';
	else
		curs_char = str[ins_point];
	
	str[ins_point] = '\0';
	curs_start = GStextwidth(str);
	str[0] = curs_char;
	str[1] = '\0';
	curs_len = GStextwidth(str);
	
	GSwdrawline(x+1+curs_start, y+SMALL+2, 
			x+1+curs_start+curs_len, y+SMALL+2);
}


/*
	display_number() : called when check number needs to be
	                   displayed
*/

void display_number()
{
	int x, y, wd, ht;
	GSColor col;
	BigString str;
	
	GSsetcurrentnamedwindow(CHECK_WINDOW);
	GSsetfont(SMALL);
	
	GSgetfieldrect(NUMBER_FLD, &x, &y, &wd, &ht, &col);
	
	GSsetcolor(col);	
	GSwfillrect(x, y, wd, ht);

	GSframefield(CHECK_WINDOW, NUMBER_FLD, 
			GSconvertcolor("black"), GSconvertcolor("dgray"));

	GSsetcolor(GSconvertcolor("black"));

	sprintf(str, "%d", edit_check.number);
	GSwwritemsg(x+1, y+SMALL+1, str);
}


/*
	display_checkwind() : called when entire check window 
	                      contents need to be displayed
*/

void display_checkwind()
{
	display_number();
	display_payee();
	display_amount();
}


/*
	char_func() : called by GIL when a key is pressed
	Processes key presses;  handles typing chars, backspace,
	and arrow keys.
*/

bool char_func(int x, int y, char ch)
{
	int i;
	
	/* if check entry window is not displayed, don't
	   do anything */
	   
	if (wind_state == NOT_SHOWN)
		return;
		
		
	/* update payee string and redraw payee field */
		
	switch (ch) {
	
		case KeyLeft:
			if (ins_point > 0)
				ins_point--;
			break;
			
		case KeyRight:
			if (ins_point < payee_len)
				ins_point++;
			break;
			
		case '\b':
			if (ins_point > 0) {
				
				/* delete character before ins_point */
				
				for (i=ins_point-1; i < payee_len; i++)
					edit_check.payee[i] = edit_check.payee[i+1];
					
				payee_len--;
				ins_point--;
				}
			break;
		
		default:
			if ((ch >= ' ') && (ch <= '~') && (payee_len < MAX_PAYEE_LEN)) {
				
				/* insert ch at ins_point */
				
				for (i=payee_len; i >= ins_point; i--)
					edit_check.payee[i+1] = edit_check.payee[i];
					
				edit_check.payee[ins_point] = ch;
				payee_len++;
				ins_point++;
				}
			break;
		}
	
	display_payee();
	
	return TRUE;
}


/*
	text_func(GSlistHandle list, int slot) : sets up color for item
*/

void text_func(GSlistHandle list, int slot)
{
	int check_num = slot / NUM_COLUMNS;

	if (check_num == cur_item)
		GSsetcolor(GSconvertcolor("curitem"));
	else
		GSsetcolor(GSconvertcolor("black"));
}	


/*
	label_func(int slot) : returns character string for list element
*/

char *label_func(int slot)
{
	static BigString label;
	int check_num = slot / NUM_COLUMNS;
	
	switch (slot % NUM_COLUMNS) {
		
		case 0: /* check number */
			sprintf(label, "%d", check[check_num]->number);
			break;
			
		case 1: /* check payee */
			sprintf(label, "%s", check[check_num]->payee);
			break;
			
		case 2: /* check amount */
			sprintf(label, "%d.%02d", 
				check[check_num]->amount/100, check[check_num]->amount%100);
			break;
		}
	
	return label;
}


/*
	click_func(int slot) : called when user clicks an item
*/

void click_func(int slot)
{
	int i, old_item = cur_item;
	
	/* Since there is a selected item, enable "Edit" and "Delete". */
	
	GSenablebutton("root", EDIT_BTN, TRUE);
	GSenablebutton("root", DELETE_BTN, TRUE);
	
	cur_item = slot / NUM_COLUMNS;
	
	GSsetcurrentnamedwindow(LIST_WINDOW);
	
	/* draw old and new list items to reflect selection change */
	
	if (old_item != NO_ITEM)
		for (i=0; i < NUM_COLUMNS; i++)
			GSdrawlistitem(list_handle, New, old_item*NUM_COLUMNS + i);

	for (i=0; i < NUM_COLUMNS; i++)
		GSdrawlistitem(list_handle, New, cur_item*NUM_COLUMNS + i);
}


/*
	list() : list dragarea callback
*/

bool list(int x, int y, DragStatusType status)
{
	GShandlelist(list_handle, x, y, status);
	return TRUE;
}
	

/*
	scroll_bar() : scroll bar dragarea callback
*/

bool scroll_bar(int x, int y, DragStatusType status)
{
	GShandlesclist(sclist_handle, x, y, status);
	return TRUE;
}


/*
	list_up() : up button function
*/

bool list_up()
{
	GShandleupbutton(sclist_handle);
	return TRUE;
}
	
	
/*
	list_down() : up button function
*/

bool list_down()
{
	GShandledownbutton(sclist_handle);
	return TRUE;
}
	
	
IntrFunction FuncNames[] = {
	{"redraw_func", redraw_func},
	{"quit", quit},
	{"add", add},
	{"edit", edit},
	{"delete", delete_check},
	{"list", list},
	{"list_up", list_up},
	{"list_down", list_down},
	{"scroll_bar", scroll_bar},
	{"OK", check_ok},
	{"Cancel", check_cancel},
	{"number_inc", number_inc},
	{"number_dec", number_dec},
	{"amount_inc", amount_inc},
	{"amount_dec", amount_dec},
	{"char_func", (bool(*)())char_func},
	};
	
int NumFuncs = GSnumifuncs(FuncNames);

back