[comp.sys.sgi] GL Window class in C++

thant@horus.esd.sgi.com (Thant Tessman) (11/20/90)

I wrote a C++ class for GL Windows that has some nifty 
features.  I hope there are some folks on the net who
find it usefull.  NOTE:  This code is NOT supported by
SGI or me, and is not guaranteed to work.  It is only
offered as a convenience and to serve as an example
of how one might create a C++ application for the GL.

Thanks to Gavin Bell who pointed out a few bugs and
made a couple suggestions.


The Window class packages gl window functionality into a C++ object.

The advantages it has over straight gl windows:

    o each window gets to pretend it has its own gl input queue,

    o purely event driven (no busy wait loops)

    o a new pseudodevice QEMPTY is available to allow
      continuous action in an event driven fashion.


    o MOUSEX and MOUSEY events are automatically compressed and are
      given in relationship to the window's origin.  (If there are
      a stack of mouse position event pairs on the queue, only the 
      last pair are delivered to the window.)


The Window class is designed to serve as a virtual base class.
There are two virtual functions for the derived class to overload.
The first is

	 virtual void Window::event(Device dev, short val)

This gets called to deliver gl input events to the window.  Only input
events for a given window will be delivered to that window.  This 
means the app doesn't have to keep track of a 'current window.'
Also, the window class does a 'winset' to the window about to 
receive the input event, so the app doesn't have to (under most
circumstances).

The second virtual function is 

	virtual void Window::preferences()

which gets called before the window is opened.  The derived class should 
put window preferences (like 'prefsize' or 'keepaspect') in this function.


The List class is kinda nifty too.  It's not a proper object array,
but it's small and it fit in my brain.


Enjoy,

thant

-------------------------------------------------------------


This post should contain ten files separated by "--------"...
They are:
	List.h      List.c++           - a list class used by Window
	Window.h    Window.c++         - a GL Window base class
	MyWindow.h  MyWindow.c++       - an example app derived from Window
	Cube.h      Cube.c++           - used by MyWindow
	main.c++
	Makefile

----------------------------------(List.h)--------------------------------

#pragma once

//  For now Lists only hold things as big as a pointer (like pointers).
//  See the union below.


struct List {

    List();
    ~List();
    void insert(void const *item, int index=0);
    void remove(void const *);
    void remove(int index);
    int find(void const *);			// returns -1 if not found
    void* operator[](int index);
    int size() {return listsize;};

    // To add onto the end of the list, call:
    //     mylist.insert(item, mylist.size());

private:

    // this struct is supposed to be limited to this scope
    // but C++ isn't propper C++ yet

    struct ListNode {

	ListNode* prev;
	ListNode* next;

	// guarantee space for all these things until done right
	union {
	    void* item;
	    int dummy1;
	    double dummy2;
	    long dummy3;
	};
    };

    ListNode* head;
    ListNode* tail;

    ListNode* current;
    int index;
    int listsize;

    void gotoNode(int index);
};


#define declareList(LIST,ITEM)						\
									\
    struct LIST : List {						\
	LIST();								\
	~LIST();							\
	void insert(ITEM, int index=0);					\
	void remove(ITEM);						\
	void remove(int index);						\
	int find(ITEM);							\
	ITEM operator[](int index);					\
	int size();							\
    };									\


#define implementList(LIST,ITEM)					\
									\
    LIST::LIST() {}							\
    LIST::~LIST() {}							\
    void LIST::insert(ITEM item, int index)				\
	{List::insert((void*)item, index);}				\
    void LIST::remove(ITEM item) {List::remove((void*)item);}		\
    void LIST::remove(int index) {List::remove(index);}			\
    int LIST::find(ITEM item) {return List::find((void*)item);};	\
    ITEM LIST::operator[](int index)					\
	{return (ITEM) List::operator[](index);}			\
    int LIST::size() {return List::size();}				\


----------------------------(List.c++)-----------------------------------


#include <stdio.h>
#include "List.h"
#include <stdlib.h>


List::List() {

    head = NULL;
    tail = NULL;

    current = NULL;
    index = 0;
    listsize = 0;
}


List::~List() {

    ListNode* loop = head;
    ListNode* tmp;

    if (loop) do {
	tmp = loop;
	loop = loop->next;
	delete tmp;
    } while (loop);
}


void List::gotoNode(int index) {

    if (index==this->index) return;

    if (index==0) {
	this->index = 0;
	current = head;
	return;
    }

    if (index==listsize-1) {
	this->index = index;
	current = tail;
	return;
    }

    if (index>this->index) {

	for (int i=this->index; i<index; i++)
	    current = current->next;

    } else {

	for (int i=this->index; i>index; i--)
	    current = current->prev;
    }

    this->index = index;
}


void List::insert(void const * item, int index) {

    if ((index>listsize) || (index<0)) {
	fprintf(stderr, "\nList::insert(%d) out of bounds. (size: %d)\n",
		index, listsize);
	abort();
    }


    ListNode* n = new ListNode();
    n->item = item;

    if (listsize == 0) {

	head=n;
	tail=n;
	n->prev = NULL;
	n->next = NULL;
	this->index = 0;

    } else if (index == 0) {

	n->next = head;
	head->prev = n;
	head = n;
	n->prev = NULL;
	this->index = 0;

    } else if (index == listsize) {

	n->prev = tail;
	tail->next = n;
	tail = n;
	n->next = NULL;
	this->index = listsize;

    } else {

	gotoNode(index);
	n->next = current;
	n->prev = current->prev;
	(current->prev)->next = n;
	current->prev = n;
    }

    listsize++;
    current = n;
}


int List::find(void const * item) {

    if (listsize && (current->item == item)) return this->index;

    ListNode* look = head;
    int index = 0;

    if (look) do {
	if (look->item == item) {
	    this->index = index;
	    current = look;
	    return index;
	}
	index++;

    } while (look = look->next);

    return -1;
}


void List::remove(int index) {

    if ( (index<0) || (index>=listsize) ) {

	fprintf(stderr, "\nList::remove(%d) out of bounds.\n", index);
	abort();
    }

    gotoNode(index);

    if (head==current) head = current->next;
    if (tail==current) tail = current->prev;

    if (current->prev) (current->prev)->next = current->next;
    if (current->next) (current->next)->prev = current->prev;

    ListNode* tmp = current;

    if (current->next) {
	current = current->next;
    } else {
	current = current->prev;
	this->index--;
    }

    delete tmp;
    listsize--;
}


void List::remove(void const * item) {

    int index = find(item);
    if (index<0) {
	fprintf(stderr, "\nList::remove(item) item doesn't exist.\n");
	abort();
    }

    remove(index);
}


void* List::operator[](int index) {

    if ( (index<0) || (index>=listsize) ) {

	fprintf(stderr, "\nList::[%d] out of bounds\n", index);
	abort();
    }

    gotoNode(index);

    return current->item;
}

----------------------------------(Window.h)--------------------------------


#pragma once

#include <gl.h>
#include <device.h>
#include "List.h"

// device for doing stuff when there's nuthin better to do
#define QEMPTY 0x4000

struct Window;
declareList(WindowPtrList,Window*);
declareList(DeviceList,Device);

struct Window {

    virtual void open();
    virtual void close();
    int isOpened();

    // called by main to start processing queue
    // exits when all windows are gone
    static void Window::Go();

    virtual void qdevice(Device dev);
    virtual void unqdevice(Device dev);
    virtual void event(Device dev, short val)=0;
    virtual ~Window();


protected:

    virtual void preferences();

    Window(char* name = NULL);

    long originX, originY, sizeX, sizeY;
    long gid;

private:

    static WindowPtrList windowList;
    static WindowPtrList QEMPTYqueued;
    static Window* currentWindow;
    static Window* getWindow(long gid);

    DeviceList deviceList;
    static int init;
    char* name;
};

--------------------------------(Window.c++)----------------------------------

#include "Window.h"
#include "stdio.h"

implementList(WindowPtrList,Window*);
implementList(DeviceList,Device);

WindowPtrList Window::windowList;
WindowPtrList Window::QEMPTYqueued;
Window* Window::currentWindow = 0;

int Window::init = 0;

void Window::preferences() {}


Window::Window(char* n) {


    if (n==NULL) name = "";
    else name = n;
    gid = -1;
}


int Window::isOpened() {

    return (gid != -1);
}


void Window::open() {


    if (!init) {
	foreground();
    }

    preferences();

    gid = winopen(name);
    if (gid == -1) {
	fprintf(stderr, "Window: no additional windows available.\n");
	return;
    }

    glcompat(GLC_OLDPOLYGON, 0);
    windowList.insert(this);
    ::getorigin(&originX, &originY);
    ::getsize(&sizeX, &sizeY);

    color(0); clear();
}


void Window::close() {

    if (gid == -1) return;
    winclose(gid);
    int i=QEMPTYqueued.find(this);
    if (i>=0) QEMPTYqueued.remove(i);
    windowList.remove(this);
    gid = -1;
}


Window::~Window() {

    close();
}


void Window::qdevice(Device dev) {

    if (deviceList.find(dev)<0) deviceList.insert(dev);

    if (dev==QEMPTY && QEMPTYqueued.find(this)<0) QEMPTYqueued.insert(this);
    else ::qdevice(dev);
}


void Window::unqdevice(Device dev) {

    if (deviceList.find(dev)>=0) deviceList.remove(dev);
    if (dev==QEMPTY && QEMPTYqueued.find(this)>=0) QEMPTYqueued.remove(this);
}


static long nextqueue(short*);

void Window::Go() {

    Device dev;
    short val;

    Window* w;

    ::qdevice(WINSHUT);
    ::qdevice(WINQUIT);

    while(windowList.size()>0) {

	if (qtest() || QEMPTYqueued.size()==0) {

	    switch(dev = (Device)nextqueue(&val)) {

		case WINSHUT:
		    w = getWindow((long)val);
		    delete w;
		    currentWindow = NULL;
		    break;

		case WINQUIT:
		    while (windowList.size()) {
			w = windowList[0];
			delete w;
		    };
		    break;

		case REDRAW:
		    w = getWindow((long)val);
		    ::winset(w->gid);
		    ::getorigin(&(w->originX), &(w->originY));
		    ::getsize(&(w->sizeX), &(w->sizeY));
		    ::reshapeviewport();
		    w->event(dev, val);
		    break;

		case INPUTCHANGE:
		    if (val==0) {
			if (currentWindow) {
			    ::winset(currentWindow->gid);
			    currentWindow->event(dev, val);
			    currentWindow = NULL;
			}
		    } else {
			currentWindow = getWindow((long)val);
			if (currentWindow) {

			    // i'd like to replace this with a query
			    // from the window

			    if (currentWindow->deviceList.find(MOUSEX)>=0 &&
				currentWindow->deviceList.find(MOUSEY)>=0) {
				    ::winset(currentWindow->gid);
				    short mx = short(getvaluator(MOUSEX));
				    mx -= short(currentWindow->originX);
				    short my = short(getvaluator(MOUSEY));
				    my -= short(currentWindow->originY);
				    currentWindow->event(MOUSEX,mx);
				    currentWindow->event(MOUSEY,my);
			    }

			    currentWindow->event(dev, val);
			}
		    }
		    break;

		case MOUSEX:
		    if (currentWindow) {
			if (currentWindow->deviceList.find(MOUSEX)>=0) {
			    val -= short(currentWindow->originX);
			    ::winset(currentWindow->gid);
			    currentWindow->event(dev, val);
			}
		    }
		    break;

		case MOUSEY:
		    if (currentWindow) {
			if (currentWindow->deviceList.find(MOUSEY)>=0) {
			    val -= short(currentWindow->originY);
			    ::winset(currentWindow->gid);
			    currentWindow->event(dev, val);
			}
		    }
		    break;

		default:
		    if (currentWindow && 
			currentWindow->deviceList.find(dev)>=0) {
			    ::winset(currentWindow->gid);
			    currentWindow->event(dev, val);
		    }
		    break;
	    }

	} else {

	    for (int i=0; i<QEMPTYqueued.size(); i++) {
		::winset(QEMPTYqueued[i]->gid);
		QEMPTYqueued[i]->event(QEMPTY, 0);
	    }
	}
    }
}


Window* Window::getWindow(long gid) {

    int i=0;

    while ((i<windowList.size()) && (windowList[i]->gid!=gid)) i++;

    if (i==windowList.size()) {
	fprintf(stderr, "Window::getWindow can't find it.\n");
	return NULL;
    }
    return windowList[i];
}


static long queuebuf(short* val) {

    short dev;

    static short qbuf[100];
    static long num=0;
    static int ptr=0;

    if (ptr>=num) {
	ptr = 0;
	num = ::blkqread(qbuf, 100);
    }

    dev = qbuf[ptr];
    *val = qbuf[ptr+1];
    ptr+=2;

    return (long)dev;
}


long nextqueue(short* val) {

    static long devb[4];
    static short valb[4];
    static int ptr=0;
    static int num=0;
    long dev;

    if (ptr>=num) {
	ptr=0;
	num=1;
	if ((devb[0]=queuebuf(&valb[0]))==MOUSEX && ::qtest()) {
	    num=2;
	    if ((devb[1]=queuebuf(&valb[1]))==MOUSEY && ::qtest()) {
		do {
		    num=3;
		    if ((devb[2]=queuebuf(&valb[2]))==MOUSEX && ::qtest()) {
			num=4;
			if ((devb[3]=queuebuf(&valb[3]))==MOUSEY) {
			    devb[0] = devb[2];  valb[0] = valb[2];
			    devb[1] = devb[3];  valb[1] = valb[3];
			    num=2;
			}
		    }
		} while (num==2);
	    }
	}
    }

    *val = valb[ptr];
    dev = devb[ptr];
    ptr++;

    return dev;
}


--------------------------------(MyWindow.h)----------------------------------

#pragma once

#include "Window.h"
#include "Cube.h"

struct MyWindow : Window {

    MyWindow();
    ~MyWindow();
    virtual void open();

    virtual void event(Device dev, short val);
    virtual void preferences();

private:

    Matrix objmat;
    Matrix idmat;

    enum Mode {NOTHING, ORIENT, TUMBLE} mode;

    void orient();
    void tumble();
    void draw();

    int mx, my, omx, omy;

    // stuff for tumble
    double a, b, c;
    Cube cube;
};

--------------------------------(MyWindow.c++)--------------------------------

#include "MyWindow.h"
#include "math.h"

#include "stdio.h"

void MyWindow::preferences() {

    keepaspect(5,4);
}


MyWindow::MyWindow() : Window("My Window") {

    mode = NOTHING;

    // initialize matrices to identity matrices

    for (int i=0; i<4; i++) for (int j=0; j<4; j++) {
	objmat[i][j] = float(i==j);
	idmat[i][j] = float(i==j);
    }
}

void MyWindow::open() {

    Window::open();

    doublebuffer();
    RGBmode();
    gconfig();

    qdevice(LEFTMOUSE);
    qdevice(MIDDLEMOUSE);
    qdevice(MOUSEX);
    qdevice(MOUSEY);

    draw();
}


MyWindow::~MyWindow(){}


void MyWindow::event(Device dev, short val) {

    switch (dev) {

	case REDRAW:
	    draw();
	    break;

	case MIDDLEMOUSE:
	    if (val) {
		if (mode==NOTHING) {
		    mode = ORIENT;
		    omx = mx; omy = my;
		}
	    } else {
		if (mode==ORIENT) mode = NOTHING;
	    }
	    break;

	case LEFTMOUSE:
	    if (!val) {
		if (mode==NOTHING) {
		    mode = TUMBLE;
		    qdevice(QEMPTY);
		    a = b = c = 0.0;
		} else if (mode==TUMBLE) {
		    mode = NOTHING;
		    unqdevice(QEMPTY);
		}
	    }
	    break;

	case MOUSEX:
	    omx = mx; mx = val;
	    break;

	case MOUSEY:
	    omy = my; my = val;
	    if (mode==ORIENT) orient();
	    break;

	case QEMPTY:
	    tumble();
	    break;
    }
}


void MyWindow::orient() {

    pushmatrix();
    loadmatrix(idmat);

    rotate(mx-omx, 'y');
    rotate(omy-my, 'x');

    multmatrix(objmat);
    getmatrix(objmat);

    popmatrix();

    draw();
}


void MyWindow::tumble() {

    pushmatrix();
    loadmatrix(objmat);

    rot(sin(a*M_PI/180.0), 'y');
    rot(sin(b*M_PI/180.0), 'x');
    rot(sin(c*M_PI/180.0), 'z');

    getmatrix(objmat);

    popmatrix();

    draw();

    if ((a += 0.1) > 360.0) a-=360.0;
    if ((b += 0.3) > 360.0) b-=360.0;
    if ((c += 0.7) > 360.0) c-=360.0;
}


void MyWindow::draw() {

    RGBcolor(0, 0, 0);
    clear();

    perspective(400, 5.0/4.0, 2.0, 6.0);
    translate(0.0, 0.0, -4.0);
    multmatrix(objmat);
    cube.draw();

    swapbuffers();
}


-------------------------------(Cube.h)-----------------------------------

#pragma once

struct Cube {

    virtual void draw();

private:

    static float verts[8][3];
};

-------------------------------(Cube.c++)---------------------------------

#include "Cube.h"
#include "gl.h"

float Cube::verts[8][3] = {

    {1.0, 1.0, -1.0,},
    {1.0, -1.0, -1.0,},
    {-1.0, -1.0, -1.0,},
    {-1.0, 1.0, -1.0,},
    {1.0, 1.0, 1.0,},
    {1.0, -1.0, 1.0,},
    {-1.0, -1.0, 1.0,},
    {-1.0, 1.0, 1.0,},
};


void Cube::draw() {

    RGBcolor(255, 255, 255);

    bgnclosedline();
    v3f(verts[0]);
    v3f(verts[1]);
    v3f(verts[2]);
    v3f(verts[3]);
    endclosedline();

    bgnclosedline();
    v3f(verts[4]);
    v3f(verts[5]);
    v3f(verts[6]);
    v3f(verts[7]);
    endclosedline();

    bgnline();
    v3f(verts[0]);
    v3f(verts[4]);
    endline();

    bgnline();
    v3f(verts[1]);
    v3f(verts[5]);
    endline();

    bgnline();
    v3f(verts[2]);
    v3f(verts[6]);
    endline();

    bgnline();
    v3f(verts[3]);
    v3f(verts[7]);
    endline();
}


-------------------------------(main.c++)-----------------------------------

#include "MyWindow.h"

main() {

    Window* w1 = new MyWindow();
    Window* w2 = new MyWindow();
    w1->open();
    w2->open();

    Window::Go();
}


----------------------------------(Makefile)-----------------------------


#!smake
#

include ${ROOT}/usr/include/make/commondefs

C++FILES = \
	main.c++ \
	List.c++ \
	Window.c++ \
	MyWindow.c++ \
	Cube.c++ \
	${NULL}


OPTIMIZER = -O
LDFLAGS = -lgl_s -lm

TARGETS = main

all default: ${TARGETS}

include ${ROOT}/usr/include/make/commonrules

${TARGETS}: ${OBJECTS}
	${C++F} -o $@ ${OBJECTS} ${LDFLAGS}




-----------------------------------(end)------------------------------