diff --git a/examples/.eslintrc.yml b/examples/.eslintrc.yml
new file mode 100644
index 00000000..a481cc7b
--- /dev/null
+++ b/examples/.eslintrc.yml
@@ -0,0 +1,4 @@
+parserOptions:
+ sourceType: module
+rules:
+ no-undef: off
diff --git a/examples/GenericEditor.html b/examples/GenericEditor.html
new file mode 100644
index 00000000..94a593b0
--- /dev/null
+++ b/examples/GenericEditor.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Change count
+
+
ID Code
+
+
Molfile
+
+
+
+
diff --git a/examples/generic-editor.js b/examples/generic-editor.js
new file mode 100644
index 00000000..f4f80bbc
--- /dev/null
+++ b/examples/generic-editor.js
@@ -0,0 +1,206 @@
+import { CanvasEditor } from './generic-editor/index.js';
+
+const { Reaction, Molecule, EditorArea } = OCL;
+
+const rxn = `$RXN
+
+
+JME Molecular Editor
+ 2 3
+$MOL
+C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC
+JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015
+
+ 6 6 0 0 0 0 0 0 0 0999 V2000
+ 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1 2 1 0 0 0 0
+ 2 3 2 0 0 0 0
+ 3 4 1 0 0 0 0
+ 4 5 2 0 0 0 0
+ 5 6 1 0 0 0 0
+ 6 1 2 0 0 0 0
+M END
+$MOL
+C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC
+JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015
+
+ 2 1 0 0 0 0 0 0 0 0999 V2000
+ 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1 2 1 0 0 0 0
+M END
+$MOL
+C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC
+JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015
+
+ 6 6 0 0 0 0 0 0 0 0999 V2000
+ 2.4249 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 2.4249 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 2.8000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1 2 1 0 0 0 0
+ 2 3 2 0 0 0 0
+ 3 4 1 0 0 0 0
+ 4 5 2 0 0 0 0
+ 5 6 1 0 0 0 0
+ 6 1 2 0 0 0 0
+M END
+$MOL
+C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC
+JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015
+
+ 3 2 0 0 0 0 0 0 0 0999 V2000
+ 0.0000 2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 1.4000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1 2 1 0 0 0 0
+ 2 3 1 0 0 0 0
+M END
+$MOL
+C1=CC=CC=C1.CC>N>C1=CC=CC=C1.CCC.CC
+JME 2015-09-20 Mon Sep 28 16:02:56 GMT+200 2015
+
+ 2 1 0 0 0 0 0 0 0 0999 V2000
+ 0.0000 0.7000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.2124 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 1 2 1 0 0 0 0
+M END`;
+
+const molfile = `446220
+-OEChem-02022300542D
+
+ 43 45 0 1 0 0 0 0 0999 V2000
+ 5.5851 -0.3586 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.5407 -2.1051 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
+ 8.2717 -2.0446 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
+ 4.9927 1.2690 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
+ 8.0971 2.2384 0.0000 N 0 0 3 0 0 0 0 0 0 0 0 0
+ 8.5749 0.2899 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0
+ 8.8337 1.2558 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0
+ 7.3538 -0.5758 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0
+ 9.9738 -0.2189 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 10.2326 0.7470 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.8004 0.9447 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.5248 -0.0166 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0
+ 7.5971 3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 7.3888 -1.5752 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 4.8191 0.2842 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.5756 -3.1045 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 3.8794 -0.0578 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 3.7057 -1.0426 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 3.1133 0.5850 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 2.7660 -1.3846 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 2.1736 0.2429 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 2.0000 -0.7419 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
+ 8.3549 -0.5311 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 9.0537 2.0769 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.8116 -0.8763 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 9.7789 -0.8075 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 10.5678 -0.3967 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 10.8266 0.5692 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 10.4276 1.3356 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.7954 1.5647 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.1861 1.0285 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.4493 -0.6319 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 8.1340 3.4145 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 7.2871 3.6414 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 7.0601 2.7945 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 5.9560 -3.1261 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 6.5972 -3.7241 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 7.1952 -3.0828 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 4.1807 -1.4412 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 3.2210 1.1955 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 2.6584 -1.9952 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.6987 0.6415 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 1.4174 -0.9539 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
+ 12 1 1 1 0 0 0
+ 1 15 1 0 0 0 0
+ 2 14 1 0 0 0 0
+ 2 16 1 0 0 0 0
+ 3 14 2 0 0 0 0
+ 4 15 2 0 0 0 0
+ 5 6 1 0 0 0 0
+ 5 7 1 0 0 0 0
+ 5 13 1 0 0 0 0
+ 6 8 1 0 0 0 0
+ 6 9 1 0 0 0 0
+ 6 23 1 1 0 0 0
+ 7 10 1 0 0 0 0
+ 7 11 1 0 0 0 0
+ 7 24 1 6 0 0 0
+ 8 12 1 0 0 0 0
+ 8 14 1 1 0 0 0
+ 8 25 1 0 0 0 0
+ 9 10 1 0 0 0 0
+ 9 26 1 0 0 0 0
+ 9 27 1 0 0 0 0
+ 10 28 1 0 0 0 0
+ 10 29 1 0 0 0 0
+ 11 12 1 0 0 0 0
+ 11 30 1 0 0 0 0
+ 11 31 1 0 0 0 0
+ 12 32 1 0 0 0 0
+ 13 33 1 0 0 0 0
+ 13 34 1 0 0 0 0
+ 13 35 1 0 0 0 0
+ 15 17 1 0 0 0 0
+ 16 36 1 0 0 0 0
+ 16 37 1 0 0 0 0
+ 16 38 1 0 0 0 0
+ 17 18 2 0 0 0 0
+ 17 19 1 0 0 0 0
+ 18 20 1 0 0 0 0
+ 18 39 1 0 0 0 0
+ 19 21 2 0 0 0 0
+ 19 40 1 0 0 0 0
+ 20 22 2 0 0 0 0
+ 20 41 1 0 0 0 0
+ 21 22 1 0 0 0 0
+ 21 42 1 0 0 0 0
+ 22 43 1 0 0 0 0
+M END`;
+
+const changeCountDiv = document.getElementById('changeCount');
+const idcodeDiv = document.getElementById('idcode');
+const molfileDiv = document.getElementById('molfile');
+let changeCount = 0;
+
+const editor = new CanvasEditor(document.getElementById('editor'), {
+ onChange({ what, isUserEvent }) {
+ if (isUserEvent && what === EditorArea.EDITOR_EVENT_MOLECULE_CHANGED) {
+ changeCountDiv.innerText = ++changeCount;
+ const idcodeAndCoords = editor.getMolecule().getIDCodeAndCoordinates();
+ idcodeDiv.innerText = `${idcodeAndCoords.idCode} ${idcodeAndCoords.coordinates}`;
+ const molfile = editor.getMolecule().toMolfileV3();
+ molfileDiv.innerText = molfile;
+ }
+ },
+});
+
+document.getElementById('loadMolecule').onclick = () => {
+ // const molecule = Molecule.fromMolfile(molfile);
+ const molecule = Molecule.fromSmiles('c1ccccc1CO');
+ editor.setMolecule(molecule);
+};
+
+document.getElementById('loadFragment').onclick = () => {
+ // const molecule = Molecule.fromMolfile(molfile);
+ const molecule = Molecule.fromSmiles('CCC');
+ molecule.setFragment(true);
+ editor.setMolecule(molecule);
+};
+
+document.getElementById('loadReaction').onclick = () => {
+ const reaction = Reaction.fromSmiles('c1ccccc1..CC>CO>c1ccccc1..CCC..CC');
+ // const reaction = Reaction.fromRxn(rxn);
+ // reaction.addCatalyst(Molecule.fromSmiles('CO'));
+ console.log(reaction.toSmiles());
+ editor.setReaction(reaction);
+};
diff --git a/examples/generic-editor/CanvasClipboardHandler.js b/examples/generic-editor/CanvasClipboardHandler.js
new file mode 100644
index 00000000..ad8ec6f6
--- /dev/null
+++ b/examples/generic-editor/CanvasClipboardHandler.js
@@ -0,0 +1,11 @@
+export class CanvasClipboardHandler {
+ copyMolecule(molecule) {
+ const data = molecule.getIDCodeAndCoordinates();
+ navigator.clipboard.writeText(`${data.idCode} ${data.coordinates}`);
+ }
+
+ pasteMolecule() {
+ // TODO: find a way to implement this in a synchronous way.
+ return null;
+ }
+}
diff --git a/examples/generic-editor/CanvasDrawContext.js b/examples/generic-editor/CanvasDrawContext.js
new file mode 100644
index 00000000..7d3fa143
--- /dev/null
+++ b/examples/generic-editor/CanvasDrawContext.js
@@ -0,0 +1,142 @@
+import { toHex } from './utils.js';
+
+export class CanvasDrawContext {
+ /**
+ *
+ * @param {CanvasRenderingContext2D} ctx
+ */
+ constructor(ctx) {
+ this.ctx = ctx;
+
+ this.ctx.textAlign = 'left';
+ this.ctx.textBaseline = 'top';
+
+ this.currentFontSize = 12;
+ this.currentFont = '12px sans-serif';
+ this.ctx.font = this.currentFont;
+
+ this.currentColor = '#000000';
+ this.currentLineWidth = 1;
+ }
+
+ clearRect(x, y, w, h) {
+ this.ctx.clearRect(x, y, w, h);
+ }
+
+ getBackgroundRGB() {
+ return 0xffffff;
+ }
+
+ getForegroundRGB() {
+ return 0x000000;
+ }
+
+ getSelectionBackgroundRGB() {
+ return 0xeb8282;
+ }
+
+ getLineWidth() {
+ return this.currentLineWidth;
+ }
+
+ setRGB(rgb) {
+ const r = (rgb >>> 16) & 0xff;
+ const g = (rgb >>> 8) & 0xff;
+ const b = (rgb >>> 0) & 0xff;
+ this.currentColor = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+ this.ctx.fillStyle = this.currentColor;
+ this.ctx.strokeStyle = this.currentColor;
+ }
+
+ setFont(size, isBold, isItalic) {
+ this.currentFontSize = size;
+ this.currentFont = `${isBold ? 'bold' : ''} ${
+ isItalic ? 'italic' : ''
+ } ${size}px sans-serif`;
+ this.ctx.font = this.currentFont;
+ }
+
+ getFontSize() {
+ return this.currentFontSize;
+ }
+
+ getBounds(s, rect) {
+ const metrics = this.ctx.measureText(s);
+ rect.set(
+ metrics.actualBoundingBoxLeft,
+ metrics.actualBoundingBoxAscent,
+ metrics.actualBoundingBoxRight,
+ metrics.actualBoundingBoxAscent,
+ );
+ }
+
+ drawString(x, y, s) {
+ this.ctx.fillText(s, x, y);
+ }
+
+ drawCenteredString(x, y, s) {
+ this.ctx.textAlign = 'center';
+ this.ctx.textBaseline = 'middle';
+ this.ctx.fillText(s, x, y);
+ this.ctx.textAlign = 'left';
+ this.ctx.textBaseline = 'top';
+ }
+
+ setLineWidth(lineWidth) {
+ this.currentLineWidth = lineWidth;
+ this.ctx.lineWidth = lineWidth;
+ }
+
+ fillRectangle(x, y, w, h) {
+ this.ctx.fillRect(x, y, w, h);
+ }
+
+ fillCircle(x, y, d) {
+ const r = d / 2;
+ this.ctx.beginPath();
+ this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
+ this.ctx.fill();
+ }
+
+ drawLine(x1, y1, x2, y2) {
+ this.ctx.beginPath();
+ this.ctx.moveTo(x1, y1);
+ this.ctx.lineTo(x2, y2);
+ this.ctx.stroke();
+ }
+
+ drawPolygon(p) {
+ this.ctx.beginPath();
+ this.ctx.moveTo(p.getX(0), p.getY(0));
+ for (let i = 1; i < p.getSize(); i++) {
+ this.ctx.lineTo(p.getX(i), p.getY(i));
+ }
+ this.ctx.stroke();
+ }
+
+ fillPolygon(p) {
+ this.ctx.beginPath();
+ this.ctx.moveTo(p.getX(0), p.getY(0));
+ for (let i = 1; i < p.getSize(); i++) {
+ this.ctx.lineTo(p.getX(i), p.getY(i));
+ }
+ this.ctx.fill();
+ }
+
+ drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
+ if (arguments.length !== 9) {
+ throw new Error(
+ `drawImage call with ${arguments.length} arguments unimplemented`,
+ );
+ }
+ const fullScaleCanvas = document.createElement('canvas');
+ /** @type {ImageData} */
+ const imageData = image.imageData;
+ fullScaleCanvas.width = imageData.width;
+ fullScaleCanvas.height = imageData.height;
+ const fullScaleContext = fullScaleCanvas.getContext('2d');
+ fullScaleContext.globalAlpha = 0;
+ fullScaleContext.putImageData(imageData, 0, 0);
+ this.ctx.drawImage(fullScaleCanvas, sx, sy, sw, sh, dx, dy, dw, dh);
+ }
+}
diff --git a/examples/generic-editor/CanvasEditor.js b/examples/generic-editor/CanvasEditor.js
new file mode 100644
index 00000000..a3813008
--- /dev/null
+++ b/examples/generic-editor/CanvasEditor.js
@@ -0,0 +1,73 @@
+import { CanvasEditorArea } from './CanvasEditorArea.js';
+import { CanvasToolbar } from './CanvasToolbar.js';
+import { addMouseListeners, addKeyboardListeners } from './events.js';
+
+const { EditorArea } = OCL;
+
+export class CanvasEditor {
+ /**
+ *
+ * @param {HTMLElement} rootElement
+ */
+ constructor(rootElement, options = {}) {
+ const { onChange } = options;
+
+ const childElement = document.createElement('div');
+ childElement.setAttribute(
+ 'style',
+ 'width: 100%; height: 100%; display: flex; flex-direction: row; align-items: start; background-color: white;',
+ );
+ rootElement.appendChild(childElement);
+
+ const toolbarCanvas = document.createElement('canvas');
+ childElement.appendChild(toolbarCanvas);
+
+ const editorContainer = document.createElement('div');
+ editorContainer.setAttribute('style', 'width: 100%; height: 100%;');
+ childElement.appendChild(editorContainer);
+
+ const editorCanvas = document.createElement('canvas');
+ editorCanvas.tabIndex = '0';
+ editorCanvas.style = 'outline: none';
+ editorContainer.appendChild(editorCanvas);
+
+ const containerSize = editorContainer.getBoundingClientRect();
+ editorCanvas.width = containerSize.width;
+ editorCanvas.height = containerSize.height;
+
+ const editorArea = new EditorArea(
+ new CanvasEditorArea(editorCanvas, onChange),
+ );
+ this.editorArea = editorArea;
+
+ editorArea.draw();
+
+ this.toolbar = new CanvasToolbar(toolbarCanvas, editorArea);
+
+ const resizeObserver = new ResizeObserver(([entry]) => {
+ editorCanvas.width = entry.contentRect.width;
+ editorCanvas.height = entry.contentRect.height;
+ editorArea.repaint();
+ });
+ resizeObserver.observe(editorContainer);
+
+ addMouseListeners(editorCanvas, editorArea);
+ addKeyboardListeners(editorCanvas, editorArea);
+ }
+
+ getMolecule() {
+ return this.editorArea.getMolecule();
+ }
+
+ setMolecule(molecule) {
+ this.editorArea.setMolecule(molecule);
+ }
+
+ getReaction() {
+ return this.editorArea.getReaction();
+ }
+
+ setReaction(reaction) {
+ this.editorArea.setReaction(reaction);
+ }
+}
diff --git a/examples/generic-editor/CanvasEditorArea.js b/examples/generic-editor/CanvasEditorArea.js
new file mode 100644
index 00000000..e6cb28db
--- /dev/null
+++ b/examples/generic-editor/CanvasEditorArea.js
@@ -0,0 +1,97 @@
+import { CanvasClipboardHandler } from './CanvasClipboardHandler.js';
+import { CanvasDrawContext } from './CanvasDrawContext.js';
+import { CanvasEditorDialog } from './CanvasEditorDialog.js';
+import { CanvasEditorImage } from './CanvasEditorImage.js';
+import { decodeBase64 } from './utils.js';
+
+const { EditorArea } = OCL;
+
+export class CanvasEditorArea {
+ constructor(canvasElement, onChange) {
+ this.canvasElement = canvasElement;
+ this.changeListener = onChange;
+ }
+ // JSEditorArea methods
+ getBackgroundRGB() {
+ return 0xffffff;
+ }
+ getCanvasWidth() {
+ return this.canvasElement.width;
+ }
+ getCanvasHeight() {
+ return this.canvasElement.height;
+ }
+ getDrawContext() {
+ return new CanvasDrawContext(this.canvasElement.getContext('2d'));
+ }
+ onChange(what, isUserEvent) {
+ this.changeListener?.({ what, isUserEvent });
+ }
+ getClipboardHandler() {
+ return new CanvasClipboardHandler();
+ }
+ // JSUIHelper methods
+ grabFocus() {
+ this.canvasElement.focus();
+ }
+ setCursor(cursor) {
+ this.canvasElement.style.cursor = getCursor(cursor);
+ }
+ showHelpDialog(url, title) {
+ console.log({ url, title });
+ alert('Help dialog is not implemented yet');
+ }
+ createImageFromBase64(width, height, base64) {
+ base64 = base64.replaceAll('%', 'A'.repeat(20));
+ const decoded = decodeBase64(base64);
+ const typedArray = new Uint8ClampedArray(decoded);
+ const imageData = new ImageData(typedArray, width, height);
+ return new CanvasEditorImage(imageData);
+ }
+ createDialog(title) {
+ return new CanvasEditorDialog(title);
+ }
+}
+
+const customCursors = {
+ [EditorArea.cChainCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACOElEQVRIx+3VzUsUcRzH8c/MOLszdlFwY8H6E6JjIARFBlGt7KEsbelgj65ZayJYf0GtERSVFnQOOnTp1j9gRSIlBh7sEhn5uLpbu+3Tu8Os7qqrYpc6+P0df5/vi/k9zIy0U/++DMmQZInNIhsUkmokU0XVbAgYm8CoKHPLB9yiqgKnlXEyDRljs/a0/ashbZ2RJEuGHPlXRkT4f9x+NZwOYziqNoLCmu55OZw5h1kFwJnpvzonHk1kwlQFsGZjIk58hOZ1AO5MX+esEOLxeLYKgT3fLYTF0HsOrgIiwpnrjU6L3QgLmycfcy2rF4K90OXx90YKpzAqgIjwJ25Gv4sAItUvLMTgaJkICmsx6rXHR/NtWAcqNxFnqadzStQhikfxL13wokMf8iFKa091CmFy51O+Heu8fJKU8nJuIhb9JnYh8scwJHypbmEgnr4rhhB26ooQdTwfyZ3Fkhz5JEQgeXFy4MuDrq+elG3BbJQjCV/ymhA2z4YLJ5OXhahlcDR3AkNyS0D9xP2OxXoCCBtRCGFely3HI5zEDY998VYIPw/HsmHMZrkrwOFbU15EiEILkvyVu+4uxMrzA59/t2JeklsBhJrS5UAT5ppTd4WT6PVm747l27FUal8GjsRK18ZC0GivaXcVEf75PhEf/92GWW5fBgJjQ+FMLUGCJDswnXWAq4hwfh7PHsJy5a4BDKQ9c62T+/25fa/NN7Ppvdt4nVHRS+Alcbf5LSgB3nX+u2Lnp/Bf1B//ZTKHt1UOnwAAAABJRU5ErkJggg==)',
+ [EditorArea.cDeleteCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADc0lEQVRIx42VW29UVRiGn7XWbr0QRhM81GCwHDxEuDDRUShUagy1ETTqBdNy4eEXaADDpbbwF6AmxhvTGJWku9paewCbdGYqJRhqG4WUBE12gyad0VjnQOewPy/26sy0M9Nxrbu11vt87/vtw1JCkiyNxqNg2Mo/S7JxRyVYJIlGodBoNKBRGLsSrD5rMpHll3Z8TvSmXQEHjcbJkuQODgqNwWBQGDQOGoWDwqFDpU64Zz/bfu6RQ+rJ6V/RtoQADmgce1DbLQeNCfi045hUz1DfycdytB+NykHZF1sUB0UTCo0DatPpmFS3e/Zka44iIdqPxaSNx6O/l/dBY2wEjcZY4waDpkOle9y+M605VhAUIQ69Fpc21TrtWYcOFQ0zFhCAnqZFp0+4fad2rrKCgEUcfD3OAdkR+1MCRCm5tog1Ly0m3e32frAzb+WsR/Bg9O9yBFMybWwTD+t0t9v34a5KeSVixj+gtk2ncGBjGzVbCOtMz1Dv6V3ZDfIyou2NGdkvW2PWQXPJuMMWwiYTcT9+f3e+hryM6Hpz3N+f1qyrrVGEdSYy2HtqTz352niAlYcw9ino8oPT2W73o9N7spvIFSFW+GKu4xzX8IgywhjjXOYPxGR6Bha3SUiQegMJCTJyPfdKCh884owywSRTFFntGrjx8P+S3+1MU0TK34LmsOI5eeLTp+5CA/Mj8y+fuWfSLnlcYZI8YlYjE5dn3b/OIyEJbVb959VOoYAgCHhcZQoxueODC0ooxMaS55F7ayDW5NnOIr6VC3hcQ1Qu4s7hK1FCIT6WqOEiOD88nzkiqliSCwhicpGh60rWJoX498kL6xFB9eH5bKdQKRcQkzvuVsgtYjTZXw4SyL9ZyB7zK8yvAV4cjgbm1yNmRpf7Axf3S0iQbxfSR0QLUgXYG71UDVBCYea7RD9yn4QEGfwlc7SWXECa/Le/nlGihGrESOITAvmrteVBE5uL71yMVXtQQv7H4cSF2YlUVz25gIcgTfn3LsZrB5n7UvaJXqIewIFF8vm9A2/5X+nIC0pVvbuGf31/e/2v2uMWN2yQoBcV9f3ZHwphv251G+EWN7ltg1QiKF6ZyocFaQT4jdt4LHEHaSq8Ox4nQYIEy7OXCs8naARQHj5iL5YWaOYZdtt0i/zU8NpGeQAVv/UQqvTr1I31/AcQPMokc/81uwAAAABJRU5ErkJggg==)',
+ [EditorArea.cHandCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACXUlEQVRIx6WVT2sTURTFfzOTtNESGy1ScS3uhFTERSHa2k0WdeOHcKmiFvwKIoL7foDiphvBdtF/WhRqhXTlzlWhWGjF2oJNM+8dFzOTzCQvE8H7FvPmzrvn3TPn3fvgP83L/arB6/xe15NU+DzzPIjmSsENNCVjX1ZLQqgeeV79QwYIatQAKCKaQJ33LADc7F5c6I0f5zrr+NxlEwOUYn/VmawD4JgRhGUE8PHb1MsAF5nCB9ZyANwyQAgwwXpWFd+tbLKvSfmLwBRvo7+zmgMQhQbtoMRaQIn7LA5SoZhym1RexRi4NAjAZLir6xAGXQczARhlBqE+WqWoNbs2SAA2WBmlBB/7Qygm6DkpVKf5xCJXyjSiJC33eIjFowC8aFPx+9SVHqulE70W8lSXlXSiQ1m1dKYDNWW0J1SXdCTk98q4TUiJWS4hQFiGqeDhETBGgMdlfrBAiAfYHgpzX/iMzxjTMV+Lj48hwAd8DIcMUyFAGfGSWSNkC0OZ27E7wGuLBh4Fxqk4lE/evrKzxiEFbgBw1rd7KP7FvbaOlmV1og/6JslKMuqYbT9/C3VqoWMTaFanasmmlncDWEk/hQJHMTVoLLNFQOhM38uU9ZCzFp6GrPKHIl5OszYY4IITYIO1JQ5o5VSDxRIC+/2qcZt3FJwUhLB4BLwBmOm7yTXtymWRKkZGtcwN0d0P5vZYzDSydAZRk4VzOV20yq9JfXfIaNSUZLSrqwPuqBX0TEfxIWplIEKdqqYh8Tz/cm0MVV9yixIGk+JoOc8jNuGYO+zk5VCm0bkdHSPTs/4Cu8J6qPZw7lkAAAAASUVORK5CYII=)',
+ [EditorArea.cHandPlusCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACaUlEQVRIx6WVP2sUQRjGf7N7l1wMZ6JBItZiJ1xELITTxDRXxMYPYamiBvwKIoJ9PkCwSSOYFPlrUNCIl8rOKhCMJGJMwNzdzjwWu3vZvdvbE3yn2Jl3Z555n3nmfQf+00zuX/Wf53W7HiWWzzHHvbCvBFxfU9z25LQohGqh58U/RICgShWAIqIB1HjLPMC1zsmF7vXjXGENj9tsYoFS5K9kBpsBcMQwwjEMeHht6mWAc0ziAas5ANkyQAAwwVpaFS9b2Xhfm/AXgUleh6ezkgMQLvXbi2JrASXustBPhWLCbRNxFSPgUj8Am+Kujkvod1zMGGCEaYR6aJWg1ujYIAZYZ3mEErzrDaGIoMmkUJniPQtcLFMPg3Tc4T4OQwF41qbi9cgrPVRLx3opZFSTk3SsAzm11NS+GrLaFapJOhTyumXcIqDEDOcRIByDjGIw+IzhY7jAd+YJMIDrojD7iQ94jDEV8XV4eFgMHuBhOWCQUXyUEi/u1QM+YilzI3L7GMCPvoYC44xmKB+PPrO9ygEFrgLQ7Fk9FB1xt62hJTkda0NfJTlJVhJCisbh97fQaS6c2gSa0YlaconpnQBO0k8hfyUrui8FbcipKScnMloI8ENoKDMbHwes8IciJqdYWyxwNjOZ1lldZJ9Wqq7GfYtDOBwBsNcrG7d4QyFTgfD6GHxeAUz3zJjL2lHSkodoZWVVTb0QnfVgdpeFVCFLPhVhkYWhnCpa4ddNfUvIGJtVQ5LVji71eaOW0RMdykqSWimIQCeqakA8zX9c6wOV51ynhMUmODrO8IBNOOIW23kxlKmjnJaqWX8BrBWTpFVweJUAAAAASUVORK5CYII=)',
+ [EditorArea.cFistCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAACC0lEQVRIx+2UsWsTYRjGf3eXFktqS20pUqQVdQgUJCkKDi4mckJRKIj/guDgoGRzFzWQRZycnKSTe42zoNhkKAGXKtSlimItanK9+x6HXtKL3ncER/F5h+Pu+77ne9/nfZ+D//gH4GSslahjgMrfEih+NrmNC3yhNVxWL+LQQ71TXdNiP75RHOr4lHytyNW43ipUSyNCd3VHE6I6JMGGNuVrTpsK1daofO3qh5bVLyoB97f3MmWYZx4QDuARAHlynMYhLqZqJ4iVdQHh4hL0vwWIAivMQO1gby69jogqG7g4TFFnEUOOEeABV7jEGpkEZ3FwqHABgDlu4uASscsMpxCQI0wlKB/jw9J+XV6sQYSHBxg8bnGNk0SAlyA4QA3lNa5R+fqpdBiFknzllSZi6RyPuE4AmFRdTNyXhYFu9giKnClwmRsct863E3dneWC9p0GLN9vliKPcZ9rSmt6xxQEFEnvX+USBq4CX6bCAbuokrm/TwLXNVjwdAr7byYvakh1GoYxCPRvwRPK6Sos1ummOiTXwAJf3WWZa5SvGQqF4uNocsRZRQ48VWUqIZBTqs0qyJgloQW3tWTQwMnqpsUw7XzzEUyI6RAhhEiEMe7xiFpr2DCZpzuq5pK6MOv17jYxCRdrSeZ1oMmm38w6Nj8V7BEwwRgcn4QsPeMJrug12sv+KVWSLw38I+Auc4F9yeHFVDQAAAABJRU5ErkJggg==)',
+ [EditorArea.cLassoCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAEfklEQVRIx3WVfUzVZRTHP/fKSz8GaN6WV7GbKOpQYANUXDqV5WJO6c5S/miyzJmlrAicbm3gWtisbC5f2npZw2qNJNORC3xbqFOuQsAEkkAlXvK+IAiXd+7l/k5//C7cC17O88/znOec73l5zjmPDkLR46OhaJZiwuie2fOcJwgdIDPGnu0KdmKnnaawf32yKqPoQqnkeQCMM0hmDYbWuLrEGmNxSKMfbCwZriR7Qu2CBrq5RbXdA9DJKlCwIQgyX7I8RyxVuz3aMfDa7bFUeY5IlswXBBsKOoUWjLCKTY/ivzR/EeSzmox51DAC3c+UhFb7ebN/LKckqp4yKu0s9AK8RNqd9K2JNi0U8vtT6pbWh3fjwgbMJWTA0BR/J+GriHsAzOV8bcoFLtkrFqJTGFpOxq3X1sZp+Ke7zSWzOrjPPR7k92u8gghiWMai3hdLzDsNGu9mw5pzFIf9rROF9xs2xa8H2CZH/1hQQx3l9ifQQhn+VDCbVBJakw5sPqsDqL8eV8YJJHX4uFlFkEz1SaEclOhIFBQUigNlMVoOPinMVBHErA4fl1Qk+1IzgqwUR5HsF0MJVd7VjiA4OePdeZdB9juKVgqCXGqWbKRg7xiClD6UPDF+wqdTbNpQsDDqzzNKXulDBNk7JofpKtS4g6dk48t04AoAkMyOyRAbB09pm65CvSsEYIeE9WB5h0iCeZqqaaSGDh/DEtbzlgrgCvG2gQcgYjuRBCYjGVh8xyAYrzi9MgBQpLMvIg3dNPpkcp1qDtFNN/mwrm/Od3oAZUg/y5HnBih6VRaTNh2AnmhycPGIxxQsIfniFq2oZ9mRd2tuagkpaZOP5RUJkES8NeFC9LJC8psuzxUEKW+QrCAsifOPJh1QwGz6PTNdRzhtXrNO/mPE34/gcMwSW5W2b4UN2KWuLeOuTmFoZ39K9p5CPcBPXVsuWKP6w8DQ+0JLqBMrFWF1wxSzPZhEUgdNP2RmRWgNdeNizBVOoiCRktt9+u0AcyBvtPaGFMhWiZT1kuv+/PZfWtEj29X7pfKRLFa8A8UgOdbiwCPk58fqETnc983Ve7smTBwbcH4rByXGhjIxkdIv/+Ov9ovjdmVh7zpBkLWTAPeN1V+TAnlTZnoTPA5wxoogjt9KW6ME2aZaz0p+Rd1kb3Ldlir3Z5Itcb4XmhhhehXg2rqUimL3mpizutbXP1x9zajdLeONkdUty+uMD3BQSS1uv6fxerDZVrQiYA72eNp/dX4th2S3rJd5AWrECzBP8tvOfTA2VX2j9HwvOWKSmYHQ/UOw8qOp91hjTuzdBGe4MxSaw08EwVVcoXTQPu7vHVZO+oZ8TQVtnNKZTEtM4SiAYWRR/3uFeijdtLOZcAY0oaQp6vh9LAhCOeXaTpHcu9cRJFYGT0q0T0JFnRTCFMANbNA2w1yJv5Uu0IjDRJRPopNW+viTsYk+nY4adH0ZjwFao/0CZTYGYDkzxnOg0hkYQIxNYUMA7iCwP3XtADpRCRpl1TRuDI1sPm+dA7MdsDCghMoo/wN5WLFDBuWEyAAAAABJRU5ErkJggg==)',
+ [EditorArea.cLassoPlusCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAEk0lEQVRIx42Vf0zUZRzHX3ec0JcAyzM5hS5B01BggT+HTmJzmineNLWtxTJn2mSFwLS5iZ6dy5WNqdXsxxpqcxSZSk6Z5lJbyq+U8UMIdIQ47wd2yi8ROO776Y8vB3eEW8/zx/M8n+fzeX9+Pp9HByHoGR49MUzHjMkz9uF4rwEdIEEDz/4zpgMnrTSG/j3Mq9KHLoQKJgBgCmIWCzC2xNck3TAVBTf4wcaxrj/ZmVg1uQ43V7nu9AK0MRcUHAiCREumd19p5Uavdhx9bvSWVnr3SaZEC4IDBZ1CMyaYy7J7CQcsnxmGtc7C0mfsBfdTxSHX/azJHcgujqqlhAonsYMAKSwtT1+V5NBcIa9rXs302jA3/TiAiQR3GxsTyhO/DK8HYCKnquad4bzzWiw6hZ6ZrLu6emG8hn/EbSl+5i63qOd2XpdGs4UzlRlMaX+h2LLeqNH+qFtwkqLQmzpR+KBuWUIqwBrZf3byDWq45HwAzaRYNeY8K4BtHGkktiRvW35CB1B7Jb6EQ0ja44MWFUEy1AcFsl1iIlBQUChCrAgiVr8oxsj2BwUZKoJY1McHJQ3JOt+EIHPEVSi5YiymcnC2DgG0+ifCKLmuwjmCIOebJMvA+NOxAHuaJ9zk+MfuIFYyctwjkmDfwc3xCcqe5tdi4XTskucM7ujDQQCpJZQtdh4hEsAaCDDfd9ZWJ2WpJjLhcJAtytAfDPCWhD6kdDMRjAFAt3tY3LeXPUOk0tDUd9QCPfQHDxaOFyB87SP+3zCAr+IMSjdAoS5/imkpx5BAbbrd/pp3kQUcwLaoM/JbPYDSg3y0sx9B8rvVvfLqiNIfzAIUITj4kGoakGli/dGOILkesemxr64AyHn6TAYpLHmy3SZsJOpfms0bTSlbJwKsaMRpoDQpen/yNgUs5l8y0nWEcWdQogNDIMSYMCwSV7l0y2wHsEFdWEK1IbS6J2nz0fpNBXpYOfn791acsUd1hYKx/fnmkJflLHd90iSR9sh8NCMzXHtQOy4YHJSjIBGS4z7y7ih9YGdf1e9ik1USIamS4/m07E+t6JG16q1zYpUXlcGGYpRse9HoLeT4fXWf7O38+mL9hiEV+d0d38h2mepAGepI6Rf+8hf7wVVWUdC+SBBkYQDgloHay2KTt2Ws1pGGALTEuH4+1xIlyBrVfkLyrtUEWpPjKa30fCJZEq8RHCjDcdarAJcXzbtW5Fkw9YSu5fUd8y+btLsZvNk7v3lmjek2LiqowuOXmkELljsKZ48ag03e1p86vpJdslFSZVLgpb8LkyTvzsmtAyPFF8vD7yRbzDJ2NHR/F+wcM7fnN2THVSd2hHWEQFPYIQNcpD+Eu7T67C1nTsA3hF+t3eELndk8zRyGAhh7p3S9X6CHc8vWNxFGt8aUPEIcv48FQbjEJW2nSE71FQSJk0efS8wwh4oa4MIIwFd4Rds85teEq+kCDbjMRA1ztNFCJ78x4MveEx9fna5z3X2Alhj/RzUOIzCTIF8MVNpGBxBTY2gPgMcAzv9cu4A2VAx9zH2CGT29y0/ZI2GcC2JH5VDp418SVoR6q/CpzAAAAABJRU5ErkJggg==)',
+ [EditorArea.cSelectRectCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAC3klEQVRIx6XVz29UVRQH8M9781qhnYpYbCut1dpKo1SCEjttiBpNLCoIhjRqokv9C1yZaKZEd2rCygVqcGlC/EFIQdAQaRe0EeMCW6CQgFLFWmwQC5SxdFz08ToDHVPg3MW953vP/d6Tc849N/jUSldlRqfTzhp10nnnjMuhXI0lbnefe9ylwUFhcmJItFIG5OWM2etnhw36xSXFUqFRu1XaPKhWuSDGo9npX385ZsA3+swgUO8hdypHzoQhZxx1VOgJz+rQqib2IxiQocfNSc+giJxyQfbGT+e35BAy4WZlAlFGr/Ux440S/Gi9aFhfTICey/b4KFay1gowJXU11ond7HRAk3BEb4L/aoM6u+LREaMvOlB0ejpZ7TYiPO5IAvyh2zK3xSMVoz856ZipxCpCkA2yHDEiOm0m2er3ZrwKCm4c80ZcatfKjFHheAGw+X9DtmseinFRVYHaNJfjAk9qjdkkbad1IqErUknG0sL6BSXsEdus9aq92F6ALxfda/lCqk6F11Qad0o6QavVC1d5foFlU+YlGZ/IJFl4UpuwRVdBZdHr64KU5Y0XkbR6z9+J1qVRdNiaBNhhUq1/9AtVqlMtLIp8zqQf7LM61ts1iCKLE4MPnJA2ISdlqTot2jR7zmdxV/jdITvt8WGchYcNirg7IbjiC1AudFlehU02e992pxwy7KBeYwXvIjXXkWalUZUhvKzdBRft87nzfsNZw/Y7hw4bEGTJbwmuJXhHjf0+NuIZG6W0eV2v3XFhlenSaY22orAWESzWqlmDrba6ZLU/TaNa2mNa3K/JCneoLE3wtnVaVMn40rs69btooxekNVtiRVE7m5dgyrYkbaN2SHlFt6ctlS96nyU9OFOyOwcl6zO6zjQ75+JC1qFblFv+WAzIzzumjXgLtb7SV7TznQu+9628vIHrYzBXprWWKTNl0qNFO08JPT73uQ6V9O8BdboFFllksITNkP8AkhXgo0QeBZEAAAAASUVORK5CYII=)',
+ [EditorArea.cSelectRectPlusCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAC50lEQVRIx6XV309XdRgH8Nc53wMpQmQakBBFkqwkV7n4wly12sJKs9ZYtdVl/QGtq7baF1Z31eZVF1azrXXl+uEcGeVcwAWw6bowUKkNS5YRxoylIiGnC47n+/0ibKjPc3E+n/fned7n2ef58Qk+tdkVmdfmtLPGjZl2zqRZlKpS6WZ3ucNt6gwIU49h0WZZEJs1ocfPjhnym4uKpUy9Fls0u1e1UkGCRwuf//ztpEHf6TOPQK373KoUs6YMO+OEE0KPekqrJlVJHMGgLJ2uTzqHRMwqFeSu3TvumkXIlOuVKURZ3XYkjNdKcNQO0Yi+hACdlxz0UbLJ2SbAjMyVu07tFj69GoSjulP8dzvVOJBoa4I+r7fIey5dfWtU+IvjKfCnDuvdlGgmQX8y5qSZ1CpCkAtyHDcqOm0+Per3ZrIKCv444fWk1BbLvHHhZAHwwhK5yvsdWIJiUlRRsG0o8MtHUm3Cc8rtt10kdFkmzVi5sHZFCXvQHtu8ogd7C/ANojttWEnVKfOqNSadUp6i69QKt3hmCYcgXlCYiMVdcRCXeFHWJ7JpFh7TLGzUXlBZdPumIGWLpcl7/kl37eqFx2xNgX16VavU71AwEIwF0wFUB4JcEAcLjd3j89S+RZ0osjoFPvCrclNmZaxVoxE87bNkKvzhiP0O+jDJwv2GRNyeElz2JSgVuiRWBt631ylHjBjQbaKgLzL5ibQg9SoM4yUtzrvge4P4AmeNOOwcWu1EkCPuChYTvKPKYR8b9aRdMpq9Fkx7IymsEu3abNVcdK1FBKs12ajObrtd9IC/zGGdcg9rdLcGm9xizfIEb9uuUYWsr7yrTb8LdnlWuY0qbSoaZ0sSzNiTdsG4fTJe1uEJa8VF/blsBGeWnc7BsqUVXWWay4e4knXoBuWGHxaD4iV1zqi3UO1rfUUnh5z3ox/EYoNX30G+TKutV2LGvx4qOnlc6JH84zq8bHz3qNEhsMoqQ8vYDPsfzFvqVWmuitgAAAAASUVORK5CYII=)',
+ [EditorArea.cZoomCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADoElEQVRIx6WVW2xUVRSGv73PmaHTHopTiI1kaGlDGjHW2IQWL1SSeosVoYmODfZBQ3ygqGhMY7Q+GBLShBdjokkTbdQEGpBaLWg1ESTBjo1cpJbBRGg7WhgHS9ILMDMtcznLh5mxpJ22MfOvt3X+vW77/Gurc0xgo1AoNAoNKDQ67VNo8vGgmMRFITeZwE5/1WjMCa6mHQYaA4Wmwck6yinGwgLChBkjUDDcHXuQVSSZIJFOB6aNjQ1oBMHGm08tVefvP1UVKA66wgZYSc90+djGgft+e3aAvlB0FZopQGOiUMf4BxuNgaYRanjsyJauDZ0O5qEp7j277VuOczpGhBuAicJ4kWi6/EYHjQHvu6+0lPkNssBvHFpz7aH1TneBcdG0jUwbJxhDUsebfM+9WX9GpeibpXayIuSagem8S6v73CfT/mp5/7tNX9IZj88wjUKJg60UcZk7fY3NT18AoCG545enfjRHCTIOrMSTKP3+0U8f6DEA7qW9d1MnB+Pc4hZIpb9zd/jguV+7qm0EQfaFIm1SL9bt5Ysl9ZG2faEUo9oe6ZCav4kRBak89XXKnbL9g/KelGebgZRIy/7BFKs5Kq2SHyIOYsX3PGzPBtgd/v2AVJIVcoe0ZKro6ZcnDxNC+8Lm1Ueuz5KOFVxfjpk9gJri8KufNyQBujZQ5XUGwIfUdZ/O5K+zz3TLDlnGgpD6o74Ud/CQ3DMIPyNrr7QvFwR5IRb4RBrEwSIQK75ns40gH1+ULTfQGi57RrfG4bVI+wdlB+hR8cUCqLA5WjsJECim2Imp+MF+IvCMf2PRrs+Mr7igWBLBihBFEHRhmZgaE0405gHHVWh+xcDcmOOuGYCwgWWgFU5ygalxQd0Xr18r2rVW5reQraOV03kAVpIwmPncrSn/prLTMfTWXquwV04uOQXPpdUAnmnCYK6BkmDpUQd8WDD+xl53mVt6F7sHsRKlfW6A8jHGgGhOP5LAeWTnO5OzWlgv/UekasHjJZG2hgSCNMXkbXEKutJK3PXTilnK45EVN0ksJCae/+il1FbwnmWAGLnJWZBcFkoqYw4r7T+Ja0yuUPE/lyqZqx5iiChx/kAcsn2kozl6+0TmWnN0pEO2i2PWxTBDTJFMhUBqpLWnvymW7XBTrKdfWjO9Z0yNYOOkiGXY/EUfLy/ytDFAH9E5YvkTwcTEoBCDIH5C7Mz6uDJMLMuCGQU0Gk0heSRJkARA48BE0Eso619gHCw05MD3YAAAAABJRU5ErkJggg==)',
+ [EditorArea.cInvisibleCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAADkklEQVRIx5XVX2iWdRQH8M/zZ5oO5krUV11DUwvcZtAS6R8NtOgfgtJF5m1kRiVqYajkJt5EqOsiuilC8KabTciCigjCufUHi0oZM8t615R0FrK9a++7vU8X77PXzW1KvwPP8+Oc3/n+vs855zknSPQbcrNVk757J1mCy3r0CwUCoVCIUCBKNSVto5xLatGdaoiFQvGQfn1igVAkEglEQrFQIBaINRnQ7n0HPOguZ4TpFQmCrB9dTA+GqSkWikr4HhIbcMxew2oc9oBAj1igYoxLcEOJDWi317ARvV5zUmLFOHtIKBKX749E4vQZazKo3QHD7nbQHOft1CmwJD1bUWIwFrAoldJuZeq+z1X1DtrsLdV+t81J1F4LejhOonH7jEHt9hjSoFWdyOPeNE+vnTol5o3/hIl3XyO/X94qh9XhhNgTDqh23qu6ROaKRDv0GyrnuyRV7pdzTLMBdVrVoVulvDmWWeorF3VoUGOmaIe/DZshTkNXocpqOe12+1e9VvUSPSrEQr9YbJklvnVBlwY1YhNSEgqsltOmRV6DQ+rRk1p+NYhItVtQcJUSQJRWViTUZEi7FgPqyu5j8dmlU+Kk7frMt19TCSAsA6yUMaTNbnkrHVZvUFaFUOQnvTrRYZs+i7V4VL5UB9eCN19em33yGrRqQG85vT/bgg479anRYp2CWYISg1IdPozEeTlkLEJOhUCs23s+lOi0XdZCzdaaOfY/Z3X5XEFi2Ge+dsUbFsjY4pLEqHPOOCqR6NAo417HDUuMSCQSsr7xpURemw02OaFfs7kytvpLIme/ROKENTLWOG7IqGLqnpD1nUReu43WW2+TDpftk7HAVpckEkUd1sho9JGcxGjZPREdskjBJ44YBQWnLPekglO6XXCf2Tptk1Wj2TqzFIXjWlrUrOBjRxTLqhE/uMNTRnU566LIbn9Yao9HzGSCO0HiuA+MXNcqK71shXe8KxAqqrXXWrOnaLch1ZJJ6kFvO+slzwsU1XrduindiZrdLnJ6EsiI7y33pBFX7PKYWdM0/CBBUZujU/Co9IoVzqlTOe3EiJ5TJXCnGVOwKDhllUYz/KlqGoCQHqfFNnpGNMlcKtiixdMySFsaoadtEkww3uZF9ZLrEjcJgMBvCG2wedzhW73gHtF1oJOCmDUiSafMQqO+8Kk8Is9q9I+5Nxm7QVZRks7GjP+/YuPm8RWhqjLlG3/72PoPdU0hakaQf9YAAAAASUVORK5CYII=)',
+ [EditorArea.cPointedHandCursor]:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFYUlEQVRYw72XXUgUaxjH//POzOZ+4ULrLARZe5GbuMWK3UhZHt2bxSBWMiy8iboo6MpKuorywkDpSF3XbZFRlIUR+IVSGNVZ6WvDoMg0O6WB4NrOvvO+z7k47JCezF1P+cALM8w77/PjP8/XADnY8ePHCQCVlJSQrut0+PDhRFdXVyFWw1pbWzsAUGtrKyWTSWpubqZIJEIHDx6sXRWAxsbGPq/XS4lEgoiI7t+/T2vWrCEA1b/ifLbchunpafj9fvj9fhAR1q9fD845zp079+eqAAAAEUEIAQBQVRUAoChK+aoAmKYJVVW/dwwppQ302wF0XYcQAkQERVFARAAAIQRu3Ljhu3nzpu//AGgreamwsBD9/f3o6ekZ4Jyjqqqqd3h4+OSqAXDOMTw8jPr6+gjnHD09PZGtW7f+vXnz5r9GR0cRjUaVmpqaJ3v37p39bQDxeBwXLlwAADQ0NODt27cdQgh8/foVjx8/RjAYHAVQ/kuy4EcAW7ZsgWEYMAwDpaWlmJqaQm1tLS5evAi/34+2trbInj17akpLS2uqq6trjx07VvjLFAAAKSVUVYUQAg6HA5qm4ciRIwiFQvB6vbh37x5ev37dNzMzg1QqBVVVf6jIYgW87e3tiZaWFjp69OiJbA3QNA2c8wX1wOl0QggBVVVhmiYCgQAMwwBjDMFgEAAQCATQ2dmJpqYmDA4ORk6dOtX3UwX8fv/QmTNnIkIIuFyujk2bNhV5PB6ZhcgCAYBlWbYSiqJgfn4eUkoQESzLgsfjQV1dHRobGzE5OYmBgQF0dXX9PAamp6cjbW1tSCaT2L9/PyzLavn48WOUiGzHmqahsrISTqcTiqJACIGioiJUVFTAsiwoigLGGEzThGEY0DQNLpcLgUAAExMTy1fdsbExymQy9Pz5c/J4PKTrOgWDQRofHyfOOZmmSRMTEzQzM0OWZZFlWfTlyxeanJwkzjkJIejFixdUUFBAly9fJiklffr0ieLxODmdzr5ls+Du3bvQNA2hUAgHDhwA59yWloigqioMw4DP5wNjDIwx+Hw+rFu3DlJKSCmxYcMG9Pf3Y/fu3f86YcxW8KcAO3fuxK1bt5BOp8EYQ319vV3/FUWx40DXdSiKYj/L9gmHwwHGGNxuNyorK2EYBqSUdupm42ZJgOrq6sTQ0BAGBwehqipCoRDKy8uh6zo45xBCgDG2ACp7bR/I2H/uF+9fEuDVq1fNZWVlGBkZQSqVQlFREXbt2oVMJrNAgRV1vUVgS1pxcXHf9u3b6cOHD2RZFj179oy6u7tpbm6OTNMkKSXlY1JK+vz5M9XV1ZHX610+CBsaGvDgwQM8evQIiqIgHA4jFotB07QVKZBNVc45XC7X8r3g/PnztWVlZbh69SoymQzm5+fBGIPD4VgwD+Rq2cKUTqfhcDhya0Zr1649efv2bTx8+BButxuWZdkRnNN3/M45EUFKiampKRiGkRvAxo0bE+FwGL29vUin09B13Y7mfOUnInz79g3v379HOBzODSAWiz1xu92jd+7cwbt372zZl0ql5ezNmzfIZDIoL89jjj179mwHAGpvbyfOORFR3lmQ3Xv69GlSFIXyJo/H41RcXExjY2MkpbRXPjY3N0cVFRW0Y8cOynsiSqVSUU3TcO3aNZimaU/HS63FAwsAjIyM4OnTp4hGo4m8Aaqqqp4UFBSMXrlyBclkEpZl2f1/8freKRGBMYbZ2Vl0d3dj27ZtGB8fb/5hoObyKSKRCLndbnR2dsLpdNrNZ3HKSSmh67r9M/Py5UscOnQIsVhs8Pr163+suI43NTWdKCkpIU3TssGU89q3bx9dunRpyfD/B4mqU48lcruHAAAAAElFTkSuQmCC)',
+};
+
+function getCursor(cursor) {
+ if (customCursors[cursor]) {
+ return `${customCursors[cursor]} ${EditorArea.HOTSPOT_32[cursor * 2]} ${
+ EditorArea.HOTSPOT_32[cursor * 2 + 1]
+ }, default`;
+ }
+ switch (cursor) {
+ case EditorArea.cPointerCursor:
+ return 'default';
+ case EditorArea.cTextCursor:
+ return 'text';
+ default:
+ throw new Error(`Unknown cursor: ${cursor}`);
+ }
+}
diff --git a/examples/generic-editor/CanvasEditorDialog.js b/examples/generic-editor/CanvasEditorDialog.js
new file mode 100644
index 00000000..21a8423f
--- /dev/null
+++ b/examples/generic-editor/CanvasEditorDialog.js
@@ -0,0 +1,234 @@
+export class CanvasEditorDialog {
+ /**
+ *
+ * @param {string} title
+ */
+ constructor(title) {
+ this.title = title;
+ this.elements = [];
+ this.dialogElement = null;
+ }
+
+ setLayout(hLayout, vLayout) {
+ this.hLayout = generateLayout(hLayout);
+ this.vLayout = generateLayout(vLayout);
+ }
+
+ add(component, x, y, x2, y2) {
+ this.elements.push({ component: component.getJsComponent(), x, y, x2, y2 });
+ }
+
+ createTextField(width, height) {
+ return new TextField(width, height);
+ }
+
+ createLabel(text) {
+ return new Label(text);
+ }
+
+ createComboBox() {
+ return new ComboBox();
+ }
+
+ createCheckBox(text) {
+ return new CheckBox(text);
+ }
+
+ setEventConsumer(consumer) {
+ this.consumer = consumer;
+ }
+
+ showMessage(message) {
+ alert(message);
+ }
+
+ showDialog() {
+ const dialog = document.createElement('dialog');
+ this.dialogElement = dialog;
+ document.body.append(dialog);
+ const grid = document.createElement('div');
+ grid.style.display = 'grid';
+ grid.style.gridTemplateColumns = this.hLayout;
+ grid.style.gridTemplateRows = this.vLayout;
+ dialog.append(grid);
+ for (const { component, x, y, x2, y2 } of this.elements) {
+ const div = document.createElement('div');
+ if (x2 === undefined) {
+ div.style.gridColumn = `${x + 1} / ${x + 2}`;
+ div.style.gridRow = `${y + 1} / ${y + 2}`;
+ } else {
+ div.style.gridColumn = `${x + 1} / ${x2 + 2}`;
+ div.style.gridRow = `${y + 1} / ${y2 + 2}`;
+ }
+ div.appendChild(component.getElement());
+ grid.appendChild(div);
+ }
+ const buttons = document.createElement('div');
+ buttons.style.display = 'flex';
+ buttons.style.justifyContent = 'flex-end';
+ buttons.style.gap = '15px';
+ const okButton = document.createElement('button');
+ okButton.textContent = 'OK';
+ okButton.addEventListener('click', () => {
+ this.consumer.fireOk();
+ });
+ buttons.appendChild(okButton);
+ const cancelButton = document.createElement('button');
+ cancelButton.textContent = 'Cancel';
+ cancelButton.addEventListener('click', () => {
+ this.consumer.fireCancel();
+ });
+ buttons.appendChild(cancelButton);
+ dialog.append(buttons);
+ dialog.showModal();
+ dialog.addEventListener('cancel', () => {
+ this.consumer.fireCancel();
+ });
+ }
+
+ disposeDialog() {
+ if (this.dialogElement !== null) {
+ this.dialogElement.remove();
+ this.dialogElement = null;
+ }
+ }
+}
+
+class Component {
+ setEventHandler(eventHandler) {
+ this.eventHandler = eventHandler;
+ }
+
+ fireEvent(what, value) {
+ this.eventHandler(what, value);
+ }
+}
+
+class Label extends Component {
+ constructor(text) {
+ super();
+ this.element = document.createElement('label');
+ this.setText(text);
+ }
+
+ setText(text) {
+ this.element.textContent = text;
+ }
+
+ getElement() {
+ return this.element;
+ }
+}
+
+class TextField extends Component {
+ constructor() {
+ super();
+ this.element = document.createElement('input');
+ this.element.type = 'text';
+ }
+
+ setText(text) {
+ this.element.value = text;
+ }
+
+ getText() {
+ return this.element.value;
+ }
+
+ getElement() {
+ return this.element;
+ }
+}
+
+class ComboBox extends Component {
+ constructor() {
+ super();
+ this.element = document.createElement('select');
+ this.element.addEventListener('change', () => {
+ this.fireEvent(2, this.element.selectedIndex);
+ });
+ }
+
+ setEnabled(enabled) {
+ this.element.disabled = !enabled;
+ }
+
+ addItem(item) {
+ const option = document.createElement('option');
+ option.textContent = item;
+ this.element.append(option);
+ }
+
+ getSelectedIndex() {
+ return this.element.selectedIndex;
+ }
+
+ setSelectedIndex(index) {
+ this.element.selectedIndex = index;
+ }
+
+ setSelectedItem(item) {
+ const options = this.element.options;
+ for (let i = 0; i < options.length; i++) {
+ if (options[i].textContent === item) {
+ this.element.selectedIndex = i;
+ }
+ }
+ }
+
+ getSelectedItem() {
+ return this.element.options[this.element.selectedIndex].textContent;
+ }
+
+ removeAllItems() {
+ this.element.innerHTML = '';
+ }
+
+ getElement() {
+ return this.element;
+ }
+}
+
+class CheckBox extends Component {
+ constructor(text) {
+ super();
+ const label = document.createElement('label');
+ const input = document.createElement('input');
+ input.type = 'checkbox';
+ input.addEventListener('change', () => {
+ this.fireEvent(3, input.checked ? 1 : 0);
+ });
+ label.append(input);
+ label.append(text);
+ this.element = label;
+ this.checkBox = input;
+ }
+
+ setEnabled(enabled) {
+ this.checkBox.disabled = !enabled;
+ }
+
+ isSelected() {
+ return this.checkBox.checked;
+ }
+
+ setSelected(selected) {
+ this.checkBox.checked = selected;
+ }
+
+ getElement() {
+ return this.element;
+ }
+}
+
+function generateLayout(layout) {
+ return layout
+ .map((dim) => {
+ if (dim > 0) {
+ return `${dim}px`;
+ } else {
+ return 'auto';
+ }
+ })
+ .join(' ');
+}
diff --git a/examples/generic-editor/CanvasEditorImage.js b/examples/generic-editor/CanvasEditorImage.js
new file mode 100644
index 00000000..75ee45ee
--- /dev/null
+++ b/examples/generic-editor/CanvasEditorImage.js
@@ -0,0 +1,33 @@
+export class CanvasEditorImage {
+ /**
+ *
+ * @param {ImageData} imageData
+ */
+ constructor(imageData) {
+ this.imageData = imageData;
+ this.dataView = new DataView(imageData.data.buffer);
+ }
+
+ getWidth() {
+ return this.imageData.width;
+ }
+
+ getHeight() {
+ return this.imageData.height;
+ }
+
+ getRGB(x, y) {
+ const color = this.dataView.getInt32(
+ (y * this.imageData.width + x) * 4,
+ false,
+ );
+ const alpha = color & 0xff;
+ return (alpha << 24) | (color >>> 8);
+ }
+
+ setRGB(x, y, argb) {
+ const alpha = (argb >>> 24) & 0xff;
+ const rgb = (argb << 8) | alpha;
+ this.dataView.setInt32((y * this.imageData.width + x) * 4, rgb, false);
+ }
+}
diff --git a/examples/generic-editor/CanvasToolbar.js b/examples/generic-editor/CanvasToolbar.js
new file mode 100644
index 00000000..aa4e4d8e
--- /dev/null
+++ b/examples/generic-editor/CanvasToolbar.js
@@ -0,0 +1,27 @@
+import { CanvasDrawContext } from './CanvasDrawContext.js';
+import { addMouseListeners } from './events.js';
+
+const { EditorToolbar } = OCL;
+
+export class CanvasToolbar {
+ constructor(canvasElement, editorArea) {
+ this.toolbar = new EditorToolbar(editorArea, {
+ setDimensions(width, height) {
+ canvasElement.width = width;
+ canvasElement.height = height;
+ },
+ getDrawContext() {
+ return new CanvasDrawContext(canvasElement.getContext('2d'));
+ },
+ getBackgroundRGB() {
+ return 0xffffff;
+ },
+
+ getForegroundRGB() {
+ return 0x000000;
+ },
+ });
+
+ addMouseListeners(canvasElement, this.toolbar);
+ }
+}
diff --git a/examples/generic-editor/events.js b/examples/generic-editor/events.js
new file mode 100644
index 00000000..3f0c96db
--- /dev/null
+++ b/examples/generic-editor/events.js
@@ -0,0 +1,104 @@
+const { EditorArea } = OCL;
+
+export function addMouseListeners(canvasElement, drawArea) {
+ let isMouseDown = false;
+
+ function fireMouseEvent(what, ev, clickCount = 0) {
+ if (ev.button !== 0) {
+ // TODO: remove this to implement popup menu.
+ return;
+ }
+ drawArea.fireMouseEvent(
+ what,
+ ev.button + 1,
+ clickCount,
+ ev.offsetX,
+ ev.offsetY,
+ ev.shiftKey,
+ ev.ctrlKey,
+ ev.altKey,
+ ev.button === 2,
+ );
+ }
+
+ canvasElement.addEventListener('mousedown', (ev) => {
+ isMouseDown = true;
+ fireMouseEvent(EditorArea.MOUSE_EVENT_PRESSED, ev);
+ });
+ canvasElement.addEventListener('mouseup', (ev) => {
+ isMouseDown = false;
+ fireMouseEvent(EditorArea.MOUSE_EVENT_RELEASED, ev);
+ });
+ canvasElement.addEventListener('click', (ev) => {
+ fireMouseEvent(EditorArea.MOUSE_EVENT_CLICKED, ev, ev.detail);
+ });
+ canvasElement.addEventListener('mouseenter', (ev) => {
+ fireMouseEvent(EditorArea.MOUSE_EVENT_ENTERED, ev);
+ });
+ canvasElement.addEventListener('mouseleave', (ev) => {
+ fireMouseEvent(EditorArea.MOUSE_EVENT_EXITED, ev);
+ });
+ canvasElement.addEventListener('mousemove', (ev) => {
+ if (isMouseDown) {
+ fireMouseEvent(EditorArea.MOUSE_EVENT_DRAGGED, ev);
+ } else {
+ fireMouseEvent(EditorArea.MOUSE_EVENT_MOVED, ev);
+ }
+ });
+}
+
+export function addKeyboardListeners(canvasElement, editorArea) {
+ const isMac =
+ typeof navigator !== 'undefined' && navigator.platform === 'MacIntel';
+
+ function fireKeyEvent(what, ev) {
+ const key = getKeyFromEvent(ev);
+ if (key === null) return;
+ ev.stopPropagation();
+ ev.preventDefault();
+ editorArea.fireKeyEvent(
+ what,
+ key,
+ ev.altKey,
+ ev.ctrlKey,
+ ev.shiftKey,
+ (isMac && ev.metaKey) || (!isMac && ev.ctrlKey),
+ );
+ }
+
+ canvasElement.addEventListener('keydown', (ev) => {
+ fireKeyEvent(EditorArea.KEY_EVENT_PRESSED, ev);
+ });
+ canvasElement.addEventListener('keyup', (ev) => {
+ fireKeyEvent(EditorArea.KEY_EVENT_RELEASED, ev);
+ });
+}
+
+/**
+ *
+ * @param {KeyboardEvent} ev
+ */
+function getKeyFromEvent(ev) {
+ switch (ev.key) {
+ case 'Control':
+ return EditorArea.KEY_CTRL;
+ case 'Alt':
+ return EditorArea.KEY_ALT;
+ case 'Shift':
+ return EditorArea.KEY_SHIFT;
+ case 'Delete':
+ return EditorArea.KEY_DELETE;
+ case 'Backspace':
+ return EditorArea.KEY_BACKSPACE;
+ case 'F1':
+ return EditorArea.KEY_HELP;
+ case 'Escape':
+ return EditorArea.KEY_ESCAPE;
+ default:
+ if (ev.key.length === 1) {
+ return ev.key.charCodeAt(0);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/examples/generic-editor/index.js b/examples/generic-editor/index.js
new file mode 100644
index 00000000..a37f85b2
--- /dev/null
+++ b/examples/generic-editor/index.js
@@ -0,0 +1 @@
+export { CanvasEditor } from './CanvasEditor.js';
diff --git a/examples/generic-editor/utils.js b/examples/generic-editor/utils.js
new file mode 100644
index 00000000..fd937be2
--- /dev/null
+++ b/examples/generic-editor/utils.js
@@ -0,0 +1,46 @@
+// https://github.com/niklasvh/base64-arraybuffer/blob/master/LICENSE
+const chars =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+// Use a lookup table to find the index.
+const lookup = new Uint8Array(256);
+for (let i = 0; i < chars.length; i++) {
+ lookup[chars.charCodeAt(i)] = i;
+}
+
+export function decodeBase64(base64) {
+ let bufferLength = base64.length * 0.75;
+ let len = base64.length;
+ let i;
+ let p = 0;
+ let encoded1;
+ let encoded2;
+ let encoded3;
+ let encoded4;
+
+ if (base64[base64.length - 1] === '=') {
+ bufferLength--;
+ if (base64[base64.length - 2] === '=') {
+ bufferLength--;
+ }
+ }
+
+ const arraybuffer = new ArrayBuffer(bufferLength);
+ const bytes = new Uint8Array(arraybuffer);
+
+ for (i = 0; i < len; i += 4) {
+ encoded1 = lookup[base64.charCodeAt(i)];
+ encoded2 = lookup[base64.charCodeAt(i + 1)];
+ encoded3 = lookup[base64.charCodeAt(i + 2)];
+ encoded4 = lookup[base64.charCodeAt(i + 3)];
+
+ bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
+ bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
+ bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
+ }
+
+ return arraybuffer;
+}
+
+export function toHex(v) {
+ return v.toString(16).padStart(2, '0');
+}
diff --git a/package-lock.json b/package-lock.json
index 99d68688..248800e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"benchmark": "^2.1.4",
"eslint": "^8.37.0",
"eslint-config-cheminfo": "^8.2.0",
+ "fast-png": "^6.1.0",
"gwt-api-exporter": "^2.0.0",
"jest": "^29.5.0",
"openchemlib-utils": "^2.4.0",
@@ -1316,6 +1317,12 @@
"dev": true,
"peer": true
},
+ "node_modules/@types/pako": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz",
+ "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==",
+ "dev": true
+ },
"node_modules/@types/prettier": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
@@ -2653,6 +2660,17 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fast-png": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.1.0.tgz",
+ "integrity": "sha512-v8e/40RKZbE1mALigoqBOkYnemCVSMmVlGSz8fawZAZg5UQ9OZeR00L++CPvrhIGm2F6TxV5u9lbWD0oOJHcCw==",
+ "dev": true,
+ "dependencies": {
+ "@types/pako": "^1.0.2",
+ "iobuffer": "^5.0.4",
+ "pako": "^2.0.4"
+ }
+ },
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -3169,6 +3187,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/iobuffer": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz",
+ "integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==",
+ "dev": true
+ },
"node_modules/is-any-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.0.tgz",
@@ -4663,6 +4687,12 @@
"node": ">=6"
}
},
+ "node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "dev": true
+ },
"node_modules/papaparse": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
@@ -6962,6 +6992,12 @@
"dev": true,
"peer": true
},
+ "@types/pako": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz",
+ "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==",
+ "dev": true
+ },
"@types/prettier": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
@@ -7975,6 +8011,17 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "fast-png": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.1.0.tgz",
+ "integrity": "sha512-v8e/40RKZbE1mALigoqBOkYnemCVSMmVlGSz8fawZAZg5UQ9OZeR00L++CPvrhIGm2F6TxV5u9lbWD0oOJHcCw==",
+ "dev": true,
+ "requires": {
+ "@types/pako": "^1.0.2",
+ "iobuffer": "^5.0.4",
+ "pako": "^2.0.4"
+ }
+ },
"fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -8349,6 +8396,12 @@
"side-channel": "^1.0.4"
}
},
+ "iobuffer": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz",
+ "integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==",
+ "dev": true
+ },
"is-any-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.0.tgz",
@@ -9480,6 +9533,12 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
+ "pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "dev": true
+ },
"papaparse": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
diff --git a/package.json b/package.json
index 45474578..b4f12880 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
"benchmark": "^2.1.4",
"eslint": "^8.37.0",
"eslint-config-cheminfo": "^8.2.0",
+ "fast-png": "^6.1.0",
"gwt-api-exporter": "^2.0.0",
"jest": "^29.5.0",
"openchemlib-utils": "^2.4.0",
diff --git a/scripts/openchemlib/classes.js b/scripts/openchemlib/classes.js
index f19705c8..6838722c 100644
--- a/scripts/openchemlib/classes.js
+++ b/scripts/openchemlib/classes.js
@@ -214,6 +214,7 @@ exports.removed = removed.map(getFolderName);
const generated = [
['chem/conf/TorsionDBData', require('./generateTorsionDBData')],
['chem/forcefield/mmff/CsvData', require('./generateCsvData')],
+ ['@gwt/gui/generic/ImageData', require('./generateImageData')],
];
exports.generated = generated.map((file) => [getFilename(file[0]), file[1]]);
diff --git a/scripts/openchemlib/generateImageData.js b/scripts/openchemlib/generateImageData.js
new file mode 100644
index 00000000..8a2046e5
--- /dev/null
+++ b/scripts/openchemlib/generateImageData.js
@@ -0,0 +1,50 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+
+const { decode } = require('fast-png');
+
+const resourcesDir = path.join(
+ __dirname,
+ '../../openchemlib/src/main/resources/images',
+);
+
+const images = ['editorButtons.png', 'esrButtons.png'];
+
+const start = `package com.actelion.research.gwt.gui.generic;
+
+public class ImageData {
+`;
+
+const end = `
+}
+`;
+
+function generateImageData() {
+ const imageData = [start];
+ for (const image of images) {
+ const contents = fs.readFileSync(path.join(resourcesDir, image));
+ const png = decode(contents);
+ const name = image.replace('.png', '');
+ const base64 = Buffer.from(png.data).toString('base64');
+ const compressed = base64.replaceAll('A'.repeat(20), '%');
+ const compressedSplit = [];
+ for (let i = 0; i < compressed.length; i += 50_000) {
+ compressedSplit.push(compressed.substring(i, i + 50_000));
+ }
+ imageData.push(
+ `
+public static final int ${name}Width = ${png.width};
+public static final int ${name}Height = ${png.height};
+${compressedSplit
+ .map((s, i) => `public static final String ${name}Data${i} = "${s}";`)
+ .join('\n')}
+`,
+ );
+ }
+ imageData.push(end);
+ return imageData.join('\n');
+}
+
+module.exports = generateImageData;
diff --git a/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java b/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java
index 80cb7367..4d272130 100644
--- a/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java
+++ b/scripts/openchemlib/modified/com/actelion/research/gui/hidpi/HiDPIHelper.java
@@ -1,6 +1,10 @@
package com.actelion.research.gui.hidpi;
+import com.actelion.research.gui.LookAndFeelHelper;
import com.actelion.research.gui.generic.GenericImage;
+import com.actelion.research.util.ColorHelper;
+
+import java.awt.Color;
public class HiDPIHelper {
public static int scale(float value) {
@@ -16,6 +20,14 @@ public static void adaptForLookAndFeel(GenericImage image) {
}
public static void disableImage(GenericImage image) {
- return;
+ Color gray = ColorHelper.brighter(new Color(238, 238, 238), 0.8f);
+ int grayRGB = 0x00FFFFFF & gray.getRGB();
+
+ for (int x=0; x
+
diff --git a/src/com/actelion/research/gwt/Generic.gwt.xml b/src/com/actelion/research/gwt/Generic.gwt.xml
new file mode 100644
index 00000000..7b25342c
--- /dev/null
+++ b/src/com/actelion/research/gwt/Generic.gwt.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java b/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java
index 80cb7367..4d272130 100644
--- a/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java
+++ b/src/com/actelion/research/gwt/chemlib/com/actelion/research/gui/hidpi/HiDPIHelper.java
@@ -1,6 +1,10 @@
package com.actelion.research.gui.hidpi;
+import com.actelion.research.gui.LookAndFeelHelper;
import com.actelion.research.gui.generic.GenericImage;
+import com.actelion.research.util.ColorHelper;
+
+import java.awt.Color;
public class HiDPIHelper {
public static int scale(float value) {
@@ -16,6 +20,14 @@ public static void adaptForLookAndFeel(GenericImage image) {
}
public static void disableImage(GenericImage image) {
- return;
+ Color gray = ColorHelper.brighter(new Color(238, 238, 238), 0.8f);
+ int grayRGB = 0x00FFFFFF & gray.getRGB();
+
+ for (int x=0; x pasteMolecules() {
+ Object test = null;
+ System.out.println("test: " + test.toString());
+ return null;
+ }
+
+ public Reaction pasteReaction() {
+ Object test = null;
+ System.out.println("test: " + test.toString());
+ return null;
+ }
+
+ public boolean copyMolecule(String molfile) {
+ Object test = null;
+ System.out.println("test: " + test.toString());
+ StereoMolecule m = new StereoMolecule();
+ MolfileParser p = new MolfileParser();
+ p.parse(m, molfile);
+ return copyMolecule(m);
+ }
+
+ public native boolean copyMolecule(StereoMolecule mol)
+ /*-{
+ var handler = this.@com.actelion.research.gwt.gui.generic.JSClipboardHandler::getJsHandler()();
+ var jsMolecule = @com.actelion.research.gwt.minimal.JSMolecule::new(Lcom/actelion/research/chem/StereoMolecule;)(mol);
+ return handler.copyMolecule(jsMolecule);
+ }-*/;
+
+ public boolean copyReaction(Reaction r) {
+ Object test = null;
+ System.out.println("test: " + test.toString());
+ return true;
+ }
+
+ public boolean copyReaction(String ctab) {
+ Object test = null;
+ System.out.println("test: " + test.toString());
+ return true;
+ }
+
+ public boolean copyImage(java.awt.Image img) {
+ Object test = null;
+ System.out.println("test: " + test.toString());
+ return true;
+ }
+
+ public java.awt.Image pasteImage() {
+ Object test = null;
+ System.out.println("test: " + test.toString());
+ return null;
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSComboBox.java b/src/com/actelion/research/gwt/gui/generic/JSComboBox.java
new file mode 100644
index 00000000..43335b65
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSComboBox.java
@@ -0,0 +1,59 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class JSComboBox extends JSComponent implements GenericComboBox {
+ public JSComboBox(JavaScriptObject jsComboBox) {
+ super(jsComboBox);
+ }
+
+ @Override
+ public native void addItem(String item)
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()();
+ return component.addItem(item);
+ }-*/;
+
+ @Override
+ public native void removeAllItems()
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()();
+ return component.removeAllItems();
+ }-*/;
+
+ @Override
+ public native int getSelectedIndex()
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()();
+ return component.getSelectedIndex();
+ }-*/;
+
+ @Override
+ public native String getSelectedItem()
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()();
+ return component.getSelectedItem();
+ }-*/;
+
+ @Override
+ public native void setSelectedIndex(int index)
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()();
+ return component.setSelectedIndex(index);
+ }-*/;
+
+ @Override
+ public native void setSelectedItem(String item)
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()();
+ return component.setSelectedItem(item);
+ }-*/;
+
+ @Override
+ public native void setEditable(boolean b)
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComboBox::getJsComponent()();
+ return component.setEditable(b);
+ }-*/;
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSComponent.java b/src/com/actelion/research/gwt/gui/generic/JSComponent.java
new file mode 100644
index 00000000..edaefb06
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSComponent.java
@@ -0,0 +1,57 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.ArrayList;
+
+public class JSComponent implements GenericComponent {
+ private JavaScriptObject mJsComponent;
+ private ArrayList> mConsumerList;
+
+ public JSComponent(JavaScriptObject jsComponent) {
+ mJsComponent = jsComponent;
+ mConsumerList = new ArrayList<>();
+ setEventHandler(jsComponent);
+ }
+
+ private native void setEventHandler(JavaScriptObject jsComponent)
+ /*-{
+ var component = this;
+ jsComponent.setEventHandler(function(what, value) {
+ component.@com.actelion.research.gwt.gui.generic.JSComponent::fireEventFromJs(II)(what, value);
+ });
+ }-*/;
+
+ private void fireEventFromJs(int what, int value) {
+ fireEvent(new GenericActionEvent(this, what, value));
+ }
+
+ public JavaScriptObject getJsComponent() {
+ return mJsComponent;
+ }
+
+ @Override
+ public native void setEnabled(boolean b)
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSComponent::getJsComponent()();
+ return component.setEnabled(b);
+ }-*/;
+
+ @Override
+ public void addEventConsumer(GenericEventListener consumer) {
+ mConsumerList.add(consumer);
+ }
+
+ @Override
+ public void removeEventConsumer(GenericEventListener consumer) {
+ mConsumerList.remove(consumer);
+ }
+
+ @Override
+ public void fireEvent(GenericActionEvent event) {
+ for (GenericEventListener consumer:mConsumerList) {
+ consumer.eventHappened(event);
+ }
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSDialog.java b/src/com/actelion/research/gwt/gui/generic/JSDialog.java
new file mode 100644
index 00000000..19b7e267
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSDialog.java
@@ -0,0 +1,120 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class JSDialog implements GenericDialog {
+ private JavaScriptObject mJsDialog;
+ private GenericEventListener mConsumer;
+
+ public JSDialog(JavaScriptObject jsDialog) {
+ mJsDialog = jsDialog;
+ }
+
+ private JavaScriptObject getJsDialog() {
+ return mJsDialog;
+ }
+
+ @Override
+ public native void setLayout(int[] hLayout, int[] vLayout)
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ return dialog.setLayout(hLayout, vLayout);
+ }-*/;
+
+ @Override
+ public native void add(GenericComponent c, int x, int y)
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ return dialog.add(c, x, y);
+ }-*/;
+
+ @Override
+ public native void add(GenericComponent c, int x1, int y1, int x2, int y2)
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ return dialog.add(c, x1, y1, x2, y2);
+ }-*/;
+
+ @Override
+ public native GenericCheckBox createCheckBox(String text)
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ var checkBox = dialog.createCheckBox(text);
+ return @com.actelion.research.gwt.gui.generic.JSCheckBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(checkBox);
+ }-*/;
+
+ @Override
+ public native GenericComboBox createComboBox()
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ var comboBox = dialog.createComboBox();
+ return @com.actelion.research.gwt.gui.generic.JSComboBox::new(Lcom/google/gwt/core/client/JavaScriptObject;)(comboBox);
+ }-*/;
+
+ @Override
+ public native GenericLabel createLabel(String text)
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ var label = dialog.createLabel(text);
+ return @com.actelion.research.gwt.gui.generic.JSLabel::new(Lcom/google/gwt/core/client/JavaScriptObject;)(label);
+ }-*/;
+
+ @Override
+ public native GenericTextField createTextField(int width, int height)
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ var textField = dialog.createTextField(width, height);
+ return @com.actelion.research.gwt.gui.generic.JSTextField::new(Lcom/google/gwt/core/client/JavaScriptObject;)(textField);
+ }-*/;
+
+ @Override
+ public native void setEventConsumer(GenericEventListener consumer)
+ /*-{
+ this.@com.actelion.research.gwt.gui.generic.JSDialog::mConsumer = consumer;
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ var that = this;
+ var jsConsumer = {
+ fireOk: function ok() {
+ that.@com.actelion.research.gwt.gui.generic.JSDialog::fireOk()();
+ },
+ fireCancel: function cancel() {
+ that.@com.actelion.research.gwt.gui.generic.JSDialog::fireCancel()();
+ }
+ };
+ dialog.setEventConsumer(jsConsumer);
+ }-*/;
+
+ @Override
+ public native void showDialog()
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ dialog.showDialog();
+ }-*/;
+
+ @Override
+ public native void disposeDialog()
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ dialog.disposeDialog();
+ }-*/;
+
+ @Override
+ public native void showMessage(String message)
+ /*-{
+ var dialog = this.@com.actelion.research.gwt.gui.generic.JSDialog::getJsDialog()();
+ dialog.showMessage(message);
+ }-*/;
+
+ private void fireOk() {
+ if (mConsumer != null) {
+ mConsumer.eventHappened(new GenericActionEvent(this, GenericActionEvent.WHAT_OK, 0));
+ }
+ }
+
+ private void fireCancel() {
+ if (mConsumer != null) {
+ mConsumer.eventHappened(new GenericActionEvent(this, GenericActionEvent.WHAT_CANCEL, 0));
+ }
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java b/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java
new file mode 100644
index 00000000..8a5f75b9
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSDrawContext.java
@@ -0,0 +1,216 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class JSDrawContext implements GenericDrawContext {
+ private JavaScriptObject mJsContext;
+
+ public JSDrawContext(JavaScriptObject jsContext) {
+ mJsContext = jsContext;
+ }
+
+ private JavaScriptObject getJsContext() {
+ return mJsContext;
+ }
+
+ public native void clearRect(double x, double y, double w, double h)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.clearRect(x, y, w, h);
+ }-*/;
+
+ @Override
+ public native GenericImage createARGBImage(int width, int height)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ var image = ctx.createARGBImage(width, height);
+ return @com.actelion.research.gwt.gui.generic.JSImage::new(Lcom/google/gwt/core/client/JavaScriptObject;)(image);
+ }-*/;
+
+ @Override
+ public native void drawCenteredString(double x, double y, String s)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.drawCenteredString(x, y, s);
+ }-*/;
+
+ @Override
+ public native void drawCircle(double x, double y, double d)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.drawCircle(x, y, d);
+ }-*/;
+
+ @Override
+ public native void drawDottedLine(double x1, double y1, double x2, double y2)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.drawDottedLine(x1, y1, x2, y2);
+ }-*/;
+
+ @Override
+ public native void drawImage(GenericImage image, double x, double y)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()();
+ return ctx.drawImage(jsImage, x, y);
+ }-*/;
+
+ @Override
+ public native void drawImage(GenericImage image, double sx, double sy, double dx, double dy, double w, double h)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()();
+ return ctx.drawImage(jsImage, sx, sy, dx, dy, w, h);
+ }-*/;
+
+ @Override
+ public native void drawImage(GenericImage image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ var jsImage = image.@com.actelion.research.gui.generic.GenericImage::get()();
+ return ctx.drawImage(jsImage, sx, sy, sw, sh, dx, dy, dw, dh);
+ }-*/;
+
+ @Override
+ public native void drawLine(double x1, double y1, double x2, double y2)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.drawLine(x1, y1, x2, y2);
+ }-*/;
+
+ @Override
+ public void drawPolygon(GenericPolygon p) {
+ drawPolygon(new JSPolygon(p));
+ }
+
+ public native void drawPolygon(JSPolygon jsPolygon)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.drawPolygon(jsPolygon);
+ }-*/;
+
+ @Override
+ public native void drawRectangle(double x, double y, double w, double h)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.drawRectangle(x, y, w, h);
+ }-*/;
+
+ @Override
+ public native void drawString(double x, double y, String s)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.drawString(x, y, s);
+ }-*/;
+
+ @Override
+ public native void fillCircle(double x, double y, double d)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.fillCircle(x, y, d);
+ }-*/;
+
+ @Override
+ public void fillPolygon(GenericPolygon p) {
+ fillPolygon(new JSPolygon(p));
+ }
+
+ public native void fillPolygon(JSPolygon jsPolygon)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.fillPolygon(jsPolygon);
+ }-*/;
+
+ @Override
+ public native void fillRectangle(double x, double y, double w, double h)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.fillRectangle(x, y, w, h);
+ }-*/;
+
+ @Override
+ public native int getBackgroundRGB()
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.getBackgroundRGB();
+ }-*/;
+
+ @Override
+ public GenericRectangle getBounds(String s) {
+ JSRectangle jsRect = new JSRectangle();
+ getBounds(s, jsRect);
+ return jsRect.getRectangle();
+ }
+
+ public native GenericRectangle getBounds(String s, JSRectangle jsRect)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.getBounds(s, jsRect);
+ }-*/;
+
+ @Override
+ public native int getFontSize()
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.getFontSize();
+ }-*/;
+
+ @Override
+ public native int getForegroundRGB()
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.getForegroundRGB();
+ }-*/;
+
+ @Override
+ public native float getLineWidth()
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.getLineWidth();
+ }-*/;
+
+ @Override
+ public native int getRGB()
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.getRGB();
+ }-*/;
+
+ @Override
+ public native int getSelectionBackgroundRGB()
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.getSelectionBackgroundRGB();
+ }-*/;
+
+ @Override
+ public native boolean isDarkBackground()
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.isDarkBackground();
+ }-*/;
+
+ @Override
+ public native void setFont(int size, boolean isBold, boolean isItalic)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.setFont(size, isBold, isItalic);
+ }-*/;
+
+ @Override
+ public native void setLineWidth(float lineWidth)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.setLineWidth(lineWidth);
+ }-*/;
+
+ @Override
+ public native void setRGB(int rgb)
+ /*-{
+ var ctx = this.@com.actelion.research.gwt.gui.generic.JSDrawContext::getJsContext()();
+ return ctx.setRGB(rgb);
+ }-*/;
+
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java
new file mode 100644
index 00000000..01011fbe
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSEditorArea.java
@@ -0,0 +1,179 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.chem.StereoMolecule;
+import com.actelion.research.gui.editor.EditorEvent;
+import com.actelion.research.gui.editor.GenericEditorArea;
+import com.actelion.research.gui.generic.*;
+import com.actelion.research.gwt.minimal.JSMolecule;
+import com.actelion.research.gwt.minimal.JSReaction;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import info.clearthought.layout.TableLayout;
+
+import jsinterop.annotations.*;
+
+@JsType(name = "EditorArea")
+public class JSEditorArea implements GenericCanvas {
+ private GenericEditorArea mDrawArea;
+ private JavaScriptObject mOptions;
+ private JSMouseHandler mMouseHandler;
+ private JSKeyHandler mKeyHandler;
+
+ public static final int EDITOR_EVENT_MOLECULE_CHANGED = EditorEvent.WHAT_MOLECULE_CHANGED;
+ public static final int EDITOR_EVENT_SELECTION_CHANGED = EditorEvent.WHAT_SELECTION_CHANGED;
+ public static final int EDITOR_EVENT_HIGHLIGHT_ATOM_CHANGED = EditorEvent.WHAT_HILITE_ATOM_CHANGED;
+ public static final int EDITOR_EVENT_HIGHLIGHT_BOND_CHANGED = EditorEvent.WHAT_HILITE_BOND_CHANGED;
+
+ public static final int MOUSE_EVENT_PRESSED = GenericMouseEvent.MOUSE_PRESSED;
+ public static final int MOUSE_EVENT_RELEASED = GenericMouseEvent.MOUSE_RELEASED;
+ public static final int MOUSE_EVENT_CLICKED = GenericMouseEvent.MOUSE_CLICKED;
+ public static final int MOUSE_EVENT_ENTERED = GenericMouseEvent.MOUSE_ENTERED;
+ public static final int MOUSE_EVENT_EXITED = GenericMouseEvent.MOUSE_EXITED;
+ public static final int MOUSE_EVENT_MOVED = GenericMouseEvent.MOUSE_MOVED;
+ public static final int MOUSE_EVENT_DRAGGED = GenericMouseEvent.MOUSE_DRAGGED;
+
+ public static final int KEY_CTRL = GenericKeyEvent.KEY_CTRL;
+ public static final int KEY_ALT = GenericKeyEvent.KEY_ALT;
+ public static final int KEY_SHIFT = GenericKeyEvent.KEY_SHIFT;
+ public static final int KEY_DELETE = GenericKeyEvent.KEY_DELETE;
+ public static final int KEY_BACK_SPACE = GenericKeyEvent.KEY_BACK_SPACE;
+ public static final int KEY_HELP = GenericKeyEvent.KEY_HELP;
+ public static final int KEY_ESCAPE = GenericKeyEvent.KEY_ESCAPE;
+
+ public static final int KEY_EVENT_PRESSED = GenericKeyEvent.KEY_PRESSED;
+ public static final int KEY_EVENT_RELEASED = GenericKeyEvent.KEY_RELEASED;
+
+ public static final int cChainCursor = GenericCursorHelper.cChainCursor;
+ public static final int cDeleteCursor = GenericCursorHelper.cDeleteCursor;
+ public static final int cHandCursor = GenericCursorHelper.cHandCursor;
+ public static final int cHandPlusCursor = GenericCursorHelper.cHandPlusCursor;
+ public static final int cFistCursor = GenericCursorHelper.cFistCursor;
+ public static final int cLassoCursor = GenericCursorHelper.cLassoCursor;
+ public static final int cLassoPlusCursor = GenericCursorHelper.cLassoPlusCursor;
+ public static final int cSelectRectCursor = GenericCursorHelper.cSelectRectCursor;
+ public static final int cSelectRectPlusCursor = GenericCursorHelper.cSelectRectPlusCursor;
+ public static final int cZoomCursor = GenericCursorHelper.cZoomCursor;
+ public static final int cInvisibleCursor = GenericCursorHelper.cInvisibleCursor;
+ public static final int cPointerCursor = GenericCursorHelper.cPointerCursor;
+ public static final int cTextCursor = GenericCursorHelper.cTextCursor;
+ public static final int cPointedHandCursor = GenericCursorHelper.cPointedHandCursor;
+ public static final int[] HOTSPOT_32 = GenericCursorHelper.HOTSPOT_32;
+
+ public static final int TableLayoutPreferred = (int)TableLayout.PREFERRED;
+ public static final int TableLayoutFill = (int)TableLayout.FILL;
+
+ public JSEditorArea(JavaScriptObject options) {
+ mDrawArea = new GenericEditorArea(new StereoMolecule(), 0, new JSUIHelper(options), this);
+ mDrawArea.addDrawAreaListener(new GenericEventListener() {
+ @Override
+ public void eventHappened(EditorEvent e) {
+ callJsEventListener(e.getWhat(), e.isUserChange());
+ }
+ });
+ mOptions = options;
+
+ mMouseHandler = new JSMouseHandler(mDrawArea);
+ mMouseHandler.addListener(mDrawArea);
+
+ mKeyHandler = new JSKeyHandler(mDrawArea);
+ mKeyHandler.addListener(mDrawArea);
+
+ JavaScriptObject clipboardHandler = getClipboardHandler();
+ mDrawArea.setClipboardHandler(new JSClipboardHandler(clipboardHandler));
+ }
+
+ public void setMolecule(JSMolecule molecule) {
+ mDrawArea.setMolecule(molecule.getStereoMolecule());
+ }
+
+ public JSMolecule getMolecule() {
+ return new JSMolecule(mDrawArea.getMolecule());
+ }
+
+ public void setReaction(JSReaction reaction) {
+ mDrawArea.setReaction(reaction.getReaction());
+ }
+
+ public JSReaction getReaction() {
+ return new JSReaction(mDrawArea.getReaction());
+ }
+
+ private native JavaScriptObject getClipboardHandler()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()();
+ return options.getClipboardHandler();
+ }-*/;
+
+ private native JavaScriptObject callJsEventListener(int what, boolean isUserChange)
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()();
+ return options.onChange(what, isUserChange);
+ }-*/;
+
+ public void draw() {
+ mDrawArea.paintContent(getDrawContext());
+ }
+
+ public GenericEditorArea getGenericEditorArea() {
+ return mDrawArea;
+ }
+
+ public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) {
+ GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mDrawArea);
+ mMouseHandler.fireEvent(gme);
+ }
+
+ public void fireKeyEvent(int what, int key, boolean isAltDown, boolean isCtrlDown, boolean isShiftDown, boolean isMenuShortcut) {
+ GenericKeyEvent gke = new GenericKeyEvent(what, key, isAltDown, isCtrlDown, isShiftDown, isMenuShortcut, mDrawArea);
+ mKeyHandler.fireEvent(gke);
+ }
+
+ public void toolChanged(int newTool) {
+ mDrawArea.toolChanged(newTool);
+ }
+
+ private JavaScriptObject getOptions() {
+ return mOptions;
+ }
+
+ @Override
+ @JsIgnore
+ public native int getBackgroundRGB()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()();
+ return options.getBackgroundRGB();
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public native double getCanvasHeight()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()();
+ return options.getCanvasHeight();
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public native double getCanvasWidth()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()();
+ return options.getCanvasWidth();
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public GenericDrawContext getDrawContext() {
+ return new JSDrawContext(getDrawContextFromOptions());
+ }
+
+ private native JavaScriptObject getDrawContextFromOptions()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorArea::getOptions()();
+ return options.getDrawContext();
+ }-*/;
+
+ @Override
+ public void repaint() {
+ draw();
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java
new file mode 100644
index 00000000..1bb037a2
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSEditorToolbar.java
@@ -0,0 +1,98 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.editor.GenericEditorArea;
+import com.actelion.research.gui.editor.GenericEditorToolbar;
+import com.actelion.research.gui.generic.*;
+import com.actelion.research.gwt.minimal.JSMolecule;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import jsinterop.annotations.*;
+
+@JsType(name = "EditorToolbar")
+public class JSEditorToolbar implements GenericCanvas {
+ private GenericEditorToolbar mGenericToolbar;
+ private JavaScriptObject mOptions;
+ private JSMouseHandler mMouseHandler;
+
+ public JSEditorToolbar(JSEditorArea jsEditorArea, JavaScriptObject options) {
+ mGenericToolbar = new GenericEditorToolbar(this, jsEditorArea.getGenericEditorArea());
+ mOptions = options;
+
+ mMouseHandler = new JSMouseHandler(mGenericToolbar);
+ mMouseHandler.addListener(mGenericToolbar);
+
+ setDimensions(mGenericToolbar.getWidth(), mGenericToolbar.getHeight());
+ draw();
+ }
+
+ public int getWidth() {
+ return mGenericToolbar.getWidth();
+ }
+
+ public int getHeight() {
+ return mGenericToolbar.getHeight();
+ }
+
+ public void draw() {
+ JSDrawContext ctx = getDrawContext();
+ ctx.clearRect(0, 0, getWidth(), getHeight());
+ mGenericToolbar.paintContent(getDrawContext());
+ }
+
+ public void fireMouseEvent(int what, int button, int clickCount, int x, int y, boolean shiftDown, boolean ctrlDown, boolean altDown, boolean isPopupTrigger) {
+ GenericMouseEvent gme = new GenericMouseEvent(what, button, clickCount, x, y, shiftDown, ctrlDown, altDown, isPopupTrigger, mGenericToolbar);
+ mMouseHandler.fireEvent(gme);
+ }
+
+ private JavaScriptObject getOptions() {
+ return mOptions;
+ }
+
+ public native void setDimensions(int width, int height)
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()();
+ return options.setDimensions(width, height);
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public native int getBackgroundRGB()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()();
+ return options.getBackgroundRGB();
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public native double getCanvasHeight()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()();
+ return options.getCanvasHeight();
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public native double getCanvasWidth()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()();
+ return options.getCanvasWidth();
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public JSDrawContext getDrawContext() {
+ return new JSDrawContext(getDrawContextFromOptions());
+ }
+
+ private native JavaScriptObject getDrawContextFromOptions()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSEditorToolbar::getOptions()();
+ return options.getDrawContext();
+ }-*/;
+
+ @Override
+ @JsIgnore
+ public void repaint() {
+ draw();
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSImage.java b/src/com/actelion/research/gwt/gui/generic/JSImage.java
new file mode 100644
index 00000000..e54511db
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSImage.java
@@ -0,0 +1,58 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.GenericImage;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class JSImage implements GenericImage {
+ private JavaScriptObject mJsImage;
+
+ public JSImage(JavaScriptObject jsImage) {
+ mJsImage = jsImage;
+ }
+
+ private JavaScriptObject getJsImage() {
+ return mJsImage;
+ }
+
+ @Override
+ public native void scale(int width, int height)
+ /*-{
+ var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()();
+ jsImage.scale(width, height);
+ }-*/;
+
+ @Override
+ public native Object get()
+ /*-{
+ var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()();
+ return jsImage;
+ }-*/;
+
+ @Override
+ public native int getWidth()
+ /*-{
+ var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()();
+ return jsImage.getWidth();
+ }-*/;
+
+ @Override
+ public native int getHeight()
+ /*-{
+ var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()();
+ return jsImage.getHeight();
+ }-*/;
+
+ @Override
+ public native int getRGB(int x, int y)
+ /*-{
+ var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()();
+ return jsImage.getRGB(x, y);
+ }-*/;
+
+ @Override
+ public native void setRGB(int x, int y, int argb)
+ /*-{
+ var jsImage = this.@com.actelion.research.gwt.gui.generic.JSImage::getJsImage()();
+ jsImage.setRGB(x, y, argb);
+ }-*/;
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java b/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java
new file mode 100644
index 00000000..ffa79bc5
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSKeyHandler.java
@@ -0,0 +1,9 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+
+public class JSKeyHandler extends GenericEventHandler {
+ public JSKeyHandler(Object source) {
+ super(source);
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSLabel.java b/src/com/actelion/research/gwt/gui/generic/JSLabel.java
new file mode 100644
index 00000000..81517813
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSLabel.java
@@ -0,0 +1,17 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class JSLabel extends JSComponent implements GenericLabel {
+ public JSLabel(JavaScriptObject jsLabel) {
+ super(jsLabel);
+ }
+
+ @Override
+ public native void setText(String text)
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSLabel::getJsComponent()();
+ return component.setText(text);
+ }-*/;
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java b/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java
new file mode 100644
index 00000000..5b8e4f0d
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSMouseHandler.java
@@ -0,0 +1,9 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+
+public class JSMouseHandler extends GenericEventHandler {
+ public JSMouseHandler(Object source) {
+ super(source);
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSPolygon.java b/src/com/actelion/research/gwt/gui/generic/JSPolygon.java
new file mode 100644
index 00000000..87903005
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSPolygon.java
@@ -0,0 +1,27 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.GenericPolygon;
+
+import jsinterop.annotations.*;
+
+@JsType(name = "Polygon")
+public class JSPolygon {
+ private GenericPolygon mPolygon;
+
+ @JsIgnore
+ JSPolygon(GenericPolygon polygon) {
+ mPolygon = polygon;
+ }
+
+ public int getSize() {
+ return mPolygon.getSize();
+ }
+
+ public double getX(int i) {
+ return mPolygon.getX(i);
+ }
+
+ public double getY(int i) {
+ return mPolygon.getY(i);
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSRectangle.java b/src/com/actelion/research/gwt/gui/generic/JSRectangle.java
new file mode 100644
index 00000000..cb1f6a97
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSRectangle.java
@@ -0,0 +1,23 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.GenericRectangle;
+
+import jsinterop.annotations.*;
+
+@JsType(name = "Rectangle")
+public class JSRectangle {
+ private GenericRectangle mRectangle;
+
+ JSRectangle() {
+ mRectangle = new GenericRectangle();
+ }
+
+ public void set(double x, double y, double w, double h) {
+ mRectangle.set(x, y, w, h);
+ }
+
+ @JsIgnore()
+ public GenericRectangle getRectangle() {
+ return mRectangle;
+ }
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSTextField.java b/src/com/actelion/research/gwt/gui/generic/JSTextField.java
new file mode 100644
index 00000000..06340a24
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSTextField.java
@@ -0,0 +1,24 @@
+package com.actelion.research.gwt.gui.generic;
+
+import com.actelion.research.gui.generic.*;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class JSTextField extends JSComponent implements GenericTextField {
+ public JSTextField(JavaScriptObject jsTextField) {
+ super(jsTextField);
+ }
+
+ @Override
+ public native String getText()
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSTextField::getJsComponent()();
+ return component.getText();
+ }-*/;
+
+ @Override
+ public native void setText(String text)
+ /*-{
+ var component = this.@com.actelion.research.gwt.gui.generic.JSTextField::getJsComponent()();
+ return component.setText(text);
+ }-*/;
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java
new file mode 100644
index 00000000..de17fb2a
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/JSUIHelper.java
@@ -0,0 +1,106 @@
+package com.actelion.research.gwt.gui.generic;
+
+import java.io.File;
+
+import com.actelion.research.gui.generic.*;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class JSUIHelper implements GenericUIHelper {
+ private JavaScriptObject mOptions;
+
+ public JSUIHelper(JavaScriptObject options) {
+ mOptions = options;
+ }
+
+ private JavaScriptObject getOptions() {
+ return mOptions;
+ }
+
+ @Override
+ public GenericDialog createDialog(String title, GenericEventListener consumer) {
+ JSDialog dialog = new JSDialog(createNativeDialog(title));
+ dialog.setEventConsumer(consumer);
+ return dialog;
+ }
+
+ public native JavaScriptObject createNativeDialog(String title)
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()();
+ return options.createDialog(title);
+ }-*/;
+
+ @Override
+ public GenericImage createImage(String name) {
+ if (name.equals("editorButtons.png")) {
+ String data = ImageData.editorButtonsData0 + ImageData.editorButtonsData1 + ImageData.editorButtonsData2 + ImageData.editorButtonsData3 + ImageData.editorButtonsData4 + ImageData.editorButtonsData5;
+ return new JSImage(createImageFromBase64(ImageData.editorButtonsWidth, ImageData.editorButtonsHeight, data));
+ } else if (name.equals("esrButtons.png")) {
+ return new JSImage(createImageFromBase64(ImageData.esrButtonsWidth, ImageData.esrButtonsHeight, ImageData.esrButtonsData0));
+ } else {
+ return null;
+ }
+ }
+
+ private native JavaScriptObject createImageFromBase64(int width, int height, String base64)
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()();
+ return options.createImageFromBase64(width, height, base64);
+ }-*/;
+
+ @Override
+ public native GenericImage createImage(int width, int height)
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()();
+ var image = options.createImage(width, height);
+ return @com.actelion.research.gwt.gui.generic.JSImage::new(Lcom/google/gwt/core/client/JavaScriptObject;)(image);
+ }-*/;
+
+ @Override
+ public GenericPopupMenu createPopupMenu(GenericEventListener consumer) {
+ // TODO: implement popup menu.
+ return null;
+ }
+
+ @Override
+ public native void grabFocus()
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()();
+ return options.grabFocus();
+ }-*/;
+
+ @Override
+ public File openChemistryFile(boolean isReaction) {
+ return null;
+ }
+
+ @Override
+ public native void runLater(Runnable r)
+ /*-{
+ var that = this;
+ $wnd.requestAnimationFrame(function(){
+ that.@com.actelion.research.gwt.gui.generic.JSUIHelper::runNow(Ljava/lang/Runnable;)(r);
+ });
+ }-*/;
+
+ private void runNow(Runnable r) {
+ r.run();
+ }
+
+ @Override
+ public native void setCursor(int cursor)
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()();
+ return options.setCursor(cursor);
+ }-*/;
+
+ @Override
+ public native void showHelpDialog(String url, String title)
+ /*-{
+ var options = this.@com.actelion.research.gwt.gui.generic.JSUIHelper::getOptions()();
+ return options.showHelpDialog(url, title);
+ }-*/;
+
+ @Override
+ public void showMessage(String message) {}
+
+}
diff --git a/src/com/actelion/research/gwt/gui/generic/package-info.java b/src/com/actelion/research/gwt/gui/generic/package-info.java
new file mode 100644
index 00000000..0eb34f94
--- /dev/null
+++ b/src/com/actelion/research/gwt/gui/generic/package-info.java
@@ -0,0 +1,3 @@
+@jsinterop.annotations.JsPackage(namespace = "OCL")
+
+package com.actelion.research.gwt.gui.generic;
diff --git a/src/com/actelion/research/gwt/jre/java/awt/Color.java b/src/com/actelion/research/gwt/jre/java/awt/Color.java
index a365a341..7bb9b6ab 100644
--- a/src/com/actelion/research/gwt/jre/java/awt/Color.java
+++ b/src/com/actelion/research/gwt/jre/java/awt/Color.java
@@ -159,4 +159,23 @@ public float[] getRGBComponents(float[] compArray) {
return f;
}
+ public float[] getRGBColorComponents(float[] compArray) {
+ float[] f;
+ if (compArray == null) {
+ f = new float[3];
+ } else {
+ f = compArray;
+ }
+ if (realRGBValues == null) {
+ f[0] = ((float) getRed()) / 255f;
+ f[1] = ((float) getGreen()) / 255f;
+ f[2] = ((float) getBlue()) / 255f;
+ } else {
+ f[0] = realRGBValues[0];
+ f[1] = realRGBValues[1];
+ f[2] = realRGBValues[2];
+ }
+ return f;
+ }
+
}
diff --git a/src/com/actelion/research/gwt/jre/java/awt/Font.java b/src/com/actelion/research/gwt/jre/java/awt/Font.java
index 2e9b4b32..b4f54780 100644
--- a/src/com/actelion/research/gwt/jre/java/awt/Font.java
+++ b/src/com/actelion/research/gwt/jre/java/awt/Font.java
@@ -38,6 +38,8 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
public class Font {
public static final int PLAIN = 0;
+ public static final int BOLD = 1;
+ public static final int ITALIC = 2;
private String name;
private int size;
diff --git a/src/com/actelion/research/gwt/jre/java/awt/Image.java b/src/com/actelion/research/gwt/jre/java/awt/Image.java
new file mode 100644
index 00000000..781055f4
--- /dev/null
+++ b/src/com/actelion/research/gwt/jre/java/awt/Image.java
@@ -0,0 +1,36 @@
+/*
+
+Copyright (c) 2015-2017, cheminfo
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of {{ project }} nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+package java.awt;
+
+public abstract class Image {
+}
diff --git a/src/com/actelion/research/gwt/jre/java/lang/Character.java b/src/com/actelion/research/gwt/jre/java/lang/Character.java
index 714ff4a2..d17710d5 100644
--- a/src/com/actelion/research/gwt/jre/java/lang/Character.java
+++ b/src/com/actelion/research/gwt/jre/java/lang/Character.java
@@ -309,6 +309,10 @@ public static boolean isValidCodePoint(int codePoint) {
return codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT;
}
+ public static native boolean isWhitespace(char c)/*-{
+ return String.fromCodePoint(c).trim() === '';
+ }-*/;
+
public static int offsetByCodePoints(char[] a, int start, int count, int index, int codePointOffset) {
return offsetByCodePoints(new CharSequenceAdapter(a, start, count), index, codePointOffset);
}
diff --git a/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java b/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java
index 3f2b0e91..360df6af 100644
--- a/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java
+++ b/src/com/actelion/research/gwt/jre/javax/swing/UIManager.java
@@ -3,7 +3,7 @@
public class UIManager {
private final static LookAndFeel lookAndFeel = new LookAndFeel();
- static LookAndFeel getLookAndFeel() {
+ public static LookAndFeel getLookAndFeel() {
return lookAndFeel;
}
}