Source: widgets/chem/uiMarker/kekule.chemWidget.uiMarkers.js

/**
 * @fileoverview
 * Class to store UI marker information and the renderer to draw them on context.
 * @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /core/kekule.common.js
 * requires /utils/kekule.utils.js
 * requires /render/kekule.render.base.js
 * requires /render/2d/kekule.renderer2D.js
 */

(function()
{

"use strict";

var oneOf = Kekule.oneOf;

/**
 * A abstract marker on context.
 * @class
 * @augments ObjectEx
 *
 * @property {Hash} drawStyles Styles to draw this marker. Can including the following fields:
 *   {
 * 	   strokeWidth, strokeColor: stroke property when drawing marker.
 *     strokeDash: whether the stroke is dashed.
 *     fillColor: fill color when drawing marker, if not set, the marker will not be filled.
 *     opacity: opacity to draw marker.
 * 	 }
 * @property {Bool} visible Whether this marker can be seen in context.
 */
Kekule.ChemWidget.AbstractUIMarker = Class.create(ObjectEx,
/** @lends Kekule.ChemWidget.AbstractUIMarker# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.AbstractUIMarker',
	/** @constructs */
	initialize: function($super)
	{
		$super();
		this.setVisible(true);
	},
	/** @private */
	initProperties: function()
	{
		// draw styles
		this.defineProp('drawStyles', {'dataType': DataType.OBJECT});
		this.defineProp('visible', {'dataType': DataType.BOOL});
	}
});

/**
 * A marker based on meta shape (line, rect, circle...)
 * @class
 * @augments Kekule.ChemWidget.AbstractUIMarker
 *
 * @property {Hash} shapeInfo Meta shape object.
 * @property {Int} shapeType Type of shape.
 */
Kekule.ChemWidget.MetaShapeUiMarker = Class.create(Kekule.ChemWidget.AbstractUIMarker,
/** @lends Kekule.ChemWidget.MetaShapeUiMarker# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.MetaShapeUiMarker',
	/** @constructs */
	initialize: function($super, shapeInfo)
	{
		$super();
		if (shapeInfo)
			this.setShapeInfo(shapeInfo);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('shapeInfo', {'dataType': DataType.OBJECT});
		this.defineProp('shapeType', {'dataType': DataType.INT, 'serializable': false, 'setter': null,
			'getter': function()
				{
					var info = this.getShapeInfo();
					return info? info.shapeType: null;
				}
		});
	}
});
/** @ignore */
Kekule.ChemWidget.MetaShapeUIMarker = Kekule.ChemWidget.MetaShapeUiMarker;  // an alias for backward compatible

/**
 * A marker based on text.
 * @class
 * @augments Kekule.ChemWidget.AbstractUIMarker
 *
 * @property {Hash} coord Coord of text marker.
 * @property {String} text.
 *
 * @params {String} text
 * @params {Hash} coord
 */
Kekule.ChemWidget.TextUiMarker = Class.create(Kekule.ChemWidget.AbstractUIMarker,
/** @lends Kekule.ChemWidget.TextUiMarker# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.TextUiMarker',
	/** @constructs */
	initialize: function($super, text, coord)
	{
		$super();
		if (text)
			this.setText(text);
		if (coord)
			this.setCoord(coord);
	},
	/** @private */
	initProperties: function()
	{
		this.defineProp('text', {'dataType': DataType.STRING});
		this.defineProp('coord', {'dataType': DataType.HASH});
	}
});

/**
 * Represents all UI markers on context.
 * @class
 * @augments ObjectEx
 *
 * @property {Array} markers All markers.
 */
Kekule.ChemWidget.UiMarkerCollection = Class.create(ObjectEx,
/** @lends Kekule.ChemWidget.UiMarkerCollection# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.UiMarkerCollection',
	/** @private */
	initProperties: function()
	{
		this.defineProp('markers', {
			'dataType': DataType.ARRAY,
			'getter': function()
				{
					var result = this.getPropStoreFieldValue('markers');
					if (!result)
					{
						result = [];
						this.setPropStoreFieldValue('markers', result);
					}
					return result;
				},
			'setter': null
		});
	},
	/**
	 * Get marker count.
	 * @returns {Int}
	 */
	getMarkerCount: function()
	{
		return this.getMarkers().length;
	},
	/**
	 * Get marker at index.
	 * @param {Int} index
	 * @returns {Kekule.ChemWidget.AbstractMarker}
	 */
	getMarkerAt: function(index)
	{
		return this.getMarkers()[index];
	},
	/**
	 * Add a marker to collection.
	 * @param {Kekule.ChemWidget.AbstractMarker} marker
	 */
	addMarker: function(marker)
	{
		return this.getMarkers().push(marker);
	},
	/**
	 * Get index of marker in collection.
   * @param {Kekule.ChemWidget.AbstractMarker} marker
   * @returns {Int}
	 */
	indexOfMarker: function(marker)
	{
		return this.getMarkers().indexOf(marker);
	},
	/**
	 * Remove a marker in collection.
   * @param {Kekule.ChemWidget.AbstractMarker} marker
	 */
	removeMarker: function(marker)
	{
		var markers = this.getMarkers();
		var index = markers.indexOf(marker);
		if (index >= 0)
			markers.splice(index, 1);
	},
	/**
	 * Remove all markers in collection.
	 */
	clearMarkers: function()
	{
		this.setPropStoreFieldValue('markers', []);
	}
});


/**
 * Base Render for UI marker.
 * @class
 * @augments Kekule.Render.Base2DRenderer
 */
Kekule.ChemWidget.UiMarker2DRenderer = Class.create(Kekule.Render.Base2DRenderer,
/** @lends Kekule.ChemWidget.UiMarkerRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.UiMarkerRenderer'
});

/**
 * Render for {@link Kekule.ChemWidget.MetaShapeUiMarker}.
 * @class
 * @augments Kekule.Render.UiMarker2DRenderer
 */
Kekule.ChemWidget.MetaShapeUiMarker2DRenderer = Class.create(Kekule.ChemWidget.UiMarker2DRenderer,
/** @lends Kekule.ChemWidget.MetaShapeUiMarker2DRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.MetaShapeUiMarker2DRenderer',
	/** @private */
	doDrawSelf: function($super, context, baseCoord, options)
	{
		var marker = this.getChemObj();
		var shapeInfo = marker.getShapeInfo();
		if (!shapeInfo)
			return null;

		/* ignore baseCoord, since UI marker is not inhertied from ChemObject and do not has coord2D property
		if (!baseCoord)
			baseCoord = this.getAutoBaseCoord(options);
		*/

		var ops = Object.create(options);
		ops = Object.extend(ops, marker.getDrawStyles() || {});

		// set stroke & fill color and so on
		if (ops.color)
		{
			if (!ops.strokeColor)
				ops.strokeColor = ops.color;
			if (!ops.fillColor)
				ops.fillColor = ops.color;
		}

		var result;
		if (Kekule.Render.MetaShapeUtils.isCompositeShape(shapeInfo))  // composite shapes
		{
			if (shapeInfo.length > 1)  // more than one shape, need draw group
			{
				result = this.createDrawGroup(context);
				for (var i = 0, l = shapeInfo.length; i < l; ++i)
				{
					var shape = shapeInfo[i];
					this.doDrawShape(context, result, shape, ops);
				}
			}
			else if (shapeInfo.length === 1)  // only one shape, no need of group
			{
				result = this.doDrawShape(context, null, shapeInfo[0], ops);
			}
			else  // no actual shapes
				return null;
		}
		else
			result = this.doDrawShape(context, null, shapeInfo, ops);

		return result;
	},

	/** @private */
	doDrawShape: function(context, markerGroup, shape, options)
	{
		var T = Kekule.Render.MetaShapeType;
		var result;

		if (Kekule.Render.MetaShapeUtils.isCompositeShape(shape))  // complex shape
		{
			result = this.createDrawGroup(context);
			for (var i = 0, l = shape.length; i < l; ++i)
			{
				var childResult = this.doDrawShape(context, result, shape[i], options);
			}
		}
		else  // simple shape
		{
			var coords = shape.coords;
			//console.log('do draw shape', shape.shapeType, options, coords);
			switch (shape.shapeType)
			{
				// TODO: Point and circle currently does not support stroke dash
				case T.POINT:
					result = this.drawCircle(context, coords[0], 1, options); break;
				case T.CIRCLE:
					result = this.drawCircle(context, coords[0], shape.radius, options); break;
				case T.LINE:
				{
					var ops = options;
					if (shape.width)
					{
						ops = Object.create(options);
						ops.strokeWidth = shape.width;
					}
					result = this.drawLine(context, coords[0], coords[1], ops); break;
				}
				case T.RECT:
				{
					result = this.drawRect(context, coords[0], coords[1], options);
					break;
				}
				case T.ARC:
				{
					var ops = Object.extend({}, options);
					ops.strokeWidth = shape.width;
					ops.fillColor = null;  // do not fill
					result = this.drawArc(context, coords[0], shape.radius, shape.startAngle, shape.endAngle, shape.anticlockwise, ops);
					break;
				}
				case T.POLYGON: case T.POLYLINE:
				{
					var args = [];
					for (var i = 0, l = coords.length; i < l; ++i)
					{
						var sMethod = (i === 0)? 'M': 'L';
						args.push(sMethod);
						var coordArray = [coords[i].x, coords[i].y];
						args.push(coordArray);
					}
					// close
					if (shape.shapeType === T.POLYGON)
					{
						args.push('L');
						coordArray = [coords[0].x, coords[0].y];
						args.push(coordArray);
					}
					var path = Kekule.Render.DrawPathUtils.makePath.apply(this, args);
					result = this.drawPath(context, path, options);
					break;
				}
			}
		}
		if (markerGroup)
			this.addToDrawGroup(result, markerGroup);
		return result;
	}
});

/**
 * Render for {@link Kekule.ChemWidget.TextUiMarker}.
 * @class
 * @augments Kekule.Render.UiMarker2DRenderer
 */
Kekule.ChemWidget.TextUiMarker2DRenderer = Class.create(Kekule.ChemWidget.UiMarker2DRenderer,
/** @lends Kekule.ChemWidget.TextUiMarker2DRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.TextUiMarker2DRenderer',
	/** @private */
	doDrawSelf: function($super, context, baseCoord, options)
	{
		var marker = this.getChemObj();
		var ops = {
			textBoxXAlignment: Kekule.Render.BoxXAlignment.CENTER,
			textBoxYAlignment: Kekule.Render.BoxYAlignment.CENTER
		};  // default aligment
		ops = Object.extend(ops, options);

		ops = Object.extend(ops, marker.getDrawStyles() || {});
		var coord = marker.getCoord();
		var text = marker.getText();

		if (text && coord)
			return this.drawText(context, coord, marker.getText(), ops);
		else
			return null;
	}
});


/**
 * Render for {@link Kekule.ChemWidget.UiMarkerCollection}.
 * @class
 * @augments Kekule.Render.UiMarker2DRenderer
 */
Kekule.ChemWidget.UiMarkerCollection2DRenderer = Class.create(Kekule.ChemWidget.UiMarker2DRenderer,
/** @lends Kekule.ChemWidget.UiMarkerCollection2DRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.UiMarkerCollection2DRenderer',

	/** @ignore */
	getChildObjs: function($super)
	{
		var collection = this.getChemObj();
		var markers = collection.getMarkers();
		var result = [];
		for (var i = 0, l = markers.length; i < l; ++i)
		{
			if (markers[i].getVisible())
				result.push(markers[i]);
		}
		//result = (result || []).concat($super());
		return result;
	}
});

Kekule.Render.Renderer2DFactory.register(Kekule.ChemWidget.MetaShapeUiMarker , Kekule.ChemWidget.MetaShapeUiMarker2DRenderer);
Kekule.Render.Renderer2DFactory.register(Kekule.ChemWidget.TextUiMarker , Kekule.ChemWidget.TextUiMarker2DRenderer);
Kekule.Render.Renderer2DFactory.register(Kekule.ChemWidget.UiMarkerCollection, Kekule.ChemWidget.UiMarkerCollection2DRenderer);

/**
 * Render to draw markers of a {@link Kekule.ChemWidget.UIElementCollection}
 * @class
 * @augments Kekule.Render.Base2DRenderer
 * @deprecate
 */
Kekule.ChemWidget.UiMarkersRenderer = Class.create(Kekule.Render.Base2DRenderer,
/** @lends Kekule.ChemWidget.UiMarkersRenderer# */
{
	/** @private */
	CLASS_NAME: 'Kekule.ChemWidget.UiMarkersRenderer',
	/** @private */
	DRAW_ELEM_FIELD: '__$drawElem__',
	/* @constructs */
	initialize: function($super, obj, renderBridge)
	{
		$super(obj, renderBridge);
		this._drawGroup = null;
	},
	/** @private */
	initProperties: function()
	{
		//this.defineProp('dummy', {'dataType': DataType.OBJECT, 'serializable': false});
	},
	/** @private */
	getObjDrawElem: function(context, obj)
	{
		return this.getExtraProp2(context, obj, this.DRAW_ELEM_FIELD);
	},
	/** @private */
	setObjDrawElem: function(context, obj, value)
	{
		this.setExtraProp2(context, obj, this.DRAW_ELEM_FIELD, value);
	},
	/** @private */
	doEstimateRenderBox: function(context, baseCoord, options, allowCoordBorrow)
	{
		return null;  // usually do not need render box information
	},
	/** @private */
	doDraw: function($super, context, baseCoord, options)
	{
		$super(context, baseCoord, options);
		//this._drawParams = {'baseCoord': baseCoord, 'options': options};
		// some params (such as baseCoord) are useless here
		var collection = this.getChemObj();
		var group = this.createDrawGroup(context);
		this.setObjDrawElem(context, collection, group);
		if (collection.getMarkerCount() > 0)
		{
			this.doDrawMarkers(context, group, collection.getMarkers(), options);
		}
		return group;
	},

	/** @private */
	doUpdate1: function($super, context, updatedObjDetails, updateType)
	{
		if (this.canModifyGraphic(context))
		{
			var r = false;
			var T = Kekule.Render.ObjectUpdateType;
			switch (updateType)
			{
				case T.ADD:
					r = this.doAddNew(context, updatedObjDetails);
					break;
				case T.MODIFY:
					r = this.doModify(context, updatedObjDetails);
					break;
				case T.REMOVE:
					var objs = this._extractObjsOfUpdateObjDetails(updatedObjDetails);
					r = this.doRemove(context, objs);
					break;
				default:  // clear
					return $super(context, updatedObjDetails, updateType);
			}
			return r;
		}
		else
			return $super(context, updatedObjs, updateType);
	},
	/** @private */
	doAddNew: function(/*$super,*/ context, updatedObjDetails)
	{
		if (this.canRemoveElem())
			return this.doModify(context, updatedObjDetails);
		else
			return false;
			//return $super(context, updatedObj);
	},
	/** @private */
	doModify: function(/*$super,*/ context, updatedObjDetails)
	{
		if (this.canRemoveElem())
		{
			// find old corresponding element to updatedObj and remove it
			/*
			var elem = this.getObjDrawElem(updatedObj);
			if (elem)
				this.doRemoveElem(context, elem);
			*/
			var objs = this._extractObjsOfUpdateObjDetails(updatedObjDetails);
			this.remove(context, objs);
			// then update new one
			var chemObj = this.getChemObj();
			var group = this.getObjDrawElem(chemObj);
			var params = this.getDrawParams();
			if (objs.indexOf(chemObj) >= 0) // update whole
			{
				this.draw(context, params.baseCoord, params.options);
			}
			else
			{
				this.doDrawMarkers(context, group, objs, params.options);
			}
		}
		else
			return false;
			//return $super(context, chemObj, updatedObj);
	},
	/** @private */
	doRemove: function(/*$super,*/ context, removedObj)
	{
		if (this.canRemoveElem())
		{
			// find old corresponding element to updatedObj and remove it
			var elem = this.getObjDrawElem(removedObj);
			if (elem)
			{
				var group = this.getObjDrawElem(this.getChemObj());
				this.doRemoveFromGroup(elem, group);
				this.doRemoveElem(context, elem);
				this.setObjDrawElem(removedObj, null);
			}
		}
		else
			return false;
			//return $super(context, removedObj);
	},
	/** @private */
	doDrawMarkers: function(context, group, markers, options)
	{
		for (var i = 0, l = markers.length; i < l; ++i)
		{
			var marker = markers[i];
			if (marker.getVisible())
				this.doDrawMarker(context, group, marker, options);
		}
	},
	/** @private */
	doDrawMarker: function(context, group, marker, options)
	{
		var shapeInfo = marker.getShapeInfo();
		if (!shapeInfo)
			return null;

		var ops = Object.create(options);
		ops = Object.extend(marker.getDrawStyles() || {});

		// set stroke & fill color and so on
		if (ops.color)
		{
			if (!ops.strokeColor)
				ops.strokeColor = ops.color;
			if (!ops.fillColor)
				ops.fillColor = ops.color;
		}

		var result;
		// TODO: now only handles meta shape markers
		if (Kekule.ArrayUtils.isArray(shapeInfo))  // composite shapes
		{
			if (shapeInfo.length > 1)  // more than one shape, need draw group
			{
				result = this.createDrawGroup(context);
				for (var i = 0, l = shapeInfo.length; i < l; ++i)
				{
					var shape = shapeInfo[i];
					this.doDrawShape(context, result, shape, ops);
				}
			}
			else if (shapeInfo.length === 1)  // only one shape, no need of group
			{
				result = this.doDrawShape(context, null, shapeInfo[0], ops);
			}
			else  // no actual shapes
				return null;
		}
		else
			result = this.doDrawShape(context, null, shapeInfo, ops);
		if (result)
		{
			this.setObjDrawElem(context, marker, result);
			if (group)
				this.addToDrawGroup(result, group);
		}
		return result;
	},

	/**
	 * Draw a single shape of marker.
	 * @param context
	 * @param markerGroup
	 * @param shape
	 * @param options
	 * @private
	 */
	doDrawShape: function(context, markerGroup, shape, options)
	{
		var T = Kekule.Render.MetaShapeType;
		var result;

		if (Kekule.Render.MetaShapeUtils.isCompositeShape(shape))  // complex shape
		{
			result = this.createDrawGroup(context);
			for (var i = 0, l = shape.length; i < l; ++i)
			{
				var childResult = this.doDrawShape(context, result, shape[i], options);
			}
		}
		else  // simple shape
		{
			var coords = shape.coords;
			//console.log('do draw shape', shape.shapeType, options, coords);
			switch (shape.shapeType)
			{
				// TODO: Point and circle currently does not support stroke dash
				case T.POINT:
					result = this.drawCircle(context, coords[0], 1, options); break;
				case T.CIRCLE:
					result = this.drawCircle(context, coords[0], shape.radius, options); break;
				case T.LINE:
				{
					var ops = options;
					if (shape.width)
					{
						ops = Object.create(options);
						ops.strokeWidth = shape.width;
					}
					result = this.drawLine(context, coords[0], coords[1], ops); break;
				}
				case T.RECT:
				{
					/*
					 if (!strokeDash)
					 result = this.doDrawRect(context, coords[0], coords[1], strokeWidth, strokeColor, fillColor, opacity);
					 else
					 {
					 result = this.doCreateGroup(context);
					 var coords = [coords[0], {x: coords[0].x, y: coords[1].y}, coords[1], {x: coords[1].x, y: coords[0].y}, coords[0]];
					 var args = [];
					 for (var i = 0, l = coords.length; i < l; ++i)
					 {
					 var sMethod = (i === 0)? 'M': 'L';
					 args.push(sMethod);
					 var coordArray = [coords[i].x, coords[i].y];
					 args.push(coordArray);
					 }
					 var path = Kekule.Render.DrawPathUtils.makePath.apply(this, args);
					 result = this.doDrawPath(context, path, strokeWidth, strokeColor, strokeDash, fillColor, opacity);
					 }
					 */
					result = this.drawRect(context, coords[0], coords[1], options);
					break;
				}
				case T.POLYGON: case T.POLYLINE:
				{
					var args = [];
					for (var i = 0, l = coords.length; i < l; ++i)
					{
						var sMethod = (i === 0)? 'M': 'L';
						args.push(sMethod);
						var coordArray = [coords[i].x, coords[i].y];
						args.push(coordArray);
					}
					// close
					if (shape.shapeType === T.POLYGON)
					{
						args.push('L');
						coordArray = [coords[0].x, coords[0].y];
						args.push(coordArray);
					}
					var path = Kekule.Render.DrawPathUtils.makePath.apply(this, args);
					result = this.drawPath(context, path, options);
					break;
				}
			}
		}
		if (markerGroup)
			this.addToDrawGroup(result, markerGroup);
		return result;
	}
});
Kekule.ClassDefineUtils.addExtraTwoTupleObjMapSupport(Kekule.ChemWidget.UiMarkersRenderer);

// register renderers
//Kekule.Render.Renderer2DFactory.register(Kekule.ChemWidget.UiMarkerCollection, Kekule.ChemWidget.UiMarkersRenderer);

})();