UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,522 lines (1,470 loc) 114 kB
/** * @fileoverview * Implements of actions related with chem editor. * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /utils/kekule.utils.js * requires /io/kekule.io.js * requires /xbrowsers/kekule.x.js * requires /widgets/kekule.widget.base.js * requires /widgets/kekule.widget.clipboards.js * requires /widgets/chem/kekule.chemWidget.chemObjDisplayers.js * requires /widgets/chem/editor/kekule.chemEditor.baseEditors.js * requires /widgets/chem/editor/kekule.chemEditor.composers.js * requires /widgets/operation/kekule.actions.js * * requires /localization/kekule.localize.widget.js */ (function(){ "use strict"; var AU = Kekule.ArrayUtils; var BNS = Kekule.ChemWidget.ComponentWidgetNames; var CCNS = Kekule.ChemWidget.HtmlClassNames; //var CWT = Kekule.ChemWidgetTexts; var AM = Kekule.ActionManager; var _editorActionRegInfo = []; /** @ignore */ Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, { // predefined actions ACTION_UNDO: 'K-Chem-Undo', ACTION_REDO: 'K-Chem-Redo', ACTION_NEWDOC: 'K-Chem-NewDoc', ACTION_SELECT_ALL: 'K-Chem-SelectAll', ACTION_CLONE_SELECTION: 'K-Chem-Clone-Selection', ACTION_COPY: 'K-Chem-Copy', ACTION_CUT: 'K-Chem-Cut', ACTION_PASTE: 'K-Chem-Paste', ACTION_TOGGLE_SELECT: 'K-Chem-Toggle-Select-State', ACTION_ERASE_SELECTION: 'K-Chem-Erase-Selection', ACTION_RECHECK_ISSUES: 'K-Chem-Recheck-Issues', ACTION_TOGGLE_OBJ_INSPECTOR: 'K-Chem-Toggle-ObjInspector', ACTION_TOGGLE_ISSUE_INSPECTOR: 'K-Chem-Toggle-IssueInspector', ACTION_TOGGLE_SHOW_ISSUES: 'K-Chem-Toggle-ShowIssues', }); Object.extend(Kekule.ChemWidget.ComponentWidgetNames, { manipulateMarquee: 'manipulateMarquee', manipulateLasso: 'manipulateLasso', manipulateBrush: 'manipulateBrush', manipulateAncestor: 'manipulateAncestor', molBondSingle: 'bondSingle', molBondDouble: 'bondDouble', molBondTriple: 'bondTriple', molBondCloser: 'bondCloser', molBondWedgeUp: 'bondWedgeUp', molBondWedgeDown: 'bondWedgeDown', molBondWedgeUpOrDown: 'bondWedgeUpOrDown', molBondDoubleEither: 'bondDoubleEither', molChargeClear: 'chargeClear', molChargePositive: 'chargePositive', molChargeNegative: 'chargeNegative', molElectronicBiasPositive: 'electronicBiasPositive', molElectronicBiasNegative: 'electronicBiasNegative', molRadicalSinglet: 'radicalSinglet', molRadicalTriplet: 'radicalTriplet', molRadicalDoublet: 'radicalDoublet', molElectronLonePair: 'electronLonePair', molChain: 'chain', molRing3: 'ring3', molRing4: 'ring4', molRing5: 'ring5', molRing6: 'ring6', molRing7: 'ring7', molRing8: 'ring8', molRingAr6: 'ringAr6', molRingAr5: 'ringAr5', molFlexRing: 'flexRing', molRepCyclopentaneHaworth1: 'repCyclopentaneHaworth1', molRepCyclopentaneHaworth2: 'repCyclopentaneHaworth2', molRepCyclohexaneHaworth1: 'repCyclohexaneHaworth1', molRepCyclohexaneHaworth2: 'repCyclohexaneHaworth2', molRepCyclohexaneChair1: 'repCyclohexaneChair1', molRepCyclohexaneChair2: 'repCyclohexaneChair2', molRepSubBondMark: 'subBondMark', molRepMethane: 'methane', molRepFischer1: 'repFischer1', molRepFischer2: 'repFischer2', molRepFischer3: 'repFischer3', molRepSawhorseStaggered: 'repSawhorseStaggered', molRepSawhorseEclipsed: 'repSawhorseEclipsed', glyphRepLine: 'repLine', glyphRepOpenArrowLine: 'repOpenArrowLine', glyphRepTriangleArrowLine: 'repTriangleArrowLine', glyphRepDiOpenArrowLine: 'repDiOpenArrowLine', glyphRepDiTriangleArrowLine: 'repDiTriangleArrowLine', glyphRepReversibleArrowLine: 'repReversibleArrowLine', glyphRepOpenArrowDiLine: 'repOpenArrowDiLine', glyphRepOpenArrowArc: 'repOpenArrowArc', glyphRepSingleSideOpenArrowArc: 'repSingleSideOpenArrowArc', glyphRepHeatSymbol: 'repHeatSymbol', glyphRepAddSymbol: 'repAddSymbol', glyphElectronPushingArrow: 'repElectronPushingArrow', glyphElectronPushingArrowDouble: 'repElectronPushingArrowDouble', glyphElectronPushingArrowSingle: 'repElectronPushingArrowSingle', glyphElectronPushingArrowBondForming: 'repElectronPushingArrowBondForming', glyphRepSegment: 'repGlyphSegment', glyphReactionArrowNormal: 'repGlyphReactionArrowNormal', glyphReactionArrowReversible: 'glyphReactionArrowReversible', glyphReactionArrowResonance: 'glyphReactionArrowResonance', glyphReactionArrowRetrosynthesis: 'glyphReactionArrowRetrosynthesis' }); /** * A helper class for editor actions. * @class */ Kekule.Editor.ActionOperUtils = { /** @private */ getObjsCenterScreenCoord: function(editor, objects) { var BU = Kekule.BoxUtils; var CU = Kekule.CoordUtils; var containerBox = null; for (var i = 0, l = objects.length; i < l; ++i) { var box = Kekule.Render.ObjUtils.getContainerBox(objects[i], editor.getCoordMode(), editor.getAllowCoordBorrow()); if (!containerBox) containerBox = box; else containerBox = BU.getContainerBox(box, containerBox); } //var centerCoord = BU.getCenterCoord(containerBox); var coords = BU.getMinMaxCoords(containerBox); var screenCoords = { 'min': editor.objCoordToScreen(coords.min), 'max': editor.objCoordToScreen(coords.max) }; var result = CU.add(screenCoords.min, screenCoords.max); var result = CU.divide(result, 2); //return editor.objCoordToScreen(centerCoord); return result; }, /** * Add standalone objects to a chem space editor, with operation support. * @param {Kekule.Editor.ChemSpaceEditor} editor * @param {Array} objs * @param {Hash} options May including fields: * { * screenCoordOffset: Coords of new added objects will be added with this value. * autoAdjustPosition: Whether the newly added object will be put at the center of editor screen. * This option takes no effect when screenCoordOffset is true. * autoSelect: Whether automatically select the newly added objects. * } */ addObjectsToChemSpaceEditor: function(editor, objs, options) { var ops = Object.extend({autoAdjustPosition: true}, options); // default options var _getAppendableObjs = function(srcObj, rootSpace) { var result = []; var rootObj = rootSpace; if (rootObj && srcObj) { if (srcObj.getClass() === rootObj.getClass() || srcObj instanceof Kekule.ChemSpace) // class is same (chemspace) { result = AU.clone(srcObj.getChildren()); } else result = [srcObj]; } return result; }; var chemSpace = editor.getChemSpace && editor.getChemSpace(); if (editor && chemSpace && editor.canAddNewStandaloneObject && editor.canAddNewStandaloneObject()) { var actualObjs = []; for (var i = 0, l = objs.length; i < l; ++i) { var appendableObjs = _getAppendableObjs(objs[i], chemSpace); if (appendableObjs && appendableObjs.length) AU.pushUnique(actualObjs, appendableObjs); } //editor.beginUpdateObject(); editor.beginManipulateAndUpdateObject(); try { var marcoOper = new Kekule.MacroOperation(); for (var i = 0, l = actualObjs.length; i < l; ++i) { var obj = actualObjs[i]; var oper = new Kekule.ChemObjOperation.Add(obj, chemSpace, null, editor); marcoOper.add(oper); } marcoOper.execute(); var screenCoordOffset = ops.screenCoordOffset; if (!screenCoordOffset && ops.autoAdjustPosition) // auto adjust position { //var originCenterCoord = this.getObjsCenterScreenCoord(editor, originalSelectedObjs); var editorClientRect = editor.getClientVisibleRect(); var editorCenterScreenCoord = { 'x': editorClientRect.left + editorClientRect.width / 2, 'y': editorClientRect.top + editorClientRect.height / 2 }; var targetCenterCoord = this.getObjsCenterScreenCoord(editor, actualObjs); var deltaCoord = Kekule.CoordUtils.substract(editorCenterScreenCoord, targetCenterCoord); screenCoordOffset = deltaCoord; } if (screenCoordOffset) { for (var i = 0, l = actualObjs.length; i < l; ++i) { var obj = actualObjs[i]; var coord = editor.getObjectScreenCoord(obj); var newCoord = Kekule.CoordUtils.add(coord, screenCoordOffset); editor.setObjectScreenCoord(obj, newCoord); } } editor.pushOperation(marcoOper); if (ops.autoSelect) editor.select(actualObjs); } finally { //editor.endUpdateObject(); editor.endManipulateAndUpdateObject(); } } } }; /** * Base class for actions for chem editor only. * @class * @augments Kekule.ChemWidget.ActionOnDisplayer * * @param {Kekule.Editor.BaseEditor} editor Target editor object. * @param {String} caption * @param {String} hint * @param {String} explicitGroup Use this property to explicitly set child actions to different group. */ Kekule.Editor.ActionOnEditor = Class.create(Kekule.ChemWidget.ActionOnDisplayer, /** @lends Kekule.Editor.ActionOnEditor# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionOnEditor', /** @constructs */ initialize: function(/*$super, */editor, caption, hint) { this.tryApplySuper('initialize', [editor, caption, hint]) /* $super(editor, caption, hint) */; }, /** @private */ initProperties: function() { this.defineProp('explicitGroup', {'dataType': DataType.STRING}); }, /** * Returns the widget class that best fit this action. * Descendants may override this method. * @returns {null} */ getPreferredWidgetClass: function() { return null; }, /** @private */ doUpdate: function() { var displayer = this.getDisplayer(); this.setEnabled(displayer && displayer.getChemObj() && displayer.getChemObjLoaded() && displayer.getEnabled()); }, /** * Returns target chem editor object. * @returns {Kekule.Editor.BaseEditor} */ getEditor: function() { var result = this.getDisplayer(); return (result instanceof Kekule.Editor.BaseEditor)? result: null; } }); /** * An undo action on editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionEditorUndo = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionEditorUndo# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionEditorUndo', /** @private */ HTML_CLASSNAME: CCNS.ACTION_UNDO, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, /*CWT.CAPTION_UNDO, CWT.HINT_UNDO*/Kekule.$L('ChemWidgetTexts.CAPTION_UNDO'), Kekule.$L('ChemWidgetTexts.HINT_UNDO')]) /* $super(editor, \*CWT.CAPTION_UNDO, CWT.HINT_UNDO*\Kekule.$L('ChemWidgetTexts.CAPTION_UNDO'), Kekule.$L('ChemWidgetTexts.HINT_UNDO')) */; }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(this.getEditor().getEnableOperHistory() && this.getEditor().canUndo()); }, /** @private */ doExecute: function() { var editor = this.getEditor(); if (editor) editor.undo(); } }); /** * A redo action on editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionEditorRedo = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionEditorRedo# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionEditorRedo', /** @private */ HTML_CLASSNAME: CCNS.ACTION_REDO, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, /*CWT.CAPTION_REDO, CWT.HINT_REDO*/Kekule.$L('ChemWidgetTexts.CAPTION_REDO'), Kekule.$L('ChemWidgetTexts.HINT_REDO')]) /* $super(editor, \*CWT.CAPTION_REDO, CWT.HINT_REDO*\Kekule.$L('ChemWidgetTexts.CAPTION_REDO'), Kekule.$L('ChemWidgetTexts.HINT_REDO')) */; }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(this.getEditor().getEnableOperHistory() && this.getEditor().canRedo()); }, /** @private */ doExecute: function() { var editor = this.getEditor(); if (editor) editor.redo(); } }); /** * A new document action on editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionEditorNewDoc = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionEditorNewDoc# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionEditorNewDoc', /** @private */ HTML_CLASSNAME: CCNS.ACTION_NEWDOC, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, /*CWT.CAPTION_NEWDOC, CWT.HINT_NEWDOC*/Kekule.$L('ChemWidgetTexts.CAPTION_NEWDOC'), Kekule.$L('ChemWidgetTexts.HINT_NEWDOC')]) /* $super(editor, \*CWT.CAPTION_NEWDOC, CWT.HINT_NEWDOC*\Kekule.$L('ChemWidgetTexts.CAPTION_NEWDOC'), Kekule.$L('ChemWidgetTexts.HINT_NEWDOC')) */; }, /** @private */ doUpdate: function() { var editor = this.getDisplayer(); this.setEnabled(editor && editor.getEnabled() && editor.getEnableCreateNewDoc()); }, /** @private */ doExecute: function() { var editor = this.getEditor(); if (editor) editor.newDoc(); } }); /** * Action for loading or appending new data into editor. * @class * @augments Kekule.ChemWidget.ActionDisplayerLoadData * * @property {Bool} enableAppend Whether appending data into editor is enabled. */ Kekule.Editor.ActionEditorLoadData = Class.create(Kekule.ChemWidget.ActionDisplayerLoadData, /** @lends Kekule.Editor.ActionEditorLoadData# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionEditorLoadData', /* @private */ // HTML_CLASSNAME: CCNS.ACTION_LOADFILE, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor]) /* $super(editor) */; }, /** @private */ initProperties: function() { this.defineProp('enableAppend', {'dataType': DataType.BOOL}); }, /** @private */ doExecute: function(/*$super, */target) { var dialog = this.getDataDialog(); if (dialog && dialog.setDisplayAppendCheckBox) dialog.setDisplayAppendCheckBox(this._isEditorRootObjAppendable() && !this._isEditorEmpty()); return this.tryApplySuper('doExecute', [target]) /* $super(target) */; }, /** @private */ _getEditorRootObj: function() { var editor = this.getDisplayer(); var rootObj = editor.getChemObj(); return rootObj; }, /** @private */ _isEditorRootObjAppendable: function() { // check if the root object of editor can append child var editor = this.getDisplayer(); var rootObj = this._getEditorRootObj(); return rootObj && (rootObj instanceof Kekule.ChemSpace) && (editor.canAddNewStandaloneObject && editor.canAddNewStandaloneObject()) && (editor.getAllowAppendDataToCurr && editor.getAllowAppendDataToCurr()); }, /** @private */ _isEditorEmpty: function() { var rootObj = this._getEditorRootObj(); return (!rootObj || (rootObj.getChildCount && rootObj.getChildCount() === 0)); }, /* @private */ /* _getAppendableObjs: function(srcObj) { var result = []; var rootObj = this._getEditorRootObj(); if (rootObj && srcObj) { if (srcObj.getClass() === rootObj.getClass() || srcObj instanceof Kekule.ChemSpace) // class is same (chemspace) { result = AU.clone(srcObj.getChildren()); } else result = [srcObj]; } return result; }, */ /** @private */ createDataDialog: function() { var doc = this.getDisplayer().getDocument(); var result = new Kekule.ChemWidget.LoadOrAppendDataDialog(doc); return result; }, /** @ignore */ doLoadInDisplayer: function(displayer, dataDetails, dialog) { var editor = displayer || this.getDisplayer(); var isAppending = dialog.getIsAppending(); //console.log('is appending', isAppending); if (isAppending && !this._isEditorEmpty() && this._isEditorRootObjAppendable()) { var rootObj = this._getEditorRootObj(); //var appendableObjs = this._getAppendableObjs(chemObj); //Kekule.Editor.ActionOperUtils.addObjectsToChemSpaceEditor(editor, appendableObjs); editor.loadFromData(dataDetails.data, dataDetails.mimeType, dataDetails.fileName, dataDetails.formatId, function(chemObj){ if (chemObj) { editor.beginUpdateObject(); try { rootObj.beginUpdate(); try { Kekule.Editor.ActionOperUtils.addObjectsToChemSpaceEditor(editor, [chemObj], {'autoSelect': true}); } finally { rootObj.endUpdate(); } } finally { editor.endUpdateObject(); } } }); } else //return this.tryApplySuper('doLoadToDisplayer', [chemObj, dialog]); return this.tryApplySuper('doLoadInDisplayer', [displayer, dataDetails, dialog]); } /* @ignore */ /* doLoadToDisplayer: function(chemObj, dialog) { var editor = this.getDisplayer(); var isAppending = dialog.getIsAppending(); console.log('is appending', isAppending); if (isAppending && !this._isEditorEmpty() && this._isEditorRootObjAppendable()) { editor.beginUpdateObject(); try { var rootObj = this._getEditorRootObj(); rootObj.beginUpdate(); try { //var appendableObjs = this._getAppendableObjs(chemObj); //Kekule.Editor.ActionOperUtils.addObjectsToChemSpaceEditor(editor, appendableObjs); Kekule.Editor.ActionOperUtils.addObjectsToChemSpaceEditor(editor, [chemObj], {'autoSelect': true}); } finally { rootObj.endUpdate(); } } finally { editor.endUpdateObject(); } } else return this.tryApplySuper('doLoadToDisplayer', [chemObj, dialog]); } */ }); /** * A select-all action for editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionSelectAll = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionSelectAll# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionSelectAll', /** @private */ HTML_CLASSNAME: CCNS.ACTION_SELECT_ALL, /** @constructs */ initialize: function(editor) { this.tryApplySuper('initialize', [editor, Kekule.$L('ChemWidgetTexts.CAPTION_SELECT_ALL'), Kekule.$L('ChemWidgetTexts.HINT_SELECT_ALL')]); }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(this.getEditor().getChemObj()); }, /** @private */ doExecute: function() { this.getEditor().selectAll(); } }); /** * A clone selection action on editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionCloneSelection = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionCloneSelection# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionCloneSelection', /** @private */ HTML_CLASSNAME: CCNS.ACTION_CLONE_SELECTION, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, /*CWT.CAPTION_CLONE_SELECTION, CWT.HINT_CLONE_SELECTION*/Kekule.$L('ChemWidgetTexts.CAPTION_CLONE_SELECTION'), Kekule.$L('ChemWidgetTexts.HINT_CLONE_SELECTION')]) /* $super(editor, \*CWT.CAPTION_CLONE_SELECTION, CWT.HINT_CLONE_SELECTION*\Kekule.$L('ChemWidgetTexts.CAPTION_CLONE_SELECTION'), Kekule.$L('ChemWidgetTexts.HINT_CLONE_SELECTION')) */; }, /** @private */ _hasCloneMethod: function() { var editor = this.getEditor(); return editor && editor.cloneSelection; }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(this._hasCloneMethod() && this.getEditor().hasSelection()); this.setDisplayed(this._hasCloneMethod() && this.getEditor().canCloneObjects()); }, /** @private */ doExecute: function() { var editor = this.getEditor(); if (editor && editor.getChemSpace) { var coordOffset = editor.getDefaultCloneScreenCoordOffset && editor.getDefaultCloneScreenCoordOffset(); var objs = editor.cloneSelection(); Kekule.Editor.ActionOperUtils.addObjectsToChemSpaceEditor(editor, objs, { 'screenCoordOffset': coordOffset, 'autoSelect': true }); } /* var chemSpace = editor.getChemSpace && editor.getChemSpace(); if (editor && chemSpace) { var coordOffset = editor.getDefaultCloneScreenCoordOffset(); editor.beginUpdateObject(); try { var objs = editor.cloneSelection(); var marcoOper = new Kekule.MacroOperation(); for (var i = 0, l = objs.length; i < l; ++i) { var obj = objs[i]; var oper = new Kekule.ChemObjOperation.Add(objs[i], chemSpace); marcoOper.add(oper); } marcoOper.execute(); if (coordOffset) { for (var i = 0, l = objs.length; i < l; ++i) { var obj = objs[i]; var coord = editor.getObjectScreenCoord(obj); var newCoord = Kekule.CoordUtils.add(coord, coordOffset); editor.setObjectScreenCoord(obj, newCoord); } } editor.pushOperation(marcoOper); editor.select(objs); } finally { editor.endUpdateObject(); } } */ } }); /** * A copying selection action on editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionCopySelection = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionCopySelection# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionCopySelection', /** @private */ HTML_CLASSNAME: CCNS.ACTION_COPY, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, /*CWT.CAPTION_COPY, CWT.HINT_COPY*/Kekule.$L('ChemWidgetTexts.CAPTION_COPY'), Kekule.$L('ChemWidgetTexts.HINT_COPY')]) /* $super(editor, \*CWT.CAPTION_COPY, CWT.HINT_COPY*\Kekule.$L('ChemWidgetTexts.CAPTION_COPY'), Kekule.$L('ChemWidgetTexts.HINT_COPY')) */; }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(this.getEditor().hasSelection()); }, /** @private */ doExecute: function() { var editor = this.getEditor(); var chemSpace = editor.getChemSpace && editor.getChemSpace(); if (editor && chemSpace) { var objs = editor.cloneSelection(); /* Kekule.Widget.clipboard.setObjects(Kekule.IO.MimeType.JSON, objs); //console.log(Kekule.Widget.Clipboard.getData('text/json')); */ var space = new Kekule.IntermediateChemSpace(); try { space.appendChildren(objs); // use a space to keep all objs, to keep the relations Kekule.Widget.clipboard.setObjects(Kekule.IO.MimeType.JSON, [space]); } finally { space.finalize(); } } } }); /** * A cutting selection action on editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionCutSelection = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionCutSelection# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionCutSelection', /** @private */ HTML_CLASSNAME: CCNS.ACTION_CUT, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, /*CWT.CAPTION_CUT, CWT.HINT_CUT*/Kekule.$L('ChemWidgetTexts.CAPTION_CUT'), Kekule.$L('ChemWidgetTexts.HINT_CUT')]) /* $super(editor, \*CWT.CAPTION_CUT, CWT.HINT_CUT*\Kekule.$L('ChemWidgetTexts.CAPTION_CUT'), Kekule.$L('ChemWidgetTexts.HINT_CUT')) */; }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(this.getEditor().hasSelection()); }, /** @private */ doExecute: function() { var editor = this.getEditor(); var chemSpace = editor.getChemSpace && editor.getChemSpace(); if (editor && chemSpace) { var objs = editor.cloneSelection(); //Kekule.Widget.clipboard.setObjects(Kekule.IO.MimeType.JSON, objs); var space = new Kekule.IntermediateChemSpace(); try { space.appendChildren(objs); // use a space to keep all objs, to keep the relations Kekule.Widget.clipboard.setObjects(Kekule.IO.MimeType.JSON, [space]); } finally { space.finalize(); } // TODO: this is not a good approach var controller = editor.getIaController('BasicMolEraserIaController'); if (controller) controller.removeSelection(); } } }); /** * A copying selection action on editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionPaste = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionPaste# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionPaste', /** @private */ HTML_CLASSNAME: CCNS.ACTION_PASTE, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, /*CWT.CAPTION_PASTE, CWT.HINT_PASTE*/Kekule.$L('ChemWidgetTexts.CAPTION_PASTE'), Kekule.$L('ChemWidgetTexts.HINT_PASTE')]) /* $super(editor, \*CWT.CAPTION_PASTE, CWT.HINT_PASTE*\Kekule.$L('ChemWidgetTexts.CAPTION_PASTE'), Kekule.$L('ChemWidgetTexts.HINT_PASTE')) */; Kekule.Widget.clipboard.addEventListener('setData', this._reactClipboardChange, this); }, /** @ignore */ finalize: function(/*$super*/) { Kekule.Widget.clipboard.removeEventListener('setData', this._reactClipboardChange, this); this.tryApplySuper('finalize') /* $super() */; }, /** @private */ _reactClipboardChange: function() { this.update(); }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(Kekule.Widget.clipboard.hasData(Kekule.IO.MimeType.JSON) && this.getEditor().canAddNewStandaloneObject()); }, /** @private */ getObjsCenterScreenCoord: function(editor, objects) { /* var BU = Kekule.BoxUtils; var CU = Kekule.CoordUtils; var containerBox = null; for (var i = 0, l = objects.length; i < l; ++i) { var box = Kekule.Render.ObjUtils.getContainerBox(objects[i], editor.getCoordMode(), editor.getAllowCoordBorrow()); if (!containerBox) containerBox = box; else containerBox = BU.getContainerBox(box, containerBox); } //var centerCoord = BU.getCenterCoord(containerBox); var coords = BU.getMinMaxCoords(containerBox); var screenCoords = { 'min': editor.objCoordToScreen(coords.min), 'max': editor.objCoordToScreen(coords.max) }; var result = CU.add(screenCoords.min, screenCoords.max); var result = CU.divide(result, 2); //return editor.objCoordToScreen(centerCoord); return result; */ return Kekule.Editor.ActionOperUtils.getObjsCenterScreenCoord(editor, objects); }, /** @private */ doExecute: function() { var editor = this.getEditor(); if (editor && editor.getChemSpace) { //var objs = Kekule.Widget.clipboard.getObjects(Kekule.IO.MimeType.JSON); var space, objs; var clipboardObjs = Kekule.Widget.clipboard.getObjects(Kekule.IO.MimeType.JSON); if (clipboardObjs.length === 1 && clipboardObjs[0] instanceof Kekule.IntermediateChemSpace) { space = clipboardObjs[0]; objs = AU.clone(space.getChildren()); // remove objs from space first space.removeChildren(objs); } else objs = clipboardObjs; if (space) space.finalize(); // calc coord offset var coordOffset = null; var selObjs = editor.getSelection(); if (selObjs && selObjs.length) { coordOffset = editor.getDefaultCloneScreenCoordOffset? editor.getDefaultCloneScreenCoordOffset(): null; var originalSelectedObjs = AU.clone(selObjs); if (originalSelectedObjs && originalSelectedObjs.length && coordOffset) { var originCenterCoord = this.getObjsCenterScreenCoord(editor, originalSelectedObjs); var targetCenterCoord = this.getObjsCenterScreenCoord(editor, objs); var deltaCoord = Kekule.CoordUtils.substract(originCenterCoord, targetCenterCoord); coordOffset = Kekule.CoordUtils.add(coordOffset, deltaCoord); } } Kekule.Editor.ActionOperUtils.addObjectsToChemSpaceEditor(editor, objs, { 'screenCoordOffset': coordOffset, 'autoSelect': true }); } /* var chemSpace = editor.getChemSpace && editor.getChemSpace(); if (editor && chemSpace) { var objs = Kekule.Widget.Clipboard.getObjects('text/json'); editor.beginUpdateObject(); try { var marcoOper = new Kekule.MacroOperation(); for (var i = 0, l = objs.length; i < l; ++i) { var obj = objs[i]; var oper = new Kekule.ChemObjOperation.Add(objs[i], chemSpace); marcoOper.add(oper); } marcoOper.execute(); var coordOffset = editor.getDefaultCloneScreenCoordOffset(); if (coordOffset) { for (var i = 0, l = objs.length; i < l; ++i) { var obj = objs[i]; var coord = editor.getObjectScreenCoord(obj); var newCoord = Kekule.CoordUtils.add(coord, coordOffset); editor.setObjectScreenCoord(obj, newCoord); } } editor.pushOperation(marcoOper); editor.select(objs); } finally { editor.endUpdateObject(); } } */ } }); /** * Set isToggleSelectionOn property to editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionToggleSelectState = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionToggleSelectState# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionToggleSelectState', /** @private */ HTML_CLASSNAME: CCNS.ACTION_TOGGLE_SELECT, /** @constructs */ initialize: function(/*$super, */editor) { this.tryApplySuper('initialize', [editor, Kekule.$L('ChemWidgetTexts.CAPTION_TOGGLE_SELECT'), Kekule.$L('ChemWidgetTexts.HINT_TOGGLE_SELECT')]) /* $super(editor, Kekule.$L('ChemWidgetTexts.CAPTION_TOGGLE_SELECT'), Kekule.$L('ChemWidgetTexts.HINT_TOGGLE_SELECT')) */; this.setExplicitGroup(''); // force no check group }, /** @ignore */ getPreferredWidgetClass: function() { return Kekule.Widget.CheckButton; }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; this.setChecked(this.getEditor().getIsToggleSelectOn()); }, /** @ignore */ checkedChanged: function(/*$super*/) { this.tryApplySuper('checkedChanged') /* $super() */; }, /** @ignore */ doExecute: function(/*$super, */target, htmlEvent) { this.tryApplySuper('doExecute', [target, htmlEvent]) /* $super(target, htmlEvent) */; var oldChecked = this.getChecked(); var editor = this.getEditor(); editor.setIsToggleSelectOn(!oldChecked); this.setChecked(!oldChecked); } }); /** * A simple action to delete all selected objects in editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionEraseSelection = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionEraseSelection# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionEraseSelection', /** @private */ HTML_CLASSNAME: CCNS.ACTION_ERASE_SELECTION, /** @constructs */ initialize: function(editor) { this.tryApplySuper('initialize', [editor, Kekule.$L('ChemWidgetTexts.CAPTION_ERASE_SELECTION'), Kekule.$L('ChemWidgetTexts.HINT_ERASE_SELECTION')]); }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; if (this.getEnabled()) this.setEnabled(this.getEditor().hasSelection()); }, /** @private */ doExecute: function() { var editor = this.getEditor(); if (editor) { // TODO: this is not a good approach, need to refactor it later var controller = editor.getIaController('BasicMolEraserIaController'); if (controller) controller.removeSelection(); } } }); /** * Recheck issues for chem objects in editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionRecheckIssues = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionRecheckIssues# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionRecheckIssues', /** @private */ HTML_CLASSNAME: CCNS.ACTION_RECHECK_ISSUES, /** @constructs */ initialize: function(editor) { this.tryApplySuper('initialize', [editor, Kekule.$L('ChemWidgetTexts.CAPTION_RECHECK_ISSUES'), Kekule.$L('ChemWidgetTexts.HINT_RECHECK_ISSUES')]); }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; var editor = this.getEditor(); this.setEnabled(editor.getEnableIssueCheck()).setDisplayed(editor.getEnableIssueCheck()); }, /** @ignore */ doExecute: function(target, htmlEvent) { this.tryApplySuper('doExecute', [target, htmlEvent]) /* $super(target, htmlEvent) */; var editor = this.getEditor(); if (editor) editor.checkIssues(); } }); /** * Set {@link Kekule.Editor.BaseEditor.showAllIssueMarkers} property of editor. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionToggleShowIssueMarkers = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionToggleShowIssueMarkers# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionToggleShowIssueMarkers', /** @private */ HTML_CLASSNAME: CCNS.ACTION_TOGGLE_SHOW_ISSUES, /** @constructs */ initialize: function(editor) { this.tryApplySuper('initialize', [editor, Kekule.$L('ChemWidgetTexts.CAPTION_TOGGLE_SHOW_ISSUE_MARKERS'), Kekule.$L('ChemWidgetTexts.HINT_TOGGLE_SHOW_ISSUE_MARKERS')]); this.setExplicitGroup(''); // force no check group }, /** @ignore */ getPreferredWidgetClass: function() { return Kekule.Widget.CheckButton; }, /** @private */ doUpdate: function(/*$super*/) { this.tryApplySuper('doUpdate') /* $super() */; var editor = this.getEditor(); this.setChecked(editor.getShowAllIssueMarkers()); this.setEnabled(editor.getEnableIssueCheck()).setDisplayed(editor.getEnableIssueCheck()); }, /** @ignore */ doExecute: function(target, htmlEvent) { this.tryApplySuper('doExecute', [target, htmlEvent]) /* $super(target, htmlEvent) */; var oldChecked = this.getChecked(); var editor = this.getEditor(); editor.setShowAllIssueMarkers(!oldChecked); this.setChecked(!oldChecked); } }); /** * Namespace of all operation creation actions for editor. * @namespace */ Kekule.Editor.ActionOperationCreate = {}; /** * Base operation creation action for editor. * This type of action is a special action, rather then run execute() directly, * it's main propers is to create one or multiple operations that need to be performed by the editor. * Usually, this type of actions should not be bound to UI directly. * @class * @augments Kekule.Editor.ActionOnEditor * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionOperationCreate.Base = Class.create(Kekule.Editor.ActionOnEditor, /** @lends Kekule.Editor.ActionOperationCreate.Base# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionOperationCreate.Base', /** @constructs */ initialize: function(editor) { this.tryApplySuper('initialize', [editor]); }, /** * Check whether this action can be applied to editor. * Descendants should override this method. * @param {Kekule.Editor.BaseEditor} editor */ applicable: function(editor) { var targets = this.getOperationTargets(); if (targets && targets.length) { var data = this.getData(); for (var i = 0, l = targets.length; i < l; ++i) { if (this.applicableOnTarget(targets[i], data, editor)) { return true; } } return false; } return false; }, /** * Check if an operation can be created on target object in editor. * Descendants should override this method. * @param {Object} target * @param {Hash} data * @param {Kekule.Editor.BaseEditor} editor * @returns {Bool} */ applicableOnTarget: function(target, data, editor) { return false; }, /** * Returns objects in editor that act as operation targets. * Descendants should override this method. * @param {Kekule.Editor.BaseEditor} editor * @returns {Array} */ getOperationTargets: function(editor) { return []; }, /** * Returns the associated data to run the action. * @returns {Hash} * @private */ getData: function() { return this.ACTION_DATA || {}; }, /** * If action can be applied to target chem objects, this method create the concrete operation. * @param {Kekule.Editor.BaseEditor} editor * @returns {Kekule.Operation} * @private */ createOperations: function(editor) { var chemObjs = this.getOperationTargets(editor); var data = this.getData(); var opers = []; for (var i = 0, l = chemObjs.length; i < l; ++i) { var target = chemObjs[i]; if (this.applicableOnTarget(target, data, editor)) { var oper = this.doCreateOperationOnTarget(target, data, editor); if (oper) { opers.push(oper); } } } return opers; }, /** * Do concrete work of creating operation on one target. * Descendants should override this method. * @param {Object} target * @param {Hash} data Data of the modification. * @param {Kekule.Editor.BaseEditor} editor * @returns {Kekule.Operation} * @private */ doCreateOperationOnTarget: function(target, data, editor) { return null; }, /** @private */ doExecute: function() { var editor = this.getEditor(); var targets = this.getModificationTargets(editor); var opers = this.createOperations(targets, data, editor); if (opers && opers.length) { editor.execOperations(opers); return true; // indicating something actually be done } else return false; } }); /** * Base operation creation action for editor. * This type of action is a special action, rather then run execute() directly, * it's main propers is to create one or multiple operations that need to be performed by the editor. * Usually, this type of actions should not be bound to UI directly. * @class * @augments Kekule.Editor.ActionOperationCreate.Base * * @param {Kekule.Editor.BaseEditor} editor Target editor object. */ Kekule.Editor.ActionOperationCreate.ChemObjModify = Class.create(Kekule.Editor.ActionOperationCreate.Base, /** @lends Kekule.Editor.ActionOperationCreate.ChemObjModify# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionOperationCreate.ChemObjModify', /** @ignore */ getOperationTargets: function(editor) { var result = editor.getHotTrackedObjs(); // first try to apply modification to hot trackeed object if (!result || !result.length) // then the selection { result = editor.getSelection(); } return result; }, }); /** * Modify or replace a chem node in editor. * @class * @augments Kekule.Editor.ActionOperationCreate.ChemObjModify */ Kekule.Editor.ActionOperationCreate.ChemNodeModify = Class.create(Kekule.Editor.ActionOperationCreate.ChemObjModify, /** @lends Kekule.Editor.ActionOperationCreate.ChemNodeModify# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionOperationCreate.ChemNodeModify', /** @ignore */ applicableOnTarget: function(target, data, editor) { // at least there should be a bond connected to target, avoid modifier standalone molecule in editor return (target instanceof Kekule.ChemStructureNode) && !(target instanceof Kekule.Molecule) && (target.getLinkedBonds().length); }, /** @ignore */ doCreateOperationOnTarget: function(target, data, editor) { return Kekule.Editor.OperationUtils.createNodeModificationOperationFromData(target, data, editor); } }); /** * Modify or replace a chem connector in editor. * @class * @augments Kekule.Editor.ActionOperationCreate.ChemObjModify */ Kekule.Editor.ActionOperationCreate.ChemConnectorModify = Class.create(Kekule.Editor.ActionOperationCreate.ChemObjModify, /** @lends Kekule.Editor.ActionOperationCreate.ChemConnectorModify# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionOperationCreate.ChemConnectorModify', /** @ignore */ applicableOnTarget: function(target, data, editor) { return (target instanceof Kekule.ChemStructureConnector); }, /** @ignore */ doCreateOperationOnTarget: function(target, data, editor) { // data is simply a prop-value pair hash if (Kekule.ObjUtils.match(target, data)) // no actual modified props return null; else return new Kekule.ChemObjOperation.Modify(target, data, editor); } }); /** @ignore */ Kekule.Editor.createEditorOperationCreateActionClass = function(classShortName, actionRegName, superClass, actionData) { var definition = {}; if (classShortName) definition.CLASS_NAME = 'Kekule.Editor.ActionOperationCreate.' + classShortName; if (actionData) definition.ACTION_DATA = actionData; var result = Class.create(superClass, definition); if (actionRegName) { _editorActionRegInfo.push({'name': actionRegName, 'actionClass': result}); } return result; }; // create and register some default operation create action classes function _createAndRegisterNodeModifyActions() { var atomIsotopeIds = ['C', 'H', 'O', 'N', 'P', 'S', 'Si', 'F', 'Cl', 'Br', 'I', 'B', 'K', 'Na', 'D']; var repSubgroupNames = ['methyl', 'ethyl', 'phenyl', 'Ac', 'TMS', 'COOCH3', 'OTs']; var superClass = Kekule.Editor.ActionOperationCreate.ChemNodeModify; var create = Kekule.Editor.createEditorOperationCreateActionClass; for (var i = 0, l = atomIsotopeIds.length; i < l; ++i) { var actionId = 'atom_' + atomIsotopeIds[i]; create(null, actionId, superClass, {'nodeClass': Kekule.Atom, props: {'isotopeId': atomIsotopeIds[i]}}); } for (var i = 0, l = repSubgroupNames.length; i < l; ++i) { var repName = repSubgroupNames[i]; var actionId = 'subgroup_' + repName; var repItem = Kekule.Editor.RepositoryItemManager.getItem(repName); var structFragment = repItem.getStructureFragment && repItem.getStructureFragment(); if (structFragment) create(null, actionId, superClass, {'nodeClass': structFragment.getClass(), 'repositoryItem': repItem}); } // special nodes create(null, 'subgroup_R', superClass, {'nodeClass': Kekule.SubGroup}); create(null, 'atom_variable', superClass, {'nodeClass': Kekule.VariableAtom}); create(null, 'atom_dummy', superClass, {'nodeClass': Kekule.Pseudoatom, 'props': {'atomType': Kekule.PseudoatomType.DUMMY}}); create(null, 'atom_hetero', superClass, {'nodeClass': Kekule.Pseudoatom, 'props': {'atomType': Kekule.PseudoatomType.HETERO}}); create(null, 'atom_any', superClass, {'nodeClass': Kekule.Pseudoatom, 'props': {'atomType': Kekule.PseudoatomType.ANY}}); } function _createAndRegisterConnectorModifyActions() { var BT = Kekule.BondType; var BO = Kekule.BondOrder; var BS = Kekule.BondStereo; var bondMap = { 'single': {'bondType': BT.COVALENT, 'bondOrder': BO.SINGLE, 'stereo': BS.NONE}, 'double': {'bondType': BT.COVALENT, 'bondOrder': BO.DOUBLE, 'stereo': BS.NONE}, 'triple': {'bondType': BT.COVALENT, 'bondOrder': BO.TRIPLE, 'stereo': BS.NONE}, 'quad': {'bondType': BT.COVALENT, 'bondOrder': BO.QUAD, 'stereo': BS.NONE}, 'up': {'bondType': BT.COVALENT, 'bondOrder': BO.SINGLE, 'stereo': BS.UP}, 'down': {'bondType': BT.COVALENT, 'bondOrder': BO.SINGLE, 'stereo': BS.DOWN}, 'closer': {'bondType': BT.COVALENT, 'bondOrder': BO.SINGLE, 'stereo': BS.CLOSER}, 'upOrDown': {'bondType': BT.COVALENT, 'bondOrder': BO.SINGLE, 'stereo': BS.UP_OR_DOWN} }; var superClass = Kekule.Editor.ActionOperationCreate.ChemConnectorModify; var create = Kekule.Editor.createEditorOperationCreateActionClass; var names = Kekule.ObjUtils.getOwnedFieldNames(bondMap); for (var i = 0, l = names.length; i < l; ++i) { create(null, 'bond_' + names[i], superClass, bondMap[names[i]]); } } function _createAndRegisterPredefinedOperationCreateActions() { _createAndRegisterNodeModifyActions(); _createAndRegisterConnectorModifyActions(); } /** * Base class for actions for chem composer. * @class * @augments Kekule.Action * * @param {Kekule.Editor.Composer} composer Target composer widget. * @param {String} caption * @param {String} hint */ Kekule.Editor.ActionOnComposer = Class.create(Kekule.Action, /** @lends Kekule.Editor.ActionOnComposer# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionOnComposer', /** @constructs */ initialize: function(/*$super, */composer, caption, hint) { this.tryApplySuper('initialize') /* $super() */; this.setText(caption); this.setHint(hint); this.setComposer(composer); }, /** @private */ initProperties: function() { this.defineProp('composer', {'dataType': 'Kekule.Editor.Composer', 'serializable': false}); }, /** @private */ doUpdate: function() { var composer = this.getComposer(); var editor = this.getEditor(); this.setEnabled(composer && composer.getEnabled() && editor && editor.getChemObj() && editor.getChemObjLoaded() && editor.getEnabled()); }, /** * Returns chem editor object inside composer. * @returns {Kekule.Editor.BaseEditor} */ getEditor: function() { var composer = this.getComposer(); return composer? composer.getEditor(): null; } }); /** * Actions that has a series of child attached actions on composer (such as bond action * may has single, double, triple bond variations). * @class * @augments Kekule.Editor.ActionOnComposer * * @property {Kekule.ActionList} attachedActions * @property {Kekule.Action} defaultAttachedAction If this property is set, when check on the parent action, * if no attached action is checked, default one will be checked on automatically. * @property {Class} defaultAttachedActionClass If this property is set, when creating child attached actions, * {@link Kekule.Editor.ActionOnComposerAdv.defaultAttachedAction} will be set automatically. */ Kekule.Editor.ActionOnComposerAdv = Class.create(Kekule.Editor.ActionOnComposer, /** @lends Kekule.Editor.ActionOnComposerAdv# */ { /** @private */ CLASS_NAME: 'Kekule.Editor.ActionOnComposerAdv', /** @constructs */ initialize: function(/*$super, */composer, caption, hint) { var actions = new Kekule.ActionList(); actions.setOwnActions(true); this.setPropStoreFieldValue('attachedActions', actions); this.tryApplySuper('initialize', [composer, caption, hint]) /* $super(composer, caption, hint) */; }, finalize: function(/*$super*/) { this.getAttachedActions().finalize(); this.tryApplySuper('finalize') /* $super() */; }, /** @private */ initProperties: function() { this.defineProp('attachedActionClasses', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': null}); this.defineProp('attachedActions', {'dataType': 'Kekule.ActionList', 'serializable': false, 'setter': null}); this.defineProp('defaultAttachedAction', {'dataType': 'Kekule.Action', 'serializable': false}); this.defineProp('defaultAttachedActionClass', {'dataType': DataType.CLASS, 'serializable': false}); }, /** @private */ checkedChanged: function(/*$super*/) { this.tryApplySuper('checkedChanged') /* $super() */; var checked = this.getChecked(); //console.log('self checked change', this.getClassName(), checked); if (this.hasAttachedActions()) { var composer = this.getComposer(); if (composer) { if (checked) { var attachedActions = this.getAttachedActions(); composer.bindAssocActions(attachedActions); composer.showAssocToolbar(); var checkedChild = attachedActions.getCheckedAction(this.getClassName()); //console.log('child checked change', this.getClassName(), checkedChild.getClassName(), attachedActions.getActions()); /* if (checkedChild) { console.log('execute child', checkedChild.getClassName()); checkedChild.execute(); } else { var defAction = this.getDefaultAttachedAction(); if (defAction) { if (defAction.getCheckGroup() && !this.getAttachedActions().hasActionChecked(defAction.getCheckGroup())) defAction.execute(); } } */ if (!checkedChild) checkedChild = this.getDefaultAttachedAction(); // check and execute child if (checkedChild) { checkedChild.setChecked(false); // important, force execute again checkedChild.execute(); } } else { composer.hideAssocToolbar(); } } } }, doSetDefaultAttachedActionClass: function(value) { this.setPropStoreFieldValue('defaultAttachedActionClass', value); if (value && this.hasAttachedActions()) { for (var i = 0, l = this.getAttachedActions().getActionCount(); i < l; ++i) { var attachedActio