// // SPDrawableArc.m // SonoDraw // // Created by Brad Larson on 6/24/2008. // #import "SPDrawableArc.h" #pragma mark Static Vars static float sAngleConstraint = 0.261799387799; // 15° //static NSPoint sMouseForPathSnap = {0,0}; static int centerPointKnobPartCode = 33000; @interface SPDrawableArc (PrivateRendering) - (void)recalculatePath; @end @implementation SPDrawableArc #pragma mark - #pragma mark As a subclass of DKDrawablePath //- (NSPoint) pointForPartcode:(int) pc //{ // if ( pc != kGCDrawingNoPart && pc != kGCDrawingEntireObjectPart ) // { // if ( pc == kGCSnapToNearestPathPointPartcode ) // { // // snapping to the nearest path point // // return [[self path] nearestPointToPoint:sMouseForPathSnap tolerance:4]; // } // else // { // if (pc == centerPointKnobPartCode) // return centerPoint; // else // return [[self path] controlPointForPartcode:pc]; // } // } // else // return [super pointForPartcode:pc]; //} // - (DKKnobType) knobTypeForPartCode:(int) pc { #pragma unused(pc) DKKnobType result = kDKControlPointKnobType; if ([self locked]) result |= kDKKnobIsDisabledFlag; return result; } // Make sure that on moving, the center, starting, and ending points are adjusted - (void) moveByX:(float) dx byY:(float) dy { if ( dx != 0.0 || dy != 0.0 ) { [self notifyVisualChange]; [[[self undoManager] prepareWithInvocationTarget:self] moveToPoint:[self location]]; centerPoint.x += dx; centerPoint.y += dy; startingPoint.x += dx; startingPoint.y += dy; endingPoint.x += dx; endingPoint.y += dy; NSAffineTransform* tfm = [NSAffineTransform transform]; [tfm translateXBy:dx yBy:dy]; [[self path] transformUsingAffineTransform:tfm]; [self notifyVisualChange]; } } // Override the dragging action when clicking on a control point - (void) mouseDraggedAtPoint:(NSPoint) mp inPart:(int) partcode event:(NSEvent*) evt { if (partcode == kGCDrawingEntireObjectPart ) { [super mouseDraggedAtPoint:mp inPart:partcode event:evt]; } else { // BOOL option = (([evt modifierFlags] & NSAlternateKeyMask ) != 0 ); // BOOL cmd = (([evt modifierFlags] & NSCommandKeyMask ) != 0 ); // BOOL shift = (([evt modifierFlags] & NSShiftKeyMask ) != 0 ); BOOL ctrl = (([evt modifierFlags] & NSControlKeyMask ) != 0 ); // modifier keys change the editing of path control points thus: // +shift - constrains curve control point angles to 15° intervals // +option - forces the control points either side of an on-path point to maintain the same radial distance // +cmd - allows control points to be moved fully independently // +ctrl - temporarily disables snap to grid mp = [self snappedMousePoint:mp withControlFlag:ctrl]; // Figure out what partcode is being dragged NSLog(@"Partcode: %d", partcode); // [self notifyVisualChange]; // TODO: Use the radius constrain flags to actually constrain radius if (partcode == startingPointKnobPartCode) { self.startingPoint = mp; } else if (partcode == endingPointKnobPartCode) { self.endingPoint = mp; } else if (partcode == centerPointKnobPartCode) { self.centerPoint = mp; } // [[self path] moveControlPointPartcode:partcode toPoint:mp colinear:!cmd coradial:option constrainAngle:shift]; NSString* abbrUnits = [[self drawing] abbreviatedDrawingUnits]; float radius = hypotf( startingPoint.x - centerPoint.x, startingPoint.y - centerPoint.y ); float rad = [[self drawing] convertLength:radius]; float startingAngle = ( atan2f( startingPoint.y - centerPoint.y, startingPoint.x - centerPoint.x ) * 180.0 ) / pi; float endingAngle = ( atan2f( endingPoint.y - centerPoint.y, endingPoint.x - centerPoint.x ) * 180.0 ) / pi; [[self layer] showInfoWindowWithString:[NSString stringWithFormat:@"radius: %.2f%@\nangle: %.1f%C", rad, abbrUnits, (endingAngle - startingAngle), 0xB0] atPoint:mp]; m_mouseEverMoved = YES; } } // Override the control point drawing methods to display our custom control points - (void) drawControlPointsOfPath:(NSBezierPath*) path usingKnobs:(DKKnob*) knobs { // draws the control points of the entire path using the knobs supplied. DKKnobType knobType; knobType = kDKOnPathKnobType; if ([self locked]) knobType |= kDKKnobIsDisabledFlag; [knobs drawKnobAtPoint:centerPoint ofType:knobType userInfo:nil]; [knobs drawKnobAtPoint:startingPoint ofType:knobType userInfo:nil]; [knobs drawKnobAtPoint:endingPoint ofType:knobType userInfo:nil]; } // Override the initial arc drawing routine - (void) arcCreateLoop:(NSPoint) initialPoint { // creates a circle segment. First click sets the centre, second the first radius, third the second radius. NSEvent* theEvent; int mask = NSLeftMouseDownMask | NSMouseMovedMask | NSPeriodicMask | NSScrollWheelMask; NSView* view = [[self layer] currentView]; BOOL loop = YES, constrain = NO; int element, partcode, phase; NSPoint p, lp, nsp; float radius = 0.0; float startAngle = 0.0; float endAngle; DKStyle* savedStyle = nil; NSString* abbrUnits = [[self drawing] abbreviatedDrawingUnits]; savedStyle = [[self style] retain]; [self setStyle:[DKStyle styleWithFillColour:nil strokeColour:[NSColor redColor]]]; p = centerPoint = [self snappedMousePoint:initialPoint withControlFlag:NO]; phase = 0; // set radius NSBezierPath* path = [NSBezierPath bezierPath]; [path moveToPoint:p]; [path lineToPoint:p]; // begin rubber band of first line segment [self setPath:path]; element = 1; partcode = partcodeForElement( element ); lp = centerPoint; while( loop ) { theEvent = [NSApp nextEventMatchingMask:mask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES]; nsp = [view convertPoint:[theEvent locationInWindow] fromView:nil]; p = [self snappedMousePoint:nsp withControlFlag:NO]; constrain = (([theEvent modifierFlags] & NSShiftKeyMask) != 0 ); if ( constrain ) { // slope of line is forced to be on 15° intervals float angle = atan2f( p.y - lp.y, p.x - lp.x ); float rem = fmodf( angle, sAngleConstraint ); float rad = hypotf( p.x - lp.x, p.y - lp.y ); if ( rem > sAngleConstraint / 2.0 ) angle += ( sAngleConstraint - rem ); else angle -= rem; p.x = lp.x + ( rad * cosf( angle )); p.y = lp.y + ( rad * sinf( angle )); } switch ([theEvent type]) { case NSLeftMouseDown: { if ( phase == 0 ) { // set radius as the distance from this click to the centre, and the // start angle based on the slope of this line startingPoint = p; radius = hypotf( p.x - centerPoint.x, p.y - centerPoint.y ); startAngle = ( atan2f( p.y - centerPoint.y, p.x - centerPoint.x ) * 180.0 ) / pi; ++phase; // now setting the arc } else loop = NO; } break; case NSMouseMoved: [self notifyVisualChange]; [view autoscroll:theEvent]; if ( phase == 0 ) { [path setControlPoint:p forPartcode:partcode]; radius = hypotf( p.x - centerPoint.x, p.y - centerPoint.y ); if([[self class] displaysSizeInfoWhenDragging]) { float rad = [[self drawing] convertLength:radius]; p.x += 4; p.y -= 12; [[self layer] showInfoWindowWithString:[NSString stringWithFormat:@"radius: %.2f%@", rad, abbrUnits] atPoint:nsp]; } } else if ( phase == 1 ) { endAngle = ( atan2f( p.y - centerPoint.y, p.x - centerPoint.x ) * 180.0 ) / pi; endingPoint.x = centerPoint.x + radius * cos(endAngle * pi / 180.0); endingPoint.y = centerPoint.y + radius * sin(endAngle * pi / 180.0); [self setStyle:savedStyle]; [path removeAllPoints]; if ([self pathEditingMode] == kGCPathCreateModeWedgeSegment) [path moveToPoint:centerPoint]; [path appendBezierPathWithArcWithCenter:centerPoint radius:radius startAngle:startAngle endAngle:endAngle]; if ([self pathEditingMode] == kGCPathCreateModeWedgeSegment) [path closePath]; [self setPath:path]; if([[self class] displaysSizeInfoWhenDragging]) { float rad = [[self drawing] convertLength:radius]; float angle = endAngle - startAngle; if ( angle < 0 ) angle = 360.0 + angle; p.x += 4; p.y -= 12; [[self layer] showInfoWindowWithString:[NSString stringWithFormat:@"radius: %.2f%@\nangle: %.1f%C", rad, abbrUnits, angle, 0xB0] atPoint:nsp]; } } break; case NSScrollWheel: [view scrollWheel:theEvent]; break; default: break; } [self notifyVisualChange]; } // NSLog(@"%f,%f",centerPoint.x, centerPoint.y); // NSLog(@"%f,%f",startingPoint.x, startingPoint.y); // NSLog(@"%f,%f",endingPoint.x, endingPoint.y); // startingPointKnobPartCode = [self hitPart:startingPoint]; endingPointKnobPartCode = [self hitPart:endingPoint]; NSLog(@"startingPartCode: %d", startingPointKnobPartCode); NSLog(@"endingPartCode: %d", endingPointKnobPartCode); [self setPathEditingMode:kGCPathCreateModeEditExisting]; [self setStyle:savedStyle]; [savedStyle release]; [self notifyVisualChange]; [view mouseUp:theEvent]; } #pragma mark - #pragma mark Internal rendering - (void)recalculatePath; { float radius = hypotf( startingPoint.x - centerPoint.x, startingPoint.y - centerPoint.y ); float startingAngle = ( atan2f( startingPoint.y - centerPoint.y, startingPoint.x - centerPoint.x ) * 180.0 ) / pi; float endingAngle = ( atan2f( endingPoint.y - centerPoint.y, endingPoint.x - centerPoint.x ) * 180.0 ) / pi; [[self path] removeAllPoints]; [[self path] appendBezierPathWithArcWithCenter:centerPoint radius:radius startAngle:startingAngle endAngle:endingAngle]; [self notifyVisualChange]; } #pragma mark - #pragma mark Accessors @synthesize centerPoint, startingPoint, endingPoint; - (void)setCenterPoint:(NSPoint)newPoint; { centerPoint = newPoint; [self recalculatePath]; } - (void)setStartingPoint:(NSPoint)newPoint; { startingPoint = newPoint; [self recalculatePath]; } - (void)setEndingPoint:(NSPoint)newPoint; { endingPoint = newPoint; [self recalculatePath]; } @end