PageRenderTime 27ms CodeModel.GetById 10ms app.highlight 7ms RepoModel.GetById 2ms app.codeStats 0ms

/YEDEdgeView.j

http://github.com/rheimbuch/YED
Unknown | 395 lines | 319 code | 76 blank | 0 comment | 0 complexity | ed4dea69d538b81ef971a6173738e275 MD5 | raw file
  1@import <AppKit/CPBezierPath.j>
  2@import <AppKit/CPView.j>
  3@import <Foundation/CPNotificationCenter.j>
  4
  5@import "YEDNodeView.j"
  6
  7var intersectLineLine = function(a1, a2, b1, b2) 
  8{
  9    var ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
 10    var ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
 11    var u_b  = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
 12
 13    if ( u_b != 0 ) {
 14        var ua = ua_t / u_b;
 15        var ub = ub_t / u_b;
 16
 17        if ( 0 <= ua && ua <= 1 && 0 <= ub && ub <= 1 ) {
 18            result = {type:"intersection", points:[]};
 19            result.points.push(
 20                {
 21                    x: a1.x + ua * (a2.x - a1.x),
 22                    y: a1.y + ua * (a2.y - a1.y)
 23                }
 24            );
 25        } else {
 26            result = {type:"none", points:[]};
 27        }
 28    } else {
 29        if ( ua_t == 0 || ub_t == 0 ) {
 30            result = {type:"coincident", points:[]};
 31        } else {
 32            result = {type:"parallel", points:[]};
 33        }
 34    }
 35    return result;
 36};
 37
 38var intersectLineRect = function(a1, a2, rect) {
 39    var min        = rect.origin;
 40    var max        = {x:rect.origin.x+rect.size.width, y:rect.origin.y+rect.size.height};
 41    var topRight   = {x:max.x, y:min.y };
 42    var bottomLeft = {x:min.x, y:max.y };
 43    
 44    var inter1 = intersectLineLine(min, topRight, a1, a2);
 45    var inter2 = intersectLineLine(topRight, max, a1, a2);
 46    var inter3 = intersectLineLine(max, bottomLeft, a1, a2);
 47    var inter4 = intersectLineLine(bottomLeft, min, a1, a2);
 48    
 49    var result = {type:"none", points:[]};
 50    
 51    result.points = result.points.concat(inter1.points);
 52    result.points = result.points.concat(inter2.points);
 53    result.points = result.points.concat(inter3.points);
 54    result.points = result.points.concat(inter4.points);
 55    
 56    if ( result.points.length > 0 )
 57        result.type = "intersection";
 58
 59    return result;
 60};
 61
 62var Padding = 20;
 63
 64
 65@implementation YEDEdgeView : CPView
 66{
 67    YEDNodeView     startNodeView       @accessors;
 68    YEDNodeView     endNodeView         @accessors;
 69    CPColor         strokeColor         @accessors;
 70    BOOL            isSelected          @accessors;
 71}
 72
 73+ (id)edgeFromView:(YEDNodeView)start toView:(YEDNodeView)end
 74{
 75    return [[self alloc] initEdgeFromView:start toView:end];
 76}
 77
 78- (id)initEdgeFromView:(YEDNodeView)start toView:(YEDNodeView)end
 79{
 80    self = [self init];
 81    if(self)
 82    {
 83        [self setStartNodeView:start];
 84        [self setEndNodeView:end];
 85        [self setStrokeColor:[CPColor blackColor]];
 86    }
 87    return self;
 88}
 89
 90- (BOOL)isEqual:(id)other
 91{
 92    if(other === self)
 93        return YES;
 94    if(!other || ![other isKindOfClass:[self class]])
 95        return NO
 96    return [self isEqualToEdgeView:other];
 97}
 98
 99- (BOOL)isEqualToEdgeView:(YEDEdgeView)otherView
100{
101    if(otherView === self)
102        return YES;
103    if(![[self startNodeView] isEqual:[otherView startNodeView]])
104        return NO;
105    if(![[self endNodeView] isEqual:[otherView endNodeView]])
106        return NO;
107    return YES;
108}
109
110- (void)setStartNodeView:(YEDNodeView)start
111{
112    if(startNodeView === start)
113        return;
114    
115    var center = [CPNotificationCenter defaultCenter];
116    
117    if(startNodeView)
118    {
119        [center removeObserver:self
120                name:CPViewFrameDidChangeNotification
121                object:startNodeView];
122    }
123    
124    [self willChangeValueForKey:@"startNodeView"];
125    startNodeView = start;
126    [self didChangeValueForKey:@"startNodeView"];
127    
128    if(startNodeView)
129    {
130        [center addObserver:self
131                selector:@selector(nodeViewFrameChanged:)
132                name:CPViewFrameDidChangeNotification
133                object:startNodeView];
134        
135        [self nodeViewFrameChanged:nil];
136        
137        // [[startNodeView superview] addSubview:self];
138        // [[startNodeView superview] addSubview:startNodeView];
139        // if(endNodeView)
140        //     [[endNodeView superview] addSubview:endNodeView];
141    }
142    else
143    {
144        // [self removeFromSuperview];
145    }
146}
147
148- (void)setEndNodeView:(YEDNodeView)endView
149{
150    if(endNodeView === endView)
151        return;
152    
153    var center = [CPNotificationCenter defaultCenter];
154    
155    if(endNodeView)
156    {
157        [center removeObserver:self
158                name:CPViewFrameDidChangeNotification
159                object:endNodeView];
160    }
161    
162    [self willChangeValueForKey:@"endNodeView"];
163    endNodeView = endView
164    [self didChangeValueForKey:@"endNodeView"];
165    
166    if(endNodeView)
167    {
168        [center addObserver:self
169                selector:@selector(nodeViewFrameChanged:)
170                name:CPViewFrameDidChangeNotification
171                object:endNodeView];
172        
173        [self nodeViewFrameChanged:nil];
174        
175        // [[endNodeView superview] addSubview:self];
176        // [[endNodeView superview] addSubview:endNodeView];
177        // if(startNodeView)
178        //     [[startNodeView superview] addSubview:startNodeView];
179    }
180    else
181    {
182        // [self removeFromSuperview];
183    }
184}
185
186- (void)mouseDown:(CPEvent)event
187{
188    var location = [self convertPoint:[event locationInWindow] fromView:nil];
189    var onEdge = [self containsPoint:location];
190    if(onEdge)
191    {
192        CPLog.trace("Selecting Edge");
193        [[CPNotificationCenter defaultCenter] 
194            postNotificationName:"YEDSelectedItemNotification"
195            object:self
196            userInfo:[CPDictionary dictionaryWithJSObject:{
197                "mouseDown":event
198            }]];
199    }
200    else
201        [super mouseDown:event];
202
203}
204
205- (id)hitTest:(CGPoint)point
206{   
207    if([self containsPoint:[self convertPoint:point fromView:[self superview]]])
208        return self;
209    else
210        return nil;
211}
212
213- (BOOL)containsPoint:(CGPoint)point
214{
215    var A = [self convertPoint:[startNodeView center] fromView:[self superview]],
216        B = [self convertPoint:[endNodeView center] fromView:[self superview]];
217    
218    var slope = (B.y - A.y)/(B.x - A.x);
219    
220    var x = point.x,
221        y = point.y;
222    
223    var Y = slope*(x - A.x) + A.y;
224    var X = (y - A.y)/slope + A.x
225    
226    return ((X <= MAX(A.x,B.x)) && (X >= MIN(A.x,B.x)) && (Y <= MAX(A.y, B.y)) && (Y >= MIN(A.y, B.y))) &&
227            (((Y-10 <= y) && (y <= Y+10)) || ((X-10 <= x) && (x <= X+10)));
228}
229
230- (void)nodeViewFrameChanged:(CPNotification)notification
231{
232    if(!startNodeView || !endNodeView)
233        return;
234    // CPLog.trace("YEDEdgeView: startNodeViewFrameChanged:");
235    var startCenter = [startNodeView center],
236        endOrigin = [endNodeView center],
237        delta = CGPointMake(endOrigin.x - startCenter.x, endOrigin.y - startCenter.y),
238        frame = CGRectMakeZero();
239    
240    var minXPoint = startCenter.x <= endOrigin.x ? startCenter.x : endOrigin.x,
241        minYPoint = startCenter.y <= endOrigin.y ? startCenter.y : endOrigin.y,
242        maxXPoint = startCenter.x >= endOrigin.x ? startCenter.x : endOrigin.x,
243        maxYPoint = startCenter.y >= endOrigin.y ? startCenter.y : endOrigin.y;
244    
245    var width = Math.abs(maxXPoint-minXPoint),
246        height = Math.abs(maxYPoint-minYPoint);
247    
248    // width = width < 20 ? 20 : width;
249    // height = height < 20 ? 20 : height;
250    // CPLog.trace("w = %s, h = %s", width, height);
251    
252    frame = CGRectMake(minXPoint,
253                       minYPoint,
254                       width,
255                       height);
256    
257    [self setFrame:CGRectInset(frame, -Padding, -Padding)];
258    [self setNeedsDisplay:YES];
259}
260
261- (void)setIsSelected:(BOOL)selected
262{
263    if(isSelected === selected)
264        return;
265    
266    [self willChangeValueForKey:@"isSelected"];
267    isSelected = selected;
268    [self didChangeValueForKey:@"isSelected"];
269    
270    if(isSelected)
271    {
272        [self setStrokeColor:[CPColor redColor]];
273        [self setNeedsDisplay:YES];
274    }
275    else
276    {
277        [self setStrokeColor:[CPColor blackColor]];
278        [self setNeedsDisplay:YES];
279    }
280}
281
282- (void)drawRect:(CGRect)rect
283{
284    // CPLog.trace("YEDEdgeView drawRect:");
285    if(!startNodeView || !endNodeView)
286        return;
287    
288    var rect = CPRectInset(rect, Padding, Padding);
289    // CPLog.trace("YEDEdgeView: drawing edge");
290    var startPoint = [self convertPoint:[startNodeView center] fromView:[self superview]],
291        endPoint = [self convertPoint:[endNodeView center] fromView:[self superview]],
292        context = [[CPGraphicsContext currentContext] graphicsPort];
293    
294    
295
296    
297    
298
299    var intersection = intersectLineRect([startNodeView center], [endNodeView center], [endNodeView frame]);
300    if(intersection.type === "intersection")
301    {
302        
303        var pt = intersection.points[0];
304        var point = [self convertPoint:CGPointMake(pt.x,pt.y) fromView:[self superview]];
305        var arrowLength = 10;
306        
307        
308        CGContextSetStrokeColor(context, [self strokeColor]);
309        CGContextSetFillColor(context, [self strokeColor]);
310        CGContextSetLineWidth(context, 3.0);
311
312        var path = [CPBezierPath bezierPath];
313        [path setLineWidth:2.0];
314        [path moveToPoint:startPoint];
315        [path lineToPoint:point];
316
317        [path stroke];
318        
319        
320        // Draw arrow end point
321        
322        
323        var aTan2FromOrigin = function(x, y, oX, oY)
324        {
325            oX = oX || 0;
326            oY = oY || 0;
327            
328            var dx = x - oX,
329                dy = y - oY;
330            
331            var angle = 0;
332            
333            if(dx > 0)
334            {
335                angle = Math.atan(dy/dx);
336            }
337            else if(dy >= 0 && dx < 0)
338            {
339                angle = Math.PI + Math.atan(dy/dx);
340            }
341            else if(dy < 0 && dx < 0)
342            {
343                angle = -Math.PI + Math.atan(dy/dx);
344            }
345            else if(dy > 0 && dx == 0)
346            {
347                angle = Math.PI/2;
348            }
349            else if(dy < 0 && dx == 0)
350            {
351                angle = -Math.PI/2;
352            }
353            else if(dy == 0 && dx ==0)
354            {
355                angle = 0;
356            }
357            
358            return angle;
359        }
360        
361        with(Math)
362        {
363            var x1 = startPoint.x,
364                y1 = startPoint.y,
365                x2 = point.x,
366                y2 = point.y;            
367
368            var lineAngle = aTan2FromOrigin(x2,y2,x1,y1),
369                arrowAngle1 = lineAngle + 45*PI/180,
370                arrowAngle2 = lineAngle - 45*PI/180;
371            
372            var x3 = x2 - arrowLength * cos(arrowAngle1),
373                y3 = y2 - arrowLength * sin(arrowAngle1),
374                x4 = x2 - arrowLength * cos(arrowAngle2),
375                y4 = y2 - arrowLength * sin(arrowAngle2);
376            
377            var arrowPath1 = [CPBezierPath bezierPath];
378            [arrowPath1 setLineWidth:2.0];
379            [arrowPath1 moveToPoint:CGPointMake(x2,y2)];
380            [arrowPath1 lineToPoint:CGPointMake(x3,y3)];
381            [arrowPath1 lineToPoint:CGPointMake(x4,y4)];
382            [arrowPath1 closePath];
383            [arrowPath1 fill];
384            
385            // var arrowPath2 = [CPBezierPath bezierPath];
386            // [arrowPath2 setLineWidth:2.0];
387            // [arrowPath2 moveToPoint:CGPointMake(x2,y2)];
388            // [arrowPath2 lineToPoint:CGPointMake(x4,y4)];
389            // [arrowPath2 fill];
390        }
391    }
392}
393
394
395@end