This is DrawingView.m in view mode; [Download] [Up]
/* * (a) (C) 1990 by Adobe Systems Incorporated. All rights reserved. * * (b) If this Sample Code is distributed as part of the Display PostScript * System Software Development Kit from Adobe Systems Incorporated, * then this copy is designated as Development Software and its use is * subject to the terms of the License Agreement attached to such Kit. * * (c) If this Sample Code is distributed independently, then the following * terms apply: * * (d) This file may be freely copied and redistributed as long as: * 1) Parts (a), (d), (e) and (f) continue to be included in the file, * 2) If the file has been modified in any way, a notice of such * modification is conspicuously indicated. * * (e) PostScript, Display PostScript, and Adobe are registered trademarks of * Adobe Systems Incorporated. * * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO * CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED * AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED. * ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY * OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO * WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY) * WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY * DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. */ /* * DrawingView.m * * This view represents the page that the bezier is drawn onto. It is * a subview of the doc view and can be zoomed and reduced. * * The buffers are used to allow for fast redrawing and moving. * BuffeAlpha is a view inside a ClipView in a plain window and * bufferBeta is a view in a plain window without a ClipView. The * drawing is performed in the alpha buffer and then this is * composited into the buffered window. When the bezier is * selected and changed, the drawing of the new bezier * occurs in the buffered window. With each mouse drag the * old image in alpha is first composited into the window and * then the new bezier drawn atop the old image. * * When the image is moved, it is first drawn into the beta * buffer and then simply composited to a new location. If * the image is larger than the beta buffer, then the image * is redrawn instead of using the buffer. * * Version: 2.0 * Author: Ken Fromm * History: * 03-07-91 Added this comment. */ #import "Bezier.h" #import "HitPointView.h" #import "DetectApp.h" #import "DocView.h" #import "DrawingView.h" #import "DrawingViewWraps.h" #import <appkit/Application.h> #import <appkit/Cell.h> #import <appkit/Cursor.h> #import <appkit/Matrix.h> #import <appkit/View.h> #import <appkit/ClipView.h> #import <appkit/Window.h> #import <appkit/nextstd.h> #import <dpsclient/wraps.h> #import <appkit/timer.h> @implementation DrawingView static char fontname[ ] = "ControlPointsFont"; /* * Timers used to automatically scroll when the mouse is * outside the drawing view and not moving. */ static void startTimer(NXTrackingTimer ** timer, int *timermask, id window) { if (!*timer) { *timer = NXBeginTimer(NULL, 0.15, 0.2); *timermask = NX_TIMERMASK; [window addToEventMask:NX_TIMERMASK]; } } static void stopTimer(NXTrackingTimer **timer, int *timermask, id window) { if (*timer) { NXEndTimer(*timer); *timer = NULL; *timermask = 0; [window removeFromEventMask:NX_TIMERMASK]; } } static void compositeBuffer(int gstate, const NXRect * srce, const NXPoint * dest, int op) { PScomposite(NX_X(srce), NX_Y(srce), NX_WIDTH(srce), NX_HEIGHT(srce), gstate, dest->x, dest->y, op); } /* * Instance variables - hitPoint holds the usre path for * hit detection while drawUpath is a scratch buffer used for * several misc. purposes. */ - initFrame:(NXRect *) frm { [super initFrame:frm]; [[self allocateGState] setClipping:NO]; [self createGrid]; PSWDefineFont(fontname); /* * Normally these buffers might be allocated in the Application subclass and kept * as an application wide resource for use with multiple documents. It is allocated * here to simplify the code. */ NX_MALLOC(drawUpath.pts, float, PTS_UPATH_BUFFER); NX_MALLOC(drawUpath.ops, char, OPS_UPATH_BUFFER); NX_MALLOC(hitPoint.pts, float, NUM_POINTS_HIT); NX_MALLOC(hitPoint.ops, char, NUM_OPS_HIT); [self initializeHitPoint]; bufferAlpha = [NXApp getBufferAlpha]; bufferBeta = [NXApp getBufferBeta]; selected = NO; timer = NULL; return self; } /* * The user path uses relative movements to reduce the number * of points that have to be inserted each mouse down. */ - initializeHitPoint { int i; for (i = 0; i < NUM_POINTS_HIT; i++) hitPoint.pts[i] = 0; hitPoint.num_pts = i; hitPoint.ops[0] = dps_setbbox; hitPoint.ops[1] = dps_moveto; hitPoint.ops[2] = dps_rlineto; hitPoint.ops[3] = dps_rlineto; hitPoint.ops[4] = dps_rlineto; hitPoint.ops[5] = dps_closepath; hitPoint.num_ops = 6; return self; } /* * Create a cached user path and store it in the server. The grid does not change * so it can be stored in the server instead of being resent each time. Drawing the * entire grid each time instead of just the portion that is necessary is faster in * this case because the cache is every time. When selectively drawing only * the portion necessary, a different path is cached thereby reducing the number * of cache hits. * */ - createGrid { int i, j, num_pts, num_ops; float pt; char *ops; float *pts; num_ops = ceil((bounds.size.width/SIZEGRID + bounds.size.height/SIZEGRID) * 2) + 2; num_pts = num_ops * 2 + 4; NX_MALLOC(pts, float, num_pts); NX_MALLOC(ops, char, num_ops); i = j = 0; ops[j++] = dps_ucache; pts[i++] = bounds.origin.x; pts[i++] = bounds.origin.y; pts[i++] = bounds.origin.x + bounds.size.width; pts[i++] = bounds.origin.y + bounds.size.height; ops[j++] = dps_setbbox; for (pt = bounds.origin.x; pt < bounds.origin.x + bounds.size.width; pt += SIZEGRID) { pts[i++] = pt; pts[i++] = bounds.origin.y; ops[j++] = dps_moveto; pts[i++] = pt; pts[i++] = bounds.origin.y + bounds.size.height; ops[j++] = dps_lineto; } for (pt = bounds.origin.y; pt < bounds.origin.y + bounds.size.height; pt += SIZEGRID) { pts[i++] = bounds.origin.x; pts[i++] = pt; ops[j++] = dps_moveto; pts[i++] = bounds.origin.x + bounds.size.width; pts[i++] = pt; ops[j++] = dps_lineto; } /* Store it as a user object first by placing it on the stack and then defining it */ PSWSetUpath(pts, i, ops, j); gridUpath = DPSDefineUserObject(gridUpath); if (pts) NX_FREE(pts); if (ops) NX_FREE(ops); return self; } /* When the bezier is created, make sure it can be seen. */ - createObject { NXRect visRect; [self getVisibleRect:&visRect]; objectId = [[Bezier alloc] initFrame:&visRect]; return self; } - free { if (drawUpath.pts) NX_FREE(drawUpath.pts); if (drawUpath.ops) NX_FREE(drawUpath.ops); if (hitPoint.pts) NX_FREE(hitPoint.pts); if (hitPoint.ops) NX_FREE(hitPoint.ops); return [super free]; } /* * Change the menu title and toggle the trace boolean */ - traceDetect:sender { if (!tracedetect) [[sender selectedCell] setTitle:"Hit Detection On"]; else [[sender selectedCell] setTitle:"Hit Detection Off"]; tracedetect = !tracedetect; return self; } - traceDraw:sender { if (!tracedraw) [[sender selectedCell] setTitle:"Drawing On"]; else [[sender selectedCell] setTitle:"Drawing Off"]; tracedraw = !tracedraw; return self; } - drawGrid:sender { drawgrid = [sender state]; [self display]; return self; } - compositeBufferAlpha:(NXRect *) r { NXRect viewFrame; if (!r) { [self getVisibleRect:&viewFrame]; r = &viewFrame; } [bufferAlpha lockFocus]; compositeBuffer([self gState], r, &r->origin, NX_COPY); [bufferAlpha unlockFocus]; return self; } /* * When the drawing view moves, then move bufferAlpha so that * the composites from bufferAlpha are taken from the correct spot. * The beta buffer does not need to be moved because the drawing * always starts in the lower left corner of the beta buffer. */ - moveTo:(NXCoord)x :(NXCoord)y { [super moveTo:x :y]; [bufferAlpha moveTo:x :y]; return self; } /* * A scale that happens to this view should be reflected in the alpha and * beta buffers. */ - scale:(NXCoord)x :(NXCoord)y { [super scale:x :y]; [bufferAlpha scale:x :y]; [bufferBeta scale:x :y]; return self; } /* * A sizeTo should also be reflected in the alpha and beta buffers. */ - sizeTo:(NXCoord)x :(NXCoord)y { [super sizeTo:x :y]; [bufferAlpha sizeTo:x :y]; [bufferBeta sizeTo:x :y]; return self; } /* * Constrain the point within the view. An offset is needed because when * an object is moved, it is often grabbed in the center of the object. If the * lower left offset and the upper right offset were not included then part of * the object could be moved off of the view. (In some applications, that might * be allowed but in this one the object is constrained to always lie in the * page.) */ - constrainPoint:(NXPoint *)aPt withOffset:(const NXSize*)llOffset :(const NXSize*)urOffset { float margin; NXPoint viewMin, viewMax; margin = ceil(FONTSIZE/2); viewMin.x = bounds.origin.x + llOffset->width + margin; viewMin.y = bounds.origin.y + llOffset->height + margin; viewMax.x = bounds.origin.x + bounds.size.width - urOffset->width - margin; viewMax.y = bounds.origin.y + bounds.size.height - urOffset->height - margin; aPt->x = MAX(viewMin.x, aPt->x); aPt->y = MAX(viewMin.y, aPt->y); aPt->x = MIN(viewMax.x, aPt->x); aPt->y = MIN(viewMax.y, aPt->y); return self; } /* * Constrain a rectangle within the view. */ - constrainRect:(NXRect *)aRect { float margin; NXPoint viewMin, viewMax; margin = ceil(FONTSIZE/2); viewMin.x = bounds.origin.x + margin; viewMin.y = bounds.origin.y + margin; viewMax.x = bounds.origin.x + bounds.size.width - aRect->size.width - margin; viewMax.y = bounds.origin.y + bounds.size.height - aRect->size.height - margin; aRect->origin.x = MAX(viewMin.x, aRect->origin.x); aRect->origin.y = MAX(viewMin.y, aRect->origin.y); aRect->origin.x = MIN(viewMax.x, aRect->origin.x ); aRect->origin.y = MIN(viewMax.y, aRect->origin.y); return self; } - (BOOL) isScrolling { return scrolling; } /* * Scrolls to rectangle passed in if it is not in visible portion of the view. * If the rectangle is larger in width or height than the view, the scrollRectToVisible * method is not altogether consistent. As a result, the rectangle contains only * the image that was previously visible. */ - scrollToRect:(const NXRect *)toRect { NXRect visRect, tooRect; [self getVisibleRect:&visRect]; if (!NXContainsRect(&visRect, toRect)) { scrolling = YES; [window disableFlushWindow]; [self scrollRectToVisible:toRect]; [window reenableFlushWindow]; scrolling = NO; startTimer(&timer, &timermask, window); } else stopTimer(&timer, &timermask, window); return self; } /* * Redraws the graphic. The image from the alpha buffer is composited * into the window and then the changed object is drawn atop the * old image. A copy of the image is necessary because when the * window is scrolled the alpha buffer is also scrolled. When the * alpha buffer is scrolled, the old image might have to be redrawn. * As a result, a copy is created and the changes performed on the * copy. Care is taken to limit the amount of area that must be * composited and redrawn. A timer is started is the scrolling rect * moves outside the visible portion of the view. */ - redrawObject:(int) pt_num :(NXRect *)redrawRect { id copyId; BOOL tracking = YES; int old_mask; NXPoint pt, pt_last, pt_old, delta; NXRect rect_now, rect_last, rect_scroll, rect_vis; NXEvent *event; /* * Composite the current image in the window into the first buffer. */ [self compositeBufferAlpha:NULL]; /* * Create a copy of the selected object. If we scroll we will need * to redraw the old curve. If we do not create a copy we will * not have an old curve. */ copyId = [objectId copy]; [copyId copyPts:objectId]; timermask = 0; if (tracedraw) DPSTraceContext(DPSGetCurrentContext(), YES); /* * The rect_scroll will cause scrolling whenever it goes outside the * visible portion of the view. */ [self getVisibleRect:&rect_vis]; [copyId getScrollRect:pt_num :&rect_scroll]; NXIntersectionRect(&rect_vis, &rect_scroll); [copyId getBounds:&rect_now withKnobs:YES]; *redrawRect = rect_last = rect_now; [copyId getPoint:pt_num :&pt_last]; pt_old = pt_last; old_mask = [window addToEventMask: NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK]; event = [NXApp getNextEvent: NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK]; if (event->type != NX_MOUSEUP) { while (tracking) { /* * If its a timer event than use the last point. It will be converted to * into the view's coordinate so it will appear as a new point. */ if (event->type == NX_TIMER) pt = pt_old; else pt = pt_old = event->location; [self convertPoint:&pt fromView:nil]; [copyId constrainPoint:&pt andNumber:pt_num toView:self]; delta.x = pt.x - pt_last.x; delta.y = pt.y - pt_last.y; if (delta.x || delta.y) { /* Change the point location and get the new bounds. */ [copyId setPoint:pt_num :&delta]; [copyId getBounds:&rect_now withKnobs:YES]; /* Change the scrolling rectangle. */ NXOffsetRect(&rect_scroll, delta.x, delta.y); [self scrollToRect:&rect_scroll]; /* Composite the old image and then redraw the new curve. */ compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY); [self drawObject:NULL for:copyId withUcache:NO]; [self drawControl:NULL for:copyId]; /* Flush the drawing so that it's consistent. */ [window flushWindow]; NXPing(); rect_last = rect_now; pt_last = pt; } else stopTimer(&timer, &timermask, window); event = [NXApp getNextEvent:NX_MOUSEUPMASK| NX_MOUSEDRAGGEDMASK|timermask]; tracking = (event->type != NX_MOUSEUP); } stopTimer(&timer, &timermask, window); } [window setEventMask:old_mask]; [objectId free]; objectId = copyId; /* * Figure out the area that has to be redrawn * (the union of the old and the new rectangles). */ NXUnionRect(&rect_now, redrawRect); return self; } /* * Moves the graphic object. If the selected graphic can fit in the beta * buffer than the image is drawn into this buffer and then composited * to each new location. The image is redrawn at the new location * when the user releases the mouse button. If the selected graphic * cannot fit in the beta buffer than it is redrawn each time. This can * happen when the drawing view is scaled upwards. * * The offsets constrain the selected object to stay within the dimensions * of the view. */ - moveObject:(NXEvent *)event :(NXRect *)redrawRect { BOOL tracking = YES, beta; int old_mask; float scale; NXSize llOffset, urOffset; NXPoint pt, pt_last, pt_old, delta, delta_scroll; NXRect rect_now, rect_start, rect_last, rect_scroll, rect_vis; /* * Composite the current image in the window * into the first buffer. */ [self compositeBufferAlpha:NULL]; if (tracedraw) DPSTraceContext(DPSGetCurrentContext(), YES); /* Check whether the object can fit in the second buffer. */ [[bufferBeta superview] getFrame:&rect_now]; [objectId getBounds:&rect_start withKnobs:YES]; scale = [superview scale]; if (rect_now.size.width > rect_start.size.width * scale && rect_now.size.height > rect_start.size.height * scale) { [bufferBeta setDrawOrigin:rect_start.origin.x :rect_start.origin.y]; [bufferBeta lockFocus]; PSsetgray(NX_WHITE); PSsetalpha(0.0); NXRectFill(&rect_start); PSsetalpha(1.0); [self drawObject:NULL for:objectId withUcache:NO]; [self drawControl:NULL for:objectId]; [bufferBeta unlockFocus]; beta = YES; } else beta = NO; /* * Get the scrolling rectangle. If it turns out to be the visible portion of the window * then reduce it a bit so that the user is not playing pong when trying to * move the image. */ [objectId getScrollRect:-1 :&rect_scroll]; [self getVisibleRect:&rect_vis]; if (NXContainsRect(&rect_scroll, &rect_vis)) { rect_scroll = rect_vis; NXInsetRect(&rect_scroll, rect_scroll.size.width * .20, rect_scroll.size.height * .20); } else NXIntersectionRect(&rect_vis, &rect_scroll); *redrawRect = rect_now = rect_last = rect_start; delta_scroll.x = rect_scroll.origin.x - rect_now.origin.x; delta_scroll.y = rect_scroll.origin.y - rect_now.origin.y; timermask = 0; pt_last = pt_old = event->location; [self convertPoint:&pt_last fromView:nil]; /* Calculate where the mouse point falls relative to the object. */ llOffset.width = pt_last.x - rect_start.origin.x; llOffset.height = pt_last.y - rect_start.origin.y; urOffset.width = rect_start.origin.x + rect_start.size.width - pt_last.x; urOffset.height = rect_start.origin.y + rect_start.size.height - pt_last.y; old_mask = [window addToEventMask: NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK]; event = [NXApp getNextEvent: NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK]; if (event->type != NX_MOUSEUP) { while (tracking) { if (event->type == NX_TIMER) pt = pt_old; else pt = pt_old = event->location; [self convertPoint:&pt fromView:nil]; [self constrainPoint:&pt withOffset:&llOffset :&urOffset]; [self constrainPoint:&pt_last withOffset:&llOffset :&urOffset]; delta.x = pt.x - pt_last.x; delta.y = pt.y - pt_last.y; if (delta.x || delta.y) { NXOffsetRect(&rect_now, delta.x, delta.y); [self constrainRect:&rect_now]; rect_scroll.origin.x = rect_now.origin.x + delta_scroll.x; rect_scroll.origin.y = rect_now.origin.y + delta_scroll.y; [self scrollToRect:&rect_scroll]; /* * Copy the old image into the window. If using the second buffer, copy * it atop the old buffer. Otherwise, translate and redraw. */ compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY); if (beta) { compositeBuffer([bufferBeta gState], &rect_start, &rect_now.origin, NX_SOVER); } else { PSgsave(); PStranslate(rect_now.origin.x - rect_start.origin.x, rect_now.origin.y - rect_start.origin.y); [self drawObject:NULL for:objectId withUcache:YES]; [self drawControl:NULL for:objectId]; PSgrestore(); } /* Flush the drawing so that it's consistent. */ [window flushWindow]; NXPing(); rect_last = rect_now; pt_last = pt; } else stopTimer(&timer, &timermask, window); event = [NXApp getNextEvent:NX_MOUSEUPMASK| NX_MOUSEDRAGGEDMASK|timermask]; tracking = (event->type != NX_MOUSEUP); } stopTimer(&timer, &timermask, window); delta.x = rect_now.origin.x - rect_start.origin.x; delta.y = rect_now.origin.y - rect_start.origin.y; [objectId moveAll:&delta]; } NXUnionRect(&rect_now, redrawRect); [window setEventMask:old_mask]; return self; } /* Check to see whether a control point has been hit. */ - (BOOL) checkControl:(const NXPoint *) p :(int *) pt_num { BOOL hit; float controlsize, hitsetting; NXRect hitRect; controlsize = [superview controlPointSize]; hitsetting = [superview hitSetting]; NXSetRect(&hitRect, p->x - hitsetting/2, p->y - hitsetting/2, hitsetting, hitsetting); hit = [objectId hitControl:&hitRect :pt_num :controlsize]; return hit; } /* * A method in the DrawingView class. Invoked by the mouse down * event. The mouse point and the current hit setting dimensions * are placed in the hit point user path before invoking the * hitObject method of the Bezier object. */ - (BOOL) checkObject:(const NXPoint *) p { BOOL hit; float hitsetting; hitsetting = [superview hitSetting]; /* Bounding Box */ hitPoint.pts[0] = floor(p->x - hitsetting/2); hitPoint.pts[1] = floor(p->y - hitsetting/2); hitPoint.pts[2] = ceil(p->x + hitsetting/2); hitPoint.pts[3] = ceil(p->y + hitsetting/2); /* Moveto */ hitPoint.pts[4] = p->x - hitsetting/2; hitPoint.pts[5] = p->y - hitsetting/2; /* Rlineto's */ hitPoint.pts[7] = hitsetting; hitPoint.pts[8] = hitsetting; hitPoint.pts[11] = -hitsetting; if (tracedetect) DPSTraceContext(DPSGetCurrentContext(), YES); hit = [objectId hitObject:&hitPoint]; if (tracedetect) DPSTraceContext(DPSGetCurrentContext(), NO); return hit; } /* * If the docview is zooming, then scale the drawing view. Else * check for hit detection on the bezier or the control points. */ - mouseDown:(NXEvent *)event { BOOL redraw = YES; int pt_num; NXPoint p; NXRect drawRect; p = event->location; if ([superview isZooming]) return [nextResponder scaleDrawView:self toPoint:&p]; [self convertPoint:&p fromView:nil]; [self lockFocus]; if (!selected) { if ([self checkObject:&p]) { [objectId getBounds:&drawRect withKnobs:YES]; selected = YES; } else redraw = NO; } else { if ([self checkControl:&p :&pt_num]) [self redrawObject:pt_num :&drawRect]; else if ([self checkObject:&p]) [self moveObject:event :&drawRect]; else { [objectId getBounds:&drawRect withKnobs:YES]; selected = NO; } } /* * The view has already been focused and we know what * has to be redrawn so call drawSelf:: instead of display */ if (redraw) { [self drawSelf:&drawRect :1]; [window flushWindow]; NXPing(); } [self unlockFocus]; return self; } /* * Draw the object. This is a relatively simple drawing operation. More * sophiticated drawing apps would probably want to pass the current * drawing parameters to avoid unnecessarily resetting them with * each drawing. */ - drawObject:(NXRect *)r for:object withUcache:(BOOL)flag { [object drawObject:r withUcache:flag]; return self; } /* * Draw the control points using the user path buffer to hold * the data for the xyshow. Having the object fill in the data * structure allows for combining the control points for multiple * objects. This increases drawing performance by reducing * the number of operations. */ - drawControl:(NXRect *)r for:object { int i; NXPoint lastpoint; PSselectfont(fontname, [superview controlPointSize]); PSsetgray(NX_BLACK); PSsetlinewidth(0.15); lastpoint.x = 0; lastpoint.y = 0; drawUpath.num_ops = 0; drawUpath.num_pts = 0; [object putControlUPath:&drawUpath forRect:r :&lastpoint]; if (drawUpath.num_ops > 0) { drawUpath.ops[drawUpath.num_ops] = 0; drawUpath.pts[drawUpath.num_pts] = 0; drawUpath.pts[drawUpath.num_pts + 1] = 0; PSWDrawControlPoints(drawUpath.pts[0], drawUpath.pts[1], &drawUpath.pts[2], drawUpath.num_pts, drawUpath.ops); } drawUpath.num_ops = 0; drawUpath.pts[0] = 99999; drawUpath.pts[1] = 99999; drawUpath.pts[2] = -99999; drawUpath.pts[3] = -99999; drawUpath.num_pts = 4; [object putControlLinesUPath:&drawUpath forRect:r]; if (drawUpath.num_ops > 0) { DPSDoUserPath(&drawUpath.pts[4], drawUpath.num_pts - 4, dps_float, drawUpath.ops, drawUpath.num_ops, drawUpath.pts, dps_ustroke); } return self; } /* * Compare the bounds of the object with the rectangle to draw in order to * eliminate unnecessary drawing. */ - drawSelf:(NXRect *)r :(int) count { NXRect objRect; if (tracedraw) DPSTraceContext(DPSGetCurrentContext(), YES); PSsetgray(NX_WHITE); NXRectFill(r); if (drawgrid) { PSgsave(); PSsetgray(COLORGRID); PSsetlinewidth(WIDTHGRID); NXRectClip(r); PSWUpathStroke(gridUpath); PSgrestore(); } [self drawObject:r for:objectId withUcache:YES]; if (selected && NXDrawingStatus == NX_DRAWING) [self drawControl:r for:objectId]; if (scrolling) [self compositeBufferAlpha:r]; if (tracedraw) { if (![superview isTraceZooming] || ![superview isZooming]) DPSTraceContext(DPSGetCurrentContext(), NO); } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.