Chapter 6. Sample Code and Cool Stuff

Table of Contents

6.1. Customizable Keypad
6.1.1. Create and Display Two Keypads
6.1.2. Sample Keypad Definition Files
6.1.3. The Keypad Class
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.1. Create and Display Two Keypads

#!/usr/cogent/bin/phgamma

require_lisp("PhotonWidgets");
require_lisp("const/Filesys.lsp");
require ("keypad.g");
require ("utility.g");

PtInit(nil);

window = new(PtWindow);
window.SetDim (700,380);
window.fill_color = PgRGB (250,200,180);

class USnumberKeypad Keypad
{
}

method USnumberKeypad.init(parent)
{
  .LoadDefinition("number_en_US.kdef", parent);
  .BlockAllWindows(t);
}

class FRalphaKeypad Keypad
{
}

method FRalphaKeypad.init(parent)
{
  .LoadDefinition("alpha_fr_FR.kdef", parent);
  .BlockAllWindows(t);
}

PtSetParentWidget(window);
kp1 = new(USnumberKeypad);
kp1.init(window);

PtSetParentWidget(window);
kp2 = new(FRalphaKeypad);
kp2.init(window);
kp2.SetPos(375,90);

PtRealizeWidget(window);
PtMainLoop();

6.1.2. Sample Keypad Definition Files

/*
 * Keypad definition file for English Numeric keypad
 */

SetGrid (30,30);
GridOffset (0, 0);
Font ("helv14b");
SetDim (9 * 30, 10 * 30 + .entryfield.dim.h + 4 * .entryfield.border_width);
SetButtons	(0, 0, 3, 2, "7", "8", "9");
SetButtons	(0, 2, 3, 2, "4", "5", "6");
SetButtons	(0, 4, 3, 2, "1", "2", "3");
SetButtons	(0, 6, 3, 2, ".", "0");
SignButton	(6, 6, 3, 2, "+/-");
DelButton	(6, 8, 3, 2, "<--");

Font ("helv10b");

CancelButton(0, 8, 2, 2, "Cancel");
ClearButton	(2, 8, 2, 2, "Clear");
EnterButton	(4, 8, 2, 2, "Accept");

/*
 * Keypad definition file for French Alpha keypad
 */

SetGrid (15,15);
GridOffset (0, 0);
SetDim (20 * 15, 11 * 15 + .entryfield.dim.h + 4 * .entryfield.border_width);
SetButtons (0, 0, 2, 2, "!", "@", "#", "$", "%", "^", "&", "*", "(", ")");
SetButtons (0, 2, 2, 2, "1", "2", "3", "4", "5", "6", "7", "8", "9", "0");
SetButtons (0, 4, 2, 2, "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P");
SetButtons (0, 6, 2, 2, "A", "S", "D", "F", "G", "H", "J", "K", "L", ".");
SetButtons (1, 8, 2, 2, "Z", "X", "C", "V", "B", "N", "M", "-", "_");

GridOffset (0, 0);
CancelButton (0, 11, 5, 2, "Cancelez");
ShiftButton (5, 11, 5, 2, "Shiftez");
DelButton (10, 11, 5, 2, "<--");
EnterButton (15, 11, 5, 2, "Acceptez");
SpaceButton (6, 13, 8, 2, "La Space");

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 = 0xaaaaff;
  .color = 0xffffff;
  .arm_fill = 1;
  .arm_color = 0x5555cc;
  .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 = "helv10b";
  
  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 = 0xaadddd;
  //	.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 ?= "")
{
  local		fptr, line, code;
  
  if (fptr = open(string(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 ", path, 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;
}