All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openbp.cockpit.modeler.tools.ModelerToolSupport Maven / Gradle / Ivy

The newest version!
/*
 *   Licensed 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.openbp.cockpit.modeler.tools;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.openbp.cockpit.modeler.ModelerGraphics;
import org.openbp.cockpit.modeler.drawing.DrawingEditorPlugin;
import org.openbp.cockpit.modeler.drawing.ProcessDrawing;
import org.openbp.cockpit.modeler.drawing.WorkspaceDrawingView;
import org.openbp.cockpit.modeler.figures.VisualElement;
import org.openbp.cockpit.modeler.figures.VisualElementEvent;
import org.openbp.cockpit.modeler.figures.process.ProcessElementContainer;
import org.openbp.cockpit.modeler.util.FigureUtil;
import org.openbp.cockpit.modeler.util.InputState;
import org.openbp.jaspira.event.JaspiraEventMgr;
import org.openbp.jaspira.gui.interaction.BasicTransferable;
import org.openbp.jaspira.plugins.statusbar.StatusBarTextEvent;

import CH.ifa.draw.framework.Figure;
import CH.ifa.draw.framework.Handle;
import CH.ifa.draw.standard.AbstractTool;

/**
 * The node manipulation tool is the standard tool used by the modeler.
 * It handles regular object selection as well as rubber band tracking, double click on objects
 * and figure popup menu invocations.
 * It also supports moving the workspace view while the ALT key is pressed (track).
 *
 * @author Stephan Pauxberger
 */
public class ModelerToolSupport extends AbstractTool
{
	/**
	 * Editor that owns the tool.
	 * (This equals the return value of editor(), however we cache it to avoid casts.)
	 */
	private DrawingEditorPlugin editor;

	/**
	 * View we refer to.
	 * (This equals the return value of view(), however we cache it to avoid casts.)
	 */
	private WorkspaceDrawingView view;

	/** Current tool */
	private ModelerTool currentTool;

	/** In place editor tool for process elements */
	private ProcessElementInPlaceEditorTool inPlaceEditorTool;

	/** Last postion of the cursor (view coordinates). Also used by the break out box mechanism */
	private Point lastPoint;

	/** Last figure that has been hovered with the mouse */
	private VisualElement lastFigure;

	/** Last handle that has been hovered with the mouse */
	private Handle lastHandle;

	/** Delay timer for the in place editor (to catch the double click) */
	private Timer inPlaceEditorDelayTimer;

	/** Default track rectangle size */
	private static final int TRACK_SIZE = 75;

	/** Table holding all tool references and tool use cases */
	private List tooDecisionTable;

	/**
	 * Constructs the tool for the given editor.
	 *
	 * @param editor Editor that owns the tool
	 */
	public ModelerToolSupport(DrawingEditorPlugin editor)
	{
		super(editor);
		this.editor = editor;
		view = (WorkspaceDrawingView) editor.view();

		lastPoint = new Point();

		tooDecisionTable = new ArrayList();
		inPlaceEditorTool = new ProcessElementInPlaceEditorTool(this);
	}

	/**
	 * Adds an entry to the tool decision table.
	 *
	 * @param tool Tool
	 * @param figureClass Class of the figure this tool is suitable for or null for any
	 * @param requiredInputState Expected keyboard and mouse button input state or 0 for any
	 */
	public void addToolDecisionTableEntry(ModelerTool tool, Class figureClass, int requiredInputState)
	{
		tooDecisionTable.add(new ModelerToolDecisionTableEntry(tool, figureClass, requiredInputState));
	}

	public void deactivate()
	{
		super.deactivate();
		if (currentTool != null)
		{
			// Pass to subordinate tool
			currentTool.deactivate();
			currentTool = null;
		}
	}

	/**
	 * Activates the given tool.
	 *
	 * @param tool Tool to activate
	 * @param objectUnderCursor Object that will be affected by the tool
	 */
	public void activateTool(ModelerTool tool, Object objectUnderCursor)
	{
		currentTool = tool;
		currentTool.setAffectedObject(objectUnderCursor);
		currentTool.activate();
	}

	/**
	 * Clears the current tool.
	 */
	protected void clearTool()
	{
		currentTool = null;
		updateToolState();
	}

	/**
	 * Returns the last known mouse coordinates.
	 * Used for break out box display.
	 */
	public Point getLastPoint()
	{
		return new Point(lastPoint);
	}

	/**
	 * Gets the last figure that has been hovered with the mouse.
	 */
	public VisualElement getLastFigure()
	{
		return lastFigure;
	}

	/**
	 * Gets the editor that owns the tool.
	 */
	public DrawingEditorPlugin getEditor()
	{
		return editor;
	}

	/**
	 * Gets the view we refer to.
	 */
	public WorkspaceDrawingView getView()
	{
		return view;
	}

	//////////////////////////////////////////////////
	// @@ Mouse event handlers
	//////////////////////////////////////////////////

	public void mouseMove(MouseEvent e, int x, int y)
	{
		// Convert to document coordinate according to view scale factor
		x = view.applyScale(x, true);
		y = view.applyScale(y, true);

		if (currentTool != null)
		{
			// Forward to current tool
			currentTool.mouseMove(e, x, y);

			lastPoint.x = e.getX();
			lastPoint.y = e.getY();
			return;
		}

		Object objectUnderCursor = null;

		lastHandle = view.findHandle(x, y);
		if (lastHandle != null)
		{
			objectUnderCursor = lastHandle;
		}
		else
		{
			// We look for the figure under the cursor
			VisualElement figure = (VisualElement) FigureUtil.findInnermostFigure(((ProcessDrawing) drawing()), x, y, VisualElement.class);
			if (figure != null)
			{
				objectUnderCursor = figure;
			}

			if (figure != lastFigure)
			{
				// Mouse moved to another figure, update figure hover effect if not entering a child element
				if (lastFigure != null && (figure == null || figure.getParentElement() != lastFigure))
				{
					// Tell the figure it is not hovered by the cursor any more
					lastFigure.invalidate();
					lastFigure.handleEvent(new VisualElementEvent(VisualElementEvent.CURSOR_LEFT, editor, e, x, y));
				}

				if (figure != null)
				{
					// Tell the figure it is hovered by the cursor
					figure.handleEvent(new VisualElementEvent(VisualElementEvent.CURSOR_ENTERED, editor, e, x, y));
				}

				lastFigure = figure;
				view.setFigureUnderCursor(figure);

				if (figure != null)
				{
					figure.invalidate();
				}
				view.repairDamage();
			}
		}

		String hintMsg = null;
		Cursor cursor = ModelerGraphics.standardCursor;

		ModelerToolDecisionTableEntry entry = determineTool(objectUnderCursor);
		if (entry != null)
		{
			ModelerTool tool = entry.getTool();
			hintMsg = tool.getToolHintMsg();
			cursor = tool.getToolCursor();
			if ((entry.getRequiredInputState() & InputState.HOVER) != 0)
			{
				activateTool(tool, objectUnderCursor);
				currentTool.mouseMove(e, x, y);
			}
		}

		// Display or remove the hint message and set the view cursor
		editor.fireEvent(new StatusBarTextEvent(editor, hintMsg));
		view.setCursor(cursor);

		lastPoint.x = e.getX();
		lastPoint.y = e.getY();
	}

	public void mouseDown(MouseEvent e, int x, int y)
	{
		// Convert to document coordinate according to view scale factor
		x = view.applyScale(x, true);
		y = view.applyScale(y, true);

		// Update input state
		InputState.updateStateOnMouseDownEvent(e);

		if (currentTool != null)
		{
			// Forward to current tool
			currentTool.mouseDown(e, x, y);
			return;
		}

		// Left mouse down activates the current tool

		if (InputState.isLeftButtonDown())
		{
			Object objectUnderCursor = lastHandle;
			if (objectUnderCursor == null)
				objectUnderCursor = lastFigure;
			ModelerToolDecisionTableEntry entry = determineTool(objectUnderCursor);
			if (entry != null)
			{
				if ((entry.getRequiredInputState() & InputState.HOVER) == 0)
				{
					ModelerTool tool = entry.getTool();
					activateTool(tool, objectUnderCursor);
					currentTool.mouseDown(e, x, y);
				}
			}
		}
	}

	public void mouseUp(MouseEvent e, int x, int y)
	{
		// Convert to document coordinate according to view scale factor
		x = view.applyScale(x, true);
		y = view.applyScale(y, true);

		if (currentTool != null)
		{
			// Reset the child tool
			currentTool.mouseUp(e, x, y);
			if (e.getClickCount() == 1)
			{
				return;
			}
		}

		boolean rightButton = InputState.isRightButtonDown();
		if (rightButton)
		{
			Figure f = null;

			ProcessElementContainer pec = (ProcessElementContainer) FigureUtil.findInnermostFigure(((ProcessDrawing) drawing()), x, y, ProcessElementContainer.class);
			if (pec != null && pec != drawing())
			{
				f = pec;
			}
			else
			{
				f = FigureUtil.findInnermostFigure(((ProcessDrawing) drawing()), x, y, VisualElement.class);
			}

			if (f != null && f != drawing())
			{
				view.displayPopupMenu(f, e);

				lastFigure = (ProcessDrawing) drawing();
			}
		}
		else if (e.getClickCount() == 2)
		{
			// Double click caught, cancel pending in place editor timer
			if (inPlaceEditorDelayTimer != null)
			{
				inPlaceEditorDelayTimer.cancel();
				inPlaceEditorDelayTimer = null;
			}

			Figure figure = drawing().findFigure(x, y);

			if (figure instanceof VisualElement)
			{
				// Let the figure itself handle the doubleclick
				if (((VisualElement) figure).handleEvent(new VisualElementEvent(VisualElementEvent.DOUBLE_CLICK, editor, e, x, y)))
				{
					// Handled by the element itself
					return;
				}
			}

			if (figure instanceof ProcessElementContainer)
			{
				// Let's see if the process element behind the figure can handle this...
				ProcessElementContainer pec = (ProcessElementContainer) FigureUtil.findInnermostFigure(figure, x, y, ProcessElementContainer.class);

				// Double click means opening the item, pass to association plugin
				JaspiraEventMgr.fireGlobalEvent("plugin.association.open", new BasicTransferable(pec.getReferredProcessElement()));
			}
		}

		// Update input state
		InputState.updateStateOnMouseUpEvent(e);

		updateToolState();
	}

	public void mouseDrag(MouseEvent e, int x, int y)
	{
		lastPoint.x = x;
		lastPoint.y = y;

		// Convert to document coordinate according to view scale factor
		x = view.applyScale(x, true);
		y = view.applyScale(y, true);

		if (!InputState.isRightButtonDown())
		{
			if (currentTool instanceof RubberBandTool)
			{
				// Make the old rubber band disappear
				((RubberBandTool) currentTool).eraseRubberBand();
			}

			view.scrollRectToVisible(new Rectangle(x, y, TRACK_SIZE, TRACK_SIZE));

			if (currentTool != null)
			{
				// Pass to subordinate tool
				currentTool.mouseDrag(e, x, y);
			}
		}
	}

	public void keyDown(KeyEvent e, int key)
	{
		updateToolState(e);

		if (currentTool != null)
		{
			// Reset the child tool
			currentTool.keyDown(e, key);
			return;
		}
	}

	public void keyUp(KeyEvent e, int key)
	{
		if (currentTool != null)
		{
			// Reset the child tool
			currentTool.keyUp(e, key);
		}

		else if (key == KeyEvent.VK_ENTER && InputState.isAltDown())
		{
			Figure selectedFigure = null;
			Iterator it = getView().selection().iterator();
			if (it.hasNext())
			{
				selectedFigure = (Figure) it.next();
			}
			if (selectedFigure instanceof ProcessElementContainer && ! (selectedFigure instanceof ProcessDrawing))
			{
				// we directly set the tool inside the editor
				toggleInPlaceEditor((ProcessElementContainer) selectedFigure, false);
				currentTool.keyDown(e, key);
				return;
			}
		}

		updateToolState(e);
	}

	/**
	 * Shows the in place editor for the given figure.
	 *
	 * @param figure Process element container figure to edit
	 */
	public void displayInPlaceEditor(ProcessElementContainer figure)
	{
		activateTool(inPlaceEditorTool, figure);
	}

	/**
	 * Shows or hides the in place editor for the given figure.
	 *
	 * @param figure Process element container figure to edit
	 */
	public void toggleInPlaceEditor(final ProcessElementContainer figure, boolean delay)
	{
		if (currentTool == inPlaceEditorTool && inPlaceEditorTool.getAffectedFigure() == figure)
		{
			clearTool();
		}
		else
		{
			if (delay)
			{
				inPlaceEditorDelayTimer = new Timer();
				inPlaceEditorDelayTimer.schedule(new TimerTask()
				{
					public void run()
					{
						activateTool(inPlaceEditorTool, figure);
						inPlaceEditorDelayTimer.cancel();
						inPlaceEditorDelayTimer = null;
					}
				}, 400);
			}
			else
			{
				activateTool(inPlaceEditorTool, figure);
			}
		}
	}

	//////////////////////////////////////////////////
	// @@ Helpers
	//////////////////////////////////////////////////

	public void updateToolState()
	{
		updateToolState(null);
	}

	/**
	 * Updates the cursor in case of a key press.
	 * @param e Key event
	 */
	protected void updateToolState(KeyEvent e)
	{
		if (e != null)
		{
			InputState.updateStateOnInputEvent(e);
		}
		else
		{
			InputState.setState(0);
		}

		// The state of the keyboard modifier keys has changed
		// Simulate a mouse movement to the current position
		int x = lastPoint.x;
		int y = lastPoint.y;

		Component source;
		long when;
		int modifiers;
		if (e != null)
		{
			source = (Component) e.getSource();
			when = e.getWhen();
			modifiers = e.getModifiers();
		}
		else
		{
			source = view;
			when = 0L;
			modifiers = 0;
		}
		MouseEvent me = new MouseEvent(source, 0, when, modifiers, x, y, 0, false);

		if (InputState.isLeftButtonDown() || InputState.isMiddleButtonDown() || InputState.isRightButtonDown())
		{
			mouseDrag(me, x, y);
		}
		else
		{
			mouseMove(me, x, y);
		}
	}

	/**
	 * Gets the tool that would be applied to the given object according to the current settings and input states.
	 *
	 * @param object Object under cursor
	 * @return The tool entry or null
	 */
	protected ModelerToolDecisionTableEntry determineTool(Object object)
	{
		int currentState = InputState.getState() & (InputState.ALT | InputState.CTRL);

		for (ModelerToolDecisionTableEntry entry : tooDecisionTable)
		{
			int requiredInputState = entry.getRequiredInputState() & (InputState.ALT | InputState.CTRL);
			if (currentState != requiredInputState)
				continue;

			Class objectClass = entry.getObjectClass();

			boolean hasObject = object != null;
			boolean wantsObject = objectClass != null;
			if (hasObject != wantsObject)
				continue;

			if (objectClass != null && ! objectClass.isInstance(object))
				continue;

			ModelerTool tool = entry.getTool();
			if (tool.appliesTo(object))
			{
				return entry;
			}
		}
		return null;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy