This is an automated email from the ASF dual-hosted git repository.
bamaer 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 c7fc11cf0d Issue #8595 : Don't show data when moving transforms (#8616)
c7fc11cf0d is described below
commit c7fc11cf0d1f3e68744fcbaedb386960328310da
Author: Matt Casters <[email protected]>
AuthorDate: Fri Sep 25 19:44:57 2026 +0200
Issue #8595 : Don't show data when moving transforms (#8616)
Releasing the mouse after a move no longer opens the output rows when the
pointer comes up on a data badge. A click on the badge still opens them.
---
.../HopGuiPipelineGraphClickContractTest.java | 3 +
.../HopGuiPipelineGraphOutputDataTest.java | 359 +++++++++++++++++++++
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 329 ++++++++++++++-----
3 files changed, 602 insertions(+), 89 deletions(-)
diff --git
a/rcp/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraphClickContractTest.java
b/rcp/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraphClickContractTest.java
index 10f695f538..0053dc29f7 100644
---
a/rcp/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraphClickContractTest.java
+++
b/rcp/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraphClickContractTest.java
@@ -369,6 +369,9 @@ class HopGuiPipelineGraphClickContractTest extends
GraphCanvasTestBase {
idle.put("selectionRegion", null);
idle.put("dragSelection", false);
idle.put("iconDragStartScreen", null);
+ idle.put("iconDragCommitted", false);
+ idle.put("outputDataPressed", false);
+ idle.put("showingOutputRows", false);
idle.put("avoidContextDialog", false);
idle.put("lastButton", 0);
assertGraphState(graph, idle);
diff --git
a/rcp/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraphOutputDataTest.java
b/rcp/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraphOutputDataTest.java
new file mode 100644
index 0000000000..876068b023
--- /dev/null
+++
b/rcp/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraphOutputDataTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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.file.pipeline;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.gui.AreaOwner;
+import org.apache.hop.core.gui.Point;
+import org.apache.hop.core.row.RowBuffer;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.PipelineHopMeta;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.engines.local.LocalPipelineEngine;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.dummy.DummyMeta;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.HopGuiEnvironment;
+import org.apache.hop.ui.hopgui.file.GraphCanvasTestBase;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swtbot.swt.finder.SWTBot;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Issue #8595: the output-rows badge sits on the corner of a transform and
part way along a hop, so
+ * the mouse-up of a move often lands on it. That release ends the move. A
click on the badge still
+ * opens the rows, once, and leaves the canvas idle so the next click is not
another move.
+ */
+@Tag("uitest")
+class HopGuiPipelineGraphOutputDataTest extends GraphCanvasTestBase {
+
+ private static final String SOURCE_TRANSFORM = "Read";
+ private static final String TARGET_TRANSFORM = "Write";
+ private static final Point SOURCE_LOCATION = new Point(80, 80);
+ private static final Point TARGET_LOCATION = new Point(320, 80);
+ private static final int DRAG_Y = 160;
+
+ @BeforeAll
+ static void registerGuiPlugins() throws HopException {
+ HopGuiEnvironment.init();
+ }
+
+ @Test
+ void clickingTheBadgeOpensTheRowsOnce() {
+ onCanvas(
+ scene -> {
+ Point badge = scene.outputBadge(scene.source);
+ Point before = scene.locationOf(scene.source);
+
+ Set<Shell> openBefore = openShells();
+ fireAsync(scene.canvas, SWT.MouseDown, scene.scale, badge, 1,
SWT.NONE);
+ fireAsync(scene.canvas, SWT.MouseUp, scene.scale, badge, 1,
SWT.BUTTON1);
+ Shell rows = awaitNewShell(scene.bot, openBefore);
+ assertNotNull(rows, "a click on the output-data badge opens the
rows");
+ assertEquals(scene.transformRowsTitle, titleOf(rows));
+
+ // The rows dialog runs its own event loop, so this second click is
delivered while the
+ // first dialog is still up. It must not open another one, or stack
them until Escape.
+ fireAsync(scene.canvas, SWT.MouseDown, scene.scale, badge, 1,
SWT.NONE);
+ fireAsync(scene.canvas, SWT.MouseUp, scene.scale, badge, 1,
SWT.BUTTON1);
+
+ assertEquals(List.of(scene.transformRowsTitle),
catchDialogs(scene.bot, openBefore));
+ assertEquals(
+ before, scene.locationOf(scene.source), "a click must not move
the transform");
+ assertTrue(scene.source.isSelected(), "viewing the rows keeps the
selection");
+ assertIdle(scene.graph);
+ });
+ }
+
+ @Test
+ void releasingADragOnTheBadgeDoesNotOpenTheRows() {
+ onCanvas(
+ scene -> {
+ Point icon = scene.icon(scene.source);
+ Point beforeSource = scene.locationOf(scene.source);
+ Point beforeTarget = scene.locationOf(scene.target);
+
+ fire(scene.canvas, SWT.MouseDown, scene.scale, icon, 1, SWT.NONE);
+ Point drop = new Point(icon.x, icon.y + DRAG_Y);
+ fire(scene.canvas, SWT.MouseMove, scene.scale, drop, 0, SWT.BUTTON1);
+
+ Point badge = scene.outputBadge(scene.source);
+ List<String> dialogs = release(scene, badge);
+
+ Point afterSource = scene.locationOf(scene.source);
+ Point afterTarget = scene.locationOf(scene.target);
+ assertTrue(dialogs.isEmpty(), "releasing a drag on the output-data
badge opens no rows");
+ assertTrue(
+ afterSource.y - beforeSource.y > 100, "the dragged transform
should have moved down");
+ assertEquals(beforeSource.x, afterSource.x);
+ assertEquals(
+ afterSource.y - beforeSource.y,
+ afterTarget.y - beforeTarget.y,
+ "every selected transform moves together");
+ assertIdle(scene.graph);
+
+ // The next click is a click again: one rows dialog, and the
transform stays where the
+ // drag left it.
+ List<String> after = click(scene, scene.outputBadge(scene.source));
+ assertEquals(List.of(scene.transformRowsTitle), after);
+ assertEquals(afterSource, scene.locationOf(scene.source));
+ assertIdle(scene.graph);
+ });
+ }
+
+ @Test
+ void draggingFromTheBadgeMovesTheTransform() {
+ onCanvas(
+ scene -> {
+ Point badge = scene.outputBadge(scene.source);
+ Point before = scene.locationOf(scene.source);
+ Point drop = new Point(badge.x, badge.y + DRAG_Y);
+
+ fire(scene.canvas, SWT.MouseDown, scene.scale, badge, 1, SWT.NONE);
+ fire(scene.canvas, SWT.MouseMove, scene.scale, drop, 0, SWT.BUTTON1);
+ // The badge was the grab point, so it is still under the pointer
when the button comes
+ // up.
+ List<String> dialogs = release(scene, drop);
+
+ assertTrue(
+ dialogs.isEmpty(),
+ "a drag that starts on the output-data badge does not open the
rows");
+ assertTrue(scene.locationOf(scene.source).y - before.y > 100);
+ assertIdle(scene.graph);
+ });
+ }
+
+ @Test
+ void hopBadgeClickOpensRowsAndADragReleaseDoesNot() {
+ onCanvas(
+ scene -> {
+ List<String> clicked = click(scene, scene.hopBadge());
+ assertEquals(List.of(scene.hopRowsTitle), clicked);
+ assertIdle(scene.graph);
+
+ Point icon = scene.icon(scene.source);
+ Point before = scene.locationOf(scene.source);
+ fire(scene.canvas, SWT.MouseDown, scene.scale, icon, 1, SWT.NONE);
+ fire(
+ scene.canvas,
+ SWT.MouseMove,
+ scene.scale,
+ new Point(icon.x, icon.y + DRAG_Y),
+ 0,
+ SWT.BUTTON1);
+ List<String> dragged = release(scene, scene.hopBadge());
+
+ assertTrue(
+ dragged.isEmpty(), "releasing a drag on the hop output-data
badge opens no rows");
+ assertTrue(scene.locationOf(scene.source).y - before.y > 100);
+ assertIdle(scene.graph);
+ });
+ }
+
+ private List<String> click(Scene scene, Point at) {
+ return clickAndCatchDialogs(scene.bot, scene.canvas, scene.scale, at, 1,
SWT.NONE);
+ }
+
+ /** Mouse-up posted without waiting, so a rows dialog cannot deadlock the
test. */
+ private List<String> release(Scene scene, Point at) {
+ Set<Shell> before = openShells();
+ fireAsync(scene.canvas, SWT.MouseUp, scene.scale, at, 1, SWT.BUTTON1);
+ return catchDialogs(scene.bot, before);
+ }
+
+ private static void assertIdle(HopGuiPipelineGraph graph) {
+ Map<String, Object> idle = new LinkedHashMap<>();
+ idle.put("startHopTransform", null);
+ idle.put("endHopTransform", null);
+ idle.put("endHopLocation", null);
+ idle.put("candidate", null);
+ idle.put("selectedTransform", null);
+ idle.put("selectedTransforms", null);
+ idle.put("selectionRegion", null);
+ idle.put("dragSelection", false);
+ idle.put("iconDragStartScreen", null);
+ idle.put("iconDragCommitted", false);
+ idle.put("outputDataPressed", false);
+ idle.put("showingOutputRows", false);
+ idle.put("avoidContextDialog", false);
+ idle.put("lastButton", 0);
+ idle.put("clickedPipelineHop", null);
+ assertGraphState(graph, idle);
+ }
+
+ private void onCanvas(Consumer<Scene> test) {
+ AtomicReference<HopGuiPipelineGraph> graphRef = new AtomicReference<>();
+ AtomicReference<PipelineMeta> metaRef = new AtomicReference<>();
+ int[] previousGrid = new int[1];
+
+ withScene(
+ shell -> {
+ shell.setSize(1000, 700);
+ shell.setLayout(new FormLayout());
+ PropsUi props = PropsUi.getInstance();
+ previousGrid[0] = props.getCanvasGridSize();
+ props.setCanvasGridSize(1);
+ props.setUseDoubleClickOnCanvas(false);
+ props.setUseRightClickForContextDialog(false);
+
+ PipelineMeta pipelineMeta = buildPipeline();
+ metaRef.set(pipelineMeta);
+ HopGuiPipelineGraph graph =
+ new HopGuiPipelineGraph(
+ shell,
+ hopGui(),
+ new ExplorerPerspective(),
+ pipelineMeta,
+ new HopPipelineFileType<>());
+ graph.pipeline = new LocalPipelineEngine(pipelineMeta);
+ graph.setOutputRowsMap(outputRows());
+ graph.setOutputHopRowsMap(hopRows(pipelineMeta));
+ pipelineMeta.findTransform(SOURCE_TRANSFORM).setSelected(true);
+ pipelineMeta.findTransform(TARGET_TRANSFORM).setSelected(true);
+ graphRef.set(graph);
+ },
+ bot -> {
+ try {
+ test.accept(new Scene(bot, graphRef.get(), metaRef.get()));
+ } finally {
+ onUi(() ->
PropsUi.getInstance().setCanvasGridSize(previousGrid[0]));
+ }
+ });
+ }
+
+ private static PipelineMeta buildPipeline() {
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ pipelineMeta.setName("output-data");
+ TransformMeta source = transform(SOURCE_TRANSFORM, SOURCE_LOCATION);
+ TransformMeta target = transform(TARGET_TRANSFORM, TARGET_LOCATION);
+ pipelineMeta.addTransform(source);
+ pipelineMeta.addTransform(target);
+ pipelineMeta.addPipelineHop(new PipelineHopMeta(source, target));
+ return pipelineMeta;
+ }
+
+ private static TransformMeta transform(String name, Point location) {
+ TransformMeta transformMeta = new TransformMeta("Dummy", name, new
DummyMeta());
+ transformMeta.setLocation(location.x, location.y);
+ return transformMeta;
+ }
+
+ private static Map<String, RowBuffer> outputRows() {
+ Map<String, RowBuffer> rows = new HashMap<>();
+ rows.put(SOURCE_TRANSFORM, sample());
+ rows.put(TARGET_TRANSFORM, sample());
+ return rows;
+ }
+
+ private static Map<String, RowBuffer> hopRows(PipelineMeta pipelineMeta) {
+ TransformMeta source = pipelineMeta.findTransform(SOURCE_TRANSFORM);
+ TransformMeta target = pipelineMeta.findTransform(TARGET_TRANSFORM);
+ Map<String, RowBuffer> rows = new HashMap<>();
+ rows.put(source.getName() + "\t" + target.getName(), sample());
+ return rows;
+ }
+
+ private static RowBuffer sample() {
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("id"));
+ RowBuffer buffer = new RowBuffer(rowMeta);
+ buffer.addRow("1");
+ return buffer;
+ }
+
+ private static String rowsTitle(String name) {
+ return BaseMessages.getString(
+ HopGui.class, "PipelineGraph.ViewOutput.OutputDialog.Header", name);
+ }
+
+ private final class Scene {
+ final SWTBot bot;
+ final HopGuiPipelineGraph graph;
+ final PipelineMeta pipelineMeta;
+ final Canvas canvas;
+ final double scale;
+ final TransformMeta source;
+ final TransformMeta target;
+ final String transformRowsTitle;
+ final String hopRowsTitle;
+
+ Scene(SWTBot bot, HopGuiPipelineGraph graph, PipelineMeta pipelineMeta) {
+ this.bot = bot;
+ this.graph = graph;
+ this.pipelineMeta = pipelineMeta;
+ this.canvas = onUi(graph::getCanvas);
+ this.scale = canvasToGraphScale(graph);
+ this.source = pipelineMeta.findTransform(SOURCE_TRANSFORM);
+ this.target = pipelineMeta.findTransform(TARGET_TRANSFORM);
+ this.transformRowsTitle = rowsTitle(SOURCE_TRANSFORM);
+ this.hopRowsTitle = rowsTitle(SOURCE_TRANSFORM + " → " +
TARGET_TRANSFORM);
+ }
+
+ Point icon(TransformMeta transform) {
+ return awaitIcon(
+ bot,
+ graph::getVisibleAreaOwner,
+ AreaOwner.AreaType.TRANSFORM_ICON,
+ transform,
+ transform == source ? SOURCE_LOCATION : TARGET_LOCATION);
+ }
+
+ Point outputBadge(TransformMeta transform) {
+ return awaitArea(
+ bot,
+ graph,
+ graph::getVisibleAreaOwner,
+ AreaOwner.AreaType.TRANSFORM_OUTPUT_DATA,
+ area -> area.getParent() == transform);
+ }
+
+ Point hopBadge() {
+ PipelineHopMeta hop = pipelineMeta.findPipelineHop(source, target);
+ return awaitArea(
+ bot,
+ graph,
+ graph::getVisibleAreaOwner,
+ AreaOwner.AreaType.HOP_OUTPUT_DATA,
+ area -> area.getParent() == hop);
+ }
+
+ Point locationOf(TransformMeta transform) {
+ Point location = onUi(transform::getLocation);
+ return new Point(location.x, location.y);
+ }
+ }
+}
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 f743301ad2..81f5436ec3 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
@@ -393,6 +393,21 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
/** True once pointer has moved past {@link #ICON_DRAG_THRESHOLD_PX} and
drag has started. */
private boolean iconDragCommitted;
+ /** Screen position of the current press, used to tell a click from a drag
on mouse-up. */
+ private Point mouseDownScreen;
+
+ /**
+ * The press landed on a transform or hop output-rows badge. The rows open
on release only for
+ * that press, and only when the pointer did not travel into a drag (issue
#8595).
+ */
+ private boolean outputDataPressed;
+
+ /**
+ * The output-rows dialog is on screen. It runs its own event loop, which
delivers any click made
+ * while it is up; that click must not move transforms or open a second copy
of the dialog.
+ */
+ private boolean showingOutputRows;
+
/**
* Display filters used while placing a transform dragged from the context
dialog (issue #3111).
* Create happens on mouse-up (drop), not on drag-start.
@@ -772,6 +787,9 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
@Override
public void mouseDoubleClick(MouseEvent event) {
+ if (showingOutputRows) {
+ return;
+ }
if (!PropsUi.getInstance().useDoubleClick()) {
return;
@@ -837,6 +855,9 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
@Override
public void mouseDown(MouseEvent event) {
+ if (showingOutputRows) {
+ return;
+ }
if (EnvironmentUtils.getInstance().isWeb()) {
// RAP does not support certain mouse events.
mouseHover(event);
@@ -854,9 +875,11 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
Point real = screen2real(event.x, event.y);
lastClick = new Point(real.x, real.y);
+ mouseDownScreen = new Point(event.x, event.y);
lastButton = event.button;
dragSelection = false;
iconDragStartScreen = null;
+ outputDataPressed = false;
// Hide the tooltip!
hideToolTips();
@@ -955,10 +978,28 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
break;
case TRANSFORM_OUTPUT_DATA:
+ // The rows open on mouse-up, and only if this press does not become
a drag. The badge
+ // covers the corner of the icon, so a press here also arms a move:
otherwise grabbing
+ // that corner cannot drag the transform, and the release still
opens the rows.
+ //
+ outputDataPressed = true;
+ if (canEditGraph() && event.button == 1 && !shift && !control) {
+ armIconDrag((TransformMeta) areaOwner.getParent(), event, real);
+ }
+ redraw();
done = true;
break;
case HOP_OUTPUT_DATA:
+ // A hop badge is a button, not a drag handle. Drop any transform
press so the release
+ // cannot finish a move and open the rows.
+ //
+ outputDataPressed = true;
+ currentTransform = null;
+ selectedTransform = null;
+ iconDragStartScreen = null;
+ iconDragCommitted = false;
+ dragSelection = false;
done = true;
break;
@@ -1026,29 +1067,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
} else if (canEditGraph()) {
// Defer entering drag mode until pointer moves past threshold
(avoids drag when
// clicking on name or making a small movement). Read-only
sessions never arm drag.
- iconDragStartScreen = new Point(event.x, event.y);
- iconDragCommitted = false;
- previousTransformLocations =
pipelineMeta.getSelectedTransformLocations();
-
- Point p = currentTransform.getLocation();
- iconOffset = new Point(real.x - p.x, real.y - p.y);
-
- // The RAP/web client does not deliver mouse-move events while a
button is held, so the
- // movement threshold in mouseMove() can never fire during a
press. Arm the drag right
- // away on mouse-down so the transform follows the cursor and is
dropped on mouse-up;
- // native SWT keeps the threshold behaviour to distinguish a click
from a drag.
- if (EnvironmentUtils.getInstance().isWeb() && event.button == 1 &&
!shift && !control) {
- iconDragCommitted = true;
- markPositionUndoPoint();
- dragSelection = true;
- canvas.setData("mode", "drag");
- selectedTransforms = pipelineMeta.getSelectedTransforms();
- selectedTransform = currentTransform;
- pipelineGridDelegate.onPipelineSelectionChanged();
- for (ITransformSelectionListener listener :
currentTransformListeners) {
- listener.onUpdateSelection(currentTransform);
- }
- }
+ armIconDrag(currentTransform, event, real);
}
redraw();
done = true;
@@ -1205,6 +1224,14 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
@Override
public void mouseUp(MouseEvent e) {
+ if (showingOutputRows) {
+ return;
+ }
+ // A preview badge opens its rows only for the press that landed on it.
Cleared before any
+ // return below, including the ones that finish a drag.
+ boolean previewPress = outputDataPressed && e.button == 1;
+ outputDataPressed = false;
+
// Track if we just completed a resize operation
boolean wasResizing = false;
@@ -1335,14 +1362,13 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
if (areaOwner != null && areaOwner.getAreaType() != null) {
switch (areaOwner.getAreaType()) {
case TRANSFORM_OUTPUT_DATA:
- if (showTransformOutputData(areaOwner)) {
- lastButton = 0;
- return;
- }
- break;
case HOP_OUTPUT_DATA:
- if (showHopOutputData(areaOwner)) {
- lastButton = 0;
+ // The badge sits on the corner of the icon and part way along the
hop, so the release of
+ // a move often lands on it. That release ends the move. A click,
the pointer having
+ // stayed within the drag threshold, is what opens the rows (issue
#8595).
+ //
+ if (previewPress && opensPreviewRows(e)) {
+ showPreviewRows(areaOwner);
return;
}
break;
@@ -1398,7 +1424,9 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
if (selectedTransform != null && startHopTransform == null) {
if (e.button == 1) {
Point realClick = screen2real(e.x, e.y);
- if (lastClick.x == realClick.x && lastClick.y == realClick.y) {
+ // A drag that ends where it started is still a drag. Hop Web arms
that flag on mouse-down,
+ // so there the coordinates alone distinguish a click.
+ if (lastClick.x == realClick.x && lastClick.y == realClick.y &&
!dragWasCommitted()) {
// Flip selection when control is pressed!
if (control) {
selectedTransform.flipSelected();
@@ -1553,6 +1581,115 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
lastButton = 0;
}
+ /**
+ * Arms a transform move from a press on its icon or on its output-rows
badge. The drag itself
+ * starts only after the pointer passes {@link #ICON_DRAG_THRESHOLD_PX},
except on Hop Web, which
+ * never delivers mouse-move events while a button is held.
+ */
+ private void armIconDrag(TransformMeta transform, MouseEvent event, Point
real) {
+ currentTransform = transform;
+ iconDragStartScreen = new Point(event.x, event.y);
+ iconDragCommitted = false;
+ previousTransformLocations = pipelineMeta.getSelectedTransformLocations();
+
+ Point p = currentTransform.getLocation();
+ iconOffset = new Point(real.x - p.x, real.y - p.y);
+
+ // The RAP/web client does not deliver mouse-move events while a button is
held, so the
+ // movement threshold in mouseMove() can never fire during a press. Arm
the drag right away on
+ // mouse-down so the transform follows the cursor and is dropped on
mouse-up; native SWT keeps
+ // the threshold behaviour to distinguish a click from a drag.
+ boolean shift = (event.stateMask & SWT.SHIFT) != 0;
+ boolean control = (event.stateMask & SWT.MOD1) != 0;
+ if (EnvironmentUtils.getInstance().isWeb() && event.button == 1 && !shift
&& !control) {
+ iconDragCommitted = true;
+ markPositionUndoPoint();
+ dragSelection = true;
+ canvas.setData("mode", "drag");
+ selectedTransforms = pipelineMeta.getSelectedTransforms();
+ selectedTransform = currentTransform;
+ pipelineGridDelegate.onPipelineSelectionChanged();
+ for (ITransformSelectionListener listener : currentTransformListeners) {
+ listener.onUpdateSelection(currentTransform);
+ }
+ }
+ }
+
+ /**
+ * A stationary release on the badge that was pressed. A drag, a hop or a
lasso that ends on the
+ * badge does not qualify: releasing the mouse after one of those ends the
gesture and nothing
+ * more.
+ */
+ private boolean opensPreviewRows(MouseEvent event) {
+ if (startHopTransform != null || selectionRegion != null ||
movedPastDragThreshold(event)) {
+ return false;
+ }
+ // On the desktop a committed drag is a move even when the pointer is back
where it started.
+ // Hop Web arms the drag on mouse-down, so there the distance above is the
whole check.
+ return !dragWasCommitted();
+ }
+
+ /** The press moved far enough to be a drag. Hop Web sets the flag before
any movement. */
+ private boolean dragWasCommitted() {
+ return iconDragCommitted && !EnvironmentUtils.getInstance().isWeb();
+ }
+
+ private boolean movedPastDragThreshold(MouseEvent event) {
+ if (mouseDownScreen == null) {
+ return false;
+ }
+ int dx = event.x - mouseDownScreen.x;
+ int dy = event.y - mouseDownScreen.y;
+ return dx * dx + dy * dy > (long) ICON_DRAG_THRESHOLD_PX *
ICON_DRAG_THRESHOLD_PX;
+ }
+
+ /**
+ * Opens the rows for the badge under the pointer. The dialog runs its own
event loop, so the drag
+ * is dropped first: a click that loop dispatches would otherwise still be
the move that just
+ * ended, and would open the rows again.
+ */
+ private void showPreviewRows(AreaOwner areaOwner) {
+ endPreviewPress();
+ if (areaOwner.getAreaType() == AreaType.HOP_OUTPUT_DATA) {
+ showHopOutputData(areaOwner);
+ } else {
+ showTransformOutputData(areaOwner);
+ }
+ }
+
+ /**
+ * Drops the press that opened the output rows, without changing what is
selected on the graph.
+ */
+ private void endPreviewPress() {
+ selectedTransform = null;
+ currentTransform = null;
+ selectedNote = null;
+ selectedTransforms = null;
+ selectedNotes = null;
+ dragSelection = false;
+ iconDragStartScreen = null;
+ iconDragCommitted = false;
+ iconOffset = null;
+ splitHop = false;
+ if (lastHopSplit != null) {
+ lastHopSplit.setSplit(false);
+ lastHopSplit = null;
+ }
+ startHopTransform = null;
+ endHopTransform = null;
+ endHopLocation = null;
+ candidate = null;
+ clickedPipelineHop = null;
+ clickedHopBadge = false;
+ selectionRegion = null;
+ lastButton = 0;
+ avoidContextDialog = false;
+ canvas.setData("mode", "null");
+ canvas.setData(START_HOP_NODE, null);
+ canvas.setData("resizeDirection", null);
+ resetPositionUndoMark();
+ }
+
@GuiContextAction(
id = "pipeline-graph-transform-1000-view-output",
parentId = HopGuiPipelineTransformContext.CONTEXT_ID,
@@ -1599,72 +1736,79 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
}
private boolean showOutputDataDialog(String titleName, String messageName,
RowBuffer rowBuffer) {
- if (rowBuffer != null) {
- synchronized (rowBuffer.getBuffer()) {
- if (!rowBuffer.isEmpty()) {
- try {
- String title =
- BaseMessages.getString(
- PKG, "PipelineGraph.ViewOutput.OutputDialog.Header",
titleName);
- String message =
- BaseMessages.getString(
- PKG,
"PipelineGraph.ViewOutput.OutputDialog.OutputRows.Text", messageName);
- String prefix = "";
-
- if (pipeline != null && pipeline.getPipelineRunConfiguration() !=
null) {
- PipelineRunConfiguration pipelineRunConfiguration =
- pipeline.getPipelineRunConfiguration();
- if (pipelineRunConfiguration.getEngineRunConfiguration()
- instanceof LocalPipelineRunConfiguration
localPipelineRunConfiguration) {
- String sampleTypeInGui =
localPipelineRunConfiguration.getSampleTypeInGui();
- if (StringUtils.isNotEmpty(sampleTypeInGui)) {
- try {
- SampleType sampleType =
SampleType.valueOf(sampleTypeInGui);
- switch (sampleType) {
- case None:
- break;
- case First:
- prefix =
- BaseMessages.getString(
- PKG,
"PipelineGraph.ViewOutput.OutputDialog.First.Text");
- break;
- case Last:
- prefix =
- BaseMessages.getString(
- PKG,
"PipelineGraph.ViewOutput.OutputDialog.Last.Text");
- break;
- case Random:
- prefix +=
- BaseMessages.getString(
- PKG,
"PipelineGraph.ViewOutput.OutputDialog.Random.Text");
- break;
- default:
- break;
- }
- } catch (Exception ex) {
- LogChannel.UI.logError("Unknown sample type: " +
sampleTypeInGui);
+ if (rowBuffer == null) {
+ return false;
+ }
+ // Already inside this dialog's event loop: the click that got us here is
not another preview.
+ if (showingOutputRows) {
+ return true;
+ }
+ synchronized (rowBuffer.getBuffer()) {
+ if (!rowBuffer.isEmpty()) {
+ showingOutputRows = true;
+ try {
+ String title =
+ BaseMessages.getString(
+ PKG, "PipelineGraph.ViewOutput.OutputDialog.Header",
titleName);
+ String message =
+ BaseMessages.getString(
+ PKG,
"PipelineGraph.ViewOutput.OutputDialog.OutputRows.Text", messageName);
+ String prefix = "";
+
+ if (pipeline != null && pipeline.getPipelineRunConfiguration() !=
null) {
+ PipelineRunConfiguration pipelineRunConfiguration =
+ pipeline.getPipelineRunConfiguration();
+ if (pipelineRunConfiguration.getEngineRunConfiguration()
+ instanceof LocalPipelineRunConfiguration
localPipelineRunConfiguration) {
+ String sampleTypeInGui =
localPipelineRunConfiguration.getSampleTypeInGui();
+ if (StringUtils.isNotEmpty(sampleTypeInGui)) {
+ try {
+ SampleType sampleType = SampleType.valueOf(sampleTypeInGui);
+ switch (sampleType) {
+ case None:
+ break;
+ case First:
+ prefix =
+ BaseMessages.getString(
+ PKG,
"PipelineGraph.ViewOutput.OutputDialog.First.Text");
+ break;
+ case Last:
+ prefix =
+ BaseMessages.getString(
+ PKG,
"PipelineGraph.ViewOutput.OutputDialog.Last.Text");
+ break;
+ case Random:
+ prefix +=
+ BaseMessages.getString(
+ PKG,
"PipelineGraph.ViewOutput.OutputDialog.Random.Text");
+ break;
+ default:
+ break;
}
+ } catch (Exception ex) {
+ LogChannel.UI.logError("Unknown sample type: " +
sampleTypeInGui);
}
}
}
-
- new ShowRowsDialog(
- hopGui.getActiveShell(),
- variables,
- title,
- prefix + message,
- rowBuffer.getRowMeta(),
- rowBuffer.getBuffer())
- .open();
- } catch (Exception ex) {
- new ErrorDialog(
- hopGui.getActiveShell(), CONST_ERROR, "Error showing output
rows dialog", ex);
}
+
+ new ShowRowsDialog(
+ hopGui.getActiveShell(),
+ variables,
+ title,
+ prefix + message,
+ rowBuffer.getRowMeta(),
+ rowBuffer.getBuffer())
+ .open();
+ } catch (Exception ex) {
+ new ErrorDialog(
+ hopGui.getActiveShell(), CONST_ERROR, "Error showing output rows
dialog", ex);
+ } finally {
+ showingOutputRows = false;
}
}
- return true;
}
- return false;
+ return true;
}
/**
@@ -1797,6 +1941,9 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
private void menuDetect(Event event) {
// No SWT menu hangs off the canvas, and in Hop Web the browser's own menu
is unwanted.
event.doit = false;
+ if (showingOutputRows) {
+ return;
+ }
if (!PropsUi.getInstance().useRightClickForContextDialog()) {
return;
}
@@ -2459,6 +2606,9 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
@Override
public void mouseMove(MouseEvent event) {
+ if (showingOutputRows) {
+ return;
+ }
boolean shift = (event.stateMask & SWT.SHIFT) != 0;
boolean doRedraw = false;
@@ -3434,6 +3584,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
dragSelection = false;
iconDragStartScreen = null;
iconDragCommitted = false;
+ outputDataPressed = false;
canvas.setData("mode", "null");
canvas.setData(START_HOP_NODE, null);
startHopTransform = null;