Another Way to Handle Input

AKA Pointers Are Your Friends

Before we begin, to read this article you must know:

  • What a Windows Message is and how it can be handled
  • What a Function Pointer is

In this article I'm going to explain another way to handle input apart from the traditional keys[ ] array. I used to mess up the code with the keys[ ] because I always add new keys or even keyboard modes when I'm coding. One incorrect brace can drive the compiler mad. So, my aim is to make a system where you can just register a key and its function. It will support several keyboard modes (menu mode, game mode, limbo mode,…..). I will also add a function for text editing and a function to scan what number a key has.

Source code can be found at the end of this article... with that said... here we go...

First, we make a header file:

// kbdctrl.h

#ifndef kbdctrl
#define kbdctrl
	void KC_init( LRESULT (*)(HWND, UINT , WPARAM ,LPARAM));
	void KC_SetMode(int);
	int KC_GetMode (void);
	void KC_SetKey(int, void( *)(int, int));
	void KC_SetKey(char, void( *)(int, int));
	void KC_SetMouse(int, void( *)(int, int));
	void KC_Editor(char *, int, bool *);
	void KC_Scan(int *, bool *);
	LRESULT KC_Parse(HWND,UINT,WPARAM,LPARAM);
#endif

Then, the source file.

// kbdctrl.cpp

#include <windows.h>
#include "kbdctrl.h"

The principle is simple, but the syntax is not. The base of our ‘system’ is a two dimensional array of pointers: the first index number is the mode, the second indicates the key the function is bound to, the value is a pointer to the function bound to that key. All the functions must return void and accept two integers as argument. I’ll reserve space for 8 modes, 256 keyboard keys and 10 mouse keys. (The keyword static can be omitted).

	static void ( *event_ptr[8][266])(int, int);

As you see, the syntax is rather odd; this is why function pointers are used so seldom.

static LRESULT ( *nextproc)(HWND, UINT , WPARAM ,LPARAM) = &DefWindowProc;

This variable will hold the pointer to the next Windows message handler. All unprocessed messages will be directed to it. It is DefWindowProc (the default handler) by default.

static int curmod = 0;

This int holds the number of the current mode (between 0 and 7).

static bool editor = false;					// Is The Editor Active
static char *ed_string;						// Pointer To The String Containing The Typed Data
static int ed_offset = 0;					// Cursor Position, The Index Of The NEXT Character In The String
static int ed_length = 0;					// Length Of ed_string
static bool *ed_done;						// Pointer To The Variable That Is Set To True If Enter Is Pressed (And The Editor Ends)

These are the global variables used by the editor mode.

static bool scan = false;					// Is The Scanner Active
static bool *sc_done;						// Pointer To The Variable Set To True If A Key Has Been Pressed
static int *sc_value;						// Pointer To The Variable Holding The Value Of The Key

For the scan mode.

void KC_EXEC(int pos, int x, int y)
{
	if(event_ptr[curmod][pos] != NULL)
		(*event_ptr[curmod][pos])(x,y);
}

This little function invokes the pos function in the current mode with parameters x and y if the pointer is not null. If the pointer is null, there is no function and no execution is needed.

void KC_init(LRESULT(*next)(HWND, UINT , WPARAM ,LPARAM))
{
	nextproc = next;
}

This function is used to register the Windows message handler that can handle the unprocessed messages.

void KC_SetMode(int mod)
{
	curmod = mod;
}

int KC_GetMode (void)
{
	return curmod;
}

Two functions to get and set the mode.

void KC_SetKey(int token, void( *event)(int, int))
{
	event_ptr[curmod][token] = event;
}

This function binds a function to a key: token holds the key number and void(*event)(int,int) holds a pointer to a function.

void KC_SetKey(char token, void( *event)(int, int))
{
	event_ptr[curmod][token] = event;
}

This function is an overloaded version of the former and can be altered if the conversion char-int is incorrect.

void KC_SetMouse(int token, void( *event)(int, int))
{
	// ********************************************
	// 	mouse tokens:	+0 double-click
	// 			+1 pressed
	// 			+2 released
	// 
	// 	[0-2] left [3-5] right [6-8] middle
	// 
	// 			 9 mouse moved
	// ********************************************
	// 	No Scrolling Support Yet
	// ********************************************

	event_ptr[curmod][token+256] = event;
}

This function is used to register a mouse key:

0 left double-click
1 left pressed
2 left released
3 right double-click
4 right pressed
5 right released
6 middle double-click
7 middle pressed
8 middle released
9 mouse moved
void KC_Editor(char *str, int len, bool *done)
{
	ed_string	= str;
	ed_offset	= 0;
	ed_length	= len;
	ed_done		= done;
	editor		= true;
}

void KC_Scan(int *val, bool *done)
{
	sc_value	= val;
	sc_done		= done;
	scan		= true;
}

These two functions register the values for the editor and scanner and enable them. The use of these functions might need a little explanation. Because we can’t wait for the functions to return data (the engine must go on), the data and state are stored in a variable the parent function can access (the pointer is passed, not the value). When the *done is true, the parent function knows the correct value is in the variable (str[ ] and *val) and can take ‘the appropriate steps’.

Now the big one...

LRESULT KC_Parse(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)

The KC_Parse funtion is to be used as a Windows message handler, this explains the rather odd data types used as return value and arguments.

{
	if(scan)
	{
		if(uMsg == WM_KEYDOWN)
		{
			*sc_value	= wParam;
			*sc_done	= true;
			scan		= false;
			return 0;
		}
	}

This is clear, I guess, if we are in scan mode and a key is held down, store the key number and set *sc_done to true, so the parent function knows the number is ready. 0 is returned, so, when in scan mode, there is no other mouse or keyboard input possible.

	else
	{
		if(editor)
		{

If in editor mode...

			if(uMsg == WM_CHAR)
			{

and a WM_CHAR message is received (this message contains a character, not a key number, so it can be used like that).

				if(wParam == VK_RETURN)
				{
					editor		= false;
					*ed_done	= true;
					return 0;
				}

If enter is pressed, end editor mode, and tell the parent the string is ready (AND return null).

				if((wParam == VK_BACK) && (ed_offset != 0) )
				{
					ed_string[--ed_offset] = NULL;
					return 0;
				}

If backspace is pressed and the cursor is not on the 0th position, set the previous character in the string to null and point the offset(‘cursor’ position) to it. (AND return 0).

				if(ed_offset < ed_length)
				{
					ed_string[ed_offset++] = wParam;
				}

If the string isn’t full (offset < length and not offset

				return 0;
			}
		}

return 0, so, that if in editor mode, a character message ends the function. The KEYDOWN message (that always comes with the character message) is not processed because of the else statement on the next line. This, in short, means that all keyboard commands are blocked, BUT, mouse input is still processed.

		else
		{
			switch (uMsg)
			{
				case WM_KEYDOWN:
				{
					KC_EXEC(wParam,1,0);	// Send 1,0
					return 0;
				}

				case WM_KEYUP:
				{
					KC_EXEC(wParam,0,0);	// Send 0,0
					return 0;
				}
			};
		}

So, when we aren’t in edit mode, keyboard input is processed. A KEYDOWN message executes the function corresponding with the key code, NOT to the character code (they often match, but not always). The function gets 1,0 as parameters. If a KEYUP is received, the parameters are 0,0.

		switch (uMsg)					// Check For Windows Messages
		{
			case WM_LBUTTONDBLCLK:
			{
				KC_EXEC(256,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_LBUTTONDOWN:
			{
				KC_EXEC(257,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_LBUTTONUP:
			{
				KC_EXEC(258,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_MBUTTONDBLCLK:
			{
				KC_EXEC(259,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_MBUTTONDOWN:
			{
				KC_EXEC(260,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_MBUTTONUP:
			{
				KC_EXEC(261,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}
       
			case WM_RBUTTONDBLCLK:
			{
				KC_EXEC(262,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_RBUTTONDOWN:
			{
				KC_EXEC(263,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_RBUTTONUP:
			{
				KC_EXEC(264,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

			case WM_MOUSEMOVE:
			{
				KC_EXEC(265,LOWORD(lParam),HIWORD(lParam));
				return 0;
			}

The mouse code. If a message is received, the corresponding function is executed. The parameters are the x and y position of the mouse relative to the upper left corner of the window. (for the LOWORD and HIGHWORD stuff, please refer to MSDN).

		};
	}

	return ( *nextproc)(hWnd,uMsg,wParam,lParam);
}

The unprocessed messages are sent to the next handler (DefWindowProc by default).

So, to make the use clear, I will implement a NeHe program using some misc functions, and full 3D movement. It, however, is only a test suit I used for tracing bugs, so it isn’t exactly esthetical (if any one wants to make something more fancy, please do so and let me know).

#include <stdlib.h>
#include <stdio.h>
#include <windows.h>						// Header File For Windows
#include <stdio.h>						// Header File For Standard Input/Output
#include <gl\gl.h>						// Header File For The OpenGL32 Library
#include <gl\glu.h>						// Header File For The GLu32 Library
#include <gl\glaux.h>						// Header File For The Glaux Library
#include <math.h>
#include "kbdctrl.h"

#define STRIDE 2						// Length Of One ‘pace’

#define piover180 0.01745329252f

GLYPHMETRICSFLOAT gmf[96];

GLuint fontbase;

Variables for 3D movement.

GLfloat xpos,ypos,zpos;
GLfloat xrot, yrot;

bool    done;
bool	active		= TRUE;					// Window Active Flag Set To TRUE By Default
bool	fullscreen	= TRUE;					// Fullscreen Flag Set To Fullscreen Mode By Default

GLfloat modif = 2.0f;

Mouse sensitivity (the smaller the more sensitive).

bool	test		= false;

Enable or disable the test data output.

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);		// Declaration For WndProc
void func1 (int, int);						// When F1 Is Pressed, This Function Is Needed


GLvoid BuildFont(GLvoid)					// Build Our Bitmap Font
{
	HFONT	font;						// Windows Font ID
	HFONT	oldfont;					// Used For Good House Keeping
	
	fontbase = glGenLists(96);				// Storage For 96 Characters ( NEW )

	font = CreateFont(	-10,
				0,				// Width Of Font
           			0,				// Angle Of Escapement
				0,				// Orientation Angle
				FW_BOLD,			// Font Weight
				FALSE,				// Italic
				FALSE,				// Underline
				FALSE,				// Strikeout
				ANSI_CHARSET,			// Character Set Identifier
				OUT_TT_PRECIS,			// Output Precision
				CLIP_DEFAULT_PRECIS,		// Clipping Precision
				ANTIALIASED_QUALITY,		// Output Quality
				FF_DONTCARE|DEFAULT_PITCH,	// Family And Pitch
				"Courier New");			// Font Name
	
	oldfont = (HFONT)SelectObject(hDC, font);		// Selects The Font We Want
	wglUseFontOutlines(hDC, 32, 96,fontbase,0.0f,0.0f,WGL_FONT_POLYGONS,gmf);
	SelectObject(hDC, oldfont);				// Selects The Font We Want
	DeleteObject(font);					// Delete The Font
}

GLvoid KillFont(GLvoid)						// Delete The Font List
{
	glDeleteLists(fontbase, 96);				// Delete All 96 Characters ( NEW )
}

The buildfont and killfont functions, they are explained in lesson 13 and 14 of the NeHe Tuts.

GLvoid glPrint(GLfloat xpos, GLfloat ypos, GLfloat zpos, GLfloat scale, char *string)
{

An adapted glPrint function, with scaling (making the font bigger or smaller).

	glPushAttrib(GL_LIST_BIT);				// Pushes The Display List Bits
	glPushMatrix();						// Pushes The Matrix
	glListBase(fontbase - 32);				// Sets The Base Character to 32
	glTranslatef(xpos,ypos,zpos);
	glScalef(scale,scale,1.0f);
	glCallLists(strlen(string), GL_UNSIGNED_BYTE, string);	// Draws The Display List Text
	glPopMatrix();						// Pops The Matrix
	glPopAttrib();						// Pops The Display List Bits
}

This should be clear.

void mousemove (int x, int y)
{
	xrot += (x-320)/modif;
	yrot += (y-240)/modif;
}

The simplest mouse function there is, unfortunately, to simple to work

void straferight (int x, int y)
{
	GLfloat  cosx = cos(xrot * piover180);
	xpos += STRIDE * cos(yrot * piover180) * cosx;
	zpos += STRIDE * sin(yrot * piover180) * cosx;
}

void strafeleft (int x, int y)
{
	GLfloat cosx = cos(xrot * piover180);
	xpos -= STRIDE * cos(yrot * piover180) * cosx;
	zpos -= STRIDE * sin(yrot * piover180) * cosx;
}

Functions for strafe left and right, and no y movement (for the math, it isn’t to hard to find that out, so, take a piece of paper and get it done)

void downmove(int x, int y)
{
	GLfloat cosx = cos(xrot * piover180);
	xpos += STRIDE * sin(yrot * piover180) * cosx;
	ypos -= STRIDE * sin(xrot * piover180);
	zpos += STRIDE * cos(yrot * piover180) * cosx;
}
	
void upmove(int x, int y)
{	
	GLfloat  cosx = cos(xrot * piover180);
	xpos -= STRIDE * sin(yrot * piover180) * cosx;
	ypos += STRIDE * sin(xrot * piover180);
	zpos -= STRIDE * cos(yrot * piover180) * cosx;
}

Moving forward and backward

void MO_mousemove(int x, int y)
{

This is a more decent mouse function.

	static int MO_x, MO_y;

The keyword static might need some explanation: if a variable is declared static, it is allocated statically, so each time you call this function, the value is still there from the last run. The only way of removing it from memory is program termination. It has all the properties of a global variable, except that it is only visible in this function. The variable hold the position the cursor had when the function was called the last time.

	yrot += (MO_x - x) /modif;
	xrot += (MO_y - y) /modif;

The x and y movements are calculated relative to the last time the function was used and dived by the speed modifier.

	if(x < 20 || x > 620 || y < 20 || y > 460)
	{	
		MO_x = 320;
		MO_y = 240;
		SetCursorPos(320,240);
	}

If the cursor approaches on of the borders of the window, reset it to the center.

	else
	{
		MO_x = x;
		MO_y = y;
	}
}

If not, store the current position for the next run.

void stop(int x, int y)
{
	done=TRUE;						// End The Main Loop
}

void AN_act(int x, int y)
{
	KC_SetMode(1);						// Go To Mode 2
}

void act(int x, int y)
{
	ShowCursor(true);					// Show The Cursor
	KC_SetMode(0);						// Go To Mode 0
}

void modif_up(int x, int y)
{
	modif += 0.1f;						// Lower Mouse Speed
}

void modif_down(int x, int y)
{
	modif -= 0.1f;						// Raise Mouse Speed
}

void MO_act(int x, int y)
{
	ShowCursor(false); 					// Hide Cursor
	KC_SetMode(2);						// Go To Mode 2
}

void reset(int x, int y)					// Reset Movement
{
	xrot = 0.0f;
	yrot = 0.0f;
	xpos = 0.0f;
	ypos = 0.0f;
	zpos = 0.0f;
}

Some more functions to be bound to the keys, most of them are self-explanatory.

void oneshot(int x, int y)
{
	static bool active = false;
	if(!active && x == 1)
	{
		active = true;
		test=!test;
	}

	if(x == 0)
	{
		active = false;
	}
}

This is the one shot function, it is used to ‘switch’ (holding the button only triggers it once). Once more, I use a static variable and not a global. It is not, like it might seem, set to false each time the function is triggered, but only when the program is started.

void ED_act(int x, int y)
{

This one is tricky, it has it’s own main loop, so seems to stop the engine and go on on it’s own.

	KC_SetMode(3);

The mode is set to three, (an empty one, so no input is processed except the edit line)

	bool ED_done = false;
	char ED_string [30];

The two variables need by the editor: the string and the status.

	memset (ED_string,NULL,30);

Fill the string with 0 (otherwise it might do weird things).

	KC_Editor(ED_string,30,&ED_done);

Start the editor.

	MSG msg;

	while(!ED_done)						// Loop That Runs While done=FALSE
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))	// Is There A Message Waiting?
		{
			if (msg.message==WM_QUIT)		// Have We Received A Quit Message?
			{
				done=TRUE;			// If So done=TRUE
			}
			else					// If Not, Deal With Window Messages
			{
				TranslateMessage(&msg);		// Translate The Message
				DispatchMessage(&msg);		// Dispatch The Message
			}
		}
		else						// If There Are No Messages
		{
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			glLoadIdentity();
			glPrint(0.0f,0.0f,-20.0f,0.5f, ED_string);

			SwapBuffers(hDC);			// Swap Buffers (Double Buffering)
		}
	}

	KC_SetMode(2);
 }  

The special main loop, going as long as the editor hasn’t given an OK and printing out the string (see tutorial 1).

GLvoid StartKC(GLvoid)
{
	KC_SetMode(0);						// Mode Is Set To 0, Mode With Crappy Mouse Movement
	KC_SetKey(VK_F1, &func1);				// Register F1
	KC_SetKey(VK_UP, &upmove);
	KC_SetKey(VK_DOWN, &downmove);
	KC_SetKey(VK_LEFT, &strafeleft);
	KC_SetKey(VK_RIGHT, &straferight);
	KC_SetMouse(9, &mousemove);				// Register Mouse Movement
	KC_SetKey(VK_ESCAPE, &stop);				// Register Escape
	KC_SetKey('A', &act);					// Register A Key To Mode 0
	KC_SetKey('Z', &AN_act);				// Register Z Key To Mode 1
	KC_SetKey('E', &MO_act);				// Register E Key To Mode 3 
	KC_SetKey('R', &reset);					// Register R Key To Reset
	KC_SetKey('T', &oneshot);				// Register T Key To The Switch Function
	KC_SetKey('Y', &ED_act);				// Register Y Key To The Editor (And The Special Main Loop)
	KC_SetMode(1);						// Mode Is Set To 1, Beter Mouse Movement
	KC_SetKey(VK_F1, &func1);
	KC_SetKey(VK_UP, &upmove);
	KC_SetKey(VK_DOWN, &downmove);
	KC_SetKey(VK_LEFT, &strafeleft);
	KC_SetKey(VK_RIGHT, &straferight);
	KC_SetMouse(9, &MO_mousemove);
	KC_SetKey(VK_ESCAPE, &stop);
	KC_SetKey('A', &act);
	KC_SetKey('Z', &AN_act);
	KC_SetKey('E', &MO_act);
	KC_SetKey('R', &reset);
	KC_SetKey('T', &oneshot);
	KC_SetMode(2);						// Mode Is Set To Two, Up And Down Are Now Used For Altering Scroll Speed
	KC_SetKey(VK_F1, &func1);
	KC_SetKey(VK_UP, &modif_up);
	KC_SetKey(VK_DOWN, &modif_down);
	KC_SetMouse(9, &MO_mousemove);
	KC_SetKey(VK_ESCAPE, &stop);
	KC_SetKey('A', &act);
	KC_SetKey('Z', &AN_act);
	KC_SetKey('E', &MO_act);
	KC_SetKey('R', &reset);
	KC_SetKey('T', &oneshot);

   	// Mode 3 Is Empty To Block Mouse Input In Editor Mode
}

Registration of the keys and their functions.

int InitGL(GLvoid)						// All Setup For OpenGL Goes Here
{
	glEnable(GL_TEXTURE_2D);				// Enable Texture Mapping ( NEW )
	glShadeModel(GL_SMOOTH);				// Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);			// Black Background
	glClearDepth(1.0f);					// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);				// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);					// The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations
	StartKC();
	BuildFont();
	return TRUE;						// Initialization Went OK
}

Two lines must be inserted: StartKC(); and BuildFont();

int DrawGLScene(GLvoid)						// Here's Where We Do All The Drawing
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear The Screen And The Depth Buffer
	glLoadIdentity();					// Reset The View

Clear the buffers and reset view.

	if(test)
	{
		char str[50];
		sprintf(str, "%d %f ", KC_GetMode(), modif);
		glPrint(0.0f,0.0f,-10.0f,0.5f, str);
		sprintf(str, "%f  %f", xrot, yrot);
		glPrint(-2.0f,-2.0f,-10.0f,0.5f, str);
	}

If test mode is on, print out the mode and mouse modification variable an under it the x and y rotation.

	glRotatef(-xrot,1.0,0.0,0.0);
	glRotatef(-yrot,0.0,1.0,0.0);
	glTranslatef(-xpos, -ypos, -zpos);

Transformations for 3D movement (all following code should move correct).

	glBegin(GL_LINES);
	glColor3f(1.0f,0.0f,0.0f);
	glVertex3f(-10.0f, 0.0f , 0.0f);
	glColor3f(0.0f,1.0f,0.0f);
	glVertex3f(10.0f, 0.0f , 0.0f);
	glColor3f(0.0f,1.0f,0.0f);
	glVertex3f(0.0f, -10.0f , 0.0f);
	glColor3f(0.0f,0.0f,1.0f);
	glVertex3f(0.0f, 10.0f , 0.0f);
	glColor3f(1.0f,0.0f,1.0f);
	glVertex3f(0.0f, 0.0f , -10.0f);
	glColor3f(1.0f,1.0f,0.0f);
	glVertex3f(0.0f, 0.0f , 10.0f);
	glEnd();

A very simple cross indicating the axis.

	glColor3f(1.0f,0.0f,0.0f);
	glPrint(0.0f,0.0f,0.0f,5.0f, "help");
	glPrint(0.0f,1.0f,12.0f,5.0f, "help2");

Two times the word help, to see if you are moving upward down or not.

	return TRUE;						// Keep Going
}

On the end of the KillGLWindow, the line.

	KillFont();

Must be inserted.

void func1 (int x, int y)
{
	static bool active = false;

	if(!active && x == 1)
	{
		active = true;
		KillGLWindow();					// Kill Our Current Window
		fullscreen=!fullscreen;				// Toggle Fullscreen / Windowed Mode

		// Recreate Our OpenGL Window
		CreateGLWindow("NeHe's Texture Mapping Tutorial",640,480,16,fullscreen)
	}

	if(x == 0)
	{
		active = false;
	}
}

A switch function for the F1 key.

LRESULT CALLBACK WndProc(	HWND	hWnd,			// Handle For This Window
				UINT	uMsg,			// Message For This Window
				WPARAM	wParam,			// Additional Message Information
				LPARAM	lParam)			// Additional Message Information
{
	switch (uMsg)						// Check For Windows Messages
	{
		case WM_ACTIVATE:				// Watch For Window Activate Message
		{
			if (!HIWORD(wParam))			// Check Minimization State
			{
				active=TRUE;			// Program Is Active
			}
			else
			{
				active=FALSE;			// Program Is No Longer Active
			}

			return 0;				// Return To The Message Loop
		}

		case WM_SYSCOMMAND:				// Intercept System Commands
		{
			switch (wParam)				// Check System Calls
			{
				case SC_SCREENSAVE:		// Screensaver Trying To Start?
				case SC_MONITORPOWER:		// Monitor Trying To Enter Powersave?
				return 0;			// Prevent From Happening
			}
			break;					// Exit
		}

		case WM_CLOSE:					// Did We Receive A Close Message?
		{
			PostQuitMessage(0);			// Send A Quit Message
			return 0;				// Jump Back
		}

		case WM_SIZE:					// Resize The OpenGL Window
		{
			// LoWord=Width, HiWord=Height
			ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));
			return 0;				// Jump Back
		}
	}

	// Pass All Unhandled Messages To DefWindowProc
	return KC_Parse(hWnd,uMsg,wParam,lParam);
}

the altered WndProc, passing all unprocessed message to the parser

int WINAPI WinMain(	HINSTANCE	hInstance,		// Instance
			HINSTANCE	hPrevInstance,		// Previous Instance
			LPSTR		lpCmdLine,		// Command Line Parameters
			int		nCmdShow)		// Window Show State
{
	MSG msg;						// Windows Message Structure
	done=FALSE;						// Bool Variable To Exit Loop

	// Ask The User Which Screen Mode They Prefer
	if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
	{
		fullscreen=FALSE;				// Windowed Mode
	}

	// Create Our OpenGL Window
	if (!CreateGLWindow("NeHe's Texture Mapping Tutorial",640,480,16,fullscreen))
	{
		return 0;					// Quit If Window Was Not Created
	}

	while(!done)						// Loop That Runs While done=FALSE
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))	// Is There A Message Waiting?
		{
			if (msg.message==WM_QUIT)		// Have We Received A Quit Message?
			{
				done=TRUE;			// If So done=TRUE
			}
			else					// If Not, Deal With Window Messages
			{
				TranslateMessage(&msg);		// Translate The Message
				DispatchMessage(&msg);		// Dispatch The Message
			}
		}
		else						// If There Are No Messages
		{
			// Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
			if ((active && !DrawGLScene()))		// Active?  Was There A Quit Received?
			{
				done=TRUE;			// ESC or DrawGLScene Signalled A Quit
			}
			else					// Not Time To Quit, Update Screen
			{
				SwapBuffers(hDC);		// Swap Buffers (Double Buffering)
			}
		}
	}

	// Shutdown
	KillGLWindow();						// Kill The Window
	return (msg.wParam);					// Exit The Program
}

The WinMain, no longer handling the keyboard.

So, to end, I would like to discuss some obvious modifications of the code:

-adepting the number of modes: just changing the array were it was declared.
-single mode use: changing the array, the exec function and the SetKey and SetMouse functions, removing the curmod variable and the GetMode and SetMode.
-usage as a class (when different modes have their own main loop and their own instance of the class): just make the static global variables private and non static (some might be used static, such as the nextproc) and make the functions public members.

Wouter De Borger

PS. The edit command always prints the key character as first character. This can be avoided in two ways. If you will never trigger the edit function with the mouse, the initial offset can be set to –1 and a new filter added before the if(ed_offset < ed_length)

if(ed_offset < 0)
{
	ed_offset++;
	return 0;
}

The other solution is using postmessage(hWnd, WM_CHAR, VK_RETURN,0); after starting the editor.

* Download Code For This Article