com.mxgraph.util.mxUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jgraphx Show documentation
Show all versions of jgraphx Show documentation
JGraphX Swing Component - Java Graph Visualization Library
This is a binary & source redistribution of the original, unmodified JGraphX library originating from:
"https://github.com/jgraph/jgraphx/archive/v3.4.1.3.zip".
The purpose of this redistribution is to make the library available to other Maven projects.
/**
* $Id: mxUtils.java,v 1.136 2012/12/14 17:49:06 gaudenz Exp $
* Copyright (c) 2007-2012, JGraph Ltd
*/
package com.mxgraph.util;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import javax.imageio.ImageIO;
import javax.swing.text.html.HTMLDocument;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.mxgraph.io.mxCodecRegistry;
import com.mxgraph.model.mxCellPath;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.view.mxCellState;
/**
* Contains various helper methods for use with mxGraph.
*/
public class mxUtils
{
/**
* True if the machine is a Mac.
*/
public static boolean IS_MAC = System.getProperty("os.name").toLowerCase()
.indexOf("mac") >= 0;
/**
* True if the machine is running a linux kernel.
*/
public static boolean IS_LINUX = System.getProperty("os.name").toLowerCase()
.indexOf("linux") >= 0;
/**
* Static Graphics used for Font Metrics.
*/
protected static transient Graphics fontGraphics;
// Creates a renderer for HTML markup (only possible in
// non-headless environment)
static
{
try
{
fontGraphics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB)
.getGraphics();
}
catch (Exception e)
{
// ignore
}
}
/**
* Returns the size for the given label. If isHtml is true then any HTML
* markup in the label is computed as HTML and all newlines inside the HTML
* body are converted into linebreaks.
*/
public static mxRectangle getLabelSize(String label,
Map style, boolean isHtml, double scale)
{
return getLabelSize(label, style, isHtml, scale, 0);
}
/**
* Returns the size for the given label. If isHtml is true then any HTML
* markup in the label is computed as HTML and all newlines inside the HTML
* body are converted into linebreaks.
*/
public static mxRectangle getLabelSize(String label,
Map style, boolean isHtml, double scale,
double htmlWrapWidth)
{
mxRectangle size;
if (isHtml)
{
size = getSizeForHtml(getBodyMarkup(label, true), style, scale,
htmlWrapWidth);
}
else
{
size = getSizeForString(label, getFont(style), scale);
}
return size;
}
/**
* Returns the body part of the given HTML markup.
*/
public static String getBodyMarkup(String markup, boolean replaceLinefeeds)
{
String lowerCase = markup.toLowerCase();
int bodyStart = lowerCase.indexOf("");
if (bodyStart >= 0)
{
bodyStart += 7;
int bodyEnd = lowerCase.lastIndexOf("");
if (bodyEnd > bodyStart)
{
markup = markup.substring(bodyStart, bodyEnd).trim();
}
}
if (replaceLinefeeds)
{
markup = markup.replaceAll("\n", "
");
}
return markup;
}
/**
* Returns the paint bounds for the given label.
*/
public static mxRectangle getLabelPaintBounds(String label,
Map style, boolean isHtml, mxPoint offset,
mxRectangle vertexBounds, double scale)
{
double wrapWidth = 0;
if (isHtml
&& vertexBounds != null
&& mxUtils.getString(style, mxConstants.STYLE_WHITE_SPACE,
"nowrap").equals("wrap"))
{
wrapWidth = vertexBounds.getWidth();
}
mxRectangle size = mxUtils.getLabelSize(label, style, isHtml, scale,
wrapWidth);
// Measures font with full scale and scales back
size.setWidth(size.getWidth() / scale);
size.setHeight(size.getHeight() / scale);
double x = offset.getX();
double y = offset.getY();
double width = 0;
double height = 0;
if (vertexBounds != null)
{
x += vertexBounds.getX();
y += vertexBounds.getY();
if (mxUtils.getString(style, mxConstants.STYLE_SHAPE, "").equals(
mxConstants.SHAPE_SWIMLANE))
{
// Limits the label to the swimlane title
boolean horizontal = mxUtils.isTrue(style,
mxConstants.STYLE_HORIZONTAL, true);
double start = mxUtils.getDouble(style,
mxConstants.STYLE_STARTSIZE,
mxConstants.DEFAULT_STARTSIZE)
* scale;
if (horizontal)
{
width += vertexBounds.getWidth();
height += start;
}
else
{
width += start;
height += vertexBounds.getHeight();
}
}
else
{
width += vertexBounds.getWidth();
height += vertexBounds.getHeight();
}
}
return mxUtils.getScaledLabelBounds(x, y, size, width, height, style,
scale);
}
/**
* Returns the bounds for a label for the given location and size, taking
* into account the alignment and spacing in the specified style, as well as
* the width and height of the rectangle that contains the label. (For edge
* labels this width and height is 0.) The scale is used to scale the given
* size and the spacings in the specified style.
*/
public static mxRectangle getScaledLabelBounds(double x, double y,
mxRectangle size, double outerWidth, double outerHeight,
Map style, double scale)
{
double inset = mxConstants.LABEL_INSET * scale;
// Scales the size of the label
// FIXME: Correct rounded font size and not-rounded scale
double width = size.getWidth() * scale + 2 * inset;
double height = size.getHeight() * scale + 2 * inset;
// Gets the global spacing and orientation
boolean horizontal = isTrue(style, mxConstants.STYLE_HORIZONTAL, true);
int spacing = (int) (getInt(style, mxConstants.STYLE_SPACING) * scale);
// Gets the alignment settings
Object align = getString(style, mxConstants.STYLE_ALIGN,
mxConstants.ALIGN_CENTER);
Object valign = getString(style, mxConstants.STYLE_VERTICAL_ALIGN,
mxConstants.ALIGN_MIDDLE);
// Gets the vertical spacing
int top = (int) (getInt(style, mxConstants.STYLE_SPACING_TOP) * scale);
int bottom = (int) (getInt(style, mxConstants.STYLE_SPACING_BOTTOM) * scale);
// Gets the horizontal spacing
int left = (int) (getInt(style, mxConstants.STYLE_SPACING_LEFT) * scale);
int right = (int) (getInt(style, mxConstants.STYLE_SPACING_RIGHT) * scale);
// Applies the orientation to the spacings and dimension
if (!horizontal)
{
int tmp = top;
top = right;
right = bottom;
bottom = left;
left = tmp;
double tmp2 = width;
width = height;
height = tmp2;
}
// Computes the position of the label for the horizontal alignment
if ((horizontal && align.equals(mxConstants.ALIGN_CENTER))
|| (!horizontal && valign.equals(mxConstants.ALIGN_MIDDLE)))
{
x += (outerWidth - width) / 2 + left - right;
}
else if ((horizontal && align.equals(mxConstants.ALIGN_RIGHT))
|| (!horizontal && valign.equals(mxConstants.ALIGN_BOTTOM)))
{
x += outerWidth - width - spacing - right;
}
else
{
x += spacing + left;
}
// Computes the position of the label for the vertical alignment
if ((!horizontal && align.equals(mxConstants.ALIGN_CENTER))
|| (horizontal && valign.equals(mxConstants.ALIGN_MIDDLE)))
{
y += (outerHeight - height) / 2 + top - bottom;
}
else if ((!horizontal && align.equals(mxConstants.ALIGN_LEFT))
|| (horizontal && valign.equals(mxConstants.ALIGN_BOTTOM)))
{
y += outerHeight - height - spacing - bottom;
}
else
{
y += spacing + top;
}
return new mxRectangle(x, y, width, height);
}
/**
* Returns the font metrics of the static font graphics instance
* @param font The font whose metrics are to be returned
* @return the font metrics of the specified font
*/
public static FontMetrics getFontMetrics(Font font)
{
if (fontGraphics != null)
{
return fontGraphics.getFontMetrics(font);
}
return null;
}
/**
* Returns an with the size (width and height in pixels) of
* the given string.
*
* @param text
* String whose size should be returned.
* @param font
* Font to be used for the computation.
*/
public static mxRectangle getSizeForString(String text, Font font,
double scale)
{
FontRenderContext frc = new FontRenderContext(null, false, false);
font = font.deriveFont((float) (font.getSize2D() * scale));
FontMetrics metrics = null;
if (fontGraphics != null)
{
metrics = fontGraphics.getFontMetrics(font);
}
double lineHeight = mxConstants.LINESPACING;
if (metrics != null)
{
lineHeight += metrics.getHeight();
}
else
{
lineHeight += font.getSize2D() * 1.27;
}
String[] lines = text.split("\n");
Rectangle2D boundingBox = null;
if (lines.length == 0)
{
boundingBox = font.getStringBounds("", frc);
}
else
{
for (int i = 0; i < lines.length; i++)
{
Rectangle2D bounds = font.getStringBounds(lines[i], frc);
if (boundingBox == null)
{
boundingBox = bounds;
}
else
{
boundingBox
.setFrame(
0,
0,
Math.max(boundingBox.getWidth(),
bounds.getWidth()),
boundingBox.getHeight() + lineHeight);
}
}
}
return new mxRectangle(boundingBox);
}
/**
* Returns the specified text in lines that fit within the specified
* width when the specified font metrics are applied to the text
* @param text the text to wrap
* @param metrics the font metrics to calculate the text size for
* @param width the width that the text must fit within
* @return the input text split in lines that fit the specified width
*/
public static String[] wordWrap(String text, FontMetrics metrics,
double width)
{
List result = new ArrayList();
// First split the processing into lines already delimited by
// newlines. We want the result to retain all newlines in position.
String[] lines = text.split("\n");
for (int i = 0; i < lines.length; i++)
{
int lineWidth = 0; // the display width of the current line
int charCount = 0; // keeps count of current position in the line
StringBuilder currentLine = new StringBuilder();
// Split the words of the current line by spaces and tabs
// The words are trimmed of tabs, space and newlines, therefore
String[] words = lines[i].split("\\s+");
// Need to a form a stack of the words in reverse order
// This is because if a word is split during the process
// the remainder of the word is added to the front of the
// stack and processed next
Stack wordStack = new Stack();
for (int j = words.length - 1; j >= 0; j--)
{
wordStack.push(words[j]);
}
while (!wordStack.isEmpty())
{
String word = wordStack.pop();
// Work out what whitespace exists before this word.
// and add the width of the whitespace to the calculation
int whitespaceCount = 0;
if (word.length() > 0)
{
// Concatenate any preceding whitespace to the
// word and calculate the number of characters of that
// whitespace
char firstWordLetter = word.charAt(0);
int letterIndex = lines[i].indexOf(firstWordLetter,
charCount);
String whitespace = lines[i].substring(charCount,
letterIndex);
whitespaceCount = whitespace.length();
word = whitespace.concat(word);
}
double wordLength;
// If the line width is zero, we are at the start of a newline
// We don't proceed preceeding whitespace in the width
// calculation
if (lineWidth > 0)
{
wordLength = metrics.stringWidth(word);
}
else
{
wordLength = metrics.stringWidth(word.trim());
}
// Does the width of line so far plus the width of the
// current word exceed the allowed width?
if (lineWidth + wordLength > width)
{
if (lineWidth > 0)
{
// There is already at least one word on this line
// and the current word takes the overall width over
// the allowed width. Because there is something on
// the line, complete the current line, reset the width
// counter, create a new line and put the current word
// back on the stack for processing in the next round
result.add(currentLine.toString());
currentLine = new StringBuilder();
wordStack.push(word.trim());
lineWidth = 0;
}
else if (mxConstants.SPLIT_WORDS)
{
// There are no words on the current line and the
// current word does not fit on it. Find the maximum
// number of characters of this word that just fit
// in the available width
word = word.trim();
for (int j = 1; j <= word.length(); j++)
{
wordLength = metrics.stringWidth(word.substring(0,
j));
if (lineWidth + wordLength > width)
{
// The last character took us over the allowed
// width, deducted it unless there is only one
// character, in which case we have to use it
// since we can't split it...
j = j > 1 ? j - 1 : j;
String chars = word.substring(0, j);
currentLine = currentLine.append(chars);
// Return the unprocessed part of the word
// to the stack
wordStack
.push(word.substring(j, word.length()));
result.add(currentLine.toString());
currentLine = new StringBuilder();
lineWidth = 0;
// Increment char counter allowing for white
// space in the original word
charCount = charCount + chars.length()
+ whitespaceCount;
break;
}
}
}
else
{
// There are no words on the current line, but
// we are not splitting.
word = word.trim();
result.add(word);
currentLine = new StringBuilder();
lineWidth = 0;
// Increment char counter allowing for white
// space in the original word
charCount = word.length() + whitespaceCount;
}
}
else
{
// The current word does not take the total line width
// over the allowed width. Append the word, removing
// preceeding whitespace if it is the first word in the
// line.
if (lineWidth > 0)
{
currentLine = currentLine.append(word);
}
else
{
currentLine = currentLine.append(word.trim());
}
lineWidth += wordLength;
charCount += word.length();
}
}
result.add(currentLine.toString());
}
return result.toArray(new String[result.size()]);
}
/**
* Returns an mxRectangle with the size (width and height in pixels) of the
* given HTML markup.
*
* @param markup
* HTML markup whose size should be returned.
*/
public static mxRectangle getSizeForHtml(String markup,
Map style, double scale, double wrapWidth)
{
mxLightweightLabel textRenderer = mxLightweightLabel
.getSharedInstance();
if (textRenderer != null)
{
// First run measures size with no wrapping
textRenderer.setText(createHtmlDocument(style, markup));
Dimension size = textRenderer.getPreferredSize();
// Second run measures size with wrapping if required.
// Note that this is only required because max-width
// is not supported and we can't get the width of an
// inner HTML element (or is this possible?).
if (wrapWidth > 0)
{
textRenderer.setText(createHtmlDocument(
style,
markup,
1,
(int) Math.ceil(wrapWidth * mxConstants.PX_PER_PIXEL
- mxConstants.LABEL_INSET * scale)));
Dimension size2 = textRenderer.getPreferredSize();
// Uses wrapped text size if any text was actually wrapped
if (size2.width < size.width)
{
size = size2;
}
}
return new mxRectangle(0, 0, size.width * scale, size.height
* scale);
}
else
{
return getSizeForString(markup, getFont(style), scale);
}
}
/**
* Function: arcToCurves
*
* Converts the given arc to a series of curves.
*/
public static double[] arcToCurves(double x0, double y0, double r1,
double r2, double angle, double largeArcFlag, double sweepFlag,
double x, double y)
{
x -= x0;
y -= y0;
if (r1 == 0 || r2 == 0)
{
return new double[0];
}
double fS = sweepFlag;
double psai = angle;
r1 = Math.abs(r1);
r2 = Math.abs(r2);
double ctx = -x / 2;
double cty = -y / 2;
double cpsi = Math.cos(psai * Math.PI / 180);
double spsi = Math.sin(psai * Math.PI / 180);
double rxd = cpsi * ctx + spsi * cty;
double ryd = -1 * spsi * ctx + cpsi * cty;
double rxdd = rxd * rxd;
double rydd = ryd * ryd;
double r1x = r1 * r1;
double r2y = r2 * r2;
double lamda = rxdd / r1x + rydd / r2y;
double sds;
if (lamda > 1)
{
r1 = Math.sqrt(lamda) * r1;
r2 = Math.sqrt(lamda) * r2;
sds = 0;
}
else
{
double seif = 1;
if (largeArcFlag == fS)
{
seif = -1;
}
sds = seif
* Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd)
/ (r1x * rydd + r2y * rxdd));
}
double txd = sds * r1 * ryd / r2;
double tyd = -1 * sds * r2 * rxd / r1;
double tx = cpsi * txd - spsi * tyd + x / 2;
double ty = spsi * txd + cpsi * tyd + y / 2;
double rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1)
- Math.atan2(0, 1);
double s1 = (rad >= 0) ? rad : 2 * Math.PI + rad;
rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1)
- Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);
double dr = (rad >= 0) ? rad : 2 * Math.PI + rad;
if (fS == 0 && dr > 0)
{
dr -= 2 * Math.PI;
}
else if (fS != 0 && dr < 0)
{
dr += 2 * Math.PI;
}
double sse = dr * 2 / Math.PI;
int seg = (int) Math.ceil(sse < 0 ? -1 * sse : sse);
double segr = dr / seg;
double t = 8 / 3 * Math.sin(segr / 4) * Math.sin(segr / 4)
/ Math.sin(segr / 2);
double cpsir1 = cpsi * r1;
double cpsir2 = cpsi * r2;
double spsir1 = spsi * r1;
double spsir2 = spsi * r2;
double mc = Math.cos(s1);
double ms = Math.sin(s1);
double x2 = -t * (cpsir1 * ms + spsir2 * mc);
double y2 = -t * (spsir1 * ms - cpsir2 * mc);
double x3 = 0;
double y3 = 0;
double[] result = new double[seg * 6];
for (int n = 0; n < seg; ++n)
{
s1 += segr;
mc = Math.cos(s1);
ms = Math.sin(s1);
x3 = cpsir1 * mc - spsir2 * ms + tx;
y3 = spsir1 * mc + cpsir2 * ms + ty;
double dx = -t * (cpsir1 * ms + spsir2 * mc);
double dy = -t * (spsir1 * ms - cpsir2 * mc);
// CurveTo updates x0, y0 so need to restore it
int index = n * 6;
result[index] = x2 + x0;
result[index + 1] = y2 + y0;
result[index + 2] = x3 - dx + x0;
result[index + 3] = y3 - dy + y0;
result[index + 4] = x3 + x0;
result[index + 5] = y3 + y0;
x2 = x3 + dx;
y2 = y3 + dy;
}
return result;
}
/**
* Returns the bounding box for the rotated rectangle.
*/
public static mxRectangle getBoundingBox(mxRectangle rect, double rotation)
{
mxRectangle result = null;
if (rect != null && rotation != 0)
{
double rad = Math.toRadians(rotation);
double cos = Math.cos(rad);
double sin = Math.sin(rad);
mxPoint cx = new mxPoint(rect.getX() + rect.getWidth() / 2,
rect.getY() + rect.getHeight() / 2);
mxPoint p1 = new mxPoint(rect.getX(), rect.getY());
mxPoint p2 = new mxPoint(rect.getX() + rect.getWidth(), rect.getY());
mxPoint p3 = new mxPoint(p2.getX(), rect.getY() + rect.getHeight());
mxPoint p4 = new mxPoint(rect.getX(), p3.getY());
p1 = getRotatedPoint(p1, cos, sin, cx);
p2 = getRotatedPoint(p2, cos, sin, cx);
p3 = getRotatedPoint(p3, cos, sin, cx);
p4 = getRotatedPoint(p4, cos, sin, cx);
Rectangle tmp = new Rectangle((int) p1.getX(), (int) p1.getY(), 0,
0);
tmp.add(p2.getPoint());
tmp.add(p3.getPoint());
tmp.add(p4.getPoint());
result = new mxRectangle(tmp);
}
else if (rect != null)
{
result = (mxRectangle) rect.clone();
}
return result;
}
/**
* Find the first character matching the input character in the given
* string where the character has no letter preceding it.
*
* @param text the string to test for the presence of the input character
* @param inputChar the test character
* @param fromIndex the index position of the string to start from
* @return the position of the first character matching the input character
* in the given string where the character has no letter preceding it.
*/
public static int firstCharAt(String text, int inputChar, int fromIndex)
{
int result = 0;
while (result >= 0)
{
result = text.indexOf(inputChar, fromIndex);
if (result == 0)
{
return result;
}
else if (result > 0)
{
// Check there is a whitespace or symbol before the hit character
if (Character.isLetter(text.codePointAt(result - 1)))
{
// The pre-increment is used in if and else branches.
if (++fromIndex >= text.length())
{
return -1;
}
else
{
// Test again from next candidate character
// This isn't the first letter of this word
result = text.indexOf(inputChar, fromIndex);
}
}
else
{
return result;
}
}
}
return result;
}
/**
* Rotates the given point by the given cos and sin.
*/
public static mxPoint getRotatedPoint(mxPoint pt, double cos, double sin)
{
return getRotatedPoint(pt, cos, sin, new mxPoint());
}
/**
* Finds the index of the nearest segment on the given cell state for the
* specified coordinate pair.
*/
public static int findNearestSegment(mxCellState state, double x, double y)
{
int index = -1;
if (state.getAbsolutePointCount() > 0)
{
mxPoint last = state.getAbsolutePoint(0);
double min = Double.MAX_VALUE;
for (int i = 1; i < state.getAbsolutePointCount(); i++)
{
mxPoint current = state.getAbsolutePoint(i);
double dist = new Line2D.Double(last.x, last.y, current.x,
current.y).ptSegDistSq(x, y);
if (dist < min)
{
min = dist;
index = i - 1;
}
last = current;
}
}
return index;
}
/**
* Rotates the given point by the given cos and sin.
*/
public static mxPoint getRotatedPoint(mxPoint pt, double cos, double sin,
mxPoint c)
{
double x = pt.getX() - c.getX();
double y = pt.getY() - c.getY();
double x1 = x * cos - y * sin;
double y1 = y * cos + x * sin;
return new mxPoint(x1 + c.getX(), y1 + c.getY());
}
/**
* Returns an integer mask of the port constraints of the given map
* @param terminal the cached cell state of the cell to determine the
* port constraints for
* @param edge the edge connected to the constrained terminal
* @param source whether or not the edge specified is connected to the
* terminal specified at its source end
* @return the mask of port constraint directions
*/
public static int getPortConstraints(mxCellState terminal,
mxCellState edge, boolean source)
{
return getPortConstraints(terminal, edge, source,
mxConstants.DIRECTION_MASK_ALL);
}
/**
* Returns an integer mask of the port constraints of the given map
* @param terminal the cached cell state of the cell to determine the
* port constraints for
* @param edge the edge connected to the constrained terminal
* @param source whether or not the edge specified is connected to the
* terminal specified at its source end
* @param defaultValue Default value to return if the key is undefined.
* @return the mask of port constraint directions
*/
public static int getPortConstraints(mxCellState terminal,
mxCellState edge, boolean source, int defaultValue)
{
Object value = terminal.getStyle().get(
mxConstants.STYLE_PORT_CONSTRAINT);
if (value == null)
{
return defaultValue;
}
else
{
String directions = value.toString();
int returnValue = mxConstants.DIRECTION_MASK_NONE;
if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0)
{
returnValue |= mxConstants.DIRECTION_MASK_NORTH;
}
if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0)
{
returnValue |= mxConstants.DIRECTION_MASK_WEST;
}
if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0)
{
returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
}
if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0)
{
returnValue |= mxConstants.DIRECTION_MASK_EAST;
}
return returnValue;
}
}
public static int reversePortConstraints(int constraint)
{
int result = 0;
result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;
result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;
result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;
return result;
}
/**
* Draws the image inside the clip bounds to the given graphics object.
*/
public static void drawImageClip(Graphics g, BufferedImage image,
ImageObserver observer)
{
Rectangle clip = g.getClipBounds();
if (clip != null)
{
int w = image.getWidth();
int h = image.getHeight();
int x = Math.max(0, Math.min(clip.x, w));
int y = Math.max(0, Math.min(clip.y, h));
w = Math.min(clip.width, w - x);
h = Math.min(clip.height, h - y);
if (w > 0 && h > 0)
{
// TODO: Support for normal images using fast subimage copies
g.drawImage(image.getSubimage(x, y, w, h), clip.x, clip.y,
observer);
}
}
else
{
g.drawImage(image, 0, 0, observer);
}
}
/**
*
*/
public static void fillClippedRect(Graphics g, int x, int y, int width,
int height)
{
Rectangle bg = new Rectangle(x, y, width, height);
try
{
if (g.getClipBounds() != null)
{
bg = bg.intersection(g.getClipBounds());
}
}
catch (Exception e)
{
// FIXME: Getting clipbounds sometimes throws an NPE
}
g.fillRect(bg.x, bg.y, bg.width, bg.height);
}
/**
* Creates a new list of new points obtained by translating the points in
* the given list by the given vector. Elements that are not mxPoints are
* added to the result as-is.
*/
public static List translatePoints(List pts, double dx,
double dy)
{
List result = null;
if (pts != null)
{
result = new ArrayList(pts.size());
Iterator it = pts.iterator();
while (it.hasNext())
{
mxPoint point = (mxPoint) it.next().clone();
point.setX(point.getX() + dx);
point.setY(point.getY() + dy);
result.add(point);
}
}
return result;
}
/**
* Returns the intersection of two lines as an mxPoint.
*
* @param x0
* X-coordinate of the first line's startpoint.
* @param y0
* Y-coordinate of the first line's startpoint.
* @param x1
* X-coordinate of the first line's endpoint.
* @param y1
* Y-coordinate of the first line's endpoint.
* @param x2
* X-coordinate of the second line's startpoint.
* @param y2
* Y-coordinate of the second line's startpoint.
* @param x3
* X-coordinate of the second line's endpoint.
* @param y3
* Y-coordinate of the second line's endpoint.
* @return Returns the intersection between the two lines.
*/
public static mxPoint intersection(double x0, double y0, double x1,
double y1, double x2, double y2, double x3, double y3)
{
double denom = ((y3 - y2) * (x1 - x0)) - ((x3 - x2) * (y1 - y0));
double nume_a = ((x3 - x2) * (y0 - y2)) - ((y3 - y2) * (x0 - x2));
double nume_b = ((x1 - x0) * (y0 - y2)) - ((y1 - y0) * (x0 - x2));
double ua = nume_a / denom;
double ub = nume_b / denom;
if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
{
// Get the intersection point
double intersectionX = x0 + ua * (x1 - x0);
double intersectionY = y0 + ua * (y1 - y0);
return new mxPoint(intersectionX, intersectionY);
}
// No intersection
return null;
}
/**
* Sorts the given cells according to the order in the cell hierarchy.
*/
public static Object[] sortCells(Object[] cells, final boolean ascending)
{
return sortCells(Arrays.asList(cells), ascending).toArray();
}
/**
* Sorts the given cells according to the order in the cell hierarchy.
*/
public static Collection