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

org.openbp.cockpit.modeler.drawing.WorkspaceDrawingView 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.drawing;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.PrintGraphics;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;

import org.openbp.cockpit.itemeditor.NodeItemEditorPlugin;
import org.openbp.cockpit.modeler.ModelerColors;
import org.openbp.cockpit.modeler.drawing.shadowlayout.ParallelProjectionShadowLayouter;
import org.openbp.cockpit.modeler.drawing.shadowlayout.ShadowLayouter;
import org.openbp.cockpit.modeler.figures.VisualElement;
import org.openbp.cockpit.modeler.figures.VisualElementEvent;
import org.openbp.cockpit.modeler.figures.generic.ChildFigure;
import org.openbp.cockpit.modeler.figures.generic.Colorizable;
import org.openbp.cockpit.modeler.figures.process.LineFigure;
import org.openbp.cockpit.modeler.figures.process.ProcessElementContainer;
import org.openbp.cockpit.modeler.skins.Skin;
import org.openbp.cockpit.modeler.tools.ModelerToolSupport;
import org.openbp.cockpit.modeler.util.FigureUtil;
import org.openbp.cockpit.modeler.util.InputState;
import org.openbp.cockpit.modeler.util.ShadowEnumerator;
import org.openbp.common.CommonUtil;
import org.openbp.common.string.TextUtil;
import org.openbp.core.model.ModelObject;
import org.openbp.core.model.item.Item;
import org.openbp.core.model.item.process.MultiSocketNode;
import org.openbp.core.model.item.process.SubprocessNode;
import org.openbp.guiclient.model.ModelConnector;
import org.openbp.jaspira.action.JaspiraActionEvent;
import org.openbp.jaspira.action.JaspiraPopupMenu;
import org.openbp.jaspira.event.InteractionEvent;
import org.openbp.jaspira.event.JaspiraEvent;
import org.openbp.jaspira.gui.interaction.BasicTransferable;
import org.openbp.jaspira.gui.interaction.BreakoutEvent;
import org.openbp.jaspira.gui.interaction.BreakoutProvider;
import org.openbp.jaspira.gui.interaction.DragDropPane;
import org.openbp.jaspira.gui.interaction.MultiTransferable;
import org.openbp.jaspira.option.Option;
import org.openbp.jaspira.option.OptionMgr;
import org.openbp.jaspira.plugin.Plugin;
import org.openbp.jaspira.plugins.propertybrowser.PropertyBrowserSetEvent;
import org.openbp.swing.Scalable;
import org.openbp.swing.SwingUtil;

import CH.ifa.draw.framework.Drawing;
import CH.ifa.draw.framework.DrawingChangeEvent;
import CH.ifa.draw.framework.DrawingChangeListener;
import CH.ifa.draw.framework.Figure;
import CH.ifa.draw.framework.FigureEnumeration;
import CH.ifa.draw.framework.Handle;
import CH.ifa.draw.standard.CompositeFigure;
import CH.ifa.draw.standard.SimpleUpdateStrategy;
import CH.ifa.draw.standard.StandardDrawingView;

/**
 * This is the standard drawing view of the modeler.
 *
 * @author Jens Ferchland
 */
public class WorkspaceDrawingView extends StandardDrawingView
	implements DrawingChangeListener, Scalable
{
	/** Grid type: Point*/
	public static final int GRIDTYPE_POINT = 1;

	/** Grid type: Line */
	public static final int GRIDTYPE_LINE = 2;

	/** Grid type: Hexadecimal grid */
	public static final int GRIDTYPE_HEX = 3;

	/** Minimum width of this view */
	private static final int MINWIDTH = 640;

	/** Minimum height of this view */
	private static final int MINHEIGHT = 480;

	/** Offset to be added to the size of the drawing to enalbe convenient drag and drop to the right and below the drawing */
	private static final int SIZE_OFFSET = 300;

	/** Minimum scaling factor */
	private static final double MIN_SCALE_FACTOR = 0.25;

	/** Maximum scaling factor */
	private static final double MAX_SCALE_FACTOR = 2.0;

	//////////////////////////////////////////////////
	// @@ Drawing properties
	//////////////////////////////////////////////////

	/** Scaling factor */
	private double scaleFactor = 1.0d;

	/** Size offset for workspace view enlargement */
	private int sizeOffset = SIZE_OFFSET;

	// TODO Refactor 6: The following option should be kept as ModelerOption members

	/** Grid visibility */
	private boolean gridDisplayed;

	/** Grid layout type */
	private int gridType = GRIDTYPE_LINE;

	/** Grid spacing */
	private int gridSpacing = 100;

	/** Shadow layouter */
	private ShadowLayouter shadowLayouter = new ParallelProjectionShadowLayouter(5, 5);

	/** Decorator for the current selection */
	private SelectionDecorator selectionDecorator;

	//////////////////////////////////////////////////
	// @@ Status variables
	//////////////////////////////////////////////////

	/** List of currently selected objects (contains {@link Figure} objects) */
	private List selection;

	/** Cache of currently active selection handles (contains Handle objects) */
	private Vector activeHandles;

	/** Figure currently under the cursor or null */
	private Figure figureUnderCursor;

	/** Indicates that the mouse moves within the view */
	protected boolean mouseInView;

	/** Current cursor */
	private Cursor cursor;

	/**
	 * Constructor.
	 *
	 * @param editor Editor that uses the view
	 */
	public WorkspaceDrawingView(final DrawingEditorPlugin editor)
	{
		super(editor, MINWIDTH, MINHEIGHT);

		// This will prevent some default FlowLayoutManager from moving ad-hoc controls
		// such as in-place editors around the workspace. 
		setLayout(null);

		// Set the background color
		Color workspaceColor = ModelerColors.WORKSPACE;
		Option o = OptionMgr.getInstance().getOption("editor.color.workspace");
		if (o != null)
		{
			workspaceColor = (Color) o.getValue();
		}
		setBackground(workspaceColor);

		// setDisplayUpdate (new BufferedUpdateStrategy ());
		setDisplayUpdate(new SimpleUpdateStrategy());

		ToolTipManager.sharedInstance().registerComponent(this);

		// Decorator handles its own adding to the decomgr.
		selectionDecorator = new SelectionDecorator(this, editor);
		selection = new LinkedList();

		// Mouse and mouse wheel handling
		addMouseListener(new MouseAdapter()
		{
			public void mouseEntered(final MouseEvent e)
			{
				mouseInView = true;
			}

			public void mouseExited(final MouseEvent e)
			{
				mouseInView = false;
			}
		});
		addMouseWheelListener(new MouseWheelListener()
		{
			public void mouseWheelMoved(MouseWheelEvent e)
			{
				int notches = e.getWheelRotation();

				if (InputState.isCtrlDown())
				{
					invalidate();

					// Scale according to mouse movement using heuristic values
					double scaleFactor = getScaleFactor();
					scaleFactor *= (100d + (notches * 2)) / 100;
					setScaleFactor(scaleFactor);

					redraw();
				}
				else
				{
					int mult = e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL ? 25 : 100;
					((Trackable) editor()).moveTrackerBy(0, notches * mult);
				}
			}
		});

		// Add support for selection and workspace movement by cursor keys
		addCursorKeySupport();

		// Breakout menu support for standard and user toolbox breakout
		addBreakoutSupport("breakout", KeyEvent.VK_SPACE);
		addBreakoutSupport("userbreakout", KeyEvent.VK_1);

		// Load user options
		gridType = OptionMgr.getInstance().getIntegerOption("editor.grid.type", gridType);
		gridDisplayed = OptionMgr.getInstance().getBooleanOption("editor.grid.display", gridDisplayed);
		o = OptionMgr.getInstance().getOption("editor.shadow");
		if (o != null)
		{
			shadowLayouter = (ShadowLayouter) o.getValue();
		}
	}

	/**
	 * Performs necessary cleanup-work when the view is not needed anymore - should allow
	 * the view to be processed by the garbage-collection.
	 */
	public void unregister()
	{
		// Remove from tooltip manager
		ToolTipManager.sharedInstance().unregisterComponent(this);

		// Remove our selectiondecorator
		selectionDecorator.uninstall();

		// Disconnect from the drawing
		if (drawing() != null)
			drawing().removeDrawingChangeListener(this);

		// Clear references for better garbage collection in case of memory leaks
		setEditor(null);
		selectionDecorator = null;
		activeHandles = null;
		selection = null;
	}

	public void setDrawing(final Drawing drawing)
	{
		if (drawing() != null)
		{
			// If the drawing changes, disconnect from the old drawing
			drawing().removeDrawingChangeListener(this);
		}

		super.setDrawing(drawing);

		if (drawing() != null)
		{
			// We need to be updated when the drawing changes
			drawing.addDrawingChangeListener(this);
		}
	}

	//////////////////////////////////////////////////
	// @@ JComponent overrides
	//////////////////////////////////////////////////

	/**
	 * Links the preferred size of the component to the size of the drawing.
	 * @return The size of the drawing + sizeOffset per axis
	 */
	public Dimension getPreferredSize()
	{
		if (drawing() == null)
			return new Dimension();

		// we make the space larger so the view grows automatically
		Rectangle r = ((Figure) drawing()).displayBox();
		r = applyScale(r, false);

		int offset = applyScale(sizeOffset, false);
		Dimension d = new Dimension(r.width + offset, r.height + offset);
		return d;
	}

	public Dimension getMinimumSize()
	{
		return getPreferredSize();
	}

	public void setPreferredSize(final Dimension d)
	{
		if (d.width < MINWIDTH)
		{
			d.width = MINWIDTH;
		}

		if (d.height < MINHEIGHT)
		{
			d.height = MINHEIGHT;
		}

		super.setPreferredSize(d);
	}

	/**
	 * Sets the current cursor.
	 */
	public void setCursor(final Cursor cursor)
	{
		if (this.cursor != cursor)
		{
			this.cursor = cursor;
			super.setCursor(cursor);
		}
	}

	/**
	 * Returns the tool tip for the given mouse event (i\.e\. the given position).
	 * This is derived from an underlying process element.
	 * @param e Current mouse event specifying the position of the cursor
	 * @return The text or null if there is no process element at this position
	 */
	public String getToolTipText(final MouseEvent e)
	{
		if (e != null)
		{
			Point p = e.getPoint();
			p = applyScale(p, true);

			ProcessElementContainer pec = (ProcessElementContainer) FigureUtil.findInnermostFigure(((ProcessDrawing) drawing()), p.x, p.y, ProcessElementContainer.class);

			if (pec != null)
			{
				// No tool tip for the drawing itself
				if (pec != drawing())
					// We can use the getInfoText Method of ModelObject
					return TextUtil.convertToHTML(pec.getReferredProcessElement().getInfoText(), true, 1, 50);
			}
		}

		return null;
	}

	//////////////////////////////////////////////////
	// @@ Painting
	//////////////////////////////////////////////////

	/**
	 * Redraws the view.
	 */
	public void redraw()
	{
		if (drawing() != null)
		{
			repaint();
		}
	}

	/**
	 * Draws the background. If a background pattern is set it
	 * is used to fill the background. Otherwise the background
	 * is filled in the background color.
	 * @param g Graphics object to draw to
	 */
	public void drawBackground(final Graphics g)
	{
		// Don't call super.drawBackground, we need to take the scaling into account, so we do background painting ourself

		// Paint the background
		g.setColor(getBackground());
		g.fillRect(0, 0, getBounds().width, getBounds().height);

		boolean isPrinting = g instanceof PrintGraphics;
		if (! isPrinting)
		{
			// Paint the grid
			g.setColor(ModelerColors.GRID);

			int height = getHeight();
			int width = getWidth();
			int max = Math.max(height, width);

			if (gridDisplayed)
			{
				// Scale the grid spacing
				int spacing = applyScale(gridSpacing, false);

				if (gridType == GRIDTYPE_POINT)
				{
					for (int x = spacing; x < max; x += spacing)
					{
						for (int y = spacing; y < max; y += spacing)
						{
							g.fillArc(x - 1, y - 1, ((x % (5 * spacing) == 0) && (y % (5 * spacing) == 0)) ? 4 : 2, ((x % (5 * spacing) == 0) && (y
								% (5 * spacing) == 0)) ? 4 : 2, 0, 360);
						}
					}
				}
				else if (gridType == GRIDTYPE_LINE)
				{
					for (int i = spacing; i < max; i += spacing)
					{
						g.drawLine(i, 0, i, height);
						g.drawLine(0, i, width, i);
					}
				}
				else if (gridType == GRIDTYPE_HEX)
				{
					Graphics2D g2 = (Graphics2D) g;

					int even = spacing / 2;

					for (double y = 0d; y < max + spacing; y += ((spacing * 5d) / 6d))
					{
						for (int x = even; x < max + spacing; x += spacing)
						{
							g2.drawLine(x, (int) y, x, (int) y + (spacing / 2));
							g2.drawLine(x, (int) y + (spacing / 2), x - (spacing / 2), (int) (y + (spacing * 5d) / 6d));
							g2.drawLine(x, (int) y + (spacing / 2), x + (spacing / 2), (int) (y + (spacing * 5d) / 6d));
							g2.fillArc(x - 1, (int) y - 1, 2, 2, 0, 360);
						}

						even = Math.abs(even - (spacing / 2));
					}
				}
			}
		}
	}

	/**
	 * Paints the Shadow of the process view.
	 *
	 * @param g the graphics object where the shadow has to paint
	 */
	public void drawShadow(final Graphics g)
	{
		Skin skin = ((ProcessDrawing) drawing()).getProcessSkin();

		if (shadowLayouter != null && ! skin.isDisableShadows())
		{
			shadowLayouter.drawShadows(ShadowEnumerator.enumerate((CompositeFigure) drawing()), g);
		}
	}

	/**
	 * Overrides StandardDrawingView for adding scale effect.
	 * @param g Graphics object to draw to
	 */
	public void drawAll(final Graphics g)
	{
		if (! (g instanceof Graphics2D))
			return;
		boolean isPrinting = g instanceof PrintGraphics;

		drawBackground(g);

		Graphics2D g2 = (Graphics2D) g;

		AffineTransform oldTransform = g2.getTransform();

		// Prepare the graphics object only if not already prepared
		if ((oldTransform.getType() & AffineTransform.TYPE_MASK_SCALE) == 0)
		{
			prepareGraphics(g2);
		}

		drawShadow(g);
		drawDrawing(g);
		if (! isPrinting)
		{
			drawHandles(g);
		}

		g2.setTransform(oldTransform);
	}

	/**
	 * Override for adding scale effect.
	 */
	public void draw(final Graphics g, final FigureEnumeration fe)
	{
		if (! (g instanceof Graphics2D))
			return;

		Graphics2D g2 = (Graphics2D) g;

		AffineTransform oldTransform = g2.getTransform();

		// Prepare the graphics object only if not already prepared
		if ((oldTransform.getType() & AffineTransform.TYPE_MASK_SCALE) == 0)
		{
			prepareGraphics(g2);
		}

		super.draw(g, fe);

		g2.setTransform(oldTransform);
	}

	/**
	 * Prepares a graphics context for rendering.
	 * Sets scaling and rendering hints.
	 *
	 * @param g2 Graphics to preprare
	 */
	public void prepareGraphics(final Graphics2D g2)
	{
		g2.scale(scaleFactor, scaleFactor);

		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		//g2.setRenderingHint (RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		//g2.setRenderingHint (RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
	}

	/**
	 * Draws the currently active handles.
	 * @param g Graphics object to draw to
	 */
	public void drawHandles(final Graphics g)
	{
		Enumeration k = selectionHandles();

		while (k.hasMoreElements())
		{
			((Handle) k.nextElement()).draw(g);
		}
	}

	/**
	 * Scrolls a figure container into view.
	 * @param figure Figure to show
	 * @param addEnlargement
	 * true: Adds an offset of 50 px to the display box of the element.
* false: Does not add an enlargement offset */ public void scrollIntoView(final Figure figure, final boolean addEnlargement) { if (figure == null) return; // Get the bounding rectangle Rectangle rect = new Rectangle(figure.displayBox()); // Enlarge by 100 pixel to prevent it from hanging in the corners if (addEnlargement) { rect.grow(50, 50); } scrollRectToVisible(rect); } public void scrollRectToVisible(Rectangle rect) { // Make sure that the rectangle lies within the preferred size of the drawing view Rectangle db = ((Figure) drawing()).displayBox(); db.width += sizeOffset; db.height += sizeOffset; rect = rect.intersection(db); if (rect.isEmpty()) return; rect = applyScale(rect, false); super.scrollRectToVisible(rect); } ////////////////////////////////////////////////// // @@ Scaling ////////////////////////////////////////////////// public void drawingRequestUpdate(final DrawingChangeEvent e) { revalidate(); } /** * Adds a rectangle to the damage section after transforming it using the shadow layouter. * * @param e The DrawingChangeEvent that describes the invalidation */ public void drawingInvalidated(final DrawingChangeEvent e) { Rectangle r = e.getInvalidatedRectangle(); if (shadowLayouter != null) { r.add(shadowLayouter.transformRectangle(r)); } r = applyScale(r, false); super.drawingInvalidated(new DrawingChangeEvent(e.getDrawing(), r)); revalidate(); } /** * Applies the current scale factor of the view to a rectangle. * * @param r rectangle to be scaled * @param scaleToDoc * true: Assumes that the coordinates are component coordinates that should be translated to document coordinates.
* false: Assumes that the coordinates are document coordinates that should be translated to component coordinates. * @return The scaled rectangle */ public Rectangle applyScale(final Rectangle r, final boolean scaleToDoc) { Rectangle result = new Rectangle(); result.x = applyScale(r.x, scaleToDoc); result.y = applyScale(r.y, scaleToDoc); result.width = applyScale(r.width, scaleToDoc); result.height = applyScale(r.height, scaleToDoc); return result; } /** * Applies the current scale factor of the view to a point. * * @param p Point to be scaled * @param scaleToDoc * true: Assumes that the coordinates are component coordinates that should be translated to document coordinates.
* false: Assumes that the coordinates are document coordinates that should be translated to component coordinates. * @return The scaled point */ public Point applyScale(final Point p, final boolean scaleToDoc) { Point result = new Point(); result.x = applyScale(p.x, scaleToDoc); result.y = applyScale(p.y, scaleToDoc); return result; } /** * Applies the current scale factor of the view to a coordinate. * * @param coordinate X or Y coordinate to be scaled * @param scaleToDoc * true: Assumes that the coordinates are component coordinates that should be translated to document coordinates.
* false: Assumes that the coordinates are document coordinates that should be translated to component coordinates. * @return The resulting coordinate */ public int applyScale(int coordinate, final boolean scaleToDoc) { if (scaleToDoc) { // Scale component coordinates to document coordinates coordinate = CommonUtil.rnd(coordinate / scaleFactor); } else { // Scale document coordinates to component coordinates coordinate = CommonUtil.rnd(coordinate * scaleFactor); } return coordinate; } /** * Adjusts the scaling factor and view position according to the given rectangle. * * @param r Rectangle to scroll into view */ public void setVisibleRect(final Rectangle r) { // Make sure that the rectangle lies within the preferred size of the drawing view JScrollPane pane = SwingUtil.getScrollPaneAncestor(this); Rectangle vr = pane.getViewport().getBounds(); double xFactor = (double) vr.width / r.width; double yFactor = (double) vr.height / r.height; double factor = Math.min(xFactor, yFactor); setScaleFactor(factor); redraw(); revalidate(); scrollRectToVisible(r); } ////////////////////////////////////////////////// // @@ Scalable implementation ////////////////////////////////////////////////// /** * Gets the scaling factor. */ public double getScaleFactor() { return scaleFactor; } /** * Sets the scaling factor. */ public void setScaleFactor(double scaleFactor) { // Constrain the scaling if (scaleFactor > MAX_SCALE_FACTOR) scaleFactor = MAX_SCALE_FACTOR; else if (scaleFactor < MIN_SCALE_FACTOR) scaleFactor = MIN_SCALE_FACTOR; this.scaleFactor = scaleFactor; ((DrawingEditorPlugin) editor()).fireEvent("modelerpage.view.showzoomfactor", new Double(scaleFactor)); } ////////////////////////////////////////////////// // @@ Selection ////////////////////////////////////////////////// /** * Gets the number of selected figures. */ public int selectionCount() { return selection.size(); } /** * Tests whether a given figure is selected. */ public boolean isFigureSelected(final Figure checkFigure) { return selection.contains(checkFigure); } public Vector selection() { return new Vector(selection); } public Vector selectionZOrdered() { return selection(); } /** * Adds a figure to the current selection. * The figure is only selected if it is also contained in the drawing * associated with this drawing view. */ public void addToSelection(Figure figure) { // Note that we can select only process element containers while (figure != null) { if (figure instanceof ProcessElementContainer || figure instanceof LineFigure) { break; } if (! (figure instanceof ChildFigure)) // No way in going up the hierarchy, give up return; figure = ((ChildFigure) figure).getParent(); } if (figure == null) // No valid parent figure found return; if (! isFigureSelected(figure)) { // Add to selection selection.add(figure); // Clear handles so they will be recalculated when needed activeHandles = null; markSelectionFigureChanged(figure, true); fireSelectionChanged(); } updateSelection(); // Reorder z-order drawing().bringToFront(figure); } /** * Removes a figure from the selection. */ public void removeFromSelection(final Figure figure) { if (isFigureSelected(figure)) { selection.remove(figure); activeHandles = null; figure.invalidate(); fireSelectionChanged(); markSelectionFigureChanged(figure, false); repairDamage(); updateSelection(); } } public void singleSelect(final Figure figure) { clearSelection(); if (figure != null) { addToSelection(figure); scrollIntoView(figure, true); } } /** * Clears the current selection. */ public void clearSelection() { // there is nothing selected if (selection.isEmpty()) // avoid unnecessary selection changed event when nothing has to be cleared return; // Save the current selection List oldSelection = new ArrayList(selection); selection.clear(); activeHandles = null; for (Iterator it = oldSelection.iterator(); it.hasNext();) { markSelectionFigureChanged((Figure) it.next(), false); } fireSelectionChanged(); updateSelection(); repairDamage(); } /** * Marks a selected or deselected figure as changed. * * @param figure Figure * @param selected * true: The figure has been selected.
* false: The figure has been deselected. */ private void markSelectionFigureChanged(Figure figure, final boolean selected) { if (figure instanceof VisualElement) { // Create a visual element event String type = selected ? VisualElementEvent.SELECTED : VisualElementEvent.DESELECTED; ((VisualElement) figure).handleEvent(new VisualElementEvent(type, (DrawingEditorPlugin) editor())); } while (figure != null) { if (figure instanceof VisualElement) { // Create a visual element event ((VisualElement) figure).handleEvent(new VisualElementEvent(VisualElementEvent.UPDATE_STATE, (DrawingEditorPlugin) editor())); } figure.changed(); if (figure instanceof ChildFigure) figure = ((ChildFigure) figure).getParent(); else break; } } /** * In addition to the super method, we fire an modeler.view.selectionchanged event. */ public void fireSelectionChanged() { super.fireSelectionChanged(); ((DrawingEditorPlugin) editor()).fireEvent("modeler.view.selectionchanged"); } /** * Notifies the property browser of selection changes. If none or more than one * element is selected, clear the property browser, else show the single element. */ public void updateSelection() { DrawingEditorPlugin modeler = (DrawingEditorPlugin) editor(); int selSize = selection.size(); if (selSize == 1) { // Single element selected, show it in the property browser. Figure figure = (Figure) selection.get(0); if (figure instanceof ProcessElementContainer) { ModelObject mo = ((ProcessElementContainer) figure).getReferredProcessElement(); Object originalObject = null; // Determine the item the figure element references to Item referencedItem = null; if (mo instanceof SubprocessNode) { referencedItem = ((SubprocessNode) mo).getSubprocess(); } if (editor() instanceof NodeItemEditorPlugin) { // We are in the nodeeditor, we need not the process object, // but the item behind it if (referencedItem != null) { // Get the reference object for name uniqueness checks originalObject = ModelConnector.getInstance().getItemByQualifier(referencedItem.getQualifier(), false); // We will edit the referenced item directly mo = referencedItem; } } else { if (mo instanceof MultiSocketNode) { MultiSocketNode node = (MultiSocketNode) mo; // If we edit an activity node, create a configuration bean if not already there // in order to be able to edit the settings in the property browser. // The bean will be removed if it contains the default values only when saving the activity node again // (see plugin_propertybrowser_executesave () in the ModelerEventModule) if (node.getConfigurationBean() == null) { // We create a new configuration bean (if defined by the underlying activity) if (referencedItem != null) { node.setConfigurationBean(referencedItem.createConfigurationBean()); } } } } // Make sure the global reference names are up to date or else we may have trouble // saving the object (the ModelObjectValidator will try to rebuild the references from the names) mo.maintainReferences(ModelObject.SYNC_GLOBAL_REFNAMES | ModelObject.SYNC_LOCAL_REFNAMES); String title = mo.getName(); String modelObjectTypeName = mo.getModelObjectTypeName(); if (modelObjectTypeName != null) { title = title + " (" + modelObjectTypeName + ")"; } // Send the object to the OE modeler.fireEvent(new PropertyBrowserSetEvent(modeler, mo, originalObject, false, mo.getDescription(), title, null, false, true)); // Show object description in info panel modeler.fireEvent(new JaspiraEvent(modeler, "plugin.infopanel.setinfotext", mo)); } if (figure instanceof Colorizable) { // Make the color chooser display the color of the selected object if it has a custom color Colorizable colorizable = (Colorizable) figure; Color figureColor = colorizable.getFillColor(); Color defaultColor = colorizable.getDefaultFillColor(); if (figureColor != null && ! figureColor.equals(defaultColor)) { modeler.fireEvent("colorchooser.setcolor", figureColor); } } } else { // More than one object selected, clear OE modeler.fireEvent(new PropertyBrowserSetEvent(modeler)); modeler.fireEvent(new JaspiraEvent(modeler, "plugin.infopanel.clearinfotext")); } // Update the cut/copy/paste button status modeler.fireEvent("global.clipboard.updatestatus"); } /** * Gets an enumeration of the currently active handles. * @return An enumeration of {@link Handle} objects */ private Enumeration selectionHandles() { if (activeHandles == null) { activeHandles = new Vector(); FigureEnumeration k = selectionElements(); while (k.hasMoreElements()) { Figure figure = k.nextFigure(); Enumeration kk = figure.handles().elements(); while (kk.hasMoreElements()) { activeHandles.addElement(kk.nextElement()); } } } return activeHandles.elements(); } /** * Finds a handle at the given coordinates. * @param x Document coordinates * @param y Document coordinates * @return The hit handle or null if no handle is found */ public Handle findHandle(final int x, final int y) { Handle handle; Enumeration k = selectionHandles(); while (k.hasMoreElements()) { handle = (Handle) k.nextElement(); if (handle.containsPoint(x, y)) return handle; } return null; } ////////////////////////////////////////////////// // @@ Keyboard and popup handling ////////////////////////////////////////////////// /** * Handles key down events. * All other keys are passed to the currently active tool. */ public void keyPressed(final KeyEvent e) { int code = e.getKeyCode(); DrawingEditorPlugin modeler = (DrawingEditorPlugin) editor(); if (code == KeyEvent.VK_ENTER && ! InputState.isCtrlDown() && ! InputState.isAltDown()) { // Purge the event e.consume(); if (selection.size() == 1) { Figure figure = (Figure) selection.get(0); displayPopupMenu(figure, null); } return; } if (code == KeyEvent.VK_ENTER && InputState.isAltDown()) { if (selection.size() == 1) { Figure figure = (Figure) selection.get(0); if (figure instanceof ProcessElementContainer && ! (figure instanceof ProcessDrawing)) { // we directly set the tool inside the editor modeler.getToolSupport().displayInPlaceEditor((ProcessElementContainer) figure); } } return; } if (code == KeyEvent.VK_DELETE) { // Purge the event e.consume(); modeler.fireEvent(new JaspiraActionEvent(modeler, "global.clipboard.delete", Plugin.LEVEL_APPLICATION)); return; } // Pass the key to the current tool tool().keyDown(e, code); } /** * Handles key up events. * All keys are passed to the currently active tool. */ public void keyReleased(final KeyEvent e) { // Pass the key to the current tool ((ModelerToolSupport) tool()).keyUp(e, e.getKeyCode()); } /** * Displays the popup menu for the given figure. * * @param figure Figure to display the menu for * @param me Mouse event that caused the popup display or null if the popup has been initiated by a key */ public void displayPopupMenu(final Figure figure, final MouseEvent me) { Transferable[] transferables = null; if (figure != null && figure != drawing()) { if (figure instanceof ProcessElementContainer) { // Obtain the underlying process element (usually a ModelObject) Object pe = ((ProcessElementContainer) figure).getReferredProcessElement(); transferables = new Transferable[] { new BasicTransferable(pe), new BasicTransferable(figure) }; } else { // Broadcast a popup interaction event to construct the popup menu transferables = new Transferable[] { new BasicTransferable(figure) }; } } if (transferables != null) { DrawingEditorPlugin modeler = (DrawingEditorPlugin) editor(); // Create an interaction event that transports the objects we refer to InteractionEvent iae = new InteractionEvent(modeler, InteractionEvent.POPUP, new MultiTransferable(transferables)); // Fire the event to the other plugins modeler.fireEvent(iae); // Create a popup menu from what the Jaspira actions the other plugins have added JaspiraPopupMenu menu = iae.createPopupMenu(); if (menu != null) { // Position and display the menu Point location = null; if (me != null) { location = new Point(me.getX(), me.getY()); } else if (figure != null) { location = figure.center(); location = applyScale(location, false); } adjustOffsets(getParent(), location); menu.setLocation(location); menu.setInvoker(this); menu.setVisible(true); if (me != null && figure != null) { figure.invalidate(); } } } } /** * Sums up iteratively all x and y offsets of all * parent compontents, translating viewport positions, * until the top parent component is reached. * * @param comp Component, which's location will be added to the point * @param offsetPoint Point to add the component coordinates to */ private void adjustOffsets(final Component comp, final Point offsetPoint) { if (comp == null) // Recursion break; return; if (comp instanceof JViewport) { // a viewport gives our component a virtual offset we have to translate // it another way than normal components Point pos = ((JViewport) comp).getViewPosition(); offsetPoint.translate(- pos.x, - pos.y); } else { Point compLocation = comp.getLocation(); offsetPoint.translate(compLocation.x, compLocation.y); } // Recurse down to parent adjustOffsets(comp.getParent(), offsetPoint); } ////////////////////////////////////////////////// // @@ Breakout support ////////////////////////////////////////////////// /** * Adds breakout support for the specified key. * * @param actionName Arbitrary name of the breakout support action * @param keyCode Key code that will initiate the breakout mode */ protected void addBreakoutSupport(final String actionName, final int keyCode) { // Breakout action map ActionMap am = getActionMap(); am.put(actionName, new BreakOutOnAction(keyCode)); // Input map entry for key pressed InputMap in = getInputMap(WHEN_FOCUSED); in.put(KeyStroke.getKeyStroke(keyCode, 0, false), actionName); } /** * Toolbox breakout initiation action. */ private class BreakOutOnAction extends AbstractAction { /** Key code used to initiate the breakout mode */ int keyCode; /** * Constructor. * * @param keyCode Key code used to initiate the breakout mode */ public BreakOutOnAction(final int keyCode) { this.keyCode = keyCode; } public void actionPerformed(final ActionEvent e) { boolean breakoutModeActive = SwingUtil.getGlassPane(WorkspaceDrawingView.this).isVisible(); if (mouseInView || breakoutModeActive) { if (! breakoutModeActive) { BreakoutEvent event = new BreakoutEvent((DrawingEditorPlugin) editor(), keyCode); ((DrawingEditorPlugin) editor()).fireEvent(event); BreakoutProvider bop = event.getProvider(); if (bop != null) { Component glassPane = SwingUtil.getGlassPane(WorkspaceDrawingView.this); if (glassPane instanceof DragDropPane) { // Get the last mouse point (view coordinates) Point lastPoint = ((ModelerToolSupport) tool()).getLastPoint(); if (lastPoint.x == 0 && lastPoint.y == 0) { // This may occur after a new process has been opened using the keyboard // and the mouse hasn't been moved so far. // Simply center the bop on the view JScrollPane scrollPane = SwingUtil.getScrollPaneAncestor(WorkspaceDrawingView.this); Rectangle viewRect = SwingUtil.convertBoundsToGlassCoords(scrollPane.getViewport()); lastPoint.x = viewRect.x + viewRect.width / 2; lastPoint.y = viewRect.y + viewRect.height / 2; } Point bobPosition = SwingUtilities.convertPoint(WorkspaceDrawingView.this, lastPoint, SwingUtil .getGlassPane(WorkspaceDrawingView.this)); ((DragDropPane) glassPane).startBreakOutMode(bop, bobPosition); } } } else { // Toggle: Deactivate the box Component glassPane = SwingUtil.getGlassPane(WorkspaceDrawingView.this); if (glassPane instanceof DragDropPane) { ((DragDropPane) glassPane).endBreakOutMode(); } } } } } ////////////////////////////////////////////////// // @@ Cursor movement support ////////////////////////////////////////////////// /** * Add support for selection and workspace movement by cursor keys. */ private void addCursorKeySupport() { // Somehow Drawing view cursor keys don't work, the non-modified keys are passed directly to the JScrollPane, the action doesn't get called, so we comment it out addCursorKeySupport(KeyEvent.VK_LEFT, InputEvent.CTRL_MASK, - 1, 0); addCursorKeySupport(KeyEvent.VK_RIGHT, InputEvent.CTRL_MASK, 1, 0); addCursorKeySupport(KeyEvent.VK_UP, InputEvent.CTRL_MASK, 0, - 1); addCursorKeySupport(KeyEvent.VK_DOWN, InputEvent.CTRL_MASK, 0, 1); addCursorKeySupport(KeyEvent.VK_LEFT, 0, - 10, 0); addCursorKeySupport(KeyEvent.VK_RIGHT, 0, 10, 0); addCursorKeySupport(KeyEvent.VK_UP, 0, 0, - 10); addCursorKeySupport(KeyEvent.VK_DOWN, 0, 0, 10); } /** * Add support for selection and workspace movement by cursor keys. * * @param keyCode Key code used to initiate the breakout mode * @param modifiers Key modifiers * @param xOffset Number of pixels to move horizontal * @param yOffset Number of pixels to move vertical */ private void addCursorKeySupport(final int keyCode, final int modifiers, final int xOffset, final int yOffset) { KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers, false); String actionName = keyStroke.toString() + "_Action"; // Breakout action map ActionMap am = getActionMap(); am.put(actionName, new CursorAction(xOffset, yOffset)); // Input map entry for key pressed InputMap in = getInputMap(WHEN_FOCUSED); in.put(KeyStroke.getKeyStroke(keyCode, modifiers, false), actionName); } /** * Toolbox breakout initiation action. */ private class CursorAction extends AbstractAction { /** Number of pixels to move horizontal */ int xOffset; /** Number of pixels to move vertical */ int yOffset; /** * Constructor. * * @param xOffset Number of pixels to move horizontal * @param yOffset Number of pixels to move vertical */ public CursorAction(final int xOffset, final int yOffset) { this.xOffset = xOffset; this.yOffset = yOffset; } public void actionPerformed(final ActionEvent e) { DrawingEditorPlugin modeler = (DrawingEditorPlugin) editor(); if (selectionCount() > 0) { // Move selection modeler.startUndo("Move element"); boolean first = true; for (Iterator it = selection.iterator(); it.hasNext();) { Figure f = (Figure) it.next(); f.moveBy(xOffset, yOffset); if (first) { scrollIntoView(f, true); first = false; } } modeler.endUndo(); checkDamage(); } else { // Move scroll pane ((Trackable) modeler).moveTrackerBy(xOffset * 20, yOffset * 20); } } } ////////////////////////////////////////////////// // @@ Member Access ////////////////////////////////////////////////// /** * Gets the shadow layouter. */ public ShadowLayouter getShadowLayouter() { return shadowLayouter; } /** * Sets the shadow layouter. */ public void setShadowLayouter(final ShadowLayouter shadowLayouter) { if (this.shadowLayouter != null) { this.shadowLayouter.releaseShadowLayouter(); } this.shadowLayouter = shadowLayouter; redraw(); } /** * Gets the figure currently under the cursor or null. */ public Figure getFigureUnderCursor() { return figureUnderCursor; } /** * Sets the figure currently under the cursor or null. */ public void setFigureUnderCursor(final Figure figureUnderCursor) { this.figureUnderCursor = figureUnderCursor; } /** * Gets the grid visibility. */ public boolean isGridDisplayed() { return gridDisplayed; } /** * Sets the grid visibility. */ public void setGridDisplayed(final boolean gridDisplayed) { boolean oldGridDisplayed = this.gridDisplayed; this.gridDisplayed = gridDisplayed; if (oldGridDisplayed != this.gridDisplayed) { redraw(); } } /** * Gets the grid layout type. */ public int getGridType() { return gridType; } /** * Sets the grid layout type. */ public void setGridType(final int gridType) { int oldGridType = this.gridType; this.gridType = gridType; if (gridDisplayed && oldGridType != this.gridType) { redraw(); } } /** * Gets the grid spacing. */ public int getGridSpacing() { return gridSpacing; } /** * Sets the grid spacing. */ public void setGridSpacing(final int gridSpacing) { int oldGridSpacing = this.gridSpacing; this.gridSpacing = gridSpacing; if (gridDisplayed && oldGridSpacing != this.gridSpacing) { redraw(); } } /** * Sets the size offset for workspace view enlargement. */ public void setSizeOffset(final int sizeOffset) { this.sizeOffset = sizeOffset; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy