Source: widgets/chem/editor/kekule.chemEditor.operations.js

  1. /**
  2. * @fileoverview
  3. * Operations need to implement an editor.
  4. * @author Partridge Jiang
  5. */
  6. /*
  7. * requires /lan/classes.js
  8. * requires /utils/kekule.utils.js
  9. * requires /widgets/operation/kekule.operations.js
  10. * requires /core/kekule.structures.js
  11. * requires /widgets/chem/editor/kekule.chemEditor.baseEditors.js
  12. * requires /localization/
  13. */
  14. (function(){
  15. "use strict";
  16. /**
  17. * A namespace for operation about normal ChemObject instance.
  18. * @namespace
  19. */
  20. Kekule.ChemObjOperation = {};
  21. /**
  22. * Base operation for ChemObject instance.
  23. * @class
  24. * @augments Kekule.Operation
  25. *
  26. * @param {Kekule.ChemObject} chemObject Target chem object.
  27. *
  28. * @property {Kekule.ChemObject} target Target chem object.
  29. * @property {Bool} allowCoordBorrow Whether allow borrowing between 2D and 3D when manipulating coords.
  30. * @property {Kekule.Editor.BaseEditor} The editor object associated.
  31. */
  32. Kekule.ChemObjOperation.Base = Class.create(Kekule.Operation,
  33. /** @lends Kekule.ChemObjOperation.Base# */
  34. {
  35. /** @private */
  36. CLASS_NAME: 'Kekule.ChemObjOperation.Base',
  37. /** @constructs */
  38. initialize: function($super, chemObj, editor)
  39. {
  40. $super();
  41. this.setTarget(chemObj);
  42. if (editor)
  43. this.setEditor(editor);
  44. },
  45. /** @private */
  46. initProperties: function()
  47. {
  48. this.defineProp('target', {'dataType': 'Kekule.ChemObject', 'serializable': false});
  49. this.defineProp('allowCoordBorrow', {'dataType': DataType.BOOL});
  50. this.defineProp('editor', {'dataType': 'Kekule.Editor.BaseEditor', 'serializable': false});
  51. },
  52. // A series of notification method to target object
  53. /** @private */
  54. notifyBeforeAddingByEditor: function(obj, parent, refSibling)
  55. {
  56. if (obj.beforeAddingByEditor) // if this special notification method exists, call it first
  57. obj.beforeAddingByEditor(parent, refSibling);
  58. },
  59. /** @private */
  60. notifyBeforeRemovingByEditor: function(obj, parent)
  61. {
  62. if (obj.beforeRemovingByEditor) // if this special notification method exists, call it first
  63. obj.beforeRemovingByEditor(parent);
  64. },
  65. /** @private */
  66. notifyBeforeModifyingByEditor: function(obj, propValues)
  67. {
  68. if (obj.beforeModifyingByEditor) // if this special notification method exists, call it first
  69. obj.beforeModifyingByEditor(propValues);
  70. },
  71. /** @private */
  72. notifyAfterAddingByEditor: function(obj, parent, refSibling)
  73. {
  74. if (obj.afterAddingByEditor) // if this special notification method exists, call it first
  75. obj.afterAddingByEditor(parent, refSibling);
  76. },
  77. /** @private */
  78. notifyAfterRemovingByEditor: function(obj, parent)
  79. {
  80. if (obj.afterRemovingByEditor) // if this special notification method exists, call it first
  81. obj.afterRemovingByEditor(parent);
  82. },
  83. /** @private */
  84. notifyAfterModifyingByEditor: function(obj, propValues)
  85. {
  86. if (obj.afterModifyingByEditor) // if this special notification method exists, call it first
  87. obj.afterModifyingByEditor(propValues);
  88. }
  89. });
  90. /**
  91. * Operation of changing a chemObject's properties.
  92. * @class
  93. * @augments Kekule.ChemObjOperation.Base
  94. *
  95. * @param {Kekule.ChemObject} chemObject Target chem object.
  96. * @param {Hash} newPropValues A hash of new prop-value map.
  97. *
  98. * @property {Hash} newPropValues A hash of new prop-value map.
  99. */
  100. Kekule.ChemObjOperation.Modify = Class.create(Kekule.ChemObjOperation.Base,
  101. /** @lends Kekule.ChemObjOperation.Modify# */
  102. {
  103. /** @private */
  104. CLASS_NAME: 'Kekule.ChemObjOperation.Modify',
  105. /** @constructs */
  106. initialize: function($super, chemObj, newPropValues, editor)
  107. {
  108. $super(chemObj, editor);
  109. if (newPropValues)
  110. this.setNewPropValues(newPropValues);
  111. },
  112. /** @private */
  113. initProperties: function()
  114. {
  115. this.defineProp('newPropValues', {'dataType': DataType.HASH});
  116. this.defineProp('oldPropValues', {'dataType': DataType.HASH}); // private
  117. },
  118. /** @private */
  119. doExecute: function()
  120. {
  121. var oldValues = {};
  122. var map = this.getNewPropValues();
  123. var obj = this.getTarget();
  124. obj.beginUpdate();
  125. try
  126. {
  127. this.notifyBeforeModifyingByEditor(obj, map);
  128. for (var prop in map)
  129. {
  130. var value = map[prop];
  131. // store old value first
  132. oldValues[prop] = obj.getPropValue(prop);
  133. // set new value
  134. obj.setPropValue(prop, value);
  135. }
  136. this.notifyAfterModifyingByEditor(obj, map);
  137. }
  138. finally
  139. {
  140. obj.endUpdate();
  141. }
  142. this.setOldPropValues(oldValues);
  143. },
  144. /** @private */
  145. doReverse: function()
  146. {
  147. var map = this.getOldPropValues();
  148. var obj = this.getTarget();
  149. obj.beginUpdate();
  150. try
  151. {
  152. this.notifyBeforeModifyingByEditor(obj, map);
  153. for (var prop in map)
  154. {
  155. var value = map[prop];
  156. // restore old value
  157. obj.setPropValue(prop, value);
  158. }
  159. this.notifyAfterModifyingByEditor(obj, map);
  160. }
  161. finally
  162. {
  163. obj.endUpdate();
  164. }
  165. }
  166. });
  167. /**
  168. * Operation of changing a chemObject's coord.
  169. * @class
  170. * @augments Kekule.ChemObjOperation.Base
  171. *
  172. * @param {Kekule.ChemObject} chemObject Target chem object.
  173. * @param {Hash} newCoord
  174. * @param {Int} coordMode
  175. * @param {Bool} useAbsBaseCoord
  176. *
  177. * @property {Hash} newCoord
  178. * @property {Hash} oldCoord If old coord is not set, this property will be automatically calculated when execute the operation.
  179. * @property {Int} coordMode
  180. * @property {Bool} useAbsBaseCoord
  181. */
  182. Kekule.ChemObjOperation.MoveTo = Class.create(Kekule.ChemObjOperation.Base,
  183. /** @lends Kekule.ChemObjOperation.MoveTo# */
  184. {
  185. /** @private */
  186. CLASS_NAME: 'Kekule.ChemObjOperation.MoveTo',
  187. /** @constructs */
  188. initialize: function($super, chemObj, newCoord, coordMode, useAbsBaseCoord, editor)
  189. {
  190. $super(chemObj, editor);
  191. if (newCoord)
  192. this.setNewCoord(newCoord);
  193. this.setCoordMode(coordMode || Kekule.CoordMode.COORD2D);
  194. this.setUseAbsBaseCoord(!!useAbsBaseCoord);
  195. },
  196. /** @private */
  197. initProperties: function()
  198. {
  199. this.defineProp('newCoord', {'dataType': DataType.HASH});
  200. this.defineProp('oldCoord', {'dataType': DataType.HASH});
  201. this.defineProp('coordMode', {'dataType': DataType.INT});
  202. this.defineProp('useAbsBaseCoord', {'dataType': DataType.BOOL});
  203. },
  204. /** @private */
  205. setObjCoord: function(obj, coord, coordMode)
  206. {
  207. if (obj && coord && coordMode)
  208. {
  209. var success = false;
  210. if (this.getUseAbsBaseCoord())
  211. {
  212. /*
  213. if (obj.setAbsCoordOfMode)
  214. {
  215. obj.setAbsCoordOfMode(coord, coordMode);
  216. success = true;
  217. }
  218. */
  219. if (obj.setAbsBaseCoord)
  220. {
  221. obj.setAbsBaseCoord(coord, coordMode, this.getAllowCoordBorrow());
  222. success = true;
  223. }
  224. }
  225. else
  226. {
  227. if (obj.setCoordOfMode)
  228. {
  229. obj.setCoordOfMode(coord, coordMode);
  230. success = true;
  231. }
  232. }
  233. if (!success)
  234. {
  235. var className = obj.getClassName? obj.getClassName(): (typeof obj);
  236. Kekule.warn(/*Kekule.ErrorMsg.CAN_NOT_SET_COORD_OF_CLASS*/Kekule.$L('ErrorMsg.CAN_NOT_SET_COORD_OF_CLASS').format(className));
  237. }
  238. }
  239. },
  240. /** @private */
  241. getObjCoord: function(obj, coordMode)
  242. {
  243. if (this.getUseAbsBaseCoord())
  244. {
  245. /*
  246. if (obj.getAbsCoordOfMode)
  247. return obj.getAbsCoordOfMode(coordMode, this.getAllowCoordBorrow());
  248. */
  249. if (obj.getAbsBaseCoord)
  250. return obj.getAbsBaseCoord(coordMode, this.getAllowCoordBorrow());
  251. }
  252. else
  253. {
  254. if (obj.getCoordOfMode)
  255. return obj.getCoordOfMode(coordMode, this.getAllowCoordBorrow());
  256. }
  257. return null;
  258. },
  259. /** @private */
  260. doExecute: function()
  261. {
  262. var obj = this.getTarget();
  263. if (!this.getOldCoord())
  264. this.setOldCoord(this.getObjCoord(obj, this.getCoordMode()));
  265. if (this.getNewCoord())
  266. this.setObjCoord(this.getTarget(), this.getNewCoord(), this.getCoordMode());
  267. },
  268. /** @private */
  269. doReverse: function()
  270. {
  271. if (this.getOldCoord())
  272. {
  273. this.setObjCoord(this.getTarget(), this.getOldCoord(), this.getCoordMode());
  274. }
  275. }
  276. });
  277. /**
  278. * Operation of changing a chem object's size and coord.
  279. * @class
  280. * @augments Kekule.ChemObjOperation.MoveTo
  281. *
  282. * @param {Kekule.ChemObject} chemObject Target chem object.
  283. * @param {Hash} newDimension {width, height}
  284. * @param {Hash} newCoord
  285. * @param {Int} coordMode
  286. * @param {Bool} useAbsCoord
  287. *
  288. * @property {Hash} newDimension
  289. * @property {Hash} oldDimension If old dimension is not set, this property will be automatically calculated when execute the operation.
  290. */
  291. Kekule.ChemObjOperation.MoveAndResize = Class.create(Kekule.ChemObjOperation.MoveTo,
  292. /** @lends Kekule.ChemObjOperation.MoveAndResize# */
  293. {
  294. /** @private */
  295. CLASS_NAME: 'Kekule.ChemObjOperation.MoveAndResize',
  296. /** @constructs */
  297. initialize: function($super, chemObj, newDimension, newCoord, coordMode, useAbsCoord, editor)
  298. {
  299. $super(chemObj, newCoord, coordMode, useAbsCoord, editor);
  300. },
  301. /** @private */
  302. initProperties: function()
  303. {
  304. this.defineProp('newDimension', {'dataType': DataType.HASH});
  305. this.defineProp('oldDimension', {'dataType': DataType.HASH});
  306. },
  307. /** @private */
  308. setObjSize: function(obj, dimension, coordMode)
  309. {
  310. if (obj && dimension)
  311. {
  312. if (obj.setSizeOfMode)
  313. {
  314. obj.setSizeOfMode(dimension, coordMode);
  315. }
  316. else
  317. {
  318. var className = obj.getClassName? obj.getClassName(): (typeof obj);
  319. Kekule.warn(/*Kekule.ErrorMsg.CAN_NOT_SET_DIMENSION_OF_CLASS*/Kekule.$L('ErrorMsg.CAN_NOT_SET_DIMENSION_OF_CLASS').format(className));
  320. }
  321. }
  322. },
  323. /** @private */
  324. getObjSize: function(obj, coordMode)
  325. {
  326. if (obj.getSizeOfMode)
  327. return obj.getSizeOfMode(coordMode, this.getAllowCoordBorrow());
  328. else
  329. return null;
  330. },
  331. /** @private */
  332. doExecute: function($super)
  333. {
  334. $super();
  335. var obj = this.getTarget();
  336. if (!this.getOldDimension())
  337. {
  338. this.setOldDimension(this.getObjSize(obj, this.getCoordMode()));
  339. }
  340. if (this.getNewDimension())
  341. this.setObjSize(this.getTarget(), this.getNewDimension(), this.getCoordMode());
  342. },
  343. /** @private */
  344. doReverse: function($super)
  345. {
  346. if (this.getOldDimension())
  347. this.setObjSize(this.getTarget(), this.getOldDimension(), this.getCoordMode());
  348. $super();
  349. }
  350. });
  351. /**
  352. * Operation of adding a chem object to parent.
  353. * @class
  354. * @augments Kekule.ChemObjOperation.Base
  355. *
  356. * @param {Kekule.ChemObject} chemObject Target chem object.
  357. * @param {Kekule.ChemObject} parentObj Object should be added to.
  358. * @param {Kekule.ChemObject} refSibling If this property is set, chem object will be inserted before this sibling.
  359. *
  360. * @property {Kekule.ChemObject} parentObj Object should be added to.
  361. * @property {Kekule.ChemObject} refSibling If this property is set, chem object will be inserted before this sibling.
  362. */
  363. Kekule.ChemObjOperation.Add = Class.create(Kekule.ChemObjOperation.Base,
  364. /** @lends Kekule.ChemObjOperation.Add# */
  365. {
  366. /** @private */
  367. CLASS_NAME: 'Kekule.ChemObjOperation.Add',
  368. /** @constructs */
  369. initialize: function($super, chemObj, parentObj, refSibling, editor)
  370. {
  371. $super(chemObj, editor);
  372. this.setParentObj(parentObj);
  373. this.setRefSibling(refSibling);
  374. },
  375. /** @private */
  376. initProperties: function()
  377. {
  378. this.defineProp('parentObj', {'dataType': 'Kekule.ChemObject', 'serializable': false});
  379. this.defineProp('refSibling', {'dataType': 'Kekule.ChemObject', 'serializable': false});
  380. },
  381. /** @private */
  382. doExecute: function()
  383. {
  384. var parent = this.getParentObj();
  385. var obj = this.getTarget();
  386. if (parent && obj)
  387. {
  388. var sibling = this.getRefSibling() || null;
  389. this.notifyBeforeAddingByEditor(obj, parent, sibling);
  390. parent.insertBefore(obj, sibling);
  391. this.notifyAfterAddingByEditor(obj, parent, sibling);
  392. }
  393. },
  394. /** @private */
  395. doReverse: function()
  396. {
  397. var obj = this.getTarget();
  398. /*
  399. var parent = obj.getParent? obj.getParent(): null;
  400. if (!parent)
  401. parent = this.getParentObj();
  402. if (parent !== this.getParentObj())
  403. console.log('[abnormal!!!!!!!]', parent.getId(), this.getParentObj().getId());
  404. */
  405. var parent = this.getParentObj();
  406. if (parent && obj)
  407. {
  408. var sibling = this.getRefSibling();
  409. if (!sibling) // auto calc
  410. {
  411. sibling = obj.getNextSibling();
  412. this.setRefSibling(sibling);
  413. }
  414. this.notifyBeforeRemovingByEditor(obj, parent);
  415. parent.removeChild(obj);
  416. this.notifyAfterRemovingByEditor(obj, parent);
  417. }
  418. }
  419. });
  420. /**
  421. * Operation of removing a chem object from its parent.
  422. * @class
  423. * @augments Kekule.ChemObjOperation.Base
  424. *
  425. * @param {Kekule.ChemObject} chemObject Target chem object.
  426. * @param {Kekule.ChemObject} parentObj Object should be added to.
  427. * @param {Kekule.ChemObject} refSibling Sibling after target object before removing.
  428. *
  429. * @property {Kekule.ChemObject} parentObj Object should be added to.
  430. * @property {Kekule.ChemObject} refSibling Sibling after target object before removing.
  431. * This property is used in reversing the operation. If not set, it will be calculated automatically in execution.
  432. */
  433. Kekule.ChemObjOperation.Remove = Class.create(Kekule.ChemObjOperation.Base,
  434. /** @lends Kekule.ChemObjOperation.Remove# */
  435. {
  436. /** @private */
  437. CLASS_NAME: 'Kekule.ChemObjOperation.Remove',
  438. /** @constructs */
  439. initialize: function($super, chemObj, parentObj, refSibling, editor)
  440. {
  441. $super(chemObj, editor);
  442. this.setParentObj(parentObj);
  443. this.setRefSibling(refSibling);
  444. },
  445. /** @private */
  446. initProperties: function()
  447. {
  448. this.defineProp('parentObj', {'dataType': 'Kekule.ChemObject', 'serializable': false});
  449. this.defineProp('ownerObj', {'dataType': 'Kekule.ChemObject', 'serializable': false});
  450. this.defineProp('refSibling', {'dataType': 'Kekule.ChemObject', 'serializable': false});
  451. },
  452. /** @private */
  453. _isInEditorSelection: function(obj)
  454. {
  455. var editor = this.getEditor();
  456. return ((editor && editor.getSelection && editor.getSelection()) || []).indexOf(obj) >= 0;
  457. },
  458. /** @private */
  459. doExecute: function()
  460. {
  461. var obj = this.getTarget();
  462. var parent = this.getParentObj();
  463. var owner = this.getOwnerObj();
  464. if (!parent && obj.getParent)
  465. {
  466. parent = obj.getParent();
  467. this.setParentObj(parent);
  468. }
  469. if (!owner && obj.getOwner)
  470. {
  471. owner = obj.getOwner();
  472. this.setOwnerObj(owner);
  473. }
  474. if (parent && obj)
  475. {
  476. if (!this.getRefSibling())
  477. {
  478. var sibling = obj.getNextSibling? obj.getNextSibling(): null;
  479. this.setRefSibling(sibling);
  480. }
  481. //console.log('remove', obj.getId());
  482. // ensure obj is also removed from editor's selection
  483. var editor = this.getEditor();
  484. var needModifySelection = this._isInEditorSelection(obj);
  485. if (needModifySelection)
  486. editor.beginUpdateSelection();
  487. //console.log('remove child', parent.getClassName(), obj.getClassName());
  488. this.notifyBeforeRemovingByEditor(obj, parent);
  489. parent.removeChild(obj);
  490. this.notifyAfterRemovingByEditor(obj, parent);
  491. if (needModifySelection)
  492. {
  493. //console.log('remove from selection', obj.getId());
  494. editor.removeFromSelection(obj);
  495. editor.endUpdateSelection();
  496. }
  497. }
  498. },
  499. /** @private */
  500. doReverse: function()
  501. {
  502. var parent = this.getParentObj();
  503. var owner = this.getOwnerObj();
  504. var obj = this.getTarget();
  505. if (parent && obj)
  506. {
  507. var sibling = this.getRefSibling();
  508. if (owner)
  509. obj.setOwner(owner);
  510. this.notifyBeforeAddingByEditor(obj, parent, sibling);
  511. parent.insertBefore(obj, sibling);
  512. this.notifyAfterAddingByEditor(obj, parent, sibling);
  513. }
  514. }
  515. });
  516. /**
  517. * A namespace for operation about Chem Structure instance.
  518. * @namespace
  519. */
  520. Kekule.ChemStructOperation = {};
  521. /**
  522. * Operation of adding a chem node to a structure fragment / molecule.
  523. * @class
  524. * @augments Kekule.ChemObjOperation.Add
  525. */
  526. Kekule.ChemStructOperation.AddNode = Class.create(Kekule.ChemObjOperation.Add,
  527. /** @lends Kekule.ChemStructOperation.AddNode# */
  528. {
  529. /** @private */
  530. CLASS_NAME: 'Kekule.ChemStructOperation.AddNode'
  531. });
  532. /**
  533. * Operation of removing a chem node from a structure fragment / molecule.
  534. * @class
  535. * @augments Kekule.ChemObjOperation.Remove
  536. *
  537. * @property {Array} linkedConnectors
  538. */
  539. Kekule.ChemStructOperation.RemoveNode = Class.create(Kekule.ChemObjOperation.Remove,
  540. /** @lends Kekule.ChemStructOperation.RemoveNode# */
  541. {
  542. /** @private */
  543. CLASS_NAME: 'Kekule.ChemStructOperation.RemoveNode',
  544. /** @private */
  545. initProperties: function()
  546. {
  547. this.defineProp('linkedConnectors', {'dataType': DataType.ARRAY, 'serializable': false});
  548. },
  549. /** @private */
  550. doExecute: function($super)
  551. {
  552. if (!this.getLinkedConnectors())
  553. {
  554. this.setLinkedConnectors(Kekule.ArrayUtils.clone(this.getTarget().getLinkedConnectors()));
  555. }
  556. $super()
  557. },
  558. /** @private */
  559. doReverse: function($super)
  560. {
  561. $super();
  562. var linkedConnectors = this.getLinkedConnectors();
  563. //console.log('reverse node', this.getTarget().getId());
  564. if (linkedConnectors && linkedConnectors.length)
  565. {
  566. //this.getTarget().setLinkedConnectors(linkedConnectors);
  567. var target = this.getTarget();
  568. //console.log('reverse append connector', linkedConnectors.length);
  569. for (var i = 0, l = linkedConnectors.length; i < l; ++i)
  570. {
  571. //linkedConnectors[i].appendConnectedObj(target);
  572. target.appendLinkedConnector(linkedConnectors[i]);
  573. }
  574. }
  575. }
  576. });
  577. /**
  578. * Operation of replace a chem node with another one.
  579. * @class
  580. * @augments Kekule.Operation
  581. */
  582. Kekule.ChemStructOperation.ReplaceNode = Class.create(Kekule.Operation,
  583. /** @lends Kekule.ChemStructOperation.ReplaceNode# */
  584. {
  585. /** @private */
  586. CLASS_NAME: 'Kekule.ChemStructOperation.ReplaceNode',
  587. /** @constructs */
  588. initialize: function($super, oldNode, newNode, parentObj, editor)
  589. {
  590. $super();
  591. this.setOldNode(oldNode);
  592. this.setNewNode(newNode);
  593. this.setParentObj(parentObj);
  594. this.setEditor(editor);
  595. },
  596. /** @private */
  597. initProperties: function()
  598. {
  599. this.defineProp('oldNode', {'dataType': 'Kekule.ChemStructureNode', 'serializable': false});
  600. this.defineProp('newNode', {'dataType': 'Kekule.ChemStructureNode', 'serializable': false});
  601. this.defineProp('parentObj', {'dataType': 'Kekule.ChemStructureFragment', 'serializable': false});
  602. this.defineProp('editor', {'dataType': 'Kekule.Editor.BaseEditor', 'serializable': false});
  603. },
  604. /** @private */
  605. _isInEditorSelection: function(node)
  606. {
  607. var editor = this.getEditor();
  608. return ((editor && editor.getSelection && editor.getSelection()) || []).indexOf(node) >= 0;
  609. },
  610. /** @private */
  611. doExecute: function()
  612. {
  613. var oldNode = this.getOldNode();
  614. var newNode = this.getNewNode();
  615. if (oldNode && newNode)
  616. {
  617. var parent = this.getParentObj();
  618. if (!parent)
  619. {
  620. parent = oldNode.getParent();
  621. this.setParentObj(parent);
  622. }
  623. if (parent.replaceNode)
  624. {
  625. var editor = this.getEditor();
  626. var needModifySelection = this._isInEditorSelection(oldNode);
  627. if (needModifySelection)
  628. editor.beginUpdateSelection();
  629. parent.replaceNode(oldNode, newNode);
  630. if (needModifySelection)
  631. {
  632. editor.removeFromSelection(oldNode);
  633. editor.addObjToSelection(newNode);
  634. editor.endUpdateSelection();
  635. }
  636. }
  637. }
  638. },
  639. /** @private */
  640. doReverse: function()
  641. {
  642. var oldNode = this.getOldNode();
  643. var newNode = this.getNewNode();
  644. if (oldNode && newNode)
  645. {
  646. var parent = this.getParentObj() || newNode.getParent();
  647. if (parent.replaceNode)
  648. {
  649. //console.log('reverse!');
  650. var editor = this.getEditor();
  651. var needModifySelection = this._isInEditorSelection(newNode);
  652. if (needModifySelection)
  653. editor.beginUpdateSelection();
  654. parent.replaceNode(newNode, oldNode);
  655. if (needModifySelection)
  656. {
  657. editor.removeFromSelection(newNode)
  658. editor.addObjToSelection(oldNode);
  659. editor.endUpdateSelection();
  660. }
  661. }
  662. }
  663. }
  664. });
  665. /**
  666. * Operation of adding a chem connector to a structure fragment / molecule.
  667. * @class
  668. * @augments Kekule.ChemObjOperation.Add
  669. *
  670. * @param {Kekule.ChemObject} chemObject Target chem object.
  671. * @param {Kekule.ChemObject} parentObj Object should be added to.
  672. * @param {Kekule.ChemObject} refSibling If this property is set, chem object will be inserted before this sibling.
  673. * @param {Array} connectedObjs Objects that connected by this connector.
  674. *
  675. * @property {Array} connectedObjs Objects that connected by this connector.
  676. */
  677. Kekule.ChemStructOperation.AddConnector = Class.create(Kekule.ChemObjOperation.Add,
  678. /** @lends Kekule.ChemStructOperation.AddConnector# */
  679. {
  680. /** @private */
  681. CLASS_NAME: 'Kekule.ChemStructOperation.AddConnector',
  682. /** @constructs */
  683. initialize: function($super, chemObj, parentObj, refSibling, connectedObjs, editor)
  684. {
  685. $super(chemObj, parentObj, refSibling, editor);
  686. //this.setParentObj(parentObj);
  687. //this.setRefSibling(refSibling);
  688. this.setConnectedObjs(connectedObjs);
  689. },
  690. /** @private */
  691. initProperties: function()
  692. {
  693. this.defineProp('connectedObjs', {'dataType': DataType.ARRAY, 'serializable': false});
  694. },
  695. /** @private */
  696. doExecute: function($super)
  697. {
  698. $super();
  699. var connObjs = Kekule.ArrayUtils.clone(this.getConnectedObjs());
  700. if (connObjs && connObjs.length)
  701. {
  702. this.getTarget().setConnectedObjs(connObjs);
  703. }
  704. },
  705. /** @private */
  706. doReverse: function($super)
  707. {
  708. $super();
  709. }
  710. });
  711. /**
  712. * Operation of removing a chem connector from a structure fragment / molecule.
  713. * @class
  714. * @augments Kekule.ChemObjOperation.Remove
  715. *
  716. * @property {Array} connectedObjs Objects that connected by this connector.
  717. * This property is used in operation reversing. If not set, value will be automatically calculated in operation executing.
  718. */
  719. Kekule.ChemStructOperation.RemoveConnector = Class.create(Kekule.ChemObjOperation.Remove,
  720. /** @lends Kekule.ChemStructOperation.RemoveConnector# */
  721. {
  722. /** @private */
  723. CLASS_NAME: 'Kekule.ChemStructOperation.RemoveConnector',
  724. /** @private */
  725. initProperties: function()
  726. {
  727. this.defineProp('connectedObjs', {'dataType': DataType.ARRAY, 'serializable': false});
  728. },
  729. /** @private */
  730. doExecute: function($super)
  731. {
  732. if (!this.getConnectedObjs())
  733. {
  734. this.setConnectedObjs(Kekule.ArrayUtils.clone(this.getTarget().getConnectedObjs()));
  735. }
  736. $super()
  737. },
  738. /** @private */
  739. doReverse: function($super)
  740. {
  741. $super();
  742. var connObjs = this.getConnectedObjs();
  743. if (connObjs && connObjs.length)
  744. {
  745. this.getTarget().setConnectedObjs(connObjs);
  746. }
  747. }
  748. });
  749. /**
  750. * The base operation of merging two nodes as one, acts as the parent class of MergeNodes and MergeNodesPreview.
  751. * @class
  752. * @augments Kekule.ChemObjOperation.Base
  753. *
  754. * @param {Kekule.ChemStructureNode} target Source node, all connectors to this node will be connected to toNode.
  755. * @param {Kekule.ChemStructureNode} dest Destination node.
  756. * @param {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging nodes between different molecule.
  757. *
  758. * @property {Kekule.ChemStructureNode} target Source node, all connectors to this node will be connected to toNode.
  759. * @property {Kekule.ChemStructureNode} dest Destination node.
  760. * @property {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging nodes between different molecule.
  761. */
  762. Kekule.ChemStructOperation.MergeNodesBase = Class.create(Kekule.ChemObjOperation.Base,
  763. /** @lends Kekule.ChemStructOperation.MergeNodesBase# */
  764. {
  765. /** @private */
  766. CLASS_NAME: 'Kekule.ChemStructOperation.MergeNodesBase',
  767. /** @constructs */
  768. initialize: function($super, target, dest, enableStructFragmentMerge, editor)
  769. {
  770. $super(target, editor);
  771. this.setDest(dest);
  772. this.setEnableStructFragmentMerge(enableStructFragmentMerge || false);
  773. this._refSibling = null;
  774. this._nodeParent = null;
  775. },
  776. /** @private */
  777. initProperties: function()
  778. {
  779. this.defineProp('dest', {'dataType': 'Kekule.ChemStructureNode', 'serializable': false});
  780. this.defineProp('enableStructFragmentMerge', {'dataType': DataType.BOOL});
  781. },
  782. /**
  783. * Returns nodes connected with both node1 and node2.
  784. * @param {Kekule.ChemStructureNode} node1
  785. * @param {Kekule.ChemStructureNode} node2
  786. * @returns {Array}
  787. */
  788. getCommonSiblings: function(node1, node2)
  789. {
  790. var siblings1 = node1.getLinkedObjs();
  791. var siblings2 = node2.getLinkedObjs();
  792. return Kekule.ArrayUtils.intersect(siblings1, siblings2);
  793. },
  794. /** @private */
  795. doExecute: function()
  796. {
  797. // do nothing, descendants should override
  798. },
  799. /** @private */
  800. doReverse: function()
  801. {
  802. // do nothing, descendants should override
  803. }
  804. });
  805. /**
  806. * Operation of merging two nodes as one.
  807. * @class
  808. * @augments Kekule.ChemStructOperation.MergeNodesBase
  809. *
  810. * @param {Kekule.ChemStructureNode} target Source node, all connectors to this node will be connected to toNode.
  811. * @param {Kekule.ChemStructureNode} dest Destination node.
  812. * @param {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging nodes between different molecule.
  813. *
  814. * @property {Array} changedConnectors Connectors modified during merge.
  815. * @property {Array} removedConnectors Connectors removed during merge.
  816. */
  817. Kekule.ChemStructOperation.MergeNodes = Class.create(Kekule.ChemStructOperation.MergeNodesBase,
  818. /** @lends Kekule.ChemStructOperation.MergeNodes# */
  819. {
  820. /** @private */
  821. CLASS_NAME: 'Kekule.ChemStructOperation.MergeNodes',
  822. /** @constructs */
  823. initialize: function($super, target, dest, enableStructFragmentMerge, editor)
  824. {
  825. $super(target, dest, enableStructFragmentMerge, editor);
  826. this._refSibling = null;
  827. this._nodeParent = null;
  828. this._structFragmentMergeOperation = null;
  829. this._removeConnectorOperations = [];
  830. this._removeNodeOperation = null;
  831. },
  832. /** @private */
  833. initProperties: function()
  834. {
  835. this.defineProp('changedConnectors', {'dataType': DataType.ARRAY, 'serializable': false});
  836. this.defineProp('removedConnectors', {'dataType': DataType.ARRAY, 'serializable': false});
  837. //this.defineProp('enableStructFragmentMerge', {'dataType': DataType.BOOL});
  838. },
  839. /** @ignore */
  840. doExecute: function()
  841. {
  842. var fromNode = this.getTarget();
  843. var toNode = this.getDest();
  844. var structFragment = fromNode.getParentFragment();
  845. var destFragment = toNode.getParentFragment();
  846. if (structFragment !== destFragment) // from different molecule
  847. {
  848. //console.log('need merge mol');
  849. if (this.getEnableStructFragmentMerge())
  850. {
  851. this._structFragmentMergeOperation = new Kekule.ChemStructOperation.MergeStructFragment(structFragment, destFragment, this.getEditor());
  852. //this._structFragmentMergeOperation = new Kekule.ChemStructOperation.MergeStructFragment(destFragment, structFragment);
  853. this._structFragmentMergeOperation.execute();
  854. structFragment = destFragment;
  855. }
  856. else
  857. return null;
  858. }
  859. this._nodeParent = structFragment;
  860. structFragment.beginUpdate();
  861. try
  862. {
  863. var editor = this.getEditor();
  864. var removedConnectors = this.getRemovedConnectors();
  865. if (!removedConnectors) // auto calc
  866. {
  867. var commonSiblings = this.getCommonSiblings(fromNode, toNode);
  868. var removedConnectors = [];
  869. if (commonSiblings.length) // has common sibling between from/toNode, bypass bond between fromNode and sibling
  870. {
  871. for (var i = 0, l = commonSiblings.length; i < l; ++i)
  872. {
  873. var sibling = commonSiblings[i];
  874. var connector = fromNode.getConnectorTo(sibling);
  875. if (connector && (connector.getConnectedObjCount() == 2))
  876. removedConnectors.push(connector);
  877. }
  878. }
  879. var directConnector = fromNode.getConnectorTo(toNode);
  880. if (directConnector)
  881. removedConnectors.push(directConnector);
  882. this.setRemovedConnectors(removedConnectors);
  883. }
  884. var connectors = this.getChangedConnectors();
  885. if (!connectors) // auto calc
  886. {
  887. var connectors = Kekule.ArrayUtils.clone(fromNode.getLinkedConnectors()) || [];
  888. connectors = Kekule.ArrayUtils.exclude(connectors, removedConnectors);
  889. this.setChangedConnectors(connectors);
  890. }
  891. // save fromNode's information
  892. this._refSibling = fromNode.getNextSibling();
  893. for (var i = 0, l = connectors.length; i < l; ++i)
  894. {
  895. var connector = connectors[i];
  896. var index = connector.indexOfConnectedObj(fromNode);
  897. connector.removeConnectedObj(fromNode);
  898. connector.insertConnectedObjAt(toNode, index); // keep the index is important, wedge bond direction is related with node sequence
  899. }
  900. this._removeConnectorOperations = [];
  901. for (var i = 0, l = removedConnectors.length; i < l; ++i)
  902. {
  903. var connector = removedConnectors[i];
  904. var oper = new Kekule.ChemStructOperation.RemoveConnector(connector, null, null, editor);
  905. oper.execute();
  906. this._removeConnectorOperations.push(oper);
  907. }
  908. //structFragment.removeNode(fromNode);
  909. this._removeNodeOperation = new Kekule.ChemStructOperation.RemoveNode(fromNode, null, null, editor);
  910. this._removeNodeOperation.execute();
  911. }
  912. finally
  913. {
  914. structFragment.endUpdate();
  915. }
  916. },
  917. /** @ignore */
  918. doReverse: function()
  919. {
  920. var fromNode = this.getTarget();
  921. var toNode = this.getDest();
  922. //var structFragment = fromNode.getParent();
  923. //var structFragment = toNode.getParent();
  924. var structFragment = this._nodeParent;
  925. structFragment.beginUpdate();
  926. try
  927. {
  928. /*
  929. console.log(fromNode.getParent(), fromNode.getParent() === structFragment,
  930. toNode.getParent(), toNode.getParent() === structFragment);
  931. */
  932. //structFragment.insertBefore(fromNode, this._refSibling);
  933. this._removeNodeOperation.reverse();
  934. if (this._removeConnectorOperations.length)
  935. {
  936. for (var i = this._removeConnectorOperations.length - 1; i >= 0; --i)
  937. {
  938. var oper = this._removeConnectorOperations[i];
  939. oper.reverse();
  940. }
  941. }
  942. this._removeConnectorOperations = [];
  943. var connectors = this.getChangedConnectors();
  944. //console.log('reverse node merge2', toNode, toNode.getParent());
  945. for (var i = 0, l = connectors.length; i < l; ++i)
  946. {
  947. var connector = connectors[i];
  948. var index = connector.indexOfConnectedObj(toNode);
  949. connector.removeConnectedObj(toNode);
  950. connector.insertConnectedObjAt(fromNode, index);
  951. }
  952. }
  953. finally
  954. {
  955. structFragment.endUpdate();
  956. }
  957. //console.log('reverse node merge', toNode, toNode.getParent());
  958. if (this._structFragmentMergeOperation)
  959. {
  960. this._structFragmentMergeOperation.reverse();
  961. }
  962. }
  963. });
  964. /**
  965. * A class method to check if two connectors can be merged
  966. * @param {Kekule.ChemStructureNode} target
  967. * @param {Kekule.ChemStructureNode} dest
  968. * @param {Bool} canMergeStructFragment
  969. * @returns {Bool}
  970. */
  971. Kekule.ChemStructOperation.MergeNodes.canMerge = function(target, dest, canMergeStructFragment, canMergeNeighborNodes)
  972. {
  973. // never allow merge to another molecule point (e.g. formula molecule) or subgroup
  974. if ((target instanceof Kekule.StructureFragment) || (dest instanceof Kekule.StructureFragment))
  975. return false;
  976. if (!((target instanceof Kekule.ChemStructureNode) && (dest instanceof Kekule.ChemStructureNode)))
  977. return false;
  978. var targetFragment = target.getParent();
  979. var destFragment = dest.getParent();
  980. var result = (targetFragment === destFragment) || canMergeStructFragment;
  981. if (!canMergeNeighborNodes)
  982. result = result && (!target.getConnectorTo(dest));
  983. return result;
  984. };
  985. /**
  986. * Preview operation of merging two nodes as one.
  987. * This operation just set the same position of merging nodes, but do not do the actual merge.
  988. * @class
  989. * @augments Kekule.ChemStructOperation.MergeNodesBase
  990. *
  991. * @param {Kekule.ChemStructureNode} target Source node, all connectors to this node will be connected to toNode.
  992. * @param {Kekule.ChemStructureNode} dest Destination node.
  993. * @param {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging nodes between different molecule.
  994. */
  995. Kekule.ChemStructOperation.MergeNodesPreview = Class.create(Kekule.ChemStructOperation.MergeNodesBase,
  996. /** @lends Kekule.ChemStructOperation.MergeNodesPreview# */
  997. {
  998. /** @private */
  999. CLASS_NAME: 'Kekule.ChemStructOperation.MergeNodesPreview',
  1000. /** @constructs */
  1001. initialize: function($super, target, dest, enableStructFragmentMerge, editor)
  1002. {
  1003. $super(target, dest, enableStructFragmentMerge, editor);
  1004. this._nodeParent = null;
  1005. },
  1006. /** @ignore */
  1007. doExecute: function()
  1008. {
  1009. this._moveNodeOperations = [];
  1010. var fromNode = this.getTarget();
  1011. var toNode = this.getDest();
  1012. var structFragment = fromNode.getParentFragment();
  1013. /*
  1014. if (!structFragment)
  1015. console.log('merge from', fromNode.getId(), 'to', toNode.getId());
  1016. */
  1017. var CM = Kekule.CoordMode;
  1018. var coordModes = [CM.COORD2D, CM.COORD3D];
  1019. if (structFragment)
  1020. structFragment.beginUpdate();
  1021. try
  1022. {
  1023. for (var i = 0, l = coordModes.length; i < l; ++i)
  1024. {
  1025. var toCoord = toNode.getAbsBaseCoord(coordModes[i], false);
  1026. var oper = new Kekule.ChemObjOperation.MoveTo(fromNode, toCoord, coordModes[i], true, this.getEditor());
  1027. oper.execute();
  1028. this._moveNodeOperations.push(oper);
  1029. }
  1030. this._nodeParent = structFragment;
  1031. }
  1032. finally
  1033. {
  1034. if (structFragment)
  1035. structFragment.endUpdate();
  1036. }
  1037. },
  1038. /** @ignore */
  1039. doReverse: function()
  1040. {
  1041. var structFragment = this._nodeParent;
  1042. if (structFragment)
  1043. structFragment.beginUpdate();
  1044. try
  1045. {
  1046. var opers = this._moveNodeOperations;
  1047. for (var i = opers.length - 1; i >= 0; --i)
  1048. {
  1049. opers[i].reverse();
  1050. }
  1051. }
  1052. finally
  1053. {
  1054. if (structFragment)
  1055. structFragment.endUpdate();
  1056. }
  1057. this._moveNodeOperations = null;
  1058. }
  1059. });
  1060. Kekule.ChemStructOperation.MergeNodesPreview.canMerge = Kekule.ChemStructOperation.MergeNodes.canMerge;
  1061. /**
  1062. * Operation of merging two connectors as one.
  1063. * @class
  1064. * @augments Kekule.ChemObjOperation.Base
  1065. *
  1066. * @param {Kekule.ChemStructureConnector} target Source connector.
  1067. * @param {Kekule.ChemStructureConnector} dest Destination connector.
  1068. * @param {Int} coordMode Coord mode of current editor.
  1069. * @param {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging connectors between different molecule.
  1070. *
  1071. * @property {Kekule.ChemStructureConnector} target Source connector.
  1072. * @property {Kekule.ChemStructureConnector} dest Destination connector.
  1073. * @property {Int} coordMode Coord mode of current editor.
  1074. * @property {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging connectors between different molecule.
  1075. */
  1076. Kekule.ChemStructOperation.MergeConnectorsBase = Class.create(Kekule.ChemObjOperation.Base,
  1077. /** @lends Kekule.ChemStructOperation.MergeConnectorsBase# */
  1078. {
  1079. /** @private */
  1080. CLASS_NAME: 'Kekule.ChemStructOperation.MergeConnectorsBase',
  1081. /** @constructs */
  1082. initialize: function($super, target, dest, coordMode, enableStructFragmentMerge, editor)
  1083. {
  1084. $super(target, editor);
  1085. this.setDest(dest);
  1086. this.setCoordMode(coordMode || Kekule.CoordMode.COORD2D);
  1087. this.setEnableStructFragmentMerge(enableStructFragmentMerge || false);
  1088. this._refSibling = null;
  1089. this._nodeParent = null;
  1090. //this._structFragmentMergeOperation = null;
  1091. this._nodeMergeOperations = [];
  1092. },
  1093. /** @private */
  1094. initProperties: function()
  1095. {
  1096. this.defineProp('dest', {'dataType': 'Kekule.ChemStructureConnector', 'serializable': false});
  1097. this.defineProp('enableStructFragmentMerge', {'dataType': DataType.BOOL});
  1098. this.defineProp('coordMode', {'dataType': DataType.INT});
  1099. },
  1100. /**
  1101. * Returns the concrete node merge operation, descendants should override this method.
  1102. */
  1103. getMergeNodeOperationClass: function()
  1104. {
  1105. // do nothing here
  1106. },
  1107. /** @private */
  1108. doExecute: function()
  1109. {
  1110. var canMerge = Kekule.ChemStructOperation.MergeConnectors.canMerge(this.getTarget(), this.getDest(), this.getEnableStructFragmentMerge());
  1111. if (!canMerge)
  1112. Kekule.error(Kekule.$L('ErrorMsg.CAN_NOT_MERGE_CONNECTORS'));
  1113. // sort targetNodes and destNodes via coord
  1114. var coordMode = this.getCoordMode();
  1115. var connectors = [this.getTarget(), this.getDest()];
  1116. var AU = Kekule.ArrayUtils;
  1117. var targetNodes = AU.clone(this.getTarget().getConnectedObjs());
  1118. var destNodes = AU.clone(this.getDest().getConnectedObjs());
  1119. var allowCoordBorrow = this.getAllowCoordBorrow();
  1120. for (var i = 0, l = connectors.length; i < l; ++i)
  1121. {
  1122. var connector = connectors[i];
  1123. var coord1 = connector.getConnectedObjAt(0).getAbsCoordOfMode(coordMode, allowCoordBorrow);
  1124. var coord2 = connector.getConnectedObjAt(1).getAbsCoordOfMode(coordMode, allowCoordBorrow);
  1125. var coordDelta = Kekule.CoordUtils.substract(coord1, coord2);
  1126. var dominateDirection = Math.abs(coordDelta.x) > Math.abs(coordDelta.y)? 'x': 'y';
  1127. if (Kekule.ObjUtils.notUnset(coord1.z))
  1128. {
  1129. if (Math.abs(coordDelta.z) > Math.abs(coordDelta.x))
  1130. dominateDirection = 'z';
  1131. }
  1132. var nodes = (i === 0)? targetNodes: destNodes;
  1133. nodes.sort(function(a, b)
  1134. {
  1135. var coord1 = a.getAbsCoordOfMode(coordMode, allowCoordBorrow);
  1136. var coord2 = b.getAbsCoordOfMode(coordMode, allowCoordBorrow);
  1137. return (coord1[dominateDirection] - coord2[dominateDirection]) || 0;
  1138. }
  1139. );
  1140. }
  1141. var commonNodes = AU.intersect(targetNodes, destNodes);
  1142. targetNodes = AU.exclude(targetNodes, commonNodes);
  1143. destNodes = AU.exclude(destNodes, commonNodes);
  1144. this._nodeMergeOperations = [];
  1145. var nodeMergeOperClass = this.getMergeNodeOperationClass();
  1146. for (var i = 0, l = targetNodes.length; i < l; ++i)
  1147. {
  1148. if (targetNodes[i] !== destNodes[i])
  1149. {
  1150. //var oper = new Kekule.ChemStructOperation.MergeNodes(targetNodes[i], destNodes[i], this.getEnableStructFragmentMerge());
  1151. var oper = new nodeMergeOperClass(targetNodes[i], destNodes[i], this.getEnableStructFragmentMerge());
  1152. oper.execute();
  1153. }
  1154. this._nodeMergeOperations.push(oper);
  1155. }
  1156. },
  1157. /** @private */
  1158. doReverse: function()
  1159. {
  1160. for (var i = this._nodeMergeOperations.length - 1; i >= 0; --i)
  1161. {
  1162. var oper = this._nodeMergeOperations[i];
  1163. oper.reverse();
  1164. }
  1165. this._nodeMergeOperations = [];
  1166. }
  1167. });
  1168. /**
  1169. * Operation of merging two connectors as one.
  1170. * @class
  1171. * @augments Kekule.ChemStructOperation.MergeConnectorsBase
  1172. *
  1173. * @param {Kekule.ChemStructureConnector} target Source connector.
  1174. * @param {Kekule.ChemStructureConnector} dest Destination connector.
  1175. * @param {Int} coordMode Coord mode of current editor.
  1176. * @param {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging connectors between different molecule.
  1177. */
  1178. Kekule.ChemStructOperation.MergeConnectors = Class.create(Kekule.ChemStructOperation.MergeConnectorsBase,
  1179. /** @lends Kekule.ChemStructOperation.MergeConnectors# */
  1180. {
  1181. /** @private */
  1182. CLASS_NAME: 'Kekule.ChemStructOperation.MergeConnectors',
  1183. /** @ignore */
  1184. getMergeNodeOperationClass: function()
  1185. {
  1186. return Kekule.ChemStructOperation.MergeNodes;
  1187. }
  1188. });
  1189. /**
  1190. * A class method to check if two connectors can be merged
  1191. * @param {Kekule.ChemStructureConnector} target
  1192. * @param {Kekule.ChemStructureConnector} dest
  1193. * @param {Bool} canMergeStructFragment
  1194. * @returns {Bool}
  1195. */
  1196. Kekule.ChemStructOperation.MergeConnectors.canMerge = function(target, dest, canMergeStructFragment)
  1197. {
  1198. if (!canMergeStructFragment && (target.getParent() !== dest.getParent()))
  1199. return false;
  1200. if (target.isConnectingConnector() || dest.isConnectingConnector())
  1201. {
  1202. return false;
  1203. }
  1204. var targetNodes = target.getConnectedExposedObjs();
  1205. var destNodes = dest.getConnectedObjs();
  1206. if (targetNodes.length !== destNodes.length)
  1207. {
  1208. return false;
  1209. }
  1210. if (targetNodes.length !== 2) // currently can only handle connector with 2 connected objects
  1211. {
  1212. return false;
  1213. }
  1214. if (Kekule.ArrayUtils.intersect(targetNodes, destNodes).length >= 1)
  1215. {
  1216. return false;
  1217. }
  1218. return true;
  1219. };
  1220. /**
  1221. * Operation of merging two connectors as one.
  1222. * @class
  1223. * @augments Kekule.ChemStructOperation.MergeConnectorsBase
  1224. *
  1225. * @param {Kekule.ChemStructureConnector} target Source connector.
  1226. * @param {Kekule.ChemStructureConnector} dest Destination connector.
  1227. * @param {Int} coordMode Coord mode of current editor.
  1228. * @param {Bool} enableStructFragmentMerge If true, molecule will be also merged when merging connectors between different molecule.
  1229. */
  1230. Kekule.ChemStructOperation.MergeConnectorsPreview = Class.create(Kekule.ChemStructOperation.MergeConnectorsBase,
  1231. /** @lends Kekule.ChemStructOperation.MergeConnectors# */
  1232. {
  1233. /** @private */
  1234. CLASS_NAME: 'Kekule.ChemStructOperation.MergeConnectors',
  1235. /** @ignore */
  1236. getMergeNodeOperationClass: function()
  1237. {
  1238. return Kekule.ChemStructOperation.MergeNodesPreview;
  1239. }
  1240. });
  1241. Kekule.ChemStructOperation.MergeConnectorsPreview.canMerge = Kekule.ChemStructOperation.MergeConnectors.canMerge;
  1242. /**
  1243. * Operation of merging two structure fragment as one.
  1244. * @class
  1245. * @augments Kekule.ChemObjOperation.Base
  1246. *
  1247. * @param {Kekule.StructureFragment} target Source fragment.
  1248. * @param {Kekule.StructureFragment} dest Destination fragment.
  1249. *
  1250. * @property {Kekule.StructureFragment} target Source fragment, all connectors and nodes will be moved to dest fragment.
  1251. * @property {Kekule.StructureFragment} dest Destination fragment.
  1252. * @property {Array} mergedNodes Nodes moved from target to dest during merging.
  1253. * @property {Array} mergedConnectors Connectors moved from target to dest during merging.
  1254. */
  1255. Kekule.ChemStructOperation.MergeStructFragment = Class.create(Kekule.ChemObjOperation.Base,
  1256. /** @lends Kekule.ChemStructOperation.MergeStructFragment# */
  1257. {
  1258. /** @private */
  1259. CLASS_NAME: 'Kekule.ChemStructOperation.MergeStructFragment',
  1260. /** @constructs */
  1261. initialize: function($super, target, dest, editor)
  1262. {
  1263. $super(target, editor);
  1264. this.setDest(dest);
  1265. this._removeOperation = null;
  1266. },
  1267. /** @private */
  1268. initProperties: function()
  1269. {
  1270. this.defineProp('dest', {'dataType': 'Kekule.StructureFragment', 'serializable': false});
  1271. this.defineProp('mergedNodes', {'dataType': DataType.ARRAY, 'serializable': false});
  1272. this.defineProp('mergedConnectors', {'dataType': DataType.ARRAY, 'serializable': false});
  1273. },
  1274. /** @private */
  1275. moveChildBetweenStructFragment: function(target, dest, nodes, connectors)
  1276. {
  1277. Kekule.ChemStructureUtils.moveChildBetweenStructFragment(target, dest, nodes, connectors);
  1278. },
  1279. /** @private */
  1280. doExecute: function()
  1281. {
  1282. var target = this.getTarget();
  1283. var dest = this.getDest();
  1284. if (target && dest)
  1285. {
  1286. var nodes = Kekule.ArrayUtils.clone(target.getNodes());
  1287. this.setMergedNodes(nodes);
  1288. var connectors = Kekule.ArrayUtils.clone(target.getConnectors());
  1289. this.setMergedConnectors(connectors);
  1290. this.moveChildBetweenStructFragment(target, dest, nodes, connectors);
  1291. var parent = target.getParent();
  1292. if (parent) // remove target from parent
  1293. {
  1294. this._removeOperation = new Kekule.ChemObjOperation.Remove(target, parent, null, this.getEditor());
  1295. this._removeOperation.execute();
  1296. }
  1297. }
  1298. },
  1299. /** @private */
  1300. doReverse: function()
  1301. {
  1302. var target = this.getTarget();
  1303. var dest = this.getDest();
  1304. if (target && dest)
  1305. {
  1306. if (this._removeOperation)
  1307. {
  1308. this._removeOperation.reverse();
  1309. this._removeOperation = null;
  1310. }
  1311. var nodes = this.getMergedNodes();
  1312. var connectors = this.getMergedConnectors();
  1313. /*
  1314. console.log('before mol merge reverse dest', dest.getNodeCount(), dest.getConnectorCount());
  1315. console.log('before mol merge reverse target', target.getNodeCount(), target.getConnectorCount());
  1316. console.log('reverse mol merge', nodes.length, connectors.length);
  1317. */
  1318. this.moveChildBetweenStructFragment(dest, target, nodes, connectors);
  1319. /*
  1320. console.log('after mol merge reverse dest', dest.getNodeCount(), dest.getConnectorCount());
  1321. console.log('after mol merge reverse target', target.getNodeCount(), target.getConnectorCount());
  1322. */
  1323. }
  1324. }
  1325. });
  1326. /**
  1327. * Operation of split one unconnected structure fragment into multiple connected ones.
  1328. * @class
  1329. * @augments Kekule.ChemObjOperation.Base
  1330. *
  1331. * @param {Kekule.StructureFragment} target.
  1332. *
  1333. * @property {Kekule.StructureFragment} target Source fragment, all connectors and nodes will be moved to dest fragment.
  1334. * @property {Array} splittedFragments Fragment splitted, this property will be automatically calculated in execution of operation.
  1335. */
  1336. Kekule.ChemStructOperation.SplitStructFragment = Class.create(Kekule.ChemObjOperation.Base,
  1337. /** @lends Kekule.ChemStructOperation.SplitStructFragment# */
  1338. {
  1339. /** @private */
  1340. CLASS_NAME: 'Kekule.ChemStructOperation.SplitStructFragment',
  1341. /** @constructs */
  1342. initialize: function($super, target, editor)
  1343. {
  1344. $super(target, editor);
  1345. },
  1346. /** @private */
  1347. initProperties: function()
  1348. {
  1349. this.defineProp('splittedFragments', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': null});
  1350. },
  1351. /** @private */
  1352. doExecute: function()
  1353. {
  1354. var splitted = Kekule.ChemStructureUtils.splitStructFragment(this.getTarget());
  1355. if (splitted.length > 1) // do really split
  1356. {
  1357. this.setPropStoreFieldValue('splittedFragments', splitted);
  1358. var parent = this.getTarget().getParent();
  1359. var ref = this.getTarget().getNextSibling();
  1360. parent.beginUpdate();
  1361. try
  1362. {
  1363. if (parent) // insert newly splitted fragments
  1364. {
  1365. for (var i = 1, l = splitted.length; i < l; ++i)
  1366. {
  1367. var frag = splitted[i];
  1368. parent.insertBefore(frag, ref);
  1369. }
  1370. }
  1371. }
  1372. finally
  1373. {
  1374. parent.endUpdate();
  1375. }
  1376. }
  1377. else // no real split actions done
  1378. {
  1379. this.setPropStoreFieldValue('splittedFragments', null);
  1380. }
  1381. },
  1382. /** @private */
  1383. doReverse: function()
  1384. {
  1385. var fragments = this.getSplittedFragments();
  1386. if (fragments && fragments.length)
  1387. {
  1388. var target = this.getTarget();
  1389. for (var i = 0, l = fragments.length; i < l; ++i)
  1390. {
  1391. var frag = fragments[i];
  1392. if (frag !== target)
  1393. {
  1394. Kekule.ChemStructureUtils.moveChildBetweenStructFragment(frag, target, Kekule.ArrayUtils.clone(frag.getNodes()), Kekule.ArrayUtils.clone(frag.getConnectors()));
  1395. var p = frag.getParent();
  1396. if (p)
  1397. p.removeChild(frag);
  1398. frag.finalize();
  1399. }
  1400. }
  1401. }
  1402. //console.log('split reverse done', target.getNodeCount(), target.getConnectorCount());
  1403. }
  1404. });
  1405. /**
  1406. * Split one unconnected structure fragment into multiple connected ones, or remove the fragment
  1407. * if the fragment contains no node.
  1408. * @class
  1409. * @augments Kekule.ChemObjOperation.Base
  1410. *
  1411. * @param {Kekule.StructureFragment} target.
  1412. *
  1413. * @property {Kekule.StructureFragment} target Source fragment.
  1414. * @property {Bool} enableRemove Whether allow remove empty structure fragment. Default is true.
  1415. * @property {Bool} enableSplit Whether allow splitting structure fragment. Default is true.
  1416. */
  1417. Kekule.ChemStructOperation.StandardizeStructFragment = Class.create(Kekule.ChemObjOperation.Base,
  1418. /** @lends Kekule.ChemStructOperation.StandardizeStructFragment# */
  1419. {
  1420. /** @private */
  1421. CLASS_NAME: 'Kekule.ChemStructOperation.StandardizeStructFragment',
  1422. /** @constructs */
  1423. initialize: function($super, target, editor)
  1424. {
  1425. $super(target, editor);
  1426. this.setEnableSplit(true);
  1427. this.setEnableRemove(true);
  1428. this._concreteOper = null; // private
  1429. },
  1430. /** @private */
  1431. initProperties: function()
  1432. {
  1433. this.defineProp('enableRemove', {'dataType': DataType.BOOL});
  1434. this.defineProp('enableSplit', {'dataType': DataType.BOOL});
  1435. },
  1436. /** @private */
  1437. doExecute: function()
  1438. {
  1439. var target = this.getTarget();
  1440. var nodeCount = target.getNodeCount();
  1441. this._concreteOper = null;
  1442. var editor = this.getEditor();
  1443. if (nodeCount <= 0)
  1444. {
  1445. if (this.getEnableRemove())
  1446. this._concreteOper = new Kekule.ChemObjOperation.Remove(target, null, null, editor);
  1447. }
  1448. else
  1449. {
  1450. if (this.getEnableSplit())
  1451. this._concreteOper = new Kekule.ChemStructOperation.SplitStructFragment(target, editor);
  1452. }
  1453. if (this._concreteOper)
  1454. return this._concreteOper.execute();
  1455. else
  1456. return null;
  1457. },
  1458. /** @private */
  1459. doReverse: function()
  1460. {
  1461. return this._concreteOper? this._concreteOper.reverse(): null;
  1462. }
  1463. });
  1464. })();