Source: chemdoc/kekule.attachedMarkers.js

  1. /**
  2. * @fileoverview
  3. * Implementation of property markers (e.g. charge mark, lone pair, etc.) binding to the chem object.
  4. * @author Partridge Jiang
  5. */
  6. /*
  7. * requires /lan/classes.js
  8. * requires /core/kekule.common.js
  9. * requires /core/kekule.structures.js
  10. * requires /core/kekule.chemUtils.js
  11. */
  12. (function(){
  13. "use strict";
  14. var AU = Kekule.ArrayUtils;
  15. /**
  16. * A recommended namespace for all attach marker classes
  17. * @namespace
  18. */
  19. Kekule.ChemMarker = {};
  20. // extend chemObject, enable associate descriptive glyphs to it
  21. /** @ignore */
  22. ClassEx.extend(Kekule.ChemObject,
  23. /** @lends Kekule.ChemObject# */
  24. {
  25. /**
  26. * Returns whether current object is an attachedMarker of parent object.
  27. * @returns {Bool}
  28. */
  29. isAttachedMarker: function()
  30. {
  31. var p = this.getParent();
  32. return (p && p.hasMarker(this));
  33. },
  34. /**
  35. * Notify {@link Kekule.ChemObject#attachedMarkers} property has been changed
  36. * @private
  37. */
  38. notifyAttachedMarkersChanged: function()
  39. {
  40. this.notifyPropSet('attachedMarkers', this.getPropStoreFieldValue('attachedMarkers'));
  41. },
  42. /** @private */
  43. _attachedMarkerAdded: function(marker)
  44. {
  45. // do nothing here
  46. },
  47. /** @private */
  48. _attachedMarkerRemoved: function(marker)
  49. {
  50. // do nothing here
  51. },
  52. /**
  53. * Return count of attached markers.
  54. * @returns {Int}
  55. */
  56. getMarkerCount: function()
  57. {
  58. var markers = this.getPropStoreFieldValue('attachedMarkers');
  59. return markers? markers.length: 0;
  60. },
  61. /**
  62. * Get attached marker at index.
  63. * @param {Int} index
  64. * @returns {Kekule.ChemObject}
  65. */
  66. getMarkerAt: function(index)
  67. {
  68. var markers = this.getPropStoreFieldValue('attachedMarkers');
  69. return markers? markers[index]: null;
  70. },
  71. /**
  72. * Returns the first child marker of a specified class type.
  73. * @param {Class} classType
  74. * @param {Bool} exactMatch If true, only marker of classType (not its descendants) should be returned.
  75. * @returns {Kekule.ChemObject}
  76. */
  77. getMarkerOfType: function(classType, exactMatch)
  78. {
  79. for (var i = 0, l = this.getMarkerCount(); i < l; ++i)
  80. {
  81. var marker = this.getMarkerAt(i);
  82. if ((exactMatch && marker.getClass() === classType) || (marker instanceof classType))
  83. return marker;
  84. }
  85. return null;
  86. },
  87. /**
  88. * Returns all child markers of a specified class type.
  89. * @param {Class} classType
  90. * @param {Bool} exactMatch If true, only markers of classType (not its descendants) should be returned.
  91. * @returns {Array}
  92. */
  93. getMarkersOfType: function(classType, exactMatch)
  94. {
  95. var result = [];
  96. for (var i = 0, l = this.getMarkerCount(); i < l; ++i)
  97. {
  98. var marker = this.getMarkerAt(i);
  99. if ((exactMatch && marker.getClass() === classType) || (marker instanceof classType))
  100. result.push(marker);
  101. }
  102. return result;
  103. },
  104. /**
  105. * Get attached marker of classType. If no such a marker currently and canCreate is true, a new marker will be created.
  106. * @param {Class} classType
  107. * @param {Bool} canCreate
  108. * @param {Bool} exactMatch If true, only marker of classType (not its descendants) should be returned.
  109. * @param {Hash} defProps If create a new marker, those prop values will be applied.
  110. * @returns {Kekule.ChemObject}
  111. */
  112. fetchMarkerOfType: function(classType, canCreate, exactMatch, defProps)
  113. {
  114. var result = this.getMarkerOfType(classType, exactMatch);
  115. if (!result && canCreate)
  116. {
  117. result = new classType();
  118. result.beginUpdate();
  119. try
  120. {
  121. if (defProps)
  122. {
  123. result.setPropValues(defProps);
  124. }
  125. //console.log('fetch create on', this.getId());
  126. this.appendMarker(result);
  127. }
  128. finally
  129. {
  130. result.endUpdate();
  131. }
  132. }
  133. return result;
  134. },
  135. /**
  136. * Returns markers that has no coord on coordMode
  137. * @param {Int} coordMode
  138. * @return {Array}
  139. */
  140. getUnplacedMarkers: function(coordMode)
  141. {
  142. var result = [];
  143. for (var i = 0, l = this.getMarkerCount(); i < l; ++i)
  144. {
  145. var marker = this.getMarkerAt(i);
  146. if (!marker.getCoordOfMode(coordMode))
  147. result.push(marker);
  148. }
  149. return result;
  150. },
  151. /**
  152. * Get index of attached marker in marker array.
  153. * @param {Kekule.ChemObject} marker
  154. * @returns {Int}
  155. */
  156. indexOfMarker: function(marker)
  157. {
  158. var markers = this.getPropStoreFieldValue('attachedMarkers');
  159. return markers? markers.indexOf(marker): -1;
  160. },
  161. /**
  162. * Check if a marker has been attached to this object.
  163. * @param {Kekule.ChemObject} marker
  164. * @returns {Bool}
  165. */
  166. hasMarker: function(marker)
  167. {
  168. return this.indexOfMarker(marker) >= 0;
  169. },
  170. /**
  171. * Returns whether there exists child markers of a specified class type.
  172. * @param {Class} classType
  173. * @param {Bool} exactMatch If true, only markers of classType (not its descendants) should be considered.
  174. * @returns {Bool}
  175. */
  176. hasMarkerOfType: function(classType, exactMatch)
  177. {
  178. return !!this.getMarkerOfType(classType, exactMatch);
  179. },
  180. /**
  181. * Attach a marker to this object. If marker already exists, nothing will be done.
  182. * @param {Kekule.ChemObject} marker
  183. */
  184. appendMarker: function(marker)
  185. {
  186. var index = this.indexOfMarker(marker);
  187. if (index >= 0) // already exists
  188. return index;// do nothing
  189. else
  190. {
  191. var result = this.getAttachedMarkers(true).push(marker);
  192. marker.beginUpdate();
  193. try
  194. {
  195. if (marker.setOwner)
  196. marker.setOwner(this.getOwner());
  197. if (marker.setParent)
  198. marker.setParent(this);
  199. this._attachedMarkerAdded(marker);
  200. }
  201. finally
  202. {
  203. marker.endUpdate();
  204. }
  205. this.notifyAttachedMarkersChanged();
  206. return result;
  207. }
  208. },
  209. /**
  210. * Insert marker before refMarker in marker list. If refMarker is null or does not exists, marker will be append to tail of list.
  211. * @param {Kekule.ChemObject} marker
  212. * @param {Kekule.ChemObject} refMarker
  213. * @return {Int} Index of obj after inserting.
  214. */
  215. insertMarkerBefore: function(marker, refMarker)
  216. {
  217. var refIndex = this.indexOfMarker(refMarker);
  218. return this.insertMarkerAt(marker, refIndex);
  219. },
  220. /**
  221. * Insert marker to index. If index is not set, marker will be inserted to the tail of the marker array.
  222. * @param {Kekule.ChemObject} marker
  223. * @param {Int} index
  224. */
  225. insertMarkerAt: function(marker, index)
  226. {
  227. var i = this.indexOfMarker(marker);
  228. var markers = this.getAttachedMarkers(true);
  229. if (Kekule.ObjUtils.isUnset(index) || (index < 0))
  230. index = markers.length;
  231. if (i >= 0) // already inside, adjust position
  232. {
  233. markers.splice(i, 1);
  234. markers.splice(index, 0, marker);
  235. }
  236. else // new one
  237. {
  238. markers.splice(index, 0, marker);
  239. if (marker.setOwner)
  240. marker.setOwner(this.getOwner());
  241. if (marker.setParent)
  242. marker.setParent(this);
  243. this._attachedMarkerAdded(marker);
  244. }
  245. this.notifyAttachedMarkersChanged();
  246. return index;
  247. },
  248. /**
  249. * Change index of marker.
  250. * @param {Kekule.ChemObject} marker
  251. * @param {Int} index
  252. */
  253. setMarkerIndex: function(marker, index)
  254. {
  255. var i = this.indexOfMarker(marker);
  256. if (i >= 0) // already inside, adjust position
  257. {
  258. var markers = this.getPropStoreFieldValue('attachedMarkers'); // this.getAttachedMarkers();
  259. markers.splice(i, 1);
  260. markers.splice(index, 0, marker);
  261. }
  262. },
  263. /**
  264. * Remove marker at index in attached marker list.
  265. * @param {Int} index
  266. */
  267. removeMarkerAt: function(index)
  268. {
  269. var marker = this.getMarkerAt(index);
  270. if (marker)
  271. {
  272. var result = this.getAttachedMarkers(true).splice(index, 1);
  273. if (marker.setOwner)
  274. marker.setOwner(null);
  275. if (marker.setParent)
  276. marker.setParent(null);
  277. this._attachedMarkerRemoved(marker);
  278. this.notifyAttachedMarkersChanged();
  279. return result;
  280. }
  281. },
  282. /**
  283. * Remove an attached marker.
  284. * @param {Kekule.ChemObject} marker
  285. */
  286. removeMarker: function(marker)
  287. {
  288. var index = this.indexOfMarker(marker);
  289. if (index >= 0)
  290. return this.removeMarkerAt(index);
  291. },
  292. /**
  293. * Replace oldMarker with new one.
  294. * @param {Kekule.ChemObject} oldMarker
  295. * @param {Kekule.ChemObject} newMarker
  296. */
  297. replaceMarker: function(oldMarker, newMarker)
  298. {
  299. var oldIndex = this.indexOfMarker(oldMarker);
  300. if (oldIndex < 0) // old marker not exists
  301. {
  302. return this;
  303. }
  304. else
  305. {
  306. this.removeMarkerAt(oldIndex);
  307. this.insertMarkerAt(newMarker, oldIndex);
  308. return this;
  309. }
  310. },
  311. /**
  312. * Remove all attached markers.
  313. */
  314. clearMarkers: function()
  315. {
  316. //var oldMarkers = AU.clone(this.getAttachedMarkers());
  317. var oldMarkers = this.getPropStoreFieldValue('attachedMarkers');
  318. if (oldMarkers)
  319. oldMarkers = AU.clone(oldMarkers);
  320. this.setPropStoreFieldValue('attachedMarkers', null);
  321. if (oldMarkers)
  322. {
  323. for (var i = 0, l = oldMarkers.length; i < l; ++i)
  324. {
  325. var marker = oldMarkers[i];
  326. if (marker.setOwner)
  327. marker.setOwner(null);
  328. if (marker.setParent)
  329. marker.setParent(null);
  330. this._attachedMarkerRemoved(marker);
  331. }
  332. }
  333. this.notifyAttachedMarkersChanged();
  334. return this;
  335. },
  336. autoSetMarker2DPos: function(marker, offset, allowCoordBorrow, avoidDirectionAngles)
  337. {
  338. if (this.hasMarker(marker))
  339. {
  340. var angle = Kekule.ChemStructureUtils.getMostEmptyDirection2DAngleOfObj(this, [marker], allowCoordBorrow, true, false, avoidDirectionAngles);
  341. var coord = {'x': offset * Math.cos(angle), 'y': offset * Math.sin(angle)};
  342. //console.log('angle2', coord);
  343. marker.setCoord2D(coord);
  344. }
  345. },
  346. /** @private */
  347. _updateAttachedMarkersOwner: function(owner)
  348. {
  349. if (!owner)
  350. owner = this.getOwner();
  351. for (var i = 0, l = this.getMarkerCount(); i < l; ++i)
  352. {
  353. var marker = this.getMarkerAt(i);
  354. if (marker.setOwner)
  355. marker.setOwner(owner);
  356. }
  357. },
  358. /** @private */
  359. _updateAttachedMarkersParent: function(parent)
  360. {
  361. if (!parent)
  362. parent = this;
  363. /*
  364. if (this.getMarkerCount() > 0)
  365. console.log('set parent', parent.getClassName());
  366. */
  367. for (var i = 0, l = this.getMarkerCount(); i < l; ++i)
  368. {
  369. var marker = this.getMarkerAt(i);
  370. if (marker.setParent)
  371. marker.setParent(parent);
  372. }
  373. }
  374. });
  375. ClassEx.extendMethod(Kekule.ChemObject, 'ownerChanged', function($origin, newOwner){
  376. $origin(newOwner);
  377. this._updateAttachedMarkersOwner(newOwner);
  378. });
  379. ClassEx.extendMethod(Kekule.ChemObject, 'removeChild', function($origin, child){
  380. //console.log('remove child', child.getClassName(), child.getId());
  381. var result = $origin(child);
  382. if (!result)
  383. result = this.removeMarker(child) || $origin(child);
  384. return result;
  385. });
  386. ClassEx.extendMethod(Kekule.ChemObject, 'insertBefore', function($origin, child, refChild){
  387. var result = $origin(child, refChild);
  388. if (result < 0)
  389. {
  390. if (refChild && this.hasMarker(refChild) || child instanceof Kekule.ChemMarker.BaseMarker)
  391. result = this.insertMarkerBefore(child, refChild);
  392. }
  393. return result;
  394. });
  395. ClassEx.defineProp(Kekule.ChemObject, 'attachedMarkers',
  396. {
  397. 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLISHED,
  398. 'getter': function(autoCreate)
  399. {
  400. var result = this.getPropStoreFieldValue('attachedMarkers');
  401. if (!result && autoCreate)
  402. {
  403. result = [];
  404. this.setPropStoreFieldValue('attachedMarkers', result);
  405. }
  406. return result;
  407. },
  408. 'setter': function(value)
  409. {
  410. //console.log('set markers', value, this.getClassName());
  411. this.clearMarkers();
  412. this.setPropStoreFieldValue('attachedMarkers', value);
  413. this._updateAttachedMarkersOwner();
  414. this._updateAttachedMarkersParent();
  415. //console.log('after set', this.getAttachedMarkers());
  416. }
  417. });
  418. /*
  419. // if true, position of newly added marker will be set automatically
  420. ClassEx.defineProp(Kekule.ChemObject, 'autoSetAttachedMarkerPos',
  421. {
  422. 'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLISHED
  423. });
  424. */
  425. })();