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; } }