mxCellRenderer.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxCellRenderer
  7. *
  8. * Renders cells into a document object model. The <defaultShapes> is a global
  9. * map of shapename, constructor pairs that is used in all instances. You can
  10. * get a list of all available shape names using the following code.
  11. *
  12. * In general the cell renderer is in charge of creating, redrawing and
  13. * destroying the shape and label associated with a cell state, as well as
  14. * some other graphical objects, namely controls and overlays. The shape
  15. * hieararchy in the display (ie. the hierarchy in which the DOM nodes
  16. * appear in the document) does not reflect the cell hierarchy. The shapes
  17. * are a (flat) sequence of shapes and labels inside the draw pane of the
  18. * graph view, with some exceptions, namely the HTML labels being placed
  19. * directly inside the graph container for certain browsers.
  20. *
  21. * (code)
  22. * mxLog.show();
  23. * for (var i in mxCellRenderer.defaultShapes)
  24. * {
  25. * mxLog.debug(i);
  26. * }
  27. * (end)
  28. *
  29. * Constructor: mxCellRenderer
  30. *
  31. * Constructs a new cell renderer with the following built-in shapes:
  32. * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
  33. * swimlane, connector, actor and cloud.
  34. */
  35. function mxCellRenderer() { };
  36. /**
  37. * Variable: defaultShapes
  38. *
  39. * Static array that contains the globally registered shapes which are
  40. * known to all instances of this class. For adding new shapes you should
  41. * use the static <mxCellRenderer.registerShape> function.
  42. */
  43. mxCellRenderer.defaultShapes = new Object();
  44. /**
  45. * Variable: defaultEdgeShape
  46. *
  47. * Defines the default shape for edges. Default is <mxConnector>.
  48. */
  49. mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
  50. /**
  51. * Variable: defaultVertexShape
  52. *
  53. * Defines the default shape for vertices. Default is <mxRectangleShape>.
  54. */
  55. mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
  56. /**
  57. * Variable: defaultTextShape
  58. *
  59. * Defines the default shape for labels. Default is <mxText>.
  60. */
  61. mxCellRenderer.prototype.defaultTextShape = mxText;
  62. /**
  63. * Variable: legacyControlPosition
  64. *
  65. * Specifies if the folding icon should ignore the horizontal
  66. * orientation of a swimlane. Default is true.
  67. */
  68. mxCellRenderer.prototype.legacyControlPosition = true;
  69. /**
  70. * Variable: legacySpacing
  71. *
  72. * Specifies if spacing and label position should be ignored if overflow is
  73. * fill or width. Default is true for backwards compatiblity.
  74. */
  75. mxCellRenderer.prototype.legacySpacing = true;
  76. /**
  77. * Variable: antiAlias
  78. *
  79. * Anti-aliasing option for new shapes. Default is true.
  80. */
  81. mxCellRenderer.prototype.antiAlias = true;
  82. /**
  83. * Variable: minSvgStrokeWidth
  84. *
  85. * Minimum stroke width for SVG output.
  86. */
  87. mxCellRenderer.prototype.minSvgStrokeWidth = 1;
  88. /**
  89. * Variable: forceControlClickHandler
  90. *
  91. * Specifies if the enabled state of the graph should be ignored in the control
  92. * click handler (to allow folding in disabled graphs). Default is false.
  93. */
  94. mxCellRenderer.prototype.forceControlClickHandler = false;
  95. /**
  96. * Function: registerShape
  97. *
  98. * Registers the given constructor under the specified key in this instance
  99. * of the renderer.
  100. *
  101. * Example:
  102. *
  103. * (code)
  104. * mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
  105. * (end)
  106. *
  107. * Parameters:
  108. *
  109. * key - String representing the shape name.
  110. * shape - Constructor of the <mxShape> subclass.
  111. */
  112. mxCellRenderer.registerShape = function(key, shape)
  113. {
  114. mxCellRenderer.defaultShapes[key] = shape;
  115. };
  116. // Adds default shapes into the default shapes array
  117. mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
  118. mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);
  119. mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);
  120. mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);
  121. mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);
  122. mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);
  123. mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);
  124. mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);
  125. mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);
  126. mxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);
  127. mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);
  128. mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR, mxArrowConnector);
  129. mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);
  130. mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);
  131. mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);
  132. mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);
  133. /**
  134. * Function: initializeShape
  135. *
  136. * Initializes the shape in the given state by calling its init method with
  137. * the correct container after configuring it using <configureShape>.
  138. *
  139. * Parameters:
  140. *
  141. * state - <mxCellState> for which the shape should be initialized.
  142. */
  143. mxCellRenderer.prototype.initializeShape = function(state)
  144. {
  145. state.shape.dialect = state.view.graph.dialect;
  146. this.configureShape(state);
  147. state.shape.init(state.view.getDrawPane());
  148. };
  149. /**
  150. * Function: createShape
  151. *
  152. * Creates and returns the shape for the given cell state.
  153. *
  154. * Parameters:
  155. *
  156. * state - <mxCellState> for which the shape should be created.
  157. */
  158. mxCellRenderer.prototype.createShape = function(state)
  159. {
  160. var shape = null;
  161. if (state.style != null)
  162. {
  163. // Checks if there is a stencil for the name and creates
  164. // a shape instance for the stencil if one exists
  165. var stencil = mxStencilRegistry.getStencil(state.style[mxConstants.STYLE_SHAPE]);
  166. if (stencil != null)
  167. {
  168. shape = new mxShape(stencil);
  169. }
  170. else
  171. {
  172. var ctor = this.getShapeConstructor(state);
  173. shape = new ctor();
  174. }
  175. }
  176. return shape;
  177. };
  178. /**
  179. * Function: createIndicatorShape
  180. *
  181. * Creates the indicator shape for the given cell state.
  182. *
  183. * Parameters:
  184. *
  185. * state - <mxCellState> for which the indicator shape should be created.
  186. */
  187. mxCellRenderer.prototype.createIndicatorShape = function(state)
  188. {
  189. state.shape.indicatorShape = this.getShape(state.view.graph.getIndicatorShape(state));
  190. };
  191. /**
  192. * Function: getShape
  193. *
  194. * Returns the shape for the given name from <defaultShapes>.
  195. */
  196. mxCellRenderer.prototype.getShape = function(name)
  197. {
  198. return (name != null) ? mxCellRenderer.defaultShapes[name] : null;
  199. };
  200. /**
  201. * Function: getShapeConstructor
  202. *
  203. * Returns the constructor to be used for creating the shape.
  204. */
  205. mxCellRenderer.prototype.getShapeConstructor = function(state)
  206. {
  207. var ctor = this.getShape(state.style[mxConstants.STYLE_SHAPE]);
  208. if (ctor == null)
  209. {
  210. ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
  211. this.defaultEdgeShape : this.defaultVertexShape;
  212. }
  213. return ctor;
  214. };
  215. /**
  216. * Function: configureShape
  217. *
  218. * Configures the shape for the given cell state.
  219. *
  220. * Parameters:
  221. *
  222. * state - <mxCellState> for which the shape should be configured.
  223. */
  224. mxCellRenderer.prototype.configureShape = function(state)
  225. {
  226. state.shape.apply(state);
  227. state.shape.image = state.view.graph.getImage(state);
  228. state.shape.indicatorColor = state.view.graph.getIndicatorColor(state);
  229. state.shape.indicatorStrokeColor = state.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];
  230. state.shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(state);
  231. state.shape.indicatorDirection = state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
  232. state.shape.indicatorImage = state.view.graph.getIndicatorImage(state);
  233. this.postConfigureShape(state);
  234. };
  235. /**
  236. * Function: postConfigureShape
  237. *
  238. * Replaces any reserved words used for attributes, eg. inherit,
  239. * indicated or swimlane for colors in the shape for the given state.
  240. * This implementation resolves these keywords on the fill, stroke
  241. * and gradient color keys.
  242. */
  243. mxCellRenderer.prototype.postConfigureShape = function(state)
  244. {
  245. if (state.shape != null)
  246. {
  247. this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
  248. this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
  249. this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
  250. this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
  251. this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
  252. }
  253. };
  254. /**
  255. * Function: checkPlaceholderStyles
  256. *
  257. * Checks if the style of the given <mxCellState> contains 'inherit',
  258. * 'indicated' or 'swimlane' for colors that support those keywords.
  259. */
  260. mxCellRenderer.prototype.checkPlaceholderStyles = function(state)
  261. {
  262. // LATER: Check if the color has actually changed
  263. if (state.style != null)
  264. {
  265. var values = ['inherit', 'swimlane', 'indicated'];
  266. var styles = [mxConstants.STYLE_FILLCOLOR, mxConstants.STYLE_STROKECOLOR,
  267. mxConstants.STYLE_GRADIENTCOLOR, mxConstants.STYLE_FONTCOLOR];
  268. for (var i = 0; i < styles.length; i++)
  269. {
  270. if (mxUtils.indexOf(values, state.style[styles[i]]) >= 0)
  271. {
  272. return true;
  273. }
  274. }
  275. }
  276. return false;
  277. };
  278. /**
  279. * Function: resolveColor
  280. *
  281. * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
  282. * the respective color on the shape.
  283. */
  284. mxCellRenderer.prototype.resolveColor = function(state, field, key)
  285. {
  286. var shape = (key == mxConstants.STYLE_FONTCOLOR) ?
  287. state.text : state.shape;
  288. if (shape != null)
  289. {
  290. var graph = state.view.graph;
  291. var value = shape[field];
  292. var referenced = null;
  293. if (value == 'inherit')
  294. {
  295. referenced = graph.model.getParent(state.cell);
  296. }
  297. else if (value == 'swimlane')
  298. {
  299. shape[field] = (key == mxConstants.STYLE_STROKECOLOR ||
  300. key == mxConstants.STYLE_FONTCOLOR) ?
  301. '#000000' : '#ffffff';
  302. if (graph.model.getTerminal(state.cell, false) != null)
  303. {
  304. referenced = graph.model.getTerminal(state.cell, false);
  305. }
  306. else
  307. {
  308. referenced = state.cell;
  309. }
  310. referenced = graph.getSwimlane(referenced);
  311. key = graph.swimlaneIndicatorColorAttribute;
  312. }
  313. else if (value == 'indicated' && state.shape != null)
  314. {
  315. shape[field] = state.shape.indicatorColor;
  316. }
  317. else if (key != mxConstants.STYLE_FILLCOLOR &&
  318. value == mxConstants.STYLE_FILLCOLOR &&
  319. state.shape != null)
  320. {
  321. shape[field] = state.style[mxConstants.STYLE_FILLCOLOR];
  322. }
  323. else if (key != mxConstants.STYLE_STROKECOLOR &&
  324. value == mxConstants.STYLE_STROKECOLOR &&
  325. state.shape != null)
  326. {
  327. shape[field] = state.style[mxConstants.STYLE_STROKECOLOR];
  328. }
  329. if (referenced != null)
  330. {
  331. var rstate = graph.getView().getState(referenced);
  332. shape[field] = null;
  333. if (rstate != null)
  334. {
  335. var rshape = (key == mxConstants.STYLE_FONTCOLOR) ? rstate.text : rstate.shape;
  336. if (rshape != null && field != 'indicatorColor')
  337. {
  338. shape[field] = rshape[field];
  339. }
  340. else
  341. {
  342. shape[field] = rstate.style[key];
  343. }
  344. }
  345. }
  346. }
  347. };
  348. /**
  349. * Function: getLabelValue
  350. *
  351. * Returns the value to be used for the label.
  352. *
  353. * Parameters:
  354. *
  355. * state - <mxCellState> for which the label should be created.
  356. */
  357. mxCellRenderer.prototype.getLabelValue = function(state)
  358. {
  359. return state.view.graph.getLabel(state.cell);
  360. };
  361. /**
  362. * Function: createLabel
  363. *
  364. * Creates the label for the given cell state.
  365. *
  366. * Parameters:
  367. *
  368. * state - <mxCellState> for which the label should be created.
  369. */
  370. mxCellRenderer.prototype.createLabel = function(state, value)
  371. {
  372. var graph = state.view.graph;
  373. var isEdge = graph.getModel().isEdge(state.cell);
  374. if (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null)
  375. {
  376. // Avoids using DOM node for empty labels
  377. var isForceHtml = (graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
  378. state.text = new this.defaultTextShape(value, new mxRectangle(),
  379. (state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER),
  380. graph.getVerticalAlign(state),
  381. state.style[mxConstants.STYLE_FONTCOLOR],
  382. state.style[mxConstants.STYLE_FONTFAMILY],
  383. state.style[mxConstants.STYLE_FONTSIZE],
  384. state.style[mxConstants.STYLE_FONTSTYLE],
  385. state.style[mxConstants.STYLE_SPACING],
  386. state.style[mxConstants.STYLE_SPACING_TOP],
  387. state.style[mxConstants.STYLE_SPACING_RIGHT],
  388. state.style[mxConstants.STYLE_SPACING_BOTTOM],
  389. state.style[mxConstants.STYLE_SPACING_LEFT],
  390. state.style[mxConstants.STYLE_HORIZONTAL],
  391. state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
  392. state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
  393. graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
  394. graph.isLabelClipped(state.cell),
  395. state.style[mxConstants.STYLE_OVERFLOW],
  396. state.style[mxConstants.STYLE_LABEL_PADDING],
  397. mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION));
  398. state.text.opacity = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100);
  399. state.text.dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
  400. state.text.style = state.style;
  401. state.text.state = state;
  402. this.initializeLabel(state, state.text);
  403. // Workaround for touch devices routing all events for a mouse gesture
  404. // (down, move, up) via the initial DOM node. IE additionally redirects
  405. // the event via the initial DOM node but the event source is the node
  406. // under the mouse, so we need to check if this is the case and force
  407. // getCellAt for the subsequent mouseMoves and the final mouseUp.
  408. var forceGetCell = false;
  409. var getState = function(evt)
  410. {
  411. var result = state;
  412. if (mxClient.IS_TOUCH || forceGetCell)
  413. {
  414. var x = mxEvent.getClientX(evt);
  415. var y = mxEvent.getClientY(evt);
  416. // Dispatches the drop event to the graph which
  417. // consumes and executes the source function
  418. var pt = mxUtils.convertPoint(graph.container, x, y);
  419. result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
  420. }
  421. return result;
  422. };
  423. // TODO: Add handling for special touch device gestures
  424. mxEvent.addGestureListeners(state.text.node,
  425. mxUtils.bind(this, function(evt)
  426. {
  427. if (this.isLabelEvent(state, evt))
  428. {
  429. graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
  430. forceGetCell = graph.dialect != mxConstants.DIALECT_SVG &&
  431. mxEvent.getSource(evt).nodeName == 'IMG';
  432. }
  433. }),
  434. mxUtils.bind(this, function(evt)
  435. {
  436. if (this.isLabelEvent(state, evt))
  437. {
  438. graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
  439. }
  440. }),
  441. mxUtils.bind(this, function(evt)
  442. {
  443. if (this.isLabelEvent(state, evt))
  444. {
  445. graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
  446. forceGetCell = false;
  447. }
  448. })
  449. );
  450. // Uses double click timeout in mxGraph for quirks mode
  451. if (graph.nativeDblClickEnabled)
  452. {
  453. mxEvent.addListener(state.text.node, 'dblclick',
  454. mxUtils.bind(this, function(evt)
  455. {
  456. if (this.isLabelEvent(state, evt))
  457. {
  458. graph.dblClick(evt, state.cell);
  459. mxEvent.consume(evt);
  460. }
  461. })
  462. );
  463. }
  464. }
  465. };
  466. /**
  467. * Function: initializeLabel
  468. *
  469. * Initiailzes the label with a suitable container.
  470. *
  471. * Parameters:
  472. *
  473. * state - <mxCellState> whose label should be initialized.
  474. */
  475. mxCellRenderer.prototype.initializeLabel = function(state, shape)
  476. {
  477. if (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect != mxConstants.DIALECT_SVG)
  478. {
  479. shape.init(state.view.graph.container);
  480. }
  481. else
  482. {
  483. shape.init(state.view.getDrawPane());
  484. }
  485. };
  486. /**
  487. * Function: createCellOverlays
  488. *
  489. * Creates the actual shape for showing the overlay for the given cell state.
  490. *
  491. * Parameters:
  492. *
  493. * state - <mxCellState> for which the overlay should be created.
  494. */
  495. mxCellRenderer.prototype.createCellOverlays = function(state)
  496. {
  497. var graph = state.view.graph;
  498. var overlays = graph.getCellOverlays(state.cell);
  499. var dict = null;
  500. if (overlays != null)
  501. {
  502. dict = new mxDictionary();
  503. for (var i = 0; i < overlays.length; i++)
  504. {
  505. var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
  506. if (shape == null)
  507. {
  508. var tmp = new mxImageShape(new mxRectangle(), overlays[i].image.src);
  509. tmp.dialect = state.view.graph.dialect;
  510. tmp.preserveImageAspect = false;
  511. tmp.overlay = overlays[i];
  512. this.initializeOverlay(state, tmp);
  513. this.installCellOverlayListeners(state, overlays[i], tmp);
  514. if (overlays[i].cursor != null)
  515. {
  516. tmp.node.style.cursor = overlays[i].cursor;
  517. }
  518. dict.put(overlays[i], tmp);
  519. }
  520. else
  521. {
  522. dict.put(overlays[i], shape);
  523. }
  524. }
  525. }
  526. // Removes unused
  527. if (state.overlays != null)
  528. {
  529. state.overlays.visit(function(id, shape)
  530. {
  531. shape.destroy();
  532. });
  533. }
  534. state.overlays = dict;
  535. };
  536. /**
  537. * Function: initializeOverlay
  538. *
  539. * Initializes the given overlay.
  540. *
  541. * Parameters:
  542. *
  543. * state - <mxCellState> for which the overlay should be created.
  544. * overlay - <mxImageShape> that represents the overlay.
  545. */
  546. mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
  547. {
  548. overlay.init(state.view.getOverlayPane());
  549. };
  550. /**
  551. * Function: installOverlayListeners
  552. *
  553. * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
  554. * <mxShape> that represents the overlay.
  555. */
  556. mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
  557. {
  558. var graph = state.view.graph;
  559. mxEvent.addListener(shape.node, 'click', function (evt)
  560. {
  561. if (graph.isEditing())
  562. {
  563. graph.stopEditing(!graph.isInvokesStopCellEditing());
  564. }
  565. overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
  566. 'event', evt, 'cell', state.cell));
  567. });
  568. mxEvent.addGestureListeners(shape.node,
  569. function (evt)
  570. {
  571. mxEvent.consume(evt);
  572. },
  573. function (evt)
  574. {
  575. graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
  576. new mxMouseEvent(evt, state));
  577. });
  578. if (mxClient.IS_TOUCH)
  579. {
  580. mxEvent.addListener(shape.node, 'touchend', function (evt)
  581. {
  582. overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
  583. 'event', evt, 'cell', state.cell));
  584. });
  585. }
  586. };
  587. /**
  588. * Function: createControl
  589. *
  590. * Creates the control for the given cell state.
  591. *
  592. * Parameters:
  593. *
  594. * state - <mxCellState> for which the control should be created.
  595. */
  596. mxCellRenderer.prototype.createControl = function(state)
  597. {
  598. var graph = state.view.graph;
  599. var image = graph.getFoldingImage(state);
  600. if (graph.foldingEnabled && image != null)
  601. {
  602. if (state.control == null)
  603. {
  604. var b = new mxRectangle(0, 0, image.width, image.height);
  605. state.control = new mxImageShape(b, image.src);
  606. state.control.preserveImageAspect = false;
  607. state.control.dialect = graph.dialect;
  608. this.initControl(state, state.control, true, this.createControlClickHandler(state));
  609. }
  610. }
  611. else if (state.control != null)
  612. {
  613. state.control.destroy();
  614. state.control = null;
  615. }
  616. };
  617. /**
  618. * Function: createControlClickHandler
  619. *
  620. * Hook for creating the click handler for the folding icon.
  621. *
  622. * Parameters:
  623. *
  624. * state - <mxCellState> whose control click handler should be returned.
  625. */
  626. mxCellRenderer.prototype.createControlClickHandler = function(state)
  627. {
  628. var graph = state.view.graph;
  629. return mxUtils.bind(this, function (evt)
  630. {
  631. if (this.forceControlClickHandler || graph.isEnabled())
  632. {
  633. var collapse = !graph.isCellCollapsed(state.cell);
  634. graph.foldCells(collapse, false, [state.cell], null, evt);
  635. mxEvent.consume(evt);
  636. }
  637. });
  638. };
  639. /**
  640. * Function: initControl
  641. *
  642. * Initializes the given control and returns the corresponding DOM node.
  643. *
  644. * Parameters:
  645. *
  646. * state - <mxCellState> for which the control should be initialized.
  647. * control - <mxShape> to be initialized.
  648. * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
  649. * clickHandler - Optional function to implement clicks on the control.
  650. */
  651. mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
  652. {
  653. var graph = state.view.graph;
  654. // In the special case where the label is in HTML and the display is SVG the image
  655. // should go into the graph container directly in order to be clickable. Otherwise
  656. // it is obscured by the HTML label that overlaps the cell.
  657. var isForceHtml = graph.isHtmlLabel(state.cell) && mxClient.NO_FO &&
  658. graph.dialect == mxConstants.DIALECT_SVG;
  659. if (isForceHtml)
  660. {
  661. control.dialect = mxConstants.DIALECT_PREFERHTML;
  662. control.init(graph.container);
  663. control.node.style.zIndex = 1;
  664. }
  665. else
  666. {
  667. control.init(state.view.getOverlayPane());
  668. }
  669. var node = control.innerNode || control.node;
  670. // Workaround for missing click event on iOS is to check tolerance below
  671. if (clickHandler != null && !mxClient.IS_IOS)
  672. {
  673. if (graph.isEnabled())
  674. {
  675. node.style.cursor = 'pointer';
  676. }
  677. mxEvent.addListener(node, 'click', clickHandler);
  678. }
  679. if (handleEvents)
  680. {
  681. var first = null;
  682. mxEvent.addGestureListeners(node,
  683. function (evt)
  684. {
  685. first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  686. graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
  687. mxEvent.consume(evt);
  688. },
  689. function (evt)
  690. {
  691. graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
  692. },
  693. function (evt)
  694. {
  695. graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, state));
  696. mxEvent.consume(evt);
  697. });
  698. // Uses capture phase for event interception to stop bubble phase
  699. if (clickHandler != null && mxClient.IS_IOS)
  700. {
  701. node.addEventListener('touchend', function(evt)
  702. {
  703. if (first != null)
  704. {
  705. var tol = graph.tolerance;
  706. if (Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
  707. Math.abs(first.y - mxEvent.getClientY(evt)) < tol)
  708. {
  709. clickHandler.call(clickHandler, evt);
  710. mxEvent.consume(evt);
  711. }
  712. }
  713. }, true);
  714. }
  715. }
  716. return node;
  717. };
  718. /**
  719. * Function: isShapeEvent
  720. *
  721. * Returns true if the event is for the shape of the given state. This
  722. * implementation always returns true.
  723. *
  724. * Parameters:
  725. *
  726. * state - <mxCellState> whose shape fired the event.
  727. * evt - Mouse event which was fired.
  728. */
  729. mxCellRenderer.prototype.isShapeEvent = function(state, evt)
  730. {
  731. return true;
  732. };
  733. /**
  734. * Function: isLabelEvent
  735. *
  736. * Returns true if the event is for the label of the given state. This
  737. * implementation always returns true.
  738. *
  739. * Parameters:
  740. *
  741. * state - <mxCellState> whose label fired the event.
  742. * evt - Mouse event which was fired.
  743. */
  744. mxCellRenderer.prototype.isLabelEvent = function(state, evt)
  745. {
  746. return true;
  747. };
  748. /**
  749. * Function: installListeners
  750. *
  751. * Installs the event listeners for the given cell state.
  752. *
  753. * Parameters:
  754. *
  755. * state - <mxCellState> for which the event listeners should be isntalled.
  756. */
  757. mxCellRenderer.prototype.installListeners = function(state)
  758. {
  759. var graph = state.view.graph;
  760. // Workaround for touch devices routing all events for a mouse
  761. // gesture (down, move, up) via the initial DOM node. Same for
  762. // HTML images in all IE versions (VML images are working).
  763. var getState = function(evt)
  764. {
  765. var result = state;
  766. if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
  767. {
  768. var x = mxEvent.getClientX(evt);
  769. var y = mxEvent.getClientY(evt);
  770. // Dispatches the drop event to the graph which
  771. // consumes and executes the source function
  772. var pt = mxUtils.convertPoint(graph.container, x, y);
  773. result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
  774. }
  775. return result;
  776. };
  777. mxEvent.addGestureListeners(state.shape.node,
  778. mxUtils.bind(this, function(evt)
  779. {
  780. if (this.isShapeEvent(state, evt))
  781. {
  782. graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
  783. }
  784. }),
  785. mxUtils.bind(this, function(evt)
  786. {
  787. if (this.isShapeEvent(state, evt))
  788. {
  789. graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
  790. }
  791. }),
  792. mxUtils.bind(this, function(evt)
  793. {
  794. if (this.isShapeEvent(state, evt))
  795. {
  796. graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
  797. }
  798. })
  799. );
  800. // Uses double click timeout in mxGraph for quirks mode
  801. if (graph.nativeDblClickEnabled)
  802. {
  803. mxEvent.addListener(state.shape.node, 'dblclick',
  804. mxUtils.bind(this, function(evt)
  805. {
  806. if (this.isShapeEvent(state, evt))
  807. {
  808. graph.dblClick(evt, state.cell);
  809. mxEvent.consume(evt);
  810. }
  811. })
  812. );
  813. }
  814. };
  815. /**
  816. * Function: redrawLabel
  817. *
  818. * Redraws the label for the given cell state.
  819. *
  820. * Parameters:
  821. *
  822. * state - <mxCellState> whose label should be redrawn.
  823. */
  824. mxCellRenderer.prototype.redrawLabel = function(state, forced)
  825. {
  826. var graph = state.view.graph;
  827. var value = this.getLabelValue(state);
  828. var wrapping = graph.isWrapping(state.cell);
  829. var clipping = graph.isLabelClipped(state.cell);
  830. var isForceHtml = (state.view.graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
  831. var dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
  832. var overflow = state.style[mxConstants.STYLE_OVERFLOW] || 'visible';
  833. if (state.text != null && (state.text.wrap != wrapping || state.text.clipped != clipping ||
  834. state.text.overflow != overflow || state.text.dialect != dialect))
  835. {
  836. state.text.destroy();
  837. state.text = null;
  838. }
  839. if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
  840. {
  841. this.createLabel(state, value);
  842. }
  843. else if (state.text != null && (value == null || value.length == 0))
  844. {
  845. state.text.destroy();
  846. state.text = null;
  847. }
  848. if (state.text != null)
  849. {
  850. // Forced is true if the style has changed, so to get the updated
  851. // result in getLabelBounds we apply the new style to the shape
  852. if (forced)
  853. {
  854. // Checks if a full repaint is needed
  855. if (state.text.lastValue != null && this.isTextShapeInvalid(state, state.text))
  856. {
  857. // Forces a full repaint
  858. state.text.lastValue = null;
  859. }
  860. state.text.resetStyles();
  861. state.text.apply(state);
  862. // Special case where value is obtained via hook in graph
  863. state.text.valign = graph.getVerticalAlign(state);
  864. }
  865. var bounds = this.getLabelBounds(state);
  866. var nextScale = this.getTextScale(state);
  867. this.resolveColor(state, 'color', mxConstants.STYLE_FONTCOLOR);
  868. if (forced || state.text.value != value || state.text.isWrapping != wrapping ||
  869. state.text.overflow != overflow || state.text.isClipping != clipping ||
  870. state.text.scale != nextScale || state.text.dialect != dialect ||
  871. state.text.bounds == null || !state.text.bounds.equals(bounds))
  872. {
  873. state.text.dialect = dialect;
  874. state.text.value = value;
  875. state.text.bounds = bounds;
  876. state.text.scale = nextScale;
  877. state.text.wrap = wrapping;
  878. state.text.clipped = clipping;
  879. state.text.overflow = overflow;
  880. // Preserves visible state
  881. var vis = state.text.node.style.visibility;
  882. this.redrawLabelShape(state.text);
  883. state.text.node.style.visibility = vis;
  884. }
  885. }
  886. };
  887. /**
  888. * Function: isTextShapeInvalid
  889. *
  890. * Returns true if the style for the text shape has changed.
  891. *
  892. * Parameters:
  893. *
  894. * state - <mxCellState> whose label should be checked.
  895. * shape - <mxText> shape to be checked.
  896. */
  897. mxCellRenderer.prototype.isTextShapeInvalid = function(state, shape)
  898. {
  899. function check(property, stylename, defaultValue)
  900. {
  901. var result = false;
  902. // Workaround for spacing added to directional spacing
  903. if (stylename == 'spacingTop' || stylename == 'spacingRight' ||
  904. stylename == 'spacingBottom' || stylename == 'spacingLeft')
  905. {
  906. result = parseFloat(shape[property]) - parseFloat(shape.spacing) !=
  907. (state.style[stylename] || defaultValue);
  908. }
  909. else
  910. {
  911. result = shape[property] != (state.style[stylename] || defaultValue);
  912. }
  913. return result;
  914. };
  915. return check('fontStyle', mxConstants.STYLE_FONTSTYLE, mxConstants.DEFAULT_FONTSTYLE) ||
  916. check('family', mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY) ||
  917. check('size', mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) ||
  918. check('color', mxConstants.STYLE_FONTCOLOR, 'black') ||
  919. check('align', mxConstants.STYLE_ALIGN, '') ||
  920. check('valign', mxConstants.STYLE_VERTICAL_ALIGN, '') ||
  921. check('spacing', mxConstants.STYLE_SPACING, 2) ||
  922. check('spacingTop', mxConstants.STYLE_SPACING_TOP, 0) ||
  923. check('spacingRight', mxConstants.STYLE_SPACING_RIGHT, 0) ||
  924. check('spacingBottom', mxConstants.STYLE_SPACING_BOTTOM, 0) ||
  925. check('spacingLeft', mxConstants.STYLE_SPACING_LEFT, 0) ||
  926. check('horizontal', mxConstants.STYLE_HORIZONTAL, true) ||
  927. check('background', mxConstants.STYLE_LABEL_BACKGROUNDCOLOR) ||
  928. check('border', mxConstants.STYLE_LABEL_BORDERCOLOR) ||
  929. check('opacity', mxConstants.STYLE_TEXT_OPACITY, 100) ||
  930. check('textDirection', mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
  931. };
  932. /**
  933. * Function: redrawLabelShape
  934. *
  935. * Called to invoked redraw on the given text shape.
  936. *
  937. * Parameters:
  938. *
  939. * shape - <mxText> shape to be redrawn.
  940. */
  941. mxCellRenderer.prototype.redrawLabelShape = function(shape)
  942. {
  943. shape.redraw();
  944. };
  945. /**
  946. * Function: getTextScale
  947. *
  948. * Returns the scaling used for the label of the given state
  949. *
  950. * Parameters:
  951. *
  952. * state - <mxCellState> whose label scale should be returned.
  953. */
  954. mxCellRenderer.prototype.getTextScale = function(state)
  955. {
  956. return state.view.scale;
  957. };
  958. /**
  959. * Function: getLabelBounds
  960. *
  961. * Returns the bounds to be used to draw the label of the given state.
  962. *
  963. * Parameters:
  964. *
  965. * state - <mxCellState> whose label bounds should be returned.
  966. */
  967. mxCellRenderer.prototype.getLabelBounds = function(state)
  968. {
  969. var graph = state.view.graph;
  970. var scale = state.view.scale;
  971. var isEdge = graph.getModel().isEdge(state.cell);
  972. var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
  973. if (isEdge)
  974. {
  975. var spacing = state.text.getSpacing();
  976. bounds.x += spacing.x * scale;
  977. bounds.y += spacing.y * scale;
  978. var geo = graph.getCellGeometry(state.cell);
  979. if (geo != null)
  980. {
  981. bounds.width = Math.max(0, geo.width * scale);
  982. bounds.height = Math.max(0, geo.height * scale);
  983. }
  984. }
  985. else
  986. {
  987. // Inverts label position
  988. if (state.text.isPaintBoundsInverted())
  989. {
  990. var tmp = bounds.x;
  991. bounds.x = bounds.y;
  992. bounds.y = tmp;
  993. }
  994. bounds.x += state.x;
  995. bounds.y += state.y;
  996. // Minimum of 1 fixes alignment bug in HTML labels
  997. bounds.width = Math.max(1, state.width);
  998. bounds.height = Math.max(1, state.height);
  999. }
  1000. if (state.text.isPaintBoundsInverted())
  1001. {
  1002. // Rotates around center of state
  1003. var t = (state.width - state.height) / 2;
  1004. bounds.x += t;
  1005. bounds.y -= t;
  1006. var tmp = bounds.width;
  1007. bounds.width = bounds.height;
  1008. bounds.height = tmp;
  1009. }
  1010. // Shape can modify its label bounds
  1011. if (state.shape != null)
  1012. {
  1013. var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
  1014. var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
  1015. if (hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE)
  1016. {
  1017. bounds = state.shape.getLabelBounds(bounds);
  1018. }
  1019. }
  1020. // Label width style overrides actual label width
  1021. var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
  1022. if (lw != null)
  1023. {
  1024. bounds.width = parseFloat(lw) * scale;
  1025. }
  1026. if (!isEdge)
  1027. {
  1028. this.rotateLabelBounds(state, bounds);
  1029. }
  1030. return bounds;
  1031. };
  1032. /**
  1033. * Function: rotateLabelBounds
  1034. *
  1035. * Adds the shape rotation to the given label bounds and
  1036. * applies the alignment and offsets.
  1037. *
  1038. * Parameters:
  1039. *
  1040. * state - <mxCellState> whose label bounds should be rotated.
  1041. * bounds - <mxRectangle> the rectangle to be rotated.
  1042. */
  1043. mxCellRenderer.prototype.rotateLabelBounds = function(state, bounds)
  1044. {
  1045. bounds.y -= state.text.margin.y * bounds.height;
  1046. bounds.x -= state.text.margin.x * bounds.width;
  1047. if (!this.legacySpacing || (state.style[mxConstants.STYLE_OVERFLOW] != 'fill' && state.style[mxConstants.STYLE_OVERFLOW] != 'width'))
  1048. {
  1049. var s = state.view.scale;
  1050. var spacing = state.text.getSpacing();
  1051. bounds.x += spacing.x * s;
  1052. bounds.y += spacing.y * s;
  1053. var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
  1054. var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
  1055. var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
  1056. bounds.width = Math.max(0, bounds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (state.text.spacingLeft * s + state.text.spacingRight * s) : 0));
  1057. bounds.height = Math.max(0, bounds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (state.text.spacingTop * s + state.text.spacingBottom * s) : 0));
  1058. }
  1059. var theta = state.text.getTextRotation();
  1060. // Only needed if rotated around another center
  1061. if (theta != 0 && state != null && state.view.graph.model.isVertex(state.cell))
  1062. {
  1063. var cx = state.getCenterX();
  1064. var cy = state.getCenterY();
  1065. if (bounds.x != cx || bounds.y != cy)
  1066. {
  1067. var rad = theta * (Math.PI / 180);
  1068. var pt = mxUtils.getRotatedPoint(new mxPoint(bounds.x, bounds.y),
  1069. Math.cos(rad), Math.sin(rad), new mxPoint(cx, cy));
  1070. bounds.x = pt.x;
  1071. bounds.y = pt.y;
  1072. }
  1073. }
  1074. };
  1075. /**
  1076. * Function: redrawCellOverlays
  1077. *
  1078. * Redraws the overlays for the given cell state.
  1079. *
  1080. * Parameters:
  1081. *
  1082. * state - <mxCellState> whose overlays should be redrawn.
  1083. */
  1084. mxCellRenderer.prototype.redrawCellOverlays = function(state, forced)
  1085. {
  1086. this.createCellOverlays(state);
  1087. if (state.overlays != null)
  1088. {
  1089. var rot = mxUtils.mod(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0), 90);
  1090. var rad = mxUtils.toRadians(rot);
  1091. var cos = Math.cos(rad);
  1092. var sin = Math.sin(rad);
  1093. state.overlays.visit(function(id, shape)
  1094. {
  1095. var bounds = shape.overlay.getBounds(state);
  1096. if (!state.view.graph.getModel().isEdge(state.cell))
  1097. {
  1098. if (state.shape != null && rot != 0)
  1099. {
  1100. var cx = bounds.getCenterX();
  1101. var cy = bounds.getCenterY();
  1102. var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
  1103. new mxPoint(state.getCenterX(), state.getCenterY()));
  1104. cx = point.x;
  1105. cy = point.y;
  1106. bounds.x = Math.round(cx - bounds.width / 2);
  1107. bounds.y = Math.round(cy - bounds.height / 2);
  1108. }
  1109. }
  1110. if (forced || shape.bounds == null || shape.scale != state.view.scale ||
  1111. !shape.bounds.equals(bounds))
  1112. {
  1113. shape.bounds = bounds;
  1114. shape.scale = state.view.scale;
  1115. shape.redraw();
  1116. }
  1117. });
  1118. }
  1119. };
  1120. /**
  1121. * Function: redrawControl
  1122. *
  1123. * Redraws the control for the given cell state.
  1124. *
  1125. * Parameters:
  1126. *
  1127. * state - <mxCellState> whose control should be redrawn.
  1128. */
  1129. mxCellRenderer.prototype.redrawControl = function(state, forced)
  1130. {
  1131. var image = state.view.graph.getFoldingImage(state);
  1132. if (state.control != null && image != null)
  1133. {
  1134. var bounds = this.getControlBounds(state, image.width, image.height);
  1135. var r = (this.legacyControlPosition) ?
  1136. mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) :
  1137. state.shape.getTextRotation();
  1138. var s = state.view.scale;
  1139. if (forced || state.control.scale != s || !state.control.bounds.equals(bounds) ||
  1140. state.control.rotation != r)
  1141. {
  1142. state.control.rotation = r;
  1143. state.control.bounds = bounds;
  1144. state.control.scale = s;
  1145. state.control.redraw();
  1146. }
  1147. }
  1148. };
  1149. /**
  1150. * Function: getControlBounds
  1151. *
  1152. * Returns the bounds to be used to draw the control (folding icon) of the
  1153. * given state.
  1154. */
  1155. mxCellRenderer.prototype.getControlBounds = function(state, w, h)
  1156. {
  1157. if (state.control != null)
  1158. {
  1159. var s = state.view.scale;
  1160. var cx = state.getCenterX();
  1161. var cy = state.getCenterY();
  1162. if (!state.view.graph.getModel().isEdge(state.cell))
  1163. {
  1164. cx = state.x + w * s;
  1165. cy = state.y + h * s;
  1166. if (state.shape != null)
  1167. {
  1168. // TODO: Factor out common code
  1169. var rot = state.shape.getShapeRotation();
  1170. if (this.legacyControlPosition)
  1171. {
  1172. rot = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
  1173. }
  1174. else
  1175. {
  1176. if (state.shape.isPaintBoundsInverted())
  1177. {
  1178. var t = (state.width - state.height) / 2;
  1179. cx += t;
  1180. cy -= t;
  1181. }
  1182. }
  1183. if (rot != 0)
  1184. {
  1185. var rad = mxUtils.toRadians(rot);
  1186. var cos = Math.cos(rad);
  1187. var sin = Math.sin(rad);
  1188. var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
  1189. new mxPoint(state.getCenterX(), state.getCenterY()));
  1190. cx = point.x;
  1191. cy = point.y;
  1192. }
  1193. }
  1194. }
  1195. return (state.view.graph.getModel().isEdge(state.cell)) ?
  1196. new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s))
  1197. : new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s));
  1198. }
  1199. return null;
  1200. };
  1201. /**
  1202. * Function: insertStateAfter
  1203. *
  1204. * Inserts the given array of <mxShapes> after the given nodes in the DOM.
  1205. *
  1206. * Parameters:
  1207. *
  1208. * shapes - Array of <mxShapes> to be inserted.
  1209. * node - Node in <drawPane> after which the shapes should be inserted.
  1210. * htmlNode - Node in the graph container after which the shapes should be inserted that
  1211. * will not go into the <drawPane> (eg. HTML labels without foreignObjects).
  1212. */
  1213. mxCellRenderer.prototype.insertStateAfter = function(state, node, htmlNode)
  1214. {
  1215. var shapes = this.getShapesForState(state);
  1216. for (var i = 0; i < shapes.length; i++)
  1217. {
  1218. if (shapes[i] != null && shapes[i].node != null)
  1219. {
  1220. var html = shapes[i].node.parentNode != state.view.getDrawPane() &&
  1221. shapes[i].node.parentNode != state.view.getOverlayPane();
  1222. var temp = (html) ? htmlNode : node;
  1223. if (temp != null && temp.nextSibling != shapes[i].node)
  1224. {
  1225. if (temp.nextSibling == null)
  1226. {
  1227. temp.parentNode.appendChild(shapes[i].node);
  1228. }
  1229. else
  1230. {
  1231. temp.parentNode.insertBefore(shapes[i].node, temp.nextSibling);
  1232. }
  1233. }
  1234. else if (temp == null)
  1235. {
  1236. // Special case: First HTML node should be first sibling after canvas
  1237. if (shapes[i].node.parentNode == state.view.graph.container)
  1238. {
  1239. var canvas = state.view.canvas;
  1240. while (canvas != null && canvas.parentNode != state.view.graph.container)
  1241. {
  1242. canvas = canvas.parentNode;
  1243. }
  1244. if (canvas != null && canvas.nextSibling != null)
  1245. {
  1246. if (canvas.nextSibling != shapes[i].node)
  1247. {
  1248. shapes[i].node.parentNode.insertBefore(shapes[i].node, canvas.nextSibling);
  1249. }
  1250. }
  1251. else
  1252. {
  1253. shapes[i].node.parentNode.appendChild(shapes[i].node);
  1254. }
  1255. }
  1256. else if (shapes[i].node.parentNode != null &&
  1257. shapes[i].node.parentNode.firstChild != null &&
  1258. shapes[i].node.parentNode.firstChild != shapes[i].node)
  1259. {
  1260. // Inserts the node as the first child of the parent to implement the order
  1261. shapes[i].node.parentNode.insertBefore(shapes[i].node, shapes[i].node.parentNode.firstChild);
  1262. }
  1263. }
  1264. if (html)
  1265. {
  1266. htmlNode = shapes[i].node;
  1267. }
  1268. else
  1269. {
  1270. node = shapes[i].node;
  1271. }
  1272. }
  1273. }
  1274. return [node, htmlNode];
  1275. };
  1276. /**
  1277. * Function: getShapesForState
  1278. *
  1279. * Returns the <mxShapes> for the given cell state in the order in which they should
  1280. * appear in the DOM.
  1281. *
  1282. * Parameters:
  1283. *
  1284. * state - <mxCellState> whose shapes should be returned.
  1285. */
  1286. mxCellRenderer.prototype.getShapesForState = function(state)
  1287. {
  1288. return [state.shape, state.text, state.control];
  1289. };
  1290. /**
  1291. * Function: redraw
  1292. *
  1293. * Updates the bounds or points and scale of the shapes for the given cell
  1294. * state. This is called in mxGraphView.validatePoints as the last step of
  1295. * updating all cells.
  1296. *
  1297. * Parameters:
  1298. *
  1299. * state - <mxCellState> for which the shapes should be updated.
  1300. * force - Optional boolean that specifies if the cell should be reconfiured
  1301. * and redrawn without any additional checks.
  1302. * rendering - Optional boolean that specifies if the cell should actually
  1303. * be drawn into the DOM. If this is false then redraw and/or reconfigure
  1304. * will not be called on the shape.
  1305. */
  1306. mxCellRenderer.prototype.redraw = function(state, force, rendering)
  1307. {
  1308. var shapeChanged = this.redrawShape(state, force, rendering);
  1309. if (state.shape != null && (rendering == null || rendering))
  1310. {
  1311. this.redrawLabel(state, shapeChanged);
  1312. this.redrawCellOverlays(state, shapeChanged);
  1313. this.redrawControl(state, shapeChanged);
  1314. }
  1315. };
  1316. /**
  1317. * Function: redrawShape
  1318. *
  1319. * Redraws the shape for the given cell state.
  1320. *
  1321. * Parameters:
  1322. *
  1323. * state - <mxCellState> whose label should be redrawn.
  1324. */
  1325. mxCellRenderer.prototype.redrawShape = function(state, force, rendering)
  1326. {
  1327. var model = state.view.graph.model;
  1328. var shapeChanged = false;
  1329. // Forces creation of new shape if shape style has changed
  1330. if (state.shape != null && state.shape.style != null && state.style != null &&
  1331. state.shape.style[mxConstants.STYLE_SHAPE] != state.style[mxConstants.STYLE_SHAPE])
  1332. {
  1333. state.shape.destroy();
  1334. state.shape = null;
  1335. }
  1336. if (state.shape == null && state.view.graph.container != null &&
  1337. state.cell != state.view.currentRoot &&
  1338. (model.isVertex(state.cell) || model.isEdge(state.cell)))
  1339. {
  1340. state.shape = this.createShape(state);
  1341. if (state.shape != null)
  1342. {
  1343. state.shape.minSvgStrokeWidth = this.minSvgStrokeWidth;
  1344. state.shape.antiAlias = this.antiAlias;
  1345. this.createIndicatorShape(state);
  1346. this.initializeShape(state);
  1347. this.createCellOverlays(state);
  1348. this.installListeners(state);
  1349. // Forces a refresh of the handler if one exists
  1350. state.view.graph.selectionCellsHandler.updateHandler(state);
  1351. }
  1352. }
  1353. else if (!force && state.shape != null && (!mxUtils.equalEntries(state.shape.style,
  1354. state.style) || this.checkPlaceholderStyles(state)))
  1355. {
  1356. state.shape.resetStyles();
  1357. this.configureShape(state);
  1358. // LATER: Ignore update for realtime to fix reset of current gesture
  1359. state.view.graph.selectionCellsHandler.updateHandler(state);
  1360. force = true;
  1361. }
  1362. // Updates indicator shape
  1363. if (state.shape != null && state.shape.indicatorShape !=
  1364. this.getShape(state.view.graph.getIndicatorShape(state)))
  1365. {
  1366. if (state.shape.indicator != null)
  1367. {
  1368. state.shape.indicator.destroy();
  1369. state.shape.indicator = null;
  1370. }
  1371. this.createIndicatorShape(state);
  1372. if (state.shape.indicatorShape != null)
  1373. {
  1374. state.shape.indicator = new state.shape.indicatorShape();
  1375. state.shape.indicator.dialect = state.shape.dialect;
  1376. state.shape.indicator.init(state.node);
  1377. force = true;
  1378. }
  1379. }
  1380. if (state.shape != null)
  1381. {
  1382. // Handles changes of the collapse icon
  1383. this.createControl(state);
  1384. // Redraws the cell if required, ignores changes to bounds if points are
  1385. // defined as the bounds are updated for the given points inside the shape
  1386. if (force || this.isShapeInvalid(state, state.shape))
  1387. {
  1388. if (state.absolutePoints != null)
  1389. {
  1390. state.shape.points = state.absolutePoints.slice();
  1391. state.shape.bounds = null;
  1392. }
  1393. else
  1394. {
  1395. state.shape.points = null;
  1396. state.shape.bounds = new mxRectangle(state.x, state.y, state.width, state.height);
  1397. }
  1398. state.shape.scale = state.view.scale;
  1399. if (rendering == null || rendering)
  1400. {
  1401. this.doRedrawShape(state);
  1402. }
  1403. else
  1404. {
  1405. state.shape.updateBoundingBox();
  1406. }
  1407. shapeChanged = true;
  1408. }
  1409. }
  1410. return shapeChanged;
  1411. };
  1412. /**
  1413. * Function: doRedrawShape
  1414. *
  1415. * Invokes redraw on the shape of the given state.
  1416. */
  1417. mxCellRenderer.prototype.doRedrawShape = function(state)
  1418. {
  1419. state.shape.redraw();
  1420. };
  1421. /**
  1422. * Function: isShapeInvalid
  1423. *
  1424. * Returns true if the given shape must be repainted.
  1425. */
  1426. mxCellRenderer.prototype.isShapeInvalid = function(state, shape)
  1427. {
  1428. return shape.bounds == null || shape.scale != state.view.scale ||
  1429. (state.absolutePoints == null && !shape.bounds.equals(state)) ||
  1430. (state.absolutePoints != null && !mxUtils.equalPoints(shape.points, state.absolutePoints))
  1431. };
  1432. /**
  1433. * Function: destroy
  1434. *
  1435. * Destroys the shapes associated with the given cell state.
  1436. *
  1437. * Parameters:
  1438. *
  1439. * state - <mxCellState> for which the shapes should be destroyed.
  1440. */
  1441. mxCellRenderer.prototype.destroy = function(state)
  1442. {
  1443. if (state.shape != null)
  1444. {
  1445. if (state.text != null)
  1446. {
  1447. state.text.destroy();
  1448. state.text = null;
  1449. }
  1450. if (state.overlays != null)
  1451. {
  1452. state.overlays.visit(function(id, shape)
  1453. {
  1454. shape.destroy();
  1455. });
  1456. state.overlays = null;
  1457. }
  1458. if (state.control != null)
  1459. {
  1460. state.control.destroy();
  1461. state.control = null;
  1462. }
  1463. state.shape.destroy();
  1464. state.shape = null;
  1465. }
  1466. };
  1467. __mxOutput.mxCellRenderer = typeof mxCellRenderer !== 'undefined' ? mxCellRenderer : undefined;