kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,460 lines (1,399 loc) • 126 kB
JavaScript
/**
* @fileoverview
* Implemetation of base class involves in displaying chem object (such as viewer or editor).
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /utils/kekule.utils.js
* requires /xbrowsers/kekule.x.js
* requires /render/kekule.render.configs.js
* requires /render/kekule.render.utils.js
* requires /widgets/operation/kekule.actions.js
* requires /widgets/kekule.widget.base.js
* requires /widgets/kekule.widget.helpers.js
* requires /widgets/kekule.widget.styleResources.js
* requires /widgets/commonCtrls/kekule.widget.buttons.js
* requires /widgets/commonCtrls/kekule.widget.tabViews.js
* requires /widgets/commonCtrls/kekule.widget.dialogs.js
* requires /widgets/commonCtrls/kekule.widget.formControls.js
* requires /widgets/chem/kekule.chemWidget.base.js
* requires /widgets/chem/kekule.chemWidget.dialogs.js
*/
(function(){
"use strict";
var PS = Class.PropertyScope;
var AU = Kekule.ArrayUtils;
var DU = Kekule.DomUtils;
var ZU = Kekule.ZoomUtils;
var CW = Kekule.ChemWidget;
//var CWT = Kekule.ChemWidgetTexts;
var CNS = Kekule.Widget.HtmlClassNames;
var CCNS = Kekule.ChemWidget.HtmlClassNames;
/** @ignore */
Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, {
DISPLAYER_DRAWCONTEXT_PARENT: 'K-Chem-Displayer-DrawContext-Parent',
ACTION_MOL_DISPLAY_TYPE: 'K-Chem-MolDisplayType',
ACTION_MOL_DISPLAY_SKELETAL: 'K-Chem-MolDisplaySkeletal',
ACTION_MOL_DISPLAY_CONDENSED: 'K-Chem-MolDisplayCondensed',
ACTION_MOL_DISPLAY_WIRE: 'K-Chem-MolDisplayWire',
ACTION_MOL_DISPLAY_BALLSTICK: 'K-Chem-MolDisplayBallStick',
ACTION_MOL_DISPLAY_STICKS: 'K-Chem-MolDisplaySticks',
ACTION_MOL_DISPLAY_SPACEFILL: 'K-Chem-MolDisplaySpaceFill',
ACTION_MOL_HIDE_HYDROGENS: 'K-Chem-MolHideHydrogens',
ACTION_MOL_AUTO_COORD_GENERATION: 'K-Chem-MolAutoCoordGeneration',
ACTION_ZOOMIN: 'K-Chem-ZoomIn',
ACTION_ZOOMOUT: 'K-Chem-ZoomOut',
ACTION_RESET: 'K-Chem-Reset',
ACTION_COPY: 'K-Chem-Copy',
ACTION_RESET_ZOOM: 'K-Chem-ResetZoom',
ACTION_LOADFILE: 'K-Chem-LoadFile',
ACTION_LOADDATA: 'K-Chem-LoadData',
ACTION_SAVEFILE: 'K-Chem-SaveFile',
ACTION_CLEAROBJS: 'K-Chem-ClearObjs',
ACTION_CONFIG: 'K-Chem-Config',
DIALOG_CHOOSE_FILE_FORAMT: 'K-Chem-Dialog-Choose-File-Format',
DIALOG_CHOOSE_FILE_FORAMT_FORMATBOX: 'K-Chem-Dialog-Choose-File-Format-FormatBox',
DIALOG_CHOOSE_FILE_FORAMT_PREVIEWER_REGION: 'K-Chem-Dialog-Choose-File-Format-PreviewerRegion',
DIALOG_CHOOSE_FILE_FORAMT_PREVIEWER: 'K-Chem-Dialog-Choose-File-Format-Previewer'
});
/**
* Root config class of {@link Kekule.ChemWidget.ChemObjDisplayer}.
* @class
* @augments Kekule.AbstractConfigs
*
* @property {Kekule.ChemWidget.ChemObjDisplayerIOConfigs} ioConfigs
*/
Kekule.ChemWidget.ChemObjDisplayerConfigs = Class.create(Kekule.AbstractConfigs,
/** @lends Kekule.ChemWidget.ChemObjDisplayerConfigs# */
{
/** @private */
CLASS_NAME: 'Kekule.ChemWidget.ChemObjDisplayerConfigs',
/** @private */
initProperties: function()
{
this.addConfigProp('ioConfigs', 'Kekule.ChemWidget.ChemObjDisplayerIOConfigs');
this.addConfigProp('environment2DConfigs', 'Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs');
this.addConfigProp('environment3DConfigs', 'Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs');
},
/** @private */
initPropValues: function(/*$super*/)
{
this.tryApplySuper('initPropValues') /* $super() */;
this.setPropStoreFieldValue('ioConfigs', new Kekule.ChemWidget.ChemObjDisplayerIOConfigs());
this.setPropStoreFieldValue('environment2DConfigs', new Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs());
this.setPropStoreFieldValue('environment3DConfigs', new Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs());
}
});
/**
* Config class of I/O options for {@link Kekule.ChemWidget.ChemObjDisplayer}.
* @class
* @augments Kekule.AbstractConfigs
*
* @property {Bool} canonicalizeBeforeSave Whether canonicalize molecules in displayer before saving them.
* @property {Bool} autoGenerateCoordsAfterLoad Whether generate coords when loading molecule without atom coords (e.g. SMILES).
*/
Kekule.ChemWidget.ChemObjDisplayerIOConfigs = Class.create(Kekule.AbstractConfigs,
/** @lends Kekule.ChemWidget.ChemObjDisplayerIOConfigs# */
{
/** @private */
CLASS_NAME: 'Kekule.ChemWidget.ChemObjDisplayerIOConfigs',
/** @private */
initProperties: function()
{
this.addBoolConfigProp('canonicalizeBeforeSave', true);
this.addBoolConfigProp('autoGenerateCoordsAfterLoad', true);
}
});
/**
* Config class of 2D rendering environment options for {@link Kekule.ChemWidget.ChemObjDisplayer}.
* @class
* @augments Kekule.AbstractConfigs
*
* @property {Bool} antialias Whether use a more aggressive antialias strategy than the browser's default.
* @property {Float} antialiasBlurRatio The blur level to make the 2D rendering more smooth.
* Effects will only be take when property {@link Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs.antialias} is true.
* @property {Float} overSamplingRatio The oversampling ratio to make the 2D rendering more smooth.
* Effects will only be take when property {@link Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs.antialias} is true.
*/
Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs = Class.create(Kekule.AbstractConfigs,
/** @lends Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs# */
{
/** @private */
CLASS_NAME: 'Kekule.ChemWidget.ChemObjDisplayerEnvironmentConfigs',
/** @private */
initProperties: function()
{
this.addBoolConfigProp('antialias', false);
this.addFloatConfigProp('antialiasBlurRatio', 0);
this.addFloatConfigProp('overSamplingRatio', 1);
}
});
/**
* An widget related to display chem objects (especially molecules).
* Note, this class is simply a base class for viewer and editor. User should not use this one directly.
* @class
* @augments Kekule.ChemWidget.AbstractWidget
*
* @param {Variant} parentOrElementOrDocument
* @param {Kekule.ChemObject} chemObj
* @param {Int} renderType Display in 2D or 3D. Value from {@link Kekule.Render.RendererType}.
* @param {Kekule.ChemWidget.ChemObjDisplayerConfigs} displayerConfigs Configs of current displayer.
*
* @property {Kekule.ChemWidget.ChemObjDisplayerConfigs} displayerConfigs Configs of current displayer.
* @property {Int} renderType Display in 2D or 3D. Value from {@link Kekule.Render.RendererType}. Read only.
* @property {Kekule.ChemObject} chemObj Object to be drawn. Set this property will repaint the client.
* @property {Bool} chemObjLoaded Whether the chemObj is successful loaded and drawn in viewer.
* @property {Bool} resetAfterLoad Whether reset display (remove rotate, zoom and so on) after set a new chem obj.
* @property {Object} renderConfigs Configuration for rendering.
* @property {Int} moleculeDisplayType Display type of molecule in displayer. Value from {@link Kekule.Render.Molecule2DDisplayType} or {@link Kekule.Render.Molecule3DDisplayType}.
* @property {Hash} drawOptions A series of params to render chem object.
* @property {Float} zoom Zoom ratio to draw chem object, equal to drawOptions.zoom.
* @property {Float} initialZoom Initial zoom when just loading a chem object. Default is 1.
* @property {Bool} autoSize Whether the widget change its size to fit the dimension of chem object.
* @property {Int} padding Padding between chem object and edge of widget, in px. Only works when autoSize is true.
* @property {Hash} baseCoordOffset Usually displayer draw object at center of widget, use this property to make
* the drawing center moved from widget center.
* Note: this property is useless when autoSize == true.
* @property {Hash} transformParams A combination of (@link Kekule.ChemWidget.ChemObjDisplayer.drawOptions} and (@link Kekule.ChemWidget.ChemObjDisplayer.baseCoordOffset}.
* @property {String} backgroundColor Get or set background color of displayer. Default is transparent.
* @property {Bool} enableLoadNewFile Whether open a external file to displayer is allowed.
* @property {Array} allowedInputFormatIds Formats that shown in input file dialog. Default is null, means accept all available formats.
* @property {Array} allowedOutputFormatIds Formats that shown in output file dialog. Default is null, means accept all available formats.
* @property {String} defaultInputFormatId
* @property {String} defaultOutputFormatId
* @property {Hash} standardizationOptions Possible options when do standardization on molecule before saving.
*/
/**
* Invoked when the a chem object (or null) is loaded into the displayer.
* event param of it has one fields: {obj: Object}.
* @name Kekule.ChemWidget.ChemObjDisplayer#load
* @event
*/
/**
* Invoked when the displayer is repainted.
* event param of it has one fields: {obj: Object}.
* @name Kekule.ChemWidget.ChemObjDisplayer#repaint
* @event
*/
Kekule.ChemWidget.ChemObjDisplayer = Class.create(Kekule.ChemWidget.AbstractWidget,
/** @lends Kekule.ChemWidget.ChemObjDisplayer# */
{
/** @private */
CLASS_NAME: 'Kekule.ChemWidget.ChemObjDisplayer',
/** @construct */
initialize: function(/*$super, */parentOrElementOrDocument, chemObj, renderType, displayerConfigs)
{
this._paintFlag = 0; // used internally
//this._errorReportElem = null; // use internally
this._bgColorMap = {}; // use internally
this._contextTransformOpsMap = new Kekule.MapEx();
this._reactChemObjDrawBind = this._reactChemObjDraw.bind(this);
this.setPropStoreFieldValue('resetAfterLoad', true);
this.setPropStoreFieldValue('renderType', renderType || Kekule.Render.RendererType.R2D); // must set this value first
this.setPropStoreFieldValue('displayerConfigs', displayerConfigs || this.createDefaultConfigs());
this.tryApplySuper('initialize', [parentOrElementOrDocument]) /* $super(parentOrElementOrDocument) */;
if (chemObj)
{
this.setChemObj(chemObj);
}
//this.setUseCornerDecoration(true);
this.setEnableLoadNewFile(true);
//this.setPropStoreFieldValue('displayerConfigs', displayerConfigs || this.createDefaultConfigs());
},
/** @private */
doFinalize: function(/*$super*/)
{
//this.setChemObj(null);
/*
var r = this.getPropStoreFieldValue('boundInfoRecorder');
if (r)
r.finalize();
this.setPropStoreFieldValue('boundInfoRecorder', null);
*/
this.clearSubViews();
this.setPropStoreFieldValue('chemObj', null);
this.getPainter().finalize();
var b = this.getPropStoreFieldValue('drawBridge');
var ctx = this.getPropStoreFieldValue('drawContext');
if (ctx && b)
b.releaseContext(ctx);
if (b && b.finalize)
b.finalize();
this.setPropStoreFieldValue('drawBridge', null);
this.setPropStoreFieldValue('drawContext', null);
if (this._contextTransformOpsMap)
this._contextTransformOpsMap.finalize();
this.tryApplySuper('doFinalize') /* $super() */;
},
/** @private */
initProperties: function()
{
this.defineProp('displayerConfigs', {'dataType': 'Kekule.ChemWidget.ChemObjDisplayerConfigs', 'serializable': false, 'scope': PS.PUBLIC});
this.defineProp('enableLoadNewFile', {'dataType': DataType.BOOL});
this.defineProp('allowedInputFormatIds', {'dataType': DataType.ARRAY});
this.defineProp('allowedOutputFormatIds', {'dataType': DataType.ARRAY});
this.defineProp('defaultInputFormatId', {'dataType': DataType.STRING, 'serializable': false});
this.defineProp('defaultOutputFormatId', {'dataType': DataType.STRING, 'serializable': false});
this.defineProp('standardizationOptions', {'dataType': DataType.HASH});
this.defineProp('resetAfterLoad', {'dataType': DataType.BOOL});
this.defineProp('chemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false, 'scope': PS.PUBLISHED,
'setter': function(value)
{
var oldObj = this.getPropStoreFieldValue('chemObj');
//if (value !== oldObj) // some times oldObj itself may change and may need to repaint
{
this.setPropStoreFieldValue('chemObj', value);
this.chemObjChanged(value, oldObj);
}
}
});
this.defineProp('chemObjData', {'dataType': DataType.STRING, 'serializable': false, 'getter': null,
'setter': function(value)
{
var jsonObj;
if (typeof(value) === 'object')
jsonObj = value;
else
jsonObj = JSON.parse(value);
//console.log('json', jsonObj, value);
if (jsonObj)
{
var chemObj;
chemObj = (jsonObj.format)? Kekule.IO.loadFormatData(jsonObj.data, jsonObj.format):
(jsonObj.mimeType)? Kekule.IO.loadMimeData(jsonObj.data, jsonObj.mimeType):
null;
if (chemObj)
this._tryAutoGenerateChemObjCoordsAndLoad(chemObj);
}
}
});
this.defineProp('chemObjLoaded', {'dataType': DataType.BOOL, 'serializable': false, 'scope': PS.PUBLIC,
'setter': null,
'getter': function() { return this.getChemObj() && this.getPropStoreFieldValue('chemObjLoaded'); }
});
this.defineProp('renderType', {'dataType': DataType.INT, 'serializable': false, 'enumSource': Kekule.Render.RendererType, 'scope': PS.PUBLISHED,
'setter': function(value)
{
if (!this.getAllowRenderTypeChange())
{
Kekule.error(Kekule.$L('ErrorMsg.RENDER_TYPE_CHANGE_NOT_ALLOWED'));
return;
}
var oldValue = this.getRenderType();
if (value !== oldValue)
{
this.setPropStoreFieldValue('renderType', value);
this.resetRenderType(oldValue, value);
}
}
});
this.defineProp('renderConfigs', {'dataType': DataType.OBJECT, 'serializable': false, 'scope': PS.PUBLIC,
'getter': function()
{
var result = this.getPropStoreFieldValue('renderConfigs');
if (!result)
result = (this.getRenderType() === Kekule.Render.RendererType.R3D)?
Kekule.Render.Render3DConfigs.getInstance():
Kekule.Render.Render2DConfigs.getInstance();
return result;
},
'setter': function(value)
{
this.setPropStoreFieldValue('renderConfigs', value);
var painter = this.getPropStoreFieldValue('painter');
if (painter)
painter.setRenderConfigs(this.getRenderConfigs());
}
});
this.defineProp('backgroundColor', {'dataType': DataType.STRING, 'scope': PS.PUBLISHED,
'setter': function(value)
{
this.setPropStoreFieldValue('backgroundColor', value);
//this.setBackgroundColorOfType(value, this.getRenderType());
this._bgColorMap[this.getRenderType().toString()] = value;
this.backgroundColorChanged();
}
});
this.defineProp('inheritedRenderStyles', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
'getter': function()
{
var result = this.getPropStoreFieldValue('inheritedRenderStyles');
if (!result)
{
result = [];
this.setPropStoreFieldValue('inheritedRenderStyles', result);
}
return result;
},
'setter': function(value)
{
this.setPropStoreFieldValue('inheritedRenderStyles', value || []);
this.requestRepaint();
}
});
this.defineProp('inheritedRenderColor', {'dataType': DataType.BOOL, 'scope': PS.PUBLISHED,
'getter': function()
{
var inheritedStyles = this.getInheritedRenderStyles();
return !!(inheritedStyles && (inheritedStyles.indexOf('color') >= 0));
},
'setter': function(value)
{
if ((!!value) !== this.getInheritedRenderColor())
{
var styles = this.getInheritedRenderStyles();
if (value)
styles.push('color');
else
Kekule.ArrayUtils.remove(styles, 'color');
this.setInheritedRenderStyles(styles);
}
}
});
this.defineProp('inheritedRenderBackgroundColor', {'dataType': DataType.BOOL, 'scope': PS.PUBLISHED,
'getter': function()
{
var inheritedStyles = this.getInheritedRenderStyles();
return !!(inheritedStyles && (inheritedStyles.indexOf('backgroundColor') >= 0));
},
'setter': function(value)
{
if ((!!value) !== this.getInheritedRenderBackgroundColor())
{
var styles = this.getInheritedRenderStyles();
if (value)
styles.push('backgroundColor');
else
Kekule.ArrayUtils.remove(styles, 'backgroundColor');
this.setInheritedRenderStyles(styles);
}
}
});
this.defineProp('enableCustomCssProperties', {'dataType': DataType.BOOL,
'setter': function(value){
if (value != this.getEnableCustomCssProperties())
{
this.setPropStoreFieldValue('enableCustomCssProperties', !!value);
this.requestRepaint();
}
}
});
//this.defineProp('actualOverSamplingRatio', {'dataType': DataType.FLOAT, 'setter': null, 'scope': PS.PRIVATE}); // private
this.defineProp('transformParams', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PUBLIC,
'getter': function()
{
var result = Object.extend({}, this.getDrawOptions());
result.screenCoordOffset = this.getBaseCoordOffset();
return result;
},
'setter': function(value)
{
var param = value || {};
this.setDrawOptions(param);
this.setBaseCoordOffset(param.baseCoordOffset);
this.drawOptionChanged();
}
});
this.defineProp('drawOptions', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PUBLIC,
'getter': function()
{
var result = this.getPropStoreFieldValue('drawOptions');
if (!result)
{
result = {};
this.setPropStoreFieldValue('drawOptions', result);
}
return result;
},
'setter': function(value)
{
this.setPropStoreFieldValue('drawOptions', value);
this.drawOptionChanged();
}
});
this.defineProp('zoom', {'dataType': DataType.FLOAT, 'serializable': false,
'getter': function()
{
var op = this.getDrawOptions() || {};
return op.zoom;
},
'setter': function(value)
{
this.zoomTo(value);
return this;
}
});
this.defineProp('initialZoom', {'dataType': DataType.FLOAT});
this.defineProp('autofit', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
var op = this.getDrawOptions() || {};
return op.autofit;
},
'setter': function(value)
{
var op = this.getDrawOptions() || {};
op.autofit = value;
return this.setDrawOptions(op);
}
});
this.defineProp('autoShrink', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
var op = this.getDrawOptions() || {};
return op.autoShrink;
},
'setter': function(value)
{
var op = this.getDrawOptions() || {};
op.autoShrink = value;
return this.setDrawOptions(op);
}
});
this.defineProp('moleculeDisplayType', {'dataType': DataType.INT, 'serializable': false,
'getter': function()
{
var op = this.getDrawOptions() || {};
return op.moleculeDisplayType;
},
'setter': function(value)
{
this.getDrawOptions().moleculeDisplayType = value;
this.drawOptionChanged();
return this;
}
});
this.defineProp('hideHydrogens', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
var op = this.getDrawOptions() || {};
return op.hideHydrogens;
},
'setter': function(value)
{
this.getDrawOptions().hideHydrogens = !!value;
this.drawOptionChanged();
return this;
}
});
this.defineProp('allowCoordBorrow', {'dataType': DataType.BOOL, 'serializable': false, 'scope': PS.PUBLIC,
'getter': function()
{
return Kekule.oneOf(this.getDrawOptions().allowCoordBorrow, this.getRenderConfigs().getGeneralConfigs().getAllowCoordBorrow());
}
});
//this.defineProp('zoom', {'dataType': DataType.FLOAT, 'serializable': false});
this.defineProp('autoSize', {'dataType': DataType.BOOL, 'serializable': false,
'setter': function(value)
{
if (value != this.getAutoSize())
{
this.setPropStoreFieldValue('autoSize', value);
if (value && this.allowAutoSize())
this.drawOptionChanged(); // force repaint
}
}
});
this.defineProp('padding', {'dataType': DataType.INT, 'serializable': false,
'setter': function(value)
{
if (value != this.getPadding())
{
this.setPropStoreFieldValue('padding', value);
/*
if (this.allowAutoSize())
this.drawOptionChanged(); // force repaint
*/
this.getDrawOptions().autofitContextPadding = value;
this.drawOptionChanged();
}
}
});
this.defineProp('baseCoordOffset', {'dataType': DataType.Hash, 'serializable': false,
'setter': function(value)
{
this.setPropStoreFieldValue('baseCoordOffset', value);
if (this.getChemObjLoaded())
this.repaint();
}
});
// private properties
this.defineProp('drawBridge', {'dataType': DataType.OBJECT, 'serializable': false, 'scope': PS.PRIVATE,
'setter': null,
'getter': function(slient)
{
var result = this.getPropStoreFieldValue('drawBridge');
if (!result && !this.__$drawBridgeInitialized$__)
{
this.__$drawBridgeInitialized$__ = true; // avoid call this.createDrawBridge() multiple times
result = this.createDrawBridge(/*slient*/);
this.setPropStoreFieldValue('drawBridge', result);
}
return result;
}
});
this.defineProp('drawContext', {'dataType': DataType.OBJECT, 'serializable': false, 'scope': PS.PRIVATE,
'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('drawContext');
if (!result)
{
var bridge = this.getDrawBridge();
if (bridge)
{
var elem = this.getDrawContextParentElem();
if (!elem)
return null;
else
{
//var dim = Kekule.HtmlElementUtils.getElemScrollDimension(elem);
//var dim = Kekule.HtmlElementUtils.getElemClientDimension(elem);
var dim = Kekule.HtmlElementUtils.getElemOffsetDimension(elem);
result = bridge.createContext(elem, dim.width, dim.height, this.getContextCreationParams(this.getRenderType(), bridge));
/*
if (result !== elem)
Kekule.HtmlElementUtils.addClass(result, CNS.DYN_CREATED);
*/
// a little smaller than current element, avoid scroll bars in when setting CSS3's resize: both property
this.setPropStoreFieldValue('drawContext', result);
}
}
}
return result;
}
});
this.defineProp('painter', {'dataType': 'Kekule.Render.ChemObjPainter', 'serializable': false, 'scope': PS.PRIVATE,
'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('painter');
if (!result)
{
result = this.createNewPainter();
}
return result;
}
});
this.defineProp('rootRenderer', {'dataType': 'Kekule.Render.AbstractRenderer', 'serializable': false, 'scope': PS.PRIVATE,
'setter': null,
'getter': function()
{
var p = this.getPainter();
return p? p.getRenderer(): null;
}
});
// private object to record all bound infos
this.defineProp('boundInfoRecorder', {'dataType': 'Kekule.Render.BoundInfoRecorder', 'serializable': false, 'scope': Class.PropertyScope.PRIVATE, 'setter': null,
'getter': function() { var p = this.getRootRenderer(); return p && p.getBoundInfoRecorder(); }
});
// private, sub views for individual objects in displayer
this.defineProp('subViews', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': null,
'getter': function(canCreate) {
var result = this.getPropStoreFieldValue('subViews');
if (!result && canCreate)
{
result = [];
this.setPropStoreFieldValue('subViews', result);
}
return result;
}
});
},
/** @ignore */
initPropValues: function(/*$super*/)
{
this.tryApplySuper('initPropValues') /* $super() */;
this.setStandardizationOptions({'unmarshalSubFragments': false, 'clearHydrogens': false}); // do not auto clear explicit H
},
/** @ignore */
doEndUpdate: function(modifiedPropNames)
{
this.tryApplySuper('doEndUpdate', [modifiedPropNames]);
if (this._requestRepainting) // pending painting job in begin/endUpadte block
{
this.repaint(this._requestRepainting.overrideOptions);
}
},
/** @ignore */
elementBound: function(element)
{
this.setObserveElemResize(true);
},
/** @ignore */
doWidgetShowStateChanged: function(/*$super, */isShown)
{
this.tryApplySuper('doWidgetShowStateChanged', [isShown]) /* $super(isShown) */;
/*
As position/size calculation may be wrong when displayed = false,
during the show phase, whole context should be force repainted.
*/
if (isShown)
this.setChemObj(this.getChemObj());
},
/** @ignore */
doFileDragDrop: function(/*$super, */files)
{
if (!files /* || files.length > 1 */)
return this.tryApplySuper('doFileDragDrop') /* $super() */;
else // if only one file is dropped in, output the file content
{
var self = this;
(function()
{
Kekule.IO.loadFileData(files[0], function(chemObj, success)
{
if (success)
{
self.setChemObj(chemObj);
}
});
}).defer(); // call load later, avoid error in load process that prevent result true is returned
return true;
}
},
/** @ignore */
notifyStyleAttribChanged: function(targetElem)
{
this.tryApplySuper('notifyStyleAttribChanged', [targetElem]);
var inheritedStyles = this.getInheritedRenderStyles() || [];
if (inheritedStyles.length)
{
this.requestRepaint();
}
},
/**
* Whether changing render type is allowed in current type of displayer.
* Default is false, descendants may override this method.
* @returns {Bool}
*/
getAllowRenderTypeChange: function()
{
return false;
},
/**
* Notify the render type has been changed.
* Descendants may override this method.
* @private
*/
resetRenderType: function(oldType, newType)
{
var chemObj = this.getChemObj();
var bridge = this.getPropStoreFieldValue('drawBridge');
var context = this.getPropStoreFieldValue('drawContext');
if (bridge)
{
if (context)
bridge.releaseContext(context);
}
this.setPropStoreFieldValue('drawContext', null);
this.setPropStoreFieldValue('drawBridge', null);
this.__$drawBridgeInitialized$__ = false; // important, marks the draw bridge should be reinitialized
var newBgColor = this.getBackgroundColorOfType(newType);
this.getDrawOptions().moleculeDisplayType = this.getDefaultMoleculeDisplayType(newType); // reset display type
//this.setBackgroundColor(newBgColor);
this.setPropStoreFieldValue('backgroundColor', newBgColor);
this.backgroundColorChanged(true); // notify back color change but not repaint, as painter currently is still old one
//if (chemObj) // repaint
//this.setChemObj(chemObj || null);
// clear old draw options if oldObj is set
this._doLoadOnObj(chemObj, {refreshOnly: true});
this.resetDisplay();
},
/**
* Force to recreate drawing context and repaint.
*/
resetRenderEnvironment: function()
{
this.resetRenderType(null, this.getRenderType());
return this;
},
/**
* Returns environment configs of current render mode.
* @private
*/
getDisplayerEnvironmentConfigs: function()
{
var envConfigs = (this.getCoordMode() === Kekule.CoordMode.COORD3D)?
this.getDisplayerConfigs().getEnvironment3DConfigs():
this.getDisplayerConfigs().getEnvironment2DConfigs();
return envConfigs;
},
/** @private */
getContextCreationParams: function(renderType, drawBridge)
{
var result;
var renderConfig = this.getRenderConfigs();
var displayerConfig = this.getDisplayerConfigs();
if (renderType === Kekule.Render.RendererType.R3D)
result = {
'alpha': true,
'antialias': renderConfig.getEnvironmentConfigs().getAntialias()
};
else // 2D
result = {
'alpha': true,
'antialias': displayerConfig.getEnvironment2DConfigs().getAntialias(),
'antialiasBlurRatio': displayerConfig.getEnvironment2DConfigs().getAntialiasBlurRatio(),
'overSamplingRatio': displayerConfig.getEnvironment2DConfigs().getOverSamplingRatio()
};
return result;
},
/**
* Create a default editor config object.
* Descendants may override this method.
* @returns {Kekule.Editor.BaseEditorConfigs}
* @private
*/
createDefaultConfigs: function()
{
return new Kekule.ChemWidget.ChemObjDisplayerConfigs();
},
/**
* Returns coord mode according to current renderType.
* @returns {Int}
*/
getCoordMode: function()
{
return (this.getRenderType() === Kekule.Render.RendererType.R3D)?
Kekule.CoordMode.COORD3D: Kekule.CoordMode.COORD2D;
},
/**
* Set renderType according to coord mode.
* @param {Int} coordMode
*/
setCoordMode: function(coordMode)
{
var rType = (coordMode === Kekule.CoordMode.COORD3D)?
Kekule.Render.RendererType.R3D: Kekule.Render.RendererType.R2D;
this.setRenderType(rType);
},
/** @private */
loadPredefinedResDataToProp: function(propName, resData, success)
{
if (propName === 'chemObj') // only this property can be set by predefined resource
{
if (success)
{
try
{
//var ext = resData.uri? Kekule.UrlUtils.extractFileExt(resData.uri): null;
var chemObj = Kekule.IO.loadTypedData(resData.data, resData.resType, resData.resUri);
if (!chemObj) // try regard resType as format ID
{
chemObj = Kekule.IO.loadFormatData(resData.data, resData.resType);
}
if (chemObj)
{
this._tryAutoGenerateChemObjCoordsAndLoad(chemObj);
}
else
Kekule.error(Kekule.$L('ErrorMsg.LOAD_CHEMDATA_FAILED'));
//this.setChemObj(chemObj);
}
catch(e)
{
this.reportException(e);
}
}
else // else, failed
{
// NOTE: just report a text msg rather than an exception object.
// In IE and Chrome, it seems that error object can not cross context, so that may cause Illegal invocation error in Kekule.ExceptionHandler
this.reportException(/*Kekule.ErrorMsg.CANNOT_LOAD_RES_OF_URI*/Kekule.$L('ErrorMsg.CANNOT_LOAD_RES_OF_URI') + resData.resUri || '');
//Kekule.throwException(Kekule.ErrorMsg.CANNOT_LOAD_RES_OF_URI + resData.resUri || '');
}
}
},
/** @private */
reportException: function(e, exceptionLevel)
{
/*
var msg = Kekule.getExceptionMsg(e);
if (!this._errorReportElem) // create error element
{
this._errorReportElem = this.getDocument().createElement('span');
this._errorReportElem.className = Kekule.Widget.HtmlClassNames.PART_ERROR_REPORT;
this.getElement().appendChild(this._errorReportElem);
}
this._errorReportElem.innerHTML = msg;
Kekule.StyleUtils.setDisplay(this._errorReportElem, '');
*/
Kekule.raise(e, exceptionLevel || Kekule.ExceptionLevel.ERROR);
},
/* @private */
/*
hideExceptionReport: function()
{
if (this._errorReportElem)
{
Kekule.StyleUtils.setDisplay(this._errorReportElem, 'none');
}
},
*/
/** @private */
createDrawBridge: function(slient)
{
var M = (this.getRenderType() === Kekule.Render.RendererType.R3D)?
Kekule.Render.DrawBridge3DMananger: Kekule.Render.DrawBridge2DMananger;
var result = M.getPreferredBridgeInstance();
if (!result) // can not find suitable draw bridge
{
if (!slient)
{
var errorMsg = M.getUnavailableMessage() || Kekule.error(Kekule.$L('ErrorMsg.DRAW_BRIDGE_NOT_SUPPORTED'));
if (errorMsg)
this.reportException(errorMsg, Kekule.ExceptionLevel.NOT_FATAL_ERROR);
}
// Kekule.error(/*Kekule.ErrorMsg.DRAW_BRIDGE_NOT_SUPPORTED*/Kekule.$L('ErrorMsg.DRAW_BRIDGE_NOT_SUPPORTED'));
}
/* infinite loop, remove this part
if (this.getBackgroundColor() && result.setClearColor)
result.setClearColor(this.getDrawContext(), this.getBackgroundColor());
*/
return result;
},
/**
* Returns the root element to hold the drawing context parents.
* Descendants may override this method
* @returns {HTMLElement}
*/
getClientElement: function()
{
return this.getElement();
},
/**
* Returns parent element to create draw context inside.
* Descendants can override this method.
*/
getDrawContextParentElem: function(disableAutoCreate)
{
//return this.getElement();
var result = this._drawContextParentElem;
if (!result && !disableAutoCreate) // create new
{
result = this.getDocument().createElement('div'); // IMPORTANT: span may cause dimension calc problem of context
this._drawContextParentElem = result;
Kekule.HtmlElementUtils.addClass(result, CNS.DYN_CREATED);
Kekule.HtmlElementUtils.addClass(result, CCNS.DISPLAYER_DRAWCONTEXT_PARENT);
// IMPORTANT: force to fullfill the parent, otherwise draw context dimension calculation may have problem
//result.style.display = 'block';
result.style.width = '100%';
result.style.height = '100%';
// insert as first child
var root = this.getClientElement();
var currFirst = Kekule.DomUtils.getFirstChildElem(root);
if (currFirst)
root.insertBefore(result, currFirst);
else
root.appendChild(result);
}
return result;
},
/**
* Whether current painter can meet the requirement of auto size.
* @returns {Bool}
*/
allowAutoSize: function()
{
// TODO: Currently autosize is not enabled in 3D mode
return this.getRenderType() === Kekule.Render.RendererType.R2D;
},
/**
* Whether context and draw bridge can modify existing graphic content.
* @returns {Bool}
*/
canModifyPartialGraphic: function(context)
{
var b = this.getDrawBridge();
return b.canModifyGraphic? b.canModifyGraphic(context || this.getDrawContext()): false;
},
/** @private */
doInsertedToDom: function()
{
this.doResize();
},
/** @private */
doResize: function()
{
//console.log('refit draw context');
// when the size of chem viewer is changed, context should also be adjusted and object should be redrawn.
this.refitDrawContext(this.isPainting()); // if is paiting, do not call repaint
},
/** @private */
refitDrawContext: function(doNotRepaint)
{
//if (this.getDrawBridge(true) && this.getDrawContext())
if (this.getDrawBridge() && this.getDrawContext())
{
//var dim = Kekule.HtmlElementUtils.getElemScrollDimension(this.getElement());
var dim = Kekule.HtmlElementUtils.getElemClientDimension(this.getDrawContextParentElem());
//console.log(this.getElement().id, dim, Kekule.HtmlElementUtils.getElemScrollDimension(this.getElement()));
//this.getDrawBridge().setContextDimension(this.getDrawContext(), dim.width, dim.height);
var success = this.changeContextDimension(dim);
if (!doNotRepaint && success)
this.repaint();
}
},
/**
* Returns dimension of context.
* @returns {Hash}
*/
getContextDimension: function()
{
if (this.getDrawBridge() && this.getDrawContext())
return this.getDrawBridge().getContextDimension(this.getDrawContext());
else
return null;
},
/** @private */
_getContextOverSamplingRatio: function()
{
var envConfigs = this.getDisplayerEnvironmentConfigs();
var enableAntialias = envConfigs.getAntialias && envConfigs.getAntialias();
var overSamplingRatio = (enableAntialias && envConfigs.getOverSamplingRatio && envConfigs.getOverSamplingRatio()) || 1;
return overSamplingRatio;
},
/**
* Change the dimension of context.
* @param {Hash} newDimension
* @returns {Bool} Return true if dimension change successfully.
*/
changeContextDimension: function(newDimension)
{
/*
if (this.getDrawBridge() && this.getDrawContext())
{
var width = newDimension.width;
var height = newDimension.height;
var elem = this.getDrawBridge().getContextElem(this.getDrawContext());
if (elem) // use transform CSS property to do resampling
{
var overSamplingRatio = this._getContextOverSamplingRatio();
if (overSamplingRatio !== 1 && Kekule.BrowserFeature.cssTranform) // oversampling enabled
{
var scale = 1 / overSamplingRatio;
elem.style.transform = 'scale(' + scale + ',' + scale + ')';
elem.style.transformOrigin = '0 0';
//this.setPropStoreFieldValue('actualOverSamplingRatio', overSamplingRatio); // store the over sampling ratio for rendering
this.getDrawBridge().setContextParam(this.getDrawContext(), 'overSamplingRatio', overSamplingRatio);
}
else
{
elem.style.transform = '';
//this.setPropStoreFieldValue('actualOverSamplingRatio', null); // store the over sampling ratio for rendering
this.getDrawBridge().setContextParam(this.getDrawContext(), 'overSamplingRatio', null);
}
}
//this._resizeContext(this.getDrawContext(), this.getDrawBridge(), width, height);
this.doChangeContextDimension(width, height);
//this.getDrawBridge().setContextDimension(this.getDrawContext(), newDimension.width, newDimension.height);
return true;
}
else
return false;
*/
return this.doChangeContextDimension(this.getDrawContext(), this.getDrawBridge(), newDimension, true);
},
/** @private */
doChangeContextDimension: function(drawContext, drawBridge, newDimension, enableOverSampling)
{
//this._resizeContext(this.getDrawContext(), this.getDrawBridge(), width, height);
if (drawBridge && drawContext)
{
var width = newDimension.width;
var height = newDimension.height;
var elem = drawBridge.getContextElem(drawContext);
if (elem) // use transform CSS property to do resampling
{
var overSamplingRatio = this._getContextOverSamplingRatio();
if (overSamplingRatio !== 1 && Kekule.BrowserFeature.cssTranform && enableOverSampling) // oversampling enabled
{
/*
width *= overSamplingRatio;
height *= overSamplingRatio;
*/
var scale = 1 / overSamplingRatio;
elem.style.transform = 'scale(' + scale + ',' + scale + ')';
elem.style.transformOrigin = '0 0';
//this.setPropStoreFieldValue('actualOverSamplingRatio', overSamplingRatio); // store the over sampling ratio for rendering
drawBridge.setContextParam(drawContext, 'overSamplingRatio', overSamplingRatio);
}
else
{
elem.style.transform = '';
//this.setPropStoreFieldValue('actualOverSamplingRatio', null); // store the over sampling ratio for rendering
drawBridge.setContextParam(drawContext, 'overSamplingRatio', null);
}
}
this._resizeContext(drawContext, drawBridge, width, height);
//this.getDrawBridge().setContextDimension(this.getDrawContext(), newDimension.width, newDimension.height);
return true;
}
else
return false;
},
/** @private */
_resizeContext: function(context, bridge, width, height)
{
if (context && bridge)
bridge.setContextDimension(context, width, height);
},
/** @private */
clearContext: function()
{
var c = this.getPropStoreFieldValue('drawContext');
if (c)
{
var p = this.getPropStoreFieldValue('painter');
if (p)
p.clear(c);
this.getDrawBridge().clearContext(c);
}
},
/** @private */
createNewPainter: function(chemObj)
{
var old = this.getPropStoreFieldValue('painter');
if (old)
{
old.removeEventListener('draw', this._reactChemObjDrawBind);
old.finalize();
}
var drawBridge = this.getDrawBridge();
var result = drawBridge? (new Kekule.Render.ChemObjPainter(this.getRenderType(), chemObj || this.getChemObj(), this.getDrawBridge())): null;
this.setPropStoreFieldValue('painter', result);
if (result)
result.addEventListener('draw', this._reactChemObjDrawBind);
// create new bound info recorder
//this.createNewBoundInfoRecorder(result);
return result;
},
/**
* Returns whether the painter, draw bridge and context are ready, the rendering can be successfully performed.
* @returns {Bool}
*/
isRenderable: function()
{
return !!(/*this.getPainter() &&*/ this.getDrawBridge() && this.getDrawContext());
//return !!(this.getPropStoreFieldValue('painter') && this.getDrawBridge() && this.getDrawContext());
},
/* @private */
/*
createNewBoundInfoRecorder: function(renderer)
{
var old = this.getPropStoreFieldValue('boundInfoRecorder');
if (old)
old.finalize();
var recorder = new Kekule.Render.BoundInfoRecorder(renderer);
//recorder.setTargetContext(this.getObjContext());
this.setPropStoreFieldValue('boundInfoRecorder', recorder);
},
*/
/**
* Called when chemObj property has been changed.
* Usually this will cause the viewer repaint itself.
* @param {Kekule.ChemObject} newObj
* @private
*/
chemObjChanged: function(newObj, oldObj)
{
//if (newObj)
//console.log('change to new Obj', newObj);
this.doLoad(newObj);
if (this.getResetAfterLoad() && oldObj) // clear old draw options if oldObj is set
this.resetDisplay();
},
/**
* Load and display chemObj in viewer
* @param {Kekule.ChemObject} chemObj
*/
load: function(chemObj)
{
this.setChemObj(chemObj);
},
/**
* Do actual job of loading a chemObj.
* @param {Kekule.ChemObject} chemObj
* @ignore
*/
doLoad: function(chemObj, options)
{
this._doLoadOnObj(chemObj);
},
/** @private */
_doLoadOnObj: function(chemObj, options)
{
var refreshOnly = options && options.refreshOnly;
this.clearSubViews(); // clear all old subviews when loading a new chem object
//console.log('doLoad', chemObj);
this.refitDrawContext(true); // ensure the context size is correct, but not force repaint.
//this.hideExceptionReport();
this.setPropStoreFieldValue('chemObjLoaded', false);
//this.clearContext();
try
{
if (chemObj)
{
/*
// debug
chemObj.addOverrideRenderOptionItem({
'atomColor': '#ff0000', 'bondColor': '#00ff00',
//'useAtomSpecifiedColor': false,
'atomRadius': 3
});
*/
var painter = this.createNewPainter(chemObj);
if (painter)
painter.setRenderConfigs(this.getRenderConfigs());
/*
var drawOptions = this.getDrawOptions();
var context = this.getDrawContext();
var baseCoord;
if (this.getAutoSize() && this.allowAutoSize()) // need to resize widget dimension
{
var padding = this.getPadding() || 0;
var renderBox = painter.estimateScreenBox(context, baseCoord, drawOptions);
var width = renderBox.x2 - renderBox.x1 + padding * 2;
var height = renderBox.y2 - renderBox.y1 + padding * 2;
this.getDrawBridge().setContextDimension(context, width, height);
this.setWidth(width + 'px');
this.setHeight(height + 'px');
baseCoord = {'x': width / 2, 'y': height / 2};
}
else
{
baseCoord = drawOptions? drawOptions.baseCoord: null;
if (!baseCoord)
{
var dim = Kekule.HtmlElementUtils.getElemClientDimension(this.getElement());
baseCoord = {'x': dim.width / 2, 'y': dim.height / 2};
}
}
painter.draw(context, baseCoord, drawOptions);
*/
this.repaint();
this.setPropStoreFieldValue('chemObjLoaded', true); // indicate obj loaded successful
if (!refreshOnly)
{
this.invokeEvent('load', {'obj': chemObj});
}
}
else // no object, clear
{
this.clearContext();
if (!refreshOnly)
this.invokeEvent('load', {'obj': chemObj}); // even chemObj is null, this event should also be invoked
}
}
catch(e)
{
this.clearContext();
this.reportException(e);
//console.log(e);
//throw e;
}
finally
{
this.doLoadEnd(this.getChemObj());
}
},
/**
* Called when a chem object is loaded into widget.
* Descendants may override this method.
* @param {Kekule.ChemObject} chemObj If loading process is failed, this param may be null.
* @private
*/
doLoadEnd: function(chemObj)
{
// do nothing here
},
/**
* Load chem object from data of special MIME type or file format.
* @param {Variant} data Usually text content.
* @param {String} mimeType
* @param {String} fromUrlOrFileName From which file or url is this data loaded.
* @param {String} formatId
*/
loadFromData: function(data, mimeType, fromUrlOrFileName, formatId, objAfterLoadCallback)
{
try
{
if (!data)
{
this.setChemObj(null);
return null;
}
else
{
//var ext = fromUrlOrFileName? Kekule.UrlUtils.extractFileExt(fromUrlOrFileName): null;
var chemObj;
if (formatId)
chemObj = Kekule.IO.loadFormatData(data, formatId);
else if (mimeType || fromUrlOrFileName)
chemObj = Kekule.IO.loadTypedData(data, mimeType, fromUrlOrFileName);
if (chemObj)
{
//this.setChemObj(chemObj);
if (objAfterLoadCallback)
this._tryAutoGenerateChemObjCoordsForLoading(chemObj, objAfterLoadCallback);
else
this._tryAutoGenerateChemObjCoordsAndLoad(chemObj);
}
else
Kekule.error(/*Kekule.ErrorMsg.LOAD_CHEMDATA_FAILED*/Kekule.$L('ErrorMsg.LOAD_CHEMDATA_FAILED'));
return chemObj;
}
}
catch(e)
{
this.reportException(e);
}
},
/**
* Load chem object from file object.
* NOTE: browser must support File Reader API to use this method.
* @param {File} file
*/
loadFromFile: function(file)
{
if (!file)
this.setChemObj(null);
else
{
var self = this;
try
{
Kekule.IO.loadFileData(file, function(chemObj, success)
{
if (success)
{
//self.setChemObj(chemObj);
self._tryAutoGenerateChemObjCoordsAndLoad(chemObj);
}
}
);
}
catch (e)
{
this.reportException(e);
}
}
},
/**
* Returns object in displayer that to be saved.
* Usually this should be the chemObj itself.
* Descendants may override this method.
* @returns {Kekule.ChemObject}
* @private
*/
getSavingTargetObj: function()
{
return this.getChemObj();
},
/**
* Save loaded chem object to data.
* @param {String} formatId
* @param {Int} dataType Text or binary. Set null to use default type.
* @param {Kekule.ChemObject} obj Object to save, default is current chemObj loaded in displayer.
* @returns {Variant} Saved data.
*/
saveData: function(formatId, dataType, obj)
{
var obj = obj || this.getSavingTargetObj(); /* this.getChemObj()*/
this.prepareSaveData(obj);
var writer = Kekule.IO.ChemDataWriterManager.getWriterByFormat(formatId, null, obj);
if (writer)
{
var doCanonicalize = this._needToCanonicalizeBeforeSaving() && this.getDisplayerConfigs().getIoConfigs().getCanonicalizeBeforeSave();
if (doCanonicalize && obj.standardize) // canonicalize first
{
//var obj = obj.clone? obj.clone(true): obj; // clone with id
var obj = this._cloneSavingTargetObj(obj);
obj.standardize(this.getStandardizationOptions());
}
if (!dataType)
{
var formatInfo = Kekule.IO.DataFormatsManager.getFormatInfo(formatId);
dataType = formatInfo.dataType;
}
var data = writer.writeData(obj, dataType, formatId);
return data;
}
else
{
Kekule.error(/*Kekule.ErrorMsg.NO_SUITABLE_WRITER_FOR_FORMAT*/Kekule.$L('ErrorMsg.NO_SUITABLE_WRITER_FOR_FORMAT'));
return null;
}
},
/** @private */
_cloneSavingTargetObj: function(obj)
{
var result = obj.clone? obj.clone(true): obj; // clone with id
return result;
},
/**
* Return whether this displayer need to canonicalize molecule before save.
* Descendants may override this method.
* @returns {Bool}
* @private
*/
_needToCanonicalizeBeforeSaving: function()
{
return false;
},
/**
* Called before obj is saved. Descendants can overrride this method.
*/
prepareSaveData: function(obj)
{
// do nothing here
},
/** @private */
_molNeedAutoCoordGeneration: function(mol, coordMode)
{
var result = (mol instanceof Kekule.StructureFragment) && (mol.getNodeCount() > 0) && !mol.nodesHasCoordOfMode(this.getCoordMode(), this.getAllowCoordBorrow(), true);
return result;
},
/** @private */
_isCoordGeneratorAvailable: function(coordMode)
{
if ( Kekule.Calculator && Kekule.Calculator.Services)
{
var serviceName = (coordMode === Kekule.CoordMode.COORD3D) ? Kekule.Calculator.Services.GEN3D : Kekule.Calculator.Services.GEN2D;
return Kekule.Calculator.hasService(serviceName)
}
else
return false;
},
/**
* Returns whether the coord auto generation can be done in current displayer.
* @returns {Bool}
*/
canAutoGenerateCoordForChemObj: function(chemObj, coordMode)
{
if (!chemObj)
chemObj = this.getChemObj();
if (Kekule.ObjUtils.isUnset(coordMode))
coordMode = this.getCoordMode();
var result = (chemObj && chemObj instanceof Kekule.StructureFragment
&& chemObj.hasCtab()
&& Kekule.Calculator && Kekule.Calculator.generateStructure
&& this._isCoordGeneratorAvailable(coordMode));
return result;
},
/** @private */
_tryAutoGenerateChemObjCoords: function(chemObj, coordMode, callback)
{
if (Kekule.ObjUtils.isUnset(coordMode))
coordMode = this.getCoordMode();
/*
if (chemObj instanceof Kekule.Molecule
&& this.getDisplayerConfigs().getIoConfigs().getAutoGenerateCoordsAfterLoad()
&& Kekule.Calculator && Kekule.Calculator.generateStructure) // can auto generate coords
*/
if (this.canAutoGenerateCoordForChemObj(chemObj, coordMode))
{
var is3D = coordMode === Kekule.CoordMode.COORD3D;
//var hasCoords = (chemObj.getNodeCount() <= 0) ||