Source: widgets/chem/kekule.chemWidget.chemObjDisplayers.js

/**
 * @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 /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 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_ZOOMIN: 'K-Chem-ZoomIn',
	ACTION_ZOOMOUT: 'K-Chem-ZoomOut',
	ACTION_RESET: 'K-Chem-Reset',
	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: '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');
	},
	/** @private */
	initPropValues: function($super)
	{
		$super();
		this.setPropStoreFieldValue('ioConfigs', new Kekule.ChemWidget.ChemObjDisplayerIOConfigs());
	}
});
/**
 * 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.
 */
Kekule.ChemWidget.ChemObjDisplayerIOConfigs = Class.create(Kekule.AbstractConfigs,
/** @lends Kekule.ChemWidget.ChemObjDisplayerIOConfigs# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ChemObjDisplayerIOConfigs',
	/** @private */
	initProperties: function()
	{
		this.addBoolConfigProp('canonicalizeBeforeSave', true);
	}
});

/**
 * 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 {Hash} standardizationOptions Possible options when do standardization on molecule before saving.
 */
/**
 * Invoked when the an chem object (or null) is loaded into the displayer.
 *   event param of it has one fields: {obj: Object}
 * @name Kekule.ChemWidget.ChemObjDisplayer#load
 * @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.setPropStoreFieldValue('resetAfterLoad', true);
		this.setPropStoreFieldValue('renderType', renderType || Kekule.Render.RendererType.R2D); // must set this value first
		$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);
		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();
		$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('standardizationOptions', {'dataType': DataType.HASH});

		this.defineProp('resetAfterLoad', {'dataType': DataType.BOOL});
		this.defineProp('chemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false, 'scope': PS.PUBLIC,
			'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('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, '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('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('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('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)
				{
					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);
							/*
							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;
			}
		});
	},
	initPropValues: function($super)
	{
		$super();
		this.setStandardizationOptions({'unmarshalSubFragments': false});
	},

	/** @ignore */
	doWidgetShowStateChanged: function($super, 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());
	},

	/**
	 * 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);
		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);
	},

	/**
	 * 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);
					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)
				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 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.getElement();
			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())
		{
			//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;
	},
	/**
	 * Change the dimension of context.
	 * @param {Hash} newDimension
	 * @returns {Bool} Return true if dimension change successfully.
	 */
	changeContextDimension: function(newDimension)
	{
		if (this.getDrawBridge() && this.getDrawContext())
		{
			this.getDrawBridge().setContextDimension(this.getDrawContext(), newDimension.width, newDimension.height);
			return true;
		}
		else
			return false;
	},

	/** @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.finalize();
		}
		var result = new Kekule.Render.ChemObjPainter(this.getRenderType(), chemObj, this.getDrawBridge());
		this.setPropStoreFieldValue('painter', result);
		return result;
	},

	/**
	 * 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)
	{
		//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);
				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
				//this.invokeEvent('load', {'obj': chemObj});
			}
			else  // no object, clear
			{
				this.clearContext();
			}
			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.
	 */
	loadFromData: function(data, mimeType, fromUrlOrFileName)
	{
		try
		{
			if (!data)
			{
				this.setChemObj(null);
				return null;
			}
			else
			{
				//var ext = fromUrlOrFileName? Kekule.UrlUtils.extractFileExt(fromUrlOrFileName): null;
				var chemObj = Kekule.IO.loadTypedData(data, mimeType, fromUrlOrFileName);
				if (chemObj)
					this.setChemObj(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);
					}
				);
			}
			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
				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;
		}
	},
	/**
	 * 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 */
	getDrawClientDimension: function()
	{
		var dim = Kekule.HtmlElementUtils.getElemClientDimension(this.getCoreElement());
		return dim;
	},
	/**
	 * Set dimension of drawn client, descendant may override this method.
	 * @param {Int} width
	 * @param {Int} height
	 */
	setDrawDimension: function(width, height)
	{
		this.setDimension(width, height);
	},

	/** @private */
	calcDrawBaseCoord: function(drawOptions)
	{
		var baseCoord;
		var context = this.getDrawContext();
		var painter = this.getPainter();
		var newDimension;
		// note in continuous repainting phase (such as periodical rotation), we disable auto size
		if ((!this._isContinuousRepainting) && this.getAutoSize() && this.allowAutoSize())  // need to resize widget dimension
		{
			var padding = this.getPadding() || 0;
			var renderBox = painter.estimateScreenBox(context, baseCoord, drawOptions, this.getAllowCoordBorrow());
			if (renderBox)
			{
				var width = renderBox.x2 - renderBox.x1 + padding * 2;
				var height = renderBox.y2 - renderBox.y1 + padding * 2;
				//this.getDrawBridge().setContextDimension(context, width, height);
				this.changeContextDimension({'width': width, 'height': height});
				/*
				 this.setWidth(width + 'px');
				 this.setHeight(height + 'px');
				 */
				this.setDrawDimension(width, height);
				newDimension = {'width': width, 'height': height};
				baseCoord = {'x': width / 2, 'y': height / 2};
			}

			// debug
			/*
			 console.log('drawOutline', renderBox);
			 var c1 = {'x': renderBox.x1, 'y': renderBox.y1};
			 var c2 = {'x': renderBox.x2, 'y': renderBox.y2};
			 c1 = Kekule.CoordUtils.add(c1, baseCoord);
			 c2 = Kekule.CoordUtils.add(c2, baseCoord);
			 this.getDrawBridge().drawRect(context, c1, c2,
			 {'strokeWidth': 1, 'strokeColor': '#000000', 'fillColor': '#00ff00'}
			 );
			 */
		}
		else
		{
			baseCoord = drawOptions? drawOptions.baseCoord: null;
			if (!baseCoord)
			{
				var dim = this.getDrawClientDimension();
				baseCoord = {'x': dim.width / 2, 'y': dim.height / 2};
				//drawOptions.baseCoord = baseCoord;
				//if (this.getRenderType() !== Kekule.Render.RendererType.R3D)
				{
					var offset = this.getBaseCoordOffset();
					if (offset)
					{
						baseCoord = Kekule.CoordUtils.add(baseCoord, offset);
					}
				}
			}
		}
		baseCoord = this.getDrawBridge().transformScreenCoordToContext(context, baseCoord);
		return {'baseCoord': baseCoord, 'baseCoordPos': Kekule.Render.CoordPos.CENTER, 'newWidgetDimension': newDimension};
	},

	/**
	 * Returns draw options actually used by repaint method.
	 * Returned value may be different to drawOption property to make some custom modifications.
	 * Descendants may override this method.
	 * @returns {Hash}
	 * @private
	 */
	getActualDrawOptions: function()
	{
		return this.getDrawOptions();
	},

	/** @private */
	calcDrawParams: function(overrideOptions)
	{
		var ops = this.getActualDrawOptions();
		if (overrideOptions)
			ops = Object.extend(ops, overrideOptions);
		var baseCoordResult = this.calcDrawBaseCoord(ops);
		return {
			'drawOptions': ops,
			'baseCoord': baseCoordResult.baseCoord,
			'baseCoordPos': baseCoordResult.baseCoordPos,
			'newWidgetDimension': baseCoordResult.newWidgetDimension};
	},

	/** @private */
	_savePainterInitialRenderTransformOptions: function(context, painter)
	{
		var actualTransformOps = painter.getActualInitialRenderTransformOptions(context);
		this._contextTransformOpsMap.set(context, actualTransformOps);
		//console.log('store transform ops', actualTransformOps);
	},

	/**
	 * Repaint the context with current chem object.
	 * @param {Hash} overrideOptions Transform options to do repainting.
	 *   If this param is set to null, all transform options will be recalculated.
	 *   If overrideOptions.preserveTransformOptions is true, transform options remains same as
	 *   last painting process (rather than recalculated).
	 */
	repaint: function(overrideOptions)
	{
		this.beginPaint();
		try
		{
			//var start = (new Date()).getTime();
			if (!overrideOptions || !overrideOptions.doNotClear)
				this.clearContext();

			if (!this.getChemObj())
				return;

			var painter = this.getPainter();
			/*
			 var drawOptions = Object.create(this.getActualDrawOptions() || null);
			 if (overrideOptions)
			 drawOptions = Object.extend(drawOptions, overrideOptions);
			 var baseCoord = this.calcDrawBaseCoord(drawOptions);
			 */
			var context = this.getDrawContext();
			var drawParams;
			var transformOps;

			var transformOpsChanged = true;
			if (overrideOptions && overrideOptions.preserveTransformOptions)
			{
				transformOps = this._contextTransformOpsMap.get(context) || null;
				transformOpsChanged = false;
				//console.log(transformOps);
				drawParams = this.calcDrawParams(transformOps);
			}
			else
				drawParams = this.calcDrawParams(overrideOptions);

			this._lastBaseCoord = drawParams.baseCoord;  // save for next time use in drawOptionChanged
			//console.log('context center', baseCoord);

			// debug
			/*
			 if (this.getRenderType() === Kekule.Render.RendererType.R3D)
			 baseCoord = null;
			 */

			//console.log(drawParams, drawParams.baseCoord, drawParams.drawOptions);
			painter.draw(context, drawParams.baseCoord, drawParams.drawOptions);

			if (transformOpsChanged)
			{
				this._savePainterInitialRenderTransformOptions(context, painter);
			}

			//var end = (new Date()).getTime();
			//var duration = end - start;
			//console.log('draw in ' + duration + ' ms');
		}
		finally
		{
			this.endPaint();
		}
	},

	/** @private */
	beginPaint: function()
	{
		if (!this._paintFlag)
			this._paintFlag = 0;
		++this._paintFlag;
	},
	/** @private */
	endPaint: function()
	{
		if (this._paintFlag > 0)
			--this._paintFlag;
		if (this._paintFlag < 0)
			this._paintFlag = 0;
	},
	/**
	 * Returns whether in the painting process.
	 * @returns {Bool}
	 */
	isPainting: function()
	{
		return this._paintFlag > 0;
	},

	/**
	 * Returns background color used for a special renderType.
	 * @param {Int} renderType
	 * @returns {String}
	 */
	getBackgroundColorOfType: function(renderType)
	{
		return this._bgColorMap[renderType.toString()];
	},
	/**
	 * Set background color used for a special renderType.
	 * @param {String} color
	 * @param {Int} renderType
	 */
	setBackgroundColorOfType: function(color, renderType)
	{
		this._bgColorMap[renderType.toString()] = color;
		if (renderType === this.getRenderType())
			this.setBackgroundColor(color);
	},
	/**
	 * Called after background color is changed, should repaint the context.
	 */
	backgroundColorChanged: function(doNotRepaint)
	{
		var color = this.getBackgroundColor();
		if (color === 'transparent')
			color = null;
		var drawBridge = this.getDrawBridge(true);
		if (drawBridge && drawBridge.setClearColor)
		{
			drawBridge.setClearColor(this.getDrawContext(), color);
			if (!doNotRepaint)
				this.repaint();
		}
	},
	/**
	 * Called after draw options are changed. Should repaint the context.
	 * Repaint is a time-consuming job. So if only rotate/scale/translate options changes,
	 * geometryOptionChanged method should be used instead.
	 * @private
	 */
	drawOptionChanged: function()
	{
		this.repaint();
	},
	/**
	 * Called after only geometry options (translate, scale, rotations) are changed.
	 * @private
	 */
	geometryOptionChanged: function()
	{
		var painter = this.getPainter();
		var drawOptions = this.getDrawOptions();
		if (painter.supportGeometryOptionChange())
		{
			var context = this.getDrawContext();
			painter.changeGeometryOptions(context, drawOptions.baseCoord || this._lastBaseCoord, drawOptions);
			this._savePainterInitialRenderTransformOptions(context, painter);
		}
		else
			this.drawOptionChanged();
	},

	/**
	 * Notify the displayer that in following process, continuous repainting will start and auto size should be disabled.
	 * After this phase, endContinuousRepainting() must be called.
	 */
	beginContinuousRepainting: function()
	{
		this._isContinuousRepainting = true;
	},
	/**
	 * Notify the displayer that continuous phase has ended, turn into normal mode.
	 */
	endContinuousRepainting: function()
	{
		if (this._isContinuousRepainting)
		{
			this._isContinuousRepainting = false;
			this.repaint();
		}
	},

	/**
	 * Reset displayer to initial state (no zoom, rotation and so on).
	 */
	resetDisplay: function()
	{
		var op = this.getDrawOptions();
		op.zoom = this.getInitialZoom() || 1;
		op.rotateX = 0;
		op.rotateY = 0;
		op.rotateZ = 0;
		op.rotateAngle = 0;
		op.rotateAxisVector = null;
		op.rotateMatrix = null;
		//this.drawOptionChanged();
		this.setBaseCoordOffset(null);
		this.geometryOptionChanged();
		return this;
	},

	/**
	 * Returns zoom property in drawOptions.
	 * @returns {Float}
	 */
	getCurrZoom: function()
	{
		return this.getDrawOptions().zoom || 1;
	},
	/**
	 * Zoom to a specified ratio
	 * @param {Float} value
	 * @param {Bool} suspendRendering Set this to true if a immediate repaint is not needed.
	 */
	zoomTo: function(value, suspendRendering)
	{
		this.getDrawOptions().zoom = value;
		//this.drawOptionChanged();
		if (!suspendRendering)
			this.geometryOptionChanged();
		return this;
	},
	/**
	 * Zoom in.
	 */
	zoomIn: function(step)
	{
		var curr = this.getCurrZoom();
		var ratio = ZU.getNextZoomInRatio(curr, step || 1);
		return this.zoomTo(ratio);
	},
	/**
	 * Zoom out.
	 */
	zoomOut: function(step)
	{
		var curr = this.getCurrZoom();
		var ratio = ZU.getNextZoomOutRatio(curr, step || 1);
		return this.zoomTo(ratio);
	},
	/**
	 * Reset to normal size.
	 */
	resetZoom: function()
	{
		return this.zoomTo(this.getInitialZoom() || 1);
	},

	/**
	 * Returns default mol display type in special render mode.
	 * @param {Int} renderType
	 * @return {Int}
	 * @private
	 */
	getDefaultMoleculeDisplayType: function(renderType)
	{
		return (renderType === Kekule.Render.RendererType.R3D)?
			Kekule.Render.Molecule3DDisplayType.DEFAULT: Kekule.Render.Molecule2DDisplayType.DEFAULT;
	},
	/**
	 * Returns current molecule display type.
	 * @returns {Int}
	 */
	getCurrMoleculeDisplayType: function()
	{
		var result = this.getDrawOptions().moleculeDisplayType;
		if (!result) // not set, use default
			result = this.getDefaultMoleculeDisplayType(this.getRenderType());
		return result;
	},
	/*
	 * Change molecule display type.
	 * @param {Int} newType Value from {@link Kekule.Render.Molecule2DDisplayType} or {@link Kekule.Render.Molecule3DDisplayType}.
	 */
	/*
	setMoleculeDisplayType: function(newType)
	{
		this.getDrawOptions().moleculeDisplayType = newType;
		this.drawOptionChanged();
		return this;
	},
	*/
	/**
	 * Hide or show all hydrogen atoms in 3D model.
	 * @param {Int} newType Value from {@link Kekule.Render.Molecule2DDisplayType} or {@link Kekule.Render.Molecule3DDisplayType}.
	 */
	setHideHydrogens: function(newValue)
	{
		this.getDrawOptions().hideHydrogens = newValue;
		this.drawOptionChanged();
		return this;
	},

	/**
	 * 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)
	{
		//console.log(this.getDrawBridge(), this.getDrawContext());
		return this.getDrawBridge().exportToDataUri(this.getDrawContext(), dataType, options);
	},

	////// about configurator
	/** @ignore */
	createConfigurator: function($super)
	{
		var result = $super();
		result.addEventListener('configChange', function(e){
			// render config change need to repaint context
			this.repaint();
		}, this);
		return result;
	}
});

/**
 * A special class to give a setting facade for ChemObjDisplayer.
 * Do not use this class alone.
 * @class
 * @augments Kekule.Widget.BaseWidget.Settings
 *
 * @param {Kekule.ChemWidget.ChemObjDisplayer} displayer
 * @ignore
 */
Kekule.ChemWidget.ChemObjDisplayer.Settings = Class.create(Kekule.Widget.BaseWidget.Settings,
/** @lends Kekule.ChemWidget.ChemObjDisplayer.Settings# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ChemObjDisplayer.Settings',
	/** @construct */
	initialize: function($super, displayer)
	{
		$super(displayer);
	},
	/** @private */
	initProperties: function()
	{
		//this.defineProp('displayer', {'dataType': 'Kekule.ChemWidget.ChemObjDisplayer', 'serializable': false, 'scope': PS.PUBLIC});
		this.defineDelegatedProps(['enableLoadNewFile']);
	},
	/** @ignore */
	getDisplayer: function()
	{
		return this.getWidget();
	}
});

/**
 * A special widget class to open a config widget for ChemObjDisplayer.
 * Do not use this widget alone.
 * @class
 * @augments Kekule.Widget.Configurator
 *
 * @param {Kekule.ChemWidget.ChemObjDisplayer} displayer
 */
Kekule.ChemWidget.ChemObjDisplayer.Configurator = Class.create(Kekule.Widget.Configurator,
/** @lends Kekule.ChemWidget.ChemObjDisplayer.Configurator# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ChemObjDisplayer.Configurator',
	/** @private */
	TAB_BTN_DATA_FIELD: '__$data__',
	/** @construct */
	initialize: function($super, displayer)
	{
		$super(displayer);
	},
	/** @ignore */
	initPropValues: function($super)
	{
		$super();
		this.setLayout(Kekule.Widget.Layout.HORIZONTAL);
	},
	/** @private */
	getCategoryInfos: function($super)
	{
		var result = $super();
		var displayer = this.getDisplayer();

		// renderConfigs and displayerConfigs
		var configObjs = [displayer.getDisplayerConfigs(), displayer.getRenderConfigs()];
		for (var j = 0, k = configObjs.length; j < k; ++j)
		{
			var config = configObjs[j];
			var props = config.getPropListOfScopes([Class.PropertyScope.PUBLISHED]);
			for (var i = 0, l = props.getLength(); i < l; ++i)
			{
				var propInfo = props.getPropInfoAt(i);
				var obj = config.getPropValue(propInfo.name);
				if (obj)
				{
					result.push({
						'obj': obj,
						'name': propInfo.name,
						'title': propInfo.title,
						'description': propInfo.description
					});
				}
			}
		}
		return result;
	},
	/** @private */
	getDisplayer: function()
	{
		return this.getWidget();
	}
});

/**
 * Action for loading a file in chem displayer.
 * @class
 * @augments Kekule.ActionLoadFileData
 */
Kekule.ChemWidget.ActionDisplayerLoadFile = Class.create(/*Kekule.ActionFileOpen*/Kekule.ActionLoadFileData,
/** @lends Kekule.ChemWidget.ActionDisplayerLoadFile# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerLoadFile',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_LOADFILE,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super();
		this.setDisplayer(displayer);
		this.setText(/*CWT.CAPTION_LOADFILE*/Kekule.$L('ChemWidgetTexts.CAPTION_LOADFILE'));
		this.setHint(/*CWT.HINT_LOADFILE*/Kekule.$L('ChemWidgetTexts.HINT_LOADFILE'));
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('displayer', {'dataType': 'Kekule.ChemWidget.ChemObjDisplayer', 'serializable': false});
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		this.setEnabled(this.getEnabled() && displayer && displayer.getEnabled() && displayer.getEnableLoadNewFile());
	},
	/* @private */
	/*
	doFileOpened: function(files)
	{
		if (files && files.length)
		{
			var file = files[0];
			this.getDisplayer().loadFromFile(file);
		}
	}
	*/
	/** @private */
	doDataLoaded: function(data, fileName, loaded)
	{
		//console.log('do data loaded', fileName);
		if (loaded)
			this.getDisplayer().loadFromData(data, null, fileName);
	}
});

/**
 * Base class for actions for chem displayer.
 * @class
 * @augments Kekule.Action
 *
 * @param {Kekule.ChemWidget.ChemObjDisplayer} displayer Target displayer widget.
 *
 * @property {Kekule.ChemWidget.ChemObjDisplayer} displayer Target displayer widget.
 */
Kekule.ChemWidget.ActionOnDisplayer = Class.create(Kekule.Action,
/** @lends Kekule.ChemWidget.ActionOnDisplayer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionOnDisplayer',
	/** @constructs */
	initialize: function($super, displayer, caption, hint)
	{
		$super();
		this.setDisplayer(displayer);
		this.setText(caption);
		this.setHint(hint);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('displayer', {'dataType': 'Kekule.ChemWidget.ChemObjDisplayer', 'serializable': false});
	},
	/** @private */
	doUpdate: function()
	{
		var displayer = this.getDisplayer();
		this.setEnabled(displayer && displayer.getChemObj() && displayer.getChemObjLoaded() && displayer.getEnabled());
	}
});

/**
 * Action for set chem object to null in chem displayer.
 * @class
 * @augments Kekule.ActionOnDisplayer
 */
Kekule.ChemWidget.ActionDisplayerClear = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerClear# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerClear',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_CLEAROBJS,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, Kekule.$L('ChemWidgetTexts.CAPTION_CLEAROBJS'), Kekule.$L('ChemWidgetTexts.HINT_CLEAROBJS'));
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		this.setEnabled(displayer && displayer.getEnabled() && displayer.getChemObj() /* && displayer.getChemObjLoaded()*/);
	},
	/** @private */
	doExecute: function(target)
	{
		var displayer = this.getDisplayer();
		displayer.setChemObj(null);
	}
});

/**
 * Action for loading chem object (either from file or from pasted text) to chem displayer.
 * @class
 * @augments Kekule.ActionOnDisplayer
 */
Kekule.ChemWidget.ActionDisplayerLoadData = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerLoadData# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerLoadData',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_LOADDATA,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, /*CWT.CAPTION_LOADDATA, CWT.HINT_LOADDATA*/Kekule.$L('ChemWidgetTexts.CAPTION_LOADDATA'), Kekule.$L('ChemWidgetTexts.HINT_LOADDATA'));
		//this.setDisplayer(displayer);
		//this.setText(CWT.CAPTION_LOADDATA);
		//this.setHint(CWT.HINT_LOADDATA);
		/*
		this._openFileAction = new Kekule.ActionFileOpen();
		this._openFileAction.update();
		this._openFileAction.addEventListener('open', this.reactFileLoad, this);
		this._sBtnLoadFromFile = CWT.CAPTION_LOADDATA_FROM_FILE;
		*/
	},
	/** @ignore */
	finalize: function($super)
	{
		//this._openFileAction.finalize();
		$super();
	},
	initPropValues: function($super)
	{
		$super();
		this.setDialogShowType(Kekule.Widget.ShowHideType.DROPDOWN);
	},
	/** @private */
	initProperties: function()
	{
		// private
		this.defineProp('dataDialog', {'dataType': 'Kekule.ChemWidget.LoadDataDialog', 'serializable': false, 'setter': null,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('dataDialog');
				if (!result)
				{
					result = this.createDataDialog();
					this.setPropStoreFieldValue('dataDialog', result);
				}
				return result;
			}
		});
		this.defineProp('dialogShowType', {'dataType': DataType.INT});
	},
	/** @private */
	createDataDialog: function()
	{
		var doc = this.getDisplayer().getDocument();
		var result = new Kekule.ChemWidget.LoadDataDialog(doc);
		return result;
	},

	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		this.setEnabled(displayer && displayer.getEnabled() && displayer.getEnableLoadNewFile());
	},
	/** @private */
	doExecute: function(target)
	{
		var self = this;
		var dialog = this.getDataDialog();
		dialog.setAllowedFormatIds(this.getDisplayer().getAllowedInputFormatIds() || Kekule.IO.ChemDataReaderManager.getAllReadableFormatIds());

		var formatSelector = dialog._formatSelector;

		//var formatItems = this.getFormatSelectorItems(chemObj, writerInfos);
		//formatSelector.setItems(formatItems);

		// open a dialog to choose format first, then save to file
		var showType = this.getDialogShowType();
		var openMethod = (showType === Kekule.Widget.ShowHideType.DIALOG)? dialog.openModal: dialog.open;
		openMethod.apply(dialog, [
			function(result)
			{
				if (dialog.isPositiveResult(result))  // load
				{
					/*
					var data = dialog._dataEditor.getValue();
					var mimeType = dialog._formatSelector.getValue();

					//self.setLastFormat(formatId);
					var displayer = self.getDisplayer();
					displayer.loadFromData(data, mimeType);
					*/
					var displayer = self.getDisplayer();
					displayer.load(dialog.getChemObj());
				}
			}, target, showType]);
	}
});

/**
 * Action for saving chem object to file in chem displayer.
 * @class
 * @augments Kekule.ActionOnDisplayer
 *
 * @property {Int} dialogShowType Value from {@link Kekule.Widget.ShowHideType}, diplay type of save dialog.
 */
Kekule.ChemWidget.ActionDisplayerSaveFile = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerSaveFile# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerSaveFile',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_SAVEFILE,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, /*CWT.CAPTION_SAVEFILE, CWT.HINT_SAVEFILE*/Kekule.$L('ChemWidgetTexts.CAPTION_SAVEFILE'), Kekule.$L('ChemWidgetTexts.HINT_SAVEFILE'));
		//this.setDisplayer(displayer);
		//this.setText(CWT.CAPTION_SAVEFILE);
		//this.setHint(CWT.HINT_SAVEFILE);
		this._saveAction = new Kekule.ActionFileSave();
	},
	/** @ignore */
	finalize: function($super)
	{
		this._saveAction.finalize();
		$super();
	},
	/** @ignore */
	initPropValues: function($super)
	{
		$super();
		this.setDialogShowType(Kekule.Widget.ShowHideType.DROPDOWN);
	},
	/** @private */
	initProperties: function()
	{
		// private
		this.defineProp('formatDialog', {'dataType': 'Kekule.Widget.Dialog', 'serializable': false, 'setter': null,
			'getter': function()
			{
				var result = this.getPropStoreFieldValue('formatDialog');
				if (!result)
				{
					result = this.createFormatDialog();
					this.setPropStoreFieldValue('formatDialog', result);
				}
				return result;
			}
		});
		this.defineProp('lastFormat', {'dataType': DataType.STRING, 'serializable': false});
		this.defineProp('currSaveData', {'dataType': DataType.VARIANT, 'serializable': false});
		this.defineProp('dialogShowType', {'dataType': DataType.INT});
	},

	/** @private */
	getChemObjSrcInfo: function(chemObj)
	{
		var displayer = this.getDisplayer();
		return displayer.getChemObjSrcInfo? displayer.getChemObjSrcInfo(chemObj):
			chemObj.getSrcInfo? chemObj.getSrcInfo():
			null;
	},
	/** @private */
	getChemObjSrcFormat: function(chemObj)
	{
		var srcInfo = this.getChemObjSrcInfo(chemObj);
		return srcInfo? srcInfo.format: null;
	},

	/** @private */
	createFormatDialog: function()
	{
		var doc = this.getDisplayer().getDocument();
		var result = new Kekule.Widget.Dialog(doc, /*CWT.CAPTION_CHOOSEFILEFORMAT*/Kekule.$L('ChemWidgetTexts.CAPTION_SAVEDATA_DIALOG'), [Kekule.Widget.DialogButtons.OK, Kekule.Widget.DialogButtons.CANCEL]);
		result.addClassName(CCNS.DIALOG_CHOOSE_FILE_FORAMT);
		result.setAutoAdjustSizeOnPopup(true);
		// label
		var elem = doc.createElement('div');
		elem.innerHTML = Kekule.$L('ChemWidgetTexts.CAPTION_SELECT_FORMAT'); //CWT.CAPTION_SELECT_FORMAT;
		result.getClientElem().appendChild(elem);
		// format selector
		elem = doc.createElement('div');
		result.getClientElem().appendChild(elem);
		var formatSelector = new Kekule.Widget.SelectBox(result);
		formatSelector.addClassName(CCNS.DIALOG_CHOOSE_FILE_FORAMT_FORMATBOX);
		formatSelector.appendToElem(elem);
		formatSelector.addEventListener('valueChange', this.reactFormatSelectorChange, this);
		result._formatSelector = formatSelector;
		// label
		var elem = doc.createElement('div');
		elem.innerHTML = Kekule.$L('ChemWidgetTexts.CAPTION_PREVIEW_FILE_CONTENT'); //CWT.CAPTION_PREVIEW_FILE_CONTENT;
		result.getClientElem().appendChild(elem);
		// preview textarea
		elem = doc.createElement('div');
		result.getClientElem().appendChild(elem);
		var previewTextArea = new Kekule.Widget.TextEditor(result); //new Kekule.Widget.TextArea(result);
		previewTextArea.setReadOnly(true);
		previewTextArea.setWrap('off');
		previewTextArea.setToolbarPos(Kekule.Widget.Position.BOTTOM);
		previewTextArea.addClassName(CCNS.DIALOG_CHOOSE_FILE_FORAMT_PREVIEWER);
		previewTextArea.appendToElem(elem);
		result._previewTextArea = previewTextArea;

		if (result.setResizable)
			result.setResizable(true);

		return result;
	},
	/** @private */

	getAvailableWriterInfos: function(chemObj)
	{
		//var obj = this.getDisplayer().getChemObj();
		if (!chemObj)
			return [];
		else
		{
			var result = Kekule.IO.ChemDataWriterManager.getAvailableWriterInfos(null, chemObj);
			return result;
		}
	},

	/** @private */
	getFormatSelectorItems: function(chemObj, writerInfos)
	{
		//console.log(writerInfos);
		var result = [];
		var formatIds = [];

		var srcInfo = this.getChemObjSrcInfo(chemObj);
		var srcFormat = (srcInfo)? srcInfo.format: null;

		for (var i = 0, l = writerInfos.length; i < l; ++i)
		{
			var info = writerInfos[i];
			Kekule.ArrayUtils.pushUnique(formatIds, info.formatId);
		}
		formatIds = Kekule.ArrayUtils.intersect(this.getAllowedFormatIds(), formatIds);

		for (var i = 0, l = formatIds.length; i < l; ++i)
		{
			var idInfo = Kekule.IO.DataFormatsManager.getFormatInfo(formatIds[i]);
			if (idInfo)
			{
				var fileExts = Kekule.ArrayUtils.clone(Kekule.ArrayUtils.toArray(idInfo.fileExts));
				for (var j = 0, k = fileExts.length; j < k; ++j)
				{
					fileExts[j] = '*.' + fileExts[j];
				}
				var sFileExt = fileExts.join(', ');
				var text = idInfo.title;
				/*
				if (idInfo.mimeType)
					text += ' | ' + idInfo.mimeType;
				*/
				if (sFileExt)
					text += ' (' + sFileExt + ')';
				var selected = srcFormat? (formatIds[i] === srcFormat):
					this.getLastFormat()? (this.getLastFormat() === formatIds[i]):
					i === 0;
				result.push({
					'value': idInfo.id,
					'text': text,
					'title': idInfo.mimeType,
					'data': idInfo,
					'selected': selected
				});
			}
		}
		result.sort(function(a, b)
			{
				return (a.text < b.text)? -1:
					(a.text > b.text)? 1:
						0;
			}
		);
		return result;
	},
	/** @private */
	getAllowedFormatIds: function()
	{
		return this.getDisplayer().getAllowedOutputFormatIds() || Kekule.IO.ChemDataWriterManager.getAllWritableFormatIds();
	},
	/** @private */
	getFileFilters: function(formatItems)
	{
		var result = [];
		for (var i = 0, l = formatItems.length; i < l; ++i)
		{
			var format = formatItems[i];
			var info = format.data;
			var title = format.text || format.title || format.value;
			var exts = Kekule.ArrayUtils.toArray(info.fileExts);
			/*
			for (var j = 0, k = exts.length; j < k; ++j)
			{
				result.push({'title': title, 'filter': '.' + exts[j]});
			}
			*/
			result.push({'title': title, 'filter': '.' + exts.join(',.')});
		}
		// add all and any filter
		result.push(Kekule.NativeServices.FILTER_ALL_SUPPORT);
		result.push(Kekule.NativeServices.FILTER_ANY);
		return result;
	},
	/** @private */
	updateFormatItems: function(chemObj)
	{
		this._formatItems = this.getFormatSelectorItems(chemObj, this.getAvailableWriterInfos());
		this._formatSelector.setItems(this._formatItems);
		var filters = this.getFileFilters(this._formatItems);
		this._saveAction.setFilters(filters);
	},
	/** @private */
	reactFormatSelectorChange: function()
	{
		var formatId = this.getFormatDialog()._formatSelector.getValue();
		this.updatePreview(formatId, this.getFormatDialog());
	},
	/** @private */
	updatePreview: function(formatId, dialogWidget)
	{
		var textArea = dialogWidget._previewTextArea;
		textArea.setValue('');
		if (formatId)
		{
			var formatInfo = Kekule.IO.DataFormatsManager.getFormatInfo(formatId);
			var obj = this.getTargetObj();
			var srcInfo = this.getChemObjSrcInfo(obj);
			if (srcInfo && srcInfo.format === formatId && srcInfo.data)  // can use src data
			{
				textArea.setValue(srcInfo.data);
				this.setCurrSaveData(srcInfo.data);
			}
			else
			{
				// get suitable writer
				/*
				var writer = Kekule.IO.ChemDataWriterManager.getWriterByFormat(formatId, null, obj);
				if (writer)
				{
					var doCanonicalize = this.getDisplayer().getDisplayerConfigs().getIoConfigs().getCanonicalizeBeforeSave();
					if (doCanonicalize && obj.canonicalize)  // canonicalize first
					{
						var obj = obj.clone? obj.clone(true): obj;  // clone with id
						obj.canonicalize();
					}
					var data = writer.writeData(obj, formatInfo.dataType, formatId);
					textArea.setValue(data);
					this.setCurrSaveData(data);
				}
				*/
				var data = this.getDisplayer().saveData(formatId, formatInfo.dataType, obj);
				textArea.setValue(data);
				this.setCurrSaveData(data);
			}
		}
	},
	/** @private */
	getTargetObj: function(formatId)
	{
		return this.getDisplayer().getSavingTargetObj();
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		if (this._saveAction)
			this._saveAction.update();
		this.setEnabled(displayer && displayer.getEnabled() && displayer.getChemObj()/* && this._saveAction.getEnabled()*/);
	},
	/** @private */
	doExecute: function(target)
	{
		var self = this;
		var dialog = this.getFormatDialog();
		var chemObj = this.getTargetObj(); //this.getDisplayer().getChemObj();

		var formatSelector = dialog._formatSelector;
		var writerInfos = this.getAvailableWriterInfos(chemObj);
		var formatItems = this.getFormatSelectorItems(chemObj, writerInfos);
		formatSelector.setItems(formatItems);
		this.reactFormatSelectorChange();

		// update OK button enabled
		var saveEnabled = false;
		if (this._saveAction)
		{
			//this._saveAction.update();
			saveEnabled = this._saveAction.getEnabled();
		}
		var btn = dialog.getDialogButton(Kekule.Widget.DialogButtons.OK);
		if (btn)
			btn.setEnabled(saveEnabled);

		// open a dialog to choose format first, then save to file
		var showType = this.getDialogShowType();
		var openMethod = (showType === Kekule.Widget.ShowHideType.DIALOG)? dialog.openModal: dialog.open;
		openMethod.apply(dialog, [
			function(result)
			{
				if (result === Kekule.Widget.DialogButtons.OK)  // save
				{
					//var data = dialog._previewTextArea.getValue();
					var data = self.getCurrSaveData();
					self._saveAction.setData(data);
					var formatId = dialog._formatSelector.getValue();
					self.setLastFormat(formatId);
					//var fileExts = Kekule.IO.DataFormatsManager.getFileExts(formatId);
					var formatInfo = Kekule.IO.DataFormatsManager.getFormatInfo(formatId);
					var fileExts = formatInfo.fileExts;
					var ext = Kekule.ArrayUtils.toArray(fileExts[0] || fileExts);
					var filters = [
						{'title': formatInfo.title || formatInfo.mimeType, 'filter': '.' + ext.join(',.')},
						Kekule.NativeServices.FILTER_ANY
					];
					var fileName = /*CWT.S_DEF_SAVE_FILENAME*/Kekule.$L('ChemWidgetTexts.S_DEF_SAVE_FILENAME') + '.' + ext;
					self._saveAction.setFileName(fileName);
					self._saveAction.setFilters(filters);
					//console.log(fileName);
					self._saveAction.execute(target);
				}
			}, target, showType]
		);
	}
});

/**
 * Action for reset viewer.
 * @class
 * @augments Kekule.ChemWidget.ActionOnDisplayer
 */
Kekule.ChemWidget.ActionDisplayerReset = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerReset# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerReset',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_RESET,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, /*CWT.CAPTION_RESETVIEW, CWT.HINT_RESETVIEW*/Kekule.$L('ChemWidgetTexts.CAPTION_RESETVIEW'), Kekule.$L('ChemWidgetTexts.HINT_RESETVIEW'));
	},
	/** @private */
	doExecute: function()
	{
		this.getDisplayer().resetDisplay();
	}
});

/**
 * Action for zoom in the viewer.
 * @class
 * @augments Kekule.ChemWidget.ActionViewer
 */
Kekule.ChemWidget.ActionDisplayerZoomIn = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerZoomIn# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerZoomIn',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_ZOOMIN,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, /*CWT.CAPTION_ZOOMIN, CWT.HINT_ZOOMIN*/Kekule.$L('ChemWidgetTexts.CAPTION_ZOOMIN'), Kekule.$L('ChemWidgetTexts.HINT_ZOOMIN'));
	},
	/** @private */
	doExecute: function()
	{
		this.getDisplayer().zoomIn();
	}
});
/**
 * Action for zoom out the viewer.
 * @class
 * @augments Kekule.ChemWidget.ActionViewer
 */
Kekule.ChemWidget.ActionDisplayerZoomOut = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerZoomOut# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerZoomOut',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_ZOOMOUT,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, /*CWT.CAPTION_ZOOMOUT, CWT.HINT_ZOOMOUT*/Kekule.$L('ChemWidgetTexts.CAPTION_ZOOMOUT'), Kekule.$L('ChemWidgetTexts.HINT_ZOOMOUT'));
	},
	/** @private */
	doExecute: function()
	{
		this.getDisplayer().zoomOut();
	}
});
/**
 * Action for reset zoom to 1 on viewer.
 * @class
 * @augments Kekule.ChemWidget.ActionViewer
 */
Kekule.ChemWidget.ActionDisplayerResetZoom = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerResetZoom# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerResetZoom',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_RESET_ZOOM,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, /*CWT.CAPTION_RESETZOOM, CWT.HINT_RESETZOOM*/Kekule.$L('ChemWidgetTexts.CAPTION_RESETZOOM'), Kekule.$L('ChemWidgetTexts.HINT_RESETZOOM'));
	},
	/** @private */
	doExecute: function()
	{
		this.getDisplayer().resetZoom();
	}
});

/**
 * Action for show or hide all hydrogen atoms in 3D mode displayer.
 * @class
 * @augments Kekule.ChemWidget.ActionOnDisplayer
 */
Kekule.ChemWidget.ActionDisplayerHideHydrogens = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerHideHydrogens# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerHideHydrogens',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_MOL_HIDE_HYDROGENS,
	/** @constructs */
	initialize: function($super, displayer)
	{
		$super(displayer, /*CWT.CAPTION_HIDEHYDROGENS, CWT.HINT_HIDEHYDROGENS*/Kekule.$L('ChemWidgetTexts.CAPTION_HIDEHYDROGENS'), Kekule.$L('ChemWidgetTexts.HINT_HIDEHYDROGENS'));
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		var flag = displayer && (displayer.getRenderType() === Kekule.Render.RendererType.R3D);
		this.setDisplayed(/*this.getDisplayed() &&*/ flag).setEnabled(this.getEnabled() && flag);
	},
	/** @private */
	doExecute: function()
	{
		var checked = !this.getChecked();
		this.setChecked(checked);
		this.getDisplayer().setHideHydrogens(checked);
	}
});

/**
 * Base action for change molecule display type of displayer.
 * @class
 * @augments Kekule.ChemWidget.ActionOnDisplayer
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase',
	/** @constructs */
	initialize: function($super, displayer, caption, hint, displayType)
	{
		$super(displayer, caption, hint);
		this._displayType = displayType;
		this.setCheckGroup('__molDisplayType__');
	},
	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		this.setChecked(displayer && displayer.getCurrMoleculeDisplayType() === this.getMolDisplayType());
	},
	/** @private */
	doExecute: function()
	{
		this.getDisplayer().setMoleculeDisplayType(this._displayType);
		this.setChecked(true);
	},
	/**
	 * Returns molecule display type set by this action.
	 * @returns {Int}
	 */
	getMolDisplayType: function()
	{
		return this._displayType;
	}
});
/**
 * Base action for change 2D molecule display type of displayer.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase2D = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase2D# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase2D',
	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		var flag = displayer && (displayer.getRenderType() === Kekule.Render.RendererType.R2D);
		this.setDisplayed(/*this.getDisplayed() &&*/ flag).setEnabled(this.getEnabled() && flag);
	}
});
/**
 * Base action for change 3D molecule display type of displayer.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D',
	/** @private */
	doUpdate: function($super)
	{
		$super();
		var displayer = this.getDisplayer();
		var flag = displayer && (displayer.getRenderType() === Kekule.Render.RendererType.R3D);
		this.setDisplayed(/*this.getDisplayed() &&*/ flag).setEnabled(this.getEnabled() && flag);
	}
});
/**
 * Action change displayer's molecule display type to {@link Kekule.Render.Molecule2DDisplayType.SKELETAL}.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase2D
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSkeletal = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase2D,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSkeletal# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSkeletal',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_MOL_DISPLAY_SKELETAL,
	/** @constructs */
	initialize: function($super, viewer)
	{
		$super(viewer, /*CWT.CAPTION_SKELETAL, CWT.HINT_SKELETAL,*/
			Kekule.$L('ChemWidgetTexts.CAPTION_SKELETAL'), Kekule.$L('ChemWidgetTexts.HINT_SKELETAL'),
			Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSkeletal.TYPE);
	}
});
/** @Ignore */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSkeletal.TYPE = Kekule.Render.Molecule2DDisplayType.SKELETAL;
/**
 * Action change displayer's molecule display type to {@link Kekule.Render.Molecule2DDisplayType.CONDENSED}.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase2D
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeCondensed = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase2D,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeCondensed# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeCondensed',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_MOL_DISPLAY_CONDENSED,
	/** @constructs */
	initialize: function($super, viewer)
	{
		$super(viewer, /*CWT.CAPTION_CONDENSED, CWT.HINT_CONDENSED,*/
			Kekule.$L('ChemWidgetTexts.CAPTION_CONDENSED'), Kekule.$L('ChemWidgetTexts.HINT_CONDENSED'),
			Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeCondensed.TYPE);
	}
});
/** @Ignore */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeCondensed.TYPE = Kekule.Render.Molecule2DDisplayType.CONDENSED;
/**
 * Action change displayer's molecule display type to {@link Kekule.Render.Molecule3DDisplayType.WIRE}.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeWire = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeWire# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeWire',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_MOL_DISPLAY_WIRE,
	/** @constructs */
	initialize: function($super, viewer)
	{
		$super(viewer, /*CWT.CAPTION_WIRE, CWT.HINT_WIRE,*/
			Kekule.$L('ChemWidgetTexts.CAPTION_WIRE'), Kekule.$L('ChemWidgetTexts.HINT_WIRE'),
			Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeWire.TYPE);
	}
});
/** @Ignore */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeWire.TYPE = Kekule.Render.Molecule3DDisplayType.WIRE;
/**
 * Action change displayer's molecule display type to {@link Kekule.Render.Molecule3DDisplayType.STICKS}.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSticks = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSticks# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSticks',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_MOL_DISPLAY_STICKS,
	/** @constructs */
	initialize: function($super, viewer)
	{
		$super(viewer, /*CWT.CAPTION_STICKS, CWT.HINT_STICKS,*/
			Kekule.$L('ChemWidgetTexts.CAPTION_STICKS'), Kekule.$L('ChemWidgetTexts.HINT_STICKS'),
			Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSticks.TYPE);
	}
});
/** @Ignore */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSticks.TYPE = Kekule.Render.Molecule3DDisplayType.STICKS;
/**
 * Action change displayer's molecule display type to {@link Kekule.Render.Molecule3DDisplayType.BALL_STICK}.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBallStick = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBallStick# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBallStick',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_MOL_DISPLAY_BALLSTICK,
	/** @constructs */
	initialize: function($super, viewer)
	{
		$super(viewer, /*CWT.CAPTION_BALLSTICK, CWT.HINT_BALLSTICK,*/
			Kekule.$L('ChemWidgetTexts.CAPTION_BALLSTICK'), Kekule.$L('ChemWidgetTexts.HINT_BALLSTICK'),
			Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBallStick.TYPE);
	}
});
/** @Ignore */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBallStick.TYPE = Kekule.Render.Molecule3DDisplayType.BALL_STICK;
/**
 * Action change displayer's molecule display type to {@link Kekule.Render.Molecule3DDisplayType.SPACE_FILL}.
 * @class
 * @augments Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D
 */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSpaceFill = Class.create(Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeBase3D,
/** @lends Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSpaceFill# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSpaceFill',
	/** @private */
	HTML_CLASSNAME: CCNS.ACTION_MOL_DISPLAY_SPACEFILL,
	/** @constructs */
	initialize: function($super, viewer)
	{
		$super(viewer, /*CWT.CAPTION_SPACEFILL, CWT.HINT_SPACEFILL,*/
			Kekule.$L('ChemWidgetTexts.CAPTION_SPACEFILL'), Kekule.$L('ChemWidgetTexts.HINT_SPACEFILL'),
			Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSpaceFill.TYPE);
	}
});
/** @Ignore */
Kekule.ChemWidget.ActionDisplayerChangeMolDisplayTypeSpaceFill.TYPE = Kekule.Render.Molecule3DDisplayType.SPACE_FILL;


})();