/* * @(#)IconExample.java 1.0 28-SEPT-04 * * Copyright (c) 2001-2004, Dean Mao All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. - Redistributions in * binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. - Neither the name of JGraph nor * the names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ package org.jgraph.example; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.EventObject; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.AbstractCellEditor; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import org.jgraph.JGraph; import org.jgraph.graph.CellView; import org.jgraph.graph.CellViewRenderer; import org.jgraph.graph.DefaultGraphCellEditor; import org.jgraph.graph.GraphCellEditor; import org.jgraph.graph.GraphConstants; import org.jgraph.graph.VertexView; /** * The cell view modifies the center point of the vertex such that it is * relative to the icon rather than the entire vertex. The perimeter * * @author Dean Mao * @created Sep 28, 2004 */ public class JGraphIconView extends VertexView { private transient MyMultiLinedEditor editor = new MyMultiLinedEditor(); protected static transient IconRenderer viewRenderer = new IconRenderer(); public JGraphIconView() { super(); } public JGraphIconView(Object cell) { super(cell); } public GraphCellEditor getEditor() { return editor; } private boolean isMouseOver; /** * The mouse over boolean is here so that we could potentially render the * icon differently when the mouse is hovering above the vertex. * * @return whether or not the mouse is over the vertex */ public boolean isMouseOver() { return isMouseOver; } public void setMouseOver(boolean isMouseOver) { this.isMouseOver = isMouseOver; } /** * The center point of the vertex should be in the middle of the icon * instead of the middle of the icon/description duo. The reason we must * calculate this is so that edges know where to point. We want to make the * edges look like they are pointing to an icon from an icon. */ public Point2D getCenterPoint() { Icon icon = GraphConstants.getIcon(getAllAttributes()); double iconWidth = icon.getIconWidth(); double iconHeight = icon.getIconHeight(); return new Point2D.Double(getBounds().getX() + iconWidth / 2, getBounds().getY() + iconHeight / 2); } /** * We don't want to calculate a perimeter point around the entire vertex. * Only the perimeter around the icon will be calculated. This calculation * is generic for elliptical perimeters. */ public Point2D getPerimeterPoint(Point2D source, Point2D p) { Rectangle2D bounds = this.getBounds(); Icon icon = GraphConstants.getIcon(getAllAttributes()); double iconWidth = icon.getIconWidth(); double iconHeight = icon.getIconHeight(); double x = bounds.getX(); double y = bounds.getY(); double a = iconWidth / 2; double b = iconHeight / 2; double eccentricity = Math.sqrt(1 - ((b / a) * (b / a))); double width = bounds.getWidth(); double height = viewRenderer.getIconDisplay().getPreferredSize() .getHeight(); double xCenter = (x + (width / 2)); double yCenter = (y + (height / 2)); double dx = p.getX() - xCenter; double dy = p.getY() - yCenter; double theta = Math.atan2(dy, dx); double eSquared = eccentricity * eccentricity; double rPrime = a * Math .sqrt((1 - eSquared) / (1 - (eSquared * (Math.cos(theta) * Math .cos(theta))))); double ex = rPrime * Math.cos(theta); double ey = rPrime * Math.sin(theta); return new Point2D.Double(ex + xCenter, ey + yCenter); } public CellViewRenderer getRenderer() { return viewRenderer; } class MyMultiLinedEditor extends MultiLinedEditor { /** * We offset the multi-lined editor by the height of the icon so that * the multi-lined editor appears directly over the description text * that we are editing. */ public Component getGraphCellEditorComponent(JGraph graph, Object cell, boolean isSelected) { Component component = super.getGraphCellEditorComponent(graph, cell, isSelected); Dimension dim = ((IconRenderer) JGraphIconView.this .getRendererComponent(graph, false, false, false)) .getIconDisplay().getPreferredSize(); offsetY = (int) dim.getHeight(); return component; } } /** * This is a combination renderer that displays both an icon and a * description under the icon. The description is html rendered so that it * can be multi-lined. * * @author Dean Mao * @created Sep 28, 2004 */ public static class IconRenderer extends JComponent implements CellViewRenderer { private IconDisplay iconDisplay; private DescriptionTextArea textRenderer; public IconRenderer() { super(); iconDisplay = new IconDisplay(); textRenderer = new DescriptionTextArea("text/html", ""); textRenderer.setOpaque(true); iconDisplay.setOpaque(false); GridBagLayout gbl = new GridBagLayout(); setLayout(gbl); GridBagConstraints defaultRendererConstraint = new GridBagConstraints(); defaultRendererConstraint.gridx = 0; defaultRendererConstraint.gridy = 0; defaultRendererConstraint.gridwidth = 1; defaultRendererConstraint.gridheight = 1; defaultRendererConstraint.weightx = 1; defaultRendererConstraint.weighty = 0; defaultRendererConstraint.fill = GridBagConstraints.HORIZONTAL; // add icon renderer: gbl.setConstraints(iconDisplay, defaultRendererConstraint); this.add(iconDisplay); GridBagConstraints textRendererConstraint = new GridBagConstraints(); textRendererConstraint.gridx = 0; textRendererConstraint.gridy = 1; textRendererConstraint.gridwidth = 1; textRendererConstraint.gridheight = GridBagConstraints.REMAINDER; textRendererConstraint.weightx = 1; textRendererConstraint.weighty = 1; textRendererConstraint.fill = GridBagConstraints.BOTH; textRendererConstraint.insets = new Insets(3, 3, 3, 3); // add description renderer: gbl.setConstraints(textRenderer, textRendererConstraint); this.add(textRenderer); } public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); dim.setSize(dim.getWidth() + 40, dim.getHeight()); return dim; } private JGraphIconView view; private boolean isSelected; private boolean isFocused; private boolean isPreview; public java.awt.Component getRendererComponent(JGraph graph, CellView view, boolean sel, boolean focus, boolean preview) { if (view instanceof JGraphIconView) { if (graph.getEditingCell() != view.getCell()) { setBackground(Color.white); } this.view = (JGraphIconView) view; this.isSelected = sel; this.isFocused = focus; this.isPreview = preview; iconDisplay.setIcon(GraphConstants.getIcon(view .getAllAttributes())); textRenderer.setDescription(graph.convertValueToString(view)); return this; } return null; } public JGraphIconView getView() { return this.view; } public boolean isSelected() { return this.isSelected; } public boolean isFocused() { return this.isFocused; } public boolean isPreview() { return this.isPreview; } public IconDisplay getIconDisplay() { return this.iconDisplay; } } /** * This JComponent only displays an icon as the upper part of the * icon/description duo. * * @author Dean Mao * @created Sep 28, 2004 */ public static class IconDisplay extends JLabel { public IconDisplay() { super(); setVerticalAlignment(JLabel.CENTER); setHorizontalAlignment(JLabel.CENTER); setHorizontalTextPosition(JLabel.CENTER); setVerticalTextPosition(JLabel.BOTTOM); setFont(UIManager.getFont("Tree.font")); setForeground(UIManager.getColor("Tree.textForeground")); setBackground(UIManager.getColor("Tree.textBackground")); } public Dimension getMinimumSize() { Dimension dim = super.getMinimumSize(); dim.setSize(dim.getWidth(), dim.getHeight() + 2); return dim; } public Dimension getPreferredSize() { return this.getMinimumSize(); } public void paint(Graphics g) { setBackground(Color.white); setBorder(null); // preview mode is "true" when we are dragging the component in the // graph if (!((IconRenderer) this.getParent()).isPreview()) { // paint the icon only in preview mode. super.paint(g); } else { // This is how we will paint the component when we are in // preview // mode (dragging component around). g.setColor(Color.BLACK); Dimension d = getSize(); g.drawOval((int) (d.width / 2 - 20), (int) (d.height / 2 - 20), 40, 40); } } } /** * This JComponent only displays html renderered text as the lower part of * the icon/description duo. * * @author Dean Mao * @created Sep 28, 2004 */ public static class DescriptionTextArea extends JEditorPane { public DescriptionTextArea(String type, String text) { super(type, text); } private String description; public void setDescription(String description) { this.description = description; setText(this.description); } public void setText(String text) { // display only the description if not blank if (text != null && !text.equals("")) { // make new lines appear as line breaks in the html renderered // text text = text.replaceAll("\n", "<br>"); super .setText("<center><font color=\"#337733\" face=Arial size=-1>" + text + "</font></center>"); } else { super.setText(""); } } } public static class MultiLinedEditor extends DefaultGraphCellEditor { public class RealCellEditor extends AbstractCellEditor implements GraphCellEditor { JTextArea editorComponent = new JTextArea(); public RealCellEditor() { editorComponent.setBorder(UIManager .getBorder("Tree.editorBorder")); editorComponent.setLineWrap(true); editorComponent.setWrapStyleWord(true); // substitute a JTextArea's VK_ENTER action with our own that // will stop an edit. editorComponent.getInputMap(JComponent.WHEN_FOCUSED).put( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enter"); editorComponent.getInputMap(JComponent.WHEN_FOCUSED).put( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK), "shiftEnter"); editorComponent.getInputMap(JComponent.WHEN_FOCUSED).put( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK), "metaEnter"); editorComponent.getInputMap(JComponent.WHEN_FOCUSED).put( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enter"); editorComponent.getActionMap().put("enter", new AbstractAction() { public void actionPerformed(ActionEvent e) { stopCellEditing(); } }); AbstractAction newLineAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { Document doc = editorComponent.getDocument(); try { doc.insertString( editorComponent.getCaretPosition(), "\n", null); } catch (BadLocationException e1) { e1.printStackTrace(); } } }; editorComponent.getActionMap().put("shiftEnter", newLineAction); editorComponent.getActionMap().put("metaEnter", newLineAction); } public Component getGraphCellEditorComponent(JGraph graph, Object value, boolean isSelected) { editorComponent.setText(value.toString()); editorComponent.selectAll(); return editorComponent; } public Object getCellEditorValue() { return editorComponent.getText(); } public boolean stopCellEditing() { // set the size of a vertex to that of an editor. CellView view = graph.getGraphLayoutCache().getMapping( graph.getEditingCell(), false); Map map = view.getAllAttributes(); Rectangle2D cellBounds = GraphConstants.getBounds(map); Rectangle editingBounds = editorComponent.getBounds(); GraphConstants.setBounds(map, new Rectangle((int) cellBounds .getX(), (int) cellBounds.getY(), editingBounds.width, editingBounds.height)); return super.stopCellEditing(); } public boolean shouldSelectCell(EventObject event) { editorComponent.requestFocus(); return super.shouldSelectCell(event); } } public MultiLinedEditor() { super(); } /** * Overriding this in order to set the size of an editor to that of an * edited view. */ public Component getGraphCellEditorComponent(JGraph graph, Object cell, boolean isSelected) { Component component = super.getGraphCellEditorComponent(graph, cell, isSelected); // set the size of an editor to that of a view CellView view = graph.getGraphLayoutCache().getMapping(cell, false); Rectangle2D tmp = view.getBounds(); editingComponent.setBounds((int) tmp.getX(), (int) tmp.getY(), (int) tmp.getWidth(), (int) tmp.getHeight()); // I have to set a font here instead of in the // RealCellEditor.getGraphCellEditorComponent() because // I don't know what cell is being edited when in the // RealCellEditor.getGraphCellEditorComponent(). Font font = GraphConstants.getFont(view.getAllAttributes()); editingComponent.setFont((font != null) ? font : graph.getFont()); return component; } protected GraphCellEditor createGraphCellEditor() { return new MultiLinedEditor.RealCellEditor(); } /** * Overriting this so that I could modify an eiditor container. see * http://sourceforge.net/forum/forum.php?thread_id=781479&forum_id=140880 */ protected Container createContainer() { return new MultiLinedEditor.ModifiedEditorContainer(); } class ModifiedEditorContainer extends EditorContainer { public void doLayout() { super.doLayout(); // substract 2 pixels that were added to the preferred size of // the container for the border. Dimension cSize = getSize(); Dimension dim = editingComponent.getSize(); editingComponent.setSize(dim.width - 2, dim.height); // reset container's size based on a potentially new preferred // size of a real editor. setSize(cSize.width, getPreferredSize().height); } } } }