ftp.nice.ch/pub/next/games/action/QuakeEd.s.tar.gz#/QuakeEd/XYView.m

This is XYView.m in view mode; [Download] [Up]

#import "qedefs.h"

id xyview_i;

id	scalemenu_i, gridmenu_i, scrollview_i, gridbutton_i, scalebutton_i;

vec3_t		xy_viewnormal;		// v_forward for xy view
float		xy_viewdist;		// clip behind this plane

@implementation XYView

/*
==================
initFrame:
==================
*/
- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	[self allocateGState];
	
	NXSetRect (&realbounds, 0,0,0,0);
	
	gridsize = 16;
	scale = 1.0;
	xyview_i = self;
	
	xy_viewnormal[2] = -1;
	xy_viewdist = -1024;
	
//		
// initialize the pop up menus
//
	scalemenu_i = [[PopUpList alloc] init];
	[scalemenu_i setTarget: self];
	[scalemenu_i setAction: @selector(scaleMenuTarget:)];

	[scalemenu_i addItem: "12.5%"];
	[scalemenu_i addItem: "25%"];
	[scalemenu_i addItem: "50%"];
	[scalemenu_i addItem: "75%"];
	[scalemenu_i addItem: "100%"];
	[scalemenu_i addItem: "200%"];
	[scalemenu_i addItem: "300%"];
	[[scalemenu_i itemList] selectCellAt: 4 : 0];
	
	scalebutton_i = NXCreatePopUpListButton(scalemenu_i);


	gridmenu_i = [[PopUpList alloc] init];
	[gridmenu_i setTarget: self];
	[gridmenu_i setAction: @selector(gridMenuTarget:)];

	[gridmenu_i addItem: "grid 1"];
	[gridmenu_i addItem: "grid 2"];
	[gridmenu_i addItem: "grid 4"];
	[gridmenu_i addItem: "grid 8"];
	[gridmenu_i addItem: "grid 16"];
	[gridmenu_i addItem: "grid 32"];
	[gridmenu_i addItem: "grid 64"];
	
	[[gridmenu_i itemList] selectCellAt: 4 : 0];
	
	gridbutton_i = NXCreatePopUpListButton(gridmenu_i);

// initialize the scroll view
	scrollview_i = [[PopScrollView alloc] 
		initFrame: 		frameRect 
		button1: 		scalebutton_i
		button2:		gridbutton_i
	];
	[scrollview_i setLineScroll: 64];
	[scrollview_i setAutosizing: NX_WIDTHSIZABLE | NX_HEIGHTSIZABLE];
	
// link objects together
	[[scrollview_i setDocView: self] free];
	
	return scrollview_i;

}

- (BOOL)acceptsFirstResponder
{
	return YES;
}

- setModeRadio: m
{ // this should be set from IB, but because I toss myself in a popscrollview
// the connection gets lost
	mode_radio_i = m;
	[mode_radio_i setTarget: self];
	[mode_radio_i setAction: @selector(drawMode:)];
	return self;
}

- drawMode: sender
{
	drawmode = [sender selectedCol];
	[quakeed_i updateXY];
	return self;
}

- setDrawMode: (drawmode_t)mode
{
	drawmode = mode;
	[mode_radio_i selectCellAt:0: mode];
	[quakeed_i updateXY];
	return self;
}


- (float)currentScale
{
	return scale;
}

/*
===================
setOrigin:scale:
===================
*/
- setOrigin: (NXPoint *)pt scale: (float)sc
{
	NXRect		sframe;
	NXRect		newbounds;
	
//
// calculate the area visible in the cliprect
//
	scale = sc;
	
	[superview getFrame: &sframe];
	[superview getFrame: &newbounds];
	newbounds.origin = *pt;
	newbounds.size.width /= scale; 
	newbounds.size.height /= scale; 
	
//
// union with the realbounds
//
	NXUnionRect (&realbounds, &newbounds);

//
// redisplay everything
//
	[quakeed_i disableDisplay];

//
// size this view
//
	[self sizeTo: newbounds.size.width : newbounds.size.height];
	[self setDrawOrigin: newbounds.origin.x : newbounds.origin.y];
	[self moveTo: newbounds.origin.x : newbounds.origin.y];
	
//
// scroll and scale the clip view
//
	[superview setDrawSize
		: sframe.size.width/scale 
		: sframe.size.height/scale];
	[superview setDrawOrigin: pt->x : pt->y];

	[quakeed_i reenableDisplay];
	[scrollview_i display];
	
	return self;
}

- centerOn: (vec3_t)org
{
	NXRect	sbounds;
	NXPoint	mid, delta;
	
	[[xyview_i superview] getBounds: &sbounds];
	
	mid.x = sbounds.origin.x + sbounds.size.width/2;
	mid.y = sbounds.origin.y + sbounds.size.height/2;
	
	delta.x = org[0] - mid.x;
	delta.y = org[1] - mid.y;

	sbounds.origin.x += delta.x;
	sbounds.origin.y += delta.y;
	
	[self setOrigin: &sbounds.origin scale: scale];
	return self;
}

/*
==================
newSuperBounds

When superview is resized
==================
*/
- newSuperBounds
{
	NXRect	r;
	
	[superview getBounds: &r];
	[self newRealBounds: &r];
	
	return self;
}

/*
===================
newRealBounds

Called when the realbounds rectangle is changed.
Should only change the scroll bars, not cause any redraws.
If realbounds has shrunk, nothing will change.
===================
*/
- newRealBounds: (NXRect *)nb
{
	NXRect		sbounds;
	
	realbounds = *nb;
	
//
// calculate the area visible in the cliprect
//
	[superview getBounds: &sbounds];
	NXUnionRect (nb, &sbounds);

//
// size this view
//
	[quakeed_i disableDisplay];

	[self suspendNotifyAncestorWhenFrameChanged:YES];
	[self sizeTo: sbounds.size.width : sbounds.size.height];
	[self setDrawOrigin: sbounds.origin.x : sbounds.origin.y];
	[self moveTo: sbounds.origin.x : sbounds.origin.y];
	[self suspendNotifyAncestorWhenFrameChanged:NO];

	[scrollview_i reflectScroll: superview];
	[quakeed_i reenableDisplay];
	
	[[scrollview_i horizScroller] display];
	[[scrollview_i vertScroller] display];
	
	return self;
}


/*
====================
scaleMenuTarget:

Called when the scaler popup on the window is used
====================
*/

- scaleMenuTarget: sender
{
	char	const	*item;
	NXRect		visrect, sframe;
	float		nscale;
	
	item = [[sender selectedCell] title];
	sscanf (item,"%f",&nscale);
	nscale /= 100;
	
	if (nscale == scale)
		return NULL;
		
// keep the center of the view constant
	[superview getBounds: &visrect];
	[superview getFrame: &sframe];
	visrect.origin.x += visrect.size.width/2;
	visrect.origin.y += visrect.size.height/2;
	
	visrect.origin.x -= sframe.size.width/2/nscale;
	visrect.origin.y -= sframe.size.height/2/nscale;
	
	[self setOrigin: &visrect.origin scale: nscale];
	
	return self;
}

/*
==============
zoomIn
==============
*/
- zoomIn: (NXPoint *)constant
{
	id			itemlist;
	int			selected, numrows, numcollumns;

	NXRect		visrect;
	NXPoint		ofs, new;

//
// set the popup
//
	itemlist = [scalemenu_i itemList];
	[itemlist getNumRows: &numrows numCols:&numcollumns];
	
	selected = [itemlist selectedRow] + 1;
	if (selected >= numrows)
		return NULL;
		
	[itemlist selectCellAt: selected : 0];
	[scalebutton_i setTitle: [[itemlist selectedCell] title]];

//
// zoom the view
//
	[superview getBounds: &visrect];
	ofs.x = constant->x - visrect.origin.x;
	ofs.y = constant->y - visrect.origin.y;
	
	new.x = constant->x - ofs.x / 2;
	new.y = constant->y - ofs.y / 2;

	[self setOrigin: &new scale: scale*2];
	
	return self;
}


/*
==============
zoomOut
==============
*/
- zoomOut: (NXPoint *)constant
{
	id			itemlist;
	int			selected, numrows, numcollumns;

	NXRect		visrect;
	NXPoint		ofs, new;
	
//
// set the popup
//
	itemlist = [scalemenu_i itemList];
	[itemlist getNumRows: &numrows numCols:&numcollumns];
	
	selected = [itemlist selectedRow] - 1;
	if (selected < 0)
		return NULL;
		
	[itemlist selectCellAt: selected : 0];
	[scalebutton_i setTitle: [[itemlist selectedCell] title]];

//
// zoom the view
//
	[superview getBounds: &visrect];
	ofs.x = constant->x - visrect.origin.x;
	ofs.y = constant->y - visrect.origin.y;
	
	new.x = constant->x - ofs.x * 2;
	new.y = constant->y - ofs.y * 2;

	[self setOrigin: &new scale: scale/2];
	
	return self;
}


/*
====================
gridMenuTarget:

Called when the scaler popup on the window is used
====================
*/

- gridMenuTarget: sender
{
	char	const	*item;
	int			grid;
	
	item = [[sender selectedCell] title];
	sscanf (item,"grid %d",&grid);

	if (grid == gridsize)
		return NULL;
		
	gridsize = grid;
	[quakeed_i updateAll];

	return self;
}


/*
====================
snapToGrid
====================
*/
- (float) snapToGrid: (float)f
{
	int		i;
	
	i = rint(f/gridsize);
	
	return i*gridsize;
}

- (int)gridsize
{
	return gridsize;
}



/*
===================
addToScrollRange::
===================
*/
- addToScrollRange: (float)x :(float)y;
{
	if (x < newrect.origin.x)
	{
		newrect.size.width += newrect.origin.x - x;
		newrect.origin.x = x;
	}
	
	if (y < newrect.origin.y)
	{
		newrect.size.height += newrect.origin.y - y;
		newrect.origin.y = y;
	}
	
	if (x > newrect.origin.x + newrect.size.width)
		newrect.size.width += x - (newrect.origin.x+newrect.size.width);
		
	if (y > newrect.origin.y + newrect.size.height)
		newrect.size.height += y - (newrect.origin.y+newrect.size.height);
		
	return self;
}

/*
===================
superviewChanged
===================
*/
- superviewChanged
{	
	[self newRealBounds: &realbounds];
	
	return self;
}


/*
===============================================================================

						DRAWING METHODS

===============================================================================
*/

vec3_t	cur_linecolor;

void linestart (float r, float g, float b)
{
	beginUserPath (upath,NO);
	cur_linecolor[0] = r;
	cur_linecolor[1] = g;
	cur_linecolor[2] = b;
}

void lineflush (void)
{
	if (!upath->numberOfPoints)
		return;
	endUserPath (upath, dps_ustroke);
	PSsetrgbcolor (cur_linecolor[0], cur_linecolor[1], cur_linecolor[2]);
	sendUserPath (upath);
	beginUserPath (upath,NO);
}

void linecolor (float r, float g, float b)
{
	if (cur_linecolor[0] == r && cur_linecolor[1] == g && cur_linecolor[2] == b)
		return;	// do nothing
	lineflush ();
	cur_linecolor[0] = r;
	cur_linecolor[1] = g;
	cur_linecolor[2] = b;
}

void XYmoveto (vec3_t pt)
{
	if (upath->numberOfPoints > 2048)
		lineflush ();
	UPmoveto (upath, pt[0], pt[1]);
}

void XYlineto (vec3_t pt)
{
	UPlineto (upath, pt[0], pt[1]);
}

/*
============
drawGrid

Draws tile markings every 64 units, and grid markings at the grid scale if
the grid lines are greater than or equal to 4 pixels apart

Rect is in global world (unscaled) coordinates
============
*/

- drawGrid: (const NXRect *)rect
{
	int	x,y, stopx, stopy;
	float	top,bottom,right,left;
	char	text[10];
	BOOL	showcoords;
	
	showcoords = [quakeed_i showCoordinates];

	left = rect->origin.x-1;
	bottom = rect->origin.y-1;
	right = rect->origin.x+rect->size.width+2;
	top = rect->origin.y+rect->size.height+2;

	PSsetlinewidth (0.15);

//
// grid
//
// can't just divide by grid size because of negetive coordinate
// truncating direction
//
	if (gridsize >= 4/scale)
	{
		y = floor(bottom/gridsize);
		stopy = floor(top/gridsize);
		x = floor(left/gridsize);
		stopx = floor(right/gridsize);
		
		y *= gridsize;
		stopy *= gridsize;
		x *= gridsize;
		stopx *= gridsize;
		if (y<bottom)
			y+= gridsize;
		if (x<left)
			x+= gridsize;
		if (stopx >= right)
			stopx -= gridsize;
		if (stopy >= top)
			stopy -= gridsize;
			
		beginUserPath (upath,NO);
		
		for ( ; y<=stopy ; y+= gridsize)
			if (y&63)
			{
				UPmoveto (upath, left, y);
				UPlineto (upath, right, y);
			}
	
		for ( ; x<=stopx ; x+= gridsize)
			if (x&63)
			{
				UPmoveto (upath, x, top);
				UPlineto (upath, x, bottom);
			}
		endUserPath (upath, dps_ustroke);
PSsetrgbcolor (0.8,0.8,1.0);	// thin grid color
		sendUserPath (upath);
	
	}

//
// tiles
//
	PSsetgray (0);		// for text

	if (scale > 4.0/64)
	{
		y = floor(bottom/64);
		stopy = floor(top/64);
		x = floor(left/64);
		stopx = floor(right/64);
		
		y *= 64;
		stopy *= 64;
		x *= 64;
		stopx *= 64;
		if (y<bottom)
			y+= 64;
		if (x<left)
			x+= 64;
		if (stopx >= right)
			stopx -= 64;
		if (stopy >= top)
			stopy -= 64;
			
		beginUserPath (upath,NO);
		
		for ( ; y<=stopy ; y+= 64)
		{
			if (showcoords)
			{
				sprintf (text, "%i",y);
				PSmoveto(left,y);
				PSshow(text);
			}
			UPmoveto (upath, left, y);
			UPlineto (upath, right, y);
		}
	
		for ( ; x<=stopx ; x+= 64)
		{
			if (showcoords)
			{
				sprintf (text, "%i",x);
				PSmoveto(x,bottom+2);
				PSshow(text);
			}
			UPmoveto (upath, x, top);
			UPlineto (upath, x, bottom);
		}
	
		endUserPath (upath, dps_ustroke);
		PSsetgray (12.0/16);
		sendUserPath (upath);
	}

	return self;
}

/*
==================
drawWire
==================
*/
- drawWire: (const NXRect *)rects
{
	NXRect	visRect;
	int	i,j, c, c2;
	id	ent, brush;
	vec3_t	mins, maxs;
	BOOL	drawnames;

	drawnames = [quakeed_i showNames];
	
	if ([quakeed_i showCoordinates])	// if coords are showing, update everything
	{
		[self getVisibleRect:&visRect];
		rects = &visRect;
		xy_draw_rect = *rects;
	}

	
	NXRectClip(rects);
		
// erase window
	NXEraseRect (rects);
	
// draw grid
	[self drawGrid: rects];

// draw all entities, world first so entities take priority
	linestart (0,0,0);

	c = [map_i count];
	for (i=0 ; i<c ; i++)
	{
		ent = [map_i objectAt: i];
		c2 = [ent count];
		for (j = c2-1 ; j >=0 ; j--)
		{
			brush = [ent objectAt: j];
			if ( [brush selected] )
				continue;
			if ([brush regioned])
				continue;
			[brush XYDrawSelf];
		}
		if (i > 0 && drawnames)
		{	// draw entity names
			brush = [ent objectAt: 0];
			if (![brush regioned])
			{
				[brush getMins: mins maxs: maxs];
				PSmoveto(mins[0], mins[1]);
				PSsetrgbcolor (0,0,0);
				PSshow([ent valueForQKey: "classname"]);
			}
		}
	}

	lineflush ();
	
// resize if needed
	newrect.origin.x -= gridsize;
	newrect.origin.y -= gridsize;
	newrect.size.width += 2*gridsize;
	newrect.size.height += 2*gridsize;
	if (!NXEqualRect (&newrect, &realbounds))
		[self newRealBounds: &newrect];

	return self;
}


/*
=============
drawSolid
=============
*/
- drawSolid
{
	unsigned char	*planes[5];
	NXRect	visRect;

	[self getVisibleRect:&visRect];

//
// draw the image into imagebuffer
//
	r_origin[0] = visRect.origin.x;
	r_origin[1] = visRect.origin.y;
	
	r_origin[2] = scale/2;	// using Z as a scale for the 2D projection
	
	r_width = visRect.size.width*r_origin[2];
	r_height = visRect.size.height*r_origin[2];
	
	if (r_width != xywidth || r_height != xyheight)
	{
		xywidth = r_width;
		xyheight = r_height;

		if (xypicbuffer)
		{
			free (xypicbuffer);
			free (xyzbuffer);
		}
		xypicbuffer = malloc (r_width*(r_height+1)*4);
		xyzbuffer = malloc (r_width*(r_height+1)*4);
	}
	
	r_picbuffer = xypicbuffer;
	r_zbuffer = xyzbuffer;
	
	REN_BeginXY ();
	REN_ClearBuffers ();

//
// render the entities
//
	[map_i makeAllPerform: @selector(XYRenderSelf)];

//
// display the output
//
	[self lockFocus];
	[[self window] setBackingType:NX_RETAINED];

	planes[0] = (unsigned char *)r_picbuffer;
	NXDrawBitmap(
		&visRect,  
		r_width, 
		r_height,
		8,
		3,
		32,
		r_width*4,
		NO,
		NO,
		NX_RGBColorSpace,
		planes
	);
	
	NXPing ();
	[[self window] setBackingType:NX_BUFFERED];
	[self unlockFocus];
	
	return self;
}

/*
===================
drawSelf
===================
*/
NXRect	xy_draw_rect;
- drawSelf:(const NXRect *)rects :(int)rectCount
{
	static float	drawtime;	// static to shut up compiler warning

	if (timedrawing)
		drawtime = I_FloatTime ();

	xy_draw_rect = *rects;
	newrect.origin.x = newrect.origin.y = 99999;
	newrect.size.width = newrect.size.height = -2*99999;

// setup for text
	PSselectfont("Helvetica-Medium",10/scale);
	PSrotate(0);

	if (drawmode == dr_texture || drawmode == dr_flat)
		[self drawSolid];
	else
		[self drawWire: rects];
	
	if (timedrawing)
	{
		NXPing ();
		drawtime = I_FloatTime() - drawtime;
		printf ("CameraView drawtime: %5.3f\n", drawtime);
	}

	return self;
}



/*
===============================================================================

						USER INTERACTION

===============================================================================
*/

/*
================
dragLoop:
================
*/
static	NXPoint		oldreletive;
- dragFrom: (NXEvent *)startevent 
	useGrid: (BOOL)ug
	callback: (void (*) (float dx, float dy)) callback
{
	NXEvent		*event;
	NXPoint		startpt, newpt;
	NXPoint		reletive, delta;

	startpt = startevent->location;
	[self convertPoint:&startpt  fromView:NULL];
	
	oldreletive.x = oldreletive.y = 0;
	
	if (ug)
	{
		startpt.x = [self snapToGrid: startpt.x];
		startpt.y = [self snapToGrid: startpt.y];
	}
	
	while (1)
	{
		event = [NXApp getNextEvent: NX_LMOUSEUPMASK | NX_LMOUSEDRAGGEDMASK
			| NX_RMOUSEUPMASK | NX_RMOUSEDRAGGEDMASK | NX_APPDEFINEDMASK];

		if (event->type == NX_LMOUSEUP || event->type == NX_RMOUSEUP)
			break;
		if (event->type == NX_APPDEFINED)
		{	// doesn't work.  grrr.
			[quakeed_i applicationDefined:event];
			continue;
		}
		
		newpt = event->location;
		[self convertPoint:&newpt  fromView:NULL];

		if (ug)
		{
			newpt.x = [self snapToGrid: newpt.x];
			newpt.y = [self snapToGrid: newpt.y];
		}

		reletive.x = newpt.x - startpt.x;
		reletive.y = newpt.y - startpt.y;
		if (reletive.x == oldreletive.x && reletive.y == oldreletive.y)
			continue;

		delta.x = reletive.x - oldreletive.x;
		delta.y = reletive.y - oldreletive.y;
		oldreletive = reletive;			

		callback (delta.x , delta.y );
		
	}

	return self;
}

//============================================================================


void DragCallback (float dx, float dy)
{
	sb_translate[0] = dx;
	sb_translate[1] = dy;
	sb_translate[2] = 0;

	[map_i makeSelectedPerform: @selector(translate)];
	
	[quakeed_i redrawInstance];
}

- selectionDragFrom: (NXEvent*)theEvent	
{
	qprintf ("dragging selection");
	[self	dragFrom:	theEvent 
			useGrid:	YES
			callback:	DragCallback ];
	[quakeed_i updateAll];
	qprintf ("");
	return self;
	
}

//============================================================================

void ScrollCallback (float dx, float dy)
{
	NXRect		basebounds;
	NXPoint		neworg;
	float		scale;
	
	[ [xyview_i superview] getBounds: &basebounds];
	[xyview_i convertRectFromSuperview: &basebounds];

	neworg.x = basebounds.origin.x - dx;
	neworg.y = basebounds.origin.y - dy;
	
	scale = [xyview_i currentScale];
	
	oldreletive.x -= dx;
	oldreletive.y -= dy;
	[xyview_i setOrigin: &neworg scale: scale];
}

- scrollDragFrom: (NXEvent*)theEvent	
{
	qprintf ("scrolling view");
	[self	dragFrom:	theEvent 
			useGrid:	YES
			callback:	ScrollCallback ];
	qprintf ("");
	return self;
	
}

//============================================================================

vec3_t	direction;

void DirectionCallback (float dx, float dy)
{
	vec3_t	org;
	float	ya;
	
	direction[0] += dx;
	direction[1] += dy;
	
	[cameraview_i getOrigin: org];

	if (direction[0] == org[0] && direction[1] == org[1])
		return;
		
	ya = atan2 (direction[1] - org[1], direction[0] - org[0]);

	[cameraview_i setOrigin: org angle: ya];
	[quakeed_i newinstance];
	[cameraview_i display];
}

- directionDragFrom: (NXEvent*)theEvent	
{
	NXPoint			pt;

	qprintf ("changing camera direction");

	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

	direction[0] = pt.x;
	direction[1] = pt.y;
	
	DirectionCallback (0,0);
	
	[self	dragFrom:	theEvent 
			useGrid:	NO
			callback:	DirectionCallback ];
	qprintf ("");
	return self;	
}

//============================================================================

id	newbrush;
vec3_t	neworg, newdrag;

void NewCallback (float dx, float dy)
{
	vec3_t	min, max;
	int		i;
	
	newdrag[0] += dx;
	newdrag[1] += dy;
	
	for (i=0 ; i<3 ; i++)
	{
		if (neworg[i] < newdrag[i])
		{
			min[i] = neworg[i];
			max[i] = newdrag[i];
		}
		else
		{
			min[i] = newdrag[i];
			max[i] = neworg[i];
		}
	}
	
	[newbrush  setMins: min maxs: max];
	
	[quakeed_i redrawInstance];
}

- newBrushDragFrom: (NXEvent*)theEvent	
{
	id				owner;
	texturedef_t	td;
	NXPoint			pt;

	qprintf ("sizing new brush");
	
	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

	neworg[0] = [self snapToGrid: pt.x];
	neworg[1] = [self snapToGrid: pt.y];
	neworg[2] = [map_i currentMinZ];

	newdrag[0] = neworg[0];
	newdrag[1] = neworg[1];
	newdrag[2] = [map_i currentMaxZ];
	
	owner = [map_i currentEntity];
	
	[texturepalette_i getTextureDef: &td];
	
	newbrush = [[SetBrush alloc] initOwner: owner
		mins: neworg maxs: newdrag texture: &td];
	[owner addObject: newbrush];
	
	[newbrush setSelected: YES];
	
	[self	dragFrom:	theEvent 
			useGrid:	YES
			callback:	NewCallback ];
			
	[newbrush removeIfInvalid];
	
	[quakeed_i updateCamera];
	qprintf ("");
	return self;
	
}

//============================================================================

void ControlCallback (float dx, float dy)
{
	int		i;
	
	for (i=0 ; i<numcontrolpoints ; i++)
	{
		controlpoints[i][0] += dx;
		controlpoints[i][1] += dy;
	}
	
	[[map_i selectedBrush] calcWindings];	
	[quakeed_i redrawInstance];
}

- (BOOL)planeDragFrom: (NXEvent*)theEvent	
{
	NXPoint			pt;
	vec3_t			dragpoint;

	if ([map_i numSelected] != 1)
		return NO;
		
	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

	dragpoint[0] = pt.x;
	dragpoint[1] = pt.y;
	dragpoint[2] = 2048;
		
	[[map_i selectedBrush] getXYdragface: dragpoint];
	if (!numcontrolpoints)
		return NO;
	
	qprintf ("dragging brush plane");
	
	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

	[self	dragFrom:	theEvent 
			useGrid:	YES
			callback:	ControlCallback ];
			
	[[map_i selectedBrush] removeIfInvalid];
	
	[quakeed_i updateAll];

	qprintf ("");
	return YES;
}

- (BOOL)shearDragFrom: (NXEvent*)theEvent	
{
	NXPoint			pt;
	vec3_t			dragpoint;
	vec3_t			p1, p2;
	float			time;
	id				br;
	int				face;
	
	if ([map_i numSelected] != 1)
		return NO;
	br = [map_i selectedBrush];
	
	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

// if the XY point is inside the brush, make the point on top
	p1[0] = pt.x;
	p1[1] = pt.y;
	VectorCopy (p1, p2);

	p1[2] = -2048*xy_viewnormal[2];
	p2[2] = 2048*xy_viewnormal[2];

	VectorCopy (p1, dragpoint);
	[br hitByRay: p1 : p2 : &time : &face];

	if (time > 0)
	{
		dragpoint[2] = p1[2] + (time-0.01)*xy_viewnormal[2];
	}
	else
	{
		[br getMins: p1 maxs: p2];
		dragpoint[2] = (p1[2] + p2[2])/2;
	}


	[br getXYShearPoints: dragpoint];
	if (!numcontrolpoints)
		return NO;
	
	qprintf ("dragging brush plane");
	
	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

	[self	dragFrom:	theEvent 
			useGrid:	YES
			callback:	ControlCallback ];
			
	[br removeIfInvalid];
	
	[quakeed_i updateAll];
	qprintf ("");
	return YES;
}


/*
===============================================================================

						INPUT METHODS

===============================================================================
*/


/*
===================
mouseDown
===================
*/
- mouseDown:(NXEvent *)theEvent
{
	NXPoint	pt;
	id		ent;
	vec3_t	p1, p2;
	int		flags;
	
	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

	p1[0] = p2[0] = pt.x;
	p1[1] = p2[1] = pt.y;
	p1[2] = xy_viewnormal[2] * -4096;
	p2[2] = xy_viewnormal[2] * 4096;

	flags = theEvent->flags & (NX_SHIFTMASK | NX_CONTROLMASK | NX_ALTERNATEMASK | NX_COMMANDMASK);
	
//
// shift click to select / deselect a brush from the world
//
	if (flags == NX_SHIFTMASK)
	{		
		[map_i selectRay: p1 : p2 : YES];
		return self;
	}
	
//
// cmd-shift click to set a target/targetname entity connection
//
	if (flags == (NX_SHIFTMASK|NX_COMMANDMASK) )
	{
		[map_i entityConnect: p1 : p2];
		return self;
	}
	
//
// bare click to either drag selection, or rubber band a new brush
//
	if ( flags == 0 )
	{
	// if double click, position Z checker
		if (theEvent->data.mouse.click > 1)
		{
			qprintf ("positioned Z checker");
			[zview_i setPoint: &pt];
			[quakeed_i newinstance];
			[quakeed_i updateZ];
			return self;
		}
		
	// check eye
		if ( [cameraview_i XYmouseDown: &pt flags: theEvent->flags] )
			return self;		// camera move
			
	// check z post
		if ( [zview_i XYmouseDown: &pt] )
			return self;		// z view move

	// check clippers
		if ( [clipper_i XYDrag: &pt] )
			return self;

	// check single plane dragging
		if ( [self planeDragFrom: theEvent] )
			return self;

	// check selection
		ent = [map_i grabRay: p1 : p2];
		if (ent)
			return [self selectionDragFrom: theEvent];
		
		if ([map_i numSelected])
		{
			qprintf ("missed");
			return self;
		}
		
		return [self newBrushDragFrom: theEvent];
	}
	
//
// control click = position and drag camera 
//
	if (flags == NX_CONTROLMASK)
	{
		[cameraview_i setXYOrigin: &pt];
		[quakeed_i newinstance];
		[cameraview_i display];
		[cameraview_i XYmouseDown: &pt flags: theEvent->flags];
		qprintf ("");
		return self;
	}
		
//
// command click = drag Z checker
//
	if (flags == NX_COMMANDMASK)
	{
// check single plane dragging
[self shearDragFrom: theEvent];
return self;

		qprintf ("moving Z checker");
		[zview_i setXYOrigin: &pt];
		[quakeed_i updateAll];
		[zview_i XYmouseDown: &pt];
		qprintf ("");
		return self;
	}

//
// alt click = set entire brush texture
//
	if (flags == NX_ALTERNATEMASK)
	{
		if (drawmode != dr_texture)
		{
			qprintf ("No texture setting except in texture mode!\n");
			NopSound ();
			return self;
		}
		[map_i setTextureRay: p1 : p2 : YES];
		[quakeed_i updateAll];
		return self;
	}
		
//
// ctrl-alt click = set single face texture
//
	if (flags == (NX_CONTROLMASK | NX_ALTERNATEMASK) )
	{
		if (drawmode != dr_texture)
		{
			qprintf ("No texture setting except in texture mode!\n");
			NopSound ();
			return self;
		}
		[map_i setTextureRay: p1 : p2 : NO];
		[quakeed_i updateAll];
		return self;
	}
		
	qprintf ("bad flags for click");
	NopSound ();
	return self;
}

/*
===================
rightMouseDown
===================
*/
- rightMouseDown:(NXEvent *)theEvent
{
	NXPoint	pt;
	int		flags;
		
	pt= theEvent->location;
	[self convertPoint:&pt  fromView:NULL];

	flags = theEvent->flags & (NX_SHIFTMASK | NX_CONTROLMASK | NX_ALTERNATEMASK | NX_COMMANDMASK);

	if (flags == NX_COMMANDMASK)
	{
		return [self scrollDragFrom: theEvent];		
	}

	if (flags == NX_ALTERNATEMASK)
	{
		return [clipper_i XYClick: pt];
	}
	
	if (flags == 0 || flags == NX_CONTROLMASK)
	{
		return [self directionDragFrom: theEvent];
	}
	
	qprintf ("bad flags for click");
	NopSound ();

	return self;
}


@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.