/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

package demolayoutgallery;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Point2D;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import demolayoutgallery.base.Ellipse;
import demolayoutgallery.base.HelpHandler;
import demolayoutgallery.base.ImageUtil;
import demolayoutgallery.base.LayoutApp;
import demolayoutgallery.base.Link;
import demolayoutgallery.base.ManagerView;
import demolayoutgallery.base.OverviewHandler;
import demolayoutgallery.base.ScrollHTMLTextArea;
import demolayoutgallery.base.SplineLink;
import ilog.views.IlvApplyObject;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvGraphicVector;
import ilog.views.IlvLinkConnector;
import ilog.views.IlvLinkImage;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.animation.IlvManagerAnimator;
import ilog.views.event.ManagerSelectionChangedEvent;
import ilog.views.graphic.IlvReliefRectangle;
import ilog.views.graphic.IlvSplineLinkImage;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.graphlayout.IlvGraphLayout;
import ilog.views.graphlayout.IlvGrapherAdapter;
import ilog.views.graphlayout.bus.IlvBusLayout;
import ilog.views.graphlayout.circular.IlvCircularLayout;
import ilog.views.graphlayout.grid.IlvGridLayout;
import ilog.views.graphlayout.hierarchical.IlvHierarchicalLayout;
import ilog.views.graphlayout.link.IlvLinkLayout;
import ilog.views.graphlayout.topologicalmesh.IlvTopologicalMeshLayout;
import ilog.views.graphlayout.tree.IlvTreeLayout;
import ilog.views.graphlayout.uniformlengthedges.IlvUniformLengthEdgesLayout;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.linkconnector.IlvClippingLinkConnector;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.java2d.IlvMultipleGradientPaint;
import ilog.views.util.java2d.IlvRadialGradientPaint;

/**
 * The main class IlvLayoutGalleryApp. It demonstrates the graph layout
 * functionality provided in JViews. It also demonstrates the support for nested
 * graphs, the labeling algorithm, and the intergraph link routing mechanism.
 */
public final class IlvLayoutGalleryApp extends LayoutApp {
  // --------------------------------------------------------------------------
  // Constants

  // The property name to store the background color of nodes temporarily
  private static final String BACKGROUND_COLOR = "__BackgroundColor";
  // if true, company's logo is added to the toolbar
  private static final boolean WITH_LOGO = false;

  // if true, the demo starts in self-running mode.
  // If true, the "delay" argument passed on the command line is
  // used. However, if no delay argument is passed, the default
  // delay = 1500 ms is used.
  private static final boolean SELF_RUNNING = false;

  // the default sizes of various windows (for the main window, this is
  // used only if STANDALONE_MAIN_FRAME is true)
  private static final int MAIN_WINDOW_DEFAULT_WIDTH = WITH_LOGO ? 750 : 750;
  private static final int MAIN_WINDOW_DEFAULT_HEIGHT = WITH_LOGO ? 700 : 700;
  private static final int HELP_WINDOW_DEFAULT_WIDTH = 500;
  private static final int HELP_WINDOW_DEFAULT_HEIGHT = 300;
  private static final int OVERVIEW_MIN_SIZE = 100;
  private static final int OVERVIEW_MAX_SIZE = 170;
  private static final int TEXT_AREA_HEIGHT = 80;

  // the path to the images of the buttons. Notice this is
  // different than the path used by the parent class.
  private static final String PATH_TO_LOCAL_BUTTON_IMAGES = "gif/";

  // the interaction mode
  private static final int SELECT_MODE = 1;
  private static final int EXPAND_MODE = 2;
  private static final int ZOOMBOX_MODE = 3;

  // --------------------------------------------------------------------------
  // Data members related to the graphs

  // whether this is the very first reading of a graph
  private boolean firstTimeRead = true;

  // the grapher
  private IlvGrapher grapher;

  // The corresponding scroll manager view
  private IlvJScrollManagerView scrollManView;

  // we need to handle the autolayout of link layout at various places,
  // therefore faster access to the link layout.
  private IlvLinkLayout linkLayout;

  // The set of layouts
  private LayoutDescriptor[] layoutDescriptions;

  // the currently selected layout
  private IlvGraphLayout currentLayout;

  // the base name (without directory nor extension) of the
  // file containing the currently loaded graph
  private String currentGraphFileBaseName;

  // The link clip provider for link clipping
  private LinkClipProvider linkClipProvider = new LinkClipProvider();

  // the grapher animator that performs the animation
  IlvManagerAnimator mgranim;

  private AbstractGraphLayoutSupport flatGraphDemoSupport;
  private AbstractGraphLayoutSupport nestedGraphDemoSupport;

  // The number of nodes and links in the grapher
  private int numberOfNodes = 0;
  private int numberOfLinks = 0;
  private int numberOfSubgraphs = 0;

  // --------------------------------------------------------------------------
  // The demo support class
  private AbstractGraphLayoutSupport layoutSupport;

  // --------------------------------------------------------------------------
  // Data members related to the GUI

  // the GUI has 3 parts: a top panel, a bottom panel, and in the middle the
  // scroll manager view.

  // the main frame
  private JFrame mainFrame;
  // the main container
  private Container mainContainer;

  // the top and bottom panel
  private JPanel topPanel;
  private JPanel bottomPanel;

  // the interactor buttons
  private AbstractButton layoutButton, randomizeButton;
  private AbstractButton selectButton, expandButton;
  private AbstractButton overviewButton;
  private AbstractButton animationButton, helpButton;

  // the interactor for the expandButton
  private ExpandInteractor expandInteractor;

  // the cursor for the expand operation
  private Cursor expandIdleCursor;
  private Cursor expandWorkCursor;

  // the chooser for layout and graph size
  private JComboBox<String> sizeChooser, layoutChooser;

  // the overview handler
  public OverviewHandler overviewHandler;

  // the help window handler
  public HelpHandler helpHandler;

  // flags to be able to restore correctly the visibility when the app
  // restarts after having been stopped
  private boolean helpVisibleWhenStop = true;
  private boolean overviewVisibleWhenStop = true;

  // the text area to show messages
  private ScrollHTMLTextArea textArea;

  // the image utilities
  private ImageUtil imageUtil;

  // --------------------------------------------------------------------------
  // Data members related to node shapes

  // constants for the node shapes
  private static final int RECTANGLE_NODE_SHAPE = 1;
  static final int SPHERE_NODE_SHAPE = 2;

  // standard size of terminal and rectangle nodes
  private IlvRect standardNodeRect = new IlvRect(0, 0, 35, 39);
  // standard size of sphere nodes
  private IlvRect standardSphereNodeRect = new IlvRect(0, 0, 26, 26);

  // the standard color
  private Color standardColor = new Color(255, 180, 180);

  // the selected and the current node shape
  private int nodeShape = SPHERE_NODE_SHAPE;
  private int currentNodeShape = nodeShape;

  // whether we have the choice of the node shapes. Some sample graphs are
  // not suitable for any node shape
  private boolean noNodeShapeChoice = false;

  // --------------------------------------------------------------------------
  // Further flags

  // whether layout is running
  private boolean layoutRunning = false;

  // whether the GUI is enabled
  private boolean isGUIenabled = true;

  // whether the graph can use animation
  private boolean animationAllowed = true;
  private boolean animation = true;

  // whether the graph can be randomized
  private boolean randomizeAllowed = true;

  // whether the layout can run automatic after layout
  private boolean automaticLayoutRun = true;

  // whether the demo is in autoplay mode
  private boolean autoPlay = false;

  // the delay (in millisec) after each automatic run in autoplay mode
  private long autoPlayDelay = 4000;

  // whether the help window should be shown at startup
  private static final boolean HELP_AT_START_UP = false;

  // --------------------------------------------------------------------------
  // Temporary data

  // temp. store whether link layout was in autolayout mode
  private boolean autoLayout = false;

  // temp. storage of the old cursor
  private Cursor oldCursor;

  // --------------------------------------------------------------------------
  // The real entry methods for this application

  /**
   * Initializes the demo.
   */
  Override
  public Container initDemo() {
    initLayouts();

    // create an image utitity for the image operations
    imageUtil = new ImageUtil(this);

    // local initialziation
    initLocal();

    // create and open the main window frame
    createMainWindow();

    // read the graph from the file
    if (!isAutoPlay()) {
      readGraph();
    }

    // show help
    if (firstTimeRead && HELP_AT_START_UP) {
      helpButton.setSelected(true);
      helpHandler.toggleHelp(true);
    }
    firstTimeRead = false;

    if (!isAutoPlay()) {
      // if necessary, perform layout
      if (isAutomaticLayoutRun())
        arrange(currentLayout, grapher, false);
    }

    // to avoid that the main window is behind the help window
    if (mainFrame != null)
      mainFrame.toFront();

    // if automatic play is required, do so
    if (isAutoPlay())
      autoPlay();

    return mainFrame != null ? mainFrame : mainContainer;
  }

  /**
   * Stops the application.
   */
  Override
  public void stop() {
    super.stop();

    // register the visibility of the help and overview frames,
    // to be able to restore them correctly if the app is restarted later.
    helpVisibleWhenStop =
        helpHandler != null && helpHandler.getFrame() != null && helpHandler.getFrame().isVisible();

    overviewVisibleWhenStop = overviewHandler != null && overviewHandler.getFrame() != null
        && overviewHandler.getFrame().isVisible();

    setVisibleFrames(false, true);
  }

  /**
   * Start the application.
   */
  Override
  public void start() {
    super.stop();
    setVisibleFrames(true, false);
  }

  private void setVisibleFrames(boolean visible, boolean force) {
    if (helpHandler != null && helpHandler.getFrame() != null
        && (force || !(visible && !helpVisibleWhenStop)) && !(visible && !HELP_AT_START_UP))
      setVisible(helpHandler.getFrame(), visible);

    if (overviewHandler != null && overviewHandler.getFrame() != null
        && (force || !(visible && !overviewVisibleWhenStop)))
      setVisible(overviewHandler.getFrame(), visible);

    if (mainFrame != null)
      setVisible(mainFrame, visible);
  }

  /**
   * Initializes the layouts.
   */
  private void initLayouts() {
    // Some settings on the layout algorithms:

    // the options for the tree layout that runs in radial mode
    IlvTreeLayout radialTreeLayout = new IlvTreeLayout();
    radialTreeLayout.setLayoutMode(IlvTreeLayout.RADIAL);
    radialTreeLayout.setSiblingOffset(20);
    radialTreeLayout.setBranchOffset(20);
    radialTreeLayout.setParentChildOffset(20);

    // increase a little the preferred link length
    IlvUniformLengthEdgesLayout ule = new IlvUniformLengthEdgesLayout();
    ule.setPreferredLinksLength(50);
    ule.setRespectNodeSizes(true);

    linkLayout = new IlvLinkLayout();
    // the link style for the orthogonal
    linkLayout.setGlobalLinkStyle(IlvLinkLayout.ORTHOGONAL_STYLE);
    linkLayout.setAutoLayout(false);

    layoutDescriptions = new LayoutDescriptor[12];
    int i = 0;
    layoutDescriptions[i++] = new LayoutDescriptor("Flow Chart", "flow-chart");
    layoutDescriptions[i++] = new LayoutDescriptor("Mixed Nesting", "mixed-nesting");
    layoutDescriptions[i++] = new LayoutDescriptor("Nested Tree", "nested-tree");
    layoutDescriptions[i++] =
        new LayoutDescriptor(new IlvHierarchicalLayout(), "Hierarchical", "hierarchical");
    layoutDescriptions[i++] = new LayoutDescriptor(new IlvTreeLayout(), "Tree", "tree");
    layoutDescriptions[i++] = new LayoutDescriptor(radialTreeLayout, "Radial Tree", "radial");
    layoutDescriptions[i++] = new LayoutDescriptor(ule, "Uniform Length Edges", "uniform");
    layoutDescriptions[i++] = new LayoutDescriptor(new IlvTopologicalMeshLayout(), "Mesh", "mesh");
    layoutDescriptions[i++] = new LayoutDescriptor(linkLayout, "Link Routing", "link");
    layoutDescriptions[i++] = new LayoutDescriptor(new IlvBusLayout(), "Bus", "bus");
    layoutDescriptions[i++] =
        new LayoutDescriptor(new IlvCircularLayout(), "Circular (Ring/Star)", "circular");
    layoutDescriptions[i++] = new LayoutDescriptor(new IlvGridLayout(), "Grid", "grid");
  }

  /**
   * Starts the demo as a Java application.
   */
  public static final void main(final String[] arg) {
    // Sun recommends that to put the entire GUI initialization into the
    // AWT thread
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        final IlvLayoutGalleryApp layoutDemo = new IlvLayoutGalleryApp();

        long delay = -1;

        if (arg.length == 1) {
          // Passing the delay argument is interpreted as a request for auto
          // play
          try {
            delay = Math.max(0, Long.parseLong(arg[0]));
          } catch (NumberFormatException ex) {
            System.err.println("bad command line argument: " + arg[0]);
            System.exit(0);
          }
        } else if (SELF_RUNNING)
          delay = 1500; // default value (ms)

        if (delay >= 0) {
          layoutDemo.setAutoPlay(true);
          layoutDemo.setAutoPlayDelay(delay);
        }

        layoutDemo.init();
        if (!layoutDemo.isAutoPlay() && layoutDemo.mainFrame != null)
          setVisible(layoutDemo.mainFrame, true);
      }
    });
  }

  // --------------------------------------------------------------------------
  // Accessors

  /**
   * Returns the top-level grapher.
   */
  public IlvGrapher getGrapher() {
    return grapher;
  }

  /**
   * Returns the manager view.
   */
  public IlvManagerView getManagerView() {
    return mgrview;
  }

  /**
   * Returns the scroll manager view.
   */
  public IlvJScrollManagerView getScrollManagerView() {
    return scrollManView;
  }

  /**
   * Returns the manager animator.
   */
  public IlvManagerAnimator getManagerAnimator() {
    return mgranim;
  }

  /**
   * Sets the "layoutRunning" flag.
   */
  public void setLayoutRunning(boolean running) {
    layoutRunning = running;
  }

  /**
   * Returns <code>true</code> if the current layout is currently running, and
   * <code>false</code> otherwise.
   */
  Override
  public boolean isLayoutRunning() {
    return layoutRunning;
  }

  /**
   * Returns the link layout.
   */
  public IlvLinkLayout getLinklayout() {
    return linkLayout;
  }

  /**
   * Returns the currently selected layout.
   */
  public IlvGraphLayout getCurrentLayout() {
    return currentLayout;
  }

  /**
   * Returns the base name (without directory nor extension) of the file
   * containing the currently loaded graph.
   */
  public String getCurrentGraphFileBaseName() {
    return currentGraphFileBaseName;
  }

  /**
   * Returns the number of nodes contained in the grapher.
   */
  public int getNumberOfNodes() {
    return numberOfNodes;
  }

  /**
   * Returns the number of links contained in the grapher.
   */
  public int getNumberOfLinks() {
    return numberOfLinks;
  }

  /**
   * Returns the number of subgraphs contained in the grapher.
   */
  public int getNumberOfSubgraphs() {
    return numberOfSubgraphs;
  }

  /**
   * Returns <code>true</code> if the GUI is enabled.
   */
  public boolean isGUIEnabled() {
    return isGUIenabled;
  }

  /**
   * Returns the standard color used for nodes.
   */
  public Color getStandardColor() {
    return standardColor;
  }

  // --------------------------------------------------------------------------
  // Initialization Routines / Creation if the GUI

  /**
   * Create the main window.
   */
  private void createMainWindow() {
    // open the frame, if appropriate
    mainFrame = new JFrame("JViews Diagrammer - Graph Layout Gallery");
    mainFrame.setSize(MAIN_WINDOW_DEFAULT_WIDTH, MAIN_WINDOW_DEFAULT_HEIGHT);
    Point p = getMainWindowLocation();
    mainFrame.setLocation(p.x, p.y);

    // create the grapher and its auxiliaries
    grapher = new IlvGrapher();
    mgranim = new IlvManagerAnimator(grapher);

    // create the GUI with buttons etc. including the scroll manager view
    mainContainer = mainFrame != null ? mainFrame.getContentPane() : new JPanel();
    mainContainer.setFont(new Font("sansserif", Font.PLAIN, 12));

    createGUI(mainContainer);

    // create the overview handler
    overviewHandler = new OverviewHandler(this, mgrview, overviewButton,
        getOverviewWindowLocation(), getOverviewWindowSize());

    // create the help handler
    helpHandler = new HelpHandler(this, getImage("gif/Background.gif"), helpButton,
        getHelpWindowLocation(), getHelpWindowSize()) {
      Override
      public URL convertHyperlinkURL(URL inputURL) {
        if (inputURL.toString().endsWith("#Description")) {
          int layoutIndex = layoutChooser.getSelectedIndex();
          int sizeIndex = sizeChooser.getSelectedIndex();
          String layoutFileNamePrefix = layoutDescriptions[layoutIndex].getLayoutFileNamePrefix();
          String sizeName = sizeChooser.getItemAt(sizeIndex);
          return getClass().getResource(
              "htmlgif/" + getGraphFileBaseName(layoutFileNamePrefix, sizeName) + ".html");
        }
        return inputURL;
      }
    };

    // attach the grapher to the layout instances
    // (only needed for flat graphs)
    IlvGrapherAdapter adapter = flatGraphDemoSupport.getGrapherAdapter();
    IlvGraphLayout layout = null;
    for (int i = 0; i < layoutDescriptions.length; i++) {
      layout = layoutDescriptions[i].getLayout();
      if (layout != null)
        layout.attach(adapter);
    }

    // finish the frame and open it
    if (mainFrame != null) {
      mainFrame.setIconImage(iconifizedIcon);
      mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
      mainFrame.addWindowListener(new WindowAdapter() {
        Override
        public void windowClosing(WindowEvent e) {
          exit();
        }
      });
      // frame.setVisible(true);
    }

  }

  /**
   * Setup the GUI. The GUI consists of 3 parts: - at the top panel, the graph
   * selector and the interactor buttons, - at the bottom, a small text area for
   * messages - in the middle, the scroll manager view
   */
  private void createGUI(Container mainContainer) {
    // create the expand cursor
    Dimension dd = Toolkit.getDefaultToolkit().getBestCursorSize(16, 16);
    Image cursorImage1, cursorImage2;
    if (dd.width > 16 || dd.height > 16) {
      cursorImage1 = getImage("gif/ExpandIdleCursor32x32.gif");
      cursorImage2 = getImage("gif/ExpandCursor32x32.gif");
    } else {
      cursorImage1 = getImage("gif/ExpandIdleCursor16x16.gif");
      cursorImage2 = getImage("gif/ExpandCursor16x16.gif");
    }
    expandIdleCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage1, new Point(3, 3),
        "ExpandIdleCursor");
    expandWorkCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage2, new Point(3, 3),
        "ExpandWorkCursor");

    // get the dimension
    Dimension d = mainContainer.getSize();

    // set the layout manager
    mainContainer.setLayout(new BorderLayout(0, 0));

    // first, the stuff in the middle:

    mgrview = new ManagerView(grapher) {
      Override
      public void afterVerifyTransformer() {
        if (!isAutoPlay()) {
          SwingUtilities.invokeLater(new Runnable() {
            Override
            public void run() {
              if (isBusy())
                return;
              if (mgrview.isMaximumZoom()) {
                if (mgrview.getInteractor() == zoomViewInteractor)
                  setInteractionMode(SELECT_MODE);
              }
            }
          });
          checkZoomButtons();
        }
      }
    };
    prepareManagerView(mgrview);
    scrollManView = new IlvJScrollManagerView(mgrview);
    scrollManView.setWheelScrollingEnabled(true);
    scrollManView.setBounds(0, d.height / 7, d.width, d.height * 5 / 7);

    mainContainer.add("Center", scrollManView);

    // now the panel at the top of the window, for the graph selector and the
    // buttons.

    topPanel = createTopPanel();
    mainContainer.add("North", topPanel);

    layoutButton.setSize(44, 30);
    layoutButton.setPreferredSize(new Dimension(44, 30));

    // finally the stuff for the bottom panel:

    textArea = new ScrollHTMLTextArea(this, TEXT_AREA_HEIGHT);

    bottomPanel = new JPanel() {
      Override
      public Insets getInsets() {
        return new Insets(5, 5, 5, 5);
      }
    };
    bottomPanel.setLayout(new BorderLayout(5, 5));
    bottomPanel.add("Center", textArea);

    mainContainer.add("South", bottomPanel);

    // Must be called one grapher, mgrview etc. are already instanciated.
    flatGraphDemoSupport = new FlatGraphLayoutSupport(this);
    nestedGraphDemoSupport = new NestedGraphLayoutSupport(this);
  }

  /**
   * Creates the top panel.
   */
  private final JPanel createTopPanel() {
    JPanel panel = new JPanel();
    panel.setBackground(GUI_BACKGROUND_COLOR);
    panel.setLayout(new BorderLayout(0, 0));

    JPanel panelA = new JPanel();
    panel.add("North", panelA);
    panelA.setBackground(GUI_BACKGROUND_COLOR);
    panelA.setLayout(new BorderLayout());

    JPanel panelB = new JPanel();
    panel.add("Center", panelB);
    panelB.setBackground(GUI_BACKGROUND_COLOR);
    panelB.setLayout(new FlowLayout(FlowLayout.LEFT, 1, 1));

    // the panelA consists of two subpanel
    JPanel auxPanel1 = new JPanel();
    panelA.add("West", auxPanel1);
    auxPanel1.setBackground(GUI_BACKGROUND_COLOR);
    auxPanel1.setLayout(new FlowLayout(FlowLayout.LEFT, 1, 1));

    JPanel auxPanel2 = new JPanel();
    panelA.add("East", auxPanel2);
    auxPanel2.setBackground(GUI_BACKGROUND_COLOR);
    auxPanel2.setLayout(new FlowLayout(FlowLayout.LEFT, 1, 1));

    // create the menu for selecting the layout algorithm
    auxPanel1.add(new JLabel("Style:"));
    layoutChooser = new JComboBox<String>();
    layoutChooser.setToolTipText("Select the layout algorithm");

    // item listener for layoutChooser and sizeChoser
    ItemListener sampleSelection = new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        readGraph();
        if (isAutomaticLayoutRun())
          arrange(currentLayout, grapher, false);
        else
          appendMessage("<br>Please press the layout button " + "to perform a layout.", true);
      }
    };

    for (int i = 0; i < layoutDescriptions.length; i++)
      layoutChooser.addItem(layoutDescriptions[i].getLayoutName());
    auxPanel1.add(layoutChooser);
    layoutChooser.addItemListener(sampleSelection);

    // create the menu for the size of the graph and for the layout style
    auxPanel2.add(new JLabel("  Graph Size:"));
    sizeChooser = new JComboBox<String>();
    sizeChooser.setToolTipText("Select the size of the sample graph");

    sizeChooser.addItem("small");
    sizeChooser.addItem("medium");
    sizeChooser.addItem("large");
    auxPanel2.add(sizeChooser);
    sizeChooser.addItemListener(sampleSelection);

    // add all the buttons
    addButtons(panelB);

    if (WITH_LOGO) {
      ImageIcon logoIcon = new ImageIcon(getImage("gif/IlogLogoTransparent.gif"));
      JLabel logoLabel = new JLabel(logoIcon);
      JLabel webLabel = new JLabel("http://www.roguewave.com/");
      webLabel.setForeground(new Color(29, 0, 250));
      Font font = webLabel.getFont();
      webLabel.setFont(new Font(font.getFontName(), font.getStyle(), 18));

      JPanel panelLogos = new JPanel();
      panelLogos.setBackground(GUI_BACKGROUND_COLOR);
      panelLogos.add(logoLabel);
      panelLogos.add(webLabel);

      panel.add("East", panelLogos);
    }

    return panel;
  }

  /**
   * Adds the interactor buttons (zoom, unzoom etc.)
   */
  private void addButtons(JPanel panel1) {
    layoutButton = addButton(panel1, "Layout", new LayoutAction(), "Perform layout", false, false);
    // the layout button is a little bit wider
    layoutButton.setSize(44, 22);
    layoutButton.setPreferredSize(new Dimension(44, 22));
    randomizeButton =
        addButton(panel1, "Random", new RandomizeAction(), "Randomize the positions", false, false);

    showAllButton =
        addButton(panel1, "ShowAll", new FitAction(), "Display the entire graph", false, false);
    identityButton = addButton(panel1, "ZoomIdentity", new IdentityAction(),
        "Original size (scale 1:1)", false, false);
    zoomButton = addButton(panel1, "ZoomIn", new ZoomAction(), "Zoom in", false, false);
    unzoomButton = addButton(panel1, "ZoomOut", new UnZoomAction(), "Zoom out", false, false);
    zoomboxButton = addButton(panel1, "ZoomBox", new ZoomBoxPermanentAction(),
        "Put the graph into Zoom Mode. Drag a rectangle " + "to zoom into that area", true, false);

    selectButton = addButton(panel1, "Select", new SelectAction(), "Select objects", true, true);

    expandButton = addButton(panel1, "Expand", new ExpandAction(), "Expand or collapse subgraphs",
        true, false);

    // the action listener of the overview button is installed by the handler
    overviewButton =
        addButton(panel1, "Overview", null, "Show or hide the Overview Window", true, false);

    animationButton = addButton(panel1, "Animation", new AnimationAction(),
        "Enable or disable animation", true, true);

    // the action listener of the help button is installed by the handler
    helpButton = addButton(panel1, "Help", null, "How to use this demo", true, false);

    // only one of select/expand/zoombox interactors can be active
    ButtonGroup group = new ButtonGroup();
    group.add(selectButton);
    group.add(expandButton);
    group.add(zoomboxButton);
  }

  /**
   * Adds a button containing an image.
   * 
   * @return The button
   */
  private AbstractButton addButton(JPanel panel, String iconName, ActionListener actionListener,
      String helpMessage, boolean toggle, boolean selected) {
    return addButton(PATH_TO_LOCAL_BUTTON_IMAGES, panel, iconName + ".gif", iconName + "Sel.gif",
        iconName + "Gray.gif", actionListener, helpMessage, toggle, selected);
  }

  // --------------------------------------------------------------------------
  // Window locations

  /**
   * Returns the location of the main window when opening.
   */
  private Point getMainWindowLocation() {
    Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
    int x, y;
    int hwR = HELP_WINDOW_DEFAULT_WIDTH + 15;
    int hwB = HELP_WINDOW_DEFAULT_HEIGHT + 3;
    if (screenDim.width >= hwR + MAIN_WINDOW_DEFAULT_WIDTH) {
      x = hwR;
      y = 3;
    } else {
      x = screenDim.width - MAIN_WINDOW_DEFAULT_WIDTH;
      if (screenDim.height >= hwB + MAIN_WINDOW_DEFAULT_HEIGHT) {
        x = 3;
        y = hwB;
      } else
        y = screenDim.height - MAIN_WINDOW_DEFAULT_HEIGHT;
    }
    if (x < 3)
      x = 3;
    if (y < 3)
      y = 3;
    return new Point(x, y);
  }

  /**
   * Returns the location of the help window when opening.
   */
  private Point getHelpWindowLocation() {
    return new Point(3, 3);
  }

  /**
   * Returns the size of the help window when opening.
   */
  private Dimension getHelpWindowSize() {
    return new Dimension(HELP_WINDOW_DEFAULT_WIDTH, HELP_WINDOW_DEFAULT_HEIGHT);
  }

  /**
   * Returns the location of the overview window when opening.
   */
  private Point getOverviewWindowLocation() {
    Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension d = getOverviewWindowSize();
    Point mwl = getMainWindowLocation();
    int x, y;
    int hwB = HELP_WINDOW_DEFAULT_HEIGHT + 31;

    if (mwl.x <= 3) {
      x = mwl.x + HELP_WINDOW_DEFAULT_WIDTH;
      if (x + d.width > screenDim.width)
        x = screenDim.width - d.width;
      y = 3;
    } else {
      if (screenDim.height >= hwB + d.height)
        y = hwB;
      else
        y = screenDim.height - d.height;
      x = mwl.x - d.width - 6;
    }

    if (x < 3)
      x = 3;
    if (y < 3)
      y = 3;
    return new Point(x, y);
  }

  /**
   * Returns the size of the overview window when opening.
   */
  private Dimension getOverviewWindowSize() {
    Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
    int w = screenDim.width / 4;
    int h = screenDim.height / 4;
    if (w < OVERVIEW_MIN_SIZE)
      w = OVERVIEW_MIN_SIZE;
    if (w > OVERVIEW_MAX_SIZE)
      w = OVERVIEW_MAX_SIZE;
    if (h < OVERVIEW_MIN_SIZE)
      h = OVERVIEW_MIN_SIZE;
    if (h > OVERVIEW_MAX_SIZE)
      h = OVERVIEW_MAX_SIZE;

    return new Dimension(w, h);
  }

  // --------------------------------------------------------------------------
  // The auto play mechanism

  /**
   * Plays automatically an endless loop for loading successively the sample
   * graphs and running the corresponding layout.
   */
  private void autoPlay() {
    int nLayouts = layoutChooser.getItemCount();
    int nSizes = sizeChooser.getItemCount();
    long delay = getAutoPlayDelay();

    enableGUI(false);
    sleep(500);

    while (true) {
      for (int i = 0; i < nLayouts; i++) {
        for (int j = 0; j < nSizes; j++) {
          grapher.setContentsAdjusting(true);
          scrollManView.setAdjusting(true);

          try {
            readGraph(i, j);

            if (layoutAllowsNodeShapes()) {
              setNodeShapeOption(SPHERE_NODE_SHAPE);
            } else {
              setNoNodeShapeChoice(true);
            }

            arrange(currentLayout, grapher, false);

            // at this moment, the demo is busy (the layout and the animation
            // are running). We wait until it's ready before passing to the
            // next sample.
            try {
              mgranim.getAnimationThread().join();
            } catch (InterruptedException ex) {
            }

            // one early sleep to have time to redraw before the garbage
            // collection
            sleep(delay > 100 ? delay / 2 : 100);

          } finally {

            // break reference to the internal thread of the animator
            mgranim.detach();
            mgranim.attach(grapher);

            grapher.setContentsAdjusting(false);
            scrollManView.setAdjusting(false);
            scrollManView.adjustScrollBars();

            // now that it's ready, sleep the specified time to let the user
            // admire the result (we also use this opportunity to do same gc...)
            System.gc();
            System.gc();
            System.gc();

            // we do the sleep at last to minimize the possibility that a
            // pending invokeLater is unsynchronized done (which would be a
            // bug anyway) before we continue
            sleep(delay > 100 ? delay / 2 : 100);
          }
        }
      }
    }
  }

  // --------------------------------------------------------------------------
  // Reading a graph from file

  /**
   * Make everything visible or invisible.
   */
  private void setGrapherVisible(boolean flag) {
    if (grapher != null && mgrview != null) {
      for (int i = 0; i < grapher.getLayersCount(); i++)
        mgrview.setVisible(i, flag);
    }
  }

  /**
   * Reads the currently selected graph from an ivl file.
   */
  private void readGraph() {
    setDefaultNodeShape();
    readGraph(layoutChooser.getSelectedIndex(), sizeChooser.getSelectedIndex());
  }

  /**
   * Reads a the sample graph for a given layout and with a given size.
   */
  private void readGraph(int layoutIndex, int sizeIndex) {
    // select the layout and get the name of the selected layout
    String layoutName = layoutChooser.getItemAt(layoutIndex);
    String layoutFileNamePrefix = null;

    for (int i = 0; i < layoutDescriptions.length; i++) {
      if (layoutName.equals(layoutDescriptions[i].getLayoutName())) {
        layoutFileNamePrefix = layoutDescriptions[i].getLayoutFileNamePrefix();
        currentLayout = layoutDescriptions[i].getLayout();
        break;
      }
    }

    if (layoutFileNamePrefix == null)
      throw new IllegalArgumentException("layout file name prefix not found: " + layoutName);

    String sizeName = sizeChooser.getItemAt(sizeIndex);

    String graphFileBaseName = getGraphFileBaseName(layoutFileNamePrefix, sizeName);
    currentGraphFileBaseName = graphFileBaseName;

    // the name of the file that will be read from the jar file
    String graphFileName = "ivl/" + graphFileBaseName + ".ivl";

    readGraph(layoutName, layoutFileNamePrefix, sizeName, graphFileName);
  }

  /**
   * Returns the name of the IVL file storing the graph, without any directory
   * prepended and without any extension.
   */
  private String getGraphFileBaseName(String layoutFileNamePrefix, String sizeName) {
    return layoutFileNamePrefix + "-" + sizeName;
  }

  /**
   * Reads a given file.
   */
  private void readGraph(String layoutName, String shortLayoutName, String sizeName,
      String graphFileName) {
    // clear the text area
    showMessage("");

    // just to be sure, disable autolayout
    linkLayout.setAutoLayout(false);

    // reset the information of the node shape
    currentNodeShape = SPHERE_NODE_SHAPE;

    // save the current cursor
    Cursor oldCursor = getDemoCursor();

    // set the wait cursor
    setDemoCursor(WAIT_CURSOR);

    try {
      // show help
      if (firstTimeRead)
        helpHandler.setHelpFile("help");
      else
        helpHandler.setHelpFile(getGraphFileBaseName(shortLayoutName, sizeName));

      String sz = sizeName;
      sz = sz.substring(0, 1).toUpperCase() + sz.substring(1);

      showMessage(getBlueBoldMessage("The " + sz + " " + layoutName + " Example") + "<br>");
      // remove all nodes and links
      grapher.deleteAll(false);
      appendMessage("Reading " + graphFileName + " ... ");

      // read the files from the jar
      setGrapherVisible(false);
      grapher.read(getClass().getResourceAsStream(graphFileName));

      // If you need to dump the file in to ASCI IVL
      // grapher.write(getGraphFileBaseName(shortLayoutName, sizeName) +
      // "-ascii.ivl", false);

      // calculate the size information
      calcSize(grapher);

      // do it once calcSize has been called!
      layoutSupport = getNumberOfSubgraphs() == 0 ? flatGraphDemoSupport : nestedGraphDemoSupport;

      // enable this in order to make graphs smaller
      layoutSupport.prepareSample(graphFileName);

      setBackgroundStyle(0);

      // enable this in order to make graphs smaller
      // writeGraph(getGraphFileBaseName(shortLayoutName, sizeName));

      // preparation after read
      afterRead();

      // and show the size information
      appendMessage("" + numberOfNodes + " nodes, " + numberOfLinks + " links, " + numberOfSubgraphs
          + " subgraphs. ");

    } catch (Exception e) {
      System.err.println("error during read: " + graphFileName);
      e.printStackTrace();
      showMessage("error during read: " + graphFileName);
    } finally {
      // pop the frame to front
      if (mainFrame != null)
        mainFrame.toFront();
      // restore the original cursor
      setDemoCursor(oldCursor);
    }
  }

  /**
   * Is called after reading the graph from file.
   */
  private void afterRead() {
    // enable/disable the expand button
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        setInteractionMode(SELECT_MODE);
        if (expandButton != null)
          expandButton.setEnabled(expandAllowed() && !isAutoPlay());
      }
    });

    // a new standard color for this graph
    Color c = imageUtil.getRandomColor();
    while (c == standardColor)
      c = imageUtil.getRandomColor();
    standardColor = c;

    if (currentLayout instanceof IlvTopologicalMeshLayout) {
      // to avoid that the layout remembers the old starting node
      ((IlvTopologicalMeshLayout) currentLayout).setStartingNode(null);
    }

    // whether we have the choice of a node shape. This must be determined
    // before preparing the grapher
    boolean allowsNodeShapes = layoutAllowsNodeShapes();

    setNoNodeShapeChoice(!allowsNodeShapes);

    if (allowsNodeShapes)
      convertColors();

    // transfer all layout parameters from the names properties
    loadLayoutProperties();

    // finally, remove the layout properties to cleanup the memory
    cleanLayoutProperties();

    // prepare the grapher
    prepareGrapherAfterRead();

    // now analize the sample type and decide what to do

    // whether the link clip provider is needed
    if (currentLayout != null && currentLayout.supportsLinkClipping()
        && !(currentLayout instanceof IlvHierarchicalLayout))
      currentLayout.setLinkClipInterface(linkClipProvider);

    // whether autolayout is enabled
    if (currentLayout instanceof IlvLinkLayout) {
      linkLayout.setAutoLayout(true);
      selectionInteractor.setOpaqueMove(false);
    } else {
      linkLayout.setAutoLayout(false);
      selectionInteractor.setOpaqueMove(true);
    }

    if (currentLayout instanceof IlvUniformLengthEdgesLayout) {
      // Because the nodes can have different sizes, ask the
      // Uniform Length Edges
      // layout to take into account the size of each node.
      // This parameter can also be saved with the IVL file.
      ((IlvUniformLengthEdgesLayout) currentLayout).setRespectNodeSizes(true);
      ((IlvUniformLengthEdgesLayout) currentLayout).setPreferredLinksLength(50);
    }

    if (currentLayout instanceof IlvTreeLayout) {
      ((IlvTreeLayout) currentLayout).setConnectorStyle(IlvTreeLayout.CLIPPED_PINS);
    }

    // whether layout can use randomization
    setRandomizeAllowed(layoutAllowsRandomization());
    // whether layout can use animation
    setAnimationAllowed(layoutAllowsAnimation());

    // which layout samples allow fit to view after layout
    if (!layoutAllowsFitToView() || !isAutomaticLayoutRun()) {
      // fit after read, because we cannot fit after layout
      setGrapherVisible(true);
      fit(mgrview, true);
    } else {
      mgrview.setTransformer(new IlvTransformer());
      setGrapherVisible(true);
      grapher.reDraw();
    }
  }

  /**
   * Loads the layout properties after reading from file.
   */
  public void loadLayoutProperties() {
    layoutSupport.loadLayoutProperties();
  }

  /**
   * Cleans up the layout properties after reading or writing to/from file.
   */
  public void cleanLayoutProperties() {
    layoutSupport.cleanLayoutProperties();
  }

  /**
   * Make all labels selectable or nonselectable.
   */
  public void makeLabelsSelectable(boolean flag) {
    layoutSupport.makeLabelsSelectable(flag);
    // and mark all labels as nonselectable
    IlvGraphicEnumeration objects = grapher.getObjects(true);
    while (objects.hasMoreElements()) {
      IlvGraphic g = objects.nextElement();
      if (g instanceof IlvZoomableLabel)
        ((IlvManager) g.getGraphicBag()).setSelectable(g, flag);
    }
  }

  /**
   * Prepares the grapher after reading.
   */
  private void prepareGrapherAfterRead() {
    if (getNumberOfSubgraphs() == 0)
      prepareFlatGrapherAfterRead();
    layoutSupport.prepareGrapherAfterRead();
  }

  /**
   * Prepares the flat grapher after reading.
   */
  public void prepareFlatGrapherAfterRead() {
    IlvGrapher grapher = getGrapher();

    // change the node shape, if necessary
    changeNodeShape(grapher);

    // install Links
    IlvGraphicEnumeration objs = grapher.getObjects();
    IlvGraphic g;
    Vector<IlvLinkImage> links = new Vector<IlvLinkImage>();
    while (objs.hasMoreElements()) {
      g = objs.nextElement();
      if (grapher.isLink(g))
        links.addElement((IlvLinkImage) g);
    }

    grapher.setContentsAdjusting(true);
    try {
      Enumeration<IlvLinkImage> e = links.elements();
      IlvLinkImage oldlink;
      IlvLinkImage newlink;
      while (e.hasMoreElements()) {
        oldlink = e.nextElement();
        if (oldlink instanceof IlvSplineLinkImage) {
          newlink = new SplineLink(oldlink.getFrom(), oldlink.getTo(), oldlink.isOriented(), null);
          ((SplineLink) newlink).setDefinitionPoints(oldlink.getLinkPoints(null));
        } else {
          newlink = new Link(oldlink.getFrom(), oldlink.getTo(), oldlink.isOriented(), null);
          ((Link) newlink).setDefinitionPoints(oldlink.getLinkPoints(null));
        }
        replaceLink(grapher, oldlink, newlink);
      }
    } finally {
      grapher.setContentsAdjusting(false);
    }

    // install clip link connectors
    grapher.setContentsAdjusting(true);
    IlvLinkConnector lc;
    try {
      objs = grapher.getObjects();
      while (objs.hasMoreElements()) {
        g = objs.nextElement();
        // make all nodes and links non editable
        grapher.setEditable(g, false);
        if (grapher.isNode(g)) {
          lc = IlvClippingLinkConnector.GetAttached(g);
          if (lc != null)
            lc.detach(false);
          new IlvClippingLinkConnector(g);
        }
      }
    } finally {
      grapher.setContentsAdjusting(false);
    }
  }

  // --------------------------------------------------------------------------
  // Writing a graph from file
  // This is normally not used. However, in order to convert the samples
  // into minimized binary form, we use it when preparing the web demo.

  // What we do when preparing the demo app is the following:
  // We enable writeGraph, and run the demo in autoPlay mode.
  // Then we compare the size of the written graphs with the size of the
  // original graphs, and choose the smaller of both. Normally, no information
  // is lost that cannot be recalculated inside the app, so it is safe.

  /**
   * Write the graph to file.
   */
  /*
   * private void writeGraph(String fileName) { // load layout parameters
   * adapter.loadParametersFromNamedProperties(currentLayout); // to get a clean situation
   * adapter.removeParametersFromNamedProperties();
   * 
   * // the following are various mechanism to make the size of ivl files // smaller
   * 
   * IlvGraphicVector nodeVector = new IlvGraphicVector(1000); IlvGraphicVector linkVector = new
   * IlvGraphicVector(1000);
   * 
   * 
   * // put the nodes and links in vectors IlvGraphicEnumeration allObjects = grapher.getObjects();
   * IlvGraphic obj; while (allObjects.hasMoreElements()) { obj = allObjects.nextElement(); if
   * (grapher.isNode(obj)) nodeVector.addElement(obj); if (grapher.isLink(obj))
   * linkVector.addElement(obj); }
   * 
   * // now replace all relief labels by normal labels to save space if (layoutAllowsNodeShapes()) {
   * grapher.setContentsAdjusting(true); try { int nNodes = nodeVector.size(); IlvGraphic node;
   * 
   * for (int i = 0; i < nNodes; i++) { node = nodeVector.elementAt(i); if (node instanceof
   * IlvReliefLabel) { IlvReliefLabel oldNode = (IlvReliefLabel)node; if (oldNode.getLabel() == null
   * || oldNode.getLabel().equals("")) { IlvRect rect = oldNode.boundingBox(null); IlvRectangle
   * newNode = new IlvRectangle(rect); replaceNode(grapher, oldNode, newNode);
   * newNode.setForeground(oldNode.getBackground()); grapher.reshapeObject(newNode, rect, false); }
   * } else if (node instanceof IlvReliefRectangle) { IlvReliefRectangle oldNode =
   * (IlvReliefRectangle)node; IlvRect rect = oldNode.boundingBox(null); IlvRectangle newNode = new
   * IlvRectangle(rect); replaceNode(grapher, oldNode, newNode);
   * newNode.setForeground(oldNode.getBackground()); grapher.reshapeObject(newNode, rect, false); }
   * } } finally { grapher.setContentsAdjusting(false); } }
   * 
   * // replace polyline links without intermediate points by linkimages.
   * grapher.setContentsAdjusting(true); try { int nLinks = linkVector.size(); IlvGraphic link;
   * 
   * for (int i = 0; i < nLinks; i++) { link = linkVector.elementAt(i); if (link instanceof
   * IlvPolylineLinkImage) { IlvPolylineLinkImage oldlink = (IlvPolylineLinkImage)link; if
   * (oldlink.getPointsCardinal() == 2) { IlvLinkImage newlink = new IlvLinkImage(oldlink.getFrom(),
   * oldlink.getTo(), oldlink.isOriented()); replaceLink(grapher, oldlink, newlink); } } } } finally
   * { grapher.setContentsAdjusting(false); }
   * 
   * // remove all link connectors (because we reinstall them on loading anyway)
   * IlvGraphicEnumeration objs = grapher.getObjects(); IlvGraphic g; Vector links = new Vector();
   * while (objs.hasMoreElements()) { g = objs.nextElement(); if (grapher.isNode(g)) {
   * IlvLinkConnector lc = IlvLinkConnector.Get(g); if (lc != null) lc.detach(false); } }
   * 
   * if (currentLayout.supportsSaveParametersToNamedProperties())
   * adapter.saveParametersToNamedProperties(currentLayout, false);
   * 
   * try { grapher.write(fileName, true); } catch (IOException ex) { ex.printStackTrace(); } }
   */

  // --------------------------------------------------------------------------
  // Small auxiliaries after reading

  /**
   * Converts the colors to the standard colors.
   */
  private void convertColors() {
    layoutSupport.convertColors();
  }

  /**
   * Calculates the size of the graph after reading.
   */
  private void calcSize(IlvGrapher grapher) {
    numberOfNodes = 0;
    numberOfLinks = 0;
    numberOfSubgraphs = 0;

    calcSizeRecursive(grapher);
  }

  private void calcSizeRecursive(IlvGrapher grapher) {
    // count the nodes and links
    IlvGraphicEnumeration e = grapher.getObjects();
    while (e.hasMoreElements()) {
      IlvGraphic obj = e.nextElement();

      if (grapher.isNode(obj)) {
        if (obj instanceof IlvGrapher) {
          numberOfSubgraphs++;
          calcSizeRecursive((IlvGrapher) obj);
        } else
          numberOfNodes++;
      } else if (grapher.isLink(obj))
        numberOfLinks++;
    }
  }

  // --------------------------------------------------------------------------
  // Node shape management

  /**
   * Sets whether we have the choice of a node shape.
   */
  private void setNoNodeShapeChoice(boolean flag) {
    if (flag != noNodeShapeChoice) {
      noNodeShapeChoice = flag;
    }
  }

  /**
   * Returns true if we have the choice of a node shape.
   */
  private boolean isNoNodeShapeChoice() {
    return noNodeShapeChoice;
  }

  /**
   * Sets the node shape option.
   */
  private void setNodeShapeOption(int shape) {
    if (isBusy())
      return;
    if (shape == SPHERE_NODE_SHAPE || shape == RECTANGLE_NODE_SHAPE)
      nodeShape = shape;
    else
      throw new IllegalArgumentException("unsupported node shape: " + shape);
  }

  /**
   * Returns the node shape option.
   */
  public int getNodeShapeOption() {
    if (isNoNodeShapeChoice())
      return RECTANGLE_NODE_SHAPE;
    return nodeShape;
  }

  /**
   * Set the default node shape of a graph depending on the size of the graph,
   * just to avoid that the demo gets boring.
   */
  private void setDefaultNodeShape() {
    setNodeShapeOption(SPHERE_NODE_SHAPE);
  }

  /**
   * Changes the shape of the nodes.
   */
  public void changeNodeShape(IlvGrapher grapher) {
    if (isBusy())
      return;

    setBackgroundStyle(getNodeShapeOption());

    // if we have no choice, don't do anything
    if (isNoNodeShapeChoice())
      return;

    // allocate vectors for the node shape replacement
    IlvGraphicVector vectNodes = new IlvGraphicVector(1000);

    // put the nodes in the Vector
    IlvGraphicEnumeration allObjects = grapher.getObjects();
    IlvGraphic obj;
    while (allObjects.hasMoreElements()) {
      obj = allObjects.nextElement();
      if (grapher.isNode(obj))
        vectNodes.addElement(obj);
    }

    // now replace all nodes
    grapher.setContentsAdjusting(true);
    try {
      int nNodes = vectNodes.size();
      IlvGraphic newNode;
      IlvGraphic oldNode;

      // save it for future use
      currentNodeShape = getNodeShapeOption();
      for (int i = 0; i < nNodes; i++) {
        oldNode = vectNodes.elementAt(i);
        newNode = createNode(oldNode, currentNodeShape);
        replaceNode(grapher, oldNode, newNode);
      }
    } finally {
      grapher.setContentsAdjusting(false);
    }

    grapher.reDraw();
  }

  /**
   * Replace a link in the grapher.
   */
  public void replaceLink(IlvGrapher grapher, IlvLinkImage oldLink, IlvLinkImage newLink) {
    layoutSupport.replaceLink(grapher, oldLink, newLink);
  }

  /**
   * Replace a node in the grapher.
   */
  public void replaceNode(final IlvGrapher grapher, final IlvGraphic oldNode,
      final IlvGraphic newNode) {
    layoutSupport.replaceNode(grapher, oldNode, newNode);
  }

  /**
   * Creates a node of the given shape.
   */
  private IlvGraphic createNode(IlvGraphic oldNode, int shapeType) {
    Color color = null;
    if (layoutPreservesNodeColor())
      color = (Color) oldNode.getProperty(BACKGROUND_COLOR);

    switch (shapeType) {
      case SPHERE_NODE_SHAPE:
        return createSphereNode(color);
      case RECTANGLE_NODE_SHAPE:
        return createRectNode(color);
    }
    return oldNode;
  }

  /**
   * Create a rectangle icon.
   */
  private IlvGraphic createRectNode(Color color) {
    IlvGraphic newNode = new IlvReliefRectangle(standardNodeRect);
    if (color == null)
      newNode.setBackground(standardColor);
    else
      newNode.setBackground(color);
    return newNode;
  }

  /**
   * Create a sphere node.
   */
  private IlvGraphic createSphereNode(Color color) {
    Ellipse node = new Ellipse(standardSphereNodeRect);

    if (color == null)
      color = standardColor;
    color = imageUtil.getClosestGradColor(color);

    float[] stops = new float[] {0f, 0.1f, 0.2f, 0.4f, 1f};
    Color[] colors = new Color[5];
    colors[0] = Color.white;
    colors[1] = imageUtil.transformColor(255, 255, 185, color);
    colors[2] = imageUtil.transformColor(255, 255, 0, color);
    colors[3] = imageUtil.transformColor(217, 217, 0, color);
    colors[4] = Color.black;

    Paint paint = new IlvRadialGradientPaint(new Point2D.Double(0.52, 0.49), 0.66, stops, colors,
        new Point2D.Double(0.34, 0.29), IlvMultipleGradientPaint.SPREAD_PAD,
        IlvMultipleGradientPaint.LINEAR_RGB, null, true);

    node.setPaint(paint);
    node.setForeground(colors[3]);

    return node;
  }

  // --------------------------------------------------------------------------
  // Related to the background

  /**
   * Set the background style of the manager view.
   */
  private void setBackgroundStyle(int style) {
    mgrview.setBackgroundPatternLocation(getClass().getResource("gif/BackgroundBlack.gif"));
  }

  // --------------------------------------------------------------------------
  // Accessor and set methods of the flags that control the app

  /**
   * Sets whether randomization is allowed.
   */
  private void setRandomizeAllowed(boolean flag) {
    if (randomizeAllowed != flag) {
      if (!isAutoPlay())
        randomizeButton.setEnabled(flag);
      randomizeAllowed = flag;
    }
  }

  /**
   * Returns true if randomization is allowed.
   */
  public boolean isRandomizeAllowed() {
    return randomizeAllowed;
  }

  /**
   * Sets whether animation is allowed.
   */
  private void setAnimationAllowed(boolean flag) {
    if (animationAllowed != flag) {
      animationButton.setEnabled(flag);
      animationAllowed = flag;
    }
  }

  /**
   * Returns true if randomization is allowed.
   */
  private boolean isAnimationAllowed() {
    return animationAllowed;
  }

  /**
   * Sets whether we use animation.
   */
  private void setAnimation(boolean enable) {
    if (isBusy())
      return;
    animation = enable;
  }

  /**
   * Returns true if animation is active.
   */
  boolean isAnimation() {
    if (!isAnimationAllowed())
      return false;
    return animation;
  }

  /**
   * Returns true if automatic layout after loading is active.
   */
  private boolean isAutomaticLayoutRun() {
    return automaticLayoutRun && animation;
  }

  /**
   * Returns true if the graph allows expand/collapse.
   */
  private boolean expandAllowed() {
    return getNumberOfSubgraphs() > 0 && !(getCurrentGraphFileBaseName() != null
        && getCurrentGraphFileBaseName().equals("mixed-nesting-small"));
  }

  /**
   * Sets the auto play mode.
   */
  private void setAutoPlay(boolean flag) {
    autoPlay = flag;
  }

  /**
   * Returns true if auto play mode is active.
   */
  public boolean isAutoPlay() {
    return autoPlay;
  }

  /**
   * Sets the delay for auto play mode.
   */
  private void setAutoPlayDelay(long delay) {
    autoPlayDelay = delay;
  }

  /**
   * Returns the autoplay delay.
   */
  private long getAutoPlayDelay() {
    return autoPlayDelay;
  }

  // --------------------------------------------------------------------------
  // Layout style control

  /**
   * Returns true if the layout style needs to preserve the node color for
   * rectangles as read from file.
   */
  public boolean layoutPreservesNodeColor() {
    return true;
  }

  /**
   * Returns true if the layout style allows node shapes.
   */
  private boolean layoutAllowsNodeShapes() {
    return currentLayout != null && !(currentLayout instanceof IlvHierarchicalLayout
        || currentLayout instanceof IlvGridLayout || currentLayout instanceof IlvLinkLayout);
  }

  /**
   * Returns true if the layout style allows to randomize the graph.
   */
  private boolean layoutAllowsRandomization() {
    return !(currentLayout instanceof IlvLinkLayout);
  }

  /**
   * Returns true if the layout style allows animation.
   */
  private boolean layoutAllowsAnimation() {
    return true;
  }

  /**
   * Returns true if the layout style allows fit to view after layout.
   */
  public boolean layoutAllowsFitToView() {
    // fit to view in link layout makes the nodes appear like moving during
    // animations. We don't want this.
    return !(currentLayout instanceof IlvLinkLayout);
  }

  // --------------------------------------------------------------------------
  // Related to the interactions

  /**
   * Returns the expand cursor used when over an object that is not a subgraph.
   */
  public Cursor getExpandIdleCursor() {
    return expandIdleCursor;
  }

  /**
   * Returns the expand cursor used when over an object that is a subgraph.
   */
  public Cursor getExpandWorkCursor() {
    return expandWorkCursor;
  }

  /**
   * Sets the interaction mode.
   */
  public void setInteractionMode(int mode) {
    if (isBusy())
      return;
    switch (mode) {
      case SELECT_MODE:
        if (selectButton != null && !selectButton.isSelected())
          selectButton.setSelected(true);
        if (expandButton != null)
          expandButton.setSelected(false);
        if (zoomboxButton != null)
          zoomboxButton.setSelected(false);
        if (mgrview.getInteractor() != selectionInteractor) {
          mgrview.popInteractor();
          mgrview.pushInteractor(selectionInteractor);
          appendMessage("<br>Click to select objects!");
        }
        break;
      case EXPAND_MODE:
        if (expandButton != null && !expandButton.isSelected())
          expandButton.setSelected(true);
        if (selectButton != null)
          selectButton.setSelected(false);
        if (zoomboxButton != null)
          zoomboxButton.setSelected(false);
        if (expandInteractor == null)
          expandInteractor = new ExpandInteractor(this);
        if (mgrview.getInteractor() != expandInteractor) {
          mgrview.popInteractor();
          mgrview.pushInteractor(expandInteractor);
          appendMessage("<br>Click on subgraphs to expand or collapse!");
        }
        break;
      case ZOOMBOX_MODE:
        if (zoomboxButton != null && !zoomboxButton.isSelected())
          zoomboxButton.setSelected(true);
        if (selectButton != null)
          selectButton.setSelected(false);
        if (expandButton != null)
          expandButton.setSelected(false);
        if (zoomViewInteractor == null) {
          zoomViewInteractor = new IlvZoomViewInteractor();
          zoomViewInteractor.setPermanent(true);
        }
        if (mgrview.getInteractor() != zoomViewInteractor) {
          mgrview.popInteractor();
          mgrview.pushInteractor(zoomViewInteractor);
          appendMessage("<br>Drag a rectangle to zoom to this rectangle!");
        }
        break;
    }
  }

  /**
   * Arranges the graph by performing layout.
   */
  private void arrange(IlvGraphLayout layout, IlvGrapher grapher, boolean isInitialLayout) {
    if (!isBusy())
      layoutSupport.arrange(layout, grapher, isInitialLayout);
  }

  /**
   * Randomize the node positions.
   */
  public void randomize() {
    if (!isBusy())
      layoutSupport.randomize();
  }

  // --------------------------------------------------------------------------
  // The corresponding actions

  public class AnimationAction implements ActionListener {
    Override
    public void actionPerformed(ActionEvent e) {
      setAnimation(animationButton.isSelected());
    }
  }

  public class LayoutAction implements ActionListener {
    Override
    public void actionPerformed(ActionEvent e) {
      arrange(currentLayout, grapher, false);
    }
  }

  public class RandomizeAction implements ActionListener {
    Override
    public void actionPerformed(ActionEvent e) {
      randomize();
    }
  }

  public class SelectAction implements ActionListener {
    Override
    public void actionPerformed(ActionEvent e) {
      setInteractionMode(SELECT_MODE);
    }
  }

  public class ExpandAction implements ActionListener {
    Override
    public void actionPerformed(ActionEvent e) {
      setInteractionMode(EXPAND_MODE);
    }
  }

  public class ZoomBoxPermanentAction implements ActionListener {
    Override
    public void actionPerformed(ActionEvent e) {
      setInteractionMode(ZOOMBOX_MODE);
    }
  }

  /**
   * Enables or disables the interactor buttons.
   */
  public void enableInteractorButtons(boolean enable) {
    enableZoomButtons(enable);

    if (layoutButton != null)
      layoutButton.setEnabled(enable);
    if (randomizeButton != null)
      randomizeButton.setEnabled(enable && isRandomizeAllowed());
    if (selectButton != null)
      selectButton.setEnabled(enable);
    if (expandButton != null)
      expandButton.setEnabled(enable && expandAllowed());
    if (overviewButton != null)
      overviewButton.setEnabled(enable);
    if (animationButton != null)
      animationButton.setEnabled(enable && isAnimationAllowed());
    if (helpButton != null)
      helpButton.setEnabled(enable);
  }

  // --------------------------------------------------------------------------
  // Performing layout

  /**
   * Preprocessing before layout.
   */
  void beforeLayout(IlvGraphLayout layout) {
    // to avoid that the animation gets too slow
    if (numberOfNodes + numberOfLinks > 300) {
      mgranim.setAnimationDelay(20);
      mgranim.setAnimationRate(20);
    } else {
      mgranim.setAnimationDelay(50);
      mgranim.setAnimationRate(20);
    }

    // make the progressbar aware of the current layout
    // progressBar.setGraphLayout(layout);

    // mark the app as busy to prevent further input
    setBusy(true);

    // disable the GUI
    if (Thread.currentThread() == mgranim.getAnimationThread()) {
      enableGUI(false);
      while (isGUIenabled) {
        sleep(300);
      }
    }

    // disable autolayout
    autoLayout = linkLayout.isAutoLayout();
    linkLayout.setAutoLayout(false);

    // specific preparations per layout style
    if (layout instanceof IlvTopologicalMeshLayout)
      prepareMeshLayout((IlvTopologicalMeshLayout) layout);
    else if (layout instanceof IlvUniformLengthEdgesLayout)
      prepareUniformLayout((IlvUniformLengthEdgesLayout) layout);
  }

  /**
   * Prepare the layout paramters for mesh layout.
   */
  private void prepareMeshLayout(IlvTopologicalMeshLayout layout) {
    // increase the allowed time for big graphs
    if (numberOfNodes > 40)
      layout.setLayoutRegion(new IlvRect(0, 0, 700, 700));
    else
      layout.setLayoutRegion(new IlvRect(0, 0, 500, 500));
  }

  /**
   * Prepare the layout paramters for uniform edges length layout.
   */
  private void prepareUniformLayout(IlvUniformLengthEdgesLayout layout) {
    // increase the allowed time for big graphs
    if (numberOfNodes > 100)
      layout.setAllowedTime(80000); // ms
    else
      layout.setAllowedTime(32000);
  }

  /**
   * Postprocessing after layout.
   */
  public void afterLayout(IlvGraphLayout layout) {
    // mark the app as not busy anymore
    setBusy(false);

    // enable the GUI
    if (!isAutoPlay()) {
      enableGUI(true);

      // restore auto layout
      linkLayout.setAutoLayout(autoLayout);
    }
  }

  /**
   * Enable or disable the buttons of the GUI.
   */
  public void enableGUI(boolean flag) {
    final boolean enable = flag;
    // avoid enable/disable for nothing.
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        if (enable != isGUIenabled) {
          if (sizeChooser != null)
            sizeChooser.setEnabled(enable);
          if (layoutChooser != null)
            layoutChooser.setEnabled(enable);
          enableInteractorButtons(enable);

          isGUIenabled = enable;

          if (mainContainer != null) {
            if (enable) {
              // restore the original cursor
              setDemoCursor(oldCursor);
            } else {
              // save the current cursor
              oldCursor = getDemoCursor();

              // set the wait cursor
              setDemoCursor(WAIT_CURSOR);
            }
          }
        }
      }
    });
  }

  // --------------------------------------------------------------------------
  // Others

  Override
  public void setDemoCursor(Cursor cursor) {
    super.setDemoCursor(cursor);
    // the interactor for expand/collapse sets the cursor
    // directly on the manager view, so when changing
    // the cursor globally we need to set it there explicitly
    if (mgrview != null)
      mgrview.setCursor(cursor);
  }

  /**
   * Displays a message.
   */
  Override
  protected void showMessage(final String message) {
    // well, it may happen that showMessage is called before the
    // initialization of textArea...
    if (textArea != null) {
      if (SwingUtilities.isEventDispatchThread())
        textArea.setMessage(message);
      else {
        SwingUtilities.invokeLater(new Runnable() {
          Override
          public void run() {
            textArea.setMessage(message);
          }
        });
      }
    } else
      System.out.println(message);
  }

  /**
   * Appends a message.
   * 
   * @param message
   *          the message
   * @param showLastLine
   *          If true, the vertical scrollbar is scrolled such as the last added
   *          line gets visible
   */
  Override
  public void appendMessage(final String message, final boolean showLastLine) {
    if (SwingUtilities.isEventDispatchThread())
      textArea.appendMessage(message, showLastLine);
    else {
      SwingUtilities.invokeLater(new Runnable() {
        Override
        public void run() {
          textArea.appendMessage(message, showLastLine);
        }
      });
    }
  }

  /**
   * Returns a text with blue and bold font.
   */
  private String getBlueBoldMessage(String message) {
    return "<b><font color=\"#000099\">" + message + "</font></b>";
  }

  // ----------------------------------------------------------------------------
  // GUI Auxiliary Classes
  // ----------------------------------------------------------------------------

  /**
   * A expand/collapse interactor.
   */
  final class ExpandInteractor extends IlvSelectInteractor {
    IlvLayoutGalleryApp app;
    IlvApplyObject expandApplyObject;
    Cursor oldCursor;

    public ExpandInteractor(IlvLayoutGalleryApp app) {
      this.app = app;
      setDragAllowed(false);
      setEditionAllowed(false);
      setMoveAllowed(false);
      setMultipleSelectionMode(true);
    }

    Override
    protected void attach(IlvManagerView v) {
      super.attach(v);
      makeLabelsSelectable(true);
      getManager().removeManagerSelectionListener(this);
      getManager().addManagerTreeSelectionListener(this);
      getManager().deSelectAll(true, true);
      oldCursor = v.getCursor();
      if (app.getExpandIdleCursor() != null)
        v.setCursor(app.getExpandIdleCursor());
    }

    Override
    protected void detach() {
      getManager().removeManagerTreeSelectionListener(this);
      makeLabelsSelectable(false);
      if (oldCursor != null)
        this.getManagerView().setCursor(oldCursor);
      oldCursor = null;
      super.detach();
    }

    private IlvGrapher getSubgrapher(IlvGraphic obj) {
      while (obj != null && !(obj instanceof IlvGrapher))
        obj = (IlvGraphic) obj.getGraphicBag();

      // test whether it is the top level grapher
      if (obj == null || obj.getGraphicBag() == null)
        return null;

      return (IlvGrapher) obj;
    }

    Override
    protected void processMouseMotionEvent(MouseEvent event) {
      switch (event.getID()) {
        case MouseEvent.MOUSE_MOVED:
          IlvPoint point = new IlvPoint(event.getX(), event.getY());
          IlvManager topManager = getManager();
          IlvManagerView view = this.getManagerView();
          IlvGraphic obj = topManager.getObject(point, view, true);
          IlvGrapher subgrapher = getSubgrapher(obj);
          if (subgrapher != null && !app.isBusy()) {
            if (app.getExpandWorkCursor() != null) {
              if (view.getCursor() != app.getExpandWorkCursor())
                view.setCursor(app.getExpandWorkCursor());
            }
          } else {
            if (app.getExpandIdleCursor() != null) {
              if (view.getCursor() != app.getExpandIdleCursor())
                view.setCursor(app.getExpandIdleCursor());
            }
          }
          break;
      }
      super.processMouseMotionEvent(event);
    }

    Override
    public void selectionChanged(ManagerSelectionChangedEvent event) {
      // get the selected or deselected object
      IlvGraphic obj = event.getGraphic();
      IlvManager manager = (IlvManager) obj.getGraphicBag();
      if (manager == null)
        return;
      if (!manager.isSelected(obj))
        return;
      if (app.isBusy()) {
        manager.setSelected(obj, false, false);
        return;
      }

      if (expandApplyObject == null)
        expandApplyObject = new IlvApplyObject() {
          Override
          public final void apply(IlvGraphic obj, Object arg) {
            ((IlvGrapher) obj).setCollapsed(!((IlvGrapher) obj).isCollapsed());
          }
        };

      // get the subgrapher related to the object
      IlvGrapher subgrapher = getSubgrapher(obj);
      if (subgrapher != null) {
        // this helps the tree layout
        layoutSupport.makeIntergraphLinksStraightAt(subgrapher);
        // collapse or expand.
        subgrapher.getGraphicBag().applyToObject(subgrapher, expandApplyObject, null, true);

        // incremental layout needed
        layoutSupport.arrangeAfterExpand();
      }

      manager.setSelected(obj, false, false);
    }
  }

  /**
   * Stores info about a layout.
   */
  private static class LayoutDescriptor {
    private IlvGraphLayout _layout;
    private String _layoutName;
    private String _layoutFileNamePrefix;

    LayoutDescriptor(String layoutName, String layoutFileNamePrefix) {
      this(null, layoutName, layoutFileNamePrefix);
    }

    LayoutDescriptor(IlvGraphLayout layout, String layoutName, String layoutFileNamePrefix) {
      _layout = layout;
      _layoutName = layoutName;
      _layoutFileNamePrefix = layoutFileNamePrefix;
    }

    IlvGraphLayout getLayout() {
      return _layout;
    }

    String getLayoutName() {
      return _layoutName;
    }

    String getLayoutFileNamePrefix() {
      return _layoutFileNamePrefix;
    }
  }
}