📄 mxgraphview.java
字号:
/** * $Id: mxGraphView.java,v 1.123 2009/05/07 06:44:42 gaudenz Exp $ * Copyright (c) 2007, Gaudenz Alder */package com.mxgraph.view;import java.awt.geom.Line2D;import java.util.ArrayList;import java.util.Hashtable;import java.util.List;import com.mxgraph.model.mxGeometry;import com.mxgraph.model.mxIGraphModel;import com.mxgraph.util.mxConstants;import com.mxgraph.util.mxEvent;import com.mxgraph.util.mxEventObject;import com.mxgraph.util.mxEventSource;import com.mxgraph.util.mxPoint;import com.mxgraph.util.mxRectangle;import com.mxgraph.util.mxUndoableEdit;import com.mxgraph.util.mxUtils;import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;import com.mxgraph.view.mxEdgeStyle.mxEdgeStyleFunction;import com.mxgraph.view.mxPerimeter.mxPerimeterFunction;/** * Implements a view for the graph. This class is in charge of computing the * absolute coordinates for the relative child geometries, the points for * perimeters and edge styles and keeping them cached in cell states for * faster retrieval. The states are updated whenever the model or the view * state (translate, scale) changes. The scale and translate are honoured in * the bounds. */public class mxGraphView extends mxEventSource{ /** * Reference to the enclosing graph. */ protected mxGraph graph; /** * mxCell that acts as the root of the displayed cell hierarchy. */ protected Object currentRoot = null; /** * Caches the current bounds of the graph. */ protected mxRectangle graphBounds = new mxRectangle(); /** * Specifies the scale. Default is 1 (100%). */ protected double scale = 1; /** * Point that specifies the current translation. Default is a new * empty point. */ protected mxPoint translate = new mxPoint(0, 0); /** * Maps from cells to cell states. */ protected Hashtable states = new Hashtable(); /** * Constructs a new view for the given graph. * * @param graph Reference to the enclosing graph. */ public mxGraphView(mxGraph graph) { this.graph = graph; } /** * Returns the enclosing graph. * * @return Returns the enclosing graph. */ public mxGraph getGraph() { return graph; } /** * Returns the dictionary that maps from cells to states. */ public Hashtable getStates() { return states; } /** * Returns the dictionary that maps from cells to states. */ public void setStates(Hashtable states) { this.states = states; } /** * Returns the cached diagram bounds. * * @return Returns the diagram bounds. */ public mxRectangle getGraphBounds() { return graphBounds; } /** * Returns the cached diagram bounds. * * @return Returns the diagram bounds. */ public void setGraphBounds(mxRectangle value) { graphBounds = value; } /** * Returns the current root. */ public Object getCurrentRoot() { return currentRoot; } /** * Sets and returns the current root and fires an undo event. * * @param root mxCell that specifies the root of the displayed cell hierarchy. * @return Returns the object that represents the current root. */ public Object setCurrentRoot(Object root) { if (currentRoot != root) { mxCurrentRootChange change = new mxCurrentRootChange(this, root); change.execute(); mxUndoableEdit edit = new mxUndoableEdit(this, false); edit.add(change); fireEvent(mxEvent.UNDO, new mxEventObject(new Object[] { edit })); } return root; } /** * Sets the scale and translation. Fires a "scaleAndTranslate" * event after calling revalidate. Revalidate is only called if * isEventsEnabled. * * @param scale Decimal value that specifies the new scale (1 is 100%). * @param dx X-coordinate of the translation. * @param dy Y-coordinate of the translation. */ public void scaleAndTranslate(double scale, double dx, double dy) { double oldScale = scale; Object oldTranslate = translate.clone(); if (scale != this.scale || dx != translate.getX() || dy != translate.getY()) { this.scale = scale; translate = new mxPoint(dx, dy); if (isEventsEnabled()) { revalidate(); } } fireEvent(mxEvent.SCALE_AND_TRANSLATE, new mxEventObject(new Object[] { oldScale, scale, oldTranslate, translate })); } /** * Returns the current scale. * * @return Returns the scale. */ public double getScale() { return scale; } /** * Sets the current scale and revalidates the view. Fires a "scale" * event after calling revalidate. Revalidate is only called if * isEventsEnabled. * * @param value New scale to be used. */ public void setScale(double value) { double old = scale; if (scale != value) { scale = value; if (isEventsEnabled()) { revalidate(); } } fireEvent(mxEvent.SCALE, new mxEventObject(new Object[] { old, scale })); } /** * Returns the current translation. * * @return Returns the translation. */ public mxPoint getTranslate() { return translate; } /** * Sets the current translation and invalidates the view. Fires * a property change event for "translate" after calling * revalidate. Revalidate is only called if isEventsEnabled. * * @param value New translation to be used. */ public void setTranslate(mxPoint value) { Object old = translate.clone(); if (value != null && (value.getX() != translate.getX() || value.getY() != translate .getY())) { translate = value; if (isEventsEnabled()) { revalidate(); } } fireEvent(mxEvent.TRANSLATE, new mxEventObject(new Object[] { old, translate })); } /** * Returns the bounding box for an array of cells or null, if no cells are * specified. * * @param cells * @return Returns the bounding box for the given cells. */ public mxRectangle getBounds(Object[] cells) { return getBounds(cells, false); } /** * Returns the bounding box for an array of cells or null, if no cells are * specified. * * @param cells * @return Returns the bounding box for the given cells. */ public mxRectangle getBoundingBox(Object[] cells) { return getBounds(cells, true); } /** * Returns the bounding box for an array of cells or null, if no cells are * specified. * * @param cells * @return Returns the bounding box for the given cells. */ public mxRectangle getBounds(Object[] cells, boolean boundingBox) { mxRectangle result = null; if (cells != null && cells.length > 0) { mxIGraphModel model = graph.getModel(); for (int i = 0; i < cells.length; i++) { if (model.isVertex(cells[i]) || model.isEdge(cells[i])) { mxCellState state = getState(cells[i]); if (state != null) { mxRectangle tmp = (boundingBox) ? state .getBoundingBox() : state; if (tmp != null) { if (result == null) { result = new mxRectangle(tmp); } else { result.add(tmp); } } } } } } return result; } /** * Removes all existing cell states and invokes revalidate. */ public void reload() { states.clear(); revalidate(); } /** * */ public void revalidate() { invalidate(); validate(); } /** * Invalidates all cell states. */ public void invalidate() { invalidate(null); } /** * Removes the state of the given cell and all descendants if the given * cell is not the current root. * * @param cell * @param force * @param recurse */ public void clear(Object cell, boolean force, boolean recurse) { removeState(cell); if (recurse && (force || cell != currentRoot)) { mxIGraphModel model = graph.getModel(); int childCount = model.getChildCount(cell); for (int i = 0; i < childCount; i++) { clear(model.getChildAt(cell, i), force, recurse); } } else { invalidate(cell); } } /** * Invalidates the state of the given cell, all its descendants and * connected edges. */ public void invalidate(Object cell) { mxIGraphModel model = graph.getModel(); cell = (cell != null) ? cell : model.getRoot(); mxCellState state = getState(cell); if (state == null || !state.isInvalid()) { if (state != null) { state.setInvalid(true); } // Recursively invalidates all descendants int childCount = model.getChildCount(cell); for (int i = 0; i < childCount; i++) { Object child = model.getChildAt(cell, i); invalidate(child); } // Propagates invalidation to all connected edges int edgeCount = model.getEdgeCount(cell); for (int i = 0; i < edgeCount; i++) { invalidate(model.getEdgeAt(cell, i)); } } } /** * First validates all bounds and then validates all points recursively on * all visible cells. */ public void validate() { Object cell = (currentRoot != null) ? currentRoot : graph.getModel() .getRoot(); if (cell != null) { validateBounds(null, cell); setGraphBounds(validatePoints(null, cell)); } } /** * Validates the bounds of the given parent's child using the given parent * state as the origin for the child. The validation is carried out * recursively for all non-collapsed descendants. * * @param parentState Object that represents the state of the parent cell. * @param cell Cell for which the bounds in the state should be updated. */ public void validateBounds(mxCellState parentState, Object cell) { mxIGraphModel model = graph.getModel(); mxCellState state = getState(cell, true); if (state != null && state.isInvalid()) { if (!graph.isCellVisible(cell)) { removeState(cell); } else if (cell != currentRoot && parentState != null) { state.setOrigin(new mxPoint(parentState.getOrigin())); mxGeometry geo = graph.getCellGeometry(cell); if (geo != null) { if (!model.isEdge(cell)) { mxPoint origin = state.getOrigin(); mxPoint offset = geo.getOffset(); if (offset == null) { offset = new mxPoint(); } if (geo.isRelative()) { origin.setX(origin.getX() + geo.getX() * parentState.getWidth() / scale + offset.getX()); origin.setY(origin.getY() + geo.getY() * parentState.getHeight() / scale + offset.getY()); } else { state.setAbsoluteOffset(new mxPoint(scale * offset.getX(), scale * offset.getY())); origin.setX(origin.getX() + geo.getX()); origin.setY(origin.getY() + geo.getY()); } } // Updates the cell state's bounds state.setX(scale * (translate.getX() + state.getOrigin().getX())); state.setY(scale * (translate.getY() + state.getOrigin().getY())); state.setWidth(scale * geo.getWidth()); state.setHeight(scale * geo.getHeight()); if (model.isVertex(cell)) { updateVertexLabelOffset(state); } } } // Applies child offset to origin mxPoint offset = graph.getChildOffsetForCell(cell); if (offset != null) { state.getOrigin() .setX(state.getOrigin().getX() + offset.getX()); state.getOrigin() .setY(state.getOrigin().getY() + offset.getY()); } } // Recursively validates the child bounds if (state != null && (!graph.isCellCollapsed(cell) || cell == currentRoot)) { int childCount = model.getChildCount(cell); for (int i = 0; i < childCount; i++) { validateBounds(state, model.getChildAt(cell, i)); } } } /** * Updates the absoluteOffset of the given vertex cell state. This takes * into account the label position styles. * * @param state Cell state whose absolute offset should be updated. */ public void updateVertexLabelOffset(mxCellState state) { String horizontal = mxUtils.getString(state.getStyle(), mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER); if (horizontal.equals(mxConstants.ALIGN_LEFT)) { state.absoluteOffset.setX(state.absoluteOffset.getX() - state.getWidth()); } else if (horizontal.equals(mxConstants.ALIGN_RIGHT)) { state.absoluteOffset.setX(state.absoluteOffset.getX() + state.getWidth()); } String vertical = mxUtils.getString(state.getStyle(), mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE); if (vertical.equals(mxConstants.ALIGN_TOP))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -