Chapter 6. Sample Code and Cool Stuff

Table of Contents
6.1. Customizable Keypad
6.2. Handling Keyboard Events
6.3. A CwGraph Rotating Cube
6.4. A CwMatrix Spreadsheet

6.1. Customizable Keypad

Here is a customizable keypad class that lets you make keypads with any number of push-button keys in any arrangement, with whatever symbols you choose to assign to them. You can include a display line for entered text, and include special keys such as Shift, Clear, Delete, Enter, and so on, or define your own function keys.

The sample code loads and displays two example keypads, one a numeric keypad with English-language key labels, and the other alpha-numeric with French-language key labels. The definition files used to create the two keypads are shown in the Sample Keypad Definition Files section of this chapter. The source code of the Keypad class itself is given in The Keypad Class section.

6.1.3. The Keypad Class

/*
 * Create a Keypad based on a definition file.  We should be able to
 * handle multiple languages, not necessarily at runtime, but certainly
 * as part of the startup configuration.
 *
 * Todo:
 * 1) options for:
 *		- no titlebar
 *		- colors, so they are not hardcoded
 *		- block all windows?
 *
 * Revision 1.5  1999/05/17 18:36:46  manuel
 * Minor fixes: missing/unused locals; change local var names (max -> max_val).
 *
 * Revision 1.4  1999/04/23 19:02:57  manuel
 * Changed argument/variable name max to max_val to avoid conflist with
 * 	the max function; also min -> min_val
 *
 * Revision 1.3  1999/02/01 16:36:47  sam
 * Keypad definitions are now loaded from the require_path, and the numeric
 * keypad has a "+/-" key.
 *
 * Revision 1.2  1999/01/11 22:42:10  sam
 * Switches now bring up a digital keypad.
 *
 * Revision 1.1  1999/01/09 02:13:51  sam
 * the slider templates now pop up a keypad to enter a number into
 *
 */

//require ("constants.g");
require_lisp ("Modal.lsp");
require_lisp ("PhotonWidgets.lsp");

/* ---------------------- KeypadButton Class ------------------------ */

class KeypadButton PtButton
{
}

method KeypadButton.constructor ()
{
//	.fill_color = LIGHTBLUE;
//	.color = 0xffffff;
//	.arm_color = DARKBLUE;
	.flags = cons (Pt_FOCUS_RENDER, nil);
	.SetDim (50,50);
	.highlight_roundness = 2;
}

/* ---------------------- KeypadText Class ------------------------ */

class KeypadText PtText
{
	entered_string;
	password_char;
}

method KeypadText.AddChars (str)
{
	if (! .password_char)
	{
		.text_string = string (.text_string, str);
		.cursor_position = strlen (.text_string);
	}
	else
	{
		.entered_string = string (.entered_string, str);
		.cursor_position = strlen (.text_string);
		.text_string = string(.text_string, substr("*******************",
							    0,strlen(str)));
	}
}

method KeypadText.constructor ()
{
//	.fill_color = 0xffffff;
	.highlight_roundness = 0;
	.text_font = "helv18";
//	.top_border_color = TOPBORDER;
//	.bot_border_color = BOTBORDER;
	.border_width = 4;
	.flags = Pt_ETCH_HIGHLIGHT | Pt_HIGHLIGHTED;
	.flags = cons (Pt_SET, nil);
	.text_flags = cons ( Pt_CURSOR_VISIBLE |
			    Pt_EDITABLE |
			    Pt_CHANGE_ACTIVATE, nil);
	
	.horizontal_alignment = Pt_RIGHT;
}

/* ------------------------ Keypad Class --------------------------- */

class Keypad
{
	window;
	entryfield;
	grid;
	gridoffset;
	shifted;
	blockwindows;
	blockallwindows;
	finished;
	buttonfont = "helv14b";

	isnumeric;
	max;
	min;
	range;
	
}

method Keypad.CreateButton (x, y, w, h, str)
{
	local		but;

	but = new (KeypadButton);
	but.SetPos (x * .grid.w + .gridoffset.x, y * .grid.h + .gridoffset.y);
	but.SetDim (w * .grid.w - 2 * but.border_width,
				h * .grid.h - 2 * but.border_width);
	but.text_string = str;
	but.text_font = .buttonfont;
	PtRealizeWidget (but);
	but;
}

method Keypad.Initialize()
{
	.window = new (PtWindow);
	.window.fill_color = 0xccbb77;
//	.window.flags = Pt_DELAY_REALIZE;
	.window.render_flags = cons(Ph_WM_RENDER_TITLE, nil);
	.entryfield = new (KeypadText);
	PtExtentWidget (.entryfield);
}

method Keypad.OutOfRangeMessage (msg_string)
{
	local val;

	val = .entryfield.text_string;

	.entryfield.text_string = msg_string;
	PtFlush();
	
	nanosleep(1,0);
	.entryfield.text_string = val;	
}

	

/* ---------------------- Definition File Functions ---------------- */

/*
 * Functions callable through the definition file:
 * SetButton (x, y, w, h, str);
 * SetButtons (x, y, w, h, str ...);
 * SetExclButton (x, y, w, h, str);
 * SetExclButtons (x, y, w, h, str ...);
 * CancelButton (x, y, w, h, str);
 * ShiftButton (x, y, w, h, str);
 * SpaceButton (x, y, w, h, str);
 * DelButton (x, y, w, h, str);
 * EnterButton (x, y, w, h, str);
 * SetGrid (w, h);
 * GridOffset (x, y);
 * SetDim (w, h);
 * SetPos (w, h);
 */

method Keypad.SetDim (w, h)
{
	.window.SetDim (w, h);
	.entryfield.SetArea (0, 0, w - 4 * .entryfield.border_width, 24);
}

method Keypad.SetPos (x, y)
{
	.window.SetPos (x, y);
}

CLICK_EVENT = Pt_CB_ACTIVATE;

method Keypad.SetButton (x, y, w, h, str, exclusive ?= nil)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT,
			  `(@self).ButtonPress(@but, @exclusive));
	but;
}

method Keypad.SetExclButton (x, y, w, h, str)
{
	.SetButton(x, y, w, h, str, t);
}

method Keypad.SetButtons (x, y, w, h, strings...)
{
	local			str, but;

	with str in strings do
	{
		but = .SetButton (x, y, w, h, str);
		x += w;
	}
}

method Keypad.SetExclButtons (x, y, w, h, strings...)
{
	local			str, but;

	with str in strings do
	{
		but = .SetExclButton (x, y, w, h, str);
		x += w;
	}
}

method Keypad.CancelButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT, `(@self).Cancel ());
	but;
}

method Keypad.NewlineButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT, `(@self).Newline ());
	but;
}

method Keypad.ClearButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT, `(@self).Clear ());
	but;
}

method Keypad.ShiftButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	but.flags = Pt_TOGGLE;
	PtAttachCallback (but, CLICK_EVENT, `(@self).Shift ());
	but;
}

method Keypad.SpaceButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT, `(@self).Space ());
	but;
}

method Keypad.DelButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT, `(@self).Backspace ());
//	PtAttachCallback (but, REPEAT_EVENT, `(@self).Backspace ());
	but;
}

method Keypad.EnterButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT, `(@self).Accept ());
	but;
}

method Keypad.SignButton (x, y, w, h, str)
{
	local		but = .CreateButton (x, y, w, h, str);
	PtAttachCallback (but, CLICK_EVENT, `(@self).ChangeSign ());
	but;
}

method Keypad.SetGrid (w, h)
{
	.grid = new (PhDim);
	.grid.w = w;
	.grid.h = h;
}

method Keypad.GridOffset (x, y)
{
	.gridoffset = new (PhPoint);
	.gridoffset.x = x;
	.gridoffset.y = y + .entryfield.dim.h + 4 * .entryfield.border_width;
}

method Keypad.Font (font)
{
	.buttonfont = font;
}

/* ----------------------------- Callbacks ---------------------------- */

method Keypad.ButtonPress (button, exclusive ?= nil)
{
	local		str = button.text_string;
	if (.shifted)
		str = toupper (str);
	else
		str = tolower (str);

	if(exclusive) .Clear();

	.entryfield.AddChars (str);
}

method Keypad.Space ()
{
	.entryfield.AddChars (" ");
}

method Keypad.Newline ()
{
	.entryfield.AddChars ("\n");
}

method Keypad.Backspace ()
{
	local		str = .entryfield.text_string;
	.entryfield.text_string = substr (str, 0, strlen(str) - 1);

	if (.entryfield.password_char)
	{
		str = .entryfield.entered_string;
		.entryfield.entered_string = substr (str, 0, strlen(str) - 1);
	}
	
	.entryfield.AddChars ("");
}

method Keypad.Shift ()
{
	local		str = .entryfield.text_string;
	.shifted = !.shifted;
	if (.shifted)
		widget.flags = Pt_SET;
	else
		widget.flags = cons (Pt_SET,nil);
}

method Keypad.Accept ()
{
	local num;
	
	if (.isnumeric)
	{
		if (.max && .min)
		{
			num = number(.entryfield.text_string);

			if (num >= .min && num <= .max)
				.finished = #Accepted;
			else
				.OutOfRangeMessage(string("Valid Range: ",
				.min,"-",.max));
		
		}
	}
	else			
	{
		if (.range)
		{
			if (strlen(.entryfield.text_string) > .range)
			{
				.OutOfRangeMessage(string("Too long: ",
				.range, " chars max"));
			}
			else
			{
				.finished = #Accepted;
			}
		}
		else
		{
			.finished = #Accepted;
		}
	}
	
}

method Keypad.Cancel ()
{
	.finished = #Cancelled;
}

method Keypad.Clear ()
{
	.entryfield.text_string = "";
	.entryfield.AddChars ("");
}

method Keypad.ChangeSign()
{
	local text = .entryfield.text_string;

	if(!text) text = "";

	.Clear();

	local lead = substr(text, 0, 1);

	if(lead == "-")
	{
		text = substr(text, 1, -1);
		if(!text) text = "";
	}
	else if(lead == "+")
	{
		text = substr(text, 1, -1);
		if(!text) text = "";
		text = string("-", text);
	}
	else
	{
		// assume positive
		text = string("-", text);
	}

	.entryfield.AddChars(text);
}

/* -------------- Initialization and Control -------------------- */

method Keypad.LoadDefinition (filename, parent, path ?= nil)
{
	local		fptr, line, code;

	if (fptr = open_in_path (cons(".", path), filename, "r", t))
	{
		PtSetParentWidget(parent);
		.Initialize ();
		PtSetParentWidget (.window);
		while ((line = read (fptr)) != _eof_)
		{
			/* Quote the method name */
			rplaca (line, list (#quote, car(line)));
			code = `call (@self, @@line);
			eval (code);
		}
		close (fptr);
	}
	else
	{
		error(string("Keypad failed to load ", filename, "!"));
	}
}

method Keypad.RemoveTitle ()
{
	.window.render_flags = cons(Ph_WM_RENDER_TITLE |
				    Ph_WM_RENDER_RESIZE,nil);
}

method Keypad.ForceFront ( flag )
{
	if (flag)
		.window.state = Ph_WM_STATE_ISFRONT;
	else
		.window.state = Ph_WM_STATE_NORMAL;
}		

method Keypad.Show ()
{
	.entryfield.text_string = "";
	PtRealizeWidget (.window);
	PtWidgetToFront(.window);
}

method Keypad.Hide ()
{
	PtUnrealizeWidget (.window);
}

method Keypad.BlockWindows (winlist)
{
	local return = .blockwindows;
	.blockwindows = winlist;
	return;
}

method Keypad.BlockAllWindows (flag)
{
	local return = .blockallwindows;
	.blockallwindows = flag;
	return;
}

method Keypad.SetPasswordChar ( character )
{
	.entryfield.password_char = character;
}

method Keypad.SetNumeric ( )
{
	.isnumeric = t;
}

method Keypad.ClearNumeric ()
{
	.isnumeric = nil;
}
	
method Keypad.SetRange (min_val, max_val)
{
	.min = min_val;
	.max = max_val;
}

method Keypad.ClearRange ()
{
	.min = .max = nil;
}

method Keypad.Input (defaultstr)
{
	local i, win;
	
	if(.blockallwindows)
	{
		with i in _photon_widgets_ do
			if(i != .window && class_of(i) == PtWindow)
				.blockwindows = cons(i, .blockwindows);
	}

	.Show();
	.entryfield.text_string = defaultstr;
	.entryfield.entered_string = "";
	.finished = nil;
	PtSetParentWidget (nil);
	with win in .blockwindows do
		win.flags = Pt_BLOCKED;
	modal (nil, #.finished);
	with win in .blockwindows do
		win.flags = cons (Pt_BLOCKED,nil);

	.Hide();
	
	if (.finished == #Accepted)
		.entryfield.text_string;
	else
		nil;
}

method Keypad.NumericInput (defaultval, min_val, max_val)
{
//	princ("call Keypad.NumericInput()\n");

	local ret_val;
	
	.SetNumeric();
	.SetRange(min_val,max_val);
	ret_val = .Input(defaultval ? string(defaultval) : "");
	.ClearRange();
	.ClearNumeric();
	
//	princ("result: ", ret_val, "\n");

	if (ret_val)
	{
		if(strlen(ret_val) > 0)
			number(ret_val);
		else
			nil;
	}
	else
	{
		nil;
	}
}

method Keypad.PasswordInput ()
{
	local ret_val, inval;

	.SetPasswordChar("*");
	.entryfield.entered_string = "";
	inval = .Input("");
	.SetPasswordChar(nil);

	if (inval)
		ret_val = .entryfield.entered_string;
	ret_val;
}

 
		

Copyright 1995-2002 by Cogent Real-Time Systems, Inc.