/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2015 
 * © 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 demolayoutgallery.base.*;
import ilog.views.animation.*;
import ilog.views.*;
import ilog.views.linkconnector.IlvClippingLinkConnector;
import ilog.views.util.java2d.IlvMultipleGradientPaint;
import ilog.views.util.java2d.IlvRadialGradientPaint;
import ilog.views.event.*;
import ilog.views.interactor.*;
import ilog.views.graphic.*;
import ilog.views.swing.*;

import ilog.views.graphlayout.*;
import ilog.views.graphlayout.grid.IlvGridLayout;
import ilog.views.graphlayout.circular.IlvCircularLayout;
import ilog.views.graphlayout.bus.IlvBusLayout;
import ilog.views.graphlayout.uniformlengthedges.IlvUniformLengthEdgesLayout;
import ilog.views.graphlayout.link.*;
import ilog.views.graphlayout.tree.*;
import ilog.views.graphlayout.topologicalmesh.*;
import ilog.views.graphlayout.hierarchical.*;

import javax.swing.*;

import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;

/**
 * The main class IlvLayoutGalleryApplet.
 * 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 IlvLayoutGalleryApplet
  extends LayoutApplet
{
  // --------------------------------------------------------------------------
  // 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_FOR_APPLET 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 (can be null for applets using a panel embedded in the page)
  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 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 applet
  // 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 for applets
  private static final boolean HELP_AT_START_UP_FOR_APPLET = false;

  // in applet mode, whether the main window should be embedded in
  // the page or standalone frame
  private static final boolean STANDALONE_MAIN_FRAME_FOR_APPLET = 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 applet or application

  /**
   * Initializes the demo.
   */
  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 &&
        !(isApplet() && !HELP_AT_START_UP_FOR_APPLET)) {
      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;
  }

  /**
   * Destroys the applet.
   */
  public void destroyDemo()
  {
    // destroy the subwindows

    if (helpHandler != null)
      helpHandler.dispose();
    helpHandler = null;

    if (overviewHandler != null)
      overviewHandler.dispose();
    overviewHandler = null;

    super.destroyDemo();

    // close the main window
    if (mainFrame != null) {
      setVisible(mainFrame, false, true);
      mainFrame = null;
    }
    mainContainer = null;
  }

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

    // register the visibility of the help and overview frames,
    // to be able to restore them correctly if the applet 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 applet or application.
   */
  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 && isApplet() && !HELP_AT_START_UP_FOR_APPLET))
      setVisible(helpHandler.getFrame(), visible);

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

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

  /**
   * Returns <code>true</code> if the main frame should be embedded
   * in the HTML page for applets, and returns <code>false</code> otherwise.
   */
  protected boolean isMainFrameStandaloneForApplet()
  {
    return STANDALONE_MAIN_FRAME_FOR_APPLET;
  }

  /**
   * 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() {
        public void run() {
          final IlvLayoutGalleryApplet layoutDemo = new IlvLayoutGalleryApplet();
          layoutDemo.setApplet(false);

          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.
   */
  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
    if (isMainFrameStandaloneForApplet() || !isApplet()) {
      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()) {
      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 = (String)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() {
        public void windowClosing(WindowEvent e) {
          exit();
          /*
          stop();
          destroy();
          if (!isApplet())
          System.exit(0);
          */
        }});
      // frame.setVisible(true);
    }
    
    if (isMainFrameStandaloneForApplet() || !isApplet()) {
      if (isAutoPlay()) {
        setVisible(mainFrame, true);
        if (overviewHandler != null)
          overviewHandler.toggleOverview(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) {
      public void afterVerifyTransformer() {
        if (!isAutoPlay()) {
         SwingUtilities.invokeLater(new Runnable() {
            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() {
       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();
    layoutChooser.setToolTipText("Select the layout algorithm");

    // item listener for layoutChooser and sizeChoser
    ItemListener sampleSelection = new ItemListener() {
      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();
    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 = (String)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 = (String)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() {
      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 links = new Vector();
    while (objs.hasMoreElements()) {
      g = objs.nextElement();
      if (grapher.isLink(g))
        links.addElement(g);
    }

    grapher.setContentsAdjusting(true);
    try {
      Enumeration e = links.elements();
      IlvLinkImage oldlink;
      IlvLinkImage newlink;
      while (e.hasMoreElements()) {
        oldlink = (IlvLinkImage)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 applet 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 applet, 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.Float(0.52f, 0.49f), 0.66f, stops, colors,
         new Point2D.Float(0.34f, 0.29f),
         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 applet

  /**
   * 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
  {
    public void actionPerformed(ActionEvent e) {
      setAnimation(animationButton.isSelected());
    }
  }

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

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

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

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

  public class ZoomBoxPermanentAction implements ActionListener
  {
    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 applet 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 applet 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() {
      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

  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.
   */
  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() {
          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
   */
  public void appendMessage(final String message, final boolean showLastLine)
  { 
    if (SwingUtilities.isEventDispatchThread())
      textArea.appendMessage(message, showLastLine);
    else {
      SwingUtilities.invokeLater(new Runnable() {
        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
  {
    IlvLayoutGalleryApplet applet;
    IlvApplyObject expandApplyObject;
    Cursor oldCursor;

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

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

    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;
    }

    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 && !applet.isBusy()) {
            if (applet.getExpandWorkCursor() != null) {
              if (view.getCursor() != applet.getExpandWorkCursor())
                view.setCursor(applet.getExpandWorkCursor());
            }
          }
          else {
            if (applet.getExpandIdleCursor() != null) {
              if (view.getCursor() != applet.getExpandIdleCursor())
                view.setCursor(applet.getExpandIdleCursor());
            }
          }
          break;
      }
      super.processMouseMotionEvent(event);
    }

    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 (applet.isBusy()) {
        manager.setSelected(obj, false, false);
        return;
      }

      if (expandApplyObject == null)
        expandApplyObject = new IlvApplyObject() {
          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;
    }
  }
}