This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new f2f6b7ab1d Improve Hop handeling in hop web, fixes #6322 (#6326)
f2f6b7ab1d is described below
commit f2f6b7ab1df10494b1edb92fc9551961ce99ae97
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Sat Jan 10 13:50:16 2026 +0100
Improve Hop handeling in hop web, fixes #6322 (#6326)
---
docker/build-hop-images.sh | 65 +++++
docker/unified.Dockerfile | 72 +++--
.../org/apache/hop/ui/hopgui/CanvasFacadeImpl.java | 7 +-
.../org/apache/hop/ui/hopgui/HopWebEntryPoint.java | 1 +
.../org/apache/hop/ui/core/widget/svg/svg-label.js | 4 -
.../resources/org/apache/hop/ui/hopgui/canvas.js | 297 +++++++++++++++------
.../org/apache/hop/ui/hopgui/dark-mode.css | 7 +
.../org/apache/hop/ui/hopgui/light-mode.css | 7 +
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 85 +++---
.../hopgui/file/workflow/HopGuiWorkflowGraph.java | 79 +++---
10 files changed, 429 insertions(+), 195 deletions(-)
diff --git a/docker/build-hop-images.sh b/docker/build-hop-images.sh
index a46403107a..e9e73f9f86 100755
--- a/docker/build-hop-images.sh
+++ b/docker/build-hop-images.sh
@@ -39,6 +39,7 @@
# --maven-threads <threads> Maven build threads (default: 1C, e.g., 2C,
4, 8)
# --progress <mode> Docker build output mode: auto (default),
plain (verbose), tty (compact)
# --builder <type> Builder type: full (Maven, default) or fast
(pre-built artifacts)
+# --skip-fat-jar Skip building the fat jar (auto-detected
based on images, only needed for dataflow/web-beam)
# --no-cache Build without using cache
# -h, --help Show this help message
#
@@ -55,12 +56,18 @@
# # Build web image with beam variant (includes fat jar for Dataflow)
# ./build-hop-images.sh --images web-beam
#
+# # Build web image only (fat jar automatically skipped)
+# ./build-hop-images.sh --images web
+#
# # Build specific variants
# ./build-hop-images.sh --images client-minimal,web-beam
#
# # Build for multiple platforms and push to Docker Hub
# ./build-hop-images.sh --platforms linux/amd64,linux/arm64 --push
--registry apache
#
+# # Manually skip fat jar generation (saves time if not building
dataflow/web-beam)
+# ./build-hop-images.sh --images web,client --skip-fat-jar
+#
################################################################################
set -e
@@ -105,6 +112,7 @@ USE_CACHE="true"
MAVEN_THREADS="1C" # Maven thread count (1C, 2C, or specific number like 4)
BUILD_PROGRESS="auto" # Docker build progress output (auto, plain, tty)
BUILDER_TYPE="full" # Builder type: full (Maven build) or fast (pre-built
artifacts)
+SKIP_FAT_JAR="auto" # Whether to skip fat jar generation (auto, true, false)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
@@ -173,6 +181,29 @@ show_help() {
exit 0
}
+# Function to determine if fat jar is needed based on images being built
+needs_fat_jar() {
+ local images_list="$1"
+
+ # Fat jar is only needed for dataflow and web-beam images
+ if [[ "$images_list" == "all" ]]; then
+ # Building all images includes dataflow and web-beam
+ return 0
+ fi
+
+ # Check if any of the requested images needs the fat jar
+ IFS=',' read -ra images_array <<< "$images_list"
+ for img in "${images_array[@]}"; do
+ img=$(echo "$img" | xargs) # trim whitespace
+ if [[ "$img" == "dataflow"* ]] || [[ "$img" == "web-beam"* ]]; then
+ return 0
+ fi
+ done
+
+ # No images that need fat jar
+ return 1
+}
+
# Function to detect version from pom.xml
detect_version() {
if [ -f "$PROJECT_ROOT/pom.xml" ]; then
@@ -285,6 +316,10 @@ parse_args() {
BUILDER_TYPE="$2"
shift 2
;;
+ --skip-fat-jar)
+ SKIP_FAT_JAR="true"
+ shift
+ ;;
--no-cache)
USE_CACHE="false"
shift
@@ -358,12 +393,28 @@ build_image() {
version_tag="${VERSION}-${variant_suffix}"
fi
+ # Determine if we should skip fat jar for this specific image
+ local skip_fat_jar_value="false"
+ if [[ "$SKIP_FAT_JAR" == "true" ]]; then
+ skip_fat_jar_value="true"
+ elif [[ "$SKIP_FAT_JAR" == "auto" ]]; then
+ # Auto-detect: skip if this image doesn't need it
+ if [[ "$stage_name" != "dataflow"* ]] && [[ "$stage_name" !=
"web-beam"* ]]; then
+ skip_fat_jar_value="true"
+ fi
+ fi
+
+ if [[ "$skip_fat_jar_value" == "true" ]]; then
+ print_info "Skipping fat jar generation (not needed for $stage_name)"
+ fi
+
# Build arguments
local build_args=(
"--build-arg" "HOP_BUILD_FROM_SOURCE=$SOURCE_TYPE"
"--build-arg" "TARGET_IMAGE=$stage_name"
"--build-arg" "MAVEN_THREADS=$maven_threads"
"--build-arg" "BUILDER_TYPE=$BUILDER_TYPE"
+ "--build-arg" "SKIP_FAT_JAR=$skip_fat_jar_value"
"--progress" "$BUILD_PROGRESS"
"--file" "$SCRIPT_DIR/unified.Dockerfile"
"--target" "$stage_name"
@@ -464,6 +515,20 @@ build_images() {
if [[ "$BUILDER_TYPE" == "full" ]]; then
echo "Maven threads: $MAVEN_THREADS"
fi
+
+ # Show fat jar status
+ if [[ "$SKIP_FAT_JAR" == "auto" ]]; then
+ if needs_fat_jar "$IMAGES"; then
+ echo "Fat jar: enabled (auto-detected, needed for
dataflow/web-beam)"
+ else
+ echo "Fat jar: disabled (auto-detected, not needed for
selected images)"
+ fi
+ elif [[ "$SKIP_FAT_JAR" == "true" ]]; then
+ echo "Fat jar: disabled (manual)"
+ else
+ echo "Fat jar: enabled"
+ fi
+
echo "Push: $PUSH"
echo "Cache: $USE_CACHE"
echo ""
diff --git a/docker/unified.Dockerfile b/docker/unified.Dockerfile
index d1bf099fac..a53dbc6d5e 100644
--- a/docker/unified.Dockerfile
+++ b/docker/unified.Dockerfile
@@ -172,12 +172,19 @@ RUN echo "=== Extracting assemblies ===" && \
echo "WARNING: Plugins assembly not found, will use built plugins
directly"; \
fi
-# Step 2: Generate fat jar for dataflow template
-RUN if [ -f /build/assemblies/client/target/hop/hop-conf.sh ]; then \
- /build/assemblies/client/target/hop/hop-conf.sh \
- --generate-fat-jar=/tmp/hop-fatjar.jar; \
+# Step 2: Generate fat jar for dataflow template (only if needed)
+ARG SKIP_FAT_JAR=false
+RUN if [ "${SKIP_FAT_JAR}" = "false" ]; then \
+ echo "=== Generating fat jar ===" && \
+ if [ -f /build/assemblies/client/target/hop/hop-conf.sh ]; then \
+ /build/assemblies/client/target/hop/hop-conf.sh \
+ --generate-fat-jar=/tmp/hop-fatjar.jar; \
+ else \
+ echo "ERROR: hop-conf.sh not found" && exit 1; \
+ fi; \
else \
- echo "ERROR: hop-conf.sh not found" && exit 1; \
+ echo "=== Skipping fat jar generation (not needed) ===" && \
+ touch /tmp/hop-fatjar.jar; \
fi
# Step 3: Prepare optimized directory structures for final images
@@ -189,12 +196,11 @@ RUN mkdir -p /build/hop-web-prepared/webapps/ROOT && \
cp -r /build/assemblies/client/target/hop/lib/beam/*
/build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
cp -r /build/assemblies/client/target/hop/lib/core/*
/build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
rm /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/hop-ui-rcp* && \
- mkdir -p /build/hop-web-prepared/bin && \
- cp -r /build/docker/resources/run-web.sh
/build/hop-web-prepared/bin/run-web.sh
+ cp /build/docker/resources/run-web.sh /build/hop-web-prepared/run-web.sh
&& \
+ chmod +x /build/hop-web-prepared/run-web.sh
# Make scripts executable
-RUN chmod +x /build/hop-web-prepared/webapps/ROOT/*.sh \
- && chmod +x /build/hop-web-prepared/bin/run-web.sh
+RUN chmod +x /build/hop-web-prepared/webapps/ROOT/*.sh
# Fix hop-config.json
RUN sed -i 's/config\/projects/${HOP_CONFIG_FOLDER}\/projects/g'
/build/hop-web-prepared/webapps/ROOT/config/hop-config.json
@@ -221,14 +227,13 @@ RUN mkdir -p /build/hop-client-prepared && \
RUN mkdir -p /build/hop-rest-prepared/plugins && \
mkdir -p /build/hop-rest-prepared/webapps && \
mkdir -p /build/hop-rest-prepared/lib/swt/linux/x86_64 && \
- mkdir -p /build/hop-rest-prepared/bin && \
# Copy plugins
cp -r /build/assemblies/plugins/target/plugins/*
/build/hop-rest-prepared/plugins/ && \
# Copy REST war
cp /build/rest/target/hop-rest*.war
/build/hop-rest-prepared/webapps/hop.war && \
# Copy run script
- cp /build/docker/resources/run-rest.sh
/build/hop-rest-prepared/bin/run-rest.sh && \
- chmod +x /build/hop-rest-prepared/bin/run-rest.sh
+ cp /build/docker/resources/run-rest.sh
/build/hop-rest-prepared/run-rest.sh && \
+ chmod +x /build/hop-rest-prepared/run-rest.sh
################################################################################
# Stage 4a: Hop Client/Server Image (Standard)
@@ -299,7 +304,7 @@ USER hop
ENV PATH=${PATH}:${DEPLOYMENT_PATH}/hop
WORKDIR /home/hop
-CMD bash -c "$DEPLOYMENT_PATH/run.sh"
+ENTRYPOINT ["/bin/bash", "/opt/hop/run.sh"]
################################################################################
# Stage 4b: Hop Web Image
@@ -352,11 +357,46 @@ COPY --from=builder --chown=hop /build/hop-web-prepared/
"${CATALINA_HOME}"
USER hop
-CMD bash -c "$CATALINA_HOME/bin/run-web.sh"
+CMD ["/bin/bash", "/usr/local/tomcat/run-web.sh"]
################################################################################
-# Stage 4c: Hop Dataflow Template Image
+# Stage 4c: Hop REST Image
+################################################################################
+FROM tomcat:10-jdk17 AS rest
+
+# Environment variables
+ENV HOP_CONFIG_FOLDER=""
+ENV HOP_AES_ENCODER_KEY=""
+ENV HOP_AUDIT_FOLDER="${CATALINA_HOME}/webapps/ROOT/audit"
+ENV HOP_CONFIG_FOLDER="${CATALINA_HOME}/webapps/ROOT/config"
+ENV HOP_LOG_LEVEL="Basic"
+ENV HOP_OPTIONS="-Xmx4g"
+ENV HOP_PASSWORD_ENCODER_PLUGIN="Hop"
+ENV HOP_PLUGIN_BASE_FOLDERS="plugins"
+ENV HOP_SHARED_JDBC_FOLDERS=""
+ENV HOP_REST_CONFIG_FOLDER="/config"
+
+# Set TOMCAT start variables
+ENV CATALINA_OPTS='${HOP_OPTIONS} \
+ -DHOP_AES_ENCODER_KEY="${HOP_AES_ENCODER_KEY}" \
+ -DHOP_AUDIT_FOLDER="${HOP_AUDIT_FOLDER}" \
+ -DHOP_CONFIG_FOLDER="${HOP_CONFIG_FOLDER}" \
+ -DHOP_LOG_LEVEL="${HOP_LOG_LEVEL}" \
+ -DHOP_PASSWORD_ENCODER_PLUGIN="${HOP_PASSWORD_ENCODER_PLUGIN}" \
+ -DHOP_PLUGIN_BASE_FOLDERS="${HOP_PLUGIN_BASE_FOLDERS}" \
+ -DHOP_REST_CONFIG_FOLDER="${HOP_REST_CONFIG_FOLDER}" \
+ -DHOP_SHARED_JDBC_FOLDERS="${HOP_SHARED_JDBC_FOLDERS}"\'
+
+# Cleanup and copy resources
+RUN rm -rf webapps/*
+
+COPY --from=builder /build/hop-rest-prepared/ "${CATALINA_HOME}"/
+
+CMD ["/bin/bash", "/usr/local/tomcat/run-rest.sh"]
+
+################################################################################
+# Stage 4d: Hop Dataflow Template Image
################################################################################
FROM gcr.io/dataflow-templates-base/java17-template-launcher-base AS dataflow
@@ -380,7 +420,7 @@ ENTRYPOINT ["/opt/google/dataflow/java_template_launcher"]
################################################################################
################################################################################
-# Stage 4e: Hop Web with Beam (includes fat jar for Dataflow)
+# Stage 4f: Hop Web with Beam (includes fat jar for Dataflow)
################################################################################
FROM web AS web-beam
LABEL variant="beam"
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
index a75bb9b453..90bd06d7d0 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasFacadeImpl.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/CanvasFacadeImpl.java
@@ -45,11 +45,8 @@ public class CanvasFacadeImpl extends CanvasFacade {
private void setDataCommon(Canvas canvas, float magnification, DPoint
offset, Object meta) {
JsonObject jsonProps = new JsonObject();
jsonProps.add("themeId", System.getProperty(HopWeb.HOP_WEB_THEME,
"light"));
- jsonProps.add(
- "gridSize",
- PropsUi.getInstance().isShowCanvasGridEnabled()
- ? PropsUi.getInstance().getCanvasGridSize()
- : 1);
+ jsonProps.add("gridSize", PropsUi.getInstance().getCanvasGridSize());
+ jsonProps.add("showGrid", PropsUi.getInstance().isShowCanvasGridEnabled());
jsonProps.add("iconSize", PropsUi.getInstance().getIconSize());
jsonProps.add("magnification", (float) (magnification *
PropsUi.getNativeZoomFactor()));
jsonProps.add("offsetX", offset.x);
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
index 63188fbfeb..9759739588 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
@@ -38,6 +38,7 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
WidgetUtil.registerDataKeys("nodes");
WidgetUtil.registerDataKeys("hops");
WidgetUtil.registerDataKeys("notes");
+ WidgetUtil.registerDataKeys("startHopNode");
// WidgetUtil.registerDataKeys("svg");
// The following options are session specific.
diff --git
a/rap/src/main/resources/org/apache/hop/ui/core/widget/svg/svg-label.js
b/rap/src/main/resources/org/apache/hop/ui/core/widget/svg/svg-label.js
index cd66df9c03..3bd93abd62 100644
--- a/rap/src/main/resources/org/apache/hop/ui/core/widget/svg/svg-label.js
+++ b/rap/src/main/resources/org/apache/hop/ui/core/widget/svg/svg-label.js
@@ -24,10 +24,6 @@ const handleEvent = function (event) {
let img = document.getElementById(id);
- console.log("svg-label event with id: " + id + ", was found? " + (img !==
null));
-
- // img.style.backgroundBlendMode = 'lighten';
-
if (img===null) {
return;
}
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
index 5810e4de9c..c0d087976d 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas.js
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/canvas.js
@@ -20,7 +20,9 @@
let x1, x2, y1, y2;
let clicked = null;
-let offsetX, offsetY, magnification, gridSize;
+let hopStartNode = null; // Track the start node for hop creation separately
+let iconOffsetX = 0, iconOffsetY = 0; // Track where in the icon the user
clicked (like desktop client)
+let offsetX, offsetY, magnification, gridSize, showGrid;
let fgColor, bgColor, selectedNodeColor, nodeColor;
// These are the colors for the theme. They are picked up by the next event
handler function.
@@ -35,9 +37,9 @@ function getThemeColors(theme) {
nodeColor = 'rgb(61, 99, 128)';
} else {
// Light theme is the default
- //
+ // Use the same background as application: colorBackground RGB(240,
240, 240)
fgColor = 'rgb(50, 50, 50)';
- bgColor = 'rgb(210, 210, 210)';
+ bgColor = 'rgb(240, 240, 240)'; // Matches GuiResource.colorBackground
selectedNodeColor = 'rgb(0, 93, 166)';
nodeColor = 'rgb(61, 99, 128)';
}
@@ -51,33 +53,62 @@ const handleEvent = function (event) {
const hops = event.widget.getData("hops");
const notes = event.widget.getData("notes");
const props = event.widget.getData("props");
+ const startHopNodeName = event.widget.getData("startHopNode");
// Global vars to make the coordinate calculation function simpler.
//
- offsetX = props.offsetX;
- offsetY = props.offsetY;
+ // Round offsets to avoid sub-pixel grid misalignment
+ offsetX = Math.round(props.offsetX);
+ offsetY = Math.round(props.offsetY);
magnification = props.magnification;
-
- const gridSize = props.gridSize;
- const iconSize = props.iconSize * magnification;
+ gridSize = props.gridSize; // Always set for snapping
+ showGrid = props.showGrid; // Control visibility only
+
+ const iconSize = Math.round(props.iconSize * magnification);
+
+ // Synchronize hopStartNode with server data
+ if (mode === "hop" && startHopNodeName && nodes[startHopNodeName]) {
+ // Always update hopStartNode when we have server data
+ hopStartNode = nodes[startHopNodeName];
+ } else if (mode !== "hop") {
+ // Clear hopStartNode when not in hop mode
+ hopStartNode = null;
+ }
switch (event.type) {
case SWT.MouseDown:
+ // Convert screen coordinates to logical coordinates
x1 = event.x / magnification;
y1 = event.y / magnification;
+
+ // Set x2/y2 to current position (needed for hop drawing after
mode change)
+ x2 = x1;
+ y2 = y1;
// Determine which node is clicked if any
+ const iconLogicalSize = iconSize / magnification;
for (let key in nodes) {
const node = nodes[key];
- if (node.x <= x1 && x1 < node.x + iconSize
- && node.y <= y1 && y1 < node.y + iconSize) {
+ if (node.x <= x1 && x1 < node.x + iconLogicalSize
+ && node.y <= y1 && y1 < node.y + iconLogicalSize) {
clicked = node;
+
+ // Calculate iconOffset: where within the icon did user
click?
+ // This matches desktop client: iconOffset = new
Point(real.x - p.x, real.y - p.y)
+ iconOffsetX = x1 - node.x;
+ iconOffsetY = y1 - node.y;
break;
}
}
break;
case SWT.MouseUp:
- clicked = null;
+ // Only reset clicked for non-hop modes
+ // In hop mode, keep the start node until mode changes
+ if (mode !== "hop") {
+ clicked = null;
+ iconOffsetX = 0;
+ iconOffsetY = 0;
+ }
break;
case SWT.MouseMove:
x2 = event.x / magnification;
@@ -98,8 +129,17 @@ const handleEvent = function (event) {
getThemeColors(props.themeId);
const gc = event.gc;
- const dx = x2 - x1;
- const dy = y2 - y1;
+
+ // Calculate delta matching desktop client logic:
+ // Desktop: icon.x = real.x - iconOffset.x (where icon top-left
should be)
+ // dx = icon.x - selectedTransform.getLocation().x
+ // Web equivalent:
+ const iconTargetX = x2 - iconOffsetX; // Where icon top-left
should be
+ const iconTargetY = y2 - iconOffsetY;
+ const iconStartX = x1 - iconOffsetX; // Where icon top-left was
at start
+ const iconStartY = y1 - iconOffsetY;
+ const dx = iconTargetX - iconStartX; // Delta for icon top-left
+ const dy = iconTargetY - iconStartY;
// Set the appropriate font size.
//
@@ -109,38 +149,68 @@ const handleEvent = function (event) {
let newSize = Math.round(oldSize * magnification);
gc.font = newSize + fontString.substring(pxIdx);
- // If we're not dragging the cursor with the mouse
- //
- // Clear the canvas, regardless of what happens below
- //
- gc.rect(0, 0, gc.canvas.width / magnification, gc.canvas.height /
magnification);
+ // Fill canvas with solid background color matching the application
+ // This creates the overlay effect during drag/hop operations
gc.fillStyle = bgColor;
- gc.fill();
+ gc.fillRect(0, 0, gc.canvas.width, gc.canvas.height);
- // Draw grids
+ // Draw grids (only if showGrid is enabled)
//
- if (gridSize > 1) {
+ if (showGrid && gridSize > 1) {
drawGrid(gc, gridSize);
}
- // Draw hops
- drawHops(hops, gc, mode, nodes, dx, iconSize, dy);
+ // Draw existing hops (skip in hop mode for cleaner view)
+ if (mode !== "hop") {
+ drawHops(hops, gc, mode, nodes, dx, iconSize, dy);
+ }
- // The nodes are action or transform icons
- //
+ // Always draw node outlines so users can see what they're
targeting
drawNodes(nodes, mode, dx, dy, gc, iconSize);
// Draw notes
drawNotes(notes, gc, mode, dx, dy);
- // Draw a new hop
- if (mode === "hop" && clicked) {
+ // Draw a new hop candidate line (matching desktop client style)
+ if (mode === "hop" && hopStartNode) {
+ // Use current mouse position, or fall back to x2/y2
+ const targetX = x2 !== undefined ? x2 : x1;
+ const targetY = y2 !== undefined ? y2 : y1;
+
+ const startX = Math.round(fx(hopStartNode.x) + iconSize / 2);
+ const startY = Math.round(fy(hopStartNode.y) + iconSize / 2);
+ const endX = Math.round(fx(targetX));
+ const endY = Math.round(fy(targetY));
+
+ // Draw solid blue line (matching desktop client)
+ gc.strokeStyle = 'rgb(0, 93, 166)'; // Blue color
+ gc.lineWidth = 2;
gc.beginPath();
- gc.moveTo(
- fx(clicked.x) + iconSize / 2,
- fy(clicked.y) + iconSize / 2);
- gc.lineTo(fx(x2), fy(y2));
+ gc.moveTo(startX, startY);
+ gc.lineTo(endX, endY);
gc.stroke();
+
+ // Draw arrow head at the end
+ const angle = Math.atan2(endY - startY, endX - startX);
+ const arrowLength = 10;
+ const arrowWidth = 5;
+
+ gc.fillStyle = 'rgb(0, 93, 166)';
+ gc.beginPath();
+ gc.moveTo(endX, endY);
+ gc.lineTo(
+ Math.round(endX - arrowLength * Math.cos(angle - Math.PI /
6)),
+ Math.round(endY - arrowLength * Math.sin(angle - Math.PI /
6))
+ );
+ gc.lineTo(
+ Math.round(endX - arrowLength * Math.cos(angle + Math.PI /
6)),
+ Math.round(endY - arrowLength * Math.sin(angle + Math.PI /
6))
+ );
+ gc.closePath();
+ gc.fill();
+
+ // Reset
+ gc.lineWidth = 1;
}
// Draw a selection rectangle
@@ -172,44 +242,90 @@ const handleEvent = function (event) {
// This gets called by the RAP code for the Canvas widget.
function drawGrid(gc, gridSize) {
gc.fillStyle = fgColor;
- gc.beginPath();
- gc.setLineDash([1, gridSize - 1]);
- // vertical grid
- for (let i = gridSize; i < gc.canvas.width / magnification; i += gridSize)
{
- gc.moveTo(fx(i), fy(0));
- gc.lineTo(fx(i), fy(gc.canvas.height / magnification));
- }
- // horizontal grid
- for (let j = gridSize; j < gc.canvas.height / magnification; j +=
gridSize) {
- gc.moveTo(fx(0), fy(j));
- gc.lineTo(fx(gc.canvas.width / magnification), fy(j));
+ gc.globalAlpha = 0.3; // Make grid more subtle
+
+ // Calculate visible area bounds
+ const width = gc.canvas.width;
+ const height = gc.canvas.height;
+
+ // Calculate the spacing in screen coordinates (rounded for pixel-perfect
alignment)
+ const spacing = Math.round(gridSize * magnification);
+
+ // Find where the first snapped logical grid coordinate (0) appears on
screen
+ // We draw grid dots at screen positions that correspond to snapped
logical coordinates
+ const originX = Math.round(fx(0));
+ const originY = Math.round(fy(0));
+
+ // Use proper modulo that handles negatives correctly for finding first
visible grid dot
+ // JavaScript % can return negative values, so we need ((n % m) + m) % m
+ const firstX = ((originX % spacing) + spacing) % spacing;
+ const firstY = ((originY % spacing) + spacing) % spacing;
+
+ // Draw dots at grid intersections (matching desktop client)
+ // Desktop uses: gc.drawPoint() which draws using current lineWidth
(typically 2px)
+ // We'll draw 2x2 pixel dots to match the desktop appearance
+ const dotSize = 2;
+ for (let x = firstX; x <= width; x += spacing) {
+ for (let y = firstY; y <= height; y += spacing) {
+ gc.fillRect(x, y, dotSize, dotSize);
+ }
}
- gc.stroke();
- gc.setLineDash([]);
- gc.fillStyle = bgColor;
+
+ gc.globalAlpha = 1.0; // Reset alpha
}
function drawHops(hops, gc, mode, nodes, dx, iconSize, dy) {
+ // Set consistent stroke style for all hops
+ gc.strokeStyle = fgColor;
+ gc.lineWidth = 1;
+
hops.forEach(function (hop) {
+ // Validate that both nodes exist before attempting to draw
+ const fromNode = nodes[hop.from];
+ const toNode = nodes[hop.to];
+
+ if (!fromNode || !toNode) {
+ // Skip this hop if either node is missing
+ console.warn("Skipping hop with missing node - from:", hop.from,
"to:", hop.to);
+ return;
+ }
+
gc.beginPath();
- if (mode === "drag" && nodes[hop.from].selected) {
- gc.moveTo(
- fx(nodes[hop.from].x + dx) + iconSize / 2,
- fy(nodes[hop.from].y + dy) + iconSize / 2);
+
+ // Calculate from coordinates (with client-side snapping when
configured)
+ let fromX, fromY;
+ if (mode === "drag" && fromNode.selected) {
+ let x = fromNode.x + dx;
+ let y = fromNode.y + dy;
+ if (gridSize > 1) {
+ x = snapToGrid(x);
+ y = snapToGrid(y);
+ }
+ fromX = Math.round(fx(x) + iconSize / 2);
+ fromY = Math.round(fy(y) + iconSize / 2);
} else {
- gc.moveTo(
- fx(nodes[hop.from].x) + iconSize / 2,
- fy(nodes[hop.from].y) + iconSize / 2);
+ fromX = Math.round(fx(fromNode.x) + iconSize / 2);
+ fromY = Math.round(fy(fromNode.y) + iconSize / 2);
}
- if (mode === "drag" && nodes[hop.to].selected) {
- gc.lineTo(
- fx(nodes[hop.to].x + dx) + iconSize / 2,
- fy(nodes[hop.to].y + dy) + iconSize / 2);
+
+ // Calculate to coordinates (with client-side snapping when configured)
+ let toX, toY;
+ if (mode === "drag" && toNode.selected) {
+ let x = toNode.x + dx;
+ let y = toNode.y + dy;
+ if (gridSize > 1) {
+ x = snapToGrid(x);
+ y = snapToGrid(y);
+ }
+ toX = Math.round(fx(x) + iconSize / 2);
+ toY = Math.round(fy(y) + iconSize / 2);
} else {
- gc.lineTo(
- fx(nodes[hop.to].x) + iconSize / 2,
- fy(nodes[hop.to].y) + iconSize / 2);
+ toX = Math.round(fx(toNode.x) + iconSize / 2);
+ toY = Math.round(fy(toNode.y) + iconSize / 2);
}
+
+ gc.moveTo(fromX, fromY);
+ gc.lineTo(toX, toY);
gc.stroke();
});
}
@@ -220,27 +336,33 @@ function drawNodes(nodes, mode, dx, dy, gc, iconSize) {
let x = node.x;
let y = node.y;
- // Move selected nodes
+ // Move selected nodes with client-side snapping
//
if (mode === "drag" && (node.selected || node === clicked)) {
+ const origX = node.x;
+ const origY = node.y;
x = node.x + dx;
y = node.y + dy;
+
+ // Apply snap-to-grid during dragging (when grid size is
configured)
+ if (gridSize > 1) {
+ x = snapToGrid(x);
+ y = snapToGrid(y);
+ }
}
- // Draw the icon background
- //
- gc.rect(fx(x), fy(y), iconSize, iconSize);
- gc.fillStyle = bgColor;
- gc.fill();
-
- // Draw a bounding rectangle
+
+ // Skip drawing backgrounds - keep transparent so SVG icons show
through
+ // Only draw the bounding rectangle outline
//
if (node.selected || node === clicked) {
gc.lineWidth = 3;
gc.strokeStyle = selectedNodeColor;
} else {
- gc.strokeStyle = nodeColor; //colorCrystalText
+ gc.lineWidth = 1;
+ gc.strokeStyle = nodeColor;
}
- drawRoundRectangle(gc, fx(x - 1), fy(y - 1), iconSize + 1, iconSize +
1, 8, 8, false);
+ // Use rounded screen coordinates for pixel-perfect rendering
+ drawRoundRectangle(gc, Math.round(fx(x - 1)), Math.round(fy(y - 1)),
iconSize + 1, iconSize + 1, 8, 8, false);
gc.strokeStyle = fgColor;
gc.lineWidth = 1;
@@ -251,9 +373,8 @@ function drawNodes(nodes, mode, dx, dy, gc, iconSize) {
// Calculate the font size and magnify it as well.
//
gc.fillText(nodeName,
- fx(x) + iconSize / 2 - gc.measureText(nodeName).width / 2,
- fy(y) + iconSize + 7);
- gc.fillStyle = bgColor;
+ Math.round(fx(x) + iconSize / 2 - gc.measureText(nodeName).width /
2),
+ Math.round(fy(y) + iconSize + 7));
}
}
@@ -262,13 +383,13 @@ function drawNotes(notes, gc, mode, dx, dy) {
gc.beginPath();
if (mode === "drag" && note.selected) {
gc.rect(
- fx(note.x + dx),
- fy(note.y + dy),
+ Math.round(fx(note.x + dx)),
+ Math.round(fy(note.y + dy)),
note.width + 10, note.height + 10);
} else {
gc.rect(
- fx(note.x),
- fy(note.y),
+ Math.round(fx(note.x)),
+ Math.round(fy(note.y)),
note.width + 10, note.height + 10);
}
gc.stroke();
@@ -276,21 +397,27 @@ function drawNotes(notes, gc, mode, dx, dy) {
}
function fx(x) {
- if (x<0) {
- return 0;
- }
- return (x + offsetX) * magnification + offsetX;
+ // Don't clamp negative values - allow drawing outside visible area
+ const result = (x + offsetX) * magnification + offsetX;
+ return result;
}
function fy(y) {
- if (y<0) {
- return 0;
- }
- return (y + offsetY) * magnification + offsetY;
+ // Don't clamp negative values - allow drawing outside visible area
+ const result = (y + offsetY) * magnification + offsetY;
+ return result;
}
function snapToGrid(x) {
- return gridSize * Math.floor(x / gridSize);
+ // Match Java behavior: integer division first, then multiply
+ // Java: gridSize * (int) Math.round((float) (p.x / gridSize))
+ // where (p.x / gridSize) is integer division in Java
+
+ // JavaScript equivalent: Math.trunc() truncates towards zero (like Java
int division)
+ const gridCell = Math.trunc(x / gridSize);
+ const snapped = Math.round(gridSize * gridCell);
+
+ return snapped;
}
/*
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css
b/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css
index a9656701e0..ee76dfd58a 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css
@@ -2522,3 +2522,10 @@ Shell.jface_contentProposalPopup,
Shell.jface_infoPopupDialog {
padding: 0;
box-shadow: 0 0 4px #ababab;
}
+
+/* Canvas sizing - ensure canvas fills its container */
+canvas {
+ width: 100% !important;
+ height: 100% !important;
+ display: block !important;
+}
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css
b/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css
index 0d0179c456..ed604890cf 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css
@@ -2534,3 +2534,10 @@ Shell.jface_contentProposalPopup,
Shell.jface_infoPopupDialog {
box-shadow: 0 0 4px #ababab;
}
+/* Canvas sizing - ensure canvas fills its container */
+canvas {
+ width: 100% !important;
+ height: 100% !important;
+ display: block !important;
+}
+
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
index 4ec638fb22..35d56fa225 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
@@ -277,17 +277,14 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
"pipeline-graph-transform-11000-view-execution-info";
public static final String CONST_ERROR = "Error";
public static final String CONST_ERROR_PREVIEWING_PIPELINE = "Error
previewing pipeline";
+ public static final String START_HOP_NODE = "startHopNode";
+ public static final String PIPELINE_GRAPH_DIALOG_HOP_CAUSES_LOOP_MESSAGE =
+ "PipelineGraph.Dialog.HopCausesLoop.Message";
private final ILogChannel log;
private static final int HOP_SEL_MARGIN = 9;
- private static final int TOOLTIP_HIDE_DELAY_FLASH = 2000;
-
- private static final int TOOLTIP_HIDE_DELAY_SHORT = 5000;
-
- private static final int TOOLTIP_HIDE_DELAY_LONG = 10000;
-
@Getter private PipelineMeta pipelineMeta;
@Getter public IPipelineEngine<PipelineMeta> pipeline;
@@ -392,7 +389,6 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
Timer redrawTimer;
@Setter private HopPipelineFileType<PipelineMeta> fileType;
- private boolean singleClick;
private boolean doubleClick;
private boolean mouseMovedSinceClick;
@@ -527,7 +523,6 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
setVisible(true);
newProps();
- canvas.setBackground(GuiResource.getInstance().getColorBlueCustomGrid());
canvas.addPaintListener(this::paintControl);
selectedTransforms = null;
@@ -550,9 +545,6 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
// where the focus should be
//
hopGui.replaceKeyboardShortcutListeners(this);
-
- // Scrolled composite ...
- //
canvas.pack();
// Update menu, toolbar, force redraw canvas
@@ -752,6 +744,7 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
// SHIFT CLICK: start drawing a new hop
//
canvas.setData("mode", "hop");
+ canvas.setData(START_HOP_NODE, currentTransform.getName());
startHopTransform = currentTransform;
} else {
canvas.setData("mode", "drag");
@@ -858,6 +851,8 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
// go away.
//
if (startHopTransform != null) {
+ canvas.setData("mode", "null");
+ canvas.setData(START_HOP_NODE, null);
startHopTransform = null;
endHopLocation = null;
candidate = null;
@@ -903,8 +898,12 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
resize = null;
forbiddenTransform = null;
- // canvas.setData("mode", null); does not work.
- canvas.setData("mode", "null");
+ // Only clear mode if we're not in the middle of creating a hop
+ // Otherwise shift+click won't work (mode gets cleared before we can
select the target)
+ if (startHopTransform == null && endHopTransform == null) {
+ canvas.setData("mode", "null");
+ canvas.setData(START_HOP_NODE, null);
+ }
if (EnvironmentUtils.getInstance().isWeb()) {
// RAP does not support certain mouse events.
@@ -1349,7 +1348,6 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
&& (!pipelineMeta.getSelectedTransforms().isEmpty()
|| !pipelineMeta.getSelectedNotes().isEmpty())) {
pipelineMeta.unselectAll();
- selectionRegion = null;
updateGui();
// Show a short tooltip
@@ -1658,7 +1656,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
candidate = null;
forbiddenTransform = transformMeta;
toolTip.setText(
- BaseMessages.getString(PKG,
"PipelineGraph.Dialog.HopCausesLoop.Message"));
+ BaseMessages.getString(PKG,
PIPELINE_GRAPH_DIALOG_HOP_CAUSES_LOOP_MESSAGE));
showToolTip(new org.eclipse.swt.graphics.Point(event.x,
event.y));
}
}
@@ -2189,6 +2187,8 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
lastButton = 0;
iconOffset = null;
dragSelection = false;
+ canvas.setData("mode", "null");
+ canvas.setData(START_HOP_NODE, null);
startHopTransform = null;
endHopTransform = null;
endHopLocation = null;
@@ -2201,8 +2201,8 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
/**
* See if location (x,y) is on a line between two transforms: the hop!
*
- * @param x
- * @param y
+ * @param x coordinate
+ * @param y coordinate
* @return the pipeline hop on the specified location, otherwise: null
*/
protected PipelineHopMeta findPipelineHop(int x, int y) {
@@ -2212,8 +2212,8 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
/**
* See if location (x,y) is on a line between two transforms: the hop!
*
- * @param x
- * @param y
+ * @param x coordinate
+ * @param y coordinate
* @param exclude the transform to exclude from the hops (from or to
location). Specify null if no
* transform is to be excluded.
* @return the pipeline hop on the specified location, otherwise: null
@@ -2628,9 +2628,10 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
pipeline.getComponentCopies(transformMeta.getName());
return !Utils.isEmpty(componentCopies);
}
+ default -> {
+ return true;
+ }
}
-
- return true;
}
@GuiContextAction(
@@ -2735,7 +2736,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
if (enabled && hasLoop) {
modalMessageDialog(
BaseMessages.getString(PKG,
"PipelineGraph.Dialog.HopCausesLoop.Title"),
- BaseMessages.getString(PKG,
"PipelineGraph.Dialog.HopCausesLoop.Message"),
+ BaseMessages.getString(PKG,
PIPELINE_GRAPH_DIALOG_HOP_CAUSES_LOOP_MESSAGE),
SWT.OK | SWT.ICON_ERROR);
}
@@ -2784,7 +2785,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
if (checkedTransforms.stream().anyMatch(entry ->
pipelineMeta.hasLoop(entry))) {
modalMessageDialog(
BaseMessages.getString(PKG,
"PipelineGraph.Dialog.HopCausesLoop.Title"),
- BaseMessages.getString(PKG,
"PipelineGraph.Dialog.HopCausesLoop.Message"),
+ BaseMessages.getString(PKG,
PIPELINE_GRAPH_DIALOG_HOP_CAUSES_LOOP_MESSAGE),
SWT.OK | SWT.ICON_ERROR);
}
@@ -3099,8 +3100,8 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
}
break;
case TRANSFORM_FAILURE_ICON:
- String log = (String) areaOwner.getParent();
- tip.append(log);
+ String failureLog = (String) areaOwner.getParent();
+ tip.append(failureLog);
tipImage = GuiResource.getInstance().getImageFailure();
break;
case HOP_COPY_ICON:
@@ -3298,13 +3299,13 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
} else {
tooltipImage = GuiResource.getInstance().getImageHopUi();
}
- showTooltip(newTip, tooltipImage, screenX, screenY);
+ showSpecialTooltip(newTip, screenX, screenY);
}
return subject;
}
- public void showTooltip(String label, Image image, int screenX, int screenY)
{
+ public void showSpecialTooltip(String label, int screenX, int screenY) {
toolTip.setText(label);
toolTip.setVisible(false);
showToolTip(new org.eclipse.swt.graphics.Point(screenX, screenY));
@@ -3586,7 +3587,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
ni.setBorderColorGreen(n.getBorderColorGreen());
ni.setBorderColorBlue(n.getBorderColorBlue());
- NotePadMeta after = (NotePadMeta) ni.clone();
+ NotePadMeta after = ni.clone();
hopGui.undoDelegate.addUndoChange(
pipelineMeta,
new NotePadMeta[] {before},
@@ -3625,6 +3626,8 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
public void newHopCandidate(HopGuiPipelineTransformContext context) {
startHopTransform = context.getTransformMeta();
endHopTransform = null;
+ canvas.setData("mode", "hop");
+ canvas.setData(START_HOP_NODE, startHopTransform.getName());
redraw();
}
@@ -3659,11 +3662,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
double anglePoint = Math.atan2(y - y1, x - x1) + Math.PI;
// Same angle, or close enough?
- if (anglePoint >= angleLine - 0.01 && anglePoint <= angleLine + 0.01) {
- return true;
- }
-
- return false;
+ return anglePoint >= angleLine - 0.01 && anglePoint <= angleLine + 0.01;
}
public SnapAllignDistribute createSnapAlignDistribute() {
@@ -3702,7 +3701,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
image = "ui/images/preview.svg",
category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Preview.Text",
categoryOrder = "3")
- /** Preview a single transform */
+ // Preview a single transform
public void preview(HopGuiPipelineTransformContext context) {
try {
context.getPipelineMeta().unselectAll();
@@ -3747,7 +3746,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
image = "ui/images/debug.svg",
category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Preview.Text",
categoryOrder = "3")
- /** Debug a single transform */
+ // Debug a single transform
public void debug(HopGuiPipelineTransformContext context) {
try {
context.getPipelineMeta().unselectAll();
@@ -3994,11 +3993,8 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
}
return true;
}
- if ((answer & SWT.NO) != 0) {
- // User doesn't want to save but close
- return true;
- }
- return false;
+ // User doesn't want to save but close
+ return (answer & SWT.NO) != 0;
} else {
return true;
}
@@ -4461,6 +4457,8 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
}
}
break;
+ default:
+ break;
}
}
});
@@ -4849,7 +4847,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
}
// Redraw the canvas to show the error icons etc.
//
- hopDisplay().asyncExec(() -> redraw());
+ hopDisplay().asyncExec(this::redraw);
}
public synchronized void showLastPreviewResults() {
@@ -4893,10 +4891,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
if (pipeline.isPreparing()) {
return true;
}
- if (pipeline.isRunning()) {
- return true;
- }
- return false;
+ return pipeline.isRunning();
}
@Override
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
index a8883521f2..37378483ec 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
@@ -223,7 +223,6 @@ public class HopGuiWorkflowGraph extends HopGuiAbstractGraph
private static final int HOP_SEL_MARGIN = 9;
- private static final int TOOLTIP_HIDE_DELAY_FLASH = 2000;
public static final String ACTION_ID_WORKFLOW_GRAPH_HOP_ENABLE =
"workflow-graph-hop-10010-hop-enable";
public static final String ACTION_ID_WORKFLOW_GRAPH_HOP_DISABLE =
@@ -241,6 +240,7 @@ public class HopGuiWorkflowGraph extends HopGuiAbstractGraph
"WorkflowGraph.Dialog.LoopAfterHopEnabled.Message";
public static final String
CONST_WORKFLOW_GRAPH_DIALOG_LOOP_AFTER_HOP_ENABLED_TITLE =
"WorkflowGraph.Dialog.LoopAfterHopEnabled.Title";
+ public static final String START_HOP_NODE = "startHopNode";
@Getter private final ExplorerPerspective perspective;
@@ -435,6 +435,11 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
setVisible(true);
+ // Set canvas background to match application background for web
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ canvas.setBackground(GuiResource.getInstance().getColorBackground());
+ }
+
canvas.addPaintListener(this::paintControl);
selectedActions = null;
@@ -595,6 +600,7 @@ public class HopGuiWorkflowGraph extends HopGuiAbstractGraph
// SHIFT CLICK is start of drag to create a new hop
//
canvas.setData("mode", "hop");
+ canvas.setData(START_HOP_NODE, currentAction.getName());
startHopAction = currentAction;
} else {
canvas.setData("mode", "drag");
@@ -727,6 +733,8 @@ public class HopGuiWorkflowGraph extends HopGuiAbstractGraph
// go away.
//
if (startHopAction != null) {
+ canvas.setData("mode", "null");
+ canvas.setData(START_HOP_NODE, null);
startHopAction = null;
hopCandidate = null;
endHopLocation = null;
@@ -771,8 +779,12 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
dragSelection = false;
forbiddenAction = null;
- // canvas.setData("mode", null); does not work.
- canvas.setData("mode", "null");
+ // Only clear mode if we're not in the middle of creating a hop
+ // Otherwise shift+click won't work (mode gets cleared before we can
select the target)
+ if (startHopAction == null && endHopAction == null) {
+ canvas.setData("mode", "null");
+ canvas.setData(START_HOP_NODE, null);
+ }
if (EnvironmentUtils.getInstance().isWeb()) {
// RAP does not support certain mouse events.
mouseMove(event);
@@ -945,7 +957,7 @@ public class HopGuiWorkflowGraph extends HopGuiAbstractGraph
BaseMessages.getString(PKG,
"HopGuiWorkflowGraph.Dialog.SplitHop.Title"),
BaseMessages.getString(PKG,
"HopGuiWorkflowGraph.Dialog.SplitHop.Message")
+ Const.CR
- + hop.toString(),
+ + hop,
SWT.ICON_QUESTION,
new String[] {
BaseMessages.getString(PKG, "System.Button.Yes"),
@@ -1113,7 +1125,6 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
&& (!workflowMeta.getSelectedActions().isEmpty()
|| !workflowMeta.getSelectedNotes().isEmpty())) {
workflowMeta.unselectAll();
- selectionRegion = null;
updateGui();
// Show a short tooltip
@@ -1489,12 +1500,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
}
public boolean checkIfHopAlreadyExists(WorkflowMeta workflowMeta,
WorkflowHopMeta newHop) {
- boolean ok = true;
- if (workflowMeta.findWorkflowHop(newHop.getFromAction(),
newHop.getToAction(), true) != null) {
- ok = false;
- }
- return ok;
+ return workflowMeta.findWorkflowHop(newHop.getFromAction(),
newHop.getToAction(), true) == null;
}
/**
@@ -1806,6 +1813,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
hopCandidate = null;
lastHopSplit = null;
lastButton = 0;
+ canvas.setData("mode", "null");
+ canvas.setData(START_HOP_NODE, null);
startHopAction = null;
endHopAction = null;
iconOffset = null;
@@ -1835,8 +1844,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
/**
* See if the location (x,y) is on a line between two actions: the hop!
*
- * @param x
- * @param y
+ * @param x coordinate
+ * @param y coordinate
* @return the workflow hop on the specified location, otherwise: null
*/
private WorkflowHopMeta findWorkflowHop(int x, int y) {
@@ -1846,8 +1855,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
/**
* See if the location (x,y) is on a line between two actions: the hop!
*
- * @param x
- * @param y
+ * @param x coordinate
+ * @param y coordinate
* @param exclude the action to exclude from the hops (from or to location).
Specify null if no
* action is to be excluded.
* @return the workflow hop on the specified location, otherwise: null
@@ -1942,6 +1951,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
public void newHopCandidate(HopGuiWorkflowActionContext context) {
startHopAction = context.getActionMeta();
endHopAction = null;
+ canvas.setData("mode", "hop");
+ canvas.setData(START_HOP_NODE, startHopAction.getName());
redraw();
}
@@ -2501,9 +2512,9 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
WorkflowHopMeta hop = workflowMeta.getWorkflowHop(i);
if (list.contains(hop.getFromAction()) &&
list.contains(hop.getToAction())) {
- WorkflowHopMeta before = (WorkflowHopMeta) hop.clone();
+ WorkflowHopMeta before = hop.clone();
hop.setEnabled(enabled);
- WorkflowHopMeta after = (WorkflowHopMeta) hop.clone();
+ WorkflowHopMeta after = hop.clone();
hopGui.undoDelegate.addUndoChange(
workflowMeta,
new WorkflowHopMeta[] {before},
@@ -2619,9 +2630,9 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
if (hop == null) {
return;
}
- WorkflowHopMeta before = (WorkflowHopMeta) hop.clone();
+ WorkflowHopMeta before = hop.clone();
hop.setEnabled(enabled);
- WorkflowHopMeta after = (WorkflowHopMeta) hop.clone();
+ WorkflowHopMeta after = hop.clone();
hopGui.undoDelegate.addUndoChange(
workflowMeta,
new WorkflowHopMeta[] {before},
@@ -2651,9 +2662,9 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
.forEach(
hop -> {
if (hop.isEnabled() != enabled) {
- WorkflowHopMeta before = (WorkflowHopMeta) hop.clone();
+ WorkflowHopMeta before = hop.clone();
hop.setEnabled(enabled);
- WorkflowHopMeta after = (WorkflowHopMeta) hop.clone();
+ WorkflowHopMeta after = hop.clone();
hopGui.undoDelegate.addUndoChange(
workflowMeta,
new WorkflowHopMeta[] {before},
@@ -2716,6 +2727,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
messageBox.open();
}
+ @SuppressWarnings({"java:S1854", "java:S1481"})
+ // Ignore warning of setting tipImage
protected void setToolTip(int x, int y, int screenX, int screenY) {
if (!hopGui.getProps().showToolTips() || openedContextDialog) {
return;
@@ -3174,8 +3187,6 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
new NotePadMeta[] {before},
new NotePadMeta[] {notePadMeta},
new int[] {workflowMeta.indexOfNote(notePadMeta)});
- // notePadMeta.width = ConstUi.NOTE_MIN_SIZE;
- // notePadMeta.height = ConstUi.NOTE_MIN_SIZE;
updateGui();
}
@@ -3212,11 +3223,7 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
double anglePoint = Math.atan2(y - y1, x - x1) + Math.PI;
// Same angle, or close enough?
- if (anglePoint >= angleLine - 0.01 && anglePoint <= angleLine + 0.01) {
- return true;
- }
-
- return false;
+ return anglePoint >= angleLine - 0.01 && anglePoint <= angleLine + 0.01;
}
@Override
@@ -3342,10 +3349,7 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
if (workflow.isActive()) {
return true;
}
- if (workflow.isInitialized()) {
- return true;
- }
- return false;
+ return workflow.isInitialized();
}
/**
@@ -3757,11 +3761,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
}
return true;
}
- if ((answer & SWT.NO) != 0) {
- // User doesn't want to save but close
- return true;
- }
- return false;
+ // User doesn't want to save but close
+ return (answer & SWT.NO) != 0;
} else {
return true;
}
@@ -4421,10 +4422,8 @@ public class HopGuiWorkflowGraph extends
HopGuiAbstractGraph
return false;
}
ActionResult actionResult = actionTracker.getActionResult();
- if (actionResult == null) {
- // No execution information available yet (not started)
- return false;
- }
+ // No execution information available yet (not started)
+ return actionResult != null;
}
return true;
}