mxEdgeHandler.js 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxEdgeHandler
  7. *
  8. * Graph event handler that reconnects edges and modifies control points and
  9. * the edge label location. Uses <mxTerminalMarker> for finding and
  10. * highlighting new source and target vertices. This handler is automatically
  11. * created in <mxGraph.createHandler> for each selected edge.
  12. *
  13. * To enable adding/removing control points, the following code can be used:
  14. *
  15. * (code)
  16. * mxEdgeHandler.prototype.addEnabled = true;
  17. * mxEdgeHandler.prototype.removeEnabled = true;
  18. * (end)
  19. *
  20. * Note: This experimental feature is not recommended for production use.
  21. *
  22. * Constructor: mxEdgeHandler
  23. *
  24. * Constructs an edge handler for the specified <mxCellState>.
  25. *
  26. * Parameters:
  27. *
  28. * state - <mxCellState> of the cell to be handled.
  29. */
  30. function mxEdgeHandler(state)
  31. {
  32. if (state != null && state.shape != null)
  33. {
  34. this.state = state;
  35. this.init();
  36. // Handles escape keystrokes
  37. this.escapeHandler = mxUtils.bind(this, function(sender, evt)
  38. {
  39. var dirty = this.index != null;
  40. this.reset();
  41. if (dirty)
  42. {
  43. this.graph.cellRenderer.redraw(this.state,
  44. false, state.view.isRendering());
  45. }
  46. });
  47. this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
  48. }
  49. };
  50. /**
  51. * Variable: graph
  52. *
  53. * Reference to the enclosing <mxGraph>.
  54. */
  55. mxEdgeHandler.prototype.graph = null;
  56. /**
  57. * Variable: state
  58. *
  59. * Reference to the <mxCellState> being modified.
  60. */
  61. mxEdgeHandler.prototype.state = null;
  62. /**
  63. * Variable: marker
  64. *
  65. * Holds the <mxTerminalMarker> which is used for highlighting terminals.
  66. */
  67. mxEdgeHandler.prototype.marker = null;
  68. /**
  69. * Variable: constraintHandler
  70. *
  71. * Holds the <mxConstraintHandler> used for drawing and highlighting
  72. * constraints.
  73. */
  74. mxEdgeHandler.prototype.constraintHandler = null;
  75. /**
  76. * Variable: error
  77. *
  78. * Holds the current validation error while a connection is being changed.
  79. */
  80. mxEdgeHandler.prototype.error = null;
  81. /**
  82. * Variable: shape
  83. *
  84. * Holds the <mxShape> that represents the preview edge.
  85. */
  86. mxEdgeHandler.prototype.shape = null;
  87. /**
  88. * Variable: bends
  89. *
  90. * Holds the <mxShapes> that represent the points.
  91. */
  92. mxEdgeHandler.prototype.bends = null;
  93. /**
  94. * Variable: labelShape
  95. *
  96. * Holds the <mxShape> that represents the label position.
  97. */
  98. mxEdgeHandler.prototype.labelShape = null;
  99. /**
  100. * Variable: cloneEnabled
  101. *
  102. * Specifies if cloning by control-drag is enabled. Default is true.
  103. */
  104. mxEdgeHandler.prototype.cloneEnabled = true;
  105. /**
  106. * Variable: addEnabled
  107. *
  108. * Specifies if adding bends by shift-click is enabled. Default is false.
  109. * Note: This experimental feature is not recommended for production use.
  110. */
  111. mxEdgeHandler.prototype.addEnabled = false;
  112. /**
  113. * Variable: removeEnabled
  114. *
  115. * Specifies if removing bends by shift-click is enabled. Default is false.
  116. * Note: This experimental feature is not recommended for production use.
  117. */
  118. mxEdgeHandler.prototype.removeEnabled = false;
  119. /**
  120. * Variable: dblClickRemoveEnabled
  121. *
  122. * Specifies if removing bends by double click is enabled. Default is false.
  123. */
  124. mxEdgeHandler.prototype.dblClickRemoveEnabled = false;
  125. /**
  126. * Variable: mergeRemoveEnabled
  127. *
  128. * Specifies if removing bends by dropping them on other bends is enabled.
  129. * Default is false.
  130. */
  131. mxEdgeHandler.prototype.mergeRemoveEnabled = false;
  132. /**
  133. * Variable: straightRemoveEnabled
  134. *
  135. * Specifies if removing bends by creating straight segments should be enabled.
  136. * If enabled, this can be overridden by holding down the alt key while moving.
  137. * Default is false.
  138. */
  139. mxEdgeHandler.prototype.straightRemoveEnabled = false;
  140. /**
  141. * Variable: virtualBendsEnabled
  142. *
  143. * Specifies if virtual bends should be added in the center of each
  144. * segments. These bends can then be used to add new waypoints.
  145. * Default is false.
  146. */
  147. mxEdgeHandler.prototype.virtualBendsEnabled = false;
  148. /**
  149. * Variable: virtualBendOpacity
  150. *
  151. * Opacity to be used for virtual bends (see <virtualBendsEnabled>).
  152. * Default is 40.
  153. */
  154. mxEdgeHandler.prototype.virtualBendOpacity = 40;
  155. /**
  156. * Variable: parentHighlightEnabled
  157. *
  158. * Specifies if the parent should be highlighted if a child cell is selected.
  159. * Default is false.
  160. */
  161. mxEdgeHandler.prototype.parentHighlightEnabled = false;
  162. /**
  163. * Variable: preferHtml
  164. *
  165. * Specifies if bends should be added to the graph container. This is updated
  166. * in <init> based on whether the edge or one of its terminals has an HTML
  167. * label in the container.
  168. */
  169. mxEdgeHandler.prototype.preferHtml = false;
  170. /**
  171. * Variable: allowHandleBoundsCheck
  172. *
  173. * Specifies if the bounds of handles should be used for hit-detection in IE
  174. * Default is true.
  175. */
  176. mxEdgeHandler.prototype.allowHandleBoundsCheck = true;
  177. /**
  178. * Variable: snapToTerminals
  179. *
  180. * Specifies if waypoints should snap to the routing centers of terminals.
  181. * Default is false.
  182. */
  183. mxEdgeHandler.prototype.snapToTerminals = false;
  184. /**
  185. * Variable: handleImage
  186. *
  187. * Optional <mxImage> to be used as handles. Default is null.
  188. */
  189. mxEdgeHandler.prototype.handleImage = null;
  190. /**
  191. * Variable: tolerance
  192. *
  193. * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
  194. */
  195. mxEdgeHandler.prototype.tolerance = 0;
  196. /**
  197. * Variable: outlineConnect
  198. *
  199. * Specifies if connections to the outline of a highlighted target should be
  200. * enabled. This will allow to place the connection point along the outline of
  201. * the highlighted target. Default is false.
  202. */
  203. mxEdgeHandler.prototype.outlineConnect = false;
  204. /**
  205. * Variable: manageLabelHandle
  206. *
  207. * Specifies if the label handle should be moved if it intersects with another
  208. * handle. Uses <checkLabelHandle> for checking and moving. Default is false.
  209. */
  210. mxEdgeHandler.prototype.manageLabelHandle = false;
  211. /**
  212. * Function: init
  213. *
  214. * Initializes the shapes required for this edge handler.
  215. */
  216. mxEdgeHandler.prototype.init = function()
  217. {
  218. this.graph = this.state.view.graph;
  219. this.marker = this.createMarker();
  220. // Clones the original points from the cell
  221. // and makes sure at least one point exists
  222. this.points = [];
  223. // Uses the absolute points of the state
  224. // for the initial configuration and preview
  225. this.abspoints = this.getSelectionPoints(this.state);
  226. this.shape = this.createSelectionShape(this.abspoints);
  227. this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
  228. mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
  229. this.shape.init(this.graph.getView().getOverlayPane());
  230. this.shape.svgStrokeTolerance = 0;
  231. this.shape.pointerEvents = false;
  232. mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
  233. if (this.graph.isCellMovable(this.state.cell))
  234. {
  235. this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE);
  236. }
  237. // Updates preferHtml
  238. this.preferHtml = this.state.text != null &&
  239. this.state.text.node.parentNode == this.graph.container;
  240. if (!this.preferHtml)
  241. {
  242. // Checks source terminal
  243. var sourceState = this.state.getVisibleTerminalState(true);
  244. if (sourceState != null)
  245. {
  246. this.preferHtml = sourceState.text != null &&
  247. sourceState.text.node.parentNode == this.graph.container;
  248. }
  249. if (!this.preferHtml)
  250. {
  251. // Checks target terminal
  252. var targetState = this.state.getVisibleTerminalState(false);
  253. if (targetState != null)
  254. {
  255. this.preferHtml = targetState.text != null &&
  256. targetState.text.node.parentNode == this.graph.container;
  257. }
  258. }
  259. }
  260. this.updateParentHighlight();
  261. this.refresh();
  262. this.redraw();
  263. };
  264. /**
  265. * Function: createLabelShape
  266. *
  267. * Creates, initializes and returns the label shape.
  268. */
  269. mxEdgeHandler.prototype.createLabelShape = function()
  270. {
  271. var shape = this.createLabelHandleShape();
  272. this.initBend(shape);
  273. return shape;
  274. };
  275. /**
  276. * Function: getConstraintHandler
  277. *
  278. * Returns the constraint handler. This implementation creates a new
  279. * <mxConstraintHandler> if one does not yet exist.
  280. */
  281. mxEdgeHandler.prototype.getConstraintHandler = function()
  282. {
  283. if (this.constraintHandler == null)
  284. {
  285. this.constraintHandler = this.createConstraintHandler();
  286. }
  287. return this.constraintHandler;
  288. };
  289. /**
  290. * Function: createConstraintHandler
  291. *
  292. * Creates and returns a new <mxConstraintHandler> for this handler.
  293. */
  294. mxEdgeHandler.prototype.createConstraintHandler = function()
  295. {
  296. return new mxConstraintHandler(this.graph);
  297. };
  298. /**
  299. * Function: isParentHighlightVisible
  300. *
  301. * Returns true if the parent highlight should be visible. This implementation
  302. * always returns true.
  303. */
  304. mxEdgeHandler.prototype.isParentHighlightVisible = mxVertexHandler.prototype.isParentHighlightVisible;
  305. /**
  306. * Function: destroyParentHighlight
  307. *
  308. * Destroys the parent highlight.
  309. */
  310. mxEdgeHandler.prototype.destroyParentHighlight = mxVertexHandler.prototype.destroyParentHighlight;
  311. /**
  312. * Function: updateParentHighlight
  313. *
  314. * Updates the highlight of the parent if <parentHighlightEnabled> is true.
  315. */
  316. mxEdgeHandler.prototype.updateParentHighlight = mxVertexHandler.prototype.updateParentHighlight;
  317. /**
  318. * Function: createCustomHandles
  319. *
  320. * Returns an array of custom handles. This implementation returns null.
  321. */
  322. mxEdgeHandler.prototype.createCustomHandles = function()
  323. {
  324. return null;
  325. };
  326. /**
  327. * Function: isVirtualBendsEnabled
  328. *
  329. * Returns true if virtual bends should be added. This returns true if
  330. * <virtualBendsEnabled> is true and the current style allows and
  331. * renders custom waypoints.
  332. */
  333. mxEdgeHandler.prototype.isVirtualBendsEnabled = function(evt)
  334. {
  335. return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null ||
  336. this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE ||
  337. this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1) &&
  338. mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow';
  339. };
  340. /**
  341. * Function: isCellEnabled
  342. *
  343. * Returns true if the given cell allows new connections to be created. This implementation
  344. * always returns true.
  345. */
  346. mxEdgeHandler.prototype.isCellEnabled = function(cell)
  347. {
  348. return true;
  349. };
  350. /**
  351. * Function: isAddPointEvent
  352. *
  353. * Returns true if the given event is a trigger to add a new point. This
  354. * implementation returns true if shift is pressed.
  355. */
  356. mxEdgeHandler.prototype.isAddPointEvent = function(evt)
  357. {
  358. return mxEvent.isShiftDown(evt);
  359. };
  360. /**
  361. * Function: isRemovePointEvent
  362. *
  363. * Returns true if the given event is a trigger to remove a point. This
  364. * implementation returns true if shift is pressed.
  365. */
  366. mxEdgeHandler.prototype.isRemovePointEvent = function(evt)
  367. {
  368. return mxEvent.isShiftDown(evt);
  369. };
  370. /**
  371. * Function: getSelectionPoints
  372. *
  373. * Returns the list of points that defines the selection stroke.
  374. */
  375. mxEdgeHandler.prototype.getSelectionPoints = function(state)
  376. {
  377. return state.absolutePoints;
  378. };
  379. /**
  380. * Function: createParentHighlightShape
  381. *
  382. * Creates the shape used to draw the selection border.
  383. */
  384. mxEdgeHandler.prototype.createParentHighlightShape = function(bounds)
  385. {
  386. var shape = new mxRectangleShape(mxRectangle.fromRectangle(bounds),
  387. null, this.getSelectionColor());
  388. shape.strokewidth = this.getSelectionStrokeWidth();
  389. shape.isDashed = this.isSelectionDashed();
  390. return shape;
  391. };
  392. /**
  393. * Function: createSelectionShape
  394. *
  395. * Creates the shape used to draw the selection border.
  396. */
  397. mxEdgeHandler.prototype.createSelectionShape = function(points)
  398. {
  399. var shape = new this.state.shape.constructor();
  400. shape.outline = true;
  401. shape.apply(this.state);
  402. shape.isDashed = this.isSelectionDashed();
  403. shape.stroke = this.getSelectionColor();
  404. shape.isShadow = false;
  405. return shape;
  406. };
  407. /**
  408. * Function: getSelectionColor
  409. *
  410. * Returns <mxConstants.EDGE_SELECTION_COLOR>.
  411. */
  412. mxEdgeHandler.prototype.getSelectionColor = function()
  413. {
  414. return (this.graph.isCellEditable(this.state.cell)) ?
  415. mxConstants.EDGE_SELECTION_COLOR :
  416. mxConstants.LOCKED_HANDLE_FILLCOLOR;
  417. };
  418. /**
  419. * Function: getSelectionStrokeWidth
  420. *
  421. * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.
  422. */
  423. mxEdgeHandler.prototype.getSelectionStrokeWidth = function()
  424. {
  425. return mxConstants.EDGE_SELECTION_STROKEWIDTH;
  426. };
  427. /**
  428. * Function: isSelectionDashed
  429. *
  430. * Returns <mxConstants.EDGE_SELECTION_DASHED>.
  431. */
  432. mxEdgeHandler.prototype.isSelectionDashed = function()
  433. {
  434. return mxConstants.EDGE_SELECTION_DASHED;
  435. };
  436. /**
  437. * Function: isConnectableCell
  438. *
  439. * Returns true if the given cell is connectable. This is a hook to
  440. * disable floating connections. This implementation returns true.
  441. */
  442. mxEdgeHandler.prototype.isConnectableCell = function(cell)
  443. {
  444. return true;
  445. };
  446. /**
  447. * Function: getCellAt
  448. *
  449. * Creates and returns the <mxCellMarker> used in <marker>.
  450. */
  451. mxEdgeHandler.prototype.getCellAt = function(x, y)
  452. {
  453. return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null;
  454. };
  455. /**
  456. * Function: createMarker
  457. *
  458. * Creates and returns the <mxCellMarker> used in <marker>.
  459. */
  460. mxEdgeHandler.prototype.createMarker = function()
  461. {
  462. var marker = new mxCellMarker(this.graph);
  463. var self = this; // closure
  464. // Only returns edges if they are connectable and never returns
  465. // the edge that is currently being modified
  466. marker.getCell = function(me)
  467. {
  468. var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
  469. // Checks for cell at preview point (with grid)
  470. if ((cell == self.state.cell || cell == null) && self.currentPoint != null)
  471. {
  472. cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y);
  473. }
  474. // Uses connectable parent vertex if one exists
  475. if (cell != null && !this.graph.isCellConnectable(cell))
  476. {
  477. var parent = this.graph.getModel().getParent(cell);
  478. if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
  479. {
  480. cell = parent;
  481. }
  482. }
  483. var model = self.graph.getModel();
  484. if ((this.graph.isSwimlane(cell) && self.currentPoint != null &&
  485. this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) ||
  486. (!self.isConnectableCell(cell)) || (cell == self.state.cell ||
  487. (cell != null && !self.graph.connectableEdges && model.isEdge(cell))) ||
  488. model.isAncestor(self.state.cell, cell))
  489. {
  490. cell = null;
  491. }
  492. if (!this.graph.isCellConnectable(cell))
  493. {
  494. cell = null;
  495. }
  496. return cell;
  497. };
  498. // Sets the highlight color according to validateConnection
  499. marker.isValidState = function(state)
  500. {
  501. var model = self.graph.getModel();
  502. var other = self.graph.view.getTerminalPort(state,
  503. self.graph.view.getState(model.getTerminal(self.state.cell,
  504. !self.isSource)), !self.isSource);
  505. var otherCell = (other != null) ? other.cell : null;
  506. var source = (self.isSource) ? state.cell : otherCell;
  507. var target = (self.isSource) ? otherCell : state.cell;
  508. // Updates the error message of the handler
  509. self.error = self.validateConnection(source, target);
  510. return self.error == null;
  511. };
  512. return marker;
  513. };
  514. /**
  515. * Function: validateConnection
  516. *
  517. * Returns the error message or an empty string if the connection for the
  518. * given source, target pair is not valid. Otherwise it returns null. This
  519. * implementation uses <mxGraph.getEdgeValidationError>.
  520. *
  521. * Parameters:
  522. *
  523. * source - <mxCell> that represents the source terminal.
  524. * target - <mxCell> that represents the target terminal.
  525. */
  526. mxEdgeHandler.prototype.validateConnection = function(source, target)
  527. {
  528. return this.graph.getEdgeValidationError(this.state.cell, source, target);
  529. };
  530. /**
  531. * Function: createBends
  532. *
  533. * Creates and returns the bends used for modifying the edge. This is
  534. * typically an array of <mxRectangleShapes>.
  535. */
  536. mxEdgeHandler.prototype.createBends = function()
  537. {
  538. var cell = this.state.cell;
  539. var bends = [];
  540. if (this.abspoints != null)
  541. {
  542. for (var i = 0; i < this.abspoints.length; i++)
  543. {
  544. if (this.isHandleVisible(i))
  545. {
  546. var source = i == 0;
  547. var target = i == this.abspoints.length - 1;
  548. var terminal = source || target;
  549. if (terminal || this.graph.isCellBendable(cell))
  550. {
  551. (mxUtils.bind(this, function(index)
  552. {
  553. var bend = this.createHandleShape(index, null, index == this.abspoints.length - 1);
  554. this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, function()
  555. {
  556. if (this.dblClickRemoveEnabled)
  557. {
  558. this.removePoint(this.state, index);
  559. }
  560. })));
  561. if (this.isHandleEnabled(i))
  562. {
  563. bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE);
  564. }
  565. bends.push(bend);
  566. if (!terminal)
  567. {
  568. this.points.push(new mxPoint(0,0));
  569. bend.node.style.visibility = 'hidden';
  570. }
  571. }))(i);
  572. }
  573. }
  574. }
  575. }
  576. return bends;
  577. };
  578. /**
  579. * Function: createVirtualBends
  580. *
  581. * Creates and returns the bends used for modifying the edge. This is
  582. * typically an array of <mxRectangleShapes>.
  583. */
  584. mxEdgeHandler.prototype.createVirtualBends = function()
  585. {
  586. var bends = [];
  587. if (this.abspoints != null && this.abspoints.length > 0 &&
  588. this.graph.isCellBendable(this.state.cell))
  589. {
  590. for (var i = 1; i < this.abspoints.length; i++)
  591. {
  592. (mxUtils.bind(this, function(bend)
  593. {
  594. this.initBend(bend);
  595. bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE);
  596. bends.push(bend);
  597. }))(this.createHandleShape());
  598. }
  599. }
  600. return bends;
  601. };
  602. /**
  603. * Function: isHandleEnabled
  604. *
  605. * Creates the shape used to display the given bend.
  606. */
  607. mxEdgeHandler.prototype.isHandleEnabled = function(index)
  608. {
  609. return true;
  610. };
  611. /**
  612. * Function: isHandleVisible
  613. *
  614. * Returns true if the handle at the given index is visible.
  615. */
  616. mxEdgeHandler.prototype.isHandleVisible = function(index)
  617. {
  618. var source = this.state.getVisibleTerminalState(true);
  619. var target = this.state.getVisibleTerminalState(false);
  620. var geo = this.graph.getCellGeometry(this.state.cell);
  621. var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null;
  622. return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1;
  623. };
  624. /**
  625. * Function: createHandleShape
  626. *
  627. * Creates the shape used to display the given bend. Note that the index may be
  628. * null for special cases, such as when called from
  629. * <mxElbowEdgeHandler.createVirtualBend>. Only images and rectangles should be
  630. * returned if support for HTML labels with not foreign objects is required.
  631. * Index if null for virtual handles.
  632. */
  633. mxEdgeHandler.prototype.createHandleShape = function(index)
  634. {
  635. if (this.handleImage != null)
  636. {
  637. var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);
  638. // Allows HTML rendering of the images
  639. shape.preserveImageAspect = false;
  640. return shape;
  641. }
  642. else
  643. {
  644. var s = mxConstants.HANDLE_SIZE;
  645. if (this.preferHtml)
  646. {
  647. s -= 1;
  648. }
  649. return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
  650. }
  651. };
  652. /**
  653. * Function: createLabelHandleShape
  654. *
  655. * Creates the shape used to display the the label handle.
  656. */
  657. mxEdgeHandler.prototype.createLabelHandleShape = function()
  658. {
  659. if (this.labelHandleImage != null)
  660. {
  661. var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src);
  662. // Allows HTML rendering of the images
  663. shape.preserveImageAspect = false;
  664. return shape;
  665. }
  666. else
  667. {
  668. var s = mxConstants.LABEL_HANDLE_SIZE;
  669. return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
  670. }
  671. };
  672. /**
  673. * Function: initBend
  674. *
  675. * Helper method to initialize the given bend.
  676. *
  677. * Parameters:
  678. *
  679. * bend - <mxShape> that represents the bend to be initialized.
  680. */
  681. mxEdgeHandler.prototype.initBend = function(bend, dblClick)
  682. {
  683. if (this.preferHtml)
  684. {
  685. bend.dialect = mxConstants.DIALECT_STRICTHTML;
  686. bend.init(this.graph.container);
  687. }
  688. else
  689. {
  690. bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
  691. mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
  692. bend.init(this.graph.getView().getOverlayPane());
  693. }
  694. mxEvent.redirectMouseEvents(bend.node, this.graph, this.state,
  695. null, null, null, dblClick);
  696. if (mxClient.IS_TOUCH)
  697. {
  698. bend.node.setAttribute('pointer-events', 'none');
  699. }
  700. };
  701. /**
  702. * Function: getHandleForEvent
  703. *
  704. * Returns the index of the handle for the given event.
  705. */
  706. mxEdgeHandler.prototype.getHandleForEvent = function(me)
  707. {
  708. var result = null;
  709. if (this.state != null)
  710. {
  711. // Connection highlight may consume events before they reach sizer handle
  712. var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? 2 * this.tolerance : 0;
  713. var hit = (!this.allowHandleBoundsCheck) ? null :
  714. new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, tol, tol);
  715. var minDistSq = null;
  716. function checkShape(shape)
  717. {
  718. if (shape != null && (me.isSource(shape) ||
  719. shape.intersectsRectangle(hit)))
  720. {
  721. var dx = me.getGraphX() - shape.bounds.getCenterX();
  722. var dy = me.getGraphY() - shape.bounds.getCenterY();
  723. var tmp = dx * dx + dy * dy;
  724. if (minDistSq == null || tmp <= minDistSq)
  725. {
  726. minDistSq = tmp;
  727. return true;
  728. }
  729. }
  730. return false;
  731. }
  732. if (this.customHandles != null && this.isCustomHandleEvent(me))
  733. {
  734. // Inverse loop order to match display order
  735. for (var i = this.customHandles.length - 1; i >= 0; i--)
  736. {
  737. if (checkShape(this.customHandles[i].shape))
  738. {
  739. // LATER: Return reference to active shape
  740. return mxEvent.CUSTOM_HANDLE - i;
  741. }
  742. }
  743. }
  744. if (this.state.text != null && (me.isSource(this.state.text) ||
  745. checkShape(this.labelShape)))
  746. {
  747. result = mxEvent.LABEL_HANDLE;
  748. }
  749. if (this.bends != null)
  750. {
  751. for (var i = 0; i < this.bends.length; i++)
  752. {
  753. if (checkShape(this.bends[i]))
  754. {
  755. result = i;
  756. }
  757. }
  758. }
  759. if (this.virtualBends != null && this.isAddVirtualBendEvent(me))
  760. {
  761. for (var i = 0; i < this.virtualBends.length; i++)
  762. {
  763. if (checkShape(this.virtualBends[i]))
  764. {
  765. result = mxEvent.VIRTUAL_HANDLE - i;
  766. }
  767. }
  768. }
  769. }
  770. return result;
  771. };
  772. /**
  773. * Function: isAddVirtualBendEvent
  774. *
  775. * Returns true if the given event allows virtual bends to be added. This
  776. * implementation returns true.
  777. */
  778. mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me)
  779. {
  780. return true;
  781. };
  782. /**
  783. * Function: isCustomHandleEvent
  784. *
  785. * Returns true if the given event allows custom handles to be changed. This
  786. * implementation returns true.
  787. */
  788. mxEdgeHandler.prototype.isCustomHandleEvent = function(me)
  789. {
  790. return true;
  791. };
  792. /**
  793. * Function: mouseDown
  794. *
  795. * Handles the event by checking if a special element of the handler
  796. * was clicked, in which case the index parameter is non-null. The
  797. * indices may be one of <LABEL_HANDLE> or the number of the respective
  798. * control point. The source and target points are used for reconnecting
  799. * the edge.
  800. */
  801. mxEdgeHandler.prototype.mouseDown = function(sender, me)
  802. {
  803. if (this.graph.isCellEditable(this.state.cell))
  804. {
  805. var handle = this.getHandleForEvent(me);
  806. if (this.bends != null && this.bends[handle] != null)
  807. {
  808. var b = this.bends[handle].bounds;
  809. this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY());
  810. }
  811. if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent()))
  812. {
  813. this.addPoint(this.state, me.getEvent());
  814. me.consume();
  815. }
  816. else if (handle != null && !me.isConsumed() && this.graph.isEnabled())
  817. {
  818. if (this.removeEnabled && this.isRemovePointEvent(me.getEvent()))
  819. {
  820. this.removePoint(this.state, handle);
  821. }
  822. else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell()))
  823. {
  824. if (handle <= mxEvent.VIRTUAL_HANDLE)
  825. {
  826. mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100);
  827. }
  828. this.mouseDownX = me.getX();
  829. this.mouseDownY = me.getY();
  830. this.handle = handle;
  831. }
  832. if (!mxEvent.isShiftDown(me.getEvent()))
  833. {
  834. me.consume();
  835. }
  836. }
  837. }
  838. };
  839. /**
  840. * Function: start
  841. *
  842. * Starts the handling of the mouse gesture.
  843. */
  844. mxEdgeHandler.prototype.start = function(x, y, index)
  845. {
  846. this.startX = x;
  847. this.startY = y;
  848. this.isSource = (this.bends == null) ? false : index == 0;
  849. this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1;
  850. this.isLabel = index == mxEvent.LABEL_HANDLE;
  851. if (this.isSource || this.isTarget)
  852. {
  853. var cell = this.state.cell;
  854. var terminal = this.graph.model.getTerminal(cell, this.isSource);
  855. if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||
  856. (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource)))
  857. {
  858. this.index = index;
  859. }
  860. }
  861. else
  862. {
  863. this.index = index;
  864. }
  865. // Hides other custom handles
  866. if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
  867. {
  868. if (this.customHandles != null)
  869. {
  870. for (var i = 0; i < this.customHandles.length; i++)
  871. {
  872. if (i != mxEvent.CUSTOM_HANDLE - this.index)
  873. {
  874. this.customHandles[i].setVisible(false);
  875. }
  876. }
  877. }
  878. }
  879. };
  880. /**
  881. * Function: clonePreviewState
  882. *
  883. * Returns a clone of the current preview state for the given point and terminal.
  884. */
  885. mxEdgeHandler.prototype.clonePreviewState = function(point, terminal)
  886. {
  887. return this.state.clone();
  888. };
  889. /**
  890. * Function: getSnapToTerminalTolerance
  891. *
  892. * Returns the tolerance for the guides. Default value is 2.
  893. */
  894. mxEdgeHandler.prototype.getSnapToTerminalTolerance = function()
  895. {
  896. return 2;
  897. };
  898. /**
  899. * Function: updateHint
  900. *
  901. * Hook for subclassers do show details while the handler is active.
  902. */
  903. mxEdgeHandler.prototype.updateHint = function(me, point) { };
  904. /**
  905. * Function: removeHint
  906. *
  907. * Hooks for subclassers to hide details when the handler gets inactive.
  908. */
  909. mxEdgeHandler.prototype.removeHint = function() { };
  910. /**
  911. * Function: roundLength
  912. *
  913. * Hook for rounding the unscaled width or height. This uses Math.round.
  914. */
  915. mxEdgeHandler.prototype.roundLength = function(length)
  916. {
  917. return Math.round(length);
  918. };
  919. /**
  920. * Function: isSnapToTerminalsEvent
  921. *
  922. * Returns true if <snapToTerminals> is true and if alt is not pressed.
  923. */
  924. mxEdgeHandler.prototype.isSnapToTerminalsEvent = function(me)
  925. {
  926. return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent());
  927. };
  928. /**
  929. * Function: getPointForEvent
  930. *
  931. * Returns the point for the given event.
  932. */
  933. mxEdgeHandler.prototype.getPointForEvent = function(me)
  934. {
  935. var view = this.graph.getView();
  936. var scale = view.scale;
  937. var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale,
  938. this.roundLength(me.getGraphY() / scale) * scale);
  939. var tt = this.getSnapToTerminalTolerance();
  940. var overrideX = false;
  941. var overrideY = false;
  942. if (tt > 0 && this.isSnapToTerminalsEvent(me))
  943. {
  944. function snapToPoint(pt)
  945. {
  946. if (pt != null)
  947. {
  948. var x = pt.x;
  949. if (Math.abs(point.x - x) < tt)
  950. {
  951. point.x = x;
  952. overrideX = true;
  953. }
  954. var y = pt.y;
  955. if (Math.abs(point.y - y) < tt)
  956. {
  957. point.y = y;
  958. overrideY = true;
  959. }
  960. }
  961. }
  962. function snapToTerminal(terminal)
  963. {
  964. if (terminal != null)
  965. {
  966. snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal),
  967. view.getRoutingCenterY(terminal)));
  968. }
  969. };
  970. snapToTerminal.call(this, this.state.getVisibleTerminalState(true));
  971. snapToTerminal.call(this, this.state.getVisibleTerminalState(false));
  972. var pts = this.state.absolutePoints;
  973. if (pts != null)
  974. {
  975. for (var i = 0; i < pts.length; i++)
  976. {
  977. if ((i > 0 || !this.state.isFloatingTerminalPoint(true)) &&
  978. (i < pts.length - 1 || !this.state.isFloatingTerminalPoint(false)))
  979. {
  980. snapToPoint.call(this, this.state.absolutePoints[i]);
  981. }
  982. }
  983. }
  984. }
  985. if (this.graph.isGridEnabledEvent(me.getEvent()))
  986. {
  987. var tr = view.translate;
  988. if (!overrideX)
  989. {
  990. point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
  991. }
  992. if (!overrideY)
  993. {
  994. point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
  995. }
  996. }
  997. return point;
  998. };
  999. /**
  1000. * Function: getPreviewTerminalState
  1001. *
  1002. * Updates the given preview state taking into account the state of the constraint handler.
  1003. */
  1004. mxEdgeHandler.prototype.getPreviewTerminalState = function(me)
  1005. {
  1006. var constraintHandler = this.getConstraintHandler();
  1007. constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint);
  1008. if (constraintHandler.currentFocus != null && constraintHandler.currentConstraint != null)
  1009. {
  1010. // Handles special case where grid is large and connection point is at actual point in which
  1011. // case the outline is not followed as long as we're < gridSize / 2 away from that point
  1012. if (this.marker.highlight != null && this.marker.highlight.state != null &&
  1013. this.marker.highlight.state.cell == constraintHandler.currentFocus.cell)
  1014. {
  1015. // Direct repaint needed if cell already highlighted
  1016. if (this.marker.highlight.shape.stroke != 'transparent')
  1017. {
  1018. this.marker.highlight.shape.stroke = 'transparent';
  1019. this.marker.highlight.repaint();
  1020. }
  1021. }
  1022. else
  1023. {
  1024. this.marker.markCell(constraintHandler.currentFocus.cell, 'transparent');
  1025. }
  1026. var model = this.graph.getModel();
  1027. var other = this.graph.view.getTerminalPort(this.state,
  1028. this.graph.view.getState(model.getTerminal(this.state.cell,
  1029. !this.isSource)), !this.isSource);
  1030. var otherCell = (other != null) ? other.cell : null;
  1031. var source = (this.isSource) ? constraintHandler.currentFocus.cell : otherCell;
  1032. var target = (this.isSource) ? otherCell : constraintHandler.currentFocus.cell;
  1033. // Updates the error message of the handler
  1034. this.error = this.validateConnection(source, target);
  1035. var result = null;
  1036. if (this.error == null)
  1037. {
  1038. result = constraintHandler.currentFocus;
  1039. }
  1040. if (this.error != null || (result != null &&
  1041. !this.isCellEnabled(result.cell)))
  1042. {
  1043. constraintHandler.reset();
  1044. }
  1045. return result;
  1046. }
  1047. else if (!this.graph.isIgnoreTerminalEvent(me.getEvent()))
  1048. {
  1049. this.marker.process(me);
  1050. var state = this.marker.getValidState();
  1051. if (state != null && !this.isCellEnabled(state.cell))
  1052. {
  1053. constraintHandler.reset();
  1054. this.marker.reset();
  1055. }
  1056. return this.marker.getValidState();
  1057. }
  1058. else
  1059. {
  1060. this.marker.reset();
  1061. return null;
  1062. }
  1063. };
  1064. /**
  1065. * Function: getPreviewPoints
  1066. *
  1067. * Updates the given preview state taking into account the state of the constraint handler.
  1068. *
  1069. * Parameters:
  1070. *
  1071. * pt - <mxPoint> that contains the current pointer position.
  1072. * me - Optional <mxMouseEvent> that contains the current event.
  1073. */
  1074. mxEdgeHandler.prototype.getPreviewPoints = function(pt, me)
  1075. {
  1076. var geometry = this.graph.getCellGeometry(this.state.cell);
  1077. var points = (geometry.points != null) ? geometry.points.slice() : null;
  1078. var point = new mxPoint(pt.x, pt.y);
  1079. var result = null;
  1080. if (!this.isSource && !this.isTarget)
  1081. {
  1082. this.convertPoint(point, false);
  1083. if (points == null)
  1084. {
  1085. points = [point];
  1086. }
  1087. else
  1088. {
  1089. // Adds point from virtual bend
  1090. if (this.index <= mxEvent.VIRTUAL_HANDLE)
  1091. {
  1092. points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point);
  1093. }
  1094. // Removes point if dragged on terminal point
  1095. if (!this.isSource && !this.isTarget)
  1096. {
  1097. for (var i = 0; i < this.bends.length; i++)
  1098. {
  1099. if (i != this.index)
  1100. {
  1101. var bend = this.bends[i];
  1102. if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y))
  1103. {
  1104. if (this.index <= mxEvent.VIRTUAL_HANDLE)
  1105. {
  1106. points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1);
  1107. }
  1108. else
  1109. {
  1110. points.splice(this.index - 1, 1);
  1111. }
  1112. result = points;
  1113. }
  1114. }
  1115. }
  1116. // Removes point if user tries to straighten a segment
  1117. if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent())))
  1118. {
  1119. var tol = this.graph.tolerance * this.graph.tolerance;
  1120. var abs = this.state.absolutePoints.slice();
  1121. abs[this.index] = pt;
  1122. // Handes special case where removing waypoint affects tolerance (flickering)
  1123. var src = this.state.getVisibleTerminalState(true);
  1124. if (src != null)
  1125. {
  1126. var c = this.graph.getConnectionConstraint(this.state, src, true);
  1127. // Checks if point is not fixed
  1128. if (c == null || this.graph.getConnectionPoint(src, c) == null)
  1129. {
  1130. abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src));
  1131. }
  1132. }
  1133. var trg = this.state.getVisibleTerminalState(false);
  1134. if (trg != null)
  1135. {
  1136. var c = this.graph.getConnectionConstraint(this.state, trg, false);
  1137. // Checks if point is not fixed
  1138. if (c == null || this.graph.getConnectionPoint(trg, c) == null)
  1139. {
  1140. abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg));
  1141. }
  1142. }
  1143. function checkRemove(idx, tmp)
  1144. {
  1145. if (idx > 0 && idx < abs.length - 1 &&
  1146. mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y,
  1147. abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol)
  1148. {
  1149. points.splice(idx - 1, 1);
  1150. result = points;
  1151. }
  1152. };
  1153. // LATER: Check if other points can be removed if a segment is made straight
  1154. checkRemove(this.index, pt);
  1155. }
  1156. }
  1157. // Updates existing point
  1158. if (result == null && this.index > mxEvent.VIRTUAL_HANDLE)
  1159. {
  1160. points[this.index - 1] = point;
  1161. }
  1162. }
  1163. }
  1164. else if (this.graph.resetEdgesOnConnect)
  1165. {
  1166. points = null;
  1167. }
  1168. return (result != null) ? result : points;
  1169. };
  1170. /**
  1171. * Function: isOutlineConnectEvent
  1172. *
  1173. * Returns true if <outlineConnect> is true and the source of the event is the
  1174. * outline shape or shift is pressed.
  1175. */
  1176. mxEdgeHandler.prototype.isOutlineConnectEvent = function(me)
  1177. {
  1178. if (mxEvent.isShiftDown(me.getEvent()) && mxEvent.isAltDown(me.getEvent()))
  1179. {
  1180. return false;
  1181. }
  1182. else
  1183. {
  1184. var offset = mxUtils.getOffset(this.graph.container);
  1185. var evt = me.getEvent();
  1186. var clientX = mxEvent.getClientX(evt);
  1187. var clientY = mxEvent.getClientY(evt);
  1188. var doc = document.documentElement;
  1189. var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
  1190. var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
  1191. var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
  1192. var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;
  1193. return this.outlineConnect && ((mxEvent.isShiftDown(me.getEvent()) &&
  1194. !mxEvent.isAltDown(me.getEvent())) || (me.isSource(this.marker.highlight.shape) ||
  1195. (!mxEvent.isShiftDown(me.getEvent()) && mxEvent.isAltDown(me.getEvent()) &&
  1196. me.getState() != null) || this.marker.highlight.isHighlightAt(clientX, clientY) ||
  1197. ((gridX != clientX || gridY != clientY) && me.getState() == null &&
  1198. this.marker.highlight.isHighlightAt(gridX, gridY))));
  1199. }
  1200. };
  1201. /**
  1202. * Function: updatePreviewState
  1203. *
  1204. * Updates the given preview state taking into account the state of the constraint handler.
  1205. */
  1206. mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me, outline)
  1207. {
  1208. // Computes the points for the edge style and terminals
  1209. var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true);
  1210. var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false);
  1211. var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);
  1212. var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);
  1213. var constraintHandler = this.getConstraintHandler();
  1214. var constraint = constraintHandler.currentConstraint;
  1215. if (constraint == null && outline)
  1216. {
  1217. if (terminalState != null)
  1218. {
  1219. // Handles special case where mouse is on outline away from actual end point
  1220. // in which case the grid is ignored and mouse point is used instead
  1221. if (me.isSource(this.marker.highlight.shape))
  1222. {
  1223. point = new mxPoint(me.getGraphX(), me.getGraphY());
  1224. }
  1225. constraint = this.graph.getOutlineConstraint(point, terminalState, me);
  1226. constraintHandler.setFocus(me, terminalState, this.isSource);
  1227. constraintHandler.currentConstraint = constraint;
  1228. constraintHandler.currentPoint = point;
  1229. }
  1230. else
  1231. {
  1232. constraint = new mxConnectionConstraint();
  1233. }
  1234. }
  1235. if (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null)
  1236. {
  1237. var s = this.graph.view.scale;
  1238. if (constraintHandler.currentConstraint != null &&
  1239. constraintHandler.currentFocus != null)
  1240. {
  1241. this.marker.highlight.shape.stroke = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent';
  1242. this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
  1243. this.marker.highlight.repaint();
  1244. }
  1245. else if (this.marker.hasValidState())
  1246. {
  1247. this.marker.highlight.shape.stroke = (this.graph.isCellConnectable(me.getCell()) &&
  1248. this.marker.getValidState() != me.getState()) ?
  1249. 'transparent' : mxConstants.DEFAULT_VALID_COLOR;
  1250. this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
  1251. this.marker.highlight.repaint();
  1252. }
  1253. }
  1254. if (this.isSource)
  1255. {
  1256. sourceConstraint = constraint;
  1257. }
  1258. else if (this.isTarget)
  1259. {
  1260. targetConstraint = constraint;
  1261. }
  1262. if (this.isSource || this.isTarget)
  1263. {
  1264. if (constraint != null && constraint.point != null)
  1265. {
  1266. edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x;
  1267. edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
  1268. }
  1269. else
  1270. {
  1271. delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
  1272. delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
  1273. }
  1274. }
  1275. edge.setVisibleTerminalState(sourceState, true);
  1276. edge.setVisibleTerminalState(targetState, false);
  1277. if (!this.isSource || sourceState != null)
  1278. {
  1279. edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);
  1280. }
  1281. if (!this.isTarget || targetState != null)
  1282. {
  1283. edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);
  1284. }
  1285. if ((this.isSource || this.isTarget) && terminalState == null)
  1286. {
  1287. edge.setAbsoluteTerminalPoint(point, this.isSource);
  1288. if (this.marker.getMarkedState() == null)
  1289. {
  1290. this.error = (this.graph.allowDanglingEdges) ? null : '';
  1291. }
  1292. }
  1293. edge.view.updatePoints(edge, this.points, sourceState, targetState);
  1294. edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);
  1295. };
  1296. /**
  1297. * Function: mouseMove
  1298. *
  1299. * Handles the event by updating the preview.
  1300. */
  1301. mxEdgeHandler.prototype.mouseMove = function(sender, me)
  1302. {
  1303. if (this.index != null && this.marker != null)
  1304. {
  1305. var constraintHandler = this.getConstraintHandler();
  1306. this.currentPoint = this.getPointForEvent(me);
  1307. this.error = null;
  1308. // Uses the current point from the constraint handler if available
  1309. if (this.snapPoint != null && mxEvent.isShiftDown(me.getEvent()) &&
  1310. !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&
  1311. constraintHandler.currentFocus == null &&
  1312. constraintHandler.currentFocus != this.state)
  1313. {
  1314. if (Math.abs(this.snapPoint.x - this.currentPoint.x) <
  1315. Math.abs(this.snapPoint.y - this.currentPoint.y))
  1316. {
  1317. this.currentPoint.x = this.snapPoint.x;
  1318. }
  1319. else
  1320. {
  1321. this.currentPoint.y = this.snapPoint.y;
  1322. }
  1323. }
  1324. if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
  1325. {
  1326. if (this.customHandles != null)
  1327. {
  1328. this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
  1329. this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].positionChanged();
  1330. if (this.shape != null && this.shape.node != null)
  1331. {
  1332. this.shape.node.style.display = 'none';
  1333. }
  1334. }
  1335. }
  1336. else if (this.isLabel)
  1337. {
  1338. this.label.x = this.currentPoint.x;
  1339. this.label.y = this.currentPoint.y;
  1340. }
  1341. else
  1342. {
  1343. this.points = this.getPreviewPoints(this.currentPoint, me);
  1344. var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null;
  1345. if (constraintHandler.currentConstraint != null &&
  1346. constraintHandler.currentFocus != null &&
  1347. constraintHandler.currentPoint != null)
  1348. {
  1349. this.currentPoint = constraintHandler.currentPoint.clone();
  1350. }
  1351. else if (this.outlineConnect)
  1352. {
  1353. // Need to check outline before cloning terminal state
  1354. var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false
  1355. if (outline)
  1356. {
  1357. terminalState = this.marker.highlight.state;
  1358. }
  1359. else if (terminalState != null && terminalState != me.getState() &&
  1360. this.graph.isCellConnectable(me.getCell()) &&
  1361. this.marker.highlight.shape != null)
  1362. {
  1363. this.marker.highlight.shape.stroke = 'transparent';
  1364. this.marker.highlight.repaint();
  1365. terminalState = null;
  1366. }
  1367. }
  1368. if (terminalState != null && !this.isCellEnabled(terminalState.cell))
  1369. {
  1370. terminalState = null;
  1371. this.marker.reset();
  1372. }
  1373. var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null);
  1374. this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline);
  1375. // Sets the color of the preview to valid or invalid, updates the
  1376. // points of the preview and redraws
  1377. var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor;
  1378. this.setPreviewColor(color);
  1379. this.abspoints = clone.absolutePoints;
  1380. this.active = true;
  1381. this.updateHint(me, this.currentPoint, clone);
  1382. }
  1383. // This should go before calling isOutlineConnectEvent above. As a workaround
  1384. // we add an offset of gridSize to the hint to avoid problem with hit detection
  1385. // in highlight.isHighlightAt (which uses comonentFromPoint)
  1386. this.drawPreview();
  1387. mxEvent.consume(me.getEvent());
  1388. me.consume();
  1389. }
  1390. else if (!mxEvent.isShiftDown(me.getEvent()) && this.handle != null &&
  1391. this.mouseDownX != null && this.mouseDownY != null)
  1392. {
  1393. var tol = this.graph.tolerance;
  1394. if ((Math.abs(this.mouseDownX - me.getX()) > tol ||
  1395. Math.abs(this.mouseDownY - me.getY()) > tol))
  1396. {
  1397. this.start(this.mouseDownX, this.mouseDownY, this.handle);
  1398. }
  1399. }
  1400. };
  1401. /**
  1402. * Function: mouseUp
  1403. *
  1404. * Handles the event to applying the previewed changes on the edge by
  1405. * using <moveLabel>, <connect> or <changePoints>.
  1406. */
  1407. mxEdgeHandler.prototype.mouseUp = function(sender, me)
  1408. {
  1409. // Workaround for wrong event source in Webkit
  1410. if (this.index != null && this.marker != null)
  1411. {
  1412. if (this.shape != null && this.shape.node != null)
  1413. {
  1414. this.shape.node.style.display = '';
  1415. }
  1416. var edge = this.state.cell;
  1417. var index = this.index;
  1418. this.index = null;
  1419. // Ignores event if mouse has not been moved
  1420. if (me.getX() != this.startX || me.getY() != this.startY)
  1421. {
  1422. var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&
  1423. this.cloneEnabled && this.graph.isCloneEvent(me.getEvent()) &&
  1424. this.graph.isCellsCloneable();
  1425. // Displays the reason for not carriying out the change
  1426. // if there is an error message with non-zero length
  1427. if (this.error != null)
  1428. {
  1429. if (this.error.length > 0)
  1430. {
  1431. this.graph.validationAlert(this.error);
  1432. }
  1433. }
  1434. else if (index <= mxEvent.CUSTOM_HANDLE && index > mxEvent.VIRTUAL_HANDLE)
  1435. {
  1436. if (this.customHandles != null)
  1437. {
  1438. var model = this.graph.getModel();
  1439. model.beginUpdate();
  1440. try
  1441. {
  1442. this.customHandles[mxEvent.CUSTOM_HANDLE - index].execute(me);
  1443. if (this.shape != null && this.shape.node != null)
  1444. {
  1445. this.shape.apply(this.state);
  1446. this.shape.redraw();
  1447. }
  1448. }
  1449. finally
  1450. {
  1451. model.endUpdate();
  1452. }
  1453. }
  1454. }
  1455. else if (this.isLabel)
  1456. {
  1457. this.moveLabel(this.state, this.label.x, this.label.y);
  1458. }
  1459. else if (this.isSource || this.isTarget)
  1460. {
  1461. var terminal = null;
  1462. if (this.constraintHandler != null &&
  1463. this.constraintHandler.currentConstraint != null &&
  1464. this.constraintHandler.currentFocus != null)
  1465. {
  1466. terminal = this.constraintHandler.currentFocus.cell;
  1467. }
  1468. if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null &&
  1469. this.marker.highlight.shape != null &&
  1470. this.marker.highlight.shape.stroke != 'transparent' &&
  1471. this.marker.highlight.shape.stroke != 'white')
  1472. {
  1473. terminal = this.marker.validState.cell;
  1474. }
  1475. if (terminal != null)
  1476. {
  1477. var model = this.graph.getModel();
  1478. var parent = model.getParent(edge);
  1479. model.beginUpdate();
  1480. try
  1481. {
  1482. // Clones and adds the cell
  1483. if (clone)
  1484. {
  1485. var geo = model.getGeometry(edge);
  1486. var clonedEdge = this.graph.cloneCell(edge);
  1487. model.add(parent, clonedEdge, model.getChildCount(parent));
  1488. if (geo != null)
  1489. {
  1490. geo = geo.clone();
  1491. model.setGeometry(clonedEdge, geo);
  1492. }
  1493. var other = model.getTerminal(edge, !this.isSource);
  1494. this.graph.connectCell(clonedEdge, other, !this.isSource);
  1495. edge = clonedEdge;
  1496. }
  1497. edge = this.connect(edge, terminal, this.isSource, clone, me);
  1498. }
  1499. finally
  1500. {
  1501. model.endUpdate();
  1502. }
  1503. }
  1504. else if (this.graph.isAllowDanglingEdges())
  1505. {
  1506. var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];
  1507. pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x);
  1508. pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y);
  1509. var pstate = this.graph.getView().getState(
  1510. this.graph.getModel().getParent(edge));
  1511. if (pstate != null)
  1512. {
  1513. pt.x -= pstate.origin.x;
  1514. pt.y -= pstate.origin.y;
  1515. }
  1516. pt.x -= this.graph.panDx / this.graph.view.scale;
  1517. pt.y -= this.graph.panDy / this.graph.view.scale;
  1518. // Destroys and recreates this handler
  1519. edge = this.changeTerminalPoint(edge, pt, this.isSource, clone);
  1520. }
  1521. }
  1522. else if (this.active)
  1523. {
  1524. edge = this.changePoints(edge, this.points, clone);
  1525. }
  1526. else
  1527. {
  1528. this.graph.getView().invalidate(this.state.cell);
  1529. this.graph.getView().validate(this.state.cell);
  1530. }
  1531. }
  1532. else if (this.graph.isToggleEvent(me.getEvent()))
  1533. {
  1534. this.graph.selectCellForEvent(this.state.cell, me.getEvent());
  1535. }
  1536. // Resets the preview color the state of the handler if this
  1537. // handler has not been recreated
  1538. if (this.marker != null)
  1539. {
  1540. this.reset();
  1541. // Updates the selection if the edge has been cloned
  1542. if (edge != this.state.cell)
  1543. {
  1544. this.graph.setSelectionCell(edge);
  1545. }
  1546. }
  1547. me.consume();
  1548. }
  1549. else if (this.handle != null && this.bends != null &&
  1550. !mxEvent.isAltDown(me.getEvent()) && (this.handle == 0 ||
  1551. this.handle == this.bends.length - 1))
  1552. {
  1553. var terminal = this.state.getVisibleTerminal(this.handle == 0);
  1554. if (terminal != null)
  1555. {
  1556. this.graph.selectCellForEvent(terminal, me.getEvent());
  1557. me.consume();
  1558. }
  1559. }
  1560. this.handle = null;
  1561. this.mouseDownX = null;
  1562. this.mouseDownY = null;
  1563. };
  1564. /**
  1565. * Function: reset
  1566. *
  1567. * Resets the state of this handler.
  1568. */
  1569. mxEdgeHandler.prototype.reset = function()
  1570. {
  1571. if (this.active)
  1572. {
  1573. this.refresh();
  1574. }
  1575. this.error = null;
  1576. this.index = null;
  1577. this.label = null;
  1578. this.points = null;
  1579. this.handle = null;
  1580. this.startX = null;
  1581. this.startY = null;
  1582. this.mouseDownX = null;
  1583. this.mouseDownY = null;
  1584. this.snapPoint = null;
  1585. this.isLabel = false;
  1586. this.isSource = false;
  1587. this.isTarget = false;
  1588. this.active = false;
  1589. if (this.livePreview && this.sizers != null)
  1590. {
  1591. for (var i = 0; i < this.sizers.length; i++)
  1592. {
  1593. if (this.sizers[i] != null)
  1594. {
  1595. this.sizers[i].node.style.display = '';
  1596. }
  1597. }
  1598. }
  1599. if (this.marker != null)
  1600. {
  1601. this.marker.reset();
  1602. }
  1603. if (this.constraintHandler != null)
  1604. {
  1605. this.constraintHandler.reset();
  1606. }
  1607. if (this.customHandles != null)
  1608. {
  1609. for (var i = 0; i < this.customHandles.length; i++)
  1610. {
  1611. this.customHandles[i].reset();
  1612. }
  1613. }
  1614. this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
  1615. this.removeHint();
  1616. this.redraw();
  1617. };
  1618. /**
  1619. * Function: setPreviewColor
  1620. *
  1621. * Sets the color of the preview to the given value.
  1622. */
  1623. mxEdgeHandler.prototype.setPreviewColor = function(color)
  1624. {
  1625. if (this.shape != null)
  1626. {
  1627. this.shape.stroke = color;
  1628. }
  1629. };
  1630. /**
  1631. * Function: convertPoint
  1632. *
  1633. * Converts the given point in-place from screen to unscaled, untranslated
  1634. * graph coordinates and applies the grid. Returns the given, modified
  1635. * point instance.
  1636. *
  1637. * Parameters:
  1638. *
  1639. * point - <mxPoint> to be converted.
  1640. * gridEnabled - Boolean that specifies if the grid should be applied.
  1641. */
  1642. mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
  1643. {
  1644. var scale = this.graph.getView().getScale();
  1645. var tr = this.graph.getView().getTranslate();
  1646. if (gridEnabled)
  1647. {
  1648. point.x = this.graph.snap(point.x);
  1649. point.y = this.graph.snap(point.y);
  1650. }
  1651. point.x = Math.round(point.x / scale - tr.x);
  1652. point.y = Math.round(point.y / scale - tr.y);
  1653. var pstate = this.graph.getView().getState(
  1654. this.graph.getModel().getParent(this.state.cell));
  1655. if (pstate != null)
  1656. {
  1657. point.x -= pstate.origin.x;
  1658. point.y -= pstate.origin.y;
  1659. }
  1660. return point;
  1661. };
  1662. /**
  1663. * Function: moveLabel
  1664. *
  1665. * Changes the coordinates for the label of the given edge.
  1666. *
  1667. * Parameters:
  1668. *
  1669. * edge - <mxCell> that represents the edge.
  1670. * x - Integer that specifies the x-coordinate of the new location.
  1671. * y - Integer that specifies the y-coordinate of the new location.
  1672. */
  1673. mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)
  1674. {
  1675. var model = this.graph.getModel();
  1676. var geometry = model.getGeometry(edgeState.cell);
  1677. if (geometry != null)
  1678. {
  1679. var scale = this.graph.getView().scale;
  1680. geometry = geometry.clone();
  1681. if (geometry.relative)
  1682. {
  1683. // Resets the relative location stored inside the geometry
  1684. var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
  1685. geometry.x = Math.round(pt.x * 10000) / 10000;
  1686. geometry.y = Math.round(pt.y);
  1687. // Resets the offset inside the geometry to find the offset
  1688. // from the resulting point
  1689. geometry.offset = new mxPoint(0, 0);
  1690. var pt = this.graph.view.getPoint(edgeState, geometry);
  1691. geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale));
  1692. }
  1693. else
  1694. {
  1695. var points = edgeState.absolutePoints;
  1696. var p0 = points[0];
  1697. var pe = points[points.length - 1];
  1698. if (p0 != null && pe != null)
  1699. {
  1700. var cx = p0.x + (pe.x - p0.x) / 2;
  1701. var cy = p0.y + (pe.y - p0.y) / 2;
  1702. geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale));
  1703. geometry.x = 0;
  1704. geometry.y = 0;
  1705. }
  1706. }
  1707. model.setGeometry(edgeState.cell, geometry);
  1708. }
  1709. };
  1710. /**
  1711. * Function: connect
  1712. *
  1713. * Changes the terminal or terminal point of the given edge in the graph
  1714. * model.
  1715. *
  1716. * Parameters:
  1717. *
  1718. * edge - <mxCell> that represents the edge to be reconnected.
  1719. * terminal - <mxCell> that represents the new terminal.
  1720. * isSource - Boolean indicating if the new terminal is the source or
  1721. * target terminal.
  1722. * isClone - Boolean indicating if the new connection should be a clone of
  1723. * the old edge.
  1724. * me - <mxMouseEvent> that contains the mouse up event.
  1725. */
  1726. mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
  1727. {
  1728. var model = this.graph.getModel();
  1729. model.beginUpdate();
  1730. try
  1731. {
  1732. var constraint = (this.constraintHandler != null) ?
  1733. this.constraintHandler.currentConstraint : null;
  1734. if (constraint == null)
  1735. {
  1736. constraint = new mxConnectionConstraint();
  1737. }
  1738. this.graph.connectCell(edge, terminal, isSource, constraint);
  1739. }
  1740. finally
  1741. {
  1742. model.endUpdate();
  1743. }
  1744. return edge;
  1745. };
  1746. /**
  1747. * Function: changeTerminalPoint
  1748. *
  1749. * Changes the terminal point of the given edge.
  1750. */
  1751. mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource, clone)
  1752. {
  1753. var model = this.graph.getModel();
  1754. model.beginUpdate();
  1755. try
  1756. {
  1757. if (clone)
  1758. {
  1759. var parent = model.getParent(edge);
  1760. var terminal = model.getTerminal(edge, !isSource);
  1761. edge = this.graph.cloneCell(edge);
  1762. model.add(parent, edge, model.getChildCount(parent));
  1763. model.setTerminal(edge, terminal, !isSource);
  1764. }
  1765. var geo = model.getGeometry(edge);
  1766. if (geo != null)
  1767. {
  1768. geo = geo.clone();
  1769. geo.setTerminalPoint(point, isSource);
  1770. model.setGeometry(edge, geo);
  1771. this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());
  1772. }
  1773. }
  1774. finally
  1775. {
  1776. model.endUpdate();
  1777. }
  1778. return edge;
  1779. };
  1780. /**
  1781. * Function: changePoints
  1782. *
  1783. * Changes the control points of the given edge in the graph model.
  1784. */
  1785. mxEdgeHandler.prototype.changePoints = function(edge, points, clone)
  1786. {
  1787. var model = this.graph.getModel();
  1788. model.beginUpdate();
  1789. try
  1790. {
  1791. if (clone)
  1792. {
  1793. var parent = model.getParent(edge);
  1794. var source = model.getTerminal(edge, true);
  1795. var target = model.getTerminal(edge, false);
  1796. edge = this.graph.cloneCell(edge);
  1797. model.add(parent, edge, model.getChildCount(parent));
  1798. model.setTerminal(edge, source, true);
  1799. model.setTerminal(edge, target, false);
  1800. }
  1801. var geo = model.getGeometry(edge);
  1802. if (geo != null)
  1803. {
  1804. geo = geo.clone();
  1805. geo.points = points;
  1806. model.setGeometry(edge, geo);
  1807. }
  1808. }
  1809. finally
  1810. {
  1811. model.endUpdate();
  1812. }
  1813. return edge;
  1814. };
  1815. /**
  1816. * Function: addPoint
  1817. *
  1818. * Adds a control point for the given state and event.
  1819. */
  1820. mxEdgeHandler.prototype.addPoint = function(state, evt)
  1821. {
  1822. var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt),
  1823. mxEvent.getClientY(evt));
  1824. var gridEnabled = this.graph.isGridEnabledEvent(evt);
  1825. this.convertPoint(pt, gridEnabled);
  1826. this.addPointAt(state, pt.x, pt.y);
  1827. mxEvent.consume(evt);
  1828. };
  1829. /**
  1830. * Function: addPointAt
  1831. *
  1832. * Adds a control point at the given point.
  1833. */
  1834. mxEdgeHandler.prototype.addPointAt = function(state, x, y)
  1835. {
  1836. var geo = this.graph.getCellGeometry(state.cell);
  1837. var pt = new mxPoint(x, y);
  1838. if (geo != null)
  1839. {
  1840. geo = geo.clone();
  1841. var t = this.graph.view.translate;
  1842. var s = this.graph.view.scale;
  1843. var offset = new mxPoint(t.x * s, t.y * s);
  1844. var parent = this.graph.model.getParent(this.state.cell);
  1845. if (this.graph.model.isVertex(parent))
  1846. {
  1847. var pState = this.graph.view.getState(parent);
  1848. offset = new mxPoint(pState.x, pState.y);
  1849. }
  1850. var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y);
  1851. if (geo.points == null)
  1852. {
  1853. geo.points = [pt];
  1854. }
  1855. else
  1856. {
  1857. geo.points.splice(index, 0, pt);
  1858. }
  1859. this.graph.getModel().setGeometry(state.cell, geo);
  1860. this.refresh();
  1861. this.redraw();
  1862. }
  1863. };
  1864. /**
  1865. * Function: removePoint
  1866. *
  1867. * Removes the control point at the given index from the given state.
  1868. */
  1869. mxEdgeHandler.prototype.removePoint = function(state, index)
  1870. {
  1871. if (index > 0 && index < this.abspoints.length - 1)
  1872. {
  1873. var geo = this.graph.getCellGeometry(this.state.cell);
  1874. if (geo != null && geo.points != null)
  1875. {
  1876. geo = geo.clone();
  1877. geo.points.splice(index - 1, 1);
  1878. this.graph.getModel().setGeometry(state.cell, geo);
  1879. this.refresh();
  1880. this.redraw();
  1881. }
  1882. }
  1883. };
  1884. /**
  1885. * Function: getHandleFillColor
  1886. *
  1887. * Returns the fillcolor for the handle at the given index.
  1888. */
  1889. mxEdgeHandler.prototype.getHandleFillColor = function(index)
  1890. {
  1891. var isSource = index == 0;
  1892. var cell = this.state.cell;
  1893. var terminal = this.graph.getModel().getTerminal(cell, isSource);
  1894. var color = mxConstants.HANDLE_FILLCOLOR;
  1895. if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||
  1896. (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource)))
  1897. {
  1898. color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
  1899. }
  1900. else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource))
  1901. {
  1902. color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
  1903. }
  1904. return color;
  1905. };
  1906. /**
  1907. * Function: redraw
  1908. *
  1909. * Redraws the preview, and the bends- and label control points.
  1910. */
  1911. mxEdgeHandler.prototype.redraw = function(ignoreHandles)
  1912. {
  1913. if (this.state != null && this.state.absolutePoints != null)
  1914. {
  1915. this.abspoints = this.state.absolutePoints.slice();
  1916. var g = this.graph.getModel().getGeometry(this.state.cell);
  1917. if (g != null)
  1918. {
  1919. var pts = g.points;
  1920. if (this.bends != null && this.bends.length > 0)
  1921. {
  1922. if (pts != null)
  1923. {
  1924. if (this.points == null)
  1925. {
  1926. this.points = [];
  1927. }
  1928. for (var i = 1; i < this.bends.length - 1; i++)
  1929. {
  1930. if (this.bends[i] != null && this.abspoints[i] != null)
  1931. {
  1932. this.points[i - 1] = pts[i - 1];
  1933. }
  1934. }
  1935. }
  1936. }
  1937. }
  1938. this.drawPreview();
  1939. if (!ignoreHandles)
  1940. {
  1941. this.redrawHandles();
  1942. }
  1943. }
  1944. };
  1945. /**
  1946. * Function: isTerminalHandleVisible
  1947. *
  1948. * Redraws the handles.
  1949. */
  1950. mxEdgeHandler.prototype.isTerminalHandleVisible = function(source)
  1951. {
  1952. return true;
  1953. };
  1954. /**
  1955. * Function: redrawHandles
  1956. *
  1957. * Redraws the handles.
  1958. */
  1959. mxEdgeHandler.prototype.redrawHandles = function()
  1960. {
  1961. var cell = this.state.cell;
  1962. // Updates the handle for the label position
  1963. if (this.labelShape != null)
  1964. {
  1965. var b = this.labelShape.bounds;
  1966. this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
  1967. this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
  1968. Math.round(this.label.y - b.height / 2), b.width, b.height);
  1969. // Shows or hides the label handle depending on the label
  1970. var lab = this.graph.getLabel(cell);
  1971. this.labelShape.visible = lab != null && lab.length > 0 &&
  1972. this.graph.isCellEditable(this.state.cell) &&
  1973. this.graph.isLabelMovable(cell) &&
  1974. this.isHandlesVisible();
  1975. }
  1976. if (this.bends != null && this.bends.length > 0)
  1977. {
  1978. var n = this.abspoints.length - 1;
  1979. var p0 = this.abspoints[0];
  1980. var x0 = p0.x;
  1981. var y0 = p0.y;
  1982. b = this.bends[0].bounds;
  1983. this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2),
  1984. Math.floor(y0 - b.height / 2), b.width, b.height);
  1985. this.bends[0].fill = this.getHandleFillColor(0);
  1986. this.bends[0].redraw();
  1987. if (this.manageLabelHandle)
  1988. {
  1989. this.checkLabelHandle(this.bends[0].bounds);
  1990. }
  1991. this.bends[0].node.style.visibility = (!this.isHandlesVisible() ||
  1992. !this.isTerminalHandleVisible(true)) ? 'hidden' : '';
  1993. var pe = this.abspoints[n];
  1994. var xn = pe.x;
  1995. var yn = pe.y;
  1996. var bn = this.bends.length - 1;
  1997. b = this.bends[bn].bounds;
  1998. this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2),
  1999. Math.floor(yn - b.height / 2), b.width, b.height);
  2000. this.bends[bn].fill = this.getHandleFillColor(bn);
  2001. this.bends[bn].redraw();
  2002. if (this.manageLabelHandle)
  2003. {
  2004. this.checkLabelHandle(this.bends[bn].bounds);
  2005. }
  2006. this.bends[bn].node.style.visibility = (!this.isHandlesVisible() ||
  2007. !this.isTerminalHandleVisible(false)) ? 'hidden' : '';
  2008. this.redrawInnerBends(p0, pe);
  2009. }
  2010. if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0)
  2011. {
  2012. var last = this.abspoints[0];
  2013. for (var i = 0; i < this.virtualBends.length; i++)
  2014. {
  2015. if (this.virtualBends[i] != null && this.abspoints[i + 1] != null)
  2016. {
  2017. var pt = this.abspoints[i + 1];
  2018. var b = this.virtualBends[i];
  2019. var x = last.x + (pt.x - last.x) / 2;
  2020. var y = last.y + (pt.y - last.y) / 2;
  2021. b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2),
  2022. Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height);
  2023. b.redraw();
  2024. mxUtils.setOpacity(b.node, this.virtualBendOpacity);
  2025. last = pt;
  2026. if (this.manageLabelHandle)
  2027. {
  2028. this.checkLabelHandle(b.bounds);
  2029. }
  2030. b.node.style.visibility = (!this.isHandlesVisible()) ? 'hidden' : '';
  2031. }
  2032. }
  2033. }
  2034. if (this.labelShape != null)
  2035. {
  2036. this.labelShape.redraw();
  2037. }
  2038. if (this.customHandles != null)
  2039. {
  2040. for (var i = 0; i < this.customHandles.length; i++)
  2041. {
  2042. var temp = this.customHandles[i].shape.node.style.display;
  2043. this.customHandles[i].redraw();
  2044. this.customHandles[i].shape.node.style.display = temp;
  2045. // Hides custom handles during text editing
  2046. this.customHandles[i].shape.node.style.visibility =
  2047. (this.graph.isEditing() || !this.isHandlesVisible() ||
  2048. !this.isCustomHandleVisible(this.customHandles[i])) ?
  2049. 'hidden' : '';
  2050. }
  2051. }
  2052. };
  2053. /**
  2054. * Function: isCustomHandleVisible
  2055. *
  2056. * Returns true if the given custom handle is visible.
  2057. */
  2058. mxEdgeHandler.prototype.isCustomHandleVisible = function(handle)
  2059. {
  2060. return this.state.view.graph.getSelectionCount() == 1;
  2061. };
  2062. /**
  2063. * Function: hideHandles
  2064. *
  2065. * Shortcut to <hideSizers>.
  2066. */
  2067. mxEdgeHandler.prototype.setHandlesVisible = function(visible)
  2068. {
  2069. if (this.bends != null)
  2070. {
  2071. for (var i = 0; i < this.bends.length; i++)
  2072. {
  2073. if (this.bends[i] != null)
  2074. {
  2075. this.bends[i].node.style.display = (visible) ? '' : 'none';
  2076. }
  2077. }
  2078. }
  2079. if (this.virtualBends != null)
  2080. {
  2081. for (var i = 0; i < this.virtualBends.length; i++)
  2082. {
  2083. if (this.virtualBends[i] != null)
  2084. {
  2085. this.virtualBends[i].node.style.display = (visible) ? '' : 'none';
  2086. }
  2087. }
  2088. }
  2089. if (this.labelShape != null)
  2090. {
  2091. this.labelShape.node.style.display = (visible) ? '' : 'none';
  2092. }
  2093. if (this.customHandles != null)
  2094. {
  2095. for (var i = 0; i < this.customHandles.length; i++)
  2096. {
  2097. this.customHandles[i].setVisible(visible);
  2098. }
  2099. }
  2100. };
  2101. /**
  2102. * Function: redrawInnerBends
  2103. *
  2104. * Updates and redraws the inner bends.
  2105. *
  2106. * Parameters:
  2107. *
  2108. * p0 - <mxPoint> that represents the location of the first point.
  2109. * pe - <mxPoint> that represents the location of the last point.
  2110. */
  2111. mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
  2112. {
  2113. for (var i = 1; i < this.bends.length - 1; i++)
  2114. {
  2115. if (this.bends[i] != null)
  2116. {
  2117. if (this.abspoints[i] != null)
  2118. {
  2119. var x = this.abspoints[i].x;
  2120. var y = this.abspoints[i].y;
  2121. var b = this.bends[i].bounds;
  2122. this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2),
  2123. Math.round(y - b.height / 2), b.width, b.height);
  2124. if (this.manageLabelHandle)
  2125. {
  2126. this.checkLabelHandle(this.bends[i].bounds);
  2127. }
  2128. else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds))
  2129. {
  2130. w = mxConstants.HANDLE_SIZE + 3;
  2131. h = mxConstants.HANDLE_SIZE + 3;
  2132. this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h);
  2133. }
  2134. this.bends[i].redraw();
  2135. this.bends[i].node.style.visibility = (!this.isHandlesVisible()) ? 'hidden' : '';
  2136. }
  2137. else
  2138. {
  2139. this.bends[i].destroy();
  2140. this.bends[i] = null;
  2141. }
  2142. }
  2143. }
  2144. };
  2145. /**
  2146. * Function: checkLabelHandle
  2147. *
  2148. * Checks if the label handle intersects the given bounds and moves it if it
  2149. * intersects.
  2150. */
  2151. mxEdgeHandler.prototype.checkLabelHandle = function(b)
  2152. {
  2153. if (this.labelShape != null)
  2154. {
  2155. var b2 = this.labelShape.bounds;
  2156. if (mxUtils.intersects(b, b2))
  2157. {
  2158. if (b.getCenterY() < b2.getCenterY())
  2159. {
  2160. b2.y = b.y + b.height;
  2161. }
  2162. else
  2163. {
  2164. b2.y = b.y - b2.height;
  2165. }
  2166. }
  2167. }
  2168. };
  2169. /**
  2170. * Function: drawPreview
  2171. *
  2172. * Redraws the preview.
  2173. */
  2174. mxEdgeHandler.prototype.drawPreview = function()
  2175. {
  2176. try
  2177. {
  2178. if (this.isLabel)
  2179. {
  2180. var b = this.labelShape.bounds;
  2181. var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
  2182. Math.round(this.label.y - b.height / 2), b.width, b.height);
  2183. if (!this.labelShape.bounds.equals(bounds))
  2184. {
  2185. this.labelShape.bounds = bounds;
  2186. this.labelShape.redraw();
  2187. }
  2188. }
  2189. if (this.shape != null && !mxUtils.equalPoints(this.shape.points, this.abspoints))
  2190. {
  2191. this.shape.apply(this.state);
  2192. this.shape.points = this.abspoints.slice();
  2193. this.shape.scale = this.state.view.scale;
  2194. this.shape.isDashed = this.isSelectionDashed();
  2195. this.shape.stroke = this.getSelectionColor();
  2196. this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale;
  2197. this.shape.isShadow = false;
  2198. this.shape.redraw();
  2199. }
  2200. this.updateParentHighlight();
  2201. }
  2202. catch (e)
  2203. {
  2204. // ignore
  2205. }
  2206. };
  2207. /**
  2208. * Function: isHandlesVisible
  2209. *
  2210. * Returns true if all handles should be visible.
  2211. */
  2212. mxEdgeHandler.prototype.isHandlesVisible = function()
  2213. {
  2214. return !this.graph.isCellLocked(this.state.cell) &&
  2215. (mxGraphHandler.prototype.maxCells <= 0 ||
  2216. this.graph.getSelectionCount() <= mxGraphHandler.prototype.maxCells);
  2217. };
  2218. /**
  2219. * Function: refresh
  2220. *
  2221. * Refreshes the bends of this handler.
  2222. */
  2223. mxEdgeHandler.prototype.refresh = function()
  2224. {
  2225. if (this.state != null)
  2226. {
  2227. this.abspoints = this.getSelectionPoints(this.state);
  2228. this.points = [];
  2229. if (this.shape != null)
  2230. {
  2231. this.shape.isDashed = this.isSelectionDashed();
  2232. this.shape.stroke = this.getSelectionColor();
  2233. this.shape.isShadow = false;
  2234. this.shape.redraw();
  2235. }
  2236. if (this.bends != null)
  2237. {
  2238. this.destroyBends(this.bends);
  2239. this.bends = null;
  2240. }
  2241. if (this.isHandlesVisible())
  2242. {
  2243. this.bends = this.createBends();
  2244. }
  2245. if (this.virtualBends != null)
  2246. {
  2247. this.destroyBends(this.virtualBends);
  2248. this.virtualBends = null;
  2249. }
  2250. if (this.isHandlesVisible())
  2251. {
  2252. this.virtualBends = this.createVirtualBends();
  2253. }
  2254. if (this.customHandles != null)
  2255. {
  2256. this.destroyBends(this.customHandles);
  2257. this.customHandles = null;
  2258. }
  2259. if (this.isHandlesVisible())
  2260. {
  2261. this.customHandles = this.createCustomHandles();
  2262. }
  2263. if (this.labelShape != null)
  2264. {
  2265. this.labelShape.destroy();
  2266. this.labelShape = null;
  2267. }
  2268. if (this.isHandlesVisible())
  2269. {
  2270. this.labelShape = this.createLabelShape();
  2271. // Puts label node on top of bends
  2272. if (this.labelShape != null && this.labelShape.node != null &&
  2273. this.labelShape.node.parentNode != null)
  2274. {
  2275. this.labelShape.node.parentNode.appendChild(this.labelShape.node);
  2276. }
  2277. }
  2278. }
  2279. };
  2280. /**
  2281. * Function: isDestroyed
  2282. *
  2283. * Returns true if <destroy> was called.
  2284. */
  2285. mxEdgeHandler.prototype.isDestroyed = function()
  2286. {
  2287. return this.shape == null;
  2288. };
  2289. /**
  2290. * Function: destroyBends
  2291. *
  2292. * Destroys all elements in <bends>.
  2293. */
  2294. mxEdgeHandler.prototype.destroyBends = function(bends)
  2295. {
  2296. if (bends != null)
  2297. {
  2298. for (var i = 0; i < bends.length; i++)
  2299. {
  2300. if (bends[i] != null)
  2301. {
  2302. bends[i].destroy();
  2303. }
  2304. }
  2305. }
  2306. };
  2307. /**
  2308. * Function: destroy
  2309. *
  2310. * Destroys the handler and all its resources and DOM nodes. This does
  2311. * normally not need to be called as handlers are destroyed automatically
  2312. * when the corresponding cell is deselected.
  2313. */
  2314. mxEdgeHandler.prototype.destroy = function()
  2315. {
  2316. if (this.escapeHandler != null)
  2317. {
  2318. this.state.view.graph.removeListener(this.escapeHandler);
  2319. this.escapeHandler = null;
  2320. }
  2321. if (this.marker != null)
  2322. {
  2323. this.marker.destroy();
  2324. this.marker = null;
  2325. }
  2326. if (this.shape != null)
  2327. {
  2328. this.shape.destroy();
  2329. this.shape = null;
  2330. }
  2331. if (this.labelShape != null)
  2332. {
  2333. this.labelShape.destroy();
  2334. this.labelShape = null;
  2335. }
  2336. if (this.constraintHandler != null)
  2337. {
  2338. this.constraintHandler.destroy();
  2339. this.constraintHandler = null;
  2340. }
  2341. if (this.parentHighlight != null)
  2342. {
  2343. this.destroyParentHighlight();
  2344. }
  2345. this.destroyBends(this.virtualBends);
  2346. this.virtualBends = null;
  2347. this.destroyBends(this.customHandles);
  2348. this.customHandles = null;
  2349. this.destroyBends(this.bends);
  2350. this.bends = null;
  2351. this.removeHint();
  2352. };