UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

1,471 lines (1,411 loc) 123 kB
/** * @fileoverview * Small widgets used by chem editor. * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /core/kekule.common.js * requires /widgets/commonCtrls/kekule.widget.containers.js * requires /widgets/commonCtrls/kekule.widget.formControls.js * requires /widgets/commonCtrls/kekule.widget.dialogs.js * requires /widgets/commonCtrls/kekule.widget.tabViews.js * requires /widgets/chem/kekule.chemWidget.base.js * requires /widgets/chem/editor/kekule.chemEditor.configs.js * requires /widgets/chem/editor/kekule.chemEditor.editorUtils.js */ (function(){ "use strict"; var OU = Kekule.ObjUtils; var AU = Kekule.ArrayUtils; var CNS = Kekule.Widget.HtmlClassNames; var CCNS = Kekule.ChemWidget.HtmlClassNames; /** @ignore */ Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, { STRUCTURE_NODE_SELECT_PANEL: 'K-Chem-StructureNodeSelectPanel', STRUCTURE_NODE_SELECT_PANEL_SET_BUTTON: 'K-Chem-StructureNodeSelectPanel-SetButton', STRUCTURE_NODE_SETTER: 'K-Chem-StructureNodeSetter', STRUCTURE_NODE_SETTER_INPUTBOX: 'K-Chem-StructureNodeSetter-InputBox', STRUCTURE_CONNECTOR_SELECT_PANEL: 'K-Chem-StructureConnectorSelectPanel', STRUCTURE_CONNECTOR_SELECT_PANEL_SET_BUTTON: 'K-Chem-StructureConnectorSelectPanel-SetButton', STRUCTURE_CONNECTOR_SELECT_PANEL_ADV: 'K-Chem-StructureConnectorSelectPanelAdv', STRUCTURE_CONNECTOR_SELECT_PANEL_ADV_EXTRA_SECTION: 'K-Chem-StructureConnectorSelectPanelAdv-ExtraSection', STRUCTURE_CONNECTOR_SELECT_PANEL_EXTRA_SECTION_BUTTON: 'K-Chem-StructureConnectorSelectPanelAdv-ExtraSection-Button', STRUCTURE_CONNECTOR_SELECT_PANEL_EXTRA_SECTION_BUTTON_KEKULIZE: 'K-Chem-StructureConnectorSelectPanelAdv-ExtraSection-Button-Kekulize', STRUCTURE_CONNECTOR_SELECT_PANEL_EXTRA_SECTION_BUTTON_HUCKLIZE: 'K-Chem-StructureConnectorSelectPanelAdv-ExtraSection-Button-Hucklize', CHARGE_SELECT_PANEL: 'K-Chem-Charge-SelectPanel', CHARGE_SELECT_PANEL_BTNGROUP: 'K-Chem-Charge-SelectPanel-BtnGroup', CHARGE_SELECT_PANEL_CHARGE_BUTTON: 'K-Chem-Charge-SelectPanel-ChargeButton', GLYPH_PATH_ARROW_SETTING_PANEL: 'K-Chem-GlyphPath-Arrow-SettingPanel', GLYPH_PATH_ARROW_STYLE_BTNGROUP: 'K-Chem-GlyphPath-Arrow-StyleButtonGroup', GLYPH_PATH_ARROW_STYLE_BUTTON: 'K-Chem-GlyphPath-Arrow-StyleButton', GLYPH_PATH_ARROW_CONTINUOUS_SIDES_BTNGROUP: 'K-Chem-GlyphPath-Arrow-ContinuousSidesButtonGroup', GLYPH_PATH_ARROW_CONTINUOUS_SIDES_BUTTON: 'K-Chem-GlyphPath-Arrow-ContinuousSidesButton', GLYPH_Path_ARROW_SIZE_SETTER: 'K-Chem-GlyphPath-Arrow-SizeSetter', GLYPH_REACTION_ARROW_PRESET_SELECTOR: 'K-Chem-Glyph-ReactionArrow-PresetSelector', GLYPH_REACTION_ARROW_PRESET_SELECTOR_BTNSET: 'K-Chem-Glyph-ReactionArrow-PresetSelector-ButtonSet', GLYPH_REACTION_ARROW_PRESET_SELECTOR_BTNSET_DROPDOWN: 'K-Chem-Glyph-ReactionArrow-PresetSelector-ButtonSet-DropDown', GLYPH_REACTION_ARROW_PRESET_SELECTOR_BUTTON: 'K-Chem-Glyph-ReactionArrow-PresetSelector-Button', GLYPH_ELECTRON_PUSHING_ARROW_PRESET_SELECTOR: 'K-Chem-Glyph-ElectronPushingArrow-PresetSelector', GLYPH_ELECTRON_PUSHING_ARROW_PRESET_SELECTOR_BTNGROUP: 'K-Chem-Glyph-ReactionArrow-PresetSelector-ButtonGroup', GLYPH_ELECTRON_PUSHING_ARROW_PRESET_SELECTOR_BUTTON: 'K-Chem-Glyph-ElectronPushingArrow-PresetSelector-Button', GLYPH_PATH_LINE_SETTING_PANEL: 'K-Chem-GlyphPath-Line-SettingPanel', GLYPH_PATH_SETTING_PANEL: 'K-Chem-GlyphPath-SettingPanel', GLYPH_PATH_SETTING_PANEL_SECTION: 'K-Chem-GlyphPath-SettingPanel-Section', GLYPH_PATH_SETTING_PANEL_SECTION_TITLE: 'K-Chem-GlyphPath-SettingPanel-Section-Title', GLYPH_PATH_SETTING_PANEL_SECTION_PANEL: 'K-Chem-GlyphPath-SettingPanel-Section-Panel', GLYPH_MULTIPATH_SETTING_PANEL: 'K-Chem-GlyphMultiPath-SettingPanel', GLYPH_MULTIPATH_SETTING_PANEL_PATH_TABGROUP: 'K-Chem-GlyphMultiPath-SettingPanel-PathTabGroup', GLYPH_MULTIPATH_SETTING_PANEL_PATH_SETTING_PANEL: 'K-Chem-GlyphMultiPath-SettingPanel-PathSettingPanel', GLYPH_REACTION_ARROW_PATH_SETTING_PANEL: 'K-Chem-ReactionArrow-SettingPanel', GLYPH_REACTION_ARROW_PATH_SETTING_PANEL_ARROW_PRESET_SECTION: 'K-Chem-ReactionArrow-SettingPanel-ArrowPresetSection', GLYPH_ARC_PATH_SETTING_PANEL: 'K-Chem-ArcPath-SettingPanel', GLYPH_MULTIARC_PATH_SETTING_PANEL: 'K-Chem-MultiArcPath-SettingPanel', GLYPH_ELECTRON_PUSHING_ARROW_SETTING_PANEL: 'K-Chem-ElectronPusingArrow-SettingPanel', GLYPH_MULTI_ELECTRON_PUSHING_ARROW_SETTING_PANEL: 'K-Chem-MultiElectronPusingArrow-SettingPanel' }); /** * An panel to set the atom of a chem structure node. * @class * @augments Kekule.Widget.Panel * * @property {Array} elementSymbols Array of atomic symbols. * Elements that can be directly selected by button. * Note: Isotope id (e.g., D, 13C) can be used here. * Note: a special symbol '...' can be used to create a periodic table button. * @property {Array} nonElementInfos Array of info of displayed non-elements. * Non element node (e.g. pseudoatom) types that can be directly selected by button. * Each item is a hash {nodeClass, props, text, hint, description, isVarList, isNotVarList}. * The field isVarList and isVarNotList is a special flag to indicate whether this item * is a variable atom list. * @property {Array} subGroupInfos Array of info of displayed subgroups. * Each item is a hash {text, hint, inputText, formulaText, description}. * @property {Array} subGroupRepItems Displayed subgroup repository items. * Change this value will update property subGroupInfos. */ /** * Invoked when the new atom value has been set. * event param of it has field: {nodeClass, props, repositoryItem} * @name Kekule.ChemWidget.StructureNodeSelectPanel#valueChange * @event */ Kekule.ChemWidget.StructureNodeSelectPanel = Class.create(Kekule.Widget.Panel, /** @lends Kekule.ChemWidget.StructureNodeSelectPanel# */ { /** @private */ CLASS_NAME: 'Kekule.ChemWidget.StructureNodeSelectPanel', /** @private */ BTN_DATA_FIELD: '__$btn_data__', /** @construct */ initialize: function(/*$super, */parentOrElementOrDocument) { this.setPropStoreFieldValue('displayElements', true); this.setPropStoreFieldValue('displayNonElements', true); this.setPropStoreFieldValue('displaySubgroups', true); this.tryApplySuper('initialize', [parentOrElementOrDocument]) /* $super(parentOrElementOrDocument) */; this.addEventListener('execute', this.reactSelButtonExec.bind(this)); }, /** @private */ initProperties: function() { this.defineProp('value', {'dataType': DataType.HASH, 'serializable': false, 'setter': null}); this.defineProp('elementSymbols', { 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC }); this.defineProp('nonElementInfos', { 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC }); this.defineProp('subGroupInfos', { 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC }); this.defineProp('subGroupRepItems', { 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC, 'setter': function(value) { this.setPropStoreFieldValue('subGroupRepItems', value); this._updateSubGroupInfosFromRepItems(value); } }); this.defineProp('displayElements', { 'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLISHED }); this.defineProp('displayNonElements', { 'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLISHED }); this.defineProp('displaySubgroups', { 'dataType': DataType.BOOL, 'scope': Class.PropertyScope.PUBLISHED }); this.defineProp('activeNode', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': function(value) { this.setPropStoreFieldValue('activeNode', value); this.activeNodeChanged(value); } }); // private this.defineProp('periodicTableDialog', { 'dataType': DataType.OBJECT, 'scope': Class.PropertyScope.PRIVATE, 'serializable': false, 'setter': null, 'getter': function(canCreate) { var result = this.getPropStoreFieldValue('periodicTableDialog'); if (!result && canCreate) // create new one { var parentElem = this.getCoreElement(); var doc = this.getDocument(); result = this._createPeriodicTableDialogWidget(doc, parentElem); this.setPropStoreFieldValue('periodicTableDialog', result); } return result; } }); }, /** @private */ initPropValues: function(/*$super*/) { return this.tryApplySuper('initPropValues') /* $super() */; }, /** @ignore */ doFinalize: function(/*$super*/) { var dialog = this.getPeriodicTableDialog(); if (dialog) dialog.finalize(); this.tryApplySuper('doFinalize') /* $super() */; }, /** * A helper method to set elementSymbols, nonElementInfos, subGroupInfos/subGroupRepItems properties at the same time. * @param {Hash} data A hash object contains fields {elementSymbols, nonElementInfos, subGroupInfos, subGroupRepItems} */ setSelectableInfos: function(data) { this.beginUpdate(); try { this.setPropValues(data); } finally { this.endUpdate(); } }, /** * Event handler to react on selector button clicked. * @private */ reactSelButtonExec: function(e) { var self = this; //var currNode = this.getNode(); var btn = e.target; if (this._selButtons.indexOf(btn) >= 0) // is a selector button { var data = btn[this.BTN_DATA_FIELD]; if (data) { if (data.isPeriodicTable) // special periodic table button to select single element { this._openPeriodicTableDialog(btn, function(result){ if (result === Kekule.Widget.DialogButtons.OK) { var symbol = self._periodicTable.getSelectedSymbol(); self.doValueChanged(self.generateSelectableDataFromElementSymbol(symbol)); } }, {'isSimpleAtom': true}); } else if (data.isVarList || data.isVarNotList) // atom list, use periodic table to select elements { this._openPeriodicTableDialog(btn, function(result){ if (result === Kekule.Widget.DialogButtons.OK) { var symbols = self._periodicTable.getSelectedSymbols(); var nodeClass = Kekule.VariableAtom; var props = data.isVarList? {'allowedIsotopeIds': symbols, 'disallowedIsotopeIds': null}: {'allowedIsotopeIds': null, 'disallowedIsotopeIds': symbols}; self.doValueChanged({'nodeClass': nodeClass, 'props': props}); } }, {'isVarList': data.isVarList, 'isVarNotList': data.isVarNotList}); } else // normal button { this.doValueChanged(data); } } } }, /** * Notify the new atom value has been setted. * @private */ doValueChanged: function(newData) { this.setPropStoreFieldValue('value', newData); this.invokeEvent('valueChange', { 'value': { 'nodeClass': newData.nodeClass, 'props': newData.props, //'node': newData.node, 'repositoryItem': newData.repositoryItem } }); }, /** @private */ activeNodeChanged: function(newNode) { // TODO: process to switch active node }, /** @ignore */ doObjectChange: function(modifiedPropNames) { var affectedProps = [ 'elementSymbols', 'nonElementInfos', 'subGroupInfos', 'displayElements', 'displayNonElements', 'displaySubGroups' ]; if (Kekule.ArrayUtils.intersect(modifiedPropNames, affectedProps).length) this.updatePanelContent(this.getDocument(), this.getCoreElement()); }, /** @ignore */ doGetWidgetClassName: function(/*$super*/) { return this.tryApplySuper('doGetWidgetClassName') /* $super() */ + ' ' + CCNS.STRUCTURE_NODE_SELECT_PANEL; }, /** @ignore */ doCreateRootElement: function(doc) { var result = doc.createElement('span'); return result; }, /** @ignore */ doCreateSubElements: function(/*$super, */doc, rootElem) { var result = this.tryApplySuper('doCreateSubElements', [doc, rootElem]) /* $super(doc, rootElem) */; this.updatePanelContent(doc, this.getChildrenHolderElement()); // Custom input return result; }, /** @private */ _updateSubGroupInfosFromRepItems: function(repItems) { var infos = []; if (repItems && repItems.length) { for (var i = 0, l = repItems.length; i < l; ++i) { var repItem = repItems[i]; var structFragment = repItem.getStructureFragment(); var text = structFragment.getAbbr(); if (!text) { var formulaText = structFragment.getFormulaText(); var formula = Kekule.FormulaUtils.textToFormula(formulaText); var richText = formula.getDisplayRichText(); text = Kekule.Render.RichTextUtils._toDebugHtml(richText); } /* if (!text) { text = repItem.getInputTexts()[0]; } */ var info = { 'text': text, 'repositoryItem': repItem, 'nodeClass': structFragment.getClass(), 'node': structFragment, 'hint': repItem.getName() }; infos.push(info); } this.setSubGroupInfos(infos); } }, /** @private */ updatePanelContent: function(doc, rootElem) { // empty old buttons and sections if (this._selButtons) { for (var i = this._selButtons.length - 1; i >= 0; --i) { var btn = this._selButtons[i]; this.removeWidget(btn); } } this._selButtons = []; Kekule.DomUtils.clearChildContent(rootElem); var btnData = this.generateSelectableData(); var tabNames = this._getDisplayedTabPageNames(); var tab = new Kekule.Widget.TabView(this); if (tabNames.indexOf('atom') >= 0) { var tabPageAtom = tab.createNewTabPage(Kekule.$L('ChemWidgetTexts.CAPTION_ATOM')); if (this.getDisplayElements()) // normal elements var section = this.doCreateSelButtonSection(doc, tabPageAtom.getCoreElement(), btnData.elementData); if (this.getDisplayNonElements())// non-element var section = this.doCreateSelButtonSection(doc, tabPageAtom.getCoreElement(), btnData.nonElementData, true); } if (tabNames.indexOf('subgroup') >= 0) { // subgroups var tabPageSubgroup = tab.createNewTabPage(Kekule.$L('ChemWidgetTexts.CAPTION_SUBGROUP')); var section = this.doCreateSelButtonSection(doc, tabPageSubgroup.getCoreElement(), btnData.subGroupData, true); } tab.setShowTabButtons(tabNames.length > 1); }, /** @private */ _getDisplayedTabPageNames: function() { var result = []; if (this.getDisplayElements() || this.getDisplayNonElements()) result.push('atom'); if (this.getDisplaySubgroups()) result.push('subgroup'); return result; }, // generate button data /** @private */ generateSelectableDataFromElementSymbol: function(symbol) { var caption = symbol; var isotopeInfo = Kekule.IsotopesDataUtil.getIsotopeInfoById(symbol); if (isotopeInfo) { if (isotopeInfo.isotopeAlias) caption = isotopeInfo.isotopeAlias; else { caption = '<sup>' + isotopeInfo.massNumber + '</sup>' + isotopeInfo.elementSymbol; } } return { 'text': caption, 'nodeClass': Kekule.Atom, 'props': {'isotopeId': symbol} }; }, /** @private */ generateSelectableData: function() { var elementSymbols = this.getElementSymbols() || []; var elementData = []; for (var i = 0, l = elementSymbols.length; i < l; ++i) { var data; if (elementSymbols[i] === '...') // a special periodic table symbol data = { 'text': '\u2026', //elementSymbols[i], 'nodeClass': null, 'hint': Kekule.$L('ChemWidgetTexts.CAPTION_ATOMLIST_PERIODIC_TABLE'), 'isPeriodicTable': true }; else data = this.generateSelectableDataFromElementSymbol(elementSymbols[i]); elementData.push(data); } var nonElementData = this.getNonElementInfos(); var subGroupData = this.getSubGroupInfos(); return { 'elementData': elementData, 'nonElementData': nonElementData, 'subGroupData': subGroupData }; }, // methods about sub widget creation /** @private */ doCreateSelButton: function(doc, parentElem, buttonData, showDescription) { var caption = buttonData.text; if (showDescription && buttonData.description) caption += '<span style="font-size:80%"> ' + buttonData.description + '</span>'; //var btnClass = buttonData.isPeriodicTable? Kekule.Widget.Button: Kekule.Widget.RadioButton; var btnClass = Kekule.Widget.Button; var result = new btnClass(doc, caption); result.addClassName(CCNS.STRUCTURE_NODE_SELECT_PANEL_SET_BUTTON); if (result.setGroup) // radio button result.setGroup(this.getClassName()); if (buttonData.hint) result.setHint(buttonData.hint); result[this.BTN_DATA_FIELD] = buttonData; this._selButtons.push(result); return result; }, /** @private */ doCreateSectionSelButtons: function(doc, sectionElem, data, showDescription) { if (!data) return; for (var i = 0, l = data.length; i < l; ++i) { var btn = this.doCreateSelButton(doc, sectionElem, data[i], showDescription); btn.setParent(this); btn.appendToElem(sectionElem); } }, /** @private */ doCreateSection: function(doc, parentElem) { var result = doc.createElement('div'); result.className = CNS.SECTION; if (parentElem) parentElem.appendChild(result); return result; }, /** @private */ doCreateSelButtonSection: function(doc, parentElem, elementData, showDescription) { var section = this.doCreateSection(doc, parentElem); this.doCreateSectionSelButtons(doc, section, elementData, showDescription); return section; }, /** @private */ _createPeriodicTableDialogWidget: function(doc, parentElem) { var dialog = new Kekule.Widget.Dialog(doc, Kekule.$L('ChemWidgetTexts.CAPTION_PERIODIC_TABLE_DIALOG'), [Kekule.Widget.DialogButtons.OK, Kekule.Widget.DialogButtons.CANCEL] ); var table = new Kekule.ChemWidget.PeriodicTable(doc); table.setUseMiniMode(true).setEnableSelect(true); table.setParent(dialog); table.appendToElem(dialog.getClientElem()); this._periodicTable = table; return dialog; }, /** @private */ _openPeriodicTableDialog: function(caller, callback, extraInfo) { var dialog = this.getPeriodicTableDialog(true); var enableMultiSelect = extraInfo.isVarList || extraInfo.isVarNotList; this._periodicTable.setEnableMultiSelect(enableMultiSelect).setSelectedSymbol(null); var node = this.getActiveNode(); // var list if ((extraInfo.isVarList || extraInfo.isVarNotList) && node instanceof Kekule.VariableAtom) { var allowedIds = node.getAllowedIsotopeIds(); var disallowedIds = node.getDisallowedIsotopeIds(); this._periodicTable.setSelectedSymbols(extraInfo.isVarList? allowedIds: disallowedIds); } // simple atom if (!enableMultiSelect && node instanceof Kekule.Atom) { this._periodicTable.setSelectedSymbol(node.getSymbol()); } dialog.openPopup(callback, caller || this); } }); /** * A widget used by AtomIaController to set node in chem structures in chem editor. * @class * @augments Kekule.Widget.BaseWidget * * @property {Kekule.Widget.ButtonTextBox} nodeInputBox A text box to input atom or subgroup directly. * @property {Kekule.ChemWidget.StructureNodeSelectPanel} nodeSelectPanel The child node selector panel inside this widget. * @property {Bool} showInputBox * @property {Bool} showSelectPanel * @property {Bool} useDropDownSelectPanel If true, the select panel will be a drop-down child widget of input box. * @property {Array} selectableElementSymbols Array of atomic symbols. * Elements that can be directly selected by button. * Note: Isotope id (e.g., D, 13C) can be used here. * Note: a special symbol '...' can be used to create a periodic table button. * @property {Array} selectableNonElementInfos Array of info of displayed non-elements. * Non element node (e.g. pseudoatom) types that can be directly selected by button. * Each item is a hash {nodeClass, props, text, hint, description}. * @property {Array} selectableSubGroupInfos Array of info of displayed subgroups. * Each item is a hash {text, hint, inputText, formulaText, description}. * @property {Array} selectableSubGroupRepItems Displayed subgroup repository items. * Change this value will update property subGroupInfos. * * @property {Bool} enableHydrogenCountInput Whether texts like 'CH3'/'NH2' (with H count) are allowed in inputting. * * @property {Object} labelConfigs Label configs object of render configs. * * @property {Array} nodes Structure nodes that currently be edited in node setter. * Note: When done editing, the changes will not directly applied to nodes, editor should handle them insteadly. * @property {Hash} value Node new properties setted by setter. Include fields: {nodeClass, props, repositoryItem} * @property {String} nodeLabel */ /** * Invoked when the new atom value has been setted. * event param of it has field: {nodeClass, props, repositoryItem} * @name Kekule.ChemWidget.StructureNodeSetter#valueChange * @event */ /** * Invoked when the new atom value has been selected from selection panel. * event param of it has field: {nodeClass, props, repositoryItem} * @name Kekule.ChemWidget.StructureNodeSetter#valueSelect * @event */ Kekule.ChemWidget.StructureNodeSetter = Class.create(Kekule.Widget.BaseWidget, /** @lends Kekule.ChemWidget.StructureNodeSetter# */ { /** @private */ CLASS_NAME: 'Kekule.ChemWidget.StructureNodeSetter', /** @construct */ initialize: function(/*$super, */parentOrElementOrDocument) { this.tryApplySuper('initialize', [parentOrElementOrDocument]) /* $super(parentOrElementOrDocument) */; this._valueSetBySelectPanel = false; // an internal flag, whether the value of node is set by click on select panel /* var self = this; this.addEventListener('keyup', function(e){ console.log('key event', e); if (self.getUseDropDownSelectPanel()) { var ev = e.htmlEvent; var keyCode = ev.getKeyCode(); if (keyCode === Kekule.X.Event.KeyCode.DOWN) { self.showNodeSelectPanel(); } else if (keyCode === Kekule.X.Event.KeyCode.UP) { self.hideNodeSelectPanel(); } } }); */ }, /** @private */ initProperties: function() { this.defineProp('enableHydrogenCountInput', {'dataType': DataType.BOOL}); this.defineProp('nodes', {'dataType': DataType.ARRAY, 'serializable': false, 'setter': function(value) { var nodes = Kekule.ArrayUtils.toArray(value); this.setPropStoreFieldValue('nodes', nodes); this.nodesChanged(nodes); } }); this.defineProp('value', {'dataType': DataType.HASH, 'serializable': false, 'setter': null, 'getter': function() { var result = this.getPropStoreFieldValue('value'); if (!result && !this._valueSetBySelectPanel) { var text = this.getNodeInputBox().getValue(); result = this._getValueFromDirectInputText(text) || result; } return result; } }); this.defineProp('nodeLabel', {'dataType': DataType.STRING, 'serializable': false, 'setter': null, 'getter': function() { return this.getNodeInputBox().getValue(); } }); this.defineProp('nodeInputBox', { 'dataType': 'Kekule.Widget.ButtonTextBox', 'serializable': false, 'setter': false }); this.defineProp('nodeInputBoxFontSize', { 'dataType': DataType.STRING, 'serializable': false, 'setter': function(value) { var elem = this.getNodeInputBox().getTextBox().getElement(); elem.style.fontSize = value; this.setPropStoreFieldValue('nodeInputBoxFontSize', value); } }); this.defineProp('nodeSelectPanel', { 'dataType': 'Kekule.ChemWidget.StructureNodeSelectPanel', 'serializable': false, 'setter': false }); this.defineProp('showInputBox', { 'dataType': DataType.BOOL, 'getter': function() { return this.getNodeInputBox().getDisplayed(); }, 'setter': function(value) { this.getNodeInputBox().setDisplayed(value); } }); this.defineProp('showSelectPanel', { 'dataType': DataType.BOOL, 'getter': function() { return this.getNodeSelectPanel().getDisplayed(); }, 'setter': function(value) { this.getNodeSelectPanel().setDisplayed(value); } }); this.defineProp('useDropDownSelectPanel', { 'dataType': DataType.BOOL, 'setter': function(value) { if (this.getUseDropDownSelectPanel() !== value) { this.setPropStoreFieldValue('useDropDownSelectPanel', value); this._updateDropDownSettings(value); } } }); this.defineProp('labelConfigs', {'dataType': DataType.OBJECT, 'serializable': false}); //this.defineProp('editor', {'dataType': DataType.OBJECT, 'serializable': false}); this._defineSelectPanelDelegatedProp('selectableElementSymbols', 'elementSymbols'); this._defineSelectPanelDelegatedProp('selectableNonElementInfos', 'nonElementInfos'); this._defineSelectPanelDelegatedProp('selectableSubGroupInfos', 'subGroupInfos'); this._defineSelectPanelDelegatedProp('selectableSubGroupRepItems', 'subGroupRepItems'); this._defineSelectPanelDelegatedProp('displaySelectableElements', 'displayElements'); this._defineSelectPanelDelegatedProp('displaySelectableNonElements', 'displayNonElements'); this._defineSelectPanelDelegatedProp('displaySelectableSubgroups', 'displaySubgroups'); }, /** @private */ initPropValues: function(/*$super*/) { this.tryApplySuper('initPropValues') /* $super() */; this.setEnableHydrogenCountInput(true); }, /** @ignore */ doFinalize: function(/*$super*/) { var panel = this.getNodeSelectPanel(); if (panel) panel.finalize(); var inputBox = this.getNodeInputBox(); if (inputBox) inputBox.finalize(); this.tryApplySuper('doFinalize') /* $super() */; }, /** * Define property that directly mapped to select panel's property. * @private */ _defineSelectPanelDelegatedProp: function(propName, selectPanelPropName) { if (!selectPanelPropName) selectPanelPropName = propName; var originalPropInfo = ClassEx.getPropInfo(Kekule.ChemWidget.StructureNodeSelectPanel, selectPanelPropName); var propOptions = Object.create(originalPropInfo); propOptions.getter = null; propOptions.setter = null; if (originalPropInfo.getter) { propOptions.getter = function() { return this.getNodeSelectPanel().getPropValue(selectPanelPropName); }; } if (originalPropInfo.setter) { propOptions.setter = function(value) { this.getNodeSelectPanel().setPropValue(selectPanelPropName, value); } } return this.defineProp(propName, propOptions); }, /** @ignore */ doGetWidgetClassName: function(/*$super*/) { return this.tryApplySuper('doGetWidgetClassName') /* $super() */ + ' ' + CCNS.STRUCTURE_NODE_SETTER; }, /** @ignore */ doCreateRootElement: function(doc) { var result = doc.createElement('span'); return result; }, /** @ignore */ doCreateSubElements: function(/*$super, */doc, rootElem) { var result = this.tryApplySuper('doCreateSubElements', [doc, rootElem]) /* $super(doc, rootElem) */; var self = this; // input box var inputter = new Kekule.Widget.ButtonTextBox(this); this.setPropStoreFieldValue('nodeInputBox', inputter); inputter.addClassName(CCNS.STRUCTURE_NODE_SETTER_INPUTBOX); inputter.appendToElem(this.getCoreElement()); inputter.addEventListener('valueChange', function(e){ self._valueSetBySelectPanel = false; e.stopPropagation(); // elimiate valueChange event of inputbox, avoid bubble to parent }); inputter.addEventListener('keyup', function(e){ // response to enter keypress in input box var ev = e.htmlEvent; var keyCode = ev.getKeyCode(); if (keyCode === Kekule.X.Event.KeyCode.ENTER) { self._applyDirectInput(); } if (self.getUseDropDownSelectPanel()) { if (keyCode === Kekule.X.Event.KeyCode.DOWN) { self.showNodeSelectPanel(); } else if (keyCode === Kekule.X.Event.KeyCode.UP) { self.hideNodeSelectPanel(); } } }); inputter.addEventListener('blur', function(e){ if (e.target === inputter.getTextBox() && inputter.getIsDirty()) { self._applyDirectInput(); } }); inputter.getButton().addEventListener('execute', function(e){ if (self.getUseDropDownSelectPanel()) { //self.getNodeSelectPanel().show(self.getNodeInputBox(), null, Kekule.Widget.ShowHideType.DROPDOWN); self.toggleNodeSelectPanel(); } else { self._applyDirectInput(); } }); // select panel var panel = new Kekule.ChemWidget.StructureNodeSelectPanel(this); this.setPropStoreFieldValue('nodeSelectPanel', panel); panel.appendToElem(this.getCoreElement()); panel.addEventListener('valueChange', function(e){ var newEventArg = Object.extend({}, e); //self.invokeEvent('valueChange', {'value': e.value}); e.stopPropagation(); self._valueSetBySelectPanel = true; self.doValueChanged(e.value, true); // when value is set by button in selector panel, auto hide it in drop down mode if (self.getUseDropDownSelectPanel()) panel.hide(); }); this._updateDropDownSettings(this.getUseDropDownSelectPanel()); return result; }, /** @private */ _updateDropDownSettings: function(useDropDownPanel) { var inputBox = this.getNodeInputBox(); var selectPanel = this.getNodeSelectPanel(); if (useDropDownPanel) { inputBox.setButtonKind(Kekule.Widget.Button.Kinds.DROPDOWN); selectPanel.removeFromDom(); selectPanel.hide(null, null, Kekule.Widget.ShowHideType.DEFAULT); } else { inputBox.setButtonKind(Kekule.Widget.Button.Kinds.ENTER); selectPanel.appendToElem(this.getCoreElement()); selectPanel.setStyleProperty('position', ''); // clear position:absolute value from previous dropdown show selectPanel.show(null, null, Kekule.Widget.ShowHideType.DEFAULT); } }, /** @private */ _indexOfNonElementLabel: function(nodeLabel) { var infos = this.getNonAtomLabelInfos(); for (var i = 0, l = infos.length; i < l; ++i) { var info = infos[i]; if (info.nodeLabel === nodeLabel) return i; } return -1; }, /** @private */ _getNonAtomInfo: function(nodeLabel) { var infos = this.getSelectableNonElementInfos(); if (infos) { for (var i = 0, l = infos.length; i <l; ++i) { var info = infos[i]; if (info.text === nodeLabel) return info; } } return null; }, /** @private */ _getNodeWithExplicitHInfo: function(nodeLabel) { var result = {}; var pattern = /^(\d*[A-Z][a-z]?\d*)H(\d*)$/; var matchResult = nodeLabel.trim().match(pattern); if (matchResult) // may has explicit hydrogens { result.core = matchResult[1]; result.explicitHCount = parseInt(matchResult[2]) || 1; } else result.core = nodeLabel; return result; }, /** @private */ _getValueFromDirectInputText: function(text) { if (!text) return null; var nodeClass, modifiedProps, newNode, repItem, isUnknownPAtom, inputHydrogenCount; var nonAtomInfo = this._getNonAtomInfo(text); if (nonAtomInfo) // is not an atom { nodeClass = nonAtomInfo.nodeClass; modifiedProps = nonAtomInfo.props; //isNonAtom = true; } else { //var editor = this.getEditor(); // check if it is predefined subgroups first var subGroupRepositoryItem = Kekule.Editor.StoredSubgroupRepositoryItem2D.getRepItemOfInputText(text); repItem = subGroupRepositoryItem; if (subGroupRepositoryItem) // add subgroup { /* var baseAtom = Kekule.ArrayUtils.toArray(this.getNodes())[0]; var repResult = subGroupRepositoryItem.createObjects(baseAtom) || {}; var repObjects = repResult.objects; if (editor) { var transformParams = Kekule.Editor.RepositoryStructureUtils.calcRepObjInitialTransformParams(editor, subGroupRepositoryItem, repResult, baseAtom, null); editor.transformCoordAndSizeOfObjects(repObjects, transformParams); } */ newNode = subGroupRepositoryItem.getStructureFragment(); //repObjects[0]; nodeClass = newNode.getClass(); } else if (text) // add normal node { var isotopeId = text; nodeClass = Kekule.ChemStructureNodeFactory.getClassByLabel(isotopeId, null); // explicit set defaultClass parameter to null if (!nodeClass) { if (this.getEnableHydrogenCountInput()) { var infoWithExplicitH = this._getNodeWithExplicitHInfo(isotopeId); if (infoWithExplicitH.explicitHCount) // may be in form like NH2, with explicit H { nodeClass = Kekule.ChemStructureNodeFactory.getClassByLabel(infoWithExplicitH.core, null); // try get class with core part of input if (nodeClass) { inputHydrogenCount = infoWithExplicitH.explicitHCount; isotopeId = infoWithExplicitH.core; } } } if (!nodeClass) { nodeClass = Kekule.Pseudoatom; isUnknownPAtom = true; } } modifiedProps = (nodeClass === Kekule.Atom) ? {'isotopeId': isotopeId, 'inputHydrogenCount': inputHydrogenCount} : (nodeClass === Kekule.Pseudoatom) ? {'symbol': text} : {}; } } var data = { 'nodeClass': nodeClass, 'props': modifiedProps, /*'node': newNode*/ 'repositoryItem': repItem, 'isUnknownPseudoatom': isUnknownPAtom }; return data; }, /** * Create new node modification information based on string in input text box directly. * @private */ _applyDirectInput: function() { this._valueSetBySelectPanel = false; var inputBox = this.getNodeInputBox(); //console.log('direct input', inputBox.getIsDirty()); if (inputBox.getIsDirty()) { var text = inputBox.getValue(); if (text) { var data = this._getValueFromDirectInputText(text); if (data) // data may be null when text is empty this.doValueChanged(data); } inputBox.setIsDirty(false); } else // actually no changes { this.doValueChanged(null); // returns null indicating no changes } }, /** @private */ _getVarAtomListLabel: function() { var labelConfigs = this.getLabelConfigs(); return labelConfigs? labelConfigs.getVariableAtom(): Kekule.ChemStructureNodeLabels.VARIABLE_ATOM; }, _getVarAtomNotListLabel: function() { var labelConfigs = this.getLabelConfigs(); return '~' + (labelConfigs? labelConfigs.getVariableAtom(): Kekule.ChemStructureNodeLabels.VARIABLE_ATOM); }, /* * Returns label that shows in node edit. * @param {Kekule.ChemStructureNode} node * @returns {String} * @private */ /* _getNodeLabel: function(node) { var labelConfigs = this.getLabelConfigs(); if (node.getIsotopeId) // atom return node.getIsotopeId(); else if (node instanceof Kekule.SubGroup) { var groupLabel = node.getAbbr() || node.getFormulaText(); return groupLabel || labelConfigs.getRgroup(); } else { var ri = node.getCoreDisplayRichTextItem(null, null, labelConfigs); return Kekule.Render.RichTextUtils.toText(ri); } }, */ /** @private */ _getAllNodeLabels: function(nodes) { var options = {}; if (this.getEnableHydrogenCountInput()) options.includeExplicitHydrogens = true; return Kekule.Editor.StructureUtils.getAllChemStructureNodesLabel(nodes, this.getLabelConfigs(), options); /* var nodeLabel; for (var i = 0, l = nodes.length; i < l; ++i) { var node = nodes[i]; var currLabel = this._getNodeLabel(node); if (!nodeLabel) nodeLabel = currLabel; else { if (nodeLabel !== currLabel) // different label, currently has different nodes { return null; } } } return nodeLabel; */ }, /** * Called when nodes property has been changed. * @private */ nodesChanged: function(newNodes) { // update node label in edit var currLabel = this._getAllNodeLabels(newNodes) || ''; var activeNode = currLabel? newNodes[0]: null; // not empty currLabel means all nodes are in the same type this.getNodeSelectPanel().setActiveNode(activeNode); this.getNodeInputBox().setValue(currLabel); this.getNodeInputBox().setIsDirty(false); this.setPropStoreFieldValue('value', null); this._valueSetBySelectPanel = false; }, /** * Notify the new atom value has been setted. * @private */ doValueChanged: function(newData, isSelectedFromPanel) { this.setPropStoreFieldValue('value', newData); var eventData = newData && { 'nodeClass': newData.nodeClass, 'props': newData.props, //'node': newData.node, 'repositoryItem': newData.repositoryItem, 'isUnknownPseudoatom': newData.isUnknownPseudoatom }; if (isSelectedFromPanel) this.invokeEvent('valueSelect', {'value': eventData}); this.invokeEvent('valueChange', {'value': eventData}); }, /** * A helper method to change elementSymbols, nonElementInfos, subGroupInfos/subGroupRepItems properties of select panel at the same time. * @param {Hash} data A hash object contains fields {elementSymbols, nonElementInfos, subGroupInfos, subGroupRepItems} */ setSelectableInfos: function(data) { this.getNodeSelectPanel().setSelectableInfos(data); }, /** * Show node select panel. * Note this method should be called in drop down mode. */ showNodeSelectPanel: function() { var panel = this.getNodeSelectPanel(); if (!panel.isShown()) { panel.show(this.getNodeInputBox(), null, Kekule.Widget.ShowHideType.DROPDOWN); } }, /** * Hide node select panel. * Note this method should be called in drop down mode. */ hideNodeSelectPanel: function() { var panel = this.getNodeSelectPanel(); if (panel.isShown()) { panel.hide(); } }, /** * Toggle the show/hide state of node select panel. * Note this method should be called in drop down mode. */ toggleNodeSelectPanel: function() { var panel = this.getNodeSelectPanel(); if (panel.isShown()) panel.hide(); else panel.show(this.getNodeInputBox(), null, Kekule.Widget.ShowHideType.DROPDOWN); } }); /** * An panel to set the bond type/order of a chem structure connector. * @class * @augments Kekule.Widget.Panel * * @property {Array} bondData Array of available bond properties. * Each item is a hash, containing the properties of this bond item. * e.g. { * 'bondProps': {'bondType': Kekule.BondType.COVALENT, 'bondOrder': Kekule.BondOrder.SINGLE, 'stereo': Kekule.BondStereo.NONE}, * 'text': 'Single Bond', 'description': 'Single bond' * } * @property {Hash} activeBondPropValues Bond property-value hash object of current selected bond. * @property {Array} bondPropNames Property names used in bondData.bondProps. Used to compare bond property values. * ReadOnly. */ /** * Invoked when the new bond property has been set. * event param of it has field: {props} * @name Kekule.ChemWidget.StructureConnectorSelectPanel#valueChange * @event */ Kekule.ChemWidget.StructureConnectorSelectPanel = Class.create(Kekule.Widget.Panel, /** @lends Kekule.ChemWidget.StructureConnectorSelectPanel# */ { /** @private */ CLASS_NAME: 'Kekule.ChemWidget.StructureConnectorSelectPanel', /** @private */ BTN_DATA_FIELD: '__$btn_data__', /** @private */ BTN_GROUP: '__$bond_btn_group__', /** @construct */ initialize: function(/*$super, */parentOrElementOrDocument) { this.tryApplySuper('initialize', [parentOrElementOrDocument]) /* $super(parentOrElementOrDocument) */; this._selButtons = []; this._activeBtn = null; this.addEventListener('execute', this.reactSelButtonExec.bind(this)); }, /** @private */ initProperties: function() { this.defineProp('value', {'dataType': DataType.HASH, 'serializable': false, 'setter': null}); this.defineProp('bondData', { 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC }); this.defineProp('activeBondPropValues', { 'dataType': DataType.HASH, 'scope': Class.PropertyScope.PUBLIC, 'setter': function(value) { this.setPropStoreFieldValue('activeBondPropValues', value); this.activeBondPropsChanged(value); } }); this.defineProp('activeBondHtmlClass', { 'dataType': DataType.STRING, 'scope': Class.PropertyScope.PRIVATE, 'setter': null, 'getter': function() { var btn = this._activeBtn; if (btn) { var data = btn[this.BTN_DATA_FIELD]; return data && data.htmlClass; } return null; } }); this.defineProp('bondPropNames', {'dataType': DataType.ARRAY, 'serializable': false}) }, /** @ignore */ doObjectChange: function(modifiedPropNames) { var affectedProps = [ 'bondData' ]; if (Kekule.ArrayUtils.intersect(modifiedPropNames, affectedProps).length) { this.updatePanelContent(this.getDocument(), this.getCoreElement()); } }, /** @ignore */ doGetWidgetClassName: function(/*$super*/) { return this.tryApplySuper('doGetWidgetClassName') /* $super() */ + ' ' + CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL; }, /** @ignore */ doCreateRootElement: function(doc) { var result = doc.createElement('span'); return result; }, /** @ignore */ doCreateSubElements: function(/*$super, */doc, rootElem) { var result = this.tryApplySuper('doCreateSubElements', [doc, rootElem]) /* $super(doc, rootElem) */; this.updatePanelContent(doc, rootElem); // Custom input return result; }, /** @private */ activeBondPropsChanged: function(value) { // deselect all buttons first if (this._activeBtn) { this._activeBtn.setChecked(false); this._activeBtn = null; } // then check the proper button if (value) { var btns = this._selButtons; for (var i = 0, l = btns.length; i < l; ++i) { var data = btns[i][this.BTN_DATA_FIELD]; if (data && this._isBondPropsMatch(value, data.bondProps, this.getBondPropNames())) { btns[i].setChecked(true); this._activeBtn = btns[i]; break; } } } }, /** @private */ _isBondPropsMatch: function(src, target, propNames) { return Kekule.Editor.StructureUtils.isBondPropsMatch(src, target, propNames); }, /** * Event handler to react on selector button clicked. * @private */ reactSelButtonExec: function(e) { var self = this; //var currNode = this.getNode(); var btn = e.target; if (this._selButtons.indexOf(btn) >= 0) // is a selector button { this._activeBtn = btn; var data = btn[this.BTN_DATA_FIELD]; if (data) { this.setPropStoreFieldValue('activeBondPropValues', data.bondProps); this.doValueChanged(data.bondProps); } } }, /** * Notify the new bond props value has been setted. * @private */ doValueChanged: function(newData) { this.setPropStoreFieldValue('value', newData); this.invokeEvent('valueChange', { 'value': newData }); }, /** @private */ updatePanelContent: function(doc, rootElem) { if (this._selButtons) { for (var i = this._selButtons.length - 1; i >= 0; --i) { var btn = this._selButtons[i]; this.removeWidget(btn); } } var propNames = []; this._selButtons = []; //Kekule.DomUtils.clearChildContent(rootElem); var bondData = this.getBondData(); if (bondData) { for (var i = 0, l = bondData.length; i < l; ++i) { var data = bondData[i]; var propData = data.bondProps; var btn = this._createBondSelButton(doc, data); btn.appendToElem(rootElem); this._selButtons.push(btn); if (propData) AU.pushUnique(propNames, Kekule.ObjUtils.getOwnedFieldNames(propData)); } } if (!this.getBondPropNames()) this.setPropStoreFieldValue('bondPropNames', propNames); }, /** @private */ _createBondSelButton: function(doc, data) { var result = new Kekule.Widget.RadioButton(this); result.addClassName(CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL_SET_BUTTON); result.setGroup(this.BTN_GROUP).setShowGlyph(true).setShowText(false); result.setText(data.text || null).setHint(data.hint || data.description || null); if (data.htmlClass) result.addClassName(data.htmlClass); result[this.BTN_DATA_FIELD] = data; return result; } }); /** * An advanced panel inherited from {@link Kekule.ChemWidget.StructureConnectorSelectPanel}. * Aside from the basic function of setting the bond type/order of a chem structure connector, it also * includes buttons to run Hucklize or Kekulize process to connectors. * @class * @augments Kekule.ChemWidget.StructureConnectorSelectPanel * * @property {Array} extraComponents Additional sections in this panel, now the only available value is ['kekulize']. */ /** * Invoked when a command button in extra section is clicked. * event param of it has field: {command} * @name Kekule.ChemWidget.StructureConnectorSelectPanelAdv#command * @event */ Kekule.ChemWidget.StructureConnectorSelectPanelAdv = Class.create(Kekule.ChemWidget.StructureConnectorSelectPanel, /** @lends Kekule.ChemWidget.StructureConnectorSelectPanelAdv# */ { /** @private */ CLASS_NAME: 'Kekule.ChemWidget.StructureConnectorSelectPanelAdv', /** @construct */ initialize: function(/*$super, */parentOrElementOrDocument) { this.setPropStoreFieldValue('extraComponents', [Kekule.ChemWidget.StructureConnectorSelectPanelAdv.Components.KEKULIZE]); this._additionalCompWidgets = []; // internal this.tryApplySuper('initialize', [parentOrElementOrDocument]) /* $super(parentOrElementOrDocument) */; }, /** @private */ initProperties: function() { this.defineProp('extraComponents', { 'dataType': DataType.ARRAY, 'scope': Class.PropertyScope.PUBLIC }); }, /** @ignore */ doGetWidgetClassName: function(/*$super*/) { return this.tryApplySuper('doGetWidgetClassName') /* $super() */ + ' ' + CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL_ADV; }, /** @ignore */ doObjectChange: function(/*$super, */modifiedPropNames) { this.tryApplySuper('doObjectChange', [modifiedPropNames]) /* $super(modifiedPropNames) */; var affectedProps = [ 'extraComponents' ]; if (Kekule.ArrayUtils.intersect(modifiedPropNames, affectedProps).length) this.updatePanelContent(this.getDocument(), this.getCoreElement()); }, /** @ignore */ updatePanelContent: function(/*$super, */doc, rootElem) { this.clearAdditionalSections(); this.tryApplySuper('updatePanelContent', [doc, rootElem]) /* $super(doc, rootElem) */; var extraComps = this.getExtraComponents(); if (extraComps) { if (extraComps.indexOf(Kekule.ChemWidget.StructureConnectorSelectPanelAdv.Components.KEKULIZE) >= 0) // need to show the hucklize-kekulize section { this._createKekulizeSection(doc, rootElem).appendToElem(rootElem); } } }, /** * Event handler to react on selector button clicked. * @private */ reactSelButtonExec: function(/*$super, */e) { this.tryApplySuper('reactSelButtonExec', [e]) /* $super(e) */; var btn = e.target; if (btn._command) // is a extra command button, not a bond select button { this.invokeEvent('command', { 'command': btn._command }); } }, /** @private */ clearAdditionalSections: function() { if (this._additionalCompWidgets) { for (var i = 0, l = this._additionalCompWidgets.length; i < l; ++i) { var w = this._additionalCompWidgets[i]; w.finalize(); } this._additionalCompWidgets = []; } }, /** * Create an additional component section in panel. * @param {HTMLDocument} doc * @param {HTMLElement} rootElem * @private */ createAdditionalSection: function(doc, rootElem) { var result = new Kekule.Widget.Container(doc); result.addClassName(CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL_ADV_EXTRA_SECTION); this._additionalCompWidgets.push(result); result.appendToElem(rootElem); result.setParent(this); return result; }, /** @private */ _createKekulizeSection: function(doc, rootElem) { var result = this.createAdditionalSection(doc, rootElem); // add kekulize and hucklize buttons var btnKekulize = new Kekule.Widget.Button(result); btnKekulize.addClassName([CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL_EXTRA_SECTION_BUTTON, CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL_EXTRA_SECTION_BUTTON_KEKULIZE]) .setText(Kekule.$L('ChemWidgetTexts.CAPTION_MOL_BOND_KEKULIZE')).setHint(Kekule.$L('ChemWidgetTexts.HINT_MOL_BOND_KEKULIZE')) .setShowGlyph(true).setShowText(false); btnKekulize._command = Kekule.ChemWidget.StructureConnectorSelectPanelAdv.Commands.KEKULIZE; result.appendWidget(btnKekulize); // double direction arrow var span = doc.createElement('span'); Kekule.DomUtils.setElementText(span, ' \u25C0 \u25B6 '); // ◀ ▶ //span.innerHTML = '&10231;'; result.getElement().appendChild(span); var btnHucklize = new Kekule.Widget.Button(result); btnHucklize.addClassName([CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL_EXTRA_SECTION_BUTTON, CCNS.STRUCTURE_CONNECTOR_SELECT_PANEL_EXTRA_SECTION_BUTTON_HUCKLIZE]) .setText(Kekule.$L('ChemWidgetTexts.CAPTION_MOL_BOND_HUCKLIZE')).setHint(Kekule.$L('ChemWidgetTexts.HINT_MOL_BOND_HUCKLIZE')) .setShowGlyph(true).setShowText(false); btnHucklize._command = Kekule.ChemWidget.StructureConnectorSelectPanelAdv.Commands.HUCKLIZE; result.appendWidget(btnHucklize); return result; } }); /** * A enumeration of available additional component names in {@link Kekule.ChemWid