You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by ha...@apache.org on 2021/01/02 23:55:01 UTC
[incubator-hop] branch master updated: HOP-2386: Add RAP fragment
to the master branch
This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-hop.git
The following commit(s) were added to refs/heads/master by this push:
new a7d18b2 HOP-2386: Add RAP fragment to the master branch
new edeb72a Merge pull request #526 from HiromuHota/HOP-2386
a7d18b2 is described below
commit a7d18b2f6e832461f51e1313d805e1c7a00c9df6
Author: Hiromu Hota <hi...@gmail.com>
AuthorDate: Sat Jan 2 10:42:31 2021 -0800
HOP-2386: Add RAP fragment to the master branch
---
pom.xml | 1 +
rap/pom.xml | 51 +++++
.../apache/hop/ui/core/gui/GuiResourceImpl.java | 27 +++
.../org/apache/hop/ui/hopgui/CanvasFacadeImpl.java | 168 +++++++++++++++++
.../apache/hop/ui/hopgui/CanvasListenerImpl.java | 46 +++++
.../java/org/apache/hop/ui/hopgui/Clipboard.java | 114 ++++++++++++
.../apache/hop/ui/hopgui/ClipboardListener.java | 33 ++++
.../hop/ui/hopgui/DownloadServiceHandler.java | 60 ++++++
.../java/org/apache/hop/ui/hopgui/HopGuiImpl.java | 26 +++
.../main/java/org/apache/hop/ui/hopgui/HopWeb.java | 58 ++++++
.../org/apache/hop/ui/hopgui/HopWebEntryPoint.java | 75 ++++++++
.../ui/hopgui/HopWebServletContextListener.java | 62 +++++++
.../hop/ui/hopgui/ServerPushSessionFacadeImpl.java | 34 ++++
.../hop/ui/hopgui/TextSizeUtilFacadeImpl.java | 31 ++++
.../hop/ui/hopgui/context/GuiContextUtilImpl.java | 27 +++
.../org/eclipse/jface/window/DefaultToolTip.java | 43 +++++
.../java/org/eclipse/jface/window/ToolTip.java | 81 ++++++++
.../resources/org/apache/hop/ui/hopgui/canvas.js | 206 +++++++++++++++++++++
.../org/apache/hop/ui/hopgui/clipboard.js | 149 +++++++++++++++
19 files changed, 1292 insertions(+)
diff --git a/pom.xml b/pom.xml
index f42e0fa..e556af7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -887,6 +887,7 @@
<module>core</module>
<module>engine</module>
<module>ui</module>
+ <module>rap</module>
<module>rcp</module>
<module>plugins</module>
</modules>
diff --git a/rap/pom.xml b/rap/pom.xml
new file mode 100644
index 0000000..c2d12f3
--- /dev/null
+++ b/rap/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.hop</groupId>
+ <artifactId>hop-ui-rap</artifactId>
+ <packaging>jar</packaging>
+ <version>0.60-SNAPSHOT</version>
+
+ <name>Hop GUI (RAP fragment)</name>
+
+ <parent>
+ <groupId>org.apache.hop</groupId>
+ <artifactId>hop</artifactId>
+ <version>0.60-SNAPSHOT</version>
+ </parent>
+
+ <properties>
+ <!-- webSpoon specific dependencies -->
+ <rap.version>3.13.0</rap.version>
+ </properties>
+
+
+ <dependencies>
+ <!-- Hop dependencies -->
+ <dependency>
+ <groupId>org.apache.hop</groupId>
+ <artifactId>hop-ui</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.eclipse.platform</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- Third-party dependencies -->
+ <dependency>
+ <groupId>org.eclipse.rap</groupId>
+ <artifactId>org.eclipse.rap.rwt</artifactId>
+ <version>${rap.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.rap</groupId>
+ <artifactId>org.eclipse.rap.jface</artifactId>
+ <version>${rap.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/rap/src/main/java/org/apache/hop/ui/core/gui/GuiResourceImpl.java b/rap/src/main/java/org/apache/hop/ui/core/gui/GuiResourceImpl.java
new file mode 100644
index 0000000..e7e0f3f
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/core/gui/GuiResourceImpl.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.core.gui;
+
+import org.apache.hop.ui.hopgui.ISingletonProvider;
+import org.eclipse.rap.rwt.SingletonUtil;
+
+public class GuiResourceImpl implements ISingletonProvider {
+ public Object getInstanceInternal() {
+ return SingletonUtil.getSessionInstance( GuiResource.class );
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasFacadeImpl.java b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasFacadeImpl.java
new file mode 100644
index 0000000..4bef25a
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasFacadeImpl.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import org.apache.hop.base.AbstractMeta;
+import org.apache.hop.core.SwtUniversalImage;
+import org.apache.hop.pipeline.PipelineHopMeta;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.hopgui.file.workflow.HopGuiWorkflowGraph;
+import org.apache.hop.workflow.WorkflowHopMeta;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.eclipse.rap.json.JsonArray;
+import org.eclipse.rap.json.JsonObject;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Display;
+
+public class CanvasFacadeImpl extends CanvasFacade {
+
+ @Override
+ void setDataInternal( Canvas canvas, float magnification, AbstractMeta meta, Class type ) {
+ setDataCommon( canvas, magnification, meta );
+ if ( type == HopGuiWorkflowGraph.class ) {
+ setDataWorkflow( canvas, magnification, meta );
+ } else {
+ setDataPipeline( canvas, magnification, meta );
+ }
+ }
+
+ private void setDataCommon ( Canvas canvas, float magnification, AbstractMeta meta ) {
+ JsonObject jsonProps = new JsonObject();
+ jsonProps.add( "gridsize", PropsUi.getInstance().isShowCanvasGridEnabled() ? PropsUi.getInstance().getCanvasGridSize() : 1 );
+ jsonProps.add( "iconsize", PropsUi.getInstance().getIconSize() );
+ jsonProps.add( "magnification", magnification );
+ canvas.setData( "props", jsonProps );
+
+ JsonArray jsonNotes = new JsonArray();
+ meta.getNotes().forEach( note -> {
+ JsonObject jsonNote = new JsonObject();
+ jsonNote.add( "x", note.getLocation().x );
+ jsonNote.add( "y", note.getLocation().y );
+ jsonNote.add( "width", note.getWidth() );
+ jsonNote.add( "height", note.getHeight() );
+ jsonNote.add( "selected", note.isSelected() );
+ jsonNote.add( "note", note.getNote() );
+ jsonNotes.add( jsonNote );
+ } );
+ canvas.setData( "notes", jsonNotes );
+ }
+
+ private void setDataWorkflow ( Canvas canvas, float magnification, AbstractMeta meta ) {
+ final int iconSize = HopGui.getInstance().getProps().getIconSize();
+
+ WorkflowMeta workflowMeta = (WorkflowMeta) meta;
+ JsonObject jsonNodes = new JsonObject();
+ workflowMeta.getActions().forEach( actionMeta -> {
+ JsonObject jsonNode = new JsonObject();
+ jsonNode.add( "x", actionMeta.getLocation().x );
+ jsonNode.add( "y", actionMeta.getLocation().y );
+ jsonNode.add( "selected", actionMeta.isSelected() );
+
+ // Translated from org.apache.hop.ui.hopgui.shared.SwtGc.drawActionIcon(int, int, ActionMeta, float)
+ SwtUniversalImage swtImage = null;
+
+ if ( actionMeta.isMissing() ) {
+ swtImage = GuiResource.getInstance().getSwtImageMissing();
+ }
+ else {
+ String pluginId = actionMeta.getAction().getPluginId();
+ if ( pluginId != null ) {
+ swtImage = GuiResource.getInstance().getImagesActions().get( pluginId );
+ }
+ }
+
+ if ( swtImage == null ) {
+ return;
+ }
+
+ int w = Math.round( iconSize * magnification );
+ int h = Math.round( iconSize * magnification );
+ Image image = swtImage.getAsBitmapForSize( Display.getCurrent(), w, h );
+ // Translated
+
+ jsonNode.add( "img", image.internalImage.getResourceName() );
+ jsonNodes.add( actionMeta.getName(), jsonNode );
+ } );
+ canvas.setData( "nodes", jsonNodes );
+
+ JsonArray jsonHops = new JsonArray();
+ for ( int i = 0; i < workflowMeta.nrWorkflowHops(); i++ ) {
+ JsonObject jsonHop = new JsonObject();
+ WorkflowHopMeta hop = workflowMeta.getWorkflowHop( i );
+ jsonHop.add( "from", hop.getFromAction().getName() );
+ jsonHop.add( "to", hop.getToAction().getName() );
+ jsonHops.add( jsonHop );
+ }
+ canvas.setData( "hops", jsonHops );
+ }
+
+ private void setDataPipeline( Canvas canvas, float magnification, AbstractMeta meta ) {
+ final int iconSize = HopGui.getInstance().getProps().getIconSize();
+
+ PipelineMeta pipelineMeta = (PipelineMeta) meta;
+ JsonObject jsonNodes = new JsonObject();
+ pipelineMeta.getTransforms().forEach( transformMeta -> {
+ JsonObject jsonNode = new JsonObject();
+ jsonNode.add( "x", transformMeta.getLocation().x );
+ jsonNode.add( "y", transformMeta.getLocation().y );
+ jsonNode.add( "selected", transformMeta.isSelected() );
+
+ // Translated from org.apache.hop.ui.hopgui.shared.SwtGc.drawTransformIcon(int, int, TransformMeta, float)
+ SwtUniversalImage swtImage = null;
+
+ if ( transformMeta.isMissing() ) {
+ swtImage = GuiResource.getInstance().getSwtImageMissing();
+ } else if ( transformMeta.isDeprecated() ) {
+ swtImage = GuiResource.getInstance().getSwtImageDeprecated();
+ } else {
+ String pluginId = transformMeta.getPluginId();
+ if ( pluginId != null ) {
+ swtImage = GuiResource.getInstance().getImagesTransforms().get( pluginId );
+ }
+ }
+
+ if ( swtImage == null ) {
+ return;
+ }
+
+ int w = Math.round( iconSize * magnification );
+ int h = Math.round( iconSize * magnification );
+ Image image = swtImage.getAsBitmapForSize( Display.getCurrent(), w, h );
+ // Translated
+
+ jsonNode.add( "img", image.internalImage.getResourceName() );
+ jsonNodes.add( transformMeta.getName(), jsonNode );
+ } );
+ canvas.setData( "nodes", jsonNodes );
+
+ JsonArray jsonHops = new JsonArray();
+ for ( int i = 0; i < pipelineMeta.nrPipelineHops(); i++ ) {
+ JsonObject jsonHop = new JsonObject();
+ PipelineHopMeta hop = pipelineMeta.getPipelineHop( i );
+ if ( hop.getFromTransform() != null && hop.getToTransform() != null ) {
+ jsonHop.add( "from", hop.getFromTransform().getName() );
+ jsonHop.add( "to", hop.getToTransform().getName() );
+ jsonHops.add( jsonHop );
+ }
+ }
+ canvas.setData( "hops", jsonHops );
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasListenerImpl.java b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasListenerImpl.java
new file mode 100644
index 0000000..c5d2983
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasListenerImpl.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import java.io.IOException;
+import org.apache.commons.io.IOUtils;
+import org.eclipse.rap.rwt.SingletonUtil;
+import org.eclipse.rap.rwt.scripting.ClientListener;
+
+public class CanvasListenerImpl extends ClientListener implements ISingletonProvider{
+
+ @Override
+ public Object getInstanceInternal() {
+ return SingletonUtil.getSessionInstance( CanvasListenerImpl.class );
+ }
+
+ protected CanvasListenerImpl() {
+ super( getText() );
+ }
+
+ private static String getText() {
+ String canvasScript = null;
+ try {
+ canvasScript = IOUtils.toString( CanvasListenerImpl.class.getResourceAsStream( "canvas.js" ) );
+ } catch ( IOException e1 ) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ return canvasScript;
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/Clipboard.java b/rap/src/main/java/org/apache/hop/ui/hopgui/Clipboard.java
new file mode 100644
index 0000000..09afe53
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/Clipboard.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.rap.json.JsonObject;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.service.ClientFileLoader;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+import org.eclipse.rap.rwt.remote.AbstractOperationHandler;
+import org.eclipse.rap.rwt.remote.Connection;
+import org.eclipse.rap.rwt.remote.RemoteObject;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.rap.rwt.widgets.WidgetUtil;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Widget;
+
+/**
+ * Clipboard implemented as a custom widget. This class is meant to be instantiated only once per UI session.
+ */
+public class Clipboard extends Widget {
+
+ private RemoteObject remoteObject;
+ private List<ClipboardListener> listeners = new ArrayList<ClipboardListener>();
+
+ public Clipboard( Composite parent ) {
+ super( parent, SWT.NONE );
+ ResourceManager resourceManager = RWT.getResourceManager();
+ ClientFileLoader clientFileLoader = RWT.getClient().getService( ClientFileLoader.class );
+ clientFileLoader.requireJs( resourceManager.getLocation( "js/clipboard.js" ) );
+
+ Connection connection = RWT.getUISession().getConnection();
+ remoteObject = connection.createRemoteObject( "webSpoon.Clipboard" );
+ remoteObject.set( "parent", WidgetUtil.getId( this ) );
+ remoteObject.set( "self", remoteObject.getId() );
+ remoteObject.setHandler( new AbstractOperationHandler() {
+ @Override
+ public void handleNotify(String event, JsonObject properties) {
+ String widgetId = properties.get( "widgetId" ).asString();
+ if ( event.equals( "paste" ) ) {
+ listeners.stream()
+ .filter( l -> l.getWidgetId().equals( widgetId ) )
+ .forEach( l -> l.pasteListener( properties.get( "text" ).asString() ) );
+ } else if ( event.equals( "cut" ) ) {
+ listeners.stream()
+ .filter( l -> l.getWidgetId().equals( widgetId ) )
+ .forEach( l -> l.cutListener() );
+ }
+ }
+ } );
+ remoteObject.listen( "paste", true );
+ remoteObject.listen( "copy", true );
+ remoteObject.listen( "cut", true );
+ }
+
+ public void dispose() {
+ remoteObject.destroy();
+ }
+
+ /**
+ * Set data to the clipboard. This should be called before cut/copy.
+ * @param text
+ */
+ public void setContents( String text ) {
+ remoteObject.set( "text", text );
+ }
+
+ /**
+ * Attach a widget to the clipboard. This should be called before any clipboard event.
+ * @param widget
+ */
+ public void attachToClipboard( Widget widget ) {
+ String widgetId = WidgetUtil.getId( widget );
+ JavaScriptExecutor executor = RWT.getClient().getService( JavaScriptExecutor.class );
+ executor.execute(
+ "var x = document.getElementById( 'input-clipboard' );\n"
+ + "x.value='" + widgetId + "';\n"
+ + "x.focus();"
+ );
+ }
+
+ public void addClipboardListener( ClipboardListener listener ) {
+ this.listeners.add( listener );
+ }
+
+ public void removeClipboardListener( ClipboardListener listener ) {
+ this.listeners.remove( listener );
+ }
+
+ public void downloadCanvasImage( String rwtId, String name ) {
+ JsonObject obj = new JsonObject();
+ obj.add( "rwtId", rwtId );
+ obj.add( "name", name );
+ remoteObject.call( "downloadCanvasImage", obj );
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/ClipboardListener.java b/rap/src/main/java/org/apache/hop/ui/hopgui/ClipboardListener.java
new file mode 100644
index 0000000..c7a136f
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/ClipboardListener.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+public interface ClipboardListener {
+
+ /**
+ * Return the Id of the associated widget.
+ * See {@link org.eclipse.rap.rwt.widgets.WidgetUtil#getId(Widget)}.
+ * @return
+ */
+ String getWidgetId();
+
+ void pasteListener( String text );
+
+ void cutListener();
+
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/DownloadServiceHandler.java b/rap/src/main/java/org/apache/hop/ui/hopgui/DownloadServiceHandler.java
new file mode 100644
index 0000000..4a9bce2
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/DownloadServiceHandler.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.rap.rwt.service.ServiceHandler;
+
+public class DownloadServiceHandler implements ServiceHandler {
+
+ public void service( HttpServletRequest request, HttpServletResponse response )
+ throws IOException, ServletException {
+ // Which file to download?
+ String fileName = request.getParameter( "filename" );
+ // Get the file content
+ File file = new File( fileName );
+ FileInputStream fin = null;
+ byte[] download = null;
+ try {
+ fin = new FileInputStream( file );
+ download = new byte[(int) file.length() ];
+ fin.read( download );
+ } catch ( IOException e ) {
+ throw new IOException( e.getMessage() );
+ }
+ fin.close();
+ /*
+ * TODO : if the following line is not executed, the exported xml file remains in the server.
+ * There might be a better way to ensure the file is deleted.
+ */
+ file.delete();
+ // Send the file in the response
+ response.setContentType( "application/octet-stream" );
+ response.setContentLength( download.length );
+ String contentDisposition = "attachment; filename=\"" + file.getName() + "\"";
+ response.setHeader( "Content-Disposition", contentDisposition );
+ response.getOutputStream().write( download );
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopGuiImpl.java b/rap/src/main/java/org/apache/hop/ui/hopgui/HopGuiImpl.java
new file mode 100644
index 0000000..d965d88
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopGuiImpl.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import org.eclipse.rap.rwt.SingletonUtil;
+
+public class HopGuiImpl implements ISingletonProvider {
+ public Object getInstanceInternal() {
+ return SingletonUtil.getSessionInstance( HopGui.class );
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWeb.java b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWeb.java
new file mode 100644
index 0000000..14f9445
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWeb.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FilenameUtils;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+public class HopWeb implements ApplicationConfiguration {
+
+ public void configure( Application application ) {
+ application.addResource( "ui/images/hopUi.ico", new ResourceLoader() {
+ public InputStream getResourceAsStream( String resourceName ) throws IOException {
+ return this.getClass().getClassLoader().getResourceAsStream( "ui/images/hopUi.ico" );
+ }
+ } );
+ Arrays.asList(
+ "org/apache/hop/ui/hopgui/clipboard.js"
+ ).stream().forEach( str -> {
+ application.addResource( "js/" + FilenameUtils.getName( str ), new ResourceLoader() {
+ @Override
+ public InputStream getResourceAsStream( String resourceName ) throws IOException {
+ return this.getClass().getClassLoader().getResourceAsStream( str );
+ }
+ } );
+ });
+ Map<String, String> properties = new HashMap<String, String>();
+ properties.put( WebClient.PAGE_TITLE, "Hop" );
+ properties.put( WebClient.FAVICON, "ui/images/hopUi.ico" );
+ application.addEntryPoint( "/ui", HopWebEntryPoint.class, properties );
+ application.setOperationMode( Application.OperationMode.SWT_COMPATIBILITY );
+
+ application.addServiceHandler( "downloadServiceHandler", new DownloadServiceHandler() );
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
new file mode 100644
index 0000000..6e0a9fc
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.extension.ExtensionPointHandler;
+import org.apache.hop.core.extension.HopExtensionPoint;
+import org.apache.hop.ui.core.PropsUi;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.AbstractEntryPoint;
+import org.eclipse.rap.rwt.client.service.StartupParameters;
+import org.eclipse.rap.rwt.widgets.WidgetUtil;
+import org.eclipse.swt.widgets.Composite;
+
+public class HopWebEntryPoint extends AbstractEntryPoint {
+
+ @Override
+ protected void createContents( Composite parent ) {
+ // Transferring Widget Data for client-side canvas drawing instructions
+ WidgetUtil.registerDataKeys( "props" );
+ WidgetUtil.registerDataKeys( "mode" );
+ WidgetUtil.registerDataKeys( "nodes" );
+ WidgetUtil.registerDataKeys( "hops" );
+ WidgetUtil.registerDataKeys( "notes" );
+ /*
+ * The following lines were migrated from Spoon.main
+ * because they are session specific.
+ */
+ // Options
+ StartupParameters serviceParams = RWT.getClient().getService( StartupParameters.class );
+ List<String> args = new ArrayList<String>();
+ String[] options = { "rep", "user", "pass", "trans", "job", "dir", "file" };
+ for ( String option : options ) {
+ if ( serviceParams.getParameter( option ) != null ) {
+ args.add( "-" + option + "=" + serviceParams.getParameter( option ) );
+ }
+ }
+
+ // Execute Spoon.createContents
+ HopGui.getInstance().setCommandLineArguments( args );
+ HopGui.getInstance().setShell( parent.getShell() );
+ HopGui.getInstance().setProps( PropsUi.getInstance() );
+ try {
+ ExtensionPointHandler.callExtensionPoint(
+ HopGui.getInstance().getLog(),
+ HopGui.getInstance().getVariables(),
+ HopExtensionPoint.HopGuiInit.id,
+ HopGui.getInstance()
+ );
+ } catch(Exception e) {
+ HopGui.getInstance().getLog().logError( "Error calling extension point plugin(s) for HopGuiInit", e);
+ }
+
+ HopGui.getInstance().open();
+ }
+
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebServletContextListener.java b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebServletContextListener.java
new file mode 100644
index 0000000..2a456b6
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebServletContextListener.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletContextEvent;
+
+import org.apache.hop.core.Const;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.www.HopServerSingleton;
+import org.eclipse.rap.rwt.engine.RWTServletContextListener;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+public class HopWebServletContextListener extends RWTServletContextListener {
+ private final static Logger logger = Logger.getLogger( HopWebServletContextListener.class.getName() );
+
+ public void contextInitialized( ServletContextEvent event ) {
+ /*
+ * The following lines are from HopGui.main
+ * because they are application-wide context.
+ */
+ try {
+ HopEnvironment.init();
+ HopGuiEnvironment.init();
+ } catch ( HopException e ) {
+ e.printStackTrace();
+ }
+ super.contextInitialized( event );
+ }
+ public void contextDestroyed( ServletContextEvent event ) {
+ super.contextDestroyed( event );
+ // Kill all remaining things in this VM!
+ System.exit( 0 );
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/ServerPushSessionFacadeImpl.java b/rap/src/main/java/org/apache/hop/ui/hopgui/ServerPushSessionFacadeImpl.java
new file mode 100644
index 0000000..4ed4abe
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/ServerPushSessionFacadeImpl.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import org.eclipse.rap.rwt.SingletonUtil;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+
+public class ServerPushSessionFacadeImpl extends ServerPushSessionFacade {
+
+ @Override
+ void startInternal() {
+ SingletonUtil.getSessionInstance( ServerPushSession.class ).start();
+ }
+
+ @Override
+ void stopInternal() {
+ SingletonUtil.getSessionInstance( ServerPushSession.class ).stop();
+ }
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/TextSizeUtilFacadeImpl.java b/rap/src/main/java/org/apache/hop/ui/hopgui/TextSizeUtilFacadeImpl.java
new file mode 100644
index 0000000..b710acd
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/TextSizeUtilFacadeImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui;
+
+import org.eclipse.rap.rwt.internal.textsize.TextSizeUtil;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Display;
+
+public class TextSizeUtilFacadeImpl extends TextSizeUtilFacade {
+
+ @Override
+ Point textExtentInternal(String text) {
+ return TextSizeUtil.stringExtent(Display.getCurrent().getSystemFont(), text);
+ }
+
+}
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/context/GuiContextUtilImpl.java b/rap/src/main/java/org/apache/hop/ui/hopgui/context/GuiContextUtilImpl.java
new file mode 100644
index 0000000..a6d0ec7
--- /dev/null
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/context/GuiContextUtilImpl.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui.context;
+
+import org.apache.hop.ui.hopgui.ISingletonProvider;
+import org.eclipse.rap.rwt.SingletonUtil;
+
+public class GuiContextUtilImpl implements ISingletonProvider {
+ public Object getInstanceInternal() {
+ return SingletonUtil.getSessionInstance( GuiContextUtil.class );
+ }
+}
diff --git a/rap/src/main/java/org/eclipse/jface/window/DefaultToolTip.java b/rap/src/main/java/org/eclipse/jface/window/DefaultToolTip.java
new file mode 100644
index 0000000..be4874b
--- /dev/null
+++ b/rap/src/main/java/org/eclipse/jface/window/DefaultToolTip.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jface.window;
+
+import org.apache.hop.ui.core.widget.CheckBoxToolTip;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Control;
+
+public class DefaultToolTip extends CheckBoxToolTip {
+
+ public DefaultToolTip( Control control ) {
+ super( control );
+ }
+
+ public DefaultToolTip( Control control, int style, boolean manualActivation ) {
+ super( control );
+ }
+
+ public void setBackgroundColor(Color color) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setForegroundColor(Color color) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/rap/src/main/java/org/eclipse/jface/window/ToolTip.java b/rap/src/main/java/org/eclipse/jface/window/ToolTip.java
new file mode 100644
index 0000000..cc12e47
--- /dev/null
+++ b/rap/src/main/java/org/eclipse/jface/window/ToolTip.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jface.window;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+
+public abstract class ToolTip extends org.eclipse.swt.widgets.ToolTip {
+
+ public static final int RECREATE = 1;
+ public static final int NO_RECREATE = 1 << 1;
+
+ public ToolTip( Control control ) {
+ super( control.getShell(), SWT.ICON_INFORMATION );
+ }
+
+ public ToolTip( Control control, int style, boolean manualActivation ) {
+ super( control.getShell(), SWT.ICON_INFORMATION );
+ }
+
+ public void setRespectMonitorBounds(boolean b) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setRespectDisplayBounds(boolean b) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setHideDelay(int i) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setPopupDelay(int i) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setHideOnMouseDown(boolean b) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setShift(Point point) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void show(Point location) {
+ super.setLocation( location );
+ super.setVisible( true );
+ }
+
+ public void hide() {
+ super.setVisible( false );
+ }
+
+ protected void afterHideToolTip(Event event) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas.js b/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas.js
new file mode 100644
index 0000000..5f26c10
--- /dev/null
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas.js
@@ -0,0 +1,206 @@
+//# sourceURL=canvas.js
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use strict";
+var x1, x2, y1, y2;
+var clicked = null;
+
+var handleEvent = function( event ) {
+ var mode = event.widget.getData( "mode" );
+ var nodes = event.widget.getData( "nodes" );
+ var hops = event.widget.getData( "hops" );
+ var notes = event.widget.getData( "notes" );
+ var props = event.widget.getData( "props" );
+ var magnification = props.magnification;
+ var gridsize = props.gridsize;
+ var iconsize = props.iconsize;
+
+ switch( event.type ) {
+ case SWT.MouseDown:
+ x1 = event.x / magnification;
+ y1 = event.y / magnification;
+
+ // Determine which node is clicked if any
+ for ( var key in nodes ) {
+ var node = nodes[key];
+ if ( node.x <= x1 && x1 < node.x + iconsize
+ && node.y <= y1 && y1 < node.y + iconsize ) {
+ clicked = node;
+ }
+ }
+ break;
+ case SWT.MouseUp:
+ clicked = null;
+ break;
+ case SWT.MouseMove:
+ x2 = event.x / magnification;
+ y2 = event.y / magnification;
+ if ( mode == null ) {
+ break;
+ }
+ if ( mode != "null" ) {
+ event.widget.redraw();
+ }
+ break;
+ case SWT.Paint:
+ // Client-side does not redraw when first-drawing (null) and after mouseup ("null")
+ if ( mode == null || mode == "null" ) {
+ break;
+ }
+ var gc = event.gc;
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+
+ // Clear the canvas
+ gc.clearRect( 0, 0, gc.canvas.width / magnification, gc.canvas.height / magnification );
+
+ // Draw grids
+ if ( gridsize > 1 ) {
+ gc.fillStyle = 'black';
+ gc.beginPath();
+ gc.setLineDash( [ 1, gridsize - 1 ] );
+ // vertical grid
+ for ( var i = gridsize; i < gc.canvas.width / magnification; i += gridsize ) {
+ gc.moveTo( i, 0 );
+ gc.lineTo( i, gc.canvas.height / magnification );
+ }
+ // horizontal grid
+ for ( var j = gridsize; j < gc.canvas.height / magnification; j += gridsize ) {
+ gc.moveTo( 0, j );
+ gc.lineTo( gc.canvas.width / magnification, j );
+ }
+ gc.stroke();
+ gc.setLineDash( [] );
+ gc.fillStyle = 'white';
+ }
+
+ // Draw hops
+ hops.forEach( function ( hop ) {
+ gc.beginPath();
+ if ( mode == "drag" && nodes[hop.from].selected ) {
+ gc.moveTo( snapToGrid( nodes[hop.from].x + dx, gridsize ) + iconsize / 2,
+ snapToGrid( nodes[hop.from].y + dy, gridsize ) + iconsize / 2 );
+ } else {
+ gc.moveTo( nodes[hop.from].x + iconsize / 2, nodes[hop.from].y + iconsize / 2 );
+ }
+ if ( mode == "drag" && nodes[hop.to].selected ) {
+ gc.lineTo( snapToGrid( nodes[hop.to].x + dx, gridsize ) + iconsize / 2,
+ snapToGrid( nodes[hop.to].y + dy, gridsize ) + iconsize / 2 );
+ } else {
+ gc.lineTo( nodes[hop.to].x + iconsize / 2, nodes[hop.to].y + iconsize / 2 );
+ }
+ gc.stroke();
+ } );
+
+ for ( var key in nodes ) {
+ var node = nodes[key];
+ var x = node.x;
+ var y = node.y;
+
+ // Move selected nodes
+ if ( mode == "drag" && ( node.selected || node == clicked ) ) {
+ x = snapToGrid( node.x + dx, gridsize );
+ y = snapToGrid( node.y + dy, gridsize );
+ }
+ // Draw a icon background
+ gc.fillRect( x, y, iconsize, iconsize );
+
+ // Draw node icon
+ var img = new Image();
+ img.src = 'rwt-resources/' + node.img;
+ gc.drawImage( img, x, y, iconsize, iconsize );
+
+ // Draw a bounding rectangle
+ if ( node.selected || node == clicked ) {
+ gc.lineWidth = 3;
+ gc.strokeStyle = 'rgb(0, 93, 166)';
+ } else {
+ gc.strokeStyle = 'rgb(61, 99, 128)'; //colorCrystalTextPentaho
+ }
+ drawRoundRectangle( gc, x - 1, y - 1, iconsize + 1, iconsize + 1, 8, 8, false );
+ gc.strokeStyle = 'black';
+ gc.lineWidth = 1;
+
+ // Draw node name
+// gc.fillStyle = 'black';
+// gc.fillText( key, x + iconsize / 2 - gc.measureText(key).width / 2, y + iconsize + 7 );
+// gc.fillStyle = 'white';
+ }
+
+ // Draw notes
+ notes.forEach( function ( note ) {
+ gc.beginPath();
+ // margin = 10 see org.pentaho.di.core.gui.BasePainter.drawNote(NotePadMeta)
+ if ( mode == "drag" && note.selected ) {
+ gc.rect( snapToGrid( note.x + dx, gridsize), snapToGrid( note.y + dy, gridsize), note.width + 10, note.height + 10 );
+ } else {
+ gc.rect( note.x, note.y, note.width + 10, note.height + 10 );
+ }
+ gc.stroke();
+ } );
+
+ // Draw a new hop
+ if ( mode == "hop" && clicked ) {
+ gc.beginPath();
+ gc.moveTo( clicked.x + iconsize / 2, clicked.y + iconsize / 2 );
+ gc.lineTo( x2, y2 );
+ gc.stroke();
+ }
+
+ // Draw a selection rectangle
+ if ( mode == "select" ) {
+ gc.beginPath();
+ gc.rect( x1, y1, dx, dy );
+ gc.stroke();
+ }
+ break;
+ }
+};
+
+function snapToGrid( x, gridsize ) {
+ return gridsize * Math.floor( x / gridsize );
+}
+
+/*
+ * Port of GCOperationWriter#drawRoundRectangle
+ */
+function drawRoundRectangle( gc, x, y, w, h, arcWidth, arcHeight, fill ) {
+ var offset = 0;
+ if( !fill && gc.lineWidth % 2 != 0 ) {
+ offset = 0.5;
+ }
+ x = x + offset;
+ y = y + offset;
+ var rx = arcWidth / 2 + 1;
+ var ry = arcHeight / 2 + 1;
+ gc.beginPath();
+ gc.moveTo( x, y + ry );
+ gc.lineTo( x, y + h - ry );
+ gc.quadraticCurveTo( x, y + h, x + rx, y + h );
+ gc.lineTo( x + w - rx, y + h );
+ gc.quadraticCurveTo( x + w, y + h, x + w, y + h - ry );
+ gc.lineTo( x + w, y + ry );
+ gc.quadraticCurveTo( x + w, y, x + w - rx, y );
+ gc.lineTo( x + rx, y );
+ gc.quadraticCurveTo( x, y, x, y + ry );
+ if ( fill ) {
+ gc.fill();
+ } else {
+ gc.stroke();
+ }
+}
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/clipboard.js b/rap/src/main/resources/org/apache/hop/ui/hopgui/clipboard.js
new file mode 100644
index 0000000..db3717d
--- /dev/null
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/clipboard.js
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use strict";
+
+(function(){
+ 'use strict';
+
+ rap.registerTypeHandler( "webSpoon.Clipboard", {
+ factory : function( properties ) {
+ this._widget = new webSpoon.Clipboard( properties );
+ if ( rwt.client.Client._browserName === 'explorer' ) {
+ var doc = rwt.widgets.base.ClientDocument.getInstance();
+ doc.addEventListener( "keydown", this._widget._onKeyDown, this._widget );
+ }
+ return this._widget;
+ },
+ destructor : function () {
+ if ( rwt.client.Client._browserName === 'explorer' ) {
+ var doc = rwt.widgets.base.ClientDocument.getInstance();
+ doc.removeEventListener( "keydown", this._widget._onKeyDown, this._widget );
+ }
+ },
+ properties : [ "text" ],
+ events : [ "paste", "copy", "cut" ],
+ methods : [ "downloadCanvasImage" ]
+ } );
+
+ rwt.define( "webSpoon" );
+
+ webSpoon.Clipboard = function ( properties ) {
+ var x = document.createElement("INPUT");
+ x.setAttribute( "id", "input-clipboard" );
+ x.setAttribute( "remoteObjectId", properties.self );
+ x.addEventListener( "paste", function( event ) {
+ var obj = rap.getObject( this.getAttribute( 'remoteObjectid' ) );
+ var remoteObject = rap.getRemoteObject( obj );
+ var text = '';
+ if ( typeof event.clipboardData === 'undefined' ) {
+ text = window.clipboardData.getData( 'Text' ); // IE
+ } else {
+ text = event.clipboardData.getData( 'text/plain' );
+ }
+ event.preventDefault();
+ remoteObject.notify( "paste", { "text": text, "widgetId": event.target.value } );
+ console.log('paste');
+ }, this );
+ x.addEventListener( "copy", function( event ) {
+ var obj = rap.getObject( this.getAttribute( 'remoteObjectid' ) );
+ var remoteObject = rap.getRemoteObject( obj );
+ if ( typeof event.clipboardData === 'undefined' ) {
+ window.clipboardData.setData( 'Text', obj.getText() ); // IE
+ } else {
+ event.clipboardData.setData( 'text/plain', obj.getText() );
+ }
+ event.preventDefault();
+ if ( rwt.client.Client._browserName != 'explorer' ) {
+ remoteObject.notify( "copy", { "widgetId": event.target.value } );
+ console.log('copy');
+ }
+ }, this );
+ x.addEventListener( "cut", function( event ) {
+ var obj = rap.getObject( this.getAttribute( 'remoteObjectid' ) );
+ var remoteObject = rap.getRemoteObject( obj );
+ if ( typeof event.clipboardData === 'undefined' ) {
+ window.clipboardData.setData( 'Text', obj.getText() ); // IE
+ } else {
+ event.clipboardData.setData( 'text/plain', obj.getText() );
+ }
+ event.preventDefault();
+ remoteObject.notify( "cut", { "widgetId": event.target.value } );
+ console.log('cut');
+ }, this );
+ document.body.appendChild( x );
+ };
+
+ webSpoon.Clipboard.prototype = {
+ setText : function( text ) {
+ this._text = text;
+ },
+
+ getText : function() {
+ return this._text;
+ },
+
+ downloadCanvasImage : function( obj ) {
+ var i = 0;
+ for ( ; i < document.getElementsByTagName( 'canvas' ).length; i++ ) {
+ if ( document.getElementsByTagName( 'canvas' )[ i ].rwtObject._rwtId === obj.rwtId + ".gc" ) {
+ break;
+ }
+ }
+ var canvas = document.getElementsByTagName( 'canvas' )[ i ];
+ if ( window.navigator && window.navigator.msSaveOrOpenBlob ) { // For IE and Edge
+ var blob = canvas.msToBlob();
+ window.navigator.msSaveOrOpenBlob( blob, obj.name + ".png" );
+ } else {
+ var data = canvas.toDataURL();
+ var link = document.createElement( 'a' )
+ link.setAttribute( 'download', obj.name + ".png" );
+ link.setAttribute( 'href', data.replace( "image/png", "image/octet-stream" ) );
+ document.body.appendChild( link );
+ link.click();
+ document.body.removeChild( link );
+ }
+ },
+
+ _onKeyDown : function( event ) {
+ const keyName = event._valueDomEvent.key;
+
+ if ( keyName === 'Control' ) {
+ return;
+ }
+
+ if ( event._valueDomEvent.ctrlKey ) {
+ var x = document.getElementById( "input-clipboard" );
+ x.select();
+ var remoteObject = rap.getRemoteObject( this );
+ if ( keyName === 'c' ) {
+ document.execCommand( 'copy' );
+ remoteObject.notify( "copy", { "widgetId": x.value } );
+ console.log('copy');
+ } else if ( keyName === 'x' ) {
+ /*
+ * cut event cannot be invoked programmatically on IE11 for some reason,
+ * so capture ctrl+x then execute copy (instead of cut) and notify cut.
+ */
+ document.execCommand( 'copy' );
+ remoteObject.notify( "cut", { "widgetId": x.value } );
+ console.log('cut');
+ }
+ }
+ }
+ };
+}());