Source: widgets/chem/viewer/kekule.chemWidget.viewers.js

  1. /**
  2. * @fileoverview
  3. * Related types and classes of chem viewer.
  4. * Viewer is a widget to show chem objects on HTML page.
  5. * @author Partridge Jiang
  6. */
  7. /*
  8. * requires /lan/classes.js
  9. * requires /utils/kekule.utils.js
  10. * requires /xbrowsers/kekule.x.js
  11. * requires /core/kekule.common.js
  12. * requires /widgets/kekule.widget.base.js
  13. * requires /widgets/kekule.widget.menus.js
  14. * requires /widgets/kekule.widget.dialogs.js
  15. * requires /widgets/kekule.widget.helpers.js
  16. * requires /widgets/chem/kekule.chemWidget.base.js
  17. * requires /widgets/chem/kekule.chemWidget.chemObjDisplayers.js
  18. * requires /widgets/operation/kekule.actions.js
  19. * requires /widgets/commonCtrls/kekule.widget.buttons.js
  20. * requires /widgets/commonCtrls/kekule.widget.containers.js
  21. *
  22. * requires /localization/kekule.localize.widget.js
  23. */
  24. (function(){
  25. "use strict";
  26. var PS = Class.PropertyScope;
  27. var AU = Kekule.ArrayUtils;
  28. var ZU = Kekule.ZoomUtils;
  29. var BNS = Kekule.ChemWidget.ComponentWidgetNames;
  30. var CW = Kekule.ChemWidget;
  31. var EM = Kekule.Widget.EvokeMode;
  32. /** @ignore */
  33. Kekule.globalOptions.add('chemWidget.viewer', {
  34. toolButtons: [
  35. //BNS.loadFile,
  36. BNS.loadData,
  37. BNS.saveData,
  38. //BNS.clearObjs,
  39. BNS.molDisplayType,
  40. BNS.molHideHydrogens,
  41. BNS.zoomIn, BNS.zoomOut,
  42. BNS.rotateX, BNS.rotateY, BNS.rotateZ,
  43. BNS.rotateLeft, BNS.rotateRight,
  44. BNS.reset,
  45. BNS.openEditor
  46. ],
  47. menuItems: [
  48. BNS.loadData,
  49. BNS.saveData,
  50. Kekule.Widget.MenuItem.SEPARATOR_TEXT,
  51. BNS.molDisplayType,
  52. BNS.molHideHydrogens,
  53. BNS.zoomIn, BNS.zoomOut,
  54. {
  55. 'text': Kekule.$L('ChemWidgetTexts.CAPTION_ROTATE'),
  56. 'hint': Kekule.$L('ChemWidgetTexts.HINT_ROTATE'),
  57. 'children': [
  58. BNS.rotateLeft, BNS.rotateRight,
  59. BNS.rotateX, BNS.rotateY, BNS.rotateZ
  60. ]
  61. },
  62. BNS.reset,
  63. Kekule.Widget.MenuItem.SEPARATOR_TEXT,
  64. BNS.openEditor,
  65. BNS.config
  66. ]
  67. });
  68. /** @ignore */
  69. Kekule.ChemWidget.HtmlClassNames = Object.extend(Kekule.ChemWidget.HtmlClassNames, {
  70. VIEWER: 'K-Chem-Viewer',
  71. VIEWER2D: 'K-Chem-Viewer2D',
  72. VIEWER3D: 'K-Chem-Viewer3D',
  73. VIEWER_CAPTION: 'K-Chem-Viewer-Caption',
  74. VIEWER_EMBEDDED_TOOLBAR: 'K-Chem-Viewer-Embedded-Toolbar',
  75. VIEWER_MENU_BUTTON: 'K-Chem-Viewer-Menu-Button',
  76. VIEWER_EDITOR_FULLCLIENT: 'K-Chem-Viewer-Editor-FullClient',
  77. // predefined actions
  78. ACTION_ROTATE_LEFT: 'K-Chem-RotateLeft',
  79. ACTION_ROTATE_RIGHT: 'K-Chem-RotateRight',
  80. ACTION_ROTATE_X: 'K-Chem-RotateX',
  81. ACTION_ROTATE_Y: 'K-Chem-RotateY',
  82. ACTION_ROTATE_Z: 'K-Chem-RotateZ',
  83. ACTION_VIEWER_EDIT: 'K-Chem-Viewer-Edit'
  84. });
  85. var CNS = Kekule.Widget.HtmlClassNames;
  86. var CCNS = Kekule.ChemWidget.HtmlClassNames;
  87. /**
  88. * An universal viewer widget for chem objects (especially molecules).
  89. * @class
  90. * @augments Kekule.ChemWidget.ChemObjDisplayer
  91. *
  92. * @param {Variant} parentOrElementOrDocument
  93. * @param {Kekule.ChemObject} chemObj
  94. * @param {Int} renderType Display in 2D or 3D. Value from {@link Kekule.Render.RendererType}.
  95. * @param {Kekule.ChemWidget.ChemObjDisplayerConfigs} viewerConfigs
  96. *
  97. * @property {Int} renderType Display in 2D or 3D. Value from {@link Kekule.Render.RendererType}. Read only.
  98. * @property {Kekule.ChemObject} chemObj Object to be drawn. Set this property will repaint the client.
  99. * @property {Bool} chemObjLoaded Whether the chemObj is successful loaded and drawn in viewer.
  100. * //@property {Object} renderConfigs Configuration for rendering.
  101. * // This property should be an instance of {@link Kekule.Render.Render2DConfigs} or {@link Kekule.Render.Render3DConfigs}
  102. * //@property {Hash} drawOptions Options to draw object.
  103. * //@property {Float} zoom Zoom ratio to draw chem object. Note this setting will overwrite drawOptions.zoom.
  104. * //@property {Bool} autoSize Whether the widget change its size to fit the dimension of chem object.
  105. * //@property {Int} padding Padding between chem object and edge of widget, in px. Only works when autoSize is true.
  106. *
  107. * @property {String} caption Caption of viewer.
  108. * @property {Bool} showCaption Whether show caption below or above viewer.
  109. * @property {Int} captionPos Value from {@link Kekule.Widget.Position}, now only TOP and BOTTOM are usable.
  110. * @property {Bool} autoCaption Set caption automatically by chemObj info.
  111. *
  112. * //@property {Bool} liveUpdate Whether viewer repaint itself automatically when containing chem object changed.
  113. *
  114. * @property {Bool} enableDirectInteraction Whether interact without tool button is allowed (e.g., zoom/rotate by mouse).
  115. * @property {Bool} enableTouchInteraction Whether touch interaction is allowed. Note if enableDirectInteraction is false, touch interaction will also be disabled.
  116. * @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.
  117. * @property {Float} restraintRotation3DEdgeRatio
  118. * @property {Bool} enableEdit Whether a edit button is shown in toolbar to edit object in viewer. Works only in 2D mode.
  119. * @property {Bool} modalEdit Whether opens a modal dialog when editting object in viewer.
  120. * @property {Bool} enableEditFromVoid Whether editor can be launched even if viewer is empty.
  121. * @property {Hash} editorProperties Hash object to set properties of popup editor.
  122. * @property {Bool} restrainEditorWithCurrObj If true, the editor popuped can only edit current object in viewer (and add new
  123. * objects is disabled). If false, the editor can do everything like a normal composer, viewer will load objects in composer
  124. * after editting (and will not keep the original object in viewer).
  125. * @property {Bool} shareEditorInstance If true, all viewers in one document will shares one editor.
  126. * This setting may reduce the cost of creating many composer widgets.
  127. *
  128. * @property {Array} toolButtons buttons in interaction tool bar. This is a array of predefined strings, e.g.: ['zoomIn', 'zoomOut', 'resetZoom', 'molDisplayType', ...]. <br />
  129. * In the array, complex hash can also be used to add custom buttons, e.g.: <br />
  130. * [ <br />
  131. * 'zoomIn', 'zoomOut',<br />
  132. * {'name': 'myCustomButton1', 'widgetClass': 'Kekule.Widget.Button', 'action': actionClass},<br />
  133. * {'name': 'myCustomButton2', 'htmlClass': 'MyClass' 'caption': 'My Button', 'hint': 'My Hint', '#execute': function(){ ... }},<br />
  134. * ]<br />
  135. * most hash fields are similar to the param of {@link Kekule.Widget.Utils.createFromHash}.<br />
  136. * If this property is not set, default buttons will be used.
  137. * @property {Bool} enableToolbar Whether show tool bar in viewer.
  138. * @property {Int} toolbarPos Value from {@link Kekule.Widget.Position}, position of toolbar in viewer.
  139. * For example, set this property to TOP will make toolbar shows in the center below the top edge of viewer,
  140. * TOP_RIGHT will make the toolbar shows at the top right corner. Default value is BOTTOM_RIGHT.
  141. * Set this property to AUTO, viewer will set toolbar position (including margin) automatically.
  142. * @property {Int} toolbarMarginHorizontal Horizontal margin of toolbar to viewer edge, in px.
  143. * Negative value means toolbar outside viewer.
  144. * @property {Int} toolbarMarginVertical Vertical margin of toolbar to viewer edge, in px.
  145. * Negative value means toolbar outside viewer.
  146. * //@property {Array} toolbarShowEvents Events to cause the display of toolbar. If set to null, the toolbar will always be visible.
  147. * @property {Array} toolbarEvokeModes Interaction modes to show the toolbar. Array item values should from {@link Kekule.Widget.EvokeMode}.
  148. * Set enableToolbar to true and include {@link Kekule.Widget.EvokeMode.ALWAYS} will always show the toolbar.
  149. * @property {Array} toolbarRevokeModes Interaction modes to hide the toolbar. Array item values should from {@link Kekule.Widget.EvokeMode}.
  150. * @property {Int} toolbarRevokeTimeout Toolbar should be hidden after how many milliseconds after shown.
  151. * Only available when {@link Kekule.Widget.EvokeMode.EVOKEE_TIMEOUT} or {@link Kekule.Widget.EvokeMode.EVOKER_TIMEOUT} in toolbarRevokeModes.
  152. *
  153. * @property {Array} allowedMolDisplayTypes Molecule types can be changed in tool bar.
  154. */
  155. Kekule.ChemWidget.Viewer = Class.create(Kekule.ChemWidget.ChemObjDisplayer,
  156. /** @lends Kekule.ChemWidget.Viewer# */
  157. {
  158. /** @private */
  159. CLASS_NAME: 'Kekule.ChemWidget.Viewer',
  160. /** @private */
  161. BINDABLE_TAG_NAMES: ['div', 'span', 'img'],
  162. /** @private */
  163. DEF_BGCOLOR_2D: null,
  164. /** @private */
  165. DEF_BGCOLOR_3D: '#000000',
  166. /** @private */
  167. DEF_TOOLBAR_EVOKE_MODES: [/*EM.ALWAYS,*/ EM.EVOKEE_CLICK, EM.EVOKEE_MOUSE_ENTER, EM.EVOKEE_TOUCH],
  168. /** @private */
  169. DEF_TOOLBAR_REVOKE_MODES: [/*EM.ALWAYS,*/ /*EM.EVOKEE_CLICK,*/ EM.EVOKEE_MOUSE_LEAVE, EM.EVOKER_TIMEOUT],
  170. /** @construct */
  171. initialize: function($super, parentOrElementOrDocument, chemObj, renderType, viewerConfigs)
  172. {
  173. //this._errorReportElem = null; // use internally
  174. this.setPropStoreFieldValue('renderType', renderType || Kekule.Render.RendererType.R2D); // must set this value first
  175. this.setPropStoreFieldValue('enableToolbar', false);
  176. this.setPropStoreFieldValue('toolbarEvokeModes', this.DEF_TOOLBAR_EVOKE_MODES);
  177. this.setPropStoreFieldValue('toolbarRevokeModes', this.DEF_TOOLBAR_REVOKE_MODES);
  178. this.setPropStoreFieldValue('enableDirectInteraction', true);
  179. this.setPropStoreFieldValue('toolbarPos', Kekule.Widget.Position.AUTO);
  180. this.setPropStoreFieldValue('toolbarMarginHorizontal', 10);
  181. this.setPropStoreFieldValue('toolbarMarginVertical', 10);
  182. this.setPropStoreFieldValue('showCaption', false);
  183. this.setPropStoreFieldValue('useCornerDecoration', true);
  184. //this.setUseCornerDecoration(true);
  185. $super(parentOrElementOrDocument, chemObj, renderType, viewerConfigs);
  186. this.setPadding(this.getRenderConfigs().getLengthConfigs().getActualLength('autofitContextPadding'));
  187. /*
  188. if (chemObj)
  189. {
  190. this.setChemObj(chemObj);
  191. }
  192. */
  193. this._isContinuousRepainting = false; // flag, use internally
  194. //this._lastRotate3DMatrix = null; // store the last 3D rotation information
  195. var RT = Kekule.Render.RendererType;
  196. var color2D = (this.getRenderType() === RT.R2D)? (this.getBackgroundColor() || this.DEF_BGCOLOR_2D): this.DEF_BGCOLOR_2D;
  197. var color3D = (this.getRenderType() === RT.R3D)? (this.getBackgroundColor() || this.DEF_BGCOLOR_3D): this.DEF_BGCOLOR_3D;
  198. this.setBackgroundColorOfType(color2D, RT.R2D);
  199. this.setBackgroundColorOfType(color3D, RT.R3D);
  200. this.useCornerDecorationChanged();
  201. this.doResize(); // adjust caption and drawParent size
  202. this.addIaController('default', new Kekule.ChemWidget.ViewerBasicInteractionController(this), true);
  203. },
  204. /** @private */
  205. doFinalize: function($super)
  206. {
  207. //this.getPainter().finalize();
  208. var toolBar = this.getToolbar();
  209. $super();
  210. if (toolBar)
  211. toolBar.finalize();
  212. if (this._composerDialog)
  213. this._composerDialog.finalize();
  214. if (this._composerPanel)
  215. this._composerPanel.finalize();
  216. },
  217. /** @private */
  218. initProperties: function()
  219. {
  220. /*
  221. this.defineProp('chemObj', {'dataType': 'Kekule.ChemObject', 'serializable': false,
  222. 'setter': function(value)
  223. {
  224. this.setPropStoreFieldValue('chemObj', value);
  225. this.chemObjChanged(value);
  226. }
  227. });
  228. this.defineProp('chemObjLoaded', {'dataType': DataType.BOOL, 'serializable': false, 'setter': null,
  229. 'getter': function() { return this.getChemObj() && this.getPropStoreFieldValue('chemObjLoaded'); }
  230. });
  231. this.defineProp('renderType', {'dataType': DataType.INT, 'serializable': false, 'setter': null});
  232. this.defineProp('renderConfigs', {'dataType': DataType.OBJECT, 'serializable': false});
  233. this.defineProp('drawOptions', {'dataType': DataType.HASH, 'serializable': false,
  234. 'getter': function()
  235. {
  236. var result = this.getPropStoreFieldValue('drawOptions');
  237. if (!result)
  238. {
  239. result = {};
  240. this.setPropStoreFieldValue('drawOptions', result);
  241. }
  242. return result;
  243. }
  244. });
  245. */
  246. //this.defineProp('zoom', {'dataType': DataType.FLOAT, 'serializable': false});
  247. this.defineProp('viewerConfigs', {'dataType': 'Kekule.ChemWidget.ChemObjDisplayerConfigs', 'serializable': false,
  248. 'getter': function() { return this.getDisplayerConfigs(); },
  249. 'setter': function(value) { return this.setDisplayerConfigs(value); }
  250. });
  251. this.defineProp('allowedMolDisplayTypes', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
  252. 'setter': function(value)
  253. {
  254. this.setPropStoreFieldValue('allowedMolDisplayTypes', value);
  255. //this.updateToolbar();
  256. this.updateUiComps();
  257. }
  258. });
  259. this.defineProp('enableRestraintRotation3D', {'dataType': DataType.BOOL});
  260. this.defineProp('restraintRotation3DEdgeRatio', {'dataType': DataType.FLOAT});
  261. //this.defineProp('liveUpdate', {'dataType': DataType.BOOL});
  262. this.defineProp('enableEdit', {'dataType': DataType.BOOL,
  263. 'getter': function()
  264. {
  265. // TODO: now only allows 2D editing
  266. return this.getPropStoreFieldValue('enableEdit') && (this.getCoordMode() !== Kekule.CoordMode.COORD3D);
  267. }
  268. });
  269. this.defineProp('shareEditorInstance', {'dataType': DataType.BOOL});
  270. this.defineProp('enableEditFromVoid', {'dataType': DataType.BOOL});
  271. this.defineProp('restrainEditorWithCurrObj', {'dataType': DataType.BOOL});
  272. this.defineProp('modalEdit', {'dataType': DataType.BOOL});
  273. this.defineProp('editorProperties', {'dataType': DataType.HASH});
  274. this.defineProp('toolButtons', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
  275. 'getter': function()
  276. {
  277. var result = this.getPropStoreFieldValue('toolButtons');
  278. /*
  279. if (!result) // create default one
  280. {
  281. result = this.getDefaultToolBarButtons();
  282. this.setPropStoreFieldValue('toolButtons', result);
  283. }
  284. */
  285. return result;
  286. },
  287. 'setter': function(value)
  288. {
  289. this.setPropStoreFieldValue('toolButtons', value);
  290. this.updateToolbar();
  291. }
  292. });
  293. this.defineProp('menuItems', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
  294. 'setter': function(value)
  295. {
  296. this.setPropStoreFieldValue('menuItems', value);
  297. this.updateMenu();
  298. }
  299. });
  300. /*
  301. // private
  302. this.defineProp('toolButtonNameMapping', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE,
  303. 'setter': null,
  304. 'getter': function()
  305. {
  306. var result = this.getPropStoreFieldValue('toolButtonNameMapping');
  307. if (!result) // create default one
  308. {
  309. result = this.createDefaultToolButtonNameMapping();
  310. this.setPropStoreFieldValue('toolButtonNameMapping', result);
  311. }
  312. return result;
  313. }
  314. });
  315. */
  316. // private
  317. this.defineProp('menu', {'dataType': 'Kekule.Widget.Menu', 'serializable': false, 'scope': PS.PRIVATE,
  318. 'setter': function(value)
  319. {
  320. var old = this.getMenu();
  321. if (value !== old)
  322. {
  323. if (old)
  324. {
  325. old.finalize();
  326. old = null;
  327. }
  328. this.setPropStoreFieldValue('menu', value);
  329. }
  330. }
  331. });
  332. this.defineProp('toolbar', {'dataType': 'Kekule.Widget.ButtonGroup', 'serializable': false, 'scope': PS.PRIVATE,
  333. 'setter': function(value)
  334. {
  335. var old = this.getToolbar();
  336. var evokeHelper = this.getToolbarEvokeHelper();
  337. if (value !== old)
  338. {
  339. if (old)
  340. {
  341. old.finalize();
  342. var helper = this.getToolbarEvokeHelper();
  343. if (helper)
  344. helper.finalize();
  345. old = null;
  346. }
  347. if (evokeHelper)
  348. evokeHelper.finalize();
  349. this.setPropStoreFieldValue('toolbar', value);
  350. // hide the new toolbar and wait for the evoke helper to display it
  351. //value.setDisplayed(false);
  352. if (value)
  353. {
  354. this.setPropStoreFieldValue('toolbarEvokeHelper',
  355. new Kekule.Widget.DynamicEvokeHelper(this, value, this.getToolbarEvokeModes(), this.getToolbarRevokeModes()));
  356. }
  357. }
  358. }
  359. });
  360. this.defineProp('enableToolbar', {'dataType': DataType.BOOL,
  361. 'setter': function(value)
  362. {
  363. this.setPropStoreFieldValue('enableToolbar', value);
  364. this.updateToolbar();
  365. }
  366. });
  367. this.defineProp('toolbarPos', {'dataType': DataType.INT, 'enumSource': Kekule.Widget.Position,
  368. 'setter': function(value)
  369. {
  370. this.setPropStoreFieldValue('toolbarPos', value);
  371. this.adjustToolbarPos();
  372. }
  373. });
  374. this.defineProp('toolbarMarginVertical', {'dataType': DataType.INT,
  375. 'setter': function(value)
  376. {
  377. this.setPropStoreFieldValue('toolbarMarginVertical', value);
  378. this.adjustToolbarPos();
  379. }
  380. });
  381. this.defineProp('toolbarMarginHorizontal', {'dataType': DataType.INT,
  382. 'setter': function(value)
  383. {
  384. this.setPropStoreFieldValue('toolbarMarginHorizontal', value);
  385. this.adjustToolbarPos();
  386. }
  387. });
  388. /*
  389. this.defineProp('toolbarShowEvents', {'dataType': DataType.ARRAY});
  390. this.defineProp('toolbarAlwaysShow', {'dataType': DataType.BOOL, 'serializable': false,
  391. 'getter': function() { return !!this.getToolbarShowEvents(); },
  392. 'setter': null
  393. });
  394. */
  395. this.defineProp('toolbarEvokeHelper', {'dataType': 'Kekule.Widget.DynamicEvokeHelper',
  396. 'serializable': false, 'setter': null, 'scope': PS.PRIVATE}); // private
  397. this.defineProp('toolbarEvokeModes', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
  398. 'setter': function(value)
  399. {
  400. this.setPropStoreFieldValue('toolbarEvokeModes', value || []);
  401. if (this.getToolbarEvokeHelper())
  402. this.getToolbarEvokeHelper().setEvokeModes(value || []);
  403. }
  404. });
  405. this.defineProp('toolbarRevokeModes', {'dataType': DataType.ARRAY, 'scope': PS.PUBLIC,
  406. 'setter': function(value)
  407. {
  408. this.setPropStoreFieldValue('toolbarRevokeModes', value || []);
  409. if (this.getToolbarEvokeHelper())
  410. this.getToolbarEvokeHelper().setRevokeModes(value || []);
  411. }
  412. });
  413. this.defineProp('toolbarRevokeTimeout', {'dataType': DataType.INT,
  414. 'setter': function(value)
  415. {
  416. this.setPropStoreFieldValue('toolbarRevokeTimeout', value);
  417. if (this.getToolbarEvokeHelper())
  418. this.getToolbarEvokeHelper().setTimeout(value);
  419. }
  420. });
  421. this.defineProp('toolbarParentElem', {'dataType': DataType.OBJECT, 'serializable': false,
  422. 'setter': function(value)
  423. {
  424. if (this.getToolbarParentElem() !== value)
  425. {
  426. this.setPropStoreFieldValue('toolbarParentElem', value);
  427. this.updateToolbar();
  428. }
  429. }
  430. });
  431. this.defineProp('caption', {'dataType': DataType.STRING,
  432. 'setter': function(value)
  433. {
  434. this.setPropStoreFieldValue('caption', value);
  435. Kekule.DomUtils.setElementText(this.getCaptionElem(), value || '');
  436. this.captionChanged();
  437. }
  438. });
  439. this.defineProp('showCaption', {'dataType': DataType.BOOL,
  440. 'setter': function(value)
  441. {
  442. this.setPropStoreFieldValue('showCaption', value);
  443. this.captionChanged();
  444. }
  445. });
  446. this.defineProp('captionPos', {'dataType': DataType.INT, 'enumSource': Kekule.Widget.Position,
  447. 'setter': function(value)
  448. {
  449. this.setPropStoreFieldValue('captionPos', value);
  450. this.captionChanged();
  451. }
  452. });
  453. this.defineProp('autoCaption', {'dataType': DataType.BOOL,
  454. 'setter': function(value)
  455. {
  456. this.setPropStoreFieldValue('autoCaption', value);
  457. if (value)
  458. this.autoDetectCaption();
  459. }
  460. });
  461. this.defineProp('captionElem', {'dataType': DataType.OBJECT, 'scope': PS.PRIVATE,
  462. 'setter': null,
  463. 'getter': function(doNotAutoCreate)
  464. {
  465. var result = this.getPropStoreFieldValue('captionElem');
  466. if (!result && !doNotAutoCreate) // create new
  467. {
  468. result = this.doCreateCaptionElem();
  469. this.setPropStoreFieldValue('captionElem', result);
  470. }
  471. return result;
  472. }
  473. });
  474. this.defineProp('actions', {'dataType': 'Kekule.ActionList', 'serializable': false, 'scope': PS.PUBLIC,
  475. 'setter': null,
  476. 'getter': function()
  477. {
  478. var result = this.getPropStoreFieldValue('actions');
  479. if (!result)
  480. {
  481. result = new Kekule.ActionList();
  482. this.setPropStoreFieldValue('actions', result);
  483. }
  484. return result;
  485. }
  486. });
  487. this.defineProp('actionMap', {'dataType': 'Kekule.MapEx', 'serializable': false, 'scope': PS.PRIVATE,
  488. 'setter': null,
  489. 'getter': function()
  490. {
  491. var result = this.getPropStoreFieldValue('actionMap');
  492. if (!result)
  493. {
  494. result = new Kekule.MapEx(true);
  495. this.setPropStoreFieldValue('actionMap', result);
  496. }
  497. return result;
  498. }
  499. });
  500. this.defineProp('enableDirectInteraction', {'dataType': DataType.BOOL});
  501. this.defineProp('enableTouchInteraction', {'dataType': DataType.BOOL,
  502. 'setter': function(value)
  503. {
  504. this.setPropStoreFieldValue('enableTouchInteraction', !!value);
  505. this.setTouchAction(value? 'none': null);
  506. }
  507. });
  508. },
  509. /** @ignore */
  510. initPropValues: function($super)
  511. {
  512. // debug
  513. /*
  514. this.setEnableEdit(true);
  515. */
  516. this.setUseNormalBackground(false);
  517. this.setModalEdit(true);
  518. this.setRestrainEditorWithCurrObj(true);
  519. this.setRestraintRotation3DEdgeRatio(0.18);
  520. this.setEnableRestraintRotation3D(true);
  521. this.setShareEditorInstance(true);
  522. this.setEnableTouchInteraction(!true);
  523. },
  524. /** @ignore */
  525. canUsePlaceHolderOnElem: function(elem)
  526. {
  527. // When using a img element with src image, it may contains the figure of chem object
  528. var imgSrc = elem.getAttribute('src');
  529. return (elem.tagName.toLowerCase() === 'img') && (!!imgSrc);
  530. },
  531. /** @ignore */
  532. doObjectChange: function($super, modifiedPropNames)
  533. {
  534. $super(modifiedPropNames);
  535. this.updateActions();
  536. },
  537. /** @ignore */
  538. doSetElement: function($super, element)
  539. {
  540. var elem = element;
  541. if (elem)
  542. {
  543. var tagName = elem.tagName.toLowerCase();
  544. if (tagName === 'img') // is an image element, need to use span to replace it
  545. {
  546. elem = Kekule.DomUtils.replaceTagName(elem, 'span');
  547. //this.setElement(elem);
  548. //console.log('replace img to span');
  549. }
  550. }
  551. return $super(elem);
  552. },
  553. /** @ignore */
  554. doUnbindElement: function($super, element)
  555. {
  556. // unbind old element, the context parent element should be set to null
  557. if (this._drawContextParentElem && this._drawContextParentElem.parentNode)
  558. {
  559. this._drawContextParentElem.parentNode.removeChild(this._drawContextParentElem);
  560. this._drawContextParentElem = null;
  561. }
  562. return $super(element);
  563. },
  564. /** @ignore */
  565. doCreateRootElement: function(doc)
  566. {
  567. var result = doc.createElement('div');
  568. return result;
  569. },
  570. /** @ignore */
  571. doGetWidgetClassName: function($super)
  572. {
  573. var result = $super() + ' ' + CCNS.VIEWER;
  574. try // may raise exception when called with class prototype (required by placeholder related methods)
  575. {
  576. var renderType = this.getRenderType();
  577. var additional = this._getRenderTypeSpecifiedHtmlClassName(renderType);
  578. result += ' ' + additional;
  579. }
  580. catch(e)
  581. {
  582. }
  583. return result;
  584. },
  585. /** @private */
  586. _getRenderTypeSpecifiedHtmlClassName: function(renderType)
  587. {
  588. return (renderType === Kekule.Render.RendererType.R3D)?
  589. CCNS.VIEWER3D: CCNS.VIEWER2D;
  590. },
  591. /** @ignore */
  592. getResizerElement: function()
  593. {
  594. return this.getDrawContextParentElem();
  595. },
  596. /** @ignore */
  597. doResize: function($super)
  598. {
  599. //$super();
  600. this.adjustDrawParentDim();
  601. this.adjustToolbarPos();
  602. $super();
  603. },
  604. /** @ignore */
  605. doWidgetShowStateChanged: function(isShown)
  606. {
  607. if (isShown)
  608. {
  609. //console.log('update toolbar');
  610. //this.updateToolbar();
  611. this.updateActions();
  612. }
  613. },
  614. /** @ignore */
  615. refitDrawContext: function($super, doNotRepaint)
  616. {
  617. // resize context, means client size changed, so toolbar should also be adjusted.
  618. $super(doNotRepaint);
  619. this.adjustToolbarPos();
  620. },
  621. /** @ignore */
  622. getAllowRenderTypeChange: function()
  623. {
  624. return true;
  625. },
  626. /** @ignore */
  627. resetRenderType: function($super, oldType, newType)
  628. {
  629. $super(oldType, newType);
  630. // classname
  631. var oldHtmlClassName = this._getRenderTypeSpecifiedHtmlClassName(oldType);
  632. var newHtmlClassName = this._getRenderTypeSpecifiedHtmlClassName(newType);
  633. this.removeClassName(oldHtmlClassName);
  634. this.addClassName(newHtmlClassName);
  635. // toolbar
  636. //this.updateToolbar();
  637. this.updateUiComps();
  638. },
  639. /** @private */
  640. doLoadEnd: function(chemObj)
  641. {
  642. this.updateActions();
  643. this.autoDetectCaption();
  644. },
  645. /** @private */
  646. doSetUseCornerDecoration: function($super, value)
  647. {
  648. $super(value);
  649. this.useCornerDecorationChanged();
  650. },
  651. /** @private */
  652. useCornerDecorationChanged: function()
  653. {
  654. var elem = this.getDrawContextParentElem(); // do not auto create element
  655. if (elem)
  656. {
  657. var v = this.getUseCornerDecoration();
  658. if (v)
  659. Kekule.HtmlElementUtils.addClass(elem, CNS.CORNER_ALL);
  660. else
  661. Kekule.HtmlElementUtils.removeClass(elem, CNS.CORNER_ALL);
  662. }
  663. },
  664. /** @private */
  665. adjustDrawParentDim: function()
  666. {
  667. var drawParentElem = this.getDrawContextParentElem();
  668. var parentElem = drawParentElem.parentNode;
  669. var captionElem = this.getCaptionElem(true); // do not auto create
  670. var dimParent = Kekule.HtmlElementUtils.getElemClientDimension(parentElem);
  671. var t, h;
  672. if (captionElem && this.captionIsShown() && captionElem.parentNode === parentElem)
  673. {
  674. var dimCaption = Kekule.HtmlElementUtils.getElemClientDimension(captionElem);
  675. h = Math.max(dimParent.height - dimCaption.height, 0); // avoid value < 0
  676. t = (this.getCaptionPos() & Kekule.Widget.Position.TOP)? dimCaption.height: 0;
  677. drawParentElem.style.top = t + 'px';
  678. drawParentElem.style.height = h + 'px';
  679. }
  680. else
  681. {
  682. /*
  683. t = 0;
  684. h = dimParent.height;
  685. */
  686. // restore 100% height setting
  687. Kekule.StyleUtils.removeStyleProperty(drawParentElem.style, 'top');
  688. //Kekule.StyleUtils.removeStyleProperty(drawParentElem.style, 'height');
  689. drawParentElem.style.height = dimParent.height + 'px'; // explicit set height, or the height may not be updated in some mobile browsers
  690. }
  691. //this.refitDrawContext();
  692. },
  693. /** @private */
  694. getInteractionReceiverElem: function()
  695. {
  696. return this.getDrawContextParentElem();
  697. },
  698. /** @ignore */
  699. setDrawDimension: function($super, width, height)
  700. {
  701. var newHeight = height;
  702. if (this.captionIsShown()) // height need add the height of caption
  703. {
  704. var dimCaption = Kekule.HtmlElementUtils.getElemClientDimension(this.getCaptionElem());
  705. newHeight += dimCaption.height || 0;
  706. }
  707. $super(width, newHeight);
  708. },
  709. /// Methods about caption: currently not used ///////////
  710. /* @private */
  711. doCreateCaptionElem: function()
  712. {
  713. var result = this.getDocument().createElement('span');
  714. result.className = CNS.DYN_CREATED + ' ' + ' ' + CNS.SELECTABLE + ' ' + CCNS.VIEWER_CAPTION;
  715. this.getElement().appendChild(result);
  716. return result;
  717. },
  718. /**
  719. * Called when caption or showCaption or captionPos property changes.
  720. * @private
  721. */
  722. captionChanged: function()
  723. {
  724. if (this.captionIsShown())
  725. {
  726. var elem = this.getCaptionElem();
  727. var style = elem.style;
  728. var pos = this.getCaptionPos();
  729. if (pos & Kekule.Widget.Position.TOP)
  730. {
  731. style.top = 0;
  732. style.bottom = 'auto';
  733. }
  734. else
  735. {
  736. style.bottom = 0;
  737. style.top = 'auto';
  738. }
  739. style.display = 'block';
  740. }
  741. else // caption need to be hidden
  742. {
  743. var elem = this.getCaptionElem(true); // do not auto create
  744. if (elem)
  745. elem.style.display = 'none';
  746. }
  747. //this.adjustDrawParentDim();
  748. this.doResize();
  749. },
  750. /**
  751. * Returns whether the caption is actually displayed.
  752. */
  753. captionIsShown: function()
  754. {
  755. return this.getCaption() && this.getShowCaption();
  756. },
  757. /*
  758. * Called when caption or showCaption property has been changed.
  759. * @private
  760. */
  761. /*
  762. captionChanged: function()
  763. {
  764. var displayCaption = this.getShowCaption() && this.getCaption();
  765. var elem = this.getCaptionElem();
  766. Kekule.DomUtils.setElementText(elem, this.getCaption());
  767. elem.style.display = displayCaption? 'inherit': 'none';
  768. },
  769. */
  770. /// Methods about popup editing ////////////////
  771. /**
  772. * Returns whether editor can be lauched in current viewer.
  773. */
  774. getAllowEditing: function()
  775. {
  776. return (this.getCoordMode() !== Kekule.CoordMode.COORD3D) &&
  777. this.getEnableEdit() && (this.getChemObj() || this.getEnableEditFromVoid());
  778. },
  779. /** @private */
  780. getComposerDialog: function()
  781. {
  782. var result;
  783. if (this.getShareEditorInstance())
  784. result = Kekule.ChemWidget.Viewer._composerDialog;
  785. else
  786. result = this._composerDialog;
  787. if (!result)
  788. {
  789. if (Kekule.Editor.ComposerDialog)
  790. {
  791. result = new Kekule.Editor.ComposerDialog(this.getDocument(), Kekule.$L('ChemWidgetTexts.CAPTION_EDIT_OBJ'), //CWT.CAPTION_EDIT_OBJ,
  792. [Kekule.Widget.DialogButtons.OK, Kekule.Widget.DialogButtons.CANCEL]);
  793. }
  794. }
  795. if (this.getShareEditorInstance())
  796. Kekule.ChemWidget.Viewer._composerDialog = result;
  797. else
  798. this._composerDialog = result;
  799. return result;
  800. },
  801. /** @private */
  802. getComposerPanel: function()
  803. {
  804. var result;
  805. if (this.getShareEditorInstance())
  806. result = Kekule.ChemWidget.Viewer._composerPanel;
  807. else
  808. result = this._composerPanel;
  809. if (!result) // create new
  810. {
  811. if (Kekule.Editor.Composer/* && Kekule.Editor.ComposerFrame*/)
  812. {
  813. result = new Kekule.Editor.Composer(this.getDocument());
  814. //result = new Kekule.Editor.ComposerFrame(this.getDocument());
  815. result.addClassName(CCNS.VIEWER_EDITOR_FULLCLIENT);
  816. result.setUseNormalBackground(true);
  817. //result.setAutoAdjustSizeOnPopup(true);
  818. var minDim = Kekule.Editor.Composer.getMinPreferredDimension();
  819. result.setMinDimension(minDim);
  820. //result.setAutoSetMinDimension(true);
  821. result.setEnableDimensionTransform(true);
  822. result.setAutoResizeConstraints({'width': 1, 'height': 1});
  823. // set toolbar buttons, remove config and inspector to save place
  824. var btns = Kekule.globalOptions.chemWidget.composer.commonToolButtons;
  825. btns = AU.exclude(btns, [BNS.cut, BNS.config, BNS.objInspector]);
  826. result.setCommonToolButtons(btns);
  827. // two custom buttons to save or discard edits
  828. var customButtons = [
  829. {
  830. 'text': Kekule.$L('ChemWidgetTexts.CAPTION_EDITOR_DONE'),
  831. 'hint': Kekule.$L('ChemWidgetTexts.HINT_EDITOR_DONE'),
  832. 'htmlClass': 'K-Res-Button-YesOk',
  833. 'showText': true,
  834. '#execute': function() { result._doneEditCallback(); result.hide(); }
  835. },
  836. {
  837. 'text': Kekule.$L('ChemWidgetTexts.CAPTION_EDITOR_CANCEL'),
  838. 'hint': Kekule.$L('ChemWidgetTexts.HINT_EDITOR_CANCEL'),
  839. 'htmlClass': 'K-Res-Button-NoCancel',
  840. 'showText': true,
  841. '#execute': function() { result.hide(); }
  842. }
  843. ];
  844. result._customEndEditButtons = customButtons; // use a special field to store in composer
  845. }
  846. }
  847. if (this.getShareEditorInstance())
  848. Kekule.ChemWidget.Viewer._composerPanel = result;
  849. else
  850. this._composerPanel = result;
  851. result._invokerViewer = this;
  852. result._doneEditCallback = null;
  853. return result;
  854. },
  855. /**
  856. * Open a popup editor to modify displayed object.
  857. * @param {Kekule.Widget.BaseWidget} callerWidget Who invokes edit action, default is the viewer itself.
  858. */
  859. openEditor: function(callerWidget)
  860. {
  861. //if (this.getEnableEdit() && this.getChemObj())
  862. if (this.getAllowEditing())
  863. {
  864. // load object in editor
  865. var chemObj = this.getChemObj();
  866. var editFromVoid = !chemObj;
  867. var editFromEmpty = chemObj && chemObj.isEmpty && chemObj.isEmpty(); // has chem object but obj is empty (e.g., mol with no atom and bond)
  868. var restrainObj = this.getRestrainEditorWithCurrObj();
  869. var clientDim = Kekule.DocumentUtils.getClientDimension(this.getDocument());
  870. //var clientDim = Kekule.DocumentUtils.getClientVisibleBox(this.getDocument());
  871. //console.log(clientDim);
  872. var minComposerDim = Kekule.Editor.Composer.getMinPreferredDimension();
  873. if (clientDim.width <= minComposerDim.width + 50 || clientDim.height < minComposerDim.height + 100)
  874. {
  875. this._openEditComposer(callerWidget, chemObj, restrainObj, editFromVoid, editFromEmpty);
  876. }
  877. else
  878. {
  879. this._openEditComposerDialog(callerWidget, chemObj, restrainObj, editFromVoid, editFromEmpty);
  880. }
  881. }
  882. },
  883. /** @private */
  884. _prepareEditComposer: function(composer, restrainObj, editFromVoid, editFromEmpty)
  885. {
  886. composer.setEnableCreateNewDoc(editFromVoid || !restrainObj);
  887. composer.setEnableLoadNewFile(editFromVoid || !restrainObj);
  888. composer.setAllowCreateNewChild(editFromVoid || !restrainObj);
  889. var editorProperties = this.getEditorProperties();
  890. if (editorProperties)
  891. composer.setPropValues(editorProperties);
  892. //composer.updateAllActions();
  893. //console.log(composer.getEnableLoadNewFile(), editFromVoid);
  894. },
  895. /** @private */
  896. _feedbackEditResult: function(composer, chemObj, editFromVoid)
  897. {
  898. if (!composer.isDirty())
  899. return;
  900. var newObj = composer.getSavingTargetObj();
  901. if (editFromVoid)
  902. {
  903. this.setChemObj(newObj.clone());
  904. }
  905. else
  906. {
  907. if (this.getRestrainEditorWithCurrObj())
  908. {
  909. if (chemObj.getClass() === newObj.getClass()) // same type of object in editor
  910. chemObj.assign(newObj.clone());
  911. else // preserve old object type in viewer
  912. chemObj.assign(cloneObj);
  913. // clear src info data
  914. chemObj.setSrcInfo(null);
  915. //self.repaint();
  916. this.setChemObj(chemObj); // force repaint, as repaint() will not reflect to object changes
  917. }
  918. else // not restrain, load object in composer directy into viewer
  919. {
  920. //console.log(newObj);
  921. this.setChemObj(newObj);
  922. }
  923. }
  924. },
  925. /** @private */
  926. _openEditComposerDialog: function(callerWidget, chemObj, restrainObj, editFromVoid, editFromEmpty)
  927. {
  928. var dialog = this.getComposerDialog();
  929. if (!dialog) // can not invoke composer dialog
  930. {
  931. Kekule.error(Kekule.$L('ErrorMsg.CAN_NOT_CREATE_EDITOR'));
  932. return;
  933. }
  934. var composer = dialog.getComposer();
  935. this._prepareEditComposer(composer, restrainObj, editFromVoid, editFromEmpty);
  936. var cloneObj;
  937. if (!editFromVoid && !editFromEmpty)
  938. {
  939. cloneObj = chemObj.clone(); // edit this cloned one, avoid affect chemObj directly
  940. dialog.setChemObj(cloneObj);
  941. }
  942. else
  943. {
  944. //dialog.setChemObj(null);
  945. dialog.getComposer().newDoc();
  946. }
  947. var self = this;
  948. var callback = function(dialogResult)
  949. {
  950. if (dialogResult === Kekule.Widget.DialogButtons.OK && dialog.getComposer().isDirty()) // feedback result
  951. {
  952. /*
  953. var newObj = dialog.getSavingTargetObj();
  954. if (editFromVoid)
  955. {
  956. self.setChemObj(newObj.clone());
  957. }
  958. else
  959. {
  960. if (self.getRestrainEditorWithCurrObj())
  961. {
  962. if (chemObj.getClass() === newObj.getClass()) // same type of object in editor
  963. chemObj.assign(newObj.clone());
  964. else // preserve old object type in viewer
  965. chemObj.assign(cloneObj);
  966. // clear src info data
  967. chemObj.setSrcInfo(null);
  968. //self.repaint();
  969. self.setChemObj(chemObj); // force repaint, as repaint() will not reflect to object changes
  970. }
  971. else // not restrain, load object in composer directy into viewer
  972. {
  973. //console.log(newObj);
  974. self.setChemObj(newObj);
  975. }
  976. }
  977. */
  978. self._feedbackEditResult(dialog.getComposer(), chemObj, editFromVoid);
  979. }
  980. //dialog.finalize();
  981. };
  982. if (this.getModalEdit())
  983. dialog.openModal(callback, callerWidget || this);
  984. else
  985. dialog.openPopup(callback, callerWidget || this);
  986. },
  987. /** @private */
  988. _openEditComposer: function(callerWidget, chemObj, restrainObj, editFromVoid, editFromEmpty)
  989. {
  990. var composer = this.getComposerPanel();
  991. if (!composer) // can not invoke composer
  992. {
  993. Kekule.error(Kekule.$L('ErrorMsg.CAN_NOT_CREATE_EDITOR'));
  994. return;
  995. }
  996. //var composer = composerFrame.getComposer();
  997. this._prepareEditComposer(composer, restrainObj, editFromVoid, editFromEmpty);
  998. //composer.newDoc();
  999. // ensure save & cancel buttons are in toolbar
  1000. var customButtons = composer._customEndEditButtons;
  1001. var toolbtns = composer.getCommonToolButtons() || [];
  1002. var btnModified = false;
  1003. for (var i = 0, l = customButtons.length; i < l; ++i)
  1004. {
  1005. var btn = customButtons[i];
  1006. if (toolbtns.indexOf(btn) < 0)
  1007. {
  1008. toolbtns.push(btn);
  1009. btnModified = true;
  1010. }
  1011. }
  1012. if (btnModified)
  1013. composer.setCommonToolButtons(toolbtns);
  1014. if (!editFromVoid && !editFromEmpty)
  1015. {
  1016. //composer.updateDimensionTransform();
  1017. var cloneObj = chemObj.clone(); // edit this cloned one, avoid affect chemObj directly
  1018. composer.setChemObj(cloneObj);
  1019. }
  1020. else
  1021. {
  1022. composer.newDoc();
  1023. }
  1024. var self = this;
  1025. composer._doneEditCallback = function(){
  1026. self._feedbackEditResult(composer, chemObj, editFromVoid);
  1027. };
  1028. composer.show(callerWidget, function(){
  1029. //var cloneObj;
  1030. if (!editFromVoid && !editFromEmpty)
  1031. {
  1032. //composer.updateDimensionTransform();
  1033. //cloneObj = chemObj.clone(); // edit this cloned one, avoid affect chemObj directly
  1034. composer.setChemObj(cloneObj); // set chemObj again, or it will not be displayed in some mobile browsers
  1035. }
  1036. else
  1037. {
  1038. //dialog.setChemObj(null);
  1039. //composer.newDoc();
  1040. }
  1041. }, Kekule.Widget.ShowHideType.POPUP);
  1042. },
  1043. /*
  1044. * Returns a new widget to edit object in viewer.
  1045. * @private
  1046. */
  1047. /*
  1048. createEditorWidget: function()
  1049. {
  1050. var result = new Kekule.Editor.Composer(this.getDocument());
  1051. var editor = result.getEditor();
  1052. editor.setEnableCreateNewDoc(false);
  1053. editor.setEnableLoadNewFile(false);
  1054. editor.setAllowCreateNewChild(false);
  1055. editor.addClassName(CNS.DYN_CREATED);
  1056. return result;
  1057. },
  1058. */
  1059. ////////////////////////////////////////////////
  1060. /**
  1061. * Reset viewer to initial state (no zoom, rotation and so on).
  1062. */
  1063. resetView: function()
  1064. {
  1065. return this.resetDisplay();
  1066. },
  1067. /**
  1068. * Returns current 2D rotation angle (in arc).
  1069. * @returns {Float}
  1070. */
  1071. getCurr2DRotationAngle: function()
  1072. {
  1073. return this.getDrawOptions().rotateAngle || 0;
  1074. },
  1075. /**
  1076. * Do a 2D rotation base on delta.
  1077. * @param {Float} delta In arc.
  1078. * @param {Bool} suspendRendering Set this to true if a immediate repaint is not needed.
  1079. */
  1080. rotate2DBy: function(delta, suspendRendering)
  1081. {
  1082. return this.rotate2DTo(this.getCurr2DRotationAngle() + delta, suspendRendering);
  1083. },
  1084. /**
  1085. * Do a 2D rotation to angle.
  1086. * @param {Float} angle In arc.
  1087. * @param {Bool} suspendRendering Set this to true if a immediate repaint is not needed.
  1088. */
  1089. rotate2DTo: function(angle, suspendRendering)
  1090. {
  1091. this.getDrawOptions().rotateAngle = angle;
  1092. //this.drawOptionChanged();
  1093. if (!suspendRendering)
  1094. this.geometryOptionChanged();
  1095. return this;
  1096. },
  1097. /**
  1098. * Returns current 3D rotation info.
  1099. * @returns {Hash} {rotateMatrix, rotateX, rotateY, rotateZ, rotateAngle, rotateAxisVector}
  1100. */
  1101. getCurr3DRotationInfo: function()
  1102. {
  1103. var result = {};
  1104. var fields = ['rotateMatrix', 'rotateX', 'rotateY', 'rotateZ', 'rotateAngle'];
  1105. var ops = this.getDrawOptions();
  1106. for (var i = 0, l = fields.length; i < l; ++i)
  1107. {
  1108. var field = fields[i];
  1109. result[field] = ops[field] || 0;
  1110. }
  1111. // rotateAxisVector
  1112. result.rotateAxisVector = ops.rotateAxisVector || null;
  1113. return result;
  1114. },
  1115. /**
  1116. * Set 3D rotation matrix.
  1117. * @param {Array} matrix A 4X4 rotation matrix.
  1118. * @param {Bool} suspendRendering Set this to true if a immediate repaint is not needed.
  1119. */
  1120. setRotate3DMatrix: function(matrix, suspendRendering)
  1121. {
  1122. this.getDrawOptions().rotateMatrix = matrix;
  1123. //this.drawOptionChanged();
  1124. if (!suspendRendering)
  1125. this.geometryOptionChanged();
  1126. return this;
  1127. },
  1128. /**
  1129. * Do a 3D rotation base on delta.
  1130. * @param {Float} deltaX In arc.
  1131. * @param {Float} deltaY In arc.
  1132. * @param {Float} deltaZ In arc.
  1133. * @param {Bool} suspendRendering Set this to true if a immediate repaint is not needed.
  1134. */
  1135. rotate3DBy: function(deltaX, deltaY, deltaZ, suspendRendering)
  1136. {
  1137. var lastInfo = this.getCurr3DRotationInfo();
  1138. var lastMatrix = lastInfo.rotateMatrix || Kekule.MatrixUtils.createIdentity(4);
  1139. //console.log('lastMatrix', lastMatrix);
  1140. var currMatrix = Kekule.CoordUtils.calcRotate3DMatrix({
  1141. 'rotateX': deltaX,
  1142. 'rotateY': deltaY,
  1143. 'rotateZ': deltaZ
  1144. });
  1145. //var matrix = Kekule.MatrixUtils.multiply(lastMatrix, currMatrix);
  1146. var matrix = Kekule.MatrixUtils.multiply(currMatrix, lastMatrix); // x/y/z system changes also after each rotation
  1147. //console.log('nowMatrix', matrix);
  1148. this.setRotate3DMatrix(matrix, suspendRendering);
  1149. /*
  1150. angles.rotateX += deltaX || 0;
  1151. angles.rotateY += deltaY || 0;
  1152. angles.rotateZ += deltaZ || 0;
  1153. return this.rotate3DTo(angles.rotateX, angles.rotateY, angles.rotateZ);
  1154. */
  1155. return this;
  1156. },
  1157. /**
  1158. * Do a 3D rotation around axis.
  1159. * @param {Float} angle In arc.
  1160. * @param {Hash} axisVector Axis vector coord.
  1161. * @param {Bool} suspendRendering Set this to true if a immediate repaint is not needed.
  1162. */
  1163. rotate3DByAxis: function(angle, axisVector, suspendRendering)
  1164. {
  1165. var lastInfo = this.getCurr3DRotationInfo();
  1166. var lastMatrix = lastInfo.rotateMatrix || Kekule.MatrixUtils.createIdentity(4);
  1167. var currMatrix = Kekule.CoordUtils.calcRotate3DMatrix({
  1168. 'rotateAngle': angle,
  1169. 'rotateAxisVector': axisVector
  1170. });
  1171. var matrix = Kekule.MatrixUtils.multiply(currMatrix, lastMatrix); // sequence is IMPORTANT!
  1172. //var matrix = Kekule.MatrixUtils.multiply(lastMatrix, currMatrix);
  1173. this.setRotate3DMatrix(matrix, suspendRendering);
  1174. return this;
  1175. },
  1176. /*
  1177. * Do a 3D rotation to angle on x/y/z axis
  1178. * @param {Float} x Rotation on X axis
  1179. * @param {Float} y Rotation on Y axis
  1180. * @param {Float} z Rotation on Z axis
  1181. */
  1182. /*
  1183. rotate3DTo: function(x, y, z)
  1184. {
  1185. var ops = this.getDrawOptions();
  1186. ops.rotateX = x || 0;
  1187. ops.rotateY = y || 0;
  1188. ops.rotateZ = z || 0;
  1189. this.drawOptionChanged();
  1190. return this;
  1191. },
  1192. */
  1193. // methods about tool buttons
  1194. /** @private */
  1195. getDefaultToolBarButtons: function()
  1196. {
  1197. return Kekule.globalOptions.chemWidget.viewer.toolButtons;
  1198. /*
  1199. var buttons = [
  1200. //BNS.loadFile,
  1201. BNS.loadData,
  1202. BNS.saveData,
  1203. //BNS.clearObjs,
  1204. BNS.molDisplayType,
  1205. BNS.molHideHydrogens,
  1206. BNS.zoomIn, BNS.zoomOut
  1207. ];
  1208. // rotate
  1209. //if (this.getRenderType() === Kekule.Render.RendererType.R3D)
  1210. {
  1211. buttons = buttons.concat([BNS.rotateX, BNS.rotateY, BNS.rotateZ]);
  1212. }
  1213. //else
  1214. {
  1215. buttons = buttons.concat([BNS.rotateLeft, BNS.rotateRight]);
  1216. }
  1217. buttons.push(BNS.reset);
  1218. // debug
  1219. buttons.push(BNS.openEditor);
  1220. // config
  1221. buttons.push(BNS.config);
  1222. // debug
  1223. //buttons.push(BNS.menu);
  1224. return buttons;
  1225. */
  1226. },
  1227. /* @private */
  1228. /*
  1229. createDefaultToolButtonNameMapping: function()
  1230. {
  1231. var result = {};
  1232. result[BNS.loadFile] = CW.ActionDisplayerLoadFile;
  1233. result[BNS.loadData] = CW.ActionDisplayerLoadData;
  1234. result[BNS.saveData] = CW.ActionDisplayerSaveFile;
  1235. result[BNS.clearObjs] = CW.ActionDisplayerClear;
  1236. result[BNS.zoomIn] = CW.ActionDisplayerZoomIn;
  1237. result[BNS.zoomOut] = CW.ActionDisplayerZoomOut;
  1238. result[BNS.rotateLeft] = CW.ActionViewerRotateLeft;
  1239. result[BNS.rotateRight] = CW.ActionViewerRotateRight;
  1240. result[BNS.rotateX] = CW.ActionViewerRotateX;
  1241. result[BNS.rotateY] = CW.ActionViewerRotateY;
  1242. result[BNS.rotateZ] = CW.ActionViewerRotateZ;
  1243. result[BNS.reset] = CW.ActionDisplayerReset;
  1244. result[BNS.molHideHydrogens] = CW.ActionDisplayerHideHydrogens;
  1245. result[BNS.molDisplayType] = CW.ActionViewerChangeMolDisplayTypeStub;
  1246. result[BNS.openEditor] = CW.ActionViewerEdit;
  1247. result[BNS.config] = Kekule.Widget.ActionOpenConfigWidget;
  1248. return result;
  1249. },
  1250. */
  1251. /**
  1252. * Return whether toolbarParentElem is not set the the toolbar is directly embedded in viewer itself.
  1253. * @returns {Bool}
  1254. */
  1255. isToolbarEmbedded: function()
  1256. {
  1257. return !this.getToolbarParentElem();
  1258. },
  1259. /**
  1260. * Recalc and set toolbar position.
  1261. * @private
  1262. */
  1263. adjustToolbarPos: function()
  1264. {
  1265. var toolbar = this.getToolbar();
  1266. if (!toolbar)
  1267. return;
  1268. if (this.isToolbarEmbedded())
  1269. {
  1270. //if (toolbar)
  1271. {
  1272. var WP = Kekule.Widget.Position;
  1273. //var viewerClientRect = Kekule.HtmlElementUtils.getElemBoundingClientRect(this.getDrawContextParentElem()); //this.getBoundingClientRect();
  1274. var viewerClientRect = Kekule.HtmlElementUtils.getElemPageRect(this.getDrawContextParentElem());
  1275. //var toolbarClientRect = toolbar.getBoundingClientRect();
  1276. var toolbarClientRect = Kekule.HtmlElementUtils.getElemPageRect(toolbar);
  1277. var pos = this.getToolbarPos();
  1278. var hMargin = this.getToolbarMarginHorizontal();
  1279. var vMargin = this.getToolbarMarginVertical();
  1280. // default
  1281. var hPosProp = 'left', vPosProp = 'top';
  1282. var hPosPropUnused = 'right', vPosPropUnused = 'bottom';
  1283. var hPosValue = (viewerClientRect.width - toolbarClientRect.width) / 2;
  1284. var vPosValue = (viewerClientRect.height - toolbarClientRect.height) / 2;
  1285. if (pos === WP.AUTO || !pos) // auto decide position, including margin
  1286. {
  1287. var toolbarTotalW = ((hMargin > 0) ? hMargin : 0) * 2 + toolbarClientRect.width;
  1288. var toolbarTotalH = ((vMargin > 0) ? vMargin : 0) * 2 + toolbarClientRect.height;
  1289. if (toolbarTotalW > viewerClientRect.width) // can not fit in viewer
  1290. {
  1291. pos |= WP.LEFT;
  1292. hMargin = 0;
  1293. }
  1294. else
  1295. pos |= WP.RIGHT;
  1296. if (toolbarTotalH > viewerClientRect.height / 2) // can not fit in viewer
  1297. {
  1298. pos |= WP.BOTTOM;
  1299. vMargin = -1;
  1300. }
  1301. else
  1302. pos |= WP.BOTTOM;
  1303. }
  1304. // horizontal direction
  1305. if (pos & WP.LEFT)
  1306. {
  1307. hPosProp = 'left';
  1308. hPosPropUnused = 'right';
  1309. hPosValue = (hMargin >= 0) ? hMargin : hMargin - toolbarClientRect.width;
  1310. }
  1311. else if (pos & WP.RIGHT)
  1312. {
  1313. hPosProp = 'right';
  1314. hPosPropUnused = 'left';
  1315. hPosValue = (hMargin >= 0) ? hMargin : hMargin - toolbarClientRect.width;
  1316. }
  1317. // vertical direction
  1318. if (pos & WP.TOP)
  1319. {
  1320. vPosProp = 'top';
  1321. vPosPropUnused = 'bottom';
  1322. vPosValue = (vMargin >= 0) ? vMargin : vMargin - toolbarClientRect.height;
  1323. }
  1324. else if (pos & WP.BOTTOM)
  1325. {
  1326. vPosProp = 'bottom';
  1327. vPosPropUnused = 'top';
  1328. vPosValue = (vMargin >= 0) ? vMargin : vMargin - toolbarClientRect.height;
  1329. }
  1330. toolbar.removeStyleProperty(hPosPropUnused);
  1331. toolbar.removeStyleProperty(vPosPropUnused);
  1332. toolbar.setStyleProperty(hPosProp, hPosValue + 'px');
  1333. toolbar.setStyleProperty(vPosProp, vPosValue + 'px');
  1334. }
  1335. }
  1336. else // toolbar parent appointed
  1337. {
  1338. toolbar.removeStyleProperty('left');
  1339. toolbar.removeStyleProperty('top');
  1340. toolbar.removeStyleProperty('width');
  1341. toolbar.removeStyleProperty('height');
  1342. }
  1343. },
  1344. /**
  1345. * Update toolbar in viewer.
  1346. */
  1347. updateToolbar: function()
  1348. {
  1349. if (this.getEnableToolbar())
  1350. {
  1351. this.createToolbar();
  1352. this.adjustToolbarPos();
  1353. }
  1354. else
  1355. this.setToolbar(null);
  1356. },
  1357. /**
  1358. * Update menu in viewer.
  1359. */
  1360. updateMenu: function()
  1361. {
  1362. this.createMenu();
  1363. },
  1364. /**
  1365. * Update toolbar and menu in viewer.
  1366. */
  1367. updateUiComps: function()
  1368. {
  1369. this.updateToolbar();
  1370. this.updateMenu();
  1371. },
  1372. /**
  1373. * Update toolbar actions.
  1374. * @private
  1375. */
  1376. updateActions: function()
  1377. {
  1378. if (this.getActions())
  1379. this.getActions().updateAll();
  1380. },
  1381. /** @private */
  1382. clearActions: function()
  1383. {
  1384. this.getActions().clear();
  1385. this.getActionMap().clear();
  1386. },
  1387. /** @private */
  1388. getCompActionClass: function(compName)
  1389. {
  1390. //return this.getToolButtonNameMapping()[compName];
  1391. return this.getChildActionClass(compName, true);
  1392. },
  1393. /** @private */
  1394. _getActionOfComp: function(compNameOrComp, canCreate, defActionClass)
  1395. {
  1396. var map = this.getActionMap();
  1397. var result = map.get(compNameOrComp);
  1398. if (!result && canCreate)
  1399. {
  1400. var c = this.getCompActionClass(compNameOrComp) || defActionClass;
  1401. if (c)
  1402. {
  1403. result = new c(this);
  1404. map.set(compNameOrComp, result);
  1405. this.getActions().add(result);
  1406. }
  1407. }
  1408. return result;
  1409. },
  1410. /** @private */
  1411. createToolbar: function()
  1412. {
  1413. this.clearActions();
  1414. var toolBar = new Kekule.Widget.ButtonGroup(this);
  1415. toolBar.addClassName(CNS.DYN_CREATED);
  1416. toolBar.setDisplayed(false); // hide at first, evokeHelper controls its visibility
  1417. //console.log('After create, display to: ', toolBar.getDisplayed());
  1418. //toolBar.show();
  1419. // add buttons
  1420. //var settings = this.getToolButtonSettings();
  1421. toolBar.setShowText(false);
  1422. toolBar.doSetShowGlyph(true);
  1423. var btns = this.getToolButtons() || this.getDefaultToolBarButtons(); //settings.buttons;
  1424. for (var i = 0, l = btns.length; i < l; ++i)
  1425. {
  1426. var name = btns[i];
  1427. var btn = this.createToolButton(name, toolBar);
  1428. }
  1429. toolBar.addClassName(CCNS.INNER_TOOLBAR);
  1430. if (this.isToolbarEmbedded())
  1431. toolBar.addClassName(CCNS.VIEWER_EMBEDDED_TOOLBAR);
  1432. toolBar.appendToElem(this.getToolbarParentElem() || this.getElement()/*this.getDrawContextParentElem()*/);
  1433. // IMPORTANT, must append to widget before setToolbar,
  1434. // otherwise in Chrome the tool bar may be hidden at first even if we set it to always show
  1435. //console.log('After append to widget: ', toolBar.getDisplayed());
  1436. this.setToolbar(toolBar);
  1437. //console.log('After set tool bar, display to: ', toolBar.getDisplayed());
  1438. //this.updateActions();
  1439. return toolBar;
  1440. },
  1441. /** @private */
  1442. createToolButton: function(btnName, parentGroup)
  1443. {
  1444. var result = null;
  1445. var beginContinuousRepaintingBind = this.beginContinuousRepainting.bind(this);
  1446. var endContinuousRepaintingBind = this.endContinuousRepainting.bind(this);
  1447. var rotateBtnNames = [BNS.rotateX, BNS.rotateY, BNS.rotateZ, BNS.rotateLeft, BNS.rotateRight];
  1448. if (DataType.isObjectValue(btnName)) // custom button
  1449. {
  1450. var objDefHash = Object.extend({'widget': Kekule.Widget.Button}, btnName);
  1451. result = Kekule.Widget.Utils.createFromHash(parentGroup, objDefHash);
  1452. var actionClass = objDefHash.actionClass;
  1453. if (actionClass) // create action
  1454. {
  1455. if (typeof(actionClass) === 'string')
  1456. actionClass = ClassEx.findClass(objDefHash.actionClass);
  1457. }
  1458. if (actionClass)
  1459. {
  1460. //var action = new actionClass(this);
  1461. //this.getActions().add(action);
  1462. var action = this._getActionOfComp(result, true, actionClass);
  1463. if (action)
  1464. result.setAction(action);
  1465. }
  1466. }
  1467. else
  1468. {
  1469. if (btnName === BNS.molDisplayType) // in 2D or 3D mode, type differs a lot, need handle separately
  1470. {
  1471. return this.createMolDisplayTypeButton(parentGroup);
  1472. }
  1473. else if (btnName === BNS.menu) // menu button
  1474. {
  1475. return this.createMenuButton(parentGroup);
  1476. }
  1477. var actionClass = this.getCompActionClass(btnName);
  1478. var btnClass = Kekule.Widget.Button;
  1479. if (btnName === BNS.molHideHydrogens)
  1480. btnClass = Kekule.Widget.CheckButton;
  1481. //if (actionClass)
  1482. {
  1483. result = new btnClass(parentGroup);
  1484. //result.addClassName(CCNS.PREFIX + btnName);
  1485. //var action = new actionClass(this);
  1486. var action = this._getActionOfComp(btnName, true);
  1487. //this.getActions().add(action);
  1488. if (action)
  1489. result.setAction(action);
  1490. if (rotateBtnNames.indexOf(btnName) >= 0)
  1491. {
  1492. result.setPeriodicalExecInterval(20);
  1493. result.setEnablePeriodicalExec(true);
  1494. result.addEventListener('activate', beginContinuousRepaintingBind);
  1495. result.addEventListener('deactivate', endContinuousRepaintingBind);
  1496. }
  1497. }
  1498. }
  1499. return result;
  1500. },
  1501. /** @private */
  1502. createMenuButton: function(parentGroup)
  1503. {
  1504. var result = new Kekule.Widget.DropDownButton(parentGroup);
  1505. result.setText(Kekule.$L('WidgetTexts.CAPTION_MENU')).setHint(Kekule.$L('WidgetTexts.HINT_MENU'));
  1506. result.addClassName(CCNS.VIEWER_MENU_BUTTON);
  1507. // create menu if essential
  1508. if (!this.getMenu())
  1509. this.createMenu();
  1510. result.setDropDownWidget(this.getMenu());
  1511. return result;
  1512. },
  1513. /** @private */
  1514. createMolDisplayTypeButton: function(parentGroup)
  1515. {
  1516. var result = new Kekule.Widget.CompactButtonSet(parentGroup);
  1517. result.getButtonSet().addClassName(CCNS.INNER_TOOLBAR);
  1518. // IMPORTANT: buttonSet may be popup and moved in DOM tree before showing,
  1519. // without this, the button size setting in CSS may be lost
  1520. // TODO: may find a better solution to solve popup widget style lost problem
  1521. result.setShowText(false);
  1522. //result.setHint(CWT.HINT_MOL_DISPLAY_TYPE);
  1523. //var action = new Kekule.ChemWidget.ActionViewerChangeMolDisplayTypeStub(this);
  1524. var action = this._getActionOfComp(BNS.molDisplayType, true);
  1525. result.setAction(action);
  1526. this.getActions().add(action);
  1527. // DONE: Now we fix the drop down direction
  1528. //result.setDropPosition(Kekule.Widget.Position.TOP);
  1529. var doc = this.getDocument();
  1530. var allowedType = this.getAllowedMolDisplayTypes();
  1531. var actionClasses = this._getUsableMolDisplayActionClasses(allowedType);
  1532. for (var i = 0, l = actionClasses.length; i < l; ++i)
  1533. {
  1534. var displayType = actionClasses[i].TYPE;
  1535. /*
  1536. if (allowedType && (allowedType.indexOf(displayType) < 0) && (displayType !== this.getCurrMoleculeDisplayType())) // not in allowed type
  1537. continue;
  1538. */
  1539. var btn = new Kekule.Widget.RadioButton(doc);
  1540. //action = new actionClasses[i](this);
  1541. var action = this._getActionOfComp(BNS.molDisplayType + displayType, true, actionClasses[i]);
  1542. //this.getActions().add(action);
  1543. btn.setAction(action);
  1544. result.append(btn, displayType === this.getCurrMoleculeDisplayType());
  1545. }
  1546. return result;
  1547. },
  1548. /* @private */
  1549. _getUsableMolDisplayActionClasses: function(allowedTypes)
  1550. {
  1551. var result = [];
  1552. var actionClasses = (this.getRenderType() === Kekule.Render.RendererType.R3D)?
  1553. CW.Viewer.molDisplayType3DActionClasses: CW.Viewer.molDisplayType2DActionClasses;
  1554. for (var i = 0, l = actionClasses.length; i < l; ++i)
  1555. {
  1556. var displayType = actionClasses[i].TYPE;
  1557. if (allowedTypes && (allowedTypes.indexOf(displayType) < 0) && (displayType !== this.getCurrMoleculeDisplayType())) // not in allowed type
  1558. continue;
  1559. result.push(actionClasses[i]);
  1560. }
  1561. return result;
  1562. },
  1563. // abount menu
  1564. /** @private */
  1565. getDefaultMenuItems: function()
  1566. {
  1567. return Kekule.globalOptions.chemWidget.viewer.menuItems;
  1568. /*
  1569. var sSeparator = Kekule.Widget.MenuItem.SEPARATOR_TEXT;
  1570. var items = [
  1571. BNS.loadData,
  1572. BNS.saveData,
  1573. sSeparator,
  1574. BNS.molDisplayType,
  1575. BNS.molHideHydrogens,
  1576. BNS.zoomIn, BNS.zoomOut
  1577. ];
  1578. // rotate
  1579. items.push({
  1580. 'text': Kekule.$L('ChemWidgetTexts.CAPTION_ROTATE'),
  1581. 'hint': Kekule.$L('ChemWidgetTexts.HINT_ROTATE'),
  1582. 'children': [
  1583. BNS.rotateLeft, BNS.rotateRight,
  1584. BNS.rotateX, BNS.rotateY, BNS.rotateZ
  1585. ]
  1586. });
  1587. items.push(BNS.reset);
  1588. items.push(sSeparator);
  1589. items.push(BNS.openEditor);
  1590. // config
  1591. items.push(BNS.config);
  1592. return items;
  1593. */
  1594. },
  1595. /** @private */
  1596. prepareMenuItems: function(itemDefs)
  1597. {
  1598. var items = [];
  1599. var sSeparator = Kekule.Widget.MenuItem.SEPARATOR_TEXT;
  1600. for (var i = 0, l = itemDefs.length; i < l; ++i)
  1601. {
  1602. var itemDef = itemDefs[i];
  1603. if (typeof(itemDef) === 'string') // not hash, but a predefined comp name or separator
  1604. {
  1605. if (itemDef !== sSeparator)
  1606. {
  1607. var defHash = this.createPredefinedMenuItemDefHash(itemDef);
  1608. if (defHash)
  1609. items.push(defHash);
  1610. }
  1611. }
  1612. else // hash definition
  1613. {
  1614. var newItem = Object.extend({}, itemDef);
  1615. if (!itemDef.widget && !itemDef.widgetClass)
  1616. {
  1617. newItem.widget = Kekule.Widget.MenuItem;
  1618. }
  1619. if (itemDef.children && itemDef.children.length)
  1620. {
  1621. var childItems = this.prepareMenuItems(itemDef.children);
  1622. newItem.children = childItems;
  1623. }
  1624. items.push(newItem);
  1625. }
  1626. }
  1627. return items;
  1628. },
  1629. /** @private */
  1630. createPredefinedMenuItemDefHash: function(compName)
  1631. {
  1632. if (compName === BNS.molDisplayType) // in 2D or 3D mode, type differs a lot, need handle separately
  1633. {
  1634. return this.createMolDisplayMenuDefHash();
  1635. }
  1636. var rotateCompNames = [BNS.rotateX, BNS.rotateY, BNS.rotateZ, BNS.rotateLeft, BNS.rotateRight];
  1637. var beginContinuousRepaintingBind = this.beginContinuousRepainting.bind(this);
  1638. var endContinuousRepaintingBind = this.endContinuousRepainting.bind(this);
  1639. var actionClass = this.getCompActionClass(compName);
  1640. var itemClass = Kekule.Widget.MenuItem;
  1641. var result = null;
  1642. var action = this._getActionOfComp(compName, true);
  1643. if (action)
  1644. {
  1645. //console.log('menu item', action.getClassName(), action.getDisplayer? action.getDisplayer().getElement().id: '-', this.getElement().id);
  1646. result = {
  1647. 'widget': itemClass,
  1648. 'action': action
  1649. };
  1650. if (rotateCompNames.indexOf(compName) >= 0)
  1651. {
  1652. result = Object.extend(result, {
  1653. 'periodicalExecInterval': 20,
  1654. 'enablePeriodicalExec': true,
  1655. '#activate': beginContinuousRepaintingBind,
  1656. '@deactivate': endContinuousRepaintingBind
  1657. });
  1658. }
  1659. }
  1660. return result;
  1661. },
  1662. /** @private */
  1663. createMolDisplayMenuDefHash: function()
  1664. {
  1665. var action = this._getActionOfComp(BNS.molDisplayType, true);
  1666. var result = {
  1667. 'widget': Kekule.Widget.MenuItem,
  1668. 'action': action
  1669. };
  1670. var children = [];
  1671. var allowedType = this.getAllowedMolDisplayTypes();
  1672. var actionClasses = this._getUsableMolDisplayActionClasses(allowedType);
  1673. for (var i = 0, l = actionClasses.length; i < l; ++i)
  1674. {
  1675. var displayType = actionClasses[i].TYPE;
  1676. var action = this._getActionOfComp(BNS.molDisplayType + displayType, true, actionClasses[i]);
  1677. children.push({
  1678. 'widget': Kekule.Widget.MenuItem,
  1679. 'action': action
  1680. });
  1681. }
  1682. if (children.length)
  1683. {
  1684. result.children = [{
  1685. 'widget': Kekule.Widget.PopupMenu,
  1686. 'children': children
  1687. }];
  1688. }
  1689. return result;
  1690. },
  1691. /** @private */
  1692. createMenu: function()
  1693. {
  1694. var result = this.getMenu();
  1695. if (!result)
  1696. {
  1697. result = new Kekule.Widget.PopupMenu(this);
  1698. result.addClassName(CNS.DYN_CREATED);
  1699. result.setDisplayed(false); // hide at first;
  1700. }
  1701. else // clear old first
  1702. {
  1703. result.clearMenuItems();
  1704. }
  1705. var items = this.getMenuItems() || this.getDefaultMenuItems();
  1706. //console.log('create menu', this.getId(), items);
  1707. items = this.prepareMenuItems(items);
  1708. /*
  1709. for (var i = 0, l = items.length; i < l; ++i)
  1710. {
  1711. var menuItem = Kekule.Widget.Utils.createFromHash(result, items[i]);
  1712. result.appendMenuItem(menuItem);
  1713. }
  1714. */
  1715. result.createChildrenByDefs(items);
  1716. this.setMenu(result);
  1717. //this.updateActions();
  1718. return result;
  1719. },
  1720. /** @private */
  1721. autoDetectCaption: function()
  1722. {
  1723. if (this.getAutoCaption())
  1724. {
  1725. var obj = this.getChemObj();
  1726. if (obj)
  1727. {
  1728. var info = obj && obj.getInfo();
  1729. var srcInfo = obj && obj.getSrcInfo();
  1730. if (info && srcInfo)
  1731. {
  1732. var caption = info.title || info.caption || obj.getName() || srcInfo.fileName;
  1733. if (caption)
  1734. this.setCaption(caption);
  1735. }
  1736. }
  1737. }
  1738. }
  1739. });
  1740. var XEvent = Kekule.X.Event;
  1741. /**
  1742. * Basic Interaction controller for general viewers, can do zoomIn/out job.
  1743. * @class
  1744. * @augments Kekule.Widget.InteractionController
  1745. *
  1746. * @param {Kekule.ChemWidget.Viewer} viewer Viewer of current object being installed to.
  1747. */
  1748. Kekule.ChemWidget.ViewerBasicInteractionController = Class.create(Kekule.Widget.InteractionController,
  1749. /** @lends Kekule.ChemWidget.ViewerBasicInteractionController# */
  1750. {
  1751. /** @private */
  1752. CLASS_NAME: 'Kekule.ChemWidget.ViewerBasicInteractionController',
  1753. /** @constructs */
  1754. initialize: function($super, viewer)
  1755. {
  1756. $super(viewer);
  1757. this._enableMouseRotate = true; // private
  1758. this._transformInfo = {
  1759. 'isTransforming': false,
  1760. //'isRotating': false,
  1761. 'lastCoord': null,
  1762. 'lastZoom': null
  1763. };
  1764. this._restraintCoord = null;
  1765. this._doInteractiveTransformStepBind = this._doInteractiveTransformStep.bind(this);
  1766. /*
  1767. this._zoomInfo = {
  1768. 'isTransforming': false,
  1769. 'lastZoom': null
  1770. }
  1771. */
  1772. },
  1773. /** @private */
  1774. initProperties: function()
  1775. {
  1776. this.defineProp('viewer', {'dataType': 'Kekule.ChemWidget.Viewer', 'serializable': false,
  1777. 'getter': function() { return this.getWidget(); }, 'setter': function(value) { this.setWidget(value); } });
  1778. },
  1779. /** @private */
  1780. getViewerRenderType: function()
  1781. {
  1782. return this.getViewer().getRenderType();
  1783. },
  1784. /** @private */
  1785. getEnableInteraction: function()
  1786. {
  1787. var v = this.getViewer();
  1788. var result = !!(v && v.getEnableDirectInteraction() && v.getChemObjLoaded());
  1789. //console.log('enabledInteraction', result);
  1790. return result;
  1791. },
  1792. /** @private */
  1793. getEnableTouchInteraction: function()
  1794. {
  1795. var v = this.getViewer();
  1796. var result = !!(v && v.getEnableDirectInteraction() && v.getEnableTouchInteraction() && v.getChemObjLoaded());
  1797. return result;
  1798. },
  1799. /** @private */
  1800. _initTransform: function()
  1801. {
  1802. var viewer = this.getViewer();
  1803. var w = viewer.getOffsetWidth();
  1804. var h = viewer.getOffsetHeight();
  1805. var refLength;
  1806. var info = this._transformInfo;
  1807. var rc = this._restraintCoord;
  1808. if (rc)
  1809. {
  1810. refLength = (rc === 'x')? h: w;
  1811. info.angleRatio = 1 / refLength * Math.PI * 2;
  1812. }
  1813. else
  1814. {
  1815. refLength = Math.min(w, h);
  1816. info.angleRatio = 1 / refLength * Math.PI;
  1817. }
  1818. //info.lastRotateXYZ = {'x': 0, 'y': 0, 'z': 0};
  1819. },
  1820. /** @private */
  1821. zoomViewer: function(delta)
  1822. {
  1823. var v = this.getViewer();
  1824. if (!v || !v.getChemObj())
  1825. return;
  1826. if (delta > 0)
  1827. {
  1828. if (v.zoomIn)
  1829. v.zoomIn(delta);
  1830. }
  1831. else if (delta < 0)
  1832. {
  1833. if (v.zoomOut)
  1834. v.zoomOut(-delta);
  1835. }
  1836. },
  1837. /** @private */
  1838. isEventFromInteractionArea: function(e)
  1839. {
  1840. var target = e.getTarget();
  1841. var interactionElem = this.getViewer().getInteractionReceiverElem();
  1842. return (target === interactionElem) || Kekule.DomUtils.isDescendantOf(target, interactionElem);
  1843. },
  1844. /** @private */
  1845. _beginInteractTransformAtCoord: function(screenX, screenY, clientX, clientY)
  1846. {
  1847. var viewer = this.getViewer();
  1848. if (viewer && viewer.getChemObj())
  1849. {
  1850. //if (viewer.getRenderType() === Kekule.Render.RendererType.R3D)
  1851. {
  1852. var info = this._transformInfo;
  1853. info.isTransforming = true;
  1854. info.lastCoord = {'x': screenX, 'y': screenY};
  1855. /*
  1856. var minLength = Math.min(viewer.getOffsetWidth(), viewer.getOffsetHeight());
  1857. info.angleRatio = 1 / minLength * Math.PI;
  1858. */
  1859. this._restraintCoord = this._calcRestraintRotateCoord(clientX, clientY);
  1860. this._initTransform();
  1861. this.getViewer().setTouchAction('none');
  1862. this._requestInteractiveTransform(screenX, screenY);
  1863. }
  1864. }
  1865. },
  1866. /** @private */
  1867. _endInteractTransform: function()
  1868. {
  1869. this._transformInfo.isTransforming = false;
  1870. this.getViewer().setTouchAction(null);
  1871. this._doInteractiveTransformEnd();
  1872. },
  1873. /** @private */
  1874. _calcRestraintRotateCoord: function(clientX, clientY)
  1875. {
  1876. var viewer = this.getViewer();
  1877. var result = null;
  1878. // check if ned restraint rotate
  1879. var restraintRotateEdgeSize = this._getRestraintRotate3DEdgeSize();
  1880. if (restraintRotateEdgeSize > 0)
  1881. {
  1882. var elem = viewer.getInteractionReceiverElem();
  1883. //var rect = Kekule.HtmlElementUtils.getElemBoundingClientRect(elem, false);
  1884. var rect = Kekule.HtmlElementUtils.getElemPageRect(elem, true);
  1885. var x1 = clientX - rect.left;
  1886. var y1 = clientY - rect.top;
  1887. var x2 = rect.right - clientX; //rect.right - screenX;
  1888. var y2 = rect.bottom - clientY; //rect.bottom - screenY;
  1889. var minX, minY, flagX, flagY;
  1890. if (x1 <= x2)
  1891. {
  1892. flagX = 1;
  1893. minX = x1;
  1894. }
  1895. else
  1896. {
  1897. flagX = -1;
  1898. minX = x2;
  1899. }
  1900. if (y1 <= y2)
  1901. {
  1902. flagY = 1;
  1903. minY = y1;
  1904. }
  1905. else
  1906. {
  1907. flagY = -1;
  1908. minY = y2;
  1909. }
  1910. var minOffset = Math.min(minX, minY);
  1911. if (minOffset > restraintRotateEdgeSize) // no restraint
  1912. return null;
  1913. else // calc restraint coord
  1914. {
  1915. if (minY > minOffset) // more near to left or right edge
  1916. {
  1917. if (flagX < 0) // on right edge
  1918. result = 'x';
  1919. }
  1920. else // more near to top/bottom edge
  1921. {
  1922. if (flagY > 0) // on top edge, rotate on z axis
  1923. result = 'z';
  1924. else // on bottom edge
  1925. result = 'y';
  1926. }
  1927. }
  1928. }
  1929. //console.log('rotate restraint coord', result);
  1930. return result;
  1931. },
  1932. /** @private */
  1933. _interactTransformAtCoord: function(screenX, screenY)
  1934. {
  1935. var lastCoord = this._transformInfo.lastCoord;
  1936. if (lastCoord)
  1937. {
  1938. var currCoord = {'x': screenX, 'y': screenY};
  1939. var distance = Kekule.CoordUtils.getDistance(lastCoord, currCoord);
  1940. if (distance < 5) // moves too little to react
  1941. return;
  1942. }
  1943. this._requestInteractiveTransform(screenX, screenY);
  1944. },
  1945. /** @private */
  1946. _requestInteractiveTransform: function(screenX, screenY)
  1947. {
  1948. /*
  1949. if (!this._transformInfo)
  1950. {
  1951. this._transformInfo = {};
  1952. }
  1953. */
  1954. this._transformInfo.interactScreenCoord = {x: screenX, y: screenY};
  1955. if (!this._interactiveTransformStepId)
  1956. this._interactiveTransformStepId = window.requestAnimationFrame(this._doInteractiveTransformStepBind);
  1957. },
  1958. /** @private */
  1959. _doInteractiveTransformStep: function()
  1960. {
  1961. if (this._transformInfo && this._transformInfo.isTransforming)
  1962. {
  1963. var screenCoord = this._transformInfo.interactScreenCoord;
  1964. if (this.getViewerRenderType() === Kekule.Render.RendererType.R3D)
  1965. this.rotateByXYDistance(screenCoord.x, screenCoord.y);
  1966. else
  1967. this.moveByXYDistance(screenCoord.x, screenCoord.y);
  1968. this._interactiveTransformStepId = window.requestAnimationFrame(this._doInteractiveTransformStepBind);
  1969. }
  1970. },
  1971. /** @private */
  1972. _doInteractiveTransformEnd: function()
  1973. {
  1974. if (this._interactiveTransformStepId)
  1975. {
  1976. window.cancelAnimationFrame(this._interactiveTransformStepId);
  1977. this._interactiveTransformStepId = null;
  1978. }
  1979. },
  1980. /** @private */
  1981. _getRestraintRotate3DEdgeSize: function()
  1982. {
  1983. var viewer = this.getViewer();
  1984. if (viewer.getEnableRestraintRotation3D() && viewer.getRenderType() === Kekule.Render.RendererType.R3D)
  1985. {
  1986. var dim = viewer.getDimension();
  1987. var length = Math.min(dim.width, dim.height);
  1988. return length * (viewer.getRestraintRotation3DEdgeRatio() || 0);
  1989. }
  1990. else
  1991. return 0;
  1992. },
  1993. /** @private */
  1994. needReactEvent: function(e)
  1995. {
  1996. return this.getEnableInteraction() && this.isEventFromInteractionArea(e);
  1997. },
  1998. /* @private */
  1999. /*
  2000. needReactToTouchEvent: function(e)
  2001. {
  2002. var touches = e.getTouches();
  2003. return this.getEnableTouchInteraction() && touches && touches.length > 1;
  2004. },
  2005. */
  2006. /** @private */
  2007. react_dblclick: function(e)
  2008. {
  2009. if (this.needReactEvent(e))
  2010. {
  2011. this.getViewer().resetDisplay();
  2012. }
  2013. },
  2014. /** @private */
  2015. react_mousewheel: function(e)
  2016. {
  2017. if (this.needReactEvent(e))
  2018. {
  2019. var delta = e.wheelDeltaY || e.wheelDelta;
  2020. if (delta)
  2021. delta /= 120;
  2022. this.zoomViewer(delta);
  2023. e.preventDefault();
  2024. return true;
  2025. }
  2026. },
  2027. /** @private */
  2028. react_pointerdown: function(e)
  2029. {
  2030. if (!this.needReactEvent(e))
  2031. return;
  2032. if (e.getButton() === XEvent.MouseButton.LEFT)
  2033. {
  2034. if (e.getPointerType() !== XEvent.PointerType.TOUCH || this.getEnableTouchInteraction())
  2035. {
  2036. // start mouse drag rotation in 3D render mode
  2037. this._beginInteractTransformAtCoord(e.getScreenX(), e.getScreenY(), e.getClientX(), e.getClientY());
  2038. }
  2039. }
  2040. },
  2041. /** @private */
  2042. react_pointerhold: function(e)
  2043. {
  2044. if (!this.needReactEvent(e))
  2045. return;
  2046. if (e.getPointerType() === XEvent.PointerType.TOUCH && e.getButton() === XEvent.MouseButton.LEFT)
  2047. {
  2048. this.getViewer().setEnableTouchInteraction(!this.getViewer().getEnableTouchInteraction());
  2049. /*
  2050. if (!this._transformInfo.isTransforming)
  2051. {
  2052. // start mouse drag rotation in 3D render mode
  2053. this._beginInteractTransformAtCoord(e.getScreenX(), e.getScreenY(), e.getClientX(), e.getClientY());
  2054. e.preventDefault();
  2055. }
  2056. */
  2057. }
  2058. },
  2059. /** @private */
  2060. /*
  2061. react_touchstart: function(e)
  2062. {
  2063. if (!this.needReactEvent(e) || !this.needReactToTouchEvent(e))
  2064. return;
  2065. var touchInfo = e.getTouches()[0];
  2066. var notUnset = Kekule.ObjUtils.notUnset;
  2067. if (touchInfo && notUnset(touchInfo.screenX) && notUnset(touchInfo.screenY))
  2068. {
  2069. this._beginInteractTransformAtCoord(touchInfo.screenX, touchInfo.screenY, touchInfo.clientX, touchInfo.clientY);
  2070. e.stopPropagation();
  2071. e.preventDefault();
  2072. }
  2073. },
  2074. */
  2075. /** @private */
  2076. react_pointerleave: function(e)
  2077. {
  2078. //this._transformInfo.isTransforming = false;
  2079. this._endInteractTransform();
  2080. },
  2081. /** @private */
  2082. /*
  2083. react_touchleave: function(e)
  2084. {
  2085. this._transformInfo.isTransforming = false;
  2086. },
  2087. */
  2088. /** @private */
  2089. /*
  2090. react_touchcancel: function(e)
  2091. {
  2092. this._transformInfo.isTransforming = false;
  2093. },
  2094. */
  2095. /** @private */
  2096. react_pointerup: function(e)
  2097. {
  2098. if (e.getButton() === XEvent.MouseButton.LEFT)
  2099. {
  2100. //this._transformInfo.isTransforming = false;
  2101. this._endInteractTransform();
  2102. }
  2103. },
  2104. /** @private */
  2105. /*
  2106. react_touchend: function(e)
  2107. {
  2108. this._transformInfo.isTransforming = false;
  2109. },
  2110. */
  2111. /** @private */
  2112. react_pointermove: function(e)
  2113. {
  2114. if (!this.needReactEvent(e))
  2115. return;
  2116. /*
  2117. if (this.getViewerRenderType() === Kekule.Render.RendererType.R3D)
  2118. this.rotateByXYDistance(e.getScreenX(), e.getScreenY());
  2119. else
  2120. this.moveByXYDistance(e.getScreenX(), e.getScreenY());
  2121. */
  2122. if (this._transformInfo.isTransforming)
  2123. {
  2124. this._interactTransformAtCoord(e.getScreenX(), e.getScreenY());
  2125. e.preventDefault();
  2126. }
  2127. },
  2128. /** @private */
  2129. /*
  2130. react_touchmove: function(e)
  2131. {
  2132. if (!this.needReactEvent(e) || !this.needReactToTouchEvent(e))
  2133. return;
  2134. //console.log(e.touches);
  2135. var touchInfo = e.getTouches()[0];
  2136. var notUnset = Kekule.ObjUtils.notUnset;
  2137. if (touchInfo && notUnset(touchInfo.screenX) && notUnset(touchInfo.screenY))
  2138. {
  2139. this._interactTransformAtCoord(touchInfo.screenX, touchInfo.screenY);
  2140. e.stopPropagation();
  2141. e.preventDefault();
  2142. }
  2143. },
  2144. */
  2145. /** @private */
  2146. moveByXYDistance: function(currX, currY)
  2147. {
  2148. var info = this._transformInfo;
  2149. if (info && info.isTransforming && (!info.calculating))
  2150. {
  2151. var viewer = this.getViewer();
  2152. if (viewer.getRenderType() === Kekule.Render.RendererType.R3D)
  2153. return;
  2154. info.calculating = true;
  2155. try
  2156. {
  2157. var currCoord = {'x': currX, 'y': currY};
  2158. var delta = Kekule.CoordUtils.substract(currCoord, info.lastCoord);
  2159. var baseCoordOffset = viewer.getBaseCoordOffset() || {};
  2160. baseCoordOffset = Kekule.CoordUtils.add(baseCoordOffset, delta);
  2161. viewer.setBaseCoordOffset(baseCoordOffset);
  2162. info.lastCoord = currCoord;
  2163. }
  2164. finally
  2165. {
  2166. info.calculating = false;
  2167. }
  2168. }
  2169. },
  2170. /** @private */
  2171. rotateByXYDistance: function(currX, currY)
  2172. {
  2173. var info = this._transformInfo;
  2174. if (info && info.isTransforming && (!info.calculating))
  2175. {
  2176. var viewer = this.getViewer();
  2177. if (viewer.getRenderType() !== Kekule.Render.RendererType.R3D)
  2178. return;
  2179. info.calculating = true;
  2180. try
  2181. {
  2182. var currCoord = {'x': currX, 'y': currY};
  2183. var delta = Kekule.CoordUtils.substract(currCoord, info.lastCoord);
  2184. delta.y = -delta.y;
  2185. var dis, rotateAngle, axisVector;
  2186. if (this._restraintCoord) // restraint rotation on one axis
  2187. {
  2188. var rc = this._restraintCoord;
  2189. if (rc === 'x')
  2190. {
  2191. dis = delta.y;
  2192. axisVector = {'x': 1, 'y': 0, 'z': 0};
  2193. }
  2194. else
  2195. {
  2196. dis = delta.x;
  2197. if (rc === 'y')
  2198. axisVector = {'x': 0, 'y': -1, 'z': 0};
  2199. else
  2200. axisVector = {'x': 0, 'y': 0, 'z': 1};
  2201. }
  2202. rotateAngle = -dis * info.angleRatio;
  2203. }
  2204. else // normal rotation
  2205. {
  2206. dis = Kekule.CoordUtils.getDistance({'x': 0, 'y': 0}, delta);
  2207. rotateAngle = dis * info.angleRatio;
  2208. axisVector = {'x': -delta.y, 'y': delta.x, 'z': 0};
  2209. }
  2210. viewer.rotate3DByAxis(rotateAngle, axisVector);
  2211. info.lastCoord = currCoord;
  2212. info.calculating = false;
  2213. }
  2214. catch(e) {} // fix IE finally bug
  2215. finally
  2216. {
  2217. info.calculating = false;
  2218. }
  2219. }
  2220. },
  2221. /* @private */
  2222. /*
  2223. react_transformstart: function(e)
  2224. {
  2225. if (!this.getEnableTouchInteraction() || !this.needReactEvent(e))
  2226. return;
  2227. var viewer = this.getViewer();
  2228. if (viewer)
  2229. {
  2230. var info = this._transformInfo;
  2231. info.isTransforming = true;
  2232. info.lastZoom = viewer.getCurrZoom();
  2233. info.lastCoord = {'x': 0, 'y': 0};
  2234. this._initTransform();
  2235. }
  2236. },
  2237. */
  2238. /* @private */
  2239. /*
  2240. react_transformend: function(e)
  2241. {
  2242. if (!this.getEnableTouchInteraction() || !this.needReactEvent(e))
  2243. return;
  2244. this._transformInfo.isTransforming = false;
  2245. },
  2246. */
  2247. /* @private */
  2248. /*
  2249. react_transform: function(e)
  2250. {
  2251. if (!this.getEnableTouchInteraction() || !this.needReactEvent(e))
  2252. return;
  2253. var viewer = this.getViewer();
  2254. var info = this._transformInfo;
  2255. if (viewer && info.isTransforming && (!info.calculating))
  2256. {
  2257. try
  2258. {
  2259. // TODO: the event detail data is binded to hammer.js, may need to change later
  2260. var gesture = e.gesture;
  2261. if (gesture)
  2262. {
  2263. // zoom
  2264. var scale = e.gesture.scale;
  2265. viewer.zoomTo((info.lastZoom || 1) * scale, true); // suspend render, as we will rotate further
  2266. //console.log('new scale', scale);
  2267. // rotate
  2268. var dx = gesture.deltaX;
  2269. var dy = gesture.deltaY;
  2270. //console.log(dx, dy, info.lastCoord.x, info.lastCoord.y);
  2271. this.rotateByXYDistance(dx, dy);
  2272. e.gesture.preventDefault();
  2273. e.gesture.stopPropagation();
  2274. }
  2275. }
  2276. finally
  2277. {
  2278. //info.calculating = false;
  2279. }
  2280. }
  2281. //console.log(e, e.gesture);
  2282. },
  2283. */
  2284. /** @private */
  2285. doTestMouseCursor: function(coord, e)
  2286. {
  2287. var result = '';
  2288. var info = this._transformInfo;
  2289. if (info.isTransforming)
  2290. result = 'move'; //'grabbing';
  2291. return result;
  2292. }
  2293. });
  2294. /**
  2295. * An 2D viewer widget for chem objects, actually a specialization of {@link Kekule.ChemWidget.Viewer}.
  2296. * @class
  2297. * @augments Kekule.ChemWidget.Viewer
  2298. *
  2299. * @param {Variant} parentOrElementOrDocument
  2300. * @param {Kekule.ChemObject} chemObj
  2301. */
  2302. Kekule.ChemWidget.Viewer2D = Class.create(Kekule.ChemWidget.Viewer,
  2303. /** @lends Kekule.ChemWidget.Viewer2D# */
  2304. {
  2305. /** @private */
  2306. CLASS_NAME: 'Kekule.ChemWidget.Viewer2D',
  2307. /** @construct */
  2308. initialize: function($super, parentOrElementOrDocument, chemObj)
  2309. {
  2310. $super(parentOrElementOrDocument, chemObj, Kekule.Render.RendererType.R2D);
  2311. },
  2312. /** @ignore */
  2313. getAllowRenderTypeChange: function()
  2314. {
  2315. return false;
  2316. }
  2317. });
  2318. /**
  2319. * An 3D viewer widget for chem objects, actually a specialization of {@link Kekule.ChemWidget.Viewer}.
  2320. * @class
  2321. * @augments Kekule.ChemWidget.Viewer
  2322. *
  2323. * @param {Variant} parentOrElementOrDocument
  2324. * @param {Kekule.ChemObject} chemObj
  2325. */
  2326. Kekule.ChemWidget.Viewer3D = Class.create(Kekule.ChemWidget.Viewer,
  2327. /** @lends Kekule.ChemWidget.Viewer3D# */
  2328. {
  2329. /** @private */
  2330. CLASS_NAME: 'Kekule.ChemWidget.Viewer3D',
  2331. /** @construct */
  2332. initialize: function($super, parentOrElementOrDocument, chemObj)
  2333. {
  2334. $super(parentOrElementOrDocument, chemObj, Kekule.Render.RendererType.R3D);
  2335. },
  2336. /** @ignore */
  2337. getAllowRenderTypeChange: function()
  2338. {
  2339. return false;
  2340. }
  2341. });
  2342. // register predefined settings of viewer
  2343. var SM = Kekule.ObjPropSettingManager;
  2344. SM.register('Kekule.ChemWidget.Viewer.fullFunc', { // viewer with all functions
  2345. enableToolbar: true,
  2346. enableDirectInteraction: true,
  2347. enableTouchInteraction: true,
  2348. enableEdit: true,
  2349. toolButtons: null // create all default tool buttons
  2350. });
  2351. SM.register('Kekule.ChemWidget.Viewer.basic', { // viewer with basic function, suitable for embedded chem object with limited size
  2352. enableToolbar: true,
  2353. enableDirectInteraction: true,
  2354. enableTouchInteraction: false,
  2355. toolButtons: [BNS.saveData, BNS.molDisplayType, BNS.zoomIn, BNS.zoomOut],
  2356. menuItems: [BNS.saveData, '-', BNS.molDisplayType, BNS.zoomIn, BNS.zoomOut]
  2357. });
  2358. SM.register('Kekule.ChemWidget.Viewer.mini', { // viewer with only one menu button
  2359. enableToolbar: true,
  2360. enableDirectInteraction: true,
  2361. enableTouchInteraction: false,
  2362. toolButtons: [BNS.menu],
  2363. menuItems: [BNS.saveData, '-', BNS.molDisplayType, BNS.zoomIn, BNS.zoomOut]
  2364. });
  2365. SM.register('Kekule.ChemWidget.Viewer.static', { // viewer with no interaction ability, suitable for static embedded chem object
  2366. enableToolbar: false,
  2367. enableDirectInteraction: false,
  2368. enableTouchInteraction: false,
  2369. toolButtons: [],
  2370. menuItems: []
  2371. });
  2372. SM.register('Kekule.ChemWidget.Viewer.editOnly', { // viewer can be editted
  2373. enableToolbar: true,
  2374. enableEdit: true,
  2375. toolButtons: [BNS.openEditor],
  2376. menuItems: [BNS.openEditor]
  2377. });
  2378. /**
  2379. * A special class to give a setting facade for Chem Viewer.
  2380. * Do not use this class alone.
  2381. * @class
  2382. * @augments Kekule.ChemWidget.ChemObjDisplayer.Settings
  2383. * @ignore
  2384. */
  2385. Kekule.ChemWidget.Viewer.Settings = Class.create(Kekule.ChemWidget.ChemObjDisplayer.Settings,
  2386. /** @lends Kekule.ChemWidget.Viewer.Settings# */
  2387. {
  2388. /** @private */
  2389. CLASS_NAME: 'Kekule.ChemWidget.Viewer.Settings',
  2390. /** @private */
  2391. initProperties: function()
  2392. {
  2393. this.defineDelegatedProps([
  2394. 'enableDirectInteraction', 'enableTouchInteraction',
  2395. 'enableToolbar', 'toolbarPos', 'toolbarMarginHorizontal', 'toolbarMarginVertical',
  2396. 'enableEdit', 'modalEdit'
  2397. ]);
  2398. },
  2399. /** @private */
  2400. getViewer: function()
  2401. {
  2402. return this.getWidget();
  2403. }
  2404. });
  2405. /**
  2406. * Base class for actions for chem viewer.
  2407. * @class
  2408. * @augments Kekule.ChemWidget.ActionOnDisplayer
  2409. *
  2410. * @param {Kekule.ChemWidget.Viewer} viewer Target viewer widget.
  2411. * @param {String} caption
  2412. * @param {String} hint
  2413. */
  2414. Kekule.ChemWidget.ActionOnViewer = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
  2415. /** @lends Kekule.ChemWidget.ActionOnViewer# */
  2416. {
  2417. /** @private */
  2418. CLASS_NAME: 'Kekule.ChemWidget.ActionOnViewer',
  2419. /** @constructs */
  2420. initialize: function($super, viewer, caption, hint)
  2421. {
  2422. $super(viewer, caption, hint);
  2423. },
  2424. /** @private */
  2425. doUpdate: function()
  2426. {
  2427. var displayer = this.getDisplayer();
  2428. this.setEnabled(displayer && displayer.getChemObj() && displayer.getChemObjLoaded() && displayer.getEnabled());
  2429. },
  2430. /**
  2431. * Returns target chem viewer.
  2432. * @returns {Kekule.ChemWidget.Viewer}
  2433. */
  2434. getViewer: function()
  2435. {
  2436. var result = this.getDisplayer();
  2437. return (result instanceof Kekule.ChemWidget.Viewer)? result: null;
  2438. }
  2439. });
  2440. /**
  2441. * Base action for make rotation in viewer.
  2442. * @class
  2443. * @augments Kekule.ChemWidget.ActionOnViewer
  2444. *
  2445. * @property {Float} delta The rotation angle.
  2446. */
  2447. Kekule.ChemWidget.ActionViewerRotateBase = Class.create(Kekule.ChemWidget.ActionOnViewer,
  2448. /** @lends Kekule.ChemWidget.ActionViewerRotateBase# */
  2449. {
  2450. /** @private */
  2451. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateBase',
  2452. /** @constructs */
  2453. initialize: function($super, viewer, caption, hint)
  2454. {
  2455. $super(viewer, caption, hint);
  2456. this.setDelta(2 * Math.PI / 180); // TODO: this default value should be configurable
  2457. },
  2458. /** @private */
  2459. initProperties: function()
  2460. {
  2461. this.defineProp('delta', {'dataType': DataType.FLOAT});
  2462. },
  2463. /** @private */
  2464. isShiftModified: function(htmlEvent)
  2465. {
  2466. return htmlEvent && htmlEvent.getShiftKey();
  2467. }
  2468. });
  2469. /**
  2470. * Base action for make rotation in 2D viewer.
  2471. * @class
  2472. * @augments Kekule.ChemWidget.ActionViewerRotateBase
  2473. *
  2474. * @property {Float} delta The rotation angle.
  2475. */
  2476. Kekule.ChemWidget.ActionViewerRotateBase2D = Class.create(Kekule.ChemWidget.ActionViewerRotateBase,
  2477. /** @lends Kekule.ChemWidget.ActionViewerRotateBase2D# */
  2478. {
  2479. /** @private */
  2480. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateBase2D',
  2481. /** @private */
  2482. doUpdate: function($super)
  2483. {
  2484. $super();
  2485. var viewer = this.getViewer();
  2486. var flag = viewer && (viewer.getRenderType() === Kekule.Render.RendererType.R2D);
  2487. this.setDisplayed(/*this.getDisplayed() &&*/ flag).setEnabled(this.getEnabled() && flag);
  2488. }
  2489. });
  2490. /**
  2491. * Base action for make rotation in 3D viewer.
  2492. * @class
  2493. * @augments Kekule.ChemWidget.ActionViewerRotateBase
  2494. *
  2495. * @property {Float} delta The rotation angle.
  2496. */
  2497. Kekule.ChemWidget.ActionViewerRotateBase3D = Class.create(Kekule.ChemWidget.ActionViewerRotateBase,
  2498. /** @lends Kekule.ChemWidget.ActionViewerRotateBase3D# */
  2499. {
  2500. /** @private */
  2501. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateBase3D',
  2502. /** @private */
  2503. doUpdate: function($super)
  2504. {
  2505. $super();
  2506. var viewer = this.getViewer();
  2507. var flag = viewer && (viewer.getRenderType() === Kekule.Render.RendererType.R3D);
  2508. this.setDisplayed(/*this.getDisplayed() &&*/ flag).setEnabled(this.getEnabled() && flag);
  2509. }
  2510. });
  2511. /**
  2512. * Action for do anticlockwise rotation in 2D mode.
  2513. * @class
  2514. * @augments Kekule.ChemWidget.ActionViewerRotateBase2D
  2515. */
  2516. Kekule.ChemWidget.ActionViewerRotateLeft = Class.create(Kekule.ChemWidget.ActionViewerRotateBase2D,
  2517. /** @lends Kekule.ChemWidget.ActionViewerRotateLeft# */
  2518. {
  2519. /** @private */
  2520. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateLeft',
  2521. /** @private */
  2522. HTML_CLASSNAME: CCNS.ACTION_ROTATE_LEFT,
  2523. /** @constructs */
  2524. initialize: function($super, viewer)
  2525. {
  2526. $super(viewer, /*CWT.CAPTION_ROTATELEFT, CWT.HINT_ROTATELEFT*/Kekule.$L('ChemWidgetTexts.CAPTION_ROTATELEFT'), Kekule.$L('ChemWidgetTexts.HINT_ROTATELEFT'));
  2527. },
  2528. /** @private */
  2529. doExecute: function(target, htmlEvent)
  2530. {
  2531. var delta = this.getDelta();
  2532. this.getViewer().rotate2DBy(delta);
  2533. }
  2534. });
  2535. /**
  2536. * Action for do clockwise rotation in 2D mode.
  2537. * @class
  2538. * @augments Kekule.ChemWidget.ActionViewerRotateBase2D
  2539. */
  2540. Kekule.ChemWidget.ActionViewerRotateRight = Class.create(Kekule.ChemWidget.ActionViewerRotateBase2D,
  2541. /** @lends Kekule.ChemWidget.ActionViewerRotateRight# */
  2542. {
  2543. /** @private */
  2544. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateRight',
  2545. /** @private */
  2546. HTML_CLASSNAME: CCNS.ACTION_ROTATE_RIGHT,
  2547. /** @constructs */
  2548. initialize: function($super, viewer)
  2549. {
  2550. $super(viewer, /*CWT.CAPTION_ROTATERIGHT, CWT.HINT_ROTATERIGHT*/Kekule.$L('ChemWidgetTexts.CAPTION_ROTATERIGHT'), Kekule.$L('ChemWidgetTexts.HINT_ROTATERIGHT'));
  2551. },
  2552. /** @private */
  2553. doExecute: function(target, htmlEvent)
  2554. {
  2555. var delta = -this.getDelta();
  2556. this.getViewer().rotate2DBy(delta);
  2557. }
  2558. });
  2559. /**
  2560. * Action for do rotation around X axis in 3D mode.
  2561. * @class
  2562. * @augments Kekule.ChemWidget.ActionViewerRotateBase3D
  2563. */
  2564. Kekule.ChemWidget.ActionViewerRotateX = Class.create(Kekule.ChemWidget.ActionViewerRotateBase3D,
  2565. /** @lends Kekule.ChemWidget.ActionViewerRotateX# */
  2566. {
  2567. /** @private */
  2568. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateX',
  2569. /** @private */
  2570. HTML_CLASSNAME: CCNS.ACTION_ROTATE_X,
  2571. /** @constructs */
  2572. initialize: function($super, viewer)
  2573. {
  2574. $super(viewer, /*CWT.CAPTION_ROTATEX, CWT.HINT_ROTATEX*/Kekule.$L('ChemWidgetTexts.CAPTION_ROTATEX'), Kekule.$L('ChemWidgetTexts.HINT_ROTATEX'));
  2575. },
  2576. /** @private */
  2577. doExecute: function(target, htmlEvent)
  2578. {
  2579. var rev = this.isShiftModified(htmlEvent);
  2580. var delta = -this.getDelta();
  2581. if (rev)
  2582. delta = -delta;
  2583. this.getViewer().rotate3DBy(delta, 0, 0);
  2584. }
  2585. });
  2586. /**
  2587. * Action for do rotation around Y axis in 3D mode.
  2588. * @class
  2589. * @augments Kekule.ChemWidget.ActionViewerRotateBase3D
  2590. */
  2591. Kekule.ChemWidget.ActionViewerRotateY = Class.create(Kekule.ChemWidget.ActionViewerRotateBase3D,
  2592. /** @lends Kekule.ChemWidget.ActionViewerRotateY# */
  2593. {
  2594. /** @private */
  2595. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateY',
  2596. /** @private */
  2597. HTML_CLASSNAME: CCNS.ACTION_ROTATE_Y,
  2598. /** @constructs */
  2599. initialize: function($super, viewer)
  2600. {
  2601. $super(viewer, /*CWT.CAPTION_ROTATEY, CWT.HINT_ROTATEY*/Kekule.$L('ChemWidgetTexts.CAPTION_ROTATEY'), Kekule.$L('ChemWidgetTexts.HINT_ROTATEY'));
  2602. },
  2603. /** @private */
  2604. doExecute: function(target, htmlEvent)
  2605. {
  2606. var rev = this.isShiftModified(htmlEvent);
  2607. var delta = -this.getDelta();
  2608. if (rev)
  2609. delta = -delta;
  2610. this.getViewer().rotate3DBy(0, delta, 0);
  2611. }
  2612. });
  2613. /**
  2614. * Action for do rotation around Z axis in 3D mode.
  2615. * @class
  2616. * @augments Kekule.ChemWidget.ActionViewerRotateBase3D
  2617. */
  2618. Kekule.ChemWidget.ActionViewerRotateZ = Class.create(Kekule.ChemWidget.ActionViewerRotateBase3D,
  2619. /** @lends Kekule.ChemWidget.ActionViewerRotateZ# */
  2620. {
  2621. /** @private */
  2622. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerRotateZ',
  2623. /** @private */
  2624. HTML_CLASSNAME: CCNS.ACTION_ROTATE_Z,
  2625. /** @constructs */
  2626. initialize: function($super, viewer)
  2627. {
  2628. $super(viewer, /*CWT.CAPTION_ROTATEZ, CWT.HINT_ROTATEZ*/Kekule.$L('ChemWidgetTexts.CAPTION_ROTATEZ'), Kekule.$L('ChemWidgetTexts.HINT_ROTATEZ'));
  2629. },
  2630. /** @private */
  2631. doExecute: function(target, htmlEvent)
  2632. {
  2633. var rev = this.isShiftModified(htmlEvent);
  2634. var delta = -this.getDelta();
  2635. if (rev)
  2636. delta = -delta;
  2637. this.getViewer().rotate3DBy(0, 0, delta);
  2638. }
  2639. });
  2640. /**
  2641. * Action used for molecule display type stub button of compact button set in viewer.
  2642. * @class
  2643. * @augments Kekule.ChemWidget.ActionViewer
  2644. */
  2645. Kekule.ChemWidget.ActionViewerChangeMolDisplayTypeStub = Class.create(Kekule.ChemWidget.ActionOnDisplayer,
  2646. /** @lends Kekule.ChemWidget.ActionViewerChangeMolDisplayTypeStub# */
  2647. {
  2648. /** @private */
  2649. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerChangeMolDisplayTypeStub',
  2650. /** @constructs */
  2651. initialize: function($super, viewer)
  2652. {
  2653. $super(viewer, /*CWT.CAPTION_MOL_DISPLAY_TYPE, CWT.HINT_MOL_DISPLAY_TYPE*/Kekule.$L('ChemWidgetTexts.CAPTION_MOL_DISPLAY_TYPE'), Kekule.$L('ChemWidgetTexts.HINT_MOL_DISPLAY_TYPE'));
  2654. },
  2655. /** @private */
  2656. doExecute: function(target)
  2657. {
  2658. // do nothing
  2659. }
  2660. });
  2661. /**
  2662. * Action to edit object in viewer.
  2663. * @class
  2664. * @augments Kekule.ChemWidget.ActionOnViewer
  2665. *
  2666. * @property {Float} delta The rotation angle.
  2667. */
  2668. Kekule.ChemWidget.ActionViewerEdit = Class.create(Kekule.ChemWidget.ActionOnViewer,
  2669. /** @lends Kekule.ChemWidget.ActionViewerEdit# */
  2670. {
  2671. /** @private */
  2672. CLASS_NAME: 'Kekule.ChemWidget.ActionViewerEdit',
  2673. /** @private */
  2674. HTML_CLASSNAME: CCNS.ACTION_VIEWER_EDIT,
  2675. /** @constructs */
  2676. initialize: function($super, viewer)
  2677. {
  2678. $super(viewer, /*CWT.CAPTION_OPENEDITOR, CWT.HINT_OPENEDITOR*/Kekule.$L('ChemWidgetTexts.CAPTION_OPENEDITOR'), Kekule.$L('ChemWidgetTexts.HINT_OPENEDITOR'));
  2679. },
  2680. /** @private */
  2681. doUpdate: function($super)
  2682. {
  2683. $super();
  2684. var viewer = this.getViewer();
  2685. //this.setEnabled(this.getEnabled() && viewer.getChemObj() && viewer.getEnableEdit());
  2686. //this.setEnabled(this.getEnabled() && viewer.getAllowEditing());
  2687. this.setEnabled(viewer && viewer.getAllowEditing() && viewer.getEnabled());
  2688. this.setDisplayed(viewer && viewer.getEnableEdit());
  2689. },
  2690. /** @private */
  2691. doExecute: function(target)
  2692. {
  2693. var viewer = this.getViewer();
  2694. viewer.openEditor(target);
  2695. }
  2696. });
  2697. /** @private */
  2698. Kekule.ChemWidget.Viewer.rotate2DActionClasses = [
  2699. CW.ActionViewerRotateLeft,
  2700. CW.ActionViewerRotateRight
  2701. ];
  2702. /** @private */
  2703. Kekule.ChemWidget.Viewer.rotate3DActionClasses = [
  2704. CW.ActionViewerRotateX,
  2705. CW.ActionViewerRotateY,
  2706. CW.ActionViewerRotateZ
  2707. ];
  2708. /** @private */
  2709. Kekule.ChemWidget.Viewer.molDisplayType2DActionClasses = [
  2710. CW.ActionDisplayerChangeMolDisplayTypeSkeletal,
  2711. CW.ActionDisplayerChangeMolDisplayTypeCondensed
  2712. ];
  2713. /** @private */
  2714. Kekule.ChemWidget.Viewer.molDisplayType3DActionClasses = [
  2715. CW.ActionDisplayerChangeMolDisplayTypeWire,
  2716. CW.ActionDisplayerChangeMolDisplayTypeSticks,
  2717. CW.ActionDisplayerChangeMolDisplayTypeBallStick,
  2718. CW.ActionDisplayerChangeMolDisplayTypeSpaceFill
  2719. ];
  2720. // register actions to viewer widget
  2721. Kekule._registerAfterLoadSysProc(function(){
  2722. var AM = Kekule.ActionManager;
  2723. var CW = Kekule.ChemWidget;
  2724. var widgetClass = Kekule.ChemWidget.Viewer;
  2725. var reg = AM.registerNamedActionClass;
  2726. reg(BNS.loadFile, CW.ActionDisplayerLoadFile, widgetClass);
  2727. reg(BNS.loadData, CW.ActionDisplayerLoadData, widgetClass);
  2728. reg(BNS.saveData, CW.ActionDisplayerSaveFile, widgetClass);
  2729. reg(BNS.clearObjs, CW.ActionDisplayerClear, widgetClass);
  2730. reg(BNS.zoomIn, CW.ActionDisplayerZoomIn, widgetClass);
  2731. reg(BNS.zoomOut, CW.ActionDisplayerZoomOut, widgetClass);
  2732. reg(BNS.rotateLeft, CW.ActionViewerRotateLeft, widgetClass);
  2733. reg(BNS.rotateRight, CW.ActionViewerRotateRight, widgetClass);
  2734. reg(BNS.rotateX, CW.ActionViewerRotateX, widgetClass);
  2735. reg(BNS.rotateY, CW.ActionViewerRotateY, widgetClass);
  2736. reg(BNS.rotateZ, CW.ActionViewerRotateZ, widgetClass);
  2737. reg(BNS.reset, CW.ActionDisplayerReset, widgetClass);
  2738. reg(BNS.molHideHydrogens, CW.ActionDisplayerHideHydrogens, widgetClass);
  2739. reg(BNS.molDisplayType, CW.ActionViewerChangeMolDisplayTypeStub, widgetClass);
  2740. reg(BNS.openEditor, CW.ActionViewerEdit, widgetClass);
  2741. reg(BNS.config, Kekule.Widget.ActionOpenConfigWidget, widgetClass);
  2742. reg(BNS.molDisplayTypeCondensed, CW.ActionDisplayerChangeMolDisplayTypeCondensed, widgetClass);
  2743. reg(BNS.molDisplayTypeSkeletal, CW.ActionDisplayerChangeMolDisplayTypeSkeletal, widgetClass);
  2744. reg(BNS.molDisplayTypeWire, CW.ActionDisplayerChangeMolDisplayTypeWire, widgetClass);
  2745. reg(BNS.molDisplayTypeSticks, CW.ActionDisplayerChangeMolDisplayTypeSticks, widgetClass);
  2746. reg(BNS.molDisplayTypeBallStick, CW.ActionDisplayerChangeMolDisplayTypeBallStick, widgetClass);
  2747. reg(BNS.molDisplayTypeSpaceFill, CW.ActionDisplayerChangeMolDisplayTypeSpaceFill, widgetClass);
  2748. });
  2749. })();