/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.flow.processrendering.view; import java.awt.Cursor; import java.awt.Point; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import javax.swing.JOptionPane; import javax.swing.JViewport; import javax.swing.SwingUtilities; import com.rapidminer.gui.flow.processrendering.draw.ProcessDrawUtils; import com.rapidminer.gui.flow.processrendering.draw.ProcessDrawer; import com.rapidminer.gui.flow.processrendering.model.ProcessRendererModel; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.operator.ExecutionUnit; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorChain; import com.rapidminer.operator.ports.InputPort; import com.rapidminer.operator.ports.OutputPort; import com.rapidminer.operator.ports.Port; import com.rapidminer.operator.ports.PortException; import com.rapidminer.tools.I18N; /** * This class handles mouse events for the {@link ProcessRendererView}. * * @author Simon Fischer, Marco Boeck * @since 6.4.0 * */ public class ProcessRendererMouseHandler { private boolean pressHasSelected; /** if the user has been dragging operators around */ private boolean hasDragged; /** port currently being dragged (within view only!) */ private Port draggedPort; /** a mapping between dragged operators and their original position */ private Map<Operator, Rectangle2D> draggedOperatorsOrigins; /** if connection dragging was canceled */ private boolean connectionDraggingCanceled; private Point mousePositionAtDragStart; private Point mousePositionAtLastEvaluation; private int pressedMouseButton; /** the view instance */ private final ProcessRendererView view; /** the model instance */ private final ProcessRendererModel model; /** the controller instance */ private final ProcessRendererController controller; public ProcessRendererMouseHandler(ProcessRendererView view, ProcessRendererModel model, ProcessRendererController controller) { this.view = view; this.model = model; this.controller = controller; } /** * Call when the mouse has moved in the {@link ProcessRendererView}. * * @param e */ public void mouseMoved(final MouseEvent e) { if (model.getConnectingPortSource() != null) { view.repaint(); } updateHoveringState(e); } /** * Call when the mouse has been dragged in the {@link ProcessRendererView}. * * @param e */ public void mouseDragged(final MouseEvent e) { // Pan viewport if ((e.getModifiers() & InputEvent.BUTTON2_MASK) != 0) { if (view.getParent() instanceof JViewport) { JViewport jv = (JViewport) view.getParent(); Point p = jv.getViewPosition(); int newX = p.x - (e.getX() - mousePositionAtDragStart.x); int newY = p.y - (e.getY() - mousePositionAtDragStart.y); int maxX = view.getWidth() - jv.getWidth(); int maxY = view.getHeight() - jv.getHeight(); if (newX < 0) { newX = 0; } if (newX > maxX) { newX = maxX; } if (newY < 0) { newY = 0; } if (newY > maxY) { newY = maxY; } jv.setViewPosition(new Point(newX, newY)); e.consume(); return; } } // drag ports if (model.getConnectingPortSource() != null) { view.repaint(); // We cannot drag when it is an inner sink: dragging means moving the port. if (model.getConnectingPortSource().getPorts().getOwner().getOperator() == model.getDisplayedChain() && e.isShiftDown()) { cancelConnectionDragging(); connectionDraggingCanceled = true; } e.consume(); } hasDragged = true; if (draggedOperatorsOrigins != null && !draggedOperatorsOrigins.isEmpty()) { ExecutionUnit draggingInSubprocess = draggedOperatorsOrigins.keySet().iterator().next().getExecutionUnit(); Operator hoveringOperator = model.getHoveringOperator(); if (hoveringOperator != null) { if (draggedOperatorsOrigins.size() == 1) { if (ProcessDrawUtils.hasOperatorFreePorts(hoveringOperator)) { int pid = controller.getIndex(draggingInSubprocess); Point processSpace = view.toProcessSpace(e.getPoint(), pid); if (processSpace != null) { model.setHoveringConnectionSource( controller.getPortForConnectorNear(processSpace, draggingInSubprocess)); } } } double difX = e.getX() - mousePositionAtDragStart.getX(); double difY = e.getY() - mousePositionAtDragStart.getY(); difX *= 1 / model.getZoomFactor(); difY *= 1 / model.getZoomFactor(); // hoveringOperator is always included in draggedOperators if (!draggedOperatorsOrigins.containsKey(hoveringOperator)) { draggedOperatorsOrigins.put(hoveringOperator, model.getOperatorRect(hoveringOperator)); } double targetX = draggedOperatorsOrigins.get(hoveringOperator).getX() + difX; double targetY = draggedOperatorsOrigins.get(hoveringOperator).getY() + difY; if (targetX < ProcessDrawer.GRID_X_OFFSET) { targetX = ProcessDrawer.GRID_X_OFFSET; } if (targetY < ProcessDrawer.GRID_Y_OFFSET) { targetY = ProcessDrawer.GRID_Y_OFFSET; } // use only hovering operator for snapping if (model.isSnapToGrid()) { Point snapped = ProcessDrawUtils.snap(new Point2D.Double(targetX, targetY)); targetX = snapped.getX(); targetY = snapped.getY(); } // now, set difX and difY to shift /after/ snapped and clipped difX = targetX - draggedOperatorsOrigins.get(hoveringOperator).getX(); difY = targetY - draggedOperatorsOrigins.get(hoveringOperator).getY(); // bound to subprocess double unitWidth = model.getProcessWidth(draggingInSubprocess); double unitHeight = model.getProcessHeight(draggingInSubprocess); double maxX = 0; double maxY = 0; // shift for (Operator op : draggedOperatorsOrigins.keySet()) { Rectangle2D origin = draggedOperatorsOrigins.get(op); if (origin.getMaxX() + difX >= unitWidth) { if (origin.getMaxX() + difX > maxX) { maxX = origin.getMaxX() + difX; } } if (origin.getMaxY() + difY >= unitHeight) { if (origin.getMaxY() + difY > maxY) { maxY = origin.getMaxY() + difY; } } if (origin.getMinY() + difY < 0) { difY = -origin.getMinY() + ProcessDrawer.GRID_Y_OFFSET; } if (origin.getMinX() + difX < 0) { difX = -origin.getMinX() + ProcessDrawer.GRID_X_OFFSET; } Rectangle2D opPos = new Rectangle2D.Double(Math.floor(origin.getX() + difX), Math.floor(origin.getY() + difY), origin.getWidth(), origin.getHeight()); model.setOperatorRect(op, opPos); } model.fireOperatorsMoved(draggedOperatorsOrigins.keySet()); e.consume(); } } else { // ports are draggeable only if they belong to the displayed chain <-> // they are innersinks of our sources if (isDisplayChainPortDragged() && // furthermore they can only be dragged with left mouse button + shift key // pressed pressedMouseButton == MouseEvent.BUTTON1 && e.isShiftDown()) { double diff = e.getY() - mousePositionAtLastEvaluation.getY(); double shifted = controller.shiftPortSpacing(draggedPort, diff); mousePositionAtLastEvaluation.setLocation(mousePositionAtLastEvaluation.getX(), mousePositionAtLastEvaluation.getY() + shifted); view.repaint(); e.consume(); } else if (model.getSelectionRectangle() != null) { model.setSelectionRectangle(ProcessDrawUtils.createRectangle(mousePositionAtDragStart, e.getPoint())); model.fireMiscChanged(); e.consume(); } else if (model.getConnectingPortSource() != null) { updateHoveringState(e); } } } /** * Call when the mouse has been pressed in the {@link ProcessRendererView}. * * @param e */ public void mousePressed(final MouseEvent e) { pressHasSelected = false; mousePositionAtDragStart = e.getPoint(); mousePositionAtLastEvaluation = e.getPoint(); hasDragged = false; pressedMouseButton = e.getButton(); connectionDraggingCanceled = false; if (SwingUtilities.isLeftMouseButton(e)) { if (model.getHoveringOperator() == null && model.getHoveringPort() == null && model.getSelectedConnectionSource() != model.getHoveringConnectionSource()) { model.setSelectedConnectionSource(model.getHoveringConnectionSource()); model.fireMiscChanged(); e.consume(); } } if (e.getButton() == MouseEvent.BUTTON2) { view.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); return; } // disconnect when clicking with alt + left mouse on connection if (model.getHoveringConnectionSource() != null && model.getHoveringOperator() == null && SwingUtilities.isLeftMouseButton(e) && e.isAltDown()) { OutputPort port = model.getHoveringConnectionSource(); if (port.isConnected()) { port.disconnect(); model.setHoveringConnectionSource(null); if (model.getSelectedConnectionSource() != null && model.getSelectedConnectionSource().equals(port)) { model.setSelectedConnectionSource(null); } model.fireMiscChanged(); } e.consume(); } // If mouse pressed while connecting, check if connecting ports should be canceled Port connectingPortSource = model.getConnectingPortSource(); if (connectingPortSource != null) { // cancel if right mouse button is pressed if (SwingUtilities.isRightMouseButton(e)) { cancelConnectionDragging(); connectionDraggingCanceled = true; e.consume(); return; } // cancel if any button is pressed but not over hovering port if (model.getHoveringPort() == null) { cancelConnectionDragging(); connectionDraggingCanceled = true; e.consume(); return; } e.consume(); } updateHoveringState(e); Port hoveringPort = model.getHoveringPort(); if (SwingUtilities.isLeftMouseButton(e) && hoveringPort != null) { // Left mouse button pressed on port with alt pressed -> remove connection if (e.isAltDown()) { if (hoveringPort instanceof OutputPort) { if (((OutputPort) hoveringPort).isConnected()) { ((OutputPort) hoveringPort).disconnect(); } } else if (hoveringPort instanceof InputPort) { if (((InputPort) hoveringPort).isConnected()) { ((InputPort) hoveringPort).getSource().disconnect(); } } view.repaint(); } else { // Left mouse button pressed on port -> start connecting ports if (hoveringPort instanceof OutputPort) { if (connectingPortSource != null && connectingPortSource instanceof InputPort) { connectConnectingPortSourceWithHoveringPort((InputPort) connectingPortSource, (OutputPort) hoveringPort, hoveringPort); } else { if (!e.isShiftDown()) { model.setConnectingPortSource(hoveringPort); } } } else if (hoveringPort instanceof InputPort) { if (connectingPortSource != null && connectingPortSource instanceof OutputPort) { connectConnectingPortSourceWithHoveringPort((InputPort) hoveringPort, (OutputPort) connectingPortSource, hoveringPort); } else { if (!e.isShiftDown()) { model.setConnectingPortSource(hoveringPort); } } } } e.consume(); } else if (model.getHoveringOperator() == null) { // deselect unless shift is pressed if (!e.isShiftDown() && !(SwingTools.isControlOrMetaDown(e) && e.getButton() == 1)) { if (model.getSelectedOperators().isEmpty() || model.getSelectedOperators().size() >= 1 && !model.getSelectedOperators().get(0).equals(model.getDisplayedChain())) { controller.selectOperator(model.getDisplayedChain(), true); } } } if (hoveringPort != null) { controller.selectOperator(hoveringPort.getPorts().getOwner().getOperator(), true); pressHasSelected = true; e.consume(); } else { if (model.getHoveringOperator() == null) { if (!e.isShiftDown() && !(SwingTools.isControlOrMetaDown(e) && e.getButton() == 1)) { if (model.getSelectedOperators().isEmpty() || model.getSelectedOperators().size() >= 1 && !model.getSelectedOperators().get(0).equals(model.getDisplayedChain())) { controller.selectOperator(model.getDisplayedChain(), true); pressHasSelected = true; } } } } if (model.getHoveringOperator() != null) { // control down and reducing selection from {A,B,C} to {A} is delayed to // mouseReleased if (!(SwingTools.isControlOrMetaDown(e) && SwingUtilities.isLeftMouseButton(e)) && !model.getSelectedOperators().contains(model.getHoveringOperator())) { controller.selectOperator(model.getHoveringOperator(), true, e.isShiftDown()); pressHasSelected = true; } // start dragging draggedOperatorsOrigins = new HashMap<>(); for (Operator op : model.getSelectedOperators()) { if (op.getExecutionUnit() == model.getHoveringOperator().getExecutionUnit()) { draggedOperatorsOrigins.put(op, (Rectangle2D) model.getOperatorRect(op).clone()); } } model.setDraggedOperators(draggedOperatorsOrigins.keySet()); e.consume(); } else if (hoveringPort != null) { draggedPort = hoveringPort; e.consume(); } else if (SwingUtilities.isLeftMouseButton(e)) { // start selection if we actually start selecting in a process if (view.getProcessIndexUnder(e.getPoint()) == -1) { return; } model.setSelectionRectangle(ProcessDrawUtils.createRectangle(mousePositionAtDragStart, e.getPoint())); } // Popup will only be triggered if mouse has been released and no dragging was done // CAUTION: Mac&Linux / Windows do different popup trigger handling. Because of this the // popup trigger has to be checked in mousePressed AND mouseReleased // WINDOWS: mouseReleased // LINUX: mousePressed // DO NOT HANDLE BACKGROUND CLICKS HERE, they are handled in a later RenderPhase Operator clickedOperator = null; if (model.getHoveringPort() != null) { clickedOperator = model.getHoveringPort().getPorts().getOwner().getOperator(); } else if (model.getHoveringOperator() != null) { clickedOperator = model.getHoveringOperator(); } else { clickedOperator = model.getDisplayedChain(); } if (e.isPopupTrigger() && (!model.getDisplayedChain().equals(clickedOperator) || model.getHoveringConnectionSource() != null)) { if (!connectionDraggingCanceled) { if (view.showPopupMenu(e)) { e.consume(); return; } } } } /** * Call when the mouse has been pressed in the {@link ProcessRendererView} and no * {@link RenderPhase} before the {@link RenderPhase#BACKGROUND} has consumed the event. * * @param e */ public void mousePressedBackground(MouseEvent e) { // Popup will only be triggered if mouse has been released and no dragging was done // CAUTION: Mac&Linux / Windows do different popup trigger handling. Because of this the // popup trigger has to be checked in mousePressed AND mouseReleased // WINDOWS: mouseReleased // LINUX: mousePressed // ONLY HANDLE BACKGROUND CLICKS HERE Operator clickedOperator = null; if (model.getHoveringPort() != null) { clickedOperator = model.getHoveringPort().getPorts().getOwner().getOperator(); } else if (model.getHoveringOperator() != null) { clickedOperator = model.getHoveringOperator(); } else { clickedOperator = model.getDisplayedChain(); } if (e.isPopupTrigger() && model.getDisplayedChain().equals(clickedOperator)) { if (!connectionDraggingCanceled) { if (view.showPopupMenu(e)) { e.consume(); return; } } } } /** * Call when the mouse has been released in the {@link ProcessRendererView}. * * @param e */ public void mouseReleased(final MouseEvent e) { if ((e.getModifiers() & InputEvent.BUTTON2_MASK) != 0) { view.setCursor(Cursor.getDefaultCursor()); e.consume(); return; } Port connectingPortSource = model.getConnectingPortSource(); if (connectingPortSource != null) { // cancel if right mouse button is released if (SwingUtilities.isRightMouseButton(e)) { cancelConnectionDragging(); connectionDraggingCanceled = true; } Port hoveringPort = model.getHoveringPort(); // cancel if any button is released but not over hovering port if (hoveringPort == null) { cancelConnectionDragging(); connectionDraggingCanceled = true; } // connect when released over hovering port if (SwingUtilities.isLeftMouseButton(e) && hoveringPort != null && !e.isAltDown()) { if (hoveringPort instanceof InputPort && connectingPortSource instanceof OutputPort) { connectConnectingPortSourceWithHoveringPort((InputPort) hoveringPort, (OutputPort) connectingPortSource, hoveringPort); } else if (hoveringPort instanceof OutputPort && connectingPortSource instanceof InputPort) { connectConnectingPortSourceWithHoveringPort((InputPort) connectingPortSource, (OutputPort) hoveringPort, hoveringPort); } } e.consume(); } try { Rectangle2D selectionRectangle = model.getSelectionRectangle(); if (selectionRectangle != null) { if (selectionRectangle.getWidth() > 3 && selectionRectangle.getHeight() > 3) { int processIndex = view.getProcessIndexUnder(mousePositionAtDragStart); if (processIndex == -1) { processIndex = view.getProcessIndexUnder(e.getPoint()); } if (processIndex == -1) { processIndex = view.getProcessIndexUnder( new Point((int) selectionRectangle.getCenterX(), (int) selectionRectangle.getCenterY())); } Point offset = view.toProcessSpace(new Point(0, 0), processIndex); if (offset != null) { if (!e.isShiftDown() && !SwingTools.isControlOrMetaDown(e) || model.getSelectedOperators().size() == 1 && model.getSelectedOperators().get(0) == model.getDisplayedChain()) { // if // we have only selected the parent, we ignore SHIFT and CTRL model.clearOperatorSelection(); } for (Operator op : model.getProcess(processIndex).getOperators()) { Rectangle2D opRect = model.getOperatorRect(op); double zoomFactor = model.getZoomFactor(); Rectangle2D selRect = new Rectangle2D.Double( model.getSelectionRectangle().getX() * (1 / zoomFactor) + offset.getX(), model.getSelectionRectangle().getY() * (1 / zoomFactor) + offset.getY(), model.getSelectionRectangle().getWidth() * (1 / zoomFactor), model.getSelectionRectangle().getHeight() * (1 / zoomFactor)); if (selRect.contains(opRect)) { controller.selectOperator(op, false); } } } } model.setSelectionRectangle(null); e.consume(); } else { if (hasDragged && draggedOperatorsOrigins != null && draggedOperatorsOrigins.size() == 1) { controller.insertIntoHoveringConnection(model.getHoveringOperator()); e.consume(); } else if (!hasDragged && model.getHoveringOperator() != null && !e.isPopupTrigger() && SwingUtilities.isLeftMouseButton(e) && (SwingTools.isControlOrMetaDown(e) || model.getSelectedOperators().contains(model.getHoveringOperator()) && !pressHasSelected)) { // control and deselection was delayed to mouseReleased controller.selectOperator(model.getHoveringOperator(), !SwingTools.isControlOrMetaDown(e), e.isShiftDown()); e.consume(); } } if (draggedOperatorsOrigins != null || draggedPort != null) { model.getDisplayedChain().getProcess().updateNotify(); } } finally { mousePositionAtDragStart = null; draggedPort = null; draggedOperatorsOrigins = null; hasDragged = false; model.clearDraggedOperators(); } // Popup will only be triggered if mouse has been released and no dragging was done // CAUTION: Mac&Linux / Windows do different popup trigger handling. Because of this the // popup trigger has to be checked in mousePressed AND mouseReleased // WINDOWS: mouseReleased // LINUX: mousePressed // DO NOT HANDLE BACKGROUND CLICKS HERE, they are handled in a later RenderPhase Operator clickedOperator = null; if (model.getHoveringPort() != null) { clickedOperator = model.getHoveringPort().getPorts().getOwner().getOperator(); } else if (model.getHoveringOperator() != null) { clickedOperator = model.getHoveringOperator(); } else { clickedOperator = model.getDisplayedChain(); } if (e.isPopupTrigger() && (!model.getDisplayedChain().equals(clickedOperator) || model.getHoveringConnectionSource() != null)) { if (!connectionDraggingCanceled) { if (view.showPopupMenu(e)) { e.consume(); return; } } } view.repaint(); } /** * Call when the mouse has been released in the {@link ProcessRendererView} and no * {@link RenderPhase} before the {@link RenderPhase#BACKGROUND} has consumed the event. * * @param e */ public void mouseReleasedBackground(final MouseEvent e) { // Popup will only be triggered if mouse has been released and no dragging was done // CAUTION: Mac&Linux / Windows do different popup trigger handling. Because of this the // popup trigger has to be checked in mousePressed AND mouseReleased // WINDOWS: mouseReleased // LINUX: mousePressed // ONLY HANDLE BACKGROUND CLICKS HERE Operator clickedOperator = null; if (model.getHoveringPort() != null) { clickedOperator = model.getHoveringPort().getPorts().getOwner().getOperator(); } else if (model.getHoveringOperator() != null) { clickedOperator = model.getHoveringOperator(); } else { clickedOperator = model.getDisplayedChain(); } if (e.isPopupTrigger() && model.getDisplayedChain().equals(clickedOperator)) { if (!connectionDraggingCanceled) { if (view.showPopupMenu(e)) { e.consume(); return; } } } } /** * Call when the mouse has been clicked in the {@link ProcessRendererView}. * * @param e */ public void mouseClicked(final MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { if (e.getClickCount() == 2) { if (model.getHoveringOperator() != null) { if (model.getHoveringOperator() instanceof OperatorChain) { model.setDisplayedChainAndFire((OperatorChain) model.getHoveringOperator()); } e.consume(); } } } else if (SwingUtilities.isRightMouseButton(e)) { if (model.getConnectingPortSource() != null) { cancelConnectionDragging(); connectionDraggingCanceled = true; e.consume(); } } } /** * Call when the mouse has entered the {@link ProcessRendererView}. * * @param e */ public void mouseEntered(final MouseEvent e) {} /** * Call when the mouse has exited the {@link ProcessRendererView}. * * @param e */ public void mouseExited(final MouseEvent e) { controller.clearStatus(); }; /** * Updates the currently hovered element * * @param e */ private void updateHoveringState(final MouseEvent e) { int hoveringProcessIndex = model.getHoveringProcessIndex(); if (model.getHoveringProcessIndex() != -1) { int relativeX = (int) model.getMousePositionRelativeToProcess().getX(); int relativeY = (int) model.getMousePositionRelativeToProcess().getY(); OutputPort connectionSourceUnderMouse = controller.getPortForConnectorNear( model.getMousePositionRelativeToProcess(), model.getProcess(hoveringProcessIndex)); if (connectionSourceUnderMouse != model.getHoveringConnectionSource()) { model.setHoveringConnectionSource(connectionSourceUnderMouse); model.fireMiscChanged(); e.consume(); } // find inner sinks/sources under mouse if (controller.checkPortUnder(model.getProcess(hoveringProcessIndex).getInnerSinks(), relativeX, relativeY) || controller.checkPortUnder(model.getProcess(hoveringProcessIndex).getInnerSources(), relativeX, relativeY)) { e.consume(); return; } // find operator under mouse List<Operator> operators = model.getProcess(hoveringProcessIndex).getOperators(); ListIterator<Operator> iterator = operators.listIterator(operators.size()); while (iterator.hasPrevious()) { Operator op = iterator.previous(); // first, check whether we are over a port if (controller.checkPortUnder(op.getInputPorts(), relativeX, relativeY) || controller.checkPortUnder(op.getOutputPorts(), relativeX, relativeY)) { e.consume(); return; } // If not, check operator. Rectangle2D rect = model.getOperatorRect(op); if (rect == null) { continue; } if (rect.contains(new Point2D.Double(relativeX, relativeY))) { if (model.getHoveringOperator() != op) { model.setHoveringPort(null); view.setHoveringOperator(op); if (model.getHoveringOperator() instanceof OperatorChain) { controller.showStatus(I18N.getGUILabel("processRenderer.displayChain.hover")); } else { controller.showStatus(I18N.getGUILabel("processRenderer.operator.hover")); } } e.consume(); return; } } } if (model.getHoveringOperator() != null) { view.setHoveringOperator(null); } if (model.getHoveringPort() != null) { model.setHoveringPort(null); view.updateCursor(); model.fireMiscChanged(); } if (model.getHoveringConnectionSource() != null) { controller.showStatus(I18N.getGUILabel("processRenderer.connection.hover")); } else { controller.clearStatus(); } } /** * Connects the clicked port with the connection source port. * * @param input * @param output * @param hoveringPort */ private void connectConnectingPortSourceWithHoveringPort(final InputPort input, final OutputPort output, final Port hoveringPort) { try { Operator destOp = input.getPorts().getOwner().getOperator(); boolean hasConnections = controller.hasConnections(destOp); controller.connect(output, input); // move directly after source if first connection if (!hasConnections) { Operator sourceOp = output.getPorts().getOwner().getOperator(); if (destOp != model.getDisplayedChain() && sourceOp != model.getDisplayedChain()) { destOp.getExecutionUnit().moveToIndex(destOp, destOp.getExecutionUnit().getOperators().indexOf(sourceOp) + 1); } } } catch (PortException e1) { if (e1.hasRepairOptions()) { // calculate popup position Point popupPosition = ProcessDrawUtils.createPortLocation(hoveringPort, model); // correct by zoomFactor double zoomFactor = model.getZoomFactor(); popupPosition = new Point((int) (popupPosition.getX() * zoomFactor), (int) (popupPosition.getY() * zoomFactor)); // take splitted process pane into account and add offset for each process we // have to the left of our current one if (hoveringPort.getPorts() != null) { ExecutionUnit process; if (hoveringPort.getPorts().getOwner().getOperator() == model.getDisplayedChain()) { // this is an inner port process = hoveringPort.getPorts().getOwner().getConnectionContext(); } else { // this is an outer port of a nested operator process = hoveringPort.getPorts().getOwner().getOperator().getExecutionUnit(); } // iterate over all processes and add widths of processes to the left int counter = 0; for (ExecutionUnit unit : model.getProcesses()) { if (unit == process) { // only add process widths until we have the process which contains // the port break; } else { counter++; popupPosition = new Point((int) (popupPosition.x + model.getProcessWidth(unit)), popupPosition.y); } } // add another wall width as offset if we have multiple processes if (counter > 0) { popupPosition = new Point(popupPosition.x + ProcessDrawer.WALL_WIDTH, popupPosition.y); } } if (hoveringPort instanceof InputPort) { popupPosition.setLocation(popupPosition.getX() + 28, popupPosition.getY() - 2); } else { popupPosition.setLocation(popupPosition.getX() - 18, popupPosition.getY() - 2); } e1.showRepairPopup(view, popupPosition); } else { JOptionPane.showMessageDialog(null, e1.getMessage(), "Cannot connect", JOptionPane.ERROR_MESSAGE); } view.repaint(); } finally { cancelConnectionDragging(); connectionDraggingCanceled = true; } } /** * Returns if a display chain port is being dragged. * * @return {@code true} if one is dragged; {@code false} otherwise */ private boolean isDisplayChainPortDragged() { return draggedPort != null && draggedPort.getPorts().getOwner().getOperator() == model.getDisplayedChain(); } /** * Cancels the ongoing connection dragging. */ private void cancelConnectionDragging() { model.setConnectingPortSource(null); model.fireMiscChanged(); } }