UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

398 lines (386 loc) 11.8 kB
/** * @fileoverview * This file contains a helper class to record all bound infos of basic object * during the rendering of chem object. * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /core/kekule.common.js * requires /utils/kekule.utils.js * requires /render/kekule.render.utils.js * requires /render/kekule.render.base.js */ /** * A helper class to record all bound info during the renderering of a renderer or painter. * * NOTE: current implementation of this class use may hidden details of TwoTupleMapEx. * Should changed in the future. * @class * @augments ObjectEx * @param {Object} rendererOrPainter Renderer or painter that do the actual render job. * * @property {Array} boundInfos Bound infos recorded. * @property {Object} targetContext If this property is set, only bound info on this context will be recorded. */ /** * Invoked when a basic object (node, connector, glyph...) is drawn, updated or removed by renderer. * event param of it has fields: {obj, parentObj, boundInfo, updateType} * where boundInfo provides the bound box information of this object on context. It has the following fields: * { * context: drawing context object * obj: drawn object * parentObj: parent of drawn object * boundInfo: a hash containing info of bound, including fields: * { * shapeType: value from {@link Kekule.Render.MetaShapeType} or {@link Kekule.Render.Meta3DShapeType}. * coords: [Array of coords] * } * updateType: add, modify or remove * } * boundInfo may also be a array for complex situation (such as multicenter bond): [boundInfo1, boundInfo2, boundInfo3...]. * Note that in removed event, boundInfo may be null. * @name Kekule.Render.BoundInfoRecorder#updateBasicDrawObject * @event */ Kekule.Render.BoundInfoRecorder = Class.create(ObjectEx, /** @lends Kekule.Render.BoundInfoRecorder# */ { /** @private */ CLASS_NAME: 'Kekule.Render.BoundInfoRecorder', /** @constructs */ initialize: function(/*$super, */rendererOrPainter) { this.tryApplySuper('initialize') /* $super() */; this.setPropStoreFieldValue('boundInfos', new Kekule.TwoTupleMapEx(true)); this._renderer = rendererOrPainter; // used internally if (rendererOrPainter && rendererOrPainter.setBoundInfoRecorder) { rendererOrPainter.setBoundInfoRecorder(this); } //console.log('boundInfoCreated', rendererOrPainter); this.installEventListener(rendererOrPainter); }, /** @ignore */ finalize: function(/*$super*/) { if (this._renderer) this.uninstallEventListener(this._renderer); var infos = this.getPropStoreFieldValue('boundInfos'); if (infos) infos.clear(); this.tryApplySuper('finalize') /* $super() */; }, /** @private */ initProperties: function() { this.defineProp('boundInfos', {'dataType': 'Kekule.TwoTupleMapEx', 'serializable': false, 'setter': null}); this.defineProp('targetContext', {'dataType': DataType.OBJECT, 'serializable': false}); }, /** @private */ needRecordOnContext: function(context) { var target = this.getTargetContext(); return (!target) || (target === context); }, /** @private */ installEventListener: function(renderer) { renderer.addEventListener('updateBasicDrawObject', this._reactRendererUpdateBasicDrawObject, this); renderer.addEventListener('clear', this._reactRendererClear, this); }, /** @private */ uninstallEventListener: function(renderer) { renderer.removeEventListener('updateBasicDrawObject', this._reactRendererUpdateBasicDrawObject, this); renderer.removeEventListener('clear', this._reactRendererClear, this); }, /** @private */ _reactRendererUpdateBasicDrawObject: function(e) { var OT = Kekule.Render.ObjectUpdateType; if (this.needRecordOnContext(e.context)) { //console.log('update', this._renderer.getClassName()); var utype = e.updateType; if (utype === OT.ADD) this.add(e.context, e.obj, e.parentObj, e.boundInfo, e.target); else if (utype === OT.MODIFY) this.modify(e.context, e.obj, e.parentObj, e.boundInfo, e.target); else if (utype === OT.REMOVE) { //console.log('object removed', e.obj.getClassName()); this.remove(e.context, e.obj); } else if (utype === OT.CLEAR) this.clear(e.context); //console.log(utype, e.context.canvas.parentNode.className, e.obj.getClassName(), e.parentObj.getClassName(), e.boundInfo); /* if (!e.boundInfo) console.log('no boundInfo', e); */ this.invokeEvent('updateBasicDrawObject', e); } }, /** @private */ _reactRendererClear: function(e) { //console.log('[RECEIVE CLEAR]', this._renderer.getClassName()); if (this.needRecordOnContext(e.context)) //this.clear(e.context); this.clearOnRenderer(e.context, e.target); var infos = this.getAllRecordedInfoOfContext(e.context); //console.log(infos.length, infos); }, /** * Returns recorded info item of obj in context. * @param {Object} context * @param {Object} obj * @returns {Object} */ getInfo: function(context, obj) { return this.getBoundInfos().get(context, obj); }, /** * Returns recorded info items based on object, or based on all children of parent object. * @param {Object} context * @param {Object} objOrParentObj * @returns {Array} */ getBelongedInfos: function(context, objOrParentObj) { var info = this.getInfo(context, objOrParentObj); if (info) return [info]; else // info map to obj not found, search all infos and check parent objects { var result = []; var infos = this.getAllRecordedInfoOfContext(context); for (var i = 0, l = infos.length; i < l; ++i) { var info = infos[i]; var currObj = info.obj; if (currObj === objOrParentObj || (currObj.isChildOf && currObj.isChildOf(objOrParentObj))) { result.push(info); } } return result; } }, /** * Returns recorded bound info of obj in context. * If direct bound not found, this method will return the container box of all bound info of obj's children. * @param {Object} context * @param {Object} obj * @returns {Object} */ getBound: function(context, obj) { var info = this.getInfo(context, obj); var result = info? info.boundInfo: null; if (!result) { var BU = Kekule.BoxUtils; var MU = Kekule.Render.MetaShapeUtils; var infos = this.getBelongedInfos(context, obj); var containerBox = null; for (var i = 0, l = infos.length; i < l; ++i) { var info = infos[i]; var box = MU.getContainerBox(info.boundInfo, 0); containerBox = containerBox? BU.getContainerBox(containerBox, box): box; } if (containerBox) { result = MU.createShapeInfo(Kekule.Render.MetaShapeType.RECT, [{'x': containerBox.x1, 'y': containerBox.y1}, {'x': containerBox.x2, 'y': containerBox.y2}] ); //console.log(containerBox, result, MU.getContainerBox(result, 0)); } } return result; }, /** * Clear recorded info of renderer on context. * @param {Object} context * @param {Object} renderer */ clearOnRenderer: function(context, renderer) { var infos = this.getBoundInfos(); var map = infos.getSecondLevelMap(context); if (map) { var objs = map.getKeys(); var infos = map.getValues(); //console.log('before remove: ', objs.length); //var count = 0; for (var i = objs.length - 1; i >= 0; --i) { var obj = objs[i]; var info = infos[i]; if (info.renderer === renderer) { map.remove(obj); //++count; //console.log('<clear info on renderer>', renderer.getClassName(), obj.getClassName()); } } //console.log('after remove: ', objs.length, count); } }, /** * Clear bound infos in a context. * @param {Object} context */ clear: function(context) { var infos = this.getBoundInfos(); var map = infos.getSecondLevelMap(context); if (map) map.clear(); return this; }, /** * Clear bound infos in all context. */ clearAll: function() { this.getBoundInfos().clear(); // clear all info return this; }, /** * Add an info item to list. * @param {Object} context * @param {Object} obj * @param {Object} parentObj * @param {Object} boundInfo * @param {Object} renderer */ add: function(context, obj, parentObj, boundInfo, renderer) { this.getBoundInfos().set(context, obj, {'obj': obj, 'parentObj': parentObj, 'boundInfo': boundInfo, 'renderer': renderer}); return this; }, /** * Modify bound info in list. * @param {Object} context * @param {Object} obj * @param {Object} parentObj * @param {Object} boundInfo * @param {Object} renderer */ modify: function(context, obj, parentObj, boundInfo, renderer) { var item = this.getBoundInfos().get(context, obj); if (!item) { item = {}; this.getBoundInfos.set(context, obj, item); } //this.getBoundInfos().set(context, obj, boundInfo); var notUnset = Kekule.ObjUtils.notUnset; if (notUnset(parentObj)) item.parentObj = parentObj; if (notUnset(boundInfo)) item.boundInfo = boundInfo; if (notUnset(renderer)) item.renderer = renderer; return this; }, /** * Remove an info item in map. * @param {Object} context * @param {Object} obj * @param {Object} parentObj */ remove: function(context, obj, parentObj) { this.getBoundInfos().remove(context, obj); return this; }, /** * Returns an array that contains bound / obj / parentObj / renderer information in a certain context. * @param {Object} context * @returns {Array} */ getAllRecordedInfoOfContext: function(context) { var infos = this.getBoundInfos(); var map = infos.getSecondLevelMap(context); if (!map) return []; else { // since this is not a weak map, we can get its array properties return map.getValues(); } }, /** * Returns an bound info array in a certain context. * @param {Object} context * @returns {Array} */ getAllBoundInfosOfContext: function(context) { var infos = this.getAllRecordedInfoOfContext(context); var result = []; for (var i = 0, l = infos.length; i < l; ++i) { var boundInfo = infos[i].boundInfo; if (boundInfo) result.push(boundInfo); } return result; }, /** * Returns a minimal box that can contains all drawn elements in context. * @param {Object} context * @returns {Hash} */ getContainerBox: function(context) { var BU = Kekule.BoxUtils; var MU = Kekule.Render.MetaShapeUtils; var result = null; var infos = this.getAllBoundInfosOfContext(context); for (var i = 0, l = infos.length; i < l; ++i) { var box = MU.getContainerBox(infos[i], 0); result = BU.getContainerBox(result, box); } return result; }, /** * Get all intersected bound and related informations on coord. * @param {Object} context * @param {Hash} coord * @param {Hash} refCoord This value is used in 3D mode. Passes null to it in 2D mode. * @param {Int} inflation * @returns {Array} */ getIntersectionInfos: function(context, coord, refCoord, inflation, filterFunc) { var result = []; // TODO: now only handles 2D itersection var infos = this.getAllRecordedInfoOfContext(context); for (var i = 0, l = infos.length; i < l; ++i) { var bound = infos[i].boundInfo; if (filterFunc && !filterFunc(infos[i])) { //console.log('filtered out', infos[i]); continue; } if (bound) { if (Kekule.Render.MetaShapeUtils.isCoordInside(coord, bound, inflation)) result.push(infos[i]); } } return result; } });