kekule
Version:
Open source JavaScript toolkit for chemoinformatics
1,473 lines (1,403 loc) • 146 kB
JavaScript
/**
* @fileoverview
* Related types and classes of chem viewer.
* Viewer is a widget to show chem objects on HTML page.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /utils/kekule.utils.js
* requires /xbrowsers/kekule.x.js
* requires /core/kekule.common.js
* requires /widgets/kekule.widget.base.js
* requires /widgets/kekule.widget.menus.js
* requires /widgets/kekule.widget.dialogs.js
* requires /widgets/kekule.widget.helpers.js
* requires /widgets/chem/kekule.chemWidget.base.js
* requires /widgets/chem/kekule.chemWidget.chemObjDisplayers.js
* requires /widgets/chem/uiMarker/kekule.chemWidget.uiMarkers.js
* requires /widgets/operation/kekule.actions.js
* requires /widgets/commonCtrls/kekule.widget.buttons.js
* requires /widgets/commonCtrls/kekule.widget.containers.js
*
* requires /localization/kekule.localize.widget.js
*/
(function(){
"use strict";
var PS = Class.PropertyScope;
var AU = Kekule.ArrayUtils;
var ZU = Kekule.ZoomUtils;
var BNS = Kekule.ChemWidget.ComponentWidgetNames;
var CW = Kekule.ChemWidget;
var EM = Kekule.Widget.EvokeMode;
/** @ignore */
Kekule.globalOptions.add('chemWidget.viewer', {
toolButtons: [
//BNS.loadFile,
BNS.loadData,
BNS.saveData,
//BNS.clearObjs,
BNS.molDisplayType,
BNS.molHideHydrogens,
//BNS.molAutoGenerateCoords,
BNS.zoomIn, BNS.zoomOut,
BNS.rotateX, BNS.rotateY, BNS.rotateZ,
BNS.rotateLeft, BNS.rotateRight,
BNS.reset,
BNS.copy,
BNS.openEditor
],
menuItems: [
BNS.loadData,
BNS.saveData,
Kekule.Widget.MenuItem.SEPARATOR_TEXT,
BNS.molDisplayType,
BNS.molHideHydrogens,
BNS.zoomIn, BNS.zoomOut,
{
'text': Kekule.$L('ChemWidgetTexts.CAPTION_ROTATE'),
'hint': Kekule.$L('ChemWidgetTexts.HINT_ROTATE'),
'children': [
BNS.rotateLeft, BNS.rotateRight,
BNS.rotateX, BNS.rotateY, BNS.rotateZ
]
},
BNS.reset,
Kekule.Widget.MenuItem.SEPARATOR_TEXT,
BNS.copy,
BNS.openEditor,
BNS.config
],
'toolbar': {
'evokeModes': [EM.EVOKEE_CLICK, EM.EVOKEE_MOUSE_ENTER, EM.EVOKEE_TOUCH],
'revokeModes': [EM.EVOKEE_MOUSE_LEAVE, EM.EVOKER_TIMEOUT],
'pos': Kekule.Widget.Position.AUTO,
'marginHorizontal': 10,
'marginVertical': 10
},
'editor': {
'modal': true,
'restrainEditorWithCurrObj': true,
'shareEditorInstance': true
},
'enableToolbar': false,
'enableDirectInteraction': true,
'enableTouchInteraction': false,
'enableGestureInteraction': false,
'enabledDirectInteractionModes': {
'move': true,
'rotate': true,
'zoom': true
},
'showCaption': false,
'useNormalBackground': false,
'enableCustomCssProperties': true,
'restraintRotation3DEdgeRatio': 0.18,
'enableRestraintRotation3D': true
});
/** @ignore */
Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, {
VIEWER: 'K-Chem-Viewer',
VIEWER2D: 'K-Chem-Viewer2D',
VIEWER3D: 'K-Chem-Viewer3D',
VIEWER_CLIENT: 'K-Chem-Viewer-Client',
VIEWER_CAPTION: 'K-Chem-Viewer-Caption',
VIEWER_EMBEDDED_TOOLBAR: 'K-Chem-Viewer-Embedded-Toolbar',
VIEWER_UICONTEXT_PARENT: 'K-Chem-Viewer-UiContext-Parent',
VIEWER_MENU_BUTTON: 'K-Chem-Viewer-Menu-Button',
VIEWER_EDITOR_FULLCLIENT: 'K-Chem-Viewer-Editor-FullClient',
// predefined actions
ACTION_ROTATE_LEFT: 'K-Chem-RotateLeft',
ACTION_ROTATE_RIGHT: 'K-Chem-RotateRight',
ACTION_ROTATE_X: 'K-Chem-RotateX',
ACTION_ROTATE_Y: 'K-Chem-RotateY',
ACTION_ROTATE_Z: 'K-Chem-RotateZ',
ACTION_VIEWER_EDIT: 'K-Chem-Viewer-Edit'
});
var CNS = Kekule.Widget.HtmlClassNames;
var CCNS = Kekule.ChemWidget.HtmlClassNames;
/**
* Enumeration of some common used UI markers groups.
* @enum
*/
Kekule.ChemWidget.ViewerUiMarkerGroup = {
/** Ui markers for hot tracking objects. */
HOTTRACK: 'hotTrack',
/** Ui markers for selecting objects. */
SELECT: 'select'
};
/**
* An universal viewer widget for chem objects (especially molecules).
* @class
* @augments Kekule.ChemWidget.ChemObjDisplayer
*
* @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} viewerConfigs
*
* @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 {Object} renderConfigs Configuration for rendering.
* // This property should be an instance of {@link Kekule.Render.Render2DConfigs} or {@link Kekule.Render.Render3DConfigs}
* //@property {Hash} drawOptions Options to draw object.
* //@property {Float} zoom Zoom ratio to draw chem object. Note this setting will overwrite drawOptions.zoom.
* //@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 {String} caption Caption of viewer.
* @property {Bool} showCaption Whether show caption below or above viewer.
* @property {Int} captionPos Value from {@link Kekule.Widget.Position}, now only TOP and BOTTOM are usable.
* @property {Bool} autoCaption Set caption automatically by chemObj info.
*
* //@property {Bool} liveUpdate Whether viewer repaint itself automatically when containing chem object changed.
*
* @property {Bool} enableHotKey Whether hot key is allowed.
* @property {Bool} enableDirectInteraction Whether interact without tool button is allowed (e.g., zoom/rotate by mouse).
* @property {Bool} enableTouchInteraction Whether touch interaction is allowed. Note if enableDirectInteraction is false, touch interaction will also be disabled.
* @property {Bool} enableRestraintRotation3D Set to true to rotate only on one axis of X/Y/Z when the starting point is near edge of viewer.
* @property {Float} restraintRotation3DEdgeRatio
* @property {Bool} enableEdit Whether a edit button is shown in toolbar to edit object in viewer. Works only in 2D mode.
* @property {Bool} modalEdit Whether opens a modal dialog when editting object in viewer.
* @property {Bool} enableEditFromVoid Whether editor can be launched even if viewer is empty.
* @property {Hash} editorProperties Hash object to set properties of popup editor.
* @property {Bool} restrainEditorWithCurrObj If true, the editor popuped can only edit current object in viewer (and add new
* objects is disabled). If false, the editor can do everything like a normal composer, viewer will load objects in composer
* after editting (and will not keep the original object in viewer).
* @property {Bool} shareEditorInstance If true, all viewers in one document will shares one editor.
* This setting may reduce the cost of creating many composer widgets.
*
* @property {Array} toolButtons buttons in interaction tool bar. This is a array of predefined strings, e.g.: ['zoomIn', 'zoomOut', 'resetZoom', 'molDisplayType', ...]. <br />
* In the array, complex hash can also be used to add custom buttons, e.g.: <br />
* [ <br />
* 'zoomIn', 'zoomOut',<br />
* {'name': 'myCustomButton1', 'widgetClass': 'Kekule.Widget.Button', 'action': actionClass},<br />
* {'name': 'myCustomButton2', 'htmlClass': 'MyClass' 'caption': 'My Button', 'hint': 'My Hint', '#execute': function(){ ... }},<br />
* ]<br />
* most hash fields are similar to the param of {@link Kekule.Widget.Utils.createFromHash}.<br />
* If this property is not set, default buttons will be used.
* @property {Bool} enableToolbar Whether show tool bar in viewer.
* @property {Int} toolbarPos Value from {@link Kekule.Widget.Position}, position of toolbar in viewer.
* For example, set this property to TOP will make toolbar shows in the center below the top edge of viewer,
* TOP_RIGHT will make the toolbar shows at the top right corner. Default value is BOTTOM_RIGHT.
* Set this property to AUTO, viewer will set toolbar position (including margin) automatically.
* @property {Int} toolbarMarginHorizontal Horizontal margin of toolbar to viewer edge, in px.
* Negative value means toolbar outside viewer.
* @property {Int} toolbarMarginVertical Vertical margin of toolbar to viewer edge, in px.
* Negative value means toolbar outside viewer.
* //@property {Array} toolbarShowEvents Events to cause the display of toolbar. If set to null, the toolbar will always be visible.
* @property {Array} toolbarEvokeModes Interaction modes to show the toolbar. Array item values should from {@link Kekule.Widget.EvokeMode}.
* Set enableToolbar to true and include {@link Kekule.Widget.EvokeMode.ALWAYS} will always show the toolbar.
* @property {Array} toolbarRevokeModes Interaction modes to hide the toolbar. Array item values should from {@link Kekule.Widget.EvokeMode}.
* @property {Int} toolbarRevokeTimeout Toolbar should be hidden after how many milliseconds after shown.
* Only available when {@link Kekule.Widget.EvokeMode.EVOKEE_TIMEOUT} or {@link Kekule.Widget.EvokeMode.EVOKER_TIMEOUT} in toolbarRevokeModes.
*
* @property {Array} allowedMolDisplayTypes Molecule types can be changed in tool bar.
*/
/**
* Invoked when the chem object (or null) in viewer has been edited by the popup editor.
* event param of it has one fields: {obj: Object}
* @name Kekule.ChemWidget.Viewer#editingDone
* @event
*/
/**
* Invoked when the pointer is hot tracking on objects in viewer.
* event param of it has fields: {objects: Array}.
* @name Kekule.ChemWidget.Viewer#hotTrackOnObjects
* @event
*/
/**
* Invoked when objects are selected in view.
* event param of it has fields: {objects: Array}.
* @name Kekule.ChemWidget.Viewer#selectionChange
* @event
*/
Kekule.ChemWidget.Viewer = Class.create(Kekule.ChemWidget.ChemObjDisplayer,
/** @lends Kekule.ChemWidget.Viewer# */
{
/** @private */
CLASS_NAME: 'Kekule.ChemWidget.Viewer',
/** @private */
BINDABLE_TAG_NAMES: ['div', 'span', 'img'],
/** @private */
DEF_BGCOLOR_2D: null,
/** @private */
DEF_BGCOLOR_3D: '#000000',
/** @private */
DEF_TOOLBAR_EVOKE_MODES: [/*EM.ALWAYS,*/ EM.EVOKEE_CLICK, EM.EVOKEE_MOUSE_ENTER, EM.EVOKEE_TOUCH],
/** @private */
DEF_TOOLBAR_REVOKE_MODES: [/*EM.ALWAYS,*/ /*EM.EVOKEE_CLICK,*/ EM.EVOKEE_MOUSE_LEAVE, EM.EVOKER_TIMEOUT],
/** @private */
OBSERVING_GESTURES: ['pinch', 'pinchstart', 'pinchmove', 'pinchend', 'pinchcancel', 'pinchin', 'pinchout'],
/** @construct */
initialize: function(/*$super, */parentOrElementOrDocument, chemObj, renderType, viewerConfigs)
{
//this._errorReportElem = null; // use internally
this.setPropStoreFieldValue('renderType', renderType || Kekule.Render.RendererType.R2D); // must set this value first
this.setPropStoreFieldValue('useCornerDecoration', true);
/*
this.setPropStoreFieldValue('enableToolbar', false);
this.setPropStoreFieldValue('toolbarEvokeModes', this.DEF_TOOLBAR_EVOKE_MODES);
this.setPropStoreFieldValue('toolbarRevokeModes', this.DEF_TOOLBAR_REVOKE_MODES);
this.setPropStoreFieldValue('enableDirectInteraction', true);
this.setPropStoreFieldValue('toolbarPos', Kekule.Widget.Position.AUTO);
this.setPropStoreFieldValue('toolbarMarginHorizontal', 10);
this.setPropStoreFieldValue('toolbarMarginVertical', 10);
this.setPropStoreFieldValue('showCaption', false);
*/
var oneOf = Kekule.oneOf;
var options = Kekule.globalOptions.get('chemWidget.viewer') || {};
this.setPropStoreFieldValue('enableToolbar', oneOf(options.enableToolbar));
this.setPropStoreFieldValue('enableDirectInteraction', oneOf(options.enableDirectInteraction, true));
this.setPropStoreFieldValue('showCaption', oneOf(options.showCaption, false));
options = options.toolbar || {};
this.setPropStoreFieldValue('toolbarEvokeModes', oneOf(options.evokeModes, this.DEF_TOOLBAR_EVOKE_MODES));
this.setPropStoreFieldValue('toolbarRevokeModes', oneOf(options.revokeModes, this.DEF_TOOLBAR_REVOKE_MODES));
this.setPropStoreFieldValue('toolbarPos', oneOf(options.pos, Kekule.Widget.Position.AUTO));
this.setPropStoreFieldValue('toolbarMarginHorizontal', oneOf(options.marginHorizontal,10));
this.setPropStoreFieldValue('toolbarMarginVertical', oneOf(options.marginVertical, 10));
this.tryApplySuper('initialize', [parentOrElementOrDocument, chemObj, renderType /*, viewerConfigs*/]) /* $super(parentOrElementOrDocument, chemObj, renderType, viewerConfigs) */;
this.setPropStoreFieldValue('viewerConfigs', viewerConfigs || this.createDefaultConfigs());
this.beginUpdate();
try
{
this.setPadding(this.getRenderConfigs().getLengthConfigs().getActualLength('autofitContextPadding'));
/*
if (chemObj)
{
this.setChemObj(chemObj);
}
*/
this._isContinuousRepainting = false; // flag, use internally
//this._lastRotate3DMatrix = null; // store the last 3D rotation information
var RT = Kekule.Render.RendererType;
var color2D = (this.getRenderType() === RT.R2D) ? (this.getBackgroundColor() || this.DEF_BGCOLOR_2D) : this.DEF_BGCOLOR_2D;
var color3D = (this.getRenderType() === RT.R3D) ? (this.getBackgroundColor() || this.DEF_BGCOLOR_3D) : this.DEF_BGCOLOR_3D;
this.setBackgroundColorOfType(color2D, RT.R2D);
this.setBackgroundColorOfType(color3D, RT.R3D);
this.useCornerDecorationChanged();
this.doResize(); // adjust caption and drawParent size
}
finally
{
this.endUpdate();
}
var gOptions = Kekule.globalOptions.get('chemWidget.viewer') || {};
if (Kekule.ObjUtils.isUnset(this.getEnableGesture()))
this.setEnableGesture(oneOf(gOptions.enableGestureInteraction, false)); // the hammer event reactor need to be installed after element is bind
this.addIaController('default', new Kekule.ChemWidget.ViewerBasicInteractionController(this), true);
},
/** @private */
doFinalize: function(/*$super*/)
{
var markers = this.getUiMarkers();
if (markers)
markers.finalize();
//this.getPainter().finalize();
var toolBar = this.getToolbar();
this.tryApplySuper('doFinalize') /* $super() */;
if (toolBar)
toolBar.finalize();
if (this._composerDialog)
this._composerDialog.finalize();
if (this._composerPanel)
this._composerPanel.finalize();
},
/** @private */
initProperties: function()
{
/*
this.defineProp('chemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false,
'setter': function(value)
{
this.setPropStoreFieldValue('chemObj', value);
this.chemObjChanged(value);
}
});
this.defineProp('chemObjLoaded', {'dataType': DataType.BOOL, 'serializable': false, 'setter': null,
'getter': function() { return this.getChemObj() && this.getPropStoreFieldValue('chemObjLoaded'); }
});
this.defineProp('renderType', {'dataType': DataType.INT, 'serializable': false, 'setter': null});
this.defineProp('renderConfigs', {'dataType': DataType.OBJECT, 'serializable': false});
this.defineProp('drawOptions', {'dataType': DataType.HASH, 'serializable': false,
'getter': function()
{
var result = this.getPropStoreFieldValue('drawOptions');
if (!result)
{
result = {};
this.setPropStoreFieldValue('drawOptions', result);
}
return result;
}
});
*/
//this.defineProp('zoom', {'dataType': DataType.FLOAT, 'serializable': false});
this.defineProp('viewerConfigs', {'dataType': 'Kekule.ChemWidget.ChemObjDisplayerConfigs', 'serializable': false,
'getter': function() { return this.getDisplayerConfigs(); },
'setter': function(value) { return this.setDisplayerConfigs(value); }
});
//this.defineProp('enableUiContext', {'dataType': DataType.BOOL});
this.defineProp('uiDrawBridge', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null,
'getter': function()
{
if (!this.getEnableUiContext())
return null;
var result = this.getPropStoreFieldValue('uiDrawBridge');
if (!result && !this.__$uiDrawBridgeInitialized$__)
{
this.__$uiDrawBridgeInitialized$__ = true;
result = this.createUiDrawBridge();
this.setPropStoreFieldValue('uiDrawBridge', result);
}
return result;
}
});
this.defineProp('uiContext', {'dataType': DataType.OBJECT, 'serializable': false,
'getter': function()
{
if (!this.getEnableUiContext())
return null;
var result = this.getPropStoreFieldValue('uiContext');
if (!result)
{
var bridge = this.getUiDrawBridge();
if (bridge)
{
var elem = this.getUiContextParentElem();
if (!elem)
return null;
else
{
var dim = Kekule.HtmlElementUtils.getElemScrollDimension(elem);
//var dim = Kekule.HtmlElementUtils.getElemClientDimension(elem);
result = bridge.createContext(elem, dim.width, dim.height);
this.setPropStoreFieldValue('uiContext', result);
}
}
}
return result;
}
});
this.defineProp('uiPainter', {'dataType': 'Kekule.Render.ChemObjPainter', 'serializable': false, 'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('uiPainter');
if (!result)
{
// ui painter will always in 2D mode
var markers = this.getUiMarkers();
result = new Kekule.Render.ChemObjPainter(Kekule.Render.RendererType.R2D, markers, this.getUiDrawBridge());
result.setCanModifyTargetObj(true);
this.setPropStoreFieldValue('uiPainter', result);
return result;
}
return result;
}
});
this.defineProp('uiRenderer', {'dataType': 'Kekule.Render.AbstractRenderer', 'serializable': false, 'setter': null,
'getter': function()
{
var p = this.getUiPainter();
if (p)
{
var r = p.getRenderer();
if (!r)
p.prepareRenderer();
return p.getRenderer() || null;
}
else
return null;
}
});
// private ui marks properties
this.defineProp('uiMarkers', {'dataType': 'Kekule.ChemWidget.UiMarkerCollection', 'serializable': false, 'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('uiMarkers');
if (!result)
{
result = new Kekule.ChemWidget.UiMarkerCollection();
this.setPropStoreFieldValue('uiMarkers', result);
}
return result;
}
});
this.defineProp('allowedMolDisplayTypes', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
'setter': function(value)
{
this.setPropStoreFieldValue('allowedMolDisplayTypes', value);
//this.updateToolbar();
this.updateUiComps();
}
});
this.defineProp('retainAspect', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function()
{
var op = this.getDrawOptions() || {};
return op.retainAspect;
},
'setter': function(value)
{
var op = this.getDrawOptions() || {};
op.retainAspect = !!value;
return this.setDrawOptions(op);
}
});
this.defineProp('enableRestraintRotation3D', {'dataType': DataType.BOOL});
this.defineProp('restraintRotation3DEdgeRatio', {'dataType': DataType.FLOAT});
//this.defineProp('liveUpdate', {'dataType': DataType.BOOL});
this.defineProp('enableHotKey', {'dataType': DataType.FLOAT});
this.defineProp('enableEdit', {'dataType': DataType.BOOL,
'getter': function()
{
// TODO: now only allows 2D editing
return this.getPropStoreFieldValue('enableEdit') && (this.getCoordMode() !== Kekule.CoordMode.COORD3D);
}
});
this.defineProp('shareEditorInstance', {'dataType': DataType.BOOL});
this.defineProp('enableEditFromVoid', {'dataType': DataType.BOOL});
this.defineProp('restrainEditorWithCurrObj', {'dataType': DataType.BOOL});
this.defineProp('modalEdit', {'dataType': DataType.BOOL});
this.defineProp('editorProperties', {'dataType': DataType.HASH});
this.defineProp('toolButtons', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
'getter': function()
{
var result = this.getPropStoreFieldValue('toolButtons');
/*
if (!result) // create default one
{
result = this.getDefaultToolBarButtons();
this.setPropStoreFieldValue('toolButtons', result);
}
*/
return result;
},
'setter': function(value)
{
this.setPropStoreFieldValue('toolButtons', value);
this.updateToolbar();
}
});
this.defineProp('menuItems', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
'setter': function(value)
{
this.setPropStoreFieldValue('menuItems', value);
this.updateMenu();
}
});
/*
// private
this.defineProp('toolButtonNameMapping', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE,
'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('toolButtonNameMapping');
if (!result) // create default one
{
result = this.createDefaultToolButtonNameMapping();
this.setPropStoreFieldValue('toolButtonNameMapping', result);
}
return result;
}
});
*/
// private
this.defineProp('menu', {'dataType': 'Kekule.Widget.Menu', 'serializable': false, 'scope': PS.PRIVATE,
'setter': function(value)
{
var old = this.getMenu();
if (value !== old)
{
if (old)
{
old.finalize();
old = null;
}
this.setPropStoreFieldValue('menu', value);
}
}
});
this.defineProp('toolbar', {'dataType': 'Kekule.Widget.ButtonGroup', 'serializable': false, 'scope': PS.PRIVATE,
'setter': function(value)
{
var old = this.getToolbar();
var evokeHelper = this.getToolbarEvokeHelper();
if (value !== old)
{
if (old)
{
old.finalize();
var helper = this.getToolbarEvokeHelper();
if (helper)
helper.finalize();
old = null;
}
if (evokeHelper)
evokeHelper.finalize();
this.setPropStoreFieldValue('toolbar', value);
// hide the new toolbar and wait for the evoke helper to display it
//value.setDisplayed(false);
if (value)
{
this.setPropStoreFieldValue('toolbarEvokeHelper',
new Kekule.Widget.DynamicEvokeHelper(this, value, this.getToolbarEvokeModes(), this.getToolbarRevokeModes()));
}
}
}
});
this.defineProp('enableToolbar', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('enableToolbar', value);
this.updateToolbar();
}
});
this.defineProp('toolbarPos', {'dataType': DataType.INT, 'enumSource': Kekule.Widget.Position,
'setter': function(value)
{
this.setPropStoreFieldValue('toolbarPos', value);
this.adjustToolbarPos();
}
});
this.defineProp('toolbarMarginVertical', {'dataType': DataType.INT,
'setter': function(value)
{
this.setPropStoreFieldValue('toolbarMarginVertical', value);
this.adjustToolbarPos();
}
});
this.defineProp('toolbarMarginHorizontal', {'dataType': DataType.INT,
'setter': function(value)
{
this.setPropStoreFieldValue('toolbarMarginHorizontal', value);
this.adjustToolbarPos();
}
});
/*
this.defineProp('toolbarShowEvents', {'dataType': DataType.ARRAY});
this.defineProp('toolbarAlwaysShow', {'dataType': DataType.BOOL, 'serializable': false,
'getter': function() { return !!this.getToolbarShowEvents(); },
'setter': null
});
*/
this.defineProp('toolbarEvokeHelper', {'dataType': 'Kekule.Widget.DynamicEvokeHelper',
'serializable': false, 'setter': null, 'scope': PS.PRIVATE}); // private
this.defineProp('toolbarEvokeModes', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
'setter': function(value)
{
this.setPropStoreFieldValue('toolbarEvokeModes', value || []);
if (this.getToolbarEvokeHelper())
this.getToolbarEvokeHelper().setEvokeModes(value || []);
}
});
this.defineProp('toolbarRevokeModes', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
'setter': function(value)
{
this.setPropStoreFieldValue('toolbarRevokeModes', value || []);
if (this.getToolbarEvokeHelper())
this.getToolbarEvokeHelper().setRevokeModes(value || []);
}
});
this.defineProp('toolbarRevokeTimeout', {'dataType': DataType.INT,
'setter': function(value)
{
this.setPropStoreFieldValue('toolbarRevokeTimeout', value);
if (this.getToolbarEvokeHelper())
this.getToolbarEvokeHelper().setTimeout(value);
}
});
this.defineProp('toolbarParentElem', {'dataType': DataType.OBJECT, 'serializable': false,
'setter': function(value)
{
if (this.getToolbarParentElem() !== value)
{
this.setPropStoreFieldValue('toolbarParentElem', value);
this.updateToolbar();
}
}
});
this.defineProp('caption', {'dataType': DataType.STRING,
'setter': function(value)
{
this.setPropStoreFieldValue('caption', value);
Kekule.DomUtils.setElementText(this.getCaptionElem(), value || '');
this.captionChanged();
}
});
this.defineProp('showCaption', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('showCaption', value);
this.captionChanged();
}
});
this.defineProp('captionPos', {'dataType': DataType.INT, 'enumSource': Kekule.Widget.Position,
'setter': function(value)
{
this.setPropStoreFieldValue('captionPos', value);
this.captionChanged();
}
});
this.defineProp('autoCaption', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('autoCaption', value);
if (value)
this.autoDetectCaption();
}
});
this.defineProp('captionElem', {'dataType': DataType.OBJECT, 'scope': PS.PRIVATE,
'setter': null,
'getter': function(doNotAutoCreate)
{
var result = this.getPropStoreFieldValue('captionElem');
if (!result && !doNotAutoCreate) // create new
{
result = this.doCreateCaptionElem();
this.setPropStoreFieldValue('captionElem', result);
}
return result;
}
});
this.defineProp('actions', {'dataType': 'Kekule.ActionList', 'serializable': false, 'scope': PS.PUBLIC,
'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('actions');
if (!result)
{
result = new Kekule.ActionList();
this.setPropStoreFieldValue('actions', result);
}
return result;
}
});
this.defineProp('actionMap', {'dataType': 'Kekule.MapEx', 'serializable': false, 'scope': PS.PRIVATE,
'setter': null,
'getter': function()
{
var result = this.getPropStoreFieldValue('actionMap');
if (!result)
{
result = new Kekule.MapEx(true);
this.setPropStoreFieldValue('actionMap', result);
}
return result;
}
});
this.defineProp('enableDirectInteraction', {'dataType': DataType.BOOL});
this.defineProp('enableTouchInteraction', {'dataType': DataType.BOOL,
'setter': function(value)
{
this.setPropStoreFieldValue('enableTouchInteraction', !!value);
this.setTouchAction(value? 'none': null);
}
});
this.defineProp('enableGesture', {'dataType': DataType.BOOL,
'setter': function(value)
{
var bValue = !!value;
if (this.getEnableGesture() !== bValue)
{
this.setPropStoreFieldValue('enableGesture', bValue);
if (bValue)
{
this.startObservingGestureEvents(this.OBSERVING_GESTURES);
}
else
{
this.stopObservingGestureEvents(this.OBSERVING_GESTURES);
}
}
}
});
this.defineProp('enabledDirectInteractionModes', {'dataType': DataType.HASH});
this.defineProp('hotTrackedObjects', {
'dataType': DataType.ARRAY,
'serializable': false,
'getter': function()
{
return this.getPropStoreFieldValue('hotTrackedObjects') || [];
},
'setter': function(value)
{
//this.setPropStoreFieldValue('hotTrackedObjects', AU.toArray(value));
this.changeHotTrackedObjects(value && AU.toArray(value), true);
}
});
this.defineProp('selectedObjects', {
'dataType': DataType.ARRAY,
'serializable': false,
'getter': function()
{
return this.getPropStoreFieldValue('selectedObjects') || [];
},
'setter': function(value)
{
this.changeSelectedObjects(value && AU.toArray(value), true);
}
});
},
/** @ignore */
initPropValues: function(/*$super*/)
{
// debug
/*
this.setEnableEdit(true);
*/
this.setStyleMode(Kekule.Widget.StyleMode.INHERITED); // embedded in document
/*
this.setUseNormalBackground(false);
//this.setInheritedRenderColor(true);
this.setEnableCustomCssProperties(true);
this.setModalEdit(true);
this.setRestrainEditorWithCurrObj(true);
this.setRestraintRotation3DEdgeRatio(0.18);
this.setEnableRestraintRotation3D(true);
this.setShareEditorInstance(true);
this.setEnableTouchInteraction(!true);
*/
var oneOf = Kekule.oneOf;
var options = Kekule.globalOptions.get('chemWidget.viewer') || {};
this.setUseNormalBackground(oneOf(options.useNormalBackground, false));
this.setEnableCustomCssProperties(oneOf(options.enableCustomCssProperties, true));
this.setRestraintRotation3DEdgeRatio(oneOf(options.restraintRotation3DEdgeRatio, 0.18));
this.setEnableRestraintRotation3D(oneOf(options.enableRestraintRotation3D, true));
this.setEnableTouchInteraction(oneOf(options.enableTouchInteraction, false));
this.setEnabledDirectInteractionModes(oneOf(options.enabledDirectInteractionModes, {
'move': true,
'rotate': true,
'zoom': true
}));
options = options.editor || {};
this.setModalEdit(oneOf(options.modal, true));
this.setRestrainEditorWithCurrObj(oneOf(options.restrainEditorWithCurrObj, true));
this.setShareEditorInstance(oneOf(options.shareEditorInstance, true));
},
/** @ignore */
createDefaultConfigs: function()
{
return new Kekule.ChemWidget.ViewerConfigs();
},
/** @ignore */
canUsePlaceHolderOnElem: function(elem)
{
// When using a img element with src image, it may contains the figure of chem object
var imgSrc = elem.getAttribute('src');
return (elem.tagName.toLowerCase() === 'img') && (!!imgSrc);
},
/** @ignore */
doObjectChange: function(/*$super, */modifiedPropNames)
{
this.tryApplySuper('doObjectChange', [modifiedPropNames]) /* $super(modifiedPropNames) */;
this.updateActions();
},
/** @ignore */
doSetElement: function(/*$super, */element)
{
var elem = element;
if (elem)
{
var tagName = elem.tagName.toLowerCase();
if (tagName === 'img') // is an image element, need to use span to replace it
{
elem = Kekule.DomUtils.replaceTagName(elem, 'span');
//this.setElement(elem);
//console.log('replace img to span');
}
}
return this.tryApplySuper('doSetElement', [elem]) /* $super(elem) */;
},
/** @ignore */
doUnbindElement: function(/*$super, */element)
{
// unbind old element, the context parent element should be set to null
if (this._drawContextParentElem && this._drawContextParentElem.parentNode)
{
this._drawContextParentElem.parentNode.removeChild(this._drawContextParentElem);
this._drawContextParentElem = null;
}
return this.tryApplySuper('doUnbindElement', [element]) /* $super(element) */;
},
/** @ignore */
elementBound: function(element)
{
this.setObserveElemResize(true);
},
/** @ignore */
doCreateRootElement: function(doc)
{
var result = doc.createElement('div');
return result;
},
/** @ignore */
doCreateSubElements: function(doc, docFragment)
{
// client element
var clientElem = doc.createElement('div');
clientElem.className = CCNS.VIEWER_CLIENT;
this._clientElement = clientElem;
docFragment.appendChild(clientElem);
return [clientElem];
},
/** @ignore */
doGetWidgetClassName: function(/*$super*/)
{
var result = this.tryApplySuper('doGetWidgetClassName') /* $super() */ + ' ' + CCNS.VIEWER;
try // may raise exception when called with class prototype (required by placeholder related methods)
{
var renderType = this.getRenderType();
var additional = this._getRenderTypeSpecifiedHtmlClassName(renderType);
result += ' ' + additional;
}
catch(e)
{
}
return result;
},
/** @private */
_getRenderTypeSpecifiedHtmlClassName: function(renderType)
{
return (renderType === Kekule.Render.RendererType.R3D)?
CCNS.VIEWER3D: CCNS.VIEWER2D;
},
/** @ignore */
getClientElement: function()
{
return this._clientElement;
},
/** @ignore */
getResizerElement: function()
{
//return this.getDrawContextParentElem();
return this.getElement();
},
/** @ignore */
doResize: function(/*$super*/)
{
//$super();
this.adjustDrawParentDim();
this.adjustToolbarPos();
this.tryApplySuper('doResize') /* $super() */;
},
/** @ignore */
doWidgetShowStateChanged: function(isShown)
{
if (isShown)
{
//console.log('update toolbar');
//this.updateToolbar();
this.updateActions();
}
},
/** @ignore */
refitDrawContext: function(/*$super, */doNotRepaint)
{
// resize context, means client size changed, so toolbar should also be adjusted.
this.tryApplySuper('refitDrawContext', [doNotRepaint]) /* $super(doNotRepaint) */;
this.adjustToolbarPos();
},
/** @ignore */
changeContextDimension: function(newDimension)
{
if (this.getEnableUiContext())
{
if (this.getUiDrawBridge() && this.getUiContext())
{
this.doChangeContextDimension(this.getUiContext(), this.getUiDrawBridge(), newDimension, true);
}
}
return this.tryApplySuper('changeContextDimension', [newDimension]);
},
/** @ignore */
getAllowRenderTypeChange: function()
{
return true;
},
/** @ignore */
resetRenderType: function(/*$super, */oldType, newType)
{
this.tryApplySuper('resetRenderType', [oldType, newType]) /* $super(oldType, newType) */;
// classname
var oldHtmlClassName = this._getRenderTypeSpecifiedHtmlClassName(oldType);
var newHtmlClassName = this._getRenderTypeSpecifiedHtmlClassName(newType);
this.removeClassName(oldHtmlClassName);
this.addClassName(newHtmlClassName);
// toolbar
//this.updateToolbar();
this.updateUiComps();
},
/** @ignore */
doLoad: function(chemObj)
{
// clear UI markers when loading a new object
/*
this.setVisibleOfUiMarkerGroup(Kekule.ChemWidget.ViewerUiMarkerGroup.SELECT, false, false);
this.setVisibleOfUiMarkerGroup(Kekule.ChemWidget.ViewerUiMarkerGroup.HOTTRACK, false, false);
*/
this.beginUpdateUiMarkers();
try
{
this.clearHotTrackedItems();
this.clearSelectedItems();
this.clearUiMarkers();
}
finally
{
this.endUpdateUiMarkers();
}
this.tryApplySuper('doLoad', [chemObj]);
},
/** @private */
doLoadEnd: function(chemObj)
{
this.updateActions();
this.autoDetectCaption();
},
/** @ignore */
_repaintCore: function(overrideOptions)
{
//console.log('do repaint');
this.tryApplySuper('_repaintCore', [overrideOptions]);
},
/** @ignore */
chemObjRendered: function(chemObj, renderOptions)
{
var result = this.tryApplySuper('chemObjRendered', [chemObj, renderOptions]);
this.updateUiMarkers(true);
//this.repaintUiMarker();
return result;
},
/** @private */
doSetUseCornerDecoration: function(/*$super, */value)
{
this.tryApplySuper('doSetUseCornerDecoration', [value]) /* $super(value) */;
this.useCornerDecorationChanged();
},
/** @private */
useCornerDecorationChanged: function()
{
var elem = this.getDrawContextParentElem(); // do not auto create element
if (elem)
{
var v = this.getUseCornerDecoration();
if (v)
Kekule.HtmlElementUtils.addClass(elem, CNS.CORNER_ALL);
else
Kekule.HtmlElementUtils.removeClass(elem, CNS.CORNER_ALL);
}
},
/** @ignore */
getBoundInfosAtCoord: function(screenCoord, filterFunc, boundInflation)
{
var boundInfos = this.tryApplySuper('getBoundInfosAtCoord', [screenCoord, filterFunc, boundInflation]);
var enableTrackNearest = this.getViewerConfigs().getInteractionConfigs().getEnableTrackOnNearest();
if (boundInfos && boundInfos.length && enableTrackNearest) // sort result by distance to screenCoord
{
var distanceMap = new Kekule.MapEx();
try
{
var SU = Kekule.Render.MetaShapeUtils;
for (var i = boundInfos.length - 1; i >= 0; --i)
{
var info = boundInfos[i];
var shapeInfo = info.boundInfo;
var currDistance = SU.getDistance(screenCoord, shapeInfo);
distanceMap.set(info, currDistance);
}
// sort by z-index, the smaller index on bottom
boundInfos.sort(function (b1, b2) {
var result = - (b1.boundInfo.shapeType - b2.boundInfo.shapeType);
if (result === 0)
result = - (distanceMap.get(b1) - distanceMap.get(b2));
return result;
});
}
finally
{
distanceMap.finalize();
}
}
//console.log('boundInfos', screenCoord, boundInfos);
return boundInfos;
},
/** @ignore */
/*
getTopmostBoundInfoAtCoord: function(screenCoord, excludeObjs, boundInflation)
{
var enableTrackNearest = this.getViewerConfigs().getInteractionConfigs().getEnableTrackOnNearest();
if (!enableTrackNearest)
return this.tryApplySuper('getTopmostBoundInfoAtCoord', [screenCoord, excludeObjs, boundInflation]);
// else, track on nearest
var SU = Kekule.Render.MetaShapeUtils;
var boundInfos = this.getBoundInfosAtCoord(screenCoord, null, boundInflation);
//var filteredBoundInfos = [];
var result, lastShapeInfo, lastDistance;
var setResult = function(boundInfo, shapeInfo, distance)
{
result = boundInfo;
lastShapeInfo = shapeInfo || boundInfo.boundInfo;
if (Kekule.ObjUtils.notUnset(distance))
lastDistance = distance;
else
lastDistance = SU.getDistance(screenCoord, lastShapeInfo);
};
for (var i = boundInfos.length - 1; i >= 0; --i)
{
var info = boundInfos[i];
if (excludeObjs && (excludeObjs.indexOf(info.obj) >= 0))
continue;
if (!result)
setResult(info);
else
{
var shapeInfo = info.boundInfo;
if (shapeInfo.shapeType < lastShapeInfo.shapeType)
setResult(info, shapeInfo);
else if (shapeInfo.shapeType === lastShapeInfo.shapeType)
{
var currDistance = SU.getDistance(screenCoord, shapeInfo);
if (currDistance < lastDistance)
{
//console.log('distanceCompare', currDistance, lastDistance);
setResult(info, shapeInfo, currDistance);
}
}
}
}
return result;
},
*/
/**
* Returns whether the UI marker context is enabled in viewer.
* Descendants or extensions may override this method.
* @returns {Bool}
*/
getEnableUiContext: function()
{
return true;
},
/**
* Returns parent element to create UI context inside viewer.
* @private
*/
getUiContextParentElem: function(disableAutoCreate)
{
if (!this.getEnableUiContext())
return null;
var result = this._uiContextParentElem;
if (!result && !disableAutoCreate) // create new
{
result = this.getDocument().createElement('div'); // IMPORTANT: span may cause dimension calc problem of context
this._uiContextParentElem = result;
Kekule.HtmlElementUtils.addClass(result, CNS.DYN_CREATED);
Kekule.HtmlElementUtils.addClass(result, CCNS.VIEWER_UICONTEXT_PARENT);
// IMPORTANT: force to fullfill the parent, otherwise draw context dimension calculation may have problem
result.style.width = '100%';
result.style.height = '100%';
// insert after draw context parent elment
var drawContextParentElem = this.getDrawContextParentElem();
var root = drawContextParentElem? drawContextParentElem.parentNode: this.getElement();
var refSibling = drawContextParentElem && drawContextParentElem.nextSibling;
if (refSibling)
root.insertBefore(result, refSibling);
else
root.appendChild(result);
}
return result;
},
/** @private */
createUiDrawBridge: function()
{
// UI marker will always be in 2D
var result = Kekule.Render.DrawBridge2DMananger.getPreferredBridgeInstance();
if (!result) // can not find suitable draw bridge
{
//Kekule.error(Kekule.$L('ErrorMsg.DRAW_BRIDGE_NOT_SUPPORTED'));
var errorMsg = Kekule.Render.DrawBridge2DMananger.getUnavailableMessage() || Kekule.error(Kekule.$L('ErrorMsg.DRAW_BRIDGE_NOT_SUPPORTED'));
if (errorMsg)
this.reportException(errorMsg, Kekule.ExceptionLevel.NOT_FATAL_ERROR);
}
return result;
},
/** @private */
adjustDrawParentDim: function()
{
var drawParentElem = this.getDrawContextParentElem();
var parentElem = drawParentElem.parentNode;
var captionElem = this.getCaptionElem(true); // do not auto create
var dimParent = Kekule.HtmlElementUtils.getElemClientDimension(parentElem);
//var t, h;
// drawParentElem is now position: absolute
if (captionElem && this.captionIsShown() && captionElem.parentNode === parentElem)
{
var dimCaption = Kekule.HtmlElementUtils.getElemClientDimension(captionElem);
var h = dimCaption.height || 0;
//console.log('here');
if (this.getCaptionPos() & Kekule.Widget.Position.TOP)
{
drawParentElem.style.top = h + 'px';
drawParentElem.style.bottom = '0px';
}
else
{
drawParentElem.style.top = '0px';
drawParentElem.style.bottom = h + 'px';
}
Kekule.StyleUtils.removeStyleProperty(drawParentElem.style, 'height');
/*
h = Math.max(dimParent.height - dimCaption.height, 0); // avoid value < 0
t = (this.getCaptionPos() & Kekule.Widget.Position.TOP)? dimCaption.height: 0;
drawParentElem.style.top = t + 'px';
drawParentElem.style.height = h + 'px';
*/
}
else
{
/*
t = 0;
h = dimParent.height;
*/
/*
// restore 100% height setting
Kekule.StyleUtils.removeStyleProperty(drawParentElem.style, 'top');
//Kekule.StyleUtils.removeStyleProperty(drawParentElem.style, 'height');
//drawParentElem.style.height = dimParent.height + 'px'; // explicit set height, or the height may not be updated in some mobile browsers
drawParentElem.style.height = '100%'; // some mobile browser has wrong height of parentElem, so here we set it to 100%
*/
Kekule.StyleUtils.removeStyleProperty(drawParentElem.style, 'top');
Kekule.StyleUtils.removeStyleProperty(drawParentElem.style, 'bottom');
drawParentElem.style.height = '100%'; // some mobile browser has wrong height of parentElem, so here we set it to 100%
}
//this.refitDrawContext();
},
/** @private */
getInteractionReceiverElem: function()
{
//return this.getDrawContextParentElem();
return this.getClientElement();
},
/** @ignore */
setDrawDimension: function(/*$super, */width, height)
{
var newHeight = height;
if (this.captionIsShown()) // height need add the height of caption
{
var dimCaption = Kekule.HtmlElementUtils.getElemClientDimension(this.getCaptionElem());
newHeight += dimCaption.height || 0;
}
this.tryApplySuper('setDrawDimension', [width, newHeight]) /* $super(width, newHeight) */;
},
/// Methods about caption: currently not used ///////////
/* @private */
doCreateCaptionElem: function()
{
var result = this.getDocument().createElement('span');
result.className = CNS.DYN_CREATED + ' ' + ' ' + CNS.SELECTABLE + ' ' + CCNS.VIEWER_CAPTION;
this.getElement().appendChild(result);
return result;
},
/**
* Called when caption or showCaption or captionPos property changes.
* @private
*/
captionChanged: function()
{
if (this.captionIsShown())
{
var elem = this.getCaptionElem();
var style = elem.style;
var pos = this.getCaptionPos();
if (pos & Kekule.Widget.Position.TOP)
{
style.top = 0;
style.bottom = 'auto';
}
else
{
style.bottom = 0;
style.top = 'auto';
}
style.display = 'block';
}
else // caption need to be hidden
{
var elem = this.getCaptionElem(true); // do not auto create
if (elem)
elem.style.display = 'none';
}
//this.adjustDrawParentDim();
this.doResize();
},
/**
* Returns whether the caption is actually displayed.
*/
captionIsShown: function()
{
return this.getCaption() && this.getShowCaption();
},
/*
* Called when caption or showCaption property has been changed.
* @private
*/
/*
captionChanged: function()
{
var displayCaption = this.getShowCaption() && this.getCaption();
var elem = this.getCaptionElem();
Kekule.DomUtils.setElementText(elem, this.getCaption());
elem.style.display = displayCaption? 'inherit': 'none';
},
*/
//////////////////// methods about UI markers ///////////////////////////////
/**
* Notify that currently is modifing UI markers and the editor need not to repaint them.
* @private
*/
beginUpdateUiMarkers: function()
{
if (Kekule.ObjUtils.isUnset(this._uiMarkerUpdateFlag))
this._uiMarkerUpdateFlag = 0;
--this._uiMarkerUpdateFlag;
this.beginUpdate(); // some times the context should also be repainted to reflect the select/hot track markers
},
/**
* Call this method to indicate the UI marker update process is over and should be immediately updated.
* @private
*/
endUpdateUiMarkers: function()
{
this.endUpdate();
++this._uiMarkerUpdateFlag;
if (this._uiMarkerUpdateFlag > 0)
this._uiMarkerUpdateFlag = 0;
if (!this.isUpdatingUiMarkers())
this.repaintUiMarker();
},
/**
* Check if the editor is under continuous UI marker update.
* @returns {Bool}
* @private
*/
isUpdatingUiMarkers: function()
{
return (this._uiMarkerUpdateFlag < 0);
},
/** @private */
_getUiMarkerOfName: function(markerName, groups, creationMethod)
{
var result = this.getUiMarkers().getMarkerOfName(markerName);
if (!result && creationMethod) // auto create one
{
result = creationMethod.apply(this);
if (result)
{
result.setName(markerName);
result.setGroups(groups);
}
}
return result;
},
/** @private */
_getDefaultHotTrackUiMarker: function(autoCreate)
{
var creationMethod;
if (autoCreate)
creationMethod = this.createShapeBasedMarker;
var result = this._getUiMarkerOfName('hotTrackMarker', [Kekule.ChemWidget.ViewerUiMarkerGroup.HOTTRACK], creationMethod);
return result;
},
/** @private */
_getDefaultSelectionUiMarker: function(autoCreate)
{
var creationMethod;
if (autoCreate)
creationMethod = this.createShapeBasedMarker;
var result = this._getUiMarkerOfName('selectionMarker', [Kekule.ChemWidget.ViewerUiMarkerGroup.SELECT], creationMethod);
return result;
},
/**
* Called when transform has been made to objects and UI markers need to be modified according to it.
* The UI markers will also be repainted.
* @private
*/
recalcUiMarkers: function()
{
if (this.getUiDrawBridge())
{
this.beginUpdateUiMarkers();
try
{
var marker;
// hot track
var hotTrackedObjs = this.getHotTrackedObjects();
//console.log('recalcUiMarkers ui', hotTrackedObjs);
if (hotTrackedObjs && hotTrackedObjs.length)
{
var bounds = this._calcBoundsOfObjects(hotTrackedObjs);
var drawStyles = this.getViewerConfigs().getUiMarkerConfigs().getHotTrackMarkerStyles() || {};
marker = this._getDefaultHotTrackUiMarker(true); // auto create
this.modifyShapeBasedMarker(marker, bounds, drawStyles, false);
this.showUiMarker(marker);
}
else // hide hot track marker
{
marker = this._getDefaultHotTrackUiMarker(false); // not need to auto create
if (marker)
this.hideUiMarker(marker, false);
}
// selected
var selectedObjs = this.getSelectedObjects();
if (selectedObjs && selectedObjs.length)
{
var bounds = this._calcBoundsOfObjects(selectedObjs);
var drawStyles = this.getViewerConfigs().getUiMarkerConfigs().getSelectionMarkerStyles() || {};
marker = this._getDefaultSelectionUiMarker(true); // auto create
this.modifyShapeBasedMarker(marker, bounds, drawStyles, false);
this.showUiMarker(marker);
}
else // hide hot track marker
{
marker = this._getDefaultSelectionUiMarker(false); // not need to auto create
if (marker)
this.hideUiMarker(marker, false);
}
}
finally
{
this.endUpdateUiMarkers();
}
}
},
/** @private */
repaintUiMarker: function()
{
//console.log('call repaintUiMarker', this._uiMarkerUpdateFlag, this.getHotTrackedObjects());
if (this.isUpdatingUiMarkers())
return;
if (this.getUiDrawBridge() && this.getUiContext())
{
this.clearUiContext();
var drawParams = this.calcDrawParams();
this.getUiPainter().draw(this.getUiContext(), drawParams.baseCoord, drawParams.drawOptions);
}
},
/**
* Update the properties of existed UI markers according the current chem object state.
* @private
*/
updateUiMarkers: function(doRepaint)
{
this.doUpdateUiMarkers();
if (doRepaint)
this.repaintUiMarker();
},
/** @private */
doUpdateUiMarkers: function()
{
this.recalcUiMarkers();
},
/**
* Create a new marker based on shapeIn