package com.mxgraph.examples.swing.editor;
import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.File;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import com.mxgraph.layout.mxCircleLayout;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.layout.mxEdgeLabelLayout;
import com.mxgraph.layout.mxIGraphLayout;
import com.mxgraph.layout.mxOrganicLayout;
import com.mxgraph.layout.mxParallelEdgeLayout;
import com.mxgraph.layout.mxPartitionLayout;
import com.mxgraph.layout.mxStackLayout;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.mxGraphOutline;
import com.mxgraph.swing.handler.mxKeyboardHandler;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.util.mxMorphing;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxResources;
import com.mxgraph.util.mxUndoManager;
import com.mxgraph.util.mxUndoableEdit;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
import com.mxgraph.view.mxGraph;
public class BasicGraphEditor extends JPanel
{
/**
*
*/
private static final long serialVersionUID = -6561623072112577140L;
/**
* Adds required resources for i18n
*/
static
{
try
{
mxResources.add("com/mxgraph/examples/swing/resources/editor");
}
catch (Exception e)
{
// ignore
}
}
/**
*
*/
protected mxGraphComponent graphComponent;
/**
*
*/
protected mxGraphOutline graphOutline;
/**
*
*/
protected JTabbedPane libraryPane;
/**
*
*/
protected mxUndoManager undoManager;
/**
*
*/
protected String appTitle;
/**
*
*/
protected JLabel statusBar;
/**
*
*/
protected File currentFile;
/**
* Flag indicating whether the current graph has been modified
*/
protected boolean modified = false;
/**
*
*/
protected mxRubberband rubberband;
/**
*
*/
protected mxKeyboardHandler keyboardHandler;
/**
*
*/
protected mxIEventListener undoHandler = new mxIEventListener()
{
public void invoke(Object source, mxEventObject evt)
{
undoManager.undoableEditHappened((mxUndoableEdit) evt
.getProperty("edit"));
}
};
/**
*
*/
protected mxIEventListener changeTracker = new mxIEventListener()
{
public void invoke(Object source, mxEventObject evt)
{
setModified(true);
}
};
/**
*
*/
public BasicGraphEditor(String appTitle, mxGraphComponent component)
{
// Stores and updates the frame title
this.appTitle = appTitle;
// Stores a reference to the graph and creates the command history
graphComponent = component;
final mxGraph graph = graphComponent.getGraph();
undoManager = createUndoManager();
// Do not change the scale and translation after files have been loaded
graph.setResetViewOnRootChange(false);
// Updates the modified flag if the graph model changes
graph.getModel().addListener(mxEvent.CHANGE, changeTracker);
// Adds the command history to the model and view
graph.getModel().addListener(mxEvent.UNDO, undoHandler);
graph.getView().addListener(mxEvent.UNDO, undoHandler);
// Keeps the selection in sync with the command history
mxIEventListener undoHandler = new mxIEventListener()
{
public void invoke(Object source, mxEventObject evt)
{
List<mxUndoableChange> changes = ((mxUndoableEdit) evt
.getProperty("edit")).getChanges();
graph.setSelectionCells(graph
.getSelectionCellsForChanges(changes));
}
};
undoManager.addListener(mxEvent.UNDO, undoHandler);
undoManager.addListener(mxEvent.REDO, undoHandler);
// Creates the graph outline component
graphOutline = new mxGraphOutline(graphComponent);
// Creates the library pane that contains the tabs with the palettes
libraryPane = new JTabbedPane();
// Creates the inner split pane that contains the library with the
// palettes and the graph outline on the left side of the window
JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
libraryPane, graphOutline);
inner.setDividerLocation(320);
inner.setResizeWeight(1);
inner.setDividerSize(6);
inner.setBorder(null);
// Creates the outer split pane that contains the inner split pane and
// the graph component on the right side of the window
JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inner,
graphComponent);
outer.setOneTouchExpandable(true);
outer.setDividerLocation(200);
outer.setDividerSize(6);
outer.setBorder(null);
// Creates the status bar
statusBar = createStatusBar();
// Display some useful information about repaint events
installRepaintListener();
// Puts everything together
setLayout(new BorderLayout());
add(outer, BorderLayout.CENTER);
add(statusBar, BorderLayout.SOUTH);
installToolBar();
// Installs rubberband selection and handling for some special
// keystrokes such as F2, Control-C, -V, X, A etc.
installHandlers();
installListeners();
updateTitle();
}
/**
*
*/
protected mxUndoManager createUndoManager()
{
return new mxUndoManager();
}
/**
*
*/
protected void installHandlers()
{
rubberband = new mxRubberband(graphComponent);
keyboardHandler = new EditorKeyboardHandler(graphComponent);
}
/**
*
*/
protected void installToolBar()
{
add(new EditorToolBar(this, JToolBar.HORIZONTAL), BorderLayout.NORTH);
}
/**
*
*/
protected JLabel createStatusBar()
{
JLabel statusBar = new JLabel(mxResources.get("ready"));
statusBar.setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4));
return statusBar;
}
/**
*
*/
protected void installRepaintListener()
{
graphComponent.getGraph().addListener(mxEvent.REPAINT,
new mxIEventListener()
{
public void invoke(Object source, mxEventObject evt)
{
String buffer = (graphComponent.getTripleBuffer() != null) ? ""
: " (unbuffered)";
mxRectangle dirty = (mxRectangle) evt
.getProperty("region");
if (dirty == null)
{
status("Repaint all" + buffer);
}
else
{
status("Repaint: x=" + (int) (dirty.getX()) + " y="
+ (int) (dirty.getY()) + " w="
+ (int) (dirty.getWidth()) + " h="
+ (int) (dirty.getHeight()) + buffer);
}
}
});
}
/**
*
*/
public EditorPalette insertPalette(String title)
{
final EditorPalette palette = new EditorPalette();
final JScrollPane scrollPane = new JScrollPane(palette);
scrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
libraryPane.add(title, scrollPane);
// Updates the widths of the palettes if the container size changes
libraryPane.addComponentListener(new ComponentAdapter()
{
/**
*
*/
public void componentResized(ComponentEvent e)
{
int w = scrollPane.getWidth()
- scrollPane.getVerticalScrollBar().getWidth();
palette.setPreferredWidth(w);
}
});
return palette;
}
/**
*
*/
protected void mouseWheelMoved(MouseWheelEvent e)
{
if (e.getWheelRotation() < 0)
{
graphComponent.zoomIn();
}
else
{
graphComponent.zoomOut();
}
status(mxResources.get("scale") + ": "
+ (int) (100 * graphComponent.getGraph().getView().getScale())
+ "%");
}
/**
*
*/
protected void showOutlinePopupMenu(MouseEvent e)
{
Point pt = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(),
graphComponent);
JCheckBoxMenuItem item = new JCheckBoxMenuItem(
mxResources.get("magnifyPage"));
item.setSelected(graphOutline.isFitPage());
item.addActionListener(new ActionListener()
{
/**
*
*/
public void actionPerformed(ActionEvent e)
{
graphOutline.setFitPage(!graphOutline.isFitPage());
graphOutline.repaint();
}
});
JCheckBoxMenuItem item2 = new JCheckBoxMenuItem(
mxResources.get("showLabels"));
item2.setSelected(graphOutline.isDrawLabels());
item2.addActionListener(new ActionListener()
{
/**
*
*/
public void actionPerformed(ActionEvent e)
{
graphOutline.setDrawLabels(!graphOutline.isDrawLabels());
graphOutline.repaint();
}
});
JCheckBoxMenuItem item3 = new JCheckBoxMenuItem(
mxResources.get("buffering"));
item3.setSelected(graphOutline.isTripleBuffered());
item3.addActionListener(new ActionListener()
{
/**
*
*/
public void actionPerformed(ActionEvent e)
{
graphOutline.setTripleBuffered(!graphOutline.isTripleBuffered());
graphOutline.repaint();
}
});
JPopupMenu menu = new JPopupMenu();
menu.add(item);
menu.add(item2);
menu.add(item3);
menu.show(graphComponent, pt.x, pt.y);
e.consume();
}
/**
*
*/
protected void showGraphPopupMenu(MouseEvent e)
{
Point pt = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(),
graphComponent);
EditorPopupMenu menu = new EditorPopupMenu(BasicGraphEditor.this);
menu.show(graphComponent, pt.x, pt.y);
e.consume();
}
/**
*
*/
protected void mouseLocationChanged(MouseEvent e)
{
status(e.getX() + ", " + e.getY());
}
/**
*
*/
protected void installListeners()
{
// Installs mouse wheel listener for zooming
MouseWheelListener wheelTracker = new MouseWheelListener()
{
/**
*
*/
public void mouseWheelMoved(MouseWheelEvent e)
{
if (e.getSource() instanceof mxGraphOutline
|| e.isControlDown())
{
BasicGraphEditor.this.mouseWheelMoved(e);
}
}
};
// Handles mouse wheel events in the outline and graph component
graphOutline.addMouseWheelListener(wheelTracker);
graphComponent.addMouseWheelListener(wheelTracker);
// Installs the popup menu in the outline
graphOutline.addMouseListener(new MouseAdapter()
{
/**
*
*/
public void mousePressed(MouseEvent e)
{
// Handles context menu on the Mac where the trigger is on mousepressed
mouseReleased(e);
}
/**
*
*/
public void mouseReleased(MouseEvent e)
{
if (e.isPopupTrigger())
{
showOutlinePopupMenu(e);
}
}
});
// Installs the popup menu in the graph component
graphComponent.getGraphControl().addMouseListener(new MouseAdapter()
{
/**
*
*/
public void mousePressed(MouseEvent e)
{
// Handles context menu on the Mac where the trigger is on mousepressed
mouseReleased(e);
}
/**
*
*/
public void mouseReleased(MouseEvent e)
{
if (e.isPopupTrigger())
{
showGraphPopupMenu(e);
}
}
});
// Installs a mouse motion listener to display the mouse location
graphComponent.getGraphControl().addMouseMotionListener(
new MouseMotionListener()
{
/*
* (non-Javadoc)
* @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent e)
{
mouseLocationChanged(e);
}
/*
* (non-Javadoc)
* @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
*/
public void mouseMoved(MouseEvent e)
{
mouseDragged(e);
}
});
}
/**
*
*/
public void setCurrentFile(File file)
{
File oldValue = currentFile;
currentFile = file;
firePropertyChange("currentFile", oldValue, file);
if (oldValue != file)
{
updateTitle();
}
}
/**
*
*/
public File getCurrentFile()
{
return currentFile;
}
/**
*
* @param modified
*/
public void setModified(boolean modified)
{
boolean oldValue = this.modified;
this.modified = modified;
firePropertyChange("modified", oldValue, modified);
if (oldValue != modified)
{
updateTitle();
}
}
/**
*
* @return whether or not the current graph has been modified
*/
public boolean isModified()
{
return modified;
}
/**
*
*/
public mxGraphComponent getGraphComponent()
{
return graphComponent;
}
/**
*
*/
public mxGraphOutline getGraphOutline()
{
return graphOutline;
}
/**
*
*/
public JTabbedPane getLibraryPane()
{
return libraryPane;
}
/**
*
*/
public mxUndoManager getUndoManager()
{
return undoManager;
}
/**
*
* @param name
* @param action
* @return a new Action bound to the specified string name
*/
public Action bind(String name, final Action action)
{
return bind(name, action, null);
}
/**
*
* @param name
* @param action
* @return a new Action bound to the specified string name and icon
*/
@SuppressWarnings("serial")
public Action bind(String name, final Action action, String iconUrl)
{
return new AbstractAction(name, (iconUrl != null) ? new ImageIcon(
BasicGraphEditor.class.getResource(iconUrl)) : null)
{
public void actionPerformed(ActionEvent e)
{
action.actionPerformed(new ActionEvent(getGraphComponent(), e
.getID(), e.getActionCommand()));
}
};
}
/**
*
* @param msg
*/
public void status(String msg)
{
statusBar.setText(msg);
}
/**
*
*/
public void updateTitle()
{
JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);
if (frame != null)
{
String title = (currentFile != null) ? currentFile
.getAbsolutePath() : mxResources.get("newDiagram");
if (modified)
{
title += "*";
}
frame.setTitle(title + " - " + appTitle);
}
}
/**
*
*/
public void about()
{
JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);
if (frame != null)
{
EditorAboutFrame about = new EditorAboutFrame(frame);
about.setModal(true);
// Centers inside the application frame
int x = frame.getX() + (frame.getWidth() - about.getWidth()) / 2;
int y = frame.getY() + (frame.getHeight() - about.getHeight()) / 2;
about.setLocation(x, y);
// Shows the modal dialog and waits
about.setVisible(true);
}
}
/**
*
*/
public void exit()
{
JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);
if (frame != null)
{
frame.dispose();
}
}
/**
*
*/
public void setLookAndFeel(String clazz)
{
JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);
if (frame != null)
{
try
{
UIManager.setLookAndFeel(clazz);
SwingUtilities.updateComponentTreeUI(frame);
// Needs to assign the key bindings again
keyboardHandler = new EditorKeyboardHandler(graphComponent);
}
catch (Exception e1)
{
e1.printStackTrace();
}
}
}
/**
*
*/
public JFrame createFrame(JMenuBar menuBar)
{
JFrame frame = new JFrame();
frame.getContentPane().add(this);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setJMenuBar(menuBar);
frame.setSize(870, 640);
// Updates the frame title
updateTitle();
return frame;
}
/**
* Creates an action that executes the specified layout.
*
* @param key Key to be used for getting the label from mxResources and also
* to create the layout instance for the commercial graph editor example.
* @return an action that executes the specified layout
*/
@SuppressWarnings("serial")
public Action graphLayout(final String key, boolean animate)
{
final mxIGraphLayout layout = createLayout(key, animate);
if (layout != null)
{
return new AbstractAction(mxResources.get(key))
{
public void actionPerformed(ActionEvent e)
{
final mxGraph graph = graphComponent.getGraph();
Object cell = graph.getSelectionCell();
if (cell == null
|| graph.getModel().getChildCount(cell) == 0)
{
cell = graph.getDefaultParent();
}
graph.getModel().beginUpdate();
try
{
long t0 = System.currentTimeMillis();
layout.execute(cell);
status("Layout: " + (System.currentTimeMillis() - t0)
+ " ms");
}
finally
{
mxMorphing morph = new mxMorphing(graphComponent, 20,
1.2, 20);
morph.addListener(mxEvent.DONE, new mxIEventListener()
{
public void invoke(Object sender, mxEventObject evt)
{
graph.getModel().endUpdate();
}
});
morph.startAnimation();
}
}
};
}
else
{
return new AbstractAction(mxResources.get(key))
{
public void actionPerformed(ActionEvent e)
{
JOptionPane.showMessageDialog(graphComponent,
mxResources.get("noLayout"));
}
};
}
}
/**
* Creates a layout instance for the given identifier.
*/
protected mxIGraphLayout createLayout(String ident, boolean animate)
{
mxIGraphLayout layout = null;
if (ident != null)
{
mxGraph graph = graphComponent.getGraph();
if (ident.equals("verticalHierarchical"))
{
layout = new mxHierarchicalLayout(graph);
}
else if (ident.equals("horizontalHierarchical"))
{
layout = new mxHierarchicalLayout(graph, JLabel.WEST);
}
else if (ident.equals("verticalTree"))
{
layout = new mxCompactTreeLayout(graph, false);
}
else if (ident.equals("horizontalTree"))
{
layout = new mxCompactTreeLayout(graph, true);
}
else if (ident.equals("parallelEdges"))
{
layout = new mxParallelEdgeLayout(graph);
}
else if (ident.equals("placeEdgeLabels"))
{
layout = new mxEdgeLabelLayout(graph);
}
else if (ident.equals("organicLayout"))
{
layout = new mxOrganicLayout(graph);
}
if (ident.equals("verticalPartition"))
{
layout = new mxPartitionLayout(graph, false)
{
/**
* Overrides the empty implementation to return the size of the
* graph control.
*/
public mxRectangle getContainerSize()
{
return graphComponent.getLayoutAreaSize();
}
};
}
else if (ident.equals("horizontalPartition"))
{
layout = new mxPartitionLayout(graph, true)
{
/**
* Overrides the empty implementation to return the size of the
* graph control.
*/
public mxRectangle getContainerSize()
{
return graphComponent.getLayoutAreaSize();
}
};
}
else if (ident.equals("verticalStack"))
{
layout = new mxStackLayout(graph, false)
{
/**
* Overrides the empty implementation to return the size of the
* graph control.
*/
public mxRectangle getContainerSize()
{
return graphComponent.getLayoutAreaSize();
}
};
}
else if (ident.equals("horizontalStack"))
{
layout = new mxStackLayout(graph, true)
{
/**
* Overrides the empty implementation to return the size of the
* graph control.
*/
public mxRectangle getContainerSize()
{
return graphComponent.getLayoutAreaSize();
}
};
}
else if (ident.equals("circleLayout"))
{
layout = new mxCircleLayout(graph);
}
}
return layout;
}
}