UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,457 lines (1,388 loc) 74.1 kB
/** * @fileoverview * A chem object setter widget based on Viewer. * This widget is mainly designed for extra web editor plugins or browser addons. * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /utils/kekule.utils.js * requires /xbrowsers/kekule.x.js * requires /core/kekule.common.js * requires /widgets/kekule.widget.base.js * requires /widgets/kekule.widget.helpers.js * requires /widgets/commonCtrls/kekule.widget.tabViewes.js * requires /widgets/commonCtrls/kekule.widget.buttons.js * requires /widgets/commonCtrls/kekule.widget.formControls.js * requires /widgets/advCtrls/kekule.widget.colorPickers.js * requires /widgets/chem/kekule.chemWidget.base.js * requires /widgets/chem/kekule.chemWidget.chemObjDisplayers.js * requires /widgets/chem/viewer/kekule.chemWidget.viewers.js * requires /widgets/chem/viewer/kekule.chemWidget.spectrumInspectors.js * requires /widgets/advCtrls/kekule.widget.widgetGrids.js * requires /widgets/chem/kekule.chemWidget.dialogs.js * requires /calculation/kekule.calc.base.js * * requires /localization/kekule.localize.widget.js */ (function(){ "use strict"; var PS = Class.PropertyScope; var OU = Kekule.ObjUtils; var DU = Kekule.DomUtils; var AU = Kekule.ArrayUtils; var CW = Kekule.ChemWidget; var BNS = Kekule.ChemWidget.ComponentWidgetNames; //var CWT = Kekule.ChemWidgetTexts; /** @ignore */ Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, { CHEMOBJSETTER: 'K-Chem-Obj-Setter', CHEMOBJSETTER_FLEX_LAYOUT: 'K-Chem-Obj-Setter-Flex-Layout', // a special class indicating that the composer is using CSS flex layout CHEMOBJSETTER_TOOLBAR_AREA: 'K-Chem-Obj-Setter-Toolbar-Area', CHEMOBJSETTER_CLIENT: 'K-Chem-Obj-Setter-Client', CHEMOBJSETTER_VIEWER: 'K-Chem-Obj-Setter-Viewer', CHEMOBJSETTER_TABGROUP: 'K-Chem-Obj-Setter-TabGroup', CHEMOBJSETTER_INFOLABEL: 'K-Chem-Obj-Setter-InfoLabel', CHEMOBJSETTER_STATUSLABEL: 'K-Chem-Obj-Setter-StatusLabel', CHEMOBJSETTER_REGION: 'K-Chem-Obj-Setter-Region', CHEMOBJSETTER_REGION_LABEL: 'K-Chem-Obj-Setter-Region-Label', CHEMOBJSETTER_LINE: 'K-Chem-Obj-Setter-Line', //CHEMOBJSETTER_OPTIONPANEL: 'K-Chem-Obj-Setter-OptionPanel', CHEMOBJSETTER_CONFIGURATOR: 'K-Chem-Obj-Setter-Configurator', SPECTRUMOBJ_INSERTER: 'K-SpectrumObjInserter', SPECTRUMOBJ_INSERTER_CONTAINER_ELEM: 'K-SpectrumObjInserter-Container', SPECTRUMOBJ_INSERTER_CLIENT: 'K-SpectrumObjInserter-Client', SPECTRUMOBJ_INSERTER_TOOLBAR_AREA: 'K-SpectrumObjInserter-Toolbar-Area', SPECTRUMOBJ_INSERTER_INFOLABEL: 'K-SpectrumObjInserter-InfoLabel', SPECTRUMOBJ_INSERTER_SPECTRUM_INSPECTOR: 'K-SpectrumObjInserter-SpectrumInspector', SPECTRUMOBJ_INSERTER_CONFIGURATOR: 'K-SpectrumObjInserter-Configurator', }); var CNS = Kekule.Widget.HtmlClassNames; var CCNS = Kekule.ChemWidget.HtmlClassNames; /** @ignore */ Kekule.globalOptions.add('chemWidget.chemObjInserter', { 'autoSizeExport': true, 'backgroundColor3D': '#000000', 'exportViewerPredefinedSetting': 'basic', 'enable3DStructureAutoGeneration': false, toolButtons: [ BNS.loadData, BNS.saveData, BNS.clearObjs, BNS.molDisplayType, BNS.molHideHydrogens, BNS.molAutoGenerateCoords, BNS.zoomIn, BNS.zoomOut, BNS.rotateX, BNS.rotateY, BNS.rotateZ, BNS.rotateLeft, BNS.rotateRight, BNS.reset, //BNS.copy, BNS.openEditor, BNS.config ] }); /** * A chem widget to insert chem viewer elements to HTML document. * This widget is mainly designed for extra web editor plugins or browser addons. * @class * @augments Kekule.ChemWidget.AbstractWidget * * @property {Kekule.ChemWidget.Viewer} viewer The viewer instance embedded in this widget. * @property {Int} renderType Display in 2D or 3D. Value from {@link Kekule.Render.RendererType}. * @property {Kekule.ChemObject} chemObj The root object in viewer. * @property {Bool} showInfo Whether info label is shown. * //@property {Bool} enable3DStructureGeneration Whether show an extra button to convert 2D structures to 3D. * @property {Bool} enable3DStructureAutoGeneration Whether generate the 3D structure from 2D automatically when the 3D tab is empty. */ Kekule.ChemWidget.ChemObjInserter = Class.create(Kekule.ChemWidget.AbstractWidget, /** @lends Kekule.ChemWidget.ChemObjInserter# */ { /** @private */ CLASS_NAME: 'Kekule.ChemWidget.ChemObjInserter', /** @private */ DEF_BGCOLOR_2D: 'transparent', /** @private */ DEF_BGCOLOR_3D: '#000000', /** @construct */ initialize: function(/*$super, */parentOrElementOrDocument, chemObj, renderType, viewerConfigs) { this._configAction = new Kekule.Widget.ActionOpenConfigWidget(this); this._toolbarParentElem = null; this._infoLabel = null; this._infoLabelTemplate = Kekule.$L('ChemWidgetTexts.CAPTION_WIDTH_HEIGHT'); //this.setPropStoreFieldValue('enable3DStructureGeneration', true); //this.setPropStoreFieldValue('enable3DStructureAutoGeneration', true); this.tryApplySuper('initialize', [parentOrElementOrDocument]) /* $super(parentOrElementOrDocument) */; var viewer = this.getViewer(); if (renderType) viewer.setRenderType(renderType); if (viewerConfigs) viewer.setViewerConfigs(viewerConfigs); this.adjustChildrenSizes(); }, /** @private */ doFinalize: function(/*$super*/) { if (this._configAction) this._configAction.finalize(); var viewer = this.getPropStoreFieldValue('viewer'); if (viewer) viewer.finalize(); this.tryApplySuper('doFinalize') /* $super() */; }, /** @private */ initProperties: function() { this.defineProp('viewer', { 'dataType': 'Kekule.ChemWidget.Viewer', 'serializable': false, 'setter': null, 'getter': function() { var result = this.getPropStoreFieldValue('viewer'); if (!result) { result = this.createViewer(); this.setPropStoreFieldValue('viewer', result); } return result; } }); this.defineProp('chemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false, 'scope': PS.PUBLIC, 'getter': function() { /* var viewer = this.getPropStoreFieldValue('viewer'); return viewer? viewer.getChemObj(): this.getPropStoreFieldValue('chemObj'); */ return this.getPropStoreFieldValue('chemObj'); }, '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); if (this.getViewer()) this.getViewer().setChemObj(value); } } }); // 3D structure generated from chemObj, private property this.defineProp('assoc3DChemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false, 'scope': PS.PRIVATE, 'setter': function(value) { var old = this.getAssoc3DChemObj(); if (value !== old) { this.setPropStoreFieldValue('assoc3DChemObj', value); if (this.getRenderType() === Kekule.Render.RendererType.R3D) { var newDisplayedChemObj = this._getDisplayedChemObjForRenderType(this.getRenderType()); this._changeDisplayedChemObj(newDisplayedChemObj); } } } }); // Current displayed object in viewer, may not same to chemObj property, private this.defineProp('currDisplayedChemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false, 'scope': PS.PRIVATE, 'setter': null, 'getter': function() { var viewer = this.getPropStoreFieldValue('viewer'); return viewer && viewer.getChemObj(); } }); this.defineProp('renderType', {'dataType': DataType.INT, 'serializable': false, 'scope': PS.PUBLIC, 'setter': function(value) { var oldValue = this.getRenderType(); if (value !== oldValue) { this.setPropStoreFieldValue('renderType', value); var oldDisplayedChemObj = this._getDisplayedChemObjForRenderType(oldValue); var newDisplayedChemObj = this._getDisplayedChemObjForRenderType(value); this.getViewer().setRenderType(value); if (oldDisplayedChemObj !== newDisplayedChemObj) { this._changeDisplayedChemObj(newDisplayedChemObj); } var tabs = this.getTabs(); if (tabs) { var index = (value === Kekule.Render.RendererType.R3D)? 1: 0; tabs.getChildWidgets()[index].setChecked(true); } } } }); this.defineProp('is3D', {'dataType': DataType.BOOL, 'serializable': false, 'getter': function() { return this.getRenderType() === Kekule.Render.RendererType.R3D; }, 'setter': function(value) { this.setRenderType(value? Kekule.Render.RendererType.R3D: Kekule.Render.RendererType.R2D); } }); //this.defineProp('enable3DStructureGeneration', {'dataType': DataType.BOOL}); this.defineProp('enable3DStructureAutoGeneration', {'dataType': DataType.BOOL}); this.defineProp('showInfo', {'dataType': DataType.BOOL, 'serializable': false, 'getter': function() { return this._infoLabel && Kekule.StyleUtils.isShown(this._infoLabel); }, 'setter': function(value) { if (this._infoLabel) Kekule.StyleUtils.setDisplay(this._infoLabel, value); } }); this.defineProp('statusMessage', {'dataType': DataType.STRING, 'serializable': false, 'scope': PS.PUBLIC, 'getter': function() { var elem = this._statusLabel; return elem && elem.innerHTML; }, 'setter': function(value) { this._updateStatusLabel(value); } }); this.defineProp('autoSizeExport', {'dataType': DataType.BOOL}); this.defineProp('backgroundColor2D', {'dataType': DataType.STRING, 'setter': function(value) { this.setPropStoreFieldValue('backgroundColor2D', value); this.backgroundColorChange(); } }); this.defineProp('backgroundColor3D', {'dataType': DataType.STRING, 'setter': function(value) { this.setPropStoreFieldValue('backgroundColor3D', value); this.backgroundColorChange(); } }); this.defineProp('exportViewerPredefinedSetting', {'dataType': DataType.STRING}); this.defineProp('clientPanel', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': false, 'scope': PS.PRIVATE}); this.defineProp('tabs', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': false, 'scope': PS.PRIVATE}); //this.defineProp('optionPanel', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': false, 'scope': PS.PRIVATE}); //this.defineProp('infoLabel', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': false, 'scope': PS.PRIVATE}); // viewer delegated property // from ChemObjDisplayer this._defineViewerDelegatedProp('chemObjLoaded'); this._defineViewerDelegatedProp('renderConfigs'); this._defineViewerDelegatedProp('drawOptions'); this._defineViewerDelegatedProp('allowCoordBorrow'); // from viewer this._defineViewerDelegatedProp('autoSize'); this._defineViewerDelegatedProp('autofit'); this._defineViewerDelegatedProp('autoShrink'); this._defineViewerDelegatedProp('viewerConfigs'); this._defineViewerDelegatedProp('allowedMolDisplayTypes'); this._defineViewerDelegatedProp('enableEdit'); this._defineViewerDelegatedProp('enableEditFromVoid'); this._defineViewerDelegatedProp('modalEdit'); this._defineViewerDelegatedProp('restrainEditorWithCurrObj'); this._defineViewerDelegatedProp('editorProperties'); this._defineViewerDelegatedProp('toolButtons'); this._defineViewerDelegatedProp('enableDirectInteraction'); this._defineViewerDelegatedProp('enableTouchInteraction'); this._defineViewerDelegatedProp('enableGesture'); }, /** @ignore */ initPropValues: function(/*$super*/) { this.tryApplySuper('initPropValues') /* $super() */; /* this.setAutoSizeExport(true); this.setBackgroundColor3D(this.DEF_BGCOLOR_3D); this.setExportViewerPredefinedSetting('basic'); */ var options = Object.extend({ 'autoSizeExport': true, 'backgroundColor3D': this.DEF_BGCOLOR_3D, 'exportViewerPredefinedSetting': 'basic', 'enable3DStructureAutoGeneration': false, }, Kekule.globalOptions.get('chemWidget.chemObjInserter'), {}); this.setAutoSizeExport(options.autoSizeExport); this.setBackgroundColor3D(options.backgroundColor3D); this.setExportViewerPredefinedSetting(options.exportViewerPredefinedSetting); }, /** * Define property that directly mapped to viewer's property. * @param {String} propName * @param {String} viewerPropName Name of corresponding property in editor. * @return {Object} Property info object added to property list. * @private */ _defineViewerDelegatedProp: function(propName, viewerPropName) { if (!viewerPropName) viewerPropName = propName; var viewerPropInfo = ClassEx.getPropInfo(Kekule.ChemWidget.Viewer, viewerPropName); var propOptions = Object.create(viewerPropInfo); propOptions.getter = null; propOptions.setter = null; if (viewerPropInfo.getter) { propOptions.getter = function() { return this.getViewer().getPropValue(viewerPropName); }; } if (viewerPropInfo.setter) { propOptions.setter = function(value) { this.getViewer().setPropValue(viewerPropName, value); } } return this.defineProp(propName, propOptions); }, /** @private */ loadPredefinedResDataToProp: function(propName, resData, success) { if (propName === 'chemObj') // only this property can be set by predefined resource { if (success) { var chemObj = Kekule.IO.loadTypedData(resData.data, resData.resType, resData.resUri); this.setChemObj(chemObj); } else // else, failed { Kekule.error(Kekule.$L('ErrorMsg.CANNOT_LOAD_RES_OF_URI') + resData.resUri || ''); } } }, /** @ignore */ doGetWidgetClassName: function(/*$super*/) { var result = this.tryApplySuper('doGetWidgetClassName') /* $super() */ + ' ' + CCNS.CHEMOBJSETTER; if (this._isUsingFlexLayout()) result += ' ' + CCNS.CHEMOBJSETTER_FLEX_LAYOUT; return result; }, /** @private */ _isUsingFlexLayout: function() { return Kekule.BrowserFeature.cssFlex; }, /** @ignore */ doCreateRootElement: function(doc) { var result = doc.createElement('div'); return result; }, /** @ignore */ doCreateSubElements: function(/*$super, */doc, rootElem) { var result = this.tryApplySuper('doCreateSubElements', [doc, rootElem]) /* $super(doc, rootElem) */; // create child widgets // toolbar this.createToolbarParent(rootElem); // client var clientPanel = new Kekule.Widget.Panel(this); clientPanel.addClassName(CCNS.CHEMOBJSETTER_CLIENT); clientPanel.setUseCornerDecoration(false); clientPanel.appendToElem(rootElem); this.setPropStoreFieldValue('clientPanel', clientPanel); this.createViewer(clientPanel.getCoreElement()); //this.createOptionPanel(clientPanel.getCoreElement()); this.createInfoLabel(rootElem, this.getViewer()); this.createStatusLabel(rootElem, this.getViewer()); // tab this.createTabs(rootElem); return result; }, /** @ignore */ elementBound: function(element) { this.setObserveElemResize(true); }, /** @ignore */ doResize: function(/*$super*/) { // notify children //this.getViewer().resized(); this.adjustChildrenSizes(); }, /** @ignore */ doWidgetShowStateChanged: function(/*$super, */isShown) { this.tryApplySuper('doWidgetShowStateChanged', [isShown]) /* $super(isShown) */; this.adjustChildrenSizes(); }, /** @ignore */ getResizerElement: function() { return this.getClientPanel()? this.getClientPanel().getCoreElement(): this.getCoreElement(); }, /** @private */ getBackgroundColor: function(renderType) { var result = (renderType === Kekule.Render.RendererType.R3D)? this.getBackgroundColor3D(): this.getBackgroundColor2D(); if (result === Kekule.Widget.ColorPicker.SpecialColors.TRANSPARENT) result = 'transparent'; return result; }, /** @private */ setBackgroundColor: function(color, renderType) { if (renderType === Kekule.Render.RendererType.R3D) { var value = color || this.DEF_BGCOLOR_3D; this.setBackgroundColor3D(value); } else { var value = color || this.DEF_BGCOLOR_2D; this.setBackgroundColor2D(value); } }, /** @private */ backgroundColorChange: function() { var color = this.getBackgroundColor(this.getRenderType()); var viewer = this.getPropStoreFieldValue('viewer'); if (viewer) { //console.log('set back color', color); /* var elem = viewer.getElement(); elem.style.backgroundColor = color || 'transparent'; */ viewer.setBackgroundColor(color); } }, /** * Adjust size and positions of children when widget size is changed. * @private */ adjustChildrenSizes: function() { if (this._isUsingFlexLayout()) { // do nothing here } else // traditional absolute layout { //var selfRect = this.getBoundingClientRect(); var selfRect = this.getPageRect(); //var toolbarRect = Kekule.HtmlElementUtils.getElemBoundingClientRect(this._toolbarParentElem); var toolbarRect = Kekule.HtmlElementUtils.getElemPageRect(this._toolbarParentElem); var tabs = this.getTabs(); //var tabRect = tabs && tabs.getBoundingClientRect(); var tabRect = tabs && tabs.getPageRect(); var h = tabRect.top - toolbarRect.bottom; //console.log(selfRect.height, toolbarRect.height, tabRect.height, h); this.getClientPanel().setHeight(h + 'px'); //console.log('set height', h, tabRect, toolbarRect); /* if (tabRect) { // client var panel = this.getClientPanel(); var style = panel.getElement().style; style.top = tabRect.height + 'px'; style.bottom = '0px'; panel.resized(); // viewer this.getViewer().resized(); } */ } this.getViewer().resized(); //var clientRect = this.getClientPanel().getBoundingClientRect(); }, /** @private */ createViewer: function(rootElem) { var BNS = Kekule.ChemWidget.ComponentWidgetNames; var EM = Kekule.Widget.EvokeMode; var result = new Kekule.ChemWidget.Viewer(this, null, this.getRenderType()); result.addClassName([CNS.DYN_CREATED, CCNS.CHEMOBJSETTER_VIEWER]); // set default value result.setRenderType(Kekule.Render.RendererType.R2D); result.setEnableEdit(true); result.setEnableEditFromVoid(true); result.setRestrainEditorWithCurrObj(false); // can edit anything defaultly result.setEnableToolbar(true); result.setPredefinedSetting('fullFunc'); // enable all functions of composer var self = this; result.overwriteMethod('createToolButton', function($origin, btnName, parentGroup){ if (btnName === BNS.config) { var result = new Kekule.Widget.Button(parentGroup); var action = self._configAction if (action) result.setAction(action); return result; } else return $origin(btnName, parentGroup); }); /* var buttons = AU.exclude(result.getDefaultToolBarButtons(), BNS.config); buttons.push({ 'action': this._configAction }); buttons.splice(2, 0, BNS.clearObjs); */ var buttons = Kekule.globalOptions.chemWidget.chemObjInserter.toolButtons; result.setToolbarParentElem(this._toolbarParentElem); result.setToolButtons(buttons); //.concat([{'text': 'MyButton', 'hint': 'Custom'}])); result.setToolbarPos(Kekule.Widget.Position.BOTTOM); result.setToolbarMarginVertical(-2); result.setToolbarEvokeModes([EM.ALWAYS]); result.addEventListener('resize', this.updateInfoLabel, this); result.addEventListener('load', this._reactViewerLoad, this); result.appendToElem(rootElem); //result.setToolbarEvokeModes([EM.ALWAYS]); result.setChemObj(this.getChemObj()); this.setPropStoreFieldValue('viewer', result); this.backgroundColorChange(); // force change background color return result; }, /** @private */ _reactViewerLoad: function(e) { //console.log('load', e); // when loading a new object, empty the assoc3DChemObj if (!this._changingDisplayedChemObjFlag || this._changingDisplayedChemObjFlag <= 0) { var chemObj = e.obj || null; this.setPropStoreFieldValue('chemObj', chemObj); this.setAssoc3DChemObj(null); this.tryAutoGenerate3DCoordsForChemObjInViewer(); } }, /** @private */ createToolbarParent: function(rootElem) { var result = rootElem.ownerDocument.createElement('div'); result.className = CCNS.CHEMOBJSETTER_TOOLBAR_AREA; rootElem.appendChild(result); this._toolbarParentElem = result; return result; }, /** @private */ createTabs: function(rootElem) { var tabTexts = [Kekule.$L('ChemWidgetTexts.CAPTION_2D'), Kekule.$L('ChemWidgetTexts.CAPTION_3D')]; var selIndex = (this.getRenderType() === Kekule.Render.RendererType.R3D)? 1: 0; var result = new Kekule.Widget.TabButtonGroup(this); result.setTabButtonPosition(Kekule.Widget.Position.BOTTOM); result.addClassName([CNS.DYN_CREATED, CCNS.CHEMOBJSETTER_TABGROUP]); var btns = []; for (var i = 0, l = tabTexts.length; i < l; ++i) { var btn = new Kekule.Widget.RadioButton(result); btn.setText(tabTexts[i]); if (i === selIndex) btn.setChecked(true); btns.push(btn); } result.addEventListener('switch', function(e){ var btn = e.button; var index = btns.indexOf(btn); var rType = (index === 1)? Kekule.Render.RendererType.R3D: Kekule.Render.RendererType.R2D; this.setRenderType(rType); this.backgroundColorChange(); // force change background color this.tryAutoGenerate3DCoordsForChemObjInViewer(); }, this); result.appendToElem(rootElem); this.setPropStoreFieldValue('tabs', result); return result; }, /* @private */ /* createOptionPanel: function(rootElem) { var result = new Kekule.Widget.Panel(this); result.addClassName([CNS.DYN_CREATED, CCNS.CHEMOBJSETTER_OPTIONPANEL]); result.appendToElem(rootElem); this.setPropStoreFieldValue('optionPanel', result); return result; }, */ /** @private */ createInfoLabel: function(rootElem, viewer) { var result = this.getDocument().createElement('div'); result.className = CNS.DYN_CREATED + ' ' + CCNS.CHEMOBJSETTER_INFOLABEL; viewer.getElement().appendChild(result); this._infoLabel = result; return result; }, /** @private */ updateInfoLabel: function() { var viewer = this.getViewer(); if (viewer && this._infoLabel) { var dim = viewer.getContextDimension(); var s = dim? this._infoLabelTemplate.format(Math.round(dim.width), Math.round(dim.height)): ''; Kekule.DomUtils.setElementText(this._infoLabel, s); } }, /** @private */ createStatusLabel: function(rootElem, viewer) { var result = this.getDocument().createElement('div'); result.className = CNS.DYN_CREATED + ' ' + CCNS.CHEMOBJSETTER_STATUSLABEL; //result.innerHTML = 'status'; viewer.getElement().appendChild(result); this._statusLabel = result; return result; }, /** @private */ _updateStatusLabel: function(message) { if (this._statusLabel) { this._statusLabel.innerHTML = message; } }, /** * Returns dimension of viewer context. * @returns {Hash} */ getContextDimension: function() { return this.getViewer().getContextDimension(); }, /** * Resize self to make viewer context at dimension. * @param {Hash} dimension */ setContextDimension: function(dimension) { var oldDim = this.getContextDimension(); var deltaW = dimension.width - oldDim.width; var deltaH = dimension.height - oldDim.height; //var elem = this.getElement(); //Kekule.StyleUtils.getComputedStyle() var selfOldDim = this.getDimension(); var selfNewDim = {'width': selfOldDim.width + deltaW, 'height': selfOldDim.height + deltaH}; this.setDimension(selfNewDim.width, selfNewDim.height); return this; }, /** @private */ _getDisplayedChemObjForRenderType: function(renderType) { if (renderType === Kekule.Render.RendererType.R3D) return this.getAssoc3DChemObj() || this.getChemObj(); else return this.getChemObj(); }, /** @private */ _changeDisplayedChemObj: function(chemObj) { if (!this._changingDisplayedChemObjFlag) this._changingDisplayedChemObjFlag = 0 ++this._changingDisplayedChemObjFlag; try { this.getViewer().setChemObj(chemObj); } finally { --this._changingDisplayedChemObjFlag; } }, // methods about 3D structure generation /** * Returns the child object of chemObj that containing ctab. * @returns {Bool} * @private */ _getContainingMoleculesWithCtab: function(chemObj) { if (!chemObj) chemObj = this.getChemObj(); if (chemObj instanceof Kekule.StructureFragment) return chemObj.hasCtab()? [chemObj]: []; else { var result = []; var structFragments = Kekule.ChemStructureUtils.getAllStructFragments(chemObj); for (var i = 0, l = structFragments.length; i < l; ++i) { var mol = structFragments[i]; if (mol.hasCtab && mol.hasCtab()) result.push(mol); } return result; } }, /** * Check if the chemObj is a single molecule without 3D coordinate and can do the 3D generation, if true, returns the generation target. * @returns {Kekule.StructureFragment} * @private */ _get3DCoordGenerationTargetForChemObj: function(chemObj) { if (!chemObj) chemObj = this.getChemObj(); var mols = this._getContainingMoleculesWithCtab(chemObj); if (mols && mols.length === 1) { var mol = mols[0]; return mol.nodesHasCoord3D(false)? null: mol; } else return null; }, /** @private */ _isCoordGeneratorAvailable: function(coordMode) { return this.getViewer()._isCoordGeneratorAvailable(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 a 3D coordinates generation can be done to current loaded chem object. * @private */ /* canGenerate3DCoordForChemObj: function(chemObj) { if (!chemObj) chemObj = this.getChemObj(); var result = !!(this._isCoordGeneratorAvailable(Kekule.CoordMode.COORD3D) && chemObj && this._get3DCoordGenerationTargetForChemObj(chemObj)); return result; }, */ /** * Automatically generate 3D coordinates for loaded chem object. * @param {Func} callback Callback of the 3D generation, has params (err, generatedMol); * @private */ doGenerate3DCoordsForChemObjInViewer: function(callback) { var chemObj = this.getChemObj(); var self = this; var done = function(err, generatedMol) { self.setStatusMessage(''); // calculation done, clear status if (callback) callback(err, generatedMol); }; if (chemObj) { //if (Kekule.Calculator && Kekule.Calculator.hasService(Kekule.Calculator.Services.GEN3D)) if (this._isCoordGeneratorAvailable(Kekule.CoordMode.COORD3D)) { var targetMol = this._get3DCoordGenerationTargetForChemObj(chemObj); if (targetMol) // we really can do the generation { self.setStatusMessage(Kekule.$L('ChemWidgetTexts.CAPTION_CHEMOBJINSERTER_GENERATING_3D_STRUCTURE')); /* Kekule.Calculator.generate3D(targetMol, null, function(generatedMol){ done(null, generatedMol); }, function(err){ //console.log('error', err); done(err, null); }); */ try { this.getViewer().autoGenerateChemObjCoords(done.bind(this, null), targetMol, Kekule.CoordMode.COORD3D); } catch(e) { done(e, chemObj); } return this; } } } // default, can not do the generation, return chemObj directly done(null, chemObj); return this; }, /** * Automatically generate 3D coordinates for loaded chem object. * @param {Func} callback Callback of the 3D generation, has params (err, generatedMol); * @private */ generate3DCoordsForChemObjInViewer: function(callback) { var self = this; this.doGenerate3DCoordsForChemObjInViewer(function(err, generatedMol){ if (!err && generatedMol) // the generation is done, show generatedMol in 3D viewer { self.setAssoc3DChemObj(generatedMol); } }); }, /** @private */ _isMolContainingCoordsOfMode: function(mol, coordMode) { var hasCoords = (mol.getNodeCount() <= 0) || mol.nodesHasCoordOfMode(coordMode, false, true); return hasCoords; }, /** * If property enable3DStructureAutoGeneration is true, this function will automatically generate the 3D structure when switching to or loading mol in 3D viewer. * @param {Func} callback Callback of the 3D generation, has params (err, generatedMol); * @private */ tryAutoGenerate3DCoordsForChemObjInViewer: function(callback) { if (this.getEnable3DStructureAutoGeneration()) { var renderType = this.getRenderType(); if (renderType === Kekule.Render.RendererType.R3D && !this.getAssoc3DChemObj()) { var chemObj = this.getChemObj(); var targetObj = this._get3DCoordGenerationTargetForChemObj(chemObj); if (targetObj && !this._isMolContainingCoordsOfMode(targetObj, Kekule.CoordMode.COORD3D)) // need to auto generate { this.generate3DCoordsForChemObjInViewer(callback); } } } }, // methods of export /** * Export drawing content in viewer to a data URL for <img> tag to use. * @param {String} dataType Type of image data, e.g. 'image/png'. * @param {Hash} options Export options, usually this is a number between 0 and 1 * indicating image quality if the requested type is image/jpeg or image/webp. * @returns {String} */ exportToDataUri: function(dataType, options) { /* if (this.getAutoSizeExport()) this.setAutoSize(true); */ var result = this.getViewer().exportToDataUri(dataType, options); //alert(result); /* if (this.getAutoSizeExport()) // restore { this.setAutoSize(false); var elem = this.getViewer().getElement(); elem.style.width = 'auto'; elem.style.height = 'auto'; this.getViewer().resized(); } */ return result; }, /** * Export drawing content in viewer and with additional informations (such as draw options). * @param {String} dataType * @param {Hash}options * @returns {Hash} * @deprecated */ exportDetails: function(dataType, options) { if (this.getAutoSizeExport()) this.setAutoSize(true); var ops = this.getViewer().getActualDrawOptions(); var dim = this.getViewer().getContextDimension(); var currDisplayedObj = this.getCurrDisplayedChemObj(); var result = currDisplayedObj? { 'dataUri': this.exportToDataUri(dataType, options), 'drawOptions': ops, 'drawOptionsJson': JSON.stringify(ops), 'chemObj': currDisplayedObj, //this.getChemObj(), 'chemObjJson': Kekule.IO.saveMimeData(currDisplayedObj, 'chemical/x-kekule-json'), 'autoSize': this.getAutoSizeExport(), 'autofit': this.getAutofit(), 'width': Math.round(dim.width), 'height': Math.round(dim.height), 'renderType': this.getRenderType() || Kekule.Render.RendererType.R2D, 'backgroundColor': this.getBackgroundColor(this.getRenderType()), 'predefinedSetting': this.getExportViewerPredefinedSetting() }: null; if (this.getAutoSizeExport()) // restore { this.setAutoSize(false); var elem = this.getViewer().getElement(); elem.style.width = 'auto'; elem.style.height = 'auto'; this.getViewer().resized(); } return result; }, /** * Returns the detail information about export drawing content in viewer and with additional informations. * @param {String} dataType * @param {Hash}options * @param {Func} callback Callback function with a Hash param details. * @deprecated */ getImgExportDetailsAsync: function(imgDataType, options, callback) { var details = this.exportDetails(imgDataType, options); callback(details); }, /** * If export viewer to a HTML img element, this method returns the essential attributes. * @param {String} dataType Export image data type. * @param {Hash} options * @returns {Hash} Attribute/value pairs. * @deprecated */ getExportImgElemAttributes: function(dataType, options) { var detail = this.exportDetails(dataType, options); if (!detail) return null; var style = 'width:' + detail.width + 'px; height:' + detail.height + 'px'; /* if (detail.backgroundColor) style += '; background-color: ' + detail.backgroundColor; else if (this.getIs3D()) style += '; background-color: #000'; */ var result = { 'src': detail.dataUri, 'style': style, 'width': detail.width, 'height': detail.height, 'data-kekule-widget': 'Kekule.ChemWidget.Viewer', 'data-render-type': detail.renderType, 'data-chem-obj': detail.chemObjJson, 'data-draw-options': detail.drawOptionsJson, 'data-predefined-setting': detail.predefinedSetting }; if (Kekule.ObjUtils.notUnset(detail.autoSize)) result['data-auto-size'] = detail.autoSize; if (Kekule.ObjUtils.notUnset(detail.autofit)) result['data-autofit'] = detail.autofit; if (Kekule.ObjUtils.notUnset(detail.backgroundColor)) result['data-background-color'] = detail.backgroundColor; return result; }, /** * If export viewer to a HTML img element, this method returns the essential attributes. * @param {String} dataType Export image data type. * @param {Hash} options * @param {Func} callback A call back function with attribs param. */ getExportImgElemAttributesAsync: function(dataType, options, callback) { var attribs = this.getExportImgElemAttributes(dataType, options); callback(attribs); }, /** * Export viewer to a new created HTML img element. * @param {HTMLElement} doc * @param {String} dataType Export image data type * @param {Hash} options * @returns {HTMLElement} */ createExportImgElement: function(doc, dataType, options) { var attribs = this.getExportImgElemAttributes(dataType, options) || {}; if (attribs) { //var detail = this.exportDetails(dataType, options); var result = doc.createElement('img'); Kekule.DomUtils.setElemAttributes(result, attribs); return result; } else return null; }, /** * Export viewer to a new created HTML element. * @param {HTMLElement} doc * @param {String} imgDataType Export image data type * @param {Hash} options * @returns {HTMLElement} */ createExportHtmlElement: function(doc, imgDataType, options) { return this.createExportImgElement(doc, imgDataType, options); }, /** * Export viewer to a new created HTML element (async way). * @param {HTMLElement} doc * @param {String} imgDataType Export image data type * @param {Hash} options * @param {Func} callback callback(htmlElement) * @returns {HTMLElement} */ createExportHtmlElementAsync: function(doc, imgDataType, options, callback) { try { var elem = this.createExportImgElement(doc, imgDataType, options); if (callback) callback(elem); } catch(e) { callback(null); throw e; } }, /** * Load a chem object and apply settings in this widget from specified attribs. * @param {Kekule.ChemObject} chemObj * @param {Hash} detail * @deprecated */ importChemObjWithDetails: function(chemObj, detail) { this.setChemObj(chemObj); //if (detail.renderType) this.setRenderType(detail.renderType || Kekule.Render.RendererType.R2D); //console.log(detail); if (Kekule.ObjUtils.notUnset(detail.autoSize)) this.setAutoSizeExport(!!detail.autoSize); if (Kekule.ObjUtils.notUnset(detail.autofit)) this.setAutofit(!!detail.autofit); if (detail.width && detail.height && (!detail.autoSize || detail.renderType === Kekule.Render.RendererType.R3D)) this.setContextDimension({'width': detail.width, 'height': detail.height}); if (detail.drawOptions) this.getViewer().setDrawOptions(detail.drawOptions); if (detail.backgroundColor) this.setBackgroundColor(detail.backgroundColor, this.getRenderType()); return this; }, /** * Load a chem object and apply settings in this widget from specified attribs. * @param {Kekule.ChemObject} chemObj * @param {Hash} detail */ importFromDetails: function(chemObj, detail) { return this.importChemObjWithDetails(chemObj, details); }, /** * Load a chem object and apply settings in this widget from hash attribs of an HTML element prviously exported. * @param {Hash} attribs */ importFromElemAttribs: function(attribs) { //if (!attribs.width) attribs.width = DataType.JsonUtility.parse(attribs.width); //if (!attribs.height) attribs.height = DataType.JsonUtility.parse(attribs.height); var chemObjJson = attribs['data-chem-obj']; var chemObj = chemObjJson? Kekule.IO.loadMimeData(chemObjJson, 'chemical/x-kekule-json'): null; if (attribs['data-render-type']) attribs.renderType = DataType.JsonUtility.parse(attribs['data-render-type']); if (attribs['data-draw-options']) attribs.drawOptions = DataType.JsonUtility.parse(attribs['data-draw-options']); if (attribs['data-auto-size']) attribs.autoSize = DataType.JsonUtility.parse(attribs['data-auto-size']); if (attribs['data-autofit']) attribs.autofit = DataType.JsonUtility.parse(attribs['data-autofit']); if (attribs['data-background-color']) attribs.backgroundColor = attribs['data-background-color']; return this.importChemObjWithDetails(chemObj, attribs); }, /** * Load a chem object and apply settings in this widget from an HTML element previously exported. * @param {HTMLElement} element */ importFromElem: function(element) { //var dim = Kekule.HtmlElementUtils.getElemBoundingClientRect(element); var dim = Kekule.HtmlElementUtils.getElemPageRect(element); var attribs = Kekule.DomUtils.fetchAttributeValuesToJson(element); if (!attribs.width) attribs.width = dim.width; if (!attribs.height) attribs.height = dim.height; return this.importFromElemAttribs(attribs); } }); /** * A special widget class to open a config widget for ChemObjDisplayer. * Do not use this widget alone. * @class * @augments Kekule.Widget.Configurator */ Kekule.ChemWidget.ChemObjInserter.Configurator = Class.create(Kekule.Widget.Configurator, /** @lends Kekule.ChemWidget.ChemObjInserter.Configurator# */ { /** @private */ CLASS_NAME: 'Kekule.ChemWidget.ChemObjInserter.Configurator', /** @construct */ initialize: function(/*$super, */widget) { this._checkBoxAutoSize = null; this._checkBoxAutofit = null; this._checkBoxShowInfo = null; this._textBoxWidth = null; this._textBoxHeight = null; this._colorPicker = null; this.tryApplySuper('initialize', [widget]) /* $super(widget) */; this.addEventListener('valueChange', function(e){ this.saveConfigValues(); }, this); }, /** @ignore */ initPropValues: function(/*$super*/) { this.tryApplySuper('initPropValues') /* $super() */; this.setAutoUpdate(true); }, /** @ignore */ doGetWidgetClassName: function(/*$super*/) { return this.tryApplySuper('doGetWidgetClassName') /* $super() */ + ' ' + CCNS.CHEMOBJSETTER_CONFIGURATOR; }, /** @ignore */ doCreateSubElements: function(doc, element) { // autosize and autofit var region = doc.createElement('div'); region.className = CCNS.CHEMOBJSETTER_REGION; var checkBox = new Kekule.Widget.CheckBox(this); checkBox.setText(Kekule.$L('ChemWidgetTexts.CAPTION_AUTOSIZE')); checkBox.addClassName(CCNS.CHEMOBJSETTER_LINE); checkBox.appendToElem(region); this._checkBoxAutoSize = checkBox; var assocText = doc.createElement('span'); assocText.className = CNS.PART_ASSOC_TEXT_CONTENT; DU.setElementText(assocText, Kekule.$L('ChemWidgetTexts.HINT_AUTOSIZE')); region.appendChild(assocText); var checkBox = new Kekule.Widget.CheckBox(this); checkBox.setText(Kekule.$L('ChemWidgetTexts.CAPTION_AUTOFIT')); checkBox.addClassName(CCNS.CHEMOBJSETTER_LINE); checkBox.appendToElem(region); this._checkBoxAutofit = checkBox; var assocText = doc.createElement('span'); assocText.className = CNS.PART_ASSOC_TEXT_CONTENT; DU.setElementText(assocText, Kekule.$L('ChemWidgetTexts.HINT_AUTOFIT')); region.appendChild(assocText); element.appendChild(region); // width/height setter var region = doc.createElement('div'); region.className = CCNS.CHEMOBJSETTER_REGION; var labelElem = doc.createElement('label'); labelElem.className = CCNS.CHEMOBJSETTER_REGION_LABEL; DU.setElementText(labelElem, Kekule.$L('ChemWidgetTexts.CAPTION_LABEL_SIZE')); region.appendChild(labelElem); region.appendChild(doc.createElement('br')); var textBox = new Kekule.Widget.TextBox(this); textBox.setPlaceholder(Kekule.$L('ChemWidgetTexts.PLACEHOLDER_WIDTH')); textBox.appendToElem(region); this._textBoxWidth = textBox; var labelElem = doc.createElement('span'); DU.setElementText(labelElem, '×'); region.appendChild(labelElem); var textBox = new Kekule.Widget.TextBox(this); textBox.setPlaceholder(Kekule.$L('ChemWidgetTexts.PLACEHOLDER_HEIGHT')); textBox.appendToElem(region); this._textBoxHeight = textBox; var checkBox = new Kekule.Widget.CheckBox(this); checkBox.setText(Kekule.$L('ChemWidgetTexts.CAPTION_SHOWSIZEINFO')); checkBox.appendToElem(region); this._checkBoxShowInfo = checkBox; element.appendChild(region); // background color setter var region = doc.createElement('div'); region.className = CCNS.CHEMOBJSETTER_REGION; var labelElem = doc.createElement('label'); labelElem.className = CCNS.CHEMOBJSETTER_REGION_LABEL; DU.setElementText(labelElem, Kekule.$L('ChemWidgetTexts.CAPTION_BACKGROUND_COLOR')); region.appendChild(labelElem); region.appendChild(doc.createElement('br')); var colorPicker = new Kekule.Widget.ColorPicker(this); colorPicker.setSpecialColors([ Kekule.Widget.ColorPicker.SpecialColors.TRANSPARENT ]); colorPicker.appendToElem(region); this._colorPicker = colorPicker; element.appendChild(region); }, /** @private */ loadConfigValues: function(/*$super*/) { this.tryApplySuper('loadConfigValues') /* $super() */; var w = this.getWidget(); if (w) { var is2D = !w.getIs3D(); this._checkBoxAutoSize.setChecked(is2D && w.getAutoSizeExport()); this._checkBoxAutoSize.setEnabled(is2D); this._checkBoxAutofit.setChecked(is2D && w.getAutofit()); this._checkBoxAutofit.setEnabled(is2D); this._checkBoxShowInfo.setChecked(w.getShowInfo()); var dim = w.getContextDimension(); this._textBoxWidth.setText(Math.round(dim.width)); this._textBoxHeight.setText(Math.round(dim.height)); var color = is2D? w.getBackgroundColor2D(): w.getBackgroundColor3D(); this._colorPicker.setValue(color || Kekule.Widget.ColorPicker.SpecialColors.TRANSPARENT); } }, /** @private */ saveConfigValues: function(/*$super*/) { this.tryApplySuper('saveConfigValues') /* $super() */; var w = this.getWidget(); if (w) { var is2D = !w.getIs3D(); if (is2D) { w.setAutoSizeExport(this._checkBoxAutoSize.getChecked()); w.setAutofit(this._checkBoxAutofit.getChecked()); w.setBackgroundColor2D(this._colorPicker.getValue()); } else { w.setBackgroundColor3D(this._colorPicker.getValue()); } w.setShowInfo(this._checkBoxShowInfo.getChecked()); var dim = {width: parseInt(this._textBoxWidth.getText()), height: parseInt(this._textBoxHeight.getText())}; w.setContextDimension(dim); w.invokeEvent('configSave'); // a special event invoked on parent widget, indicating the config value has been changed } } }); Kekule._registerAfterLoadSysProc(function(){ // the following code will be run after both spectroscopy and widget modules are loaded if (!Kekule.ChemWidget || !Kekule.Spectroscopy) return; /** @ignore */ Kekule.globalOptions.add('chemWidget.spectrumObjInserter', { toolButtons: [ BNS.loadData, BNS.saveData, BNS.clearObjs, BNS.changeSpectrumSection, BNS.zoomIn, BNS.zoomOut, BNS.reset, BNS.copy, BNS.config ] }); /** * A widget to insert spectrum elements to HTML document. * This widget is mainly designed for extra web editor plugins or browser addons. * @class * @augments Kekule.ChemWidget.AbstractWidget * * @property {Kekule.ChemWidget.SpectrumInspector} spectrumInspector The SpectrumInspector instance embedded in this widget. * @property {Kekule.ChemObject} chemObj The root object in spectrum inspector. * @property {Bool} showInfo Whether info label is shown. */ Kekule.ChemWidget.SpectrumObjInserter = Class.create(Kekule.ChemWidget.AbstractWidget, /** @lends Kekule.ChemWidget.SpectrumObjInserter# */ { /** @private */ CLASS_NAME: 'Kekule.ChemWidget.SpectrumObjInserter', /** @private */ DEF_BGCOLOR_2D: 'transparent', /** @construct */ initialize: function(parentOrElementOrDocument, spectrum) { this._configAction = new Kekule.Widget.ActionOpenConfigWidget(this); this._toolbarParentElem = null; this._infoLabel = null; this._infoLabelTemplate = Kekule.$L('ChemWidgetTexts.CAPTION_WIDTH_HEIGHT'); this.tryApplySuper('initialize', [parentOrElementOrDocument]); }, /** @private */ doFinalize: function() { if (this._configAction) this._configAction.finalize(); var inspector = this.getPropStoreFieldValue('spectrumInspector'); if (inspector) inspector.finalize(); var panel = this.getPropStoreFieldValue('clientPanel'); if (panel) panel.finalize(); this.tryApplySuper('doFinalize'); }, /** @private */ initProperties: function() { this.defineProp('spectrumInspector', { 'dataType': 'Kekule.ChemWidget.SpectrumInspector', 'serializable': false, 'setter': null, 'getter': function() { var result = this.getPropStoreFieldValue('spectrumInspector'); if (!result) { result = this.createSpectrumInspector(); this.setPropStoreFieldValue('spectrumInspector', result); } return result; } }); this.defineProp('chemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false, 'scope': PS.PUBLIC, 'getter': function() { var inspector = this.getPropStoreFieldValue('spectrumInspector'); return inspector? inspector.getChemObj(): this.getPropStoreFieldValue('chemObj'); }, '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); if (this.getSpectrumInspector()) this.getSpectrumInspector().setChemObj(value); } } }); this.defineProp('showInfo', {'dataType': DataType.BOOL, 'serializable': false, 'getter': function() { return this._infoLabel && Kekule.StyleUtils.isShown(this._infoLabel); }, 'setter': function(value) { if (this._infoLabel) Kekule.StyleUtils.setDisplay(this._infoLabel, value); } }); /* this.defineProp('backgroundColor', {'dataType': DataType.STRING, 'setter': function(value) { this.setPropStoreFieldValue('backgroundColor', value); this.backgroundColorChange(); } }); */ //this.defineProp('exportViewerPredefinedSetting', {'dataType': DataType.STRING}); this.defineProp('clientPanel', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': false, 'scope': PS.PRIVATE}); // spectrum inspector delegated properties this._defineSpectrumInspectorDelegatedProperties([ 'spectrumViewportRanges', 'backgroundColor', 'enableHotTrack', 'enableSelect', 'enableMultiSelect', 'enableDirectInteraction', 'enableTouchInteraction', 'enableGestureInteraction', 'chemObjLoaded', 'renderConfigs', 'viewerConfigs', 'toolButtons', 'spectrumViewerDrawOptions', 'assocViewerDrawOptions', 'assocViewerConfigs', 'assocViewerAllowCoordBorrow', 'assocViewerAutoSize', 'assocViewerAutofit', 'assocViewerAutoShrink', 'assocViewerAllowedMolDisplayTypes', 'assocViewerVisualMode', 'assocViewerSize' ]); }, /** * Defines properties directly map to properties of spectrum inspector. * The propNames is a array, each item is either a simple string for the property name (same in this and targetWidget) * or an array of [propertyNameForThis, propertyNameInTarget]. * @param {Array} propNames * @private */ _defineSpectrumInspectorDelegatedProperties: function(propNames) { var targetWidgetClass = Kekule.ChemWidget.SpectrumInspector; var self = this; var propName, targetPropName; for (var i = 0, l = propNames.length; i < l; ++i) { var item = propNames[i]; targetPropName = null; if (AU.isArray(item)) { propName = item[0]; targetPropName = item[1]; } else propName = item; if (!targetPropName) targetPropName = propName; (function(targetWidgetClass, propName, targetPropName){ var targetPropInfo = ClassEx.getPropInfo(targetWidgetClass, targetPropName); var propOptions = Object.create(targetPropInfo); propOptions.getter = null; propOptions.setter = null; propOptions.serializable = false; if (targetPropInfo.getter) { propOptions.getter = function() { var target = this.getSpectrumInspector(); return target? target.getPro