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) { AbstractAction newAction = 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())); } }; newAction.putValue(Action.SHORT_DESCRIPTION, action.getValue(Action.SHORT_DESCRIPTION)); return newAction; } /** * * @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; } }