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