/** * $Id: mxCellMarker.java,v 1.1 2012/11/15 13:26:44 gaudenz Exp $ * Copyright (c) 2008-2012, JGraph Ltd */ package com.mxgraph.swing.handler; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.event.MouseEvent; import javax.swing.JComponent; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.swing.util.mxSwingConstants; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventSource; import com.mxgraph.util.mxEventSource.mxIEventListener; import com.mxgraph.util.mxUtils; import com.mxgraph.view.mxCellState; import com.mxgraph.view.mxGraphView; /** * Implements a mouse tracker that marks cells under the mouse. * * This class fires the following event: * * mxEvent.MARK fires in mark and unmark to notify the listener of a new cell * under the mouse. The <code>state</code> property contains the mxCellState * of the respective cell or null if no cell is under the mouse. * * To create a cell marker which highlights cells "in-place", the following * code can be used: * <code> * mxCellMarker highlighter = new mxCellMarker(graphComponent) { * * protected Map<String, Object> lastStyle; * * public mxCellState process(MouseEvent e) * { * mxCellState state = null; * * if (isEnabled()) * { * state = getState(e); * boolean isValid = (state != null) ? isValidState(state) : false; * * if (!isValid) * { * state = null; * } * * highlight(state); * } * * return state; * } * * public void highlight(mxCellState state) * { * if (validState != state) * { * Rectangle dirty = null; * * if (validState != null) * { * validState.setStyle(lastStyle); * dirty = validState.getBoundingBox().getRectangle(); * dirty.grow(4, 4); * } * * if (state != null) * { * lastStyle = state.getStyle(); * state.setStyle(new Hashtable<String, Object>(state.getStyle())); * state.getStyle().put("strokeColor", "#00ff00"); * state.getStyle().put("fontColor", "#00ff00"); * state.getStyle().put("strokeWidth", "3"); * * Rectangle tmp = state.getBoundingBox().getRectangle(); * * if (dirty != null) * { * dirty.add(tmp); * } * else * { * dirty = tmp; * } * * dirty.grow(4, 4); * } * * validState = state; * graphComponent.repaint(dirty); * } * } * * public void reset() * { * highlight(null); * } * * public void paint(Graphics g) * { * // do nothing * } * }; * * graphComponent.getConnectionHandler().setMarker(highlighter); * </code> */ public class mxCellMarker extends JComponent { /** * */ private static final long serialVersionUID = 614473367053597572L; /** * Specifies if the highlights should appear on top of everything * else in the overlay pane. Default is false. */ public static boolean KEEP_ON_TOP = false; /** * Specifies the default stroke for the marker. */ public static Stroke DEFAULT_STROKE = new BasicStroke(3); /** * Holds the event source. */ protected mxEventSource eventSource = new mxEventSource(this); /** * Holds the enclosing graph component. */ protected mxGraphComponent graphComponent; /** * Specifies if the marker is enabled. Default is true. */ protected boolean enabled = true; /** * Specifies the portion of the width and height that should trigger * a highlight. The area around the center of the cell to be marked is used * as the hotspot. Possible values are between 0 and 1. Default is * mxConstants.DEFAULT_HOTSPOT. */ protected double hotspot; /** * Specifies if the hotspot is enabled. Default is false. */ protected boolean hotspotEnabled = false; /** * Specifies if the the content area of swimlane should be non-transparent * to mouse events. Default is false. */ protected boolean swimlaneContentEnabled = false; /** * Specifies the valid- and invalidColor for the marker. */ protected Color validColor, invalidColor; /** * Holds the current marker color. */ protected transient Color currentColor; /** * Holds the marked state if it is valid. */ protected transient mxCellState validState; /** * Holds the marked state. */ protected transient mxCellState markedState; /** * Constructs a new marker for the given graph component. * * @param graphComponent */ public mxCellMarker(mxGraphComponent graphComponent) { this(graphComponent, mxSwingConstants.DEFAULT_VALID_COLOR); } /** * Constructs a new marker for the given graph component. */ public mxCellMarker(mxGraphComponent graphComponent, Color validColor) { this(graphComponent, validColor, mxSwingConstants.DEFAULT_INVALID_COLOR); } /** * Constructs a new marker for the given graph component. */ public mxCellMarker(mxGraphComponent graphComponent, Color validColor, Color invalidColor) { this(graphComponent, validColor, invalidColor, mxConstants.DEFAULT_HOTSPOT); } /** * Constructs a new marker for the given graph component. */ public mxCellMarker(mxGraphComponent graphComponent, Color validColor, Color invalidColor, double hotspot) { this.graphComponent = graphComponent; this.validColor = validColor; this.invalidColor = invalidColor; this.hotspot = hotspot; } /** * Sets the enabled state of the marker. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Returns true if the marker is enabled, that is, if it processes events * in process. */ public boolean isEnabled() { return enabled; } /** * Sets the hotspot. */ public void setHotspot(double hotspot) { this.hotspot = hotspot; } /** * Returns the hotspot. */ public double getHotspot() { return hotspot; } /** * Specifies whether the hotspot should be used in intersects. */ public void setHotspotEnabled(boolean enabled) { this.hotspotEnabled = enabled; } /** * Returns true if hotspot is used in intersects. */ public boolean isHotspotEnabled() { return hotspotEnabled; } /** * Sets if the content area of swimlanes should not be transparent to * events. */ public void setSwimlaneContentEnabled(boolean swimlaneContentEnabled) { this.swimlaneContentEnabled = swimlaneContentEnabled; } /** * Returns true if the content area of swimlanes is non-transparent to * events. */ public boolean isSwimlaneContentEnabled() { return swimlaneContentEnabled; } /** * Sets the color used for valid highlights. */ public void setValidColor(Color value) { validColor = value; } /** * Returns the color used for valid highlights. */ public Color getValidColor() { return validColor; } /** * Sets the color used for invalid highlights. */ public void setInvalidColor(Color value) { invalidColor = value; } /** * Returns the color used for invalid highlights. */ public Color getInvalidColor() { return invalidColor; } /** * Returns true if validState is not null. */ public boolean hasValidState() { return (validState != null); } /** * Returns the valid state. */ public mxCellState getValidState() { return validState; } /** * Sets the current color. */ public void setCurrentColor(Color value) { currentColor = value; } /** * Returns the current color. */ public Color getCurrentColor() { return currentColor; } /** * Sets the marked state. */ public void setMarkedState(mxCellState value) { markedState = value; } /** * Returns the marked state. */ public mxCellState getMarkedState() { return markedState; } /** * Resets the state of the cell marker. */ public void reset() { validState = null; if (markedState != null) { markedState = null; unmark(); } } /** * Processes the given event and marks the state returned by getStateAt * with the color returned by getMarkerColor. If the markerColor is not * null, then the state is stored in markedState. If isValidState returns * true, then the state is stored in validState regardless of the marker * color. The state is returned regardless of the marker color and * valid state. */ public mxCellState process(MouseEvent e) { mxCellState state = null; if (isEnabled()) { state = getState(e); boolean valid = (state != null) ? isValidState(state) : false; Color color = getMarkerColor(e, state, valid); highlight(state, color, valid); } return state; } /** * */ public void highlight(mxCellState state, Color color) { highlight(state, color, true); } /** * */ public void highlight(mxCellState state, Color color, boolean valid) { if (valid) { validState = state; } else { validState = null; } if (state != markedState || color != currentColor) { currentColor = color; if (state != null && currentColor != null) { markedState = state; mark(); } else if (markedState != null) { markedState = null; unmark(); } } } /** * Marks the markedState and fires a mxEvent.MARK event. */ public void mark() { if (markedState != null) { Rectangle bounds = markedState.getRectangle(); bounds.grow(3, 3); bounds.width += 1; bounds.height += 1; setBounds(bounds); if (getParent() == null) { setVisible(true); if (KEEP_ON_TOP) { graphComponent.getGraphControl().add(this, 0); } else { graphComponent.getGraphControl().add(this); } } repaint(); eventSource.fireEvent(new mxEventObject(mxEvent.MARK, "state", markedState)); } } /** * Hides the marker and fires a mxEvent.MARK event. */ public void unmark() { if (getParent() != null) { setVisible(false); getParent().remove(this); eventSource.fireEvent(new mxEventObject(mxEvent.MARK)); } } /** * Returns true if the given state is a valid state. If this returns true, * then the state is stored in validState. The return value of this method * is used as the argument for getMarkerColor. */ protected boolean isValidState(mxCellState state) { return true; } /** * Returns the valid- or invalidColor depending on the value of isValid. * The given state is ignored by this implementation. */ protected Color getMarkerColor(MouseEvent e, mxCellState state, boolean isValid) { return (isValid) ? validColor : invalidColor; } /** * Uses getCell, getMarkedState and intersects to return the state for * the given event. */ protected mxCellState getState(MouseEvent e) { Object cell = getCell(e); mxGraphView view = graphComponent.getGraph().getView(); mxCellState state = getStateToMark(view.getState(cell)); return (state != null && intersects(state, e)) ? state : null; } /** * Returns the state at the given location. This uses mxGraph.getCellAt. */ protected Object getCell(MouseEvent e) { return graphComponent.getCellAt(e.getX(), e.getY(), swimlaneContentEnabled); } /** * Returns the state to be marked for the given state under the mouse. This * returns the given state. */ protected mxCellState getStateToMark(mxCellState state) { return state; } /** * Returns true if the given mouse event intersects the given state. This * returns true if the hotspot is 0 or the event is inside the hotspot for * the given cell state. */ protected boolean intersects(mxCellState state, MouseEvent e) { if (isHotspotEnabled()) { return mxUtils.intersectsHotspot(state, e.getX(), e.getY(), hotspot, mxConstants.MIN_HOTSPOT_SIZE, mxConstants.MAX_HOTSPOT_SIZE); } return true; } /** * Adds the given event listener. */ public void addListener(String eventName, mxIEventListener listener) { eventSource.addListener(eventName, listener); } /** * Removes the given event listener. */ public void removeListener(mxIEventListener listener) { eventSource.removeListener(listener); } /** * Removes the given event listener for the specified event name. */ public void removeListener(mxIEventListener listener, String eventName) { eventSource.removeListener(listener, eventName); } /** * Paints the outline of the markedState with the currentColor. */ public void paint(Graphics g) { if (markedState != null && currentColor != null) { ((Graphics2D) g).setStroke(DEFAULT_STROKE); g.setColor(currentColor); if (markedState.getAbsolutePointCount() > 0) { Point last = markedState.getAbsolutePoint(0).getPoint(); for (int i = 1; i < markedState.getAbsolutePointCount(); i++) { Point current = markedState.getAbsolutePoint(i).getPoint(); g.drawLine(last.x - getX(), last.y - getY(), current.x - getX(), current.y - getY()); last = current; } } else { g.drawRect(1, 1, getWidth() - 3, getHeight() - 3); } } } }