quakeforge/tools/Forge/Bundles/MapEdit/QuakeEd.m
Bill Currie 97e8ab7f3a A bunch more NSString and NSFileManager usage.
It does make for cleaner could, though it would have been nice if NSData
was null terminated :P
2010-12-26 22:33:04 +09:00

1055 lines
21 KiB
Objective-C

#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include "QF/dstring.h"
#include "QF/quakeio.h"
#include "QF/sys.h"
#include "QuakeEd.h"
#include "Clipper.h"
#include "XYView.h"
#include "Map.h"
#include "CameraView.h"
#include "ZView.h"
#include "Preferences.h"
#include "InspectorControl.h"
#include "Project.h"
id quakeed_i;
id entclasses_i;
extern NSBezierPath *path;
id g_cmd_out_i;
BOOL autodirty;
BOOL filter_light, filter_path, filter_entities;
BOOL filter_clip_brushes, filter_water_brushes, filter_world;
BOOL running;
int bsppid;
#if 0
// example command strings
char *fullviscmd =
"rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2 ; /LocalApps/vis $2\"";
char *fastviscmd =
"rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2 ; /LocalApps/vis -fast $2\"";
char *noviscmd =
"rsh satan \"/LocalApps/qbsp $1 $2 ; /LocalApps/light $2\"";
char *relightcmd = "rsh satan \"/LocalApps/light $2\"";
char *leakcmd = "rsh satan \"/LocalApps/qbsp -mark -notjunc $1 $2\"";
#endif
void
NopSound (void)
{
NSBeep ();
}
void
My_Malloc_Error (int code)
{
// recursive toast Error ("Malloc error: %i\n", code);
write (1, "malloc error!\n", strlen ("malloc error!\n") + 1);
}
#define FN_CMDOUT "/tmp/QuakeEdCmd.txt"
void
DisplayCmdOutput (void)
{
char *buffer;
QFile *file;
int size;
file = Qopen (FN_CMDOUT, "rt");
if (!file)
return;
size = Qfilesize (file);
buffer = malloc (size + 1);
size = Qread (file, buffer, size);
Qclose (file);
unlink (FN_CMDOUT);
[project_i addToOutput: buffer];
free (buffer);
if ([preferences_i getShowBSP])
[inspcontrol_i setCurrentInspector: i_output];
[preferences_i playBspSound];
}
/*
===============
CheckCmdDone
See if the BSP is done
===============
*/
// DPSTimedEntry cmdte;
void
CheckCmdDone (/* DPSTimedEntry tag, */ double now, void *userData)
{
union wait statusp;
struct rusage rusage;
if (!wait4 (bsppid, &statusp, WNOHANG, &rusage))
return;
DisplayCmdOutput ();
bsppid = 0;
// DPSRemoveTimedEntry( cmdte );
}
void
QuakeEd_print (const char *fmt, va_list args)
{
static dstring_t *output;
NSString *string;
if (!output)
output = dstring_new ();
dvsprintf (output, fmt, args);
string = [NSString stringWithCString: output->str];
[g_cmd_out_i setStringValue: string];
fputs (output->str, stdout);
}
// ============================================================================
@implementation QuakeEd
/*
===============
AutoSave
Every five minutes, save a modified map
===============
*/
- (void) AutoSave
{
// automatic backup
if (autodirty) {
autodirty = NO;
#define FN_AUTOSAVE "/qcache/AutoSaveMap.map"
[map_i writeMapFile: (char *) FN_AUTOSAVE useRegion: NO];
}
[map_i writeStats];
}
#define FN_TEMPSAVE @"/qcache/temp.map"
- (id) setDefaultFilename
{
filename = FN_TEMPSAVE;
[filename retain];
[self setTitleWithRepresentedFilename: filename];
return self;
}
- (BOOL) dirty
{
return dirty;
}
/*
===============================================================================
DISPLAY UPDATING (handles both camera and XYView)
===============================================================================
*/
BOOL updateinflight;
BOOL clearinstance;
BOOL updatexy;
BOOL updatez;
BOOL updatecamera;
void
postappdefined (void)
{
NSEvent *ev;
if (updateinflight)
return;
// post an event at the end of the que
ev = [NSEvent otherEventWithType: NSApplicationDefined
location: NSZeroPoint
modifierFlags: 0
timestamp: [[NSDate date] timeIntervalSinceReferenceDate]
windowNumber: 0
context: [NSApp context]
subtype: 0
data1: 0
data2: 0];
[NSApp postEvent: ev atStart: NO];
updateinflight = YES;
}
int c_updateall;
- (id) updateAll // when a model has been changed
{
updatecamera = updatexy = updatez = YES;
c_updateall++;
postappdefined ();
return self;
}
- (id) updateAll: sender
{
[self updateAll];
return self;
}
- (id) updateCamera // when the camera has moved
{
updatecamera = YES;
clearinstance = YES;
postappdefined ();
return self;
}
- (id) updateXY
{
updatexy = YES;
postappdefined ();
return self;
}
- (id) updateZ
{
updatez = YES;
postappdefined ();
return self;
}
- (void) cameraNoRestore: (NSRect)rect
{
no_restore[0] = YES;
}
- (void) xyNoRestore: (NSRect)rect
{
no_restore[1] = YES;
}
- (void) zNoRestore: (NSRect)rect
{
no_restore[2] = YES;
}
- (id) newinstance
{
clearinstance = YES;
return self;
}
- (id) redrawInstance
{
clearinstance = YES;
[self flushWindow];
return self;
}
/*
===============
flushWindow
instance draw the brush after each flush
===============
*/
- (void) flushWindow
{
NSRect rect;
int i;
NSView *cv;
[super flushWindow];
// don't lock focus before nib is finished loading
if (!running)
return;
if (_disableFlushWindow)
return;
cv = [self contentView];
[cv lockFocus];
for (i = 3; i >= 0; i--) {
if (cache[i]) {
if (!no_restore[i]) {
rect = cache_rect[i];
[cache[i] drawAtPoint: rect.origin];
}
no_restore[i] = NO;
[cache[i] release];
cache[i] = 0;
}
}
rect = [cameraview_i frame];
// rect = [cv convertRect: rect fromView: cameraview_i];
cache_rect[0] = rect = NSIntegralRect (rect);
cache[0] = [[NSBitmapImageRep alloc] initWithFocusedViewRect: rect];
rect = [[xyview_i superview] frame];
rect = [cv convertRect: rect fromView: [[xyview_i superview] superview]];
cache_rect[1] = rect = NSIntegralRect (rect);
cache[1] = [[NSBitmapImageRep alloc] initWithFocusedViewRect: rect];
rect = [[zview_i superview] frame];
rect = [cv convertRect: rect fromView: [[zview_i superview] superview]];
cache_rect[2] = rect = NSIntegralRect (rect);
cache[2] = [[NSBitmapImageRep alloc] initWithFocusedViewRect: rect];
[[self contentView] unlockFocus];
[cameraview_i lockFocus];
linestart (0, 0, 0);
[map_i makeSelectedPerform: @selector (CameraDrawSelf)];
lineflush ();
[clipper_i cameraDrawSelf];
[cameraview_i unlockFocus];
[xyview_i lockFocus];
linestart (0, 0, 0);
[map_i makeSelectedPerform: @selector (XYDrawSelf)];
lineflush ();
[cameraview_i XYDrawSelf];
[zview_i XYDrawSelf];
[clipper_i XYDrawSelf];
[xyview_i unlockFocus];
[zview_i lockFocus];
[map_i makeSelectedPerform: @selector (ZDrawSelf)];
[cameraview_i ZDrawSelf];
[clipper_i ZDrawSelf];
[zview_i unlockFocus];
clearinstance = NO;
[super flushWindow];
}
/*
==============================================================================
App delegate methods
==============================================================================
*/
- (id) applicationDefined: (NSEvent *)theEvent
{
NSEvent *evp;
updateinflight = NO;
// update screen
evp = [NSApp nextEventMatchingMask: NSAnyEventMask
untilDate: [NSDate distantPast]
inMode: NSEventTrackingRunLoopMode
dequeue: NO];
if (evp) {
postappdefined ();
return self;
}
[self disableFlushWindow];
if ([map_i count] != (unsigned) [entitycount_i intValue])
[entitycount_i setIntValue: [map_i count]];
if ([[map_i currentEntity] count] != (unsigned) [brushcount_i intValue])
[brushcount_i setIntValue: [[map_i currentEntity] count]];
if (updatecamera)
[cameraview_i display];
if (updatexy)
[xyview_i display];
if (updatez)
[zview_i display];
updatecamera = updatexy = updatez = NO;
[self enableFlushWindow];
[self flushWindow];
return self;
}
- (void) awakeFromNib
{
// XXX [self addToEventMask:
// XXX NSRightMouseDragged|NSLeftMouseDragged];
// XXX malloc_error(My_Malloc_Error);
quakeed_i = self;
dirty = autodirty = NO;
[NSTimer timerWithTimeInterval: 5 * 60
target: self
selector: @selector (AutoSave)
userInfo: nil
repeats: YES];
path = [NSBezierPath new];
}
- (void) applicationDidFinishLaunching: (NSNotification *)notification
{
NSArray *screens;
NSScreen *scrn;
running = YES;
g_cmd_out_i = cmd_out_i; // for qprintf
Sys_SetStdPrintf (QuakeEd_print);
[preferences_i readDefaults];
[project_i initProject];
[xyview_i setModeRadio: xy_drawmode_i];
// because xy view is inside
// scrollview and can't be
// connected directly in IB
[self setFrameAutosaveName: @"EditorWinFrame"];
[self clear: self];
// go to my second monitor
screens = [NSScreen screens];
if ([screens count] == 2) {
NSRect frm;
scrn = [screens objectAtIndex: 1];
frm = [scrn frame];
[self setFrameTopLeftPoint: NSMakePoint (frm.origin.x, frm.size.height)];
}
[self makeKeyAndOrderFront: self];
[map_i newMap];
Sys_Printf ("ready.\n");
}
- (id) appWillTerminate: sender
{
// FIXME: save dialog if dirty
return self;
}
// ===========================================================================
- (id) textCommand: sender
{
NSString *t;
t = [sender stringValue];
if (![t isEqualToString: @"texname"]) {
texturedef_t *td;
id b;
b = [map_i selectedBrush];
if (!b) {
Sys_Printf ("nothing selected\n");
return self;
}
td = [b texturedef];
Sys_Printf ("%s\n", td->texture);
return self;
} else {
Sys_Printf ("Unknown command\n");
} return self;
}
- (id) openProject: sender
{
[project_i openProject];
return self;
}
- (id) clear: sender
{
[map_i newMap];
[self updateAll];
[regionbutton_i setIntValue: 0];
[self setDefaultFilename];
return self;
}
- (id) centerCamera: sender
{
NSRect sbounds;
sbounds = [[xyview_i superview] bounds];
sbounds.origin.x += sbounds.size.width / 2;
sbounds.origin.y += sbounds.size.height / 2;
[cameraview_i setXYOrigin: &sbounds.origin];
[self updateAll];
return self;
}
- (id) centerZChecker: sender
{
NSRect sbounds;
sbounds = [[xyview_i superview] bounds];
sbounds.origin.x += sbounds.size.width / 2;
sbounds.origin.y += sbounds.size.height / 2;
[zview_i setPoint: &sbounds.origin];
[self updateAll];
return self;
}
- (id) changeXYLookUp: sender
{
if ([sender intValue])
xy_viewnormal[2] = 1;
else
xy_viewnormal[2] = -1;
[self updateAll];
return self;
}
/*
==============================================================================
REGION MODIFICATION
==============================================================================
*/
/*
==================
applyRegion:
==================
*/
- (id) applyRegion: sender
{
filter_clip_brushes = [filter_clip_i intValue];
filter_water_brushes = [filter_water_i intValue];
filter_light = [filter_light_i intValue];
filter_path = [filter_path_i intValue];
filter_entities = [filter_entities_i intValue];
filter_world = [filter_world_i intValue];
if (![regionbutton_i intValue]) {
region_min[0] = region_min[1] = region_min[2] = -9999;
region_max[0] = region_max[1] = region_max[2] = 9999;
}
[map_i makeGlobalPerform: @selector (newRegion)];
[self updateAll];
return self;
}
- (id) setBrushRegion: sender
{
id b;
// get the bounds of the current selection
if ([map_i numSelected] != 1) {
Sys_Printf ("must have a single brush selected\n");
return self;
}
b = [map_i selectedBrush];
[b getMins: region_min maxs: region_max];
[b remove];
// turn region on
[regionbutton_i setIntValue: 1];
[self applyRegion: self];
return self;
}
- (id) setXYRegion: sender
{
NSRect bounds;
// get xy size
bounds = [[xyview_i superview] bounds];
region_min[0] = bounds.origin.x;
region_min[1] = bounds.origin.y;
region_min[2] = -99999;
region_max[0] = bounds.origin.x + bounds.size.width;
region_max[1] = bounds.origin.y + bounds.size.height;
region_max[2] = 99999;
// turn region on
[regionbutton_i setIntValue: 1];
[self applyRegion: self];
return self;
}
// UI querie for other objects
- (BOOL) showCoordinates
{
return [show_coordinates_i intValue];
}
- (BOOL) showNames
{
return [show_names_i intValue];
}
/*
==============================================================================
BSP PROCESSING
==============================================================================
*/
void
ExpandCommand (const char *in, char *out, const char *src, const char *dest)
{
while (*in) {
if (in[0] == '$') {
if (in[1] == '1') {
strcpy (out, src);
out += strlen (src);
} else if (in[1] == '2') {
strcpy (out, dest);
out += strlen (dest);
}
in += 2;
continue;
}
*out++ = *in++;
}
*out = 0;
}
/*
=============
saveBSP
=============
*/
- (id) saveBSP: (const char *)cmdline dialog: (BOOL)wt
{
char expandedcmd[1024];
NSString *mappath;
NSString *bsppath;
int oldLightFilter;
int oldPathFilter;
NSString *destdir;
if (bsppid) {
NSBeep ();
return self;
}
// turn off the filters so all entities get saved
oldLightFilter = [filter_light_i intValue];
oldPathFilter = [filter_path_i intValue];
[filter_light_i setIntValue: 0];
[filter_path_i setIntValue: 0];
[self applyRegion: self];
if ([regionbutton_i intValue]) {
mappath = [[filename stringByDeletingPathExtension]
stringByAppendingPathExtension: @"reg"];
[map_i writeMapFile: [mappath cString] useRegion: YES];
wt = YES; // allways pop the dialog on region ops
} else {
mappath = filename;
}
// save the entire thing, just in case there is a problem
[self save: self];
[filter_light_i setIntValue: oldLightFilter];
[filter_path_i setIntValue: oldPathFilter];
[self applyRegion: self];
// write the command to the bsp host
destdir = [project_i getFinalMapDirectory];
bsppath = [destdir stringByAppendingPathComponent:
[[[mappath lastPathComponent] stringByDeletingPathExtension]
stringByAppendingPathExtension: @"bsp"]];
ExpandCommand (cmdline, expandedcmd, [mappath cString], [bsppath cString]);
strcat (expandedcmd, " > ");
strcat (expandedcmd, FN_CMDOUT);
strcat (expandedcmd, "\n");
printf ("system: %s", expandedcmd);
[project_i addToOutput: (char *) "\n\n========= BUSY =========\n\n"];
[project_i addToOutput: expandedcmd];
if ([preferences_i getShowBSP])
[inspcontrol_i setCurrentInspector: i_output];
if (wt) {
id panel;
NSModalSession session;
panel = NSGetAlertPanel (@"BSP In Progress",
[NSString stringWithCString: expandedcmd],
NULL, NULL, NULL);
session = [NSApp beginModalSessionForWindow: panel];
system (expandedcmd);
[NSApp endModalSession: session];
[panel close];
NSReleaseAlertPanel (panel);
DisplayCmdOutput ();
} else {
// cmdte = DPSAddTimedEntry(1, CheckCmdDone, self, NS_BASETHRESHOLD);
if (!(bsppid = fork ())) {
system (expandedcmd);
exit (0);
}
}
return self;
}
- (id) BSP_Full: sender
{
[self saveBSP: [project_i getFullVisCmd] dialog: NO];
return self;
}
- (id) BSP_FastVis: sender
{
[self saveBSP: [project_i getFastVisCmd] dialog: NO];
return self;
}
- (id) BSP_NoVis: sender
{
[self saveBSP: [project_i getNoVisCmd] dialog: NO];
return self;
}
- (id) BSP_relight: sender
{
[self saveBSP: [project_i getRelightCmd] dialog: NO];
return self;
}
- (id) BSP_entities: sender
{
[self saveBSP: [project_i getEntitiesCmd] dialog: NO];
return self;
}
- (id) BSP_stop: sender
{
if (!bsppid) {
NSBeep ();
return self;
}
kill (bsppid, 9);
// CheckCmdDone (cmdte, 0, NULL);
[project_i addToOutput: (char *) "\n\n========= STOPPED =========\n\n"];
return self;
}
/*
==============
doOpen:
Called by open or the project panel
==============
*/
- (id) doOpen: (NSString *)fname;
{
[fname retain];
[filename release];
filename = fname;
[map_i readMapFile: filename];
[regionbutton_i setIntValue: 0];
[self setTitleWithRepresentedFilename: fname];
[self updateAll];
return self;
}
/*
==============
open
==============
*/
- (id) open: sender;
{
id openpanel;
NSString *suffixlist[] = {@"map"};
NSArray *types = [NSArray arrayWithObjects: suffixlist count: 1];
openpanel = [NSOpenPanel new];
if ([openpanel
runModalForDirectory: [project_i getMapDirectory]
file: @""
types: types] != NSOKButton)
return self;
[self doOpen: [openpanel filename]];
return self;
}
/*
==============
save:
==============
*/
- (id) save: sender;
{
NSString *backup;
// force a name change if using tempname
if (![filename compare: FN_TEMPSAVE])
return [self saveAs: self];
dirty = autodirty = NO;
backup = [[filename stringByDeletingPathExtension]
stringByAppendingPathExtension: @"bak"];
rename ([filename cString], [backup cString]); // copy old to .bak
[map_i writeMapFile: [filename cString] useRegion: NO];
return self;
}
/*
==============
saveAs
==============
*/
- (id) saveAs: sender;
{
id panel_i;
NSString *dir;
NSString *fname;
panel_i = [NSSavePanel new];
dir = [filename stringByDeletingLastPathComponent];
[panel_i setRequiredFileType: @"map"];
if ([panel_i runModalForDirectory: [project_i getMapDirectory] file: dir]
!= NSOKButton)
return self;
fname = [[panel_i filename] retain];
[filename release];
filename = fname;
[self setTitleWithRepresentedFilename: filename];
[self save: self];
return self;
}
/*
===============================================================================
OTHER METHODS
===============================================================================
*/
// AJR - added this for Project info
- (NSString *) currentFilename
{
return filename;
}
- (id) deselect: sender
{
if ([clipper_i hide]) // first click hides only the clipper
return [self updateAll];
[map_i setCurrentEntity: [map_i objectAtIndex: 0]];
// make world selected
[map_i makeSelectedPerform: @selector (deselect)];
[self updateAll];
return self;
}
/*
===============
keyDown
===============
*/
- (id) keyDown: (NSEvent *)theEvent
{
NSString *chars = [theEvent characters];
unichar c = ([chars length] == 1) ? [chars characterAtIndex: 0] : '\0';
// function keys
switch (c) {
case NSF2FunctionKey:
[cameraview_i setDrawMode: dr_wire];
Sys_Printf ("wire draw mode\n");
return self;
case NSF3FunctionKey:
[cameraview_i setDrawMode: dr_flat];
Sys_Printf ("flat draw mode\n");
return self;
case NSF4FunctionKey:
[cameraview_i setDrawMode: dr_texture];
Sys_Printf ("texture draw mode\n");
return self;
case NSF5FunctionKey:
[xyview_i setDrawMode: dr_wire];
Sys_Printf ("wire draw mode\n");
return self;
case NSF6FunctionKey:
Sys_Printf ("texture draw mode\n");
return self;
case NSF8FunctionKey:
[cameraview_i homeView: self];
return self;
case NSF12FunctionKey:
[map_i subtractSelection: self];
return self;
case NSPageUpFunctionKey:
[cameraview_i upFloor: self];
return self;
case NSPageDownFunctionKey:
[cameraview_i downFloor: self];
return self;
case NSEndFunctionKey:
[self deselect: self];
return self;
case NSRightArrowFunctionKey:
case NSLeftArrowFunctionKey:
case NSUpArrowFunctionKey:
case NSDownArrowFunctionKey:
case 'a':
case 'A':
case 'z':
case 'Z':
case 'd':
case 'D':
case 'c':
case 'C':
case '.':
case ',':
[cameraview_i _keyDown: theEvent];
break;
case 27: // escape
autodirty = dirty = YES;
[self deselect: self];
return self;
case 127: // delete
autodirty = dirty = YES;
[map_i makeSelectedPerform: @selector (remove)];
[clipper_i hide];
[self updateAll];
break;
case '/':
[clipper_i flipNormal];
[self updateAll];
break;
case 13: // enter
[clipper_i carve];
[self updateAll];
Sys_Printf ("carved brush\n");
break;
case ' ':
[map_i cloneSelection: self];
break;
// move selection keys
case '2':
VectorCopy (vec3_origin, sb_translate);
sb_translate[1] = -[xyview_i gridsize];
[map_i makeSelectedPerform: @selector (translate)];
[self updateAll];
break;
case '8':
VectorCopy (vec3_origin, sb_translate);
sb_translate[1] = [xyview_i gridsize];
[map_i makeSelectedPerform: @selector (translate)];
[self updateAll];
break;
case '4':
VectorCopy (vec3_origin, sb_translate);
sb_translate[0] = -[xyview_i gridsize];
[map_i makeSelectedPerform: @selector (translate)];
[self updateAll];
break;
case '6':
VectorCopy (vec3_origin, sb_translate);
sb_translate[0] = [xyview_i gridsize];
[map_i makeSelectedPerform: @selector (translate)];
[self updateAll];
break;
case '-':
VectorCopy (vec3_origin, sb_translate);
sb_translate[2] = -[xyview_i gridsize];
[map_i makeSelectedPerform: @selector (translate)];
[self updateAll];
break;
case '+':
VectorCopy (vec3_origin, sb_translate);
sb_translate[2] = [xyview_i gridsize];
[map_i makeSelectedPerform: @selector (translate)];
[self updateAll];
break;
default:
Sys_Printf ("undefined keypress\n");
NopSound ();
break;
} /* switch */
return self;
}
@end