/*
 * 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.
 */

import ilog.views.*;
import ilog.views.interactor.*;
import ilog.views.graphic.*;
import ilog.views.graphic.linkbundle.IlvLinkBundle;
import ilog.views.graphic.linkbundle.IlvLinkBundleFrame;
import ilog.views.graphic.linkbundle.IlvDefaultLinkBundleFrame;
import ilog.views.graphic.composite.*;
import ilog.views.graphic.composite.layout.*;
import ilog.views.swing.*;
import ilog.views.util.IlvImageUtil;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.IlvLocaleUtil;
import ilog.views.util.swing.IlvSwingUtil;
import ilog.views.util.swing.IlvJSpinnerDecimalNumberField;

import ilog.views.linkconnector.*;

import ilog.views.graphlayout.*;
import ilog.views.graphlayout.link.*;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.text.*;

import javax.swing.*;

/**
 * The main class LinkBundleApplet. This demonstrates the class 
 * <code>ilog.views.graphic.linkbundle.IlvLinkBundle</code>.
 */
public class LinkBundleApplet
  extends JApplet
{
  static {
    // This applet is designed to run only with default resource bundle
    // and various selected other resource bundles.
    // Setting the available resource suffixes avoids that the applet
    // tries to load resource bundles for other locales over the net,
    // even if the current locale of the browser is different.
    if (IlvResourceUtil.isInApplet())
      IlvResourceUtil.setAvailableResourceSuffixes("", "_ja");

    // Test code whether the demo works in RTL locales
    // Locale newLocale = new Locale("he");
    // IlvSwingUtil.setDefaultLocale(newLocale);
  }

  {
    // This sample uses JViews Diagrammer features. When deploying an
    // application that includes this code, you need to be in possession
    // of a JViews Diagrammer Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(
        IlvProductUtil.JViews_Diagrammer_Deployment);
  }

  /** The grapher */
  IlvGrapher grapher = new IlvGrapher();

  /** The view of the grapher */
  IlvManagerView mgrview = new IlvManagerView(grapher, null);

  /** The control bar */
  IlvJManagerViewControlBar controlBar = new IlvJManagerViewControlBar();

  /** An instance of the Link Layout algorithm */
  IlvLinkLayout linkLayout = new IlvLinkLayout();

  private int linkStyle = ORTHOGONAL_LINK_STYLE;

  /** counter just to have newly create links in nice colors */
  int colorCounter = 0;

  private JCheckBox singleConnectionPointCheckbox =
    new JCheckBox(getString("singleConnectionPointCheckbox.Label"), false);
  private boolean singleConnectionPoint = false;
  private boolean linkBundleFrameEnabled = true;
  private boolean withNestedBundles = false;
  private float offset = 5f;
  private float frameMargin = 3f;

  private boolean expandedState = true;
  private boolean subbundleFlag = true;

  private static final float NODE_WIDTH = 100;
  private static final float NODE_HEIGHT = 100;
  private static final float SUBLINKS_WIDTH = 5;
  private static final float OVERVIEW_LINK_WIDTH = 15;
  private static final int N_SUBLINKS_IN_TOP_BUNDLE = 3;
  private static final int N_SUBLINKS_IN_SUB_BUNDLE = 2;

  private static int NODE_LAYER = 0;
  private static int LINK_LAYER = 1;

  // link style options
  private static final int ORTHOGONAL_LINK_STYLE = 0;
  private static final int DIRECT_LINK_STYLE = 1;
  private static final int STRAIGHT_LINK_STYLE = 2;

  // link connector modes
  private static final int FREE_LINK_CONNECTOR_ON_NODES = 0;
  private static final int CLIPPING_LINK_CONNECTOR_ON_NODES = 1;
  private static final int CLIPPING_LINK_CONNECTOR_ON_NODES_AND_FREE_ON_SUBLINKS = 2;
  
  // Colors
  private static final Color NODE_COLOR = new Color(154, 104, 3);
  private static final Color OVERVIEW_LINK_COLOR = new Color(3, 47, 189);
  private static final Color SUBLINK_COLOR = OVERVIEW_LINK_COLOR;
  private static final Color TOP_BUNDLE_FRAME_COLOR = Color.yellow;
  private static final Color SUB_BUNDLE_FRAME_COLOR1 = new Color(186, 186, 186);
  private static final Color SUB_BUNDLE_FRAME_COLOR2 = new Color(198, 233, 244);

  private static final boolean TOOLTIPS_ENABLED = true;
  private static final boolean WITH_CONTROL_BAR = true;

  /**
   * Initializes the applet/application.
   */
  public void init()
  {
    super.init();

    // Not needed here: enable the expand/collapse icons
    // mgrview.setCollapseExpandIconsEnabled(true);

    // attach the grapher to the layout instances
    IlvGrapherAdapter adapter = new IlvGrapherAdapter(grapher);
    linkLayout.setAutoLayout(true);
    linkLayout.setLinkOffset(4);
    linkLayout.setMinFinalSegmentLength(10); 
    linkLayout.attach(adapter);

    // for the pop-up menu
    // register view view to the pop-up manager
    IlvPopupMenuManager.registerView(mgrview);

    // register the menus under different names
    IlvPopupMenuManager.registerMenu("COLLAPSE_BUNDLE", createCollapseMenu());
    IlvPopupMenuManager.registerMenu("EXPAND_BUNDLE", createExpandMenu());

    // create the scroll manager view
    IlvJScrollManagerView scrollManView = new IlvJScrollManagerView(mgrview);

    // Some settings on the manager view and on the scroll manager view
    mgrview.setAntialiasing(true);
    mgrview.setKeepingAspectRatio(true);
    mgrview.setBackground(Color.white);
    mgrview.setForeground(SystemColor.windowText);
    Color xc = SystemColor.windowText;
    xc = new Color(255-xc.getRed(), 255-xc.getGreen(), 255-xc.getBlue());
    mgrview.setDefaultXORColor(xc);
    mgrview.setDefaultGhostColor(SystemColor.windowText);
    mgrview.setWheelZoomingEnabled(true);
    scrollManView.setWheelScrollingEnabled(true);
    
    if (TOOLTIPS_ENABLED)
      IlvToolTipManager.registerView(mgrview);

    // Settings parameters for selection handles
    IlvHandlesSelection.defaultHandleColor = Color.black;
    IlvHandlesSelection.defaultHandleBackgroundColor = Color.white;
    IlvHandlesSelection.defaultHandleShape = IlvHandlesSelection.SQUARE_SHAPE;

    // set the layout manager
    JPanel topPanel = new JPanel();
    JPanel bottomPanel = new JPanel();
    JPanel bottomPanel1 = new JPanel();
    JPanel bottomPanel2 = new JPanel();
    topPanel.setLayout(new FlowLayout(FlowLayout.LEADING));
    bottomPanel.setLayout(new GridLayout(2,0));
    bottomPanel1.setLayout(new FlowLayout(FlowLayout.LEADING));
    bottomPanel2.setLayout(new FlowLayout(FlowLayout.LEADING));
    bottomPanel.add(bottomPanel1);
    bottomPanel.add(bottomPanel2);

    // fit so far all together
    getContentPane().setLayout(new BorderLayout(0,0));
    getContentPane().add("Center", scrollManView);
    getContentPane().add("North", topPanel);
    getContentPane().add("South", bottomPanel);

    // create the standard control bar
    if (WITH_CONTROL_BAR) {
      controlBar.setView(mgrview);
      // modify the interactors such that the demo looks better
      IlvSelectInteractor selInteractor =
        (IlvSelectInteractor)controlBar.getSelectInteractor();
      selInteractor.setOpaqueMove(true);
      selInteractor.setOpaqueResize(true);
      selInteractor.setOpaquePolyPointsEdition(true);
      ((IlvZoomViewInteractor)controlBar.getZoomViewInteractor()).
        setPermanent(true);

      topPanel.add(controlBar);
    }

    // accelerator to allow deletion of bends and entire nodes and links
    grapher.addAccelerator(
        new IlvAccelerator(KeyEvent.KEY_PRESSED, KeyEvent.VK_DELETE, 0) {
          protected boolean  handleEvent(IlvManagerView v)
          {
            grapher.deleteSelections(true, true);
            return true;
          }
    });

    // --------------------------------------------------------

    // Add the sample graph
    createSampleGraph(true, true, false, false, isLinkBundleFrameEnabled());

    // -------------------------------------------------------

    // set the initial interactor
    if (WITH_CONTROL_BAR)
      mgrview.setInteractor(controlBar.getSelectInteractor());

    // create the layout buttons
    JButton b;
    String tooltip;

    // ----------------------------------------
    // Link style 

    bottomPanel1.add(new JLabel(getString("linkStyleChooser.Label")));
    JComboBox linkStyleChooser = new JComboBox();
    bottomPanel1.add(linkStyleChooser);
    linkStyleChooser.setToolTipText(getString("linkStyleChooser.Tooltip"));

    linkStyleChooser.addItem(getString("linkStyleChooser.OrthMode"));
    linkStyleChooser.addItem(getString("linkStyleChooser.DirectMode"));
    linkStyleChooser.addItem(getString("linkStyleChooser.StraightMode"));

    linkStyleChooser.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == event.DESELECTED)
          return;
        String styleName = (String)event.getItem();

        if (styleName.equals(getString("linkStyleChooser.OrthMode")))
          setLinkStyle(ORTHOGONAL_LINK_STYLE);
        else if (styleName.equals(getString("linkStyleChooser.DirectMode")))
          setLinkStyle(DIRECT_LINK_STYLE);
        else if (styleName.equals(getString("linkStyleChooser.StraightMode")))
          setLinkStyle(STRAIGHT_LINK_STYLE);
        else
          throw new RuntimeException("Unsupported link style: " + styleName);
      }
    });

    setLinkStyle(getLinkStyle());

    // ----------------------------------------
    // Single connection point

    bottomPanel1.add(singleConnectionPointCheckbox);
    singleConnectionPointCheckbox.setToolTipText(
      getString("singleConnectionPointCheckbox.Tooltip"));
    singleConnectionPointCheckbox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        boolean single = false;
        boolean selectionChanged = false;
        if (e.getStateChange() == ItemEvent.SELECTED) {
          selectionChanged = true;
          single = true;
        } else if (e.getStateChange() == ItemEvent.DESELECTED) {
          selectionChanged = true;
          single = false;
        }

        if (selectionChanged) 
          setSingleConnectionPoint(single, true);
      }
    });

    // --------------------------------------------
    // Offset

    JLabel offsetLabel = new JLabel(getString("Offset.Label"));
    tooltip = getString("Offset.Tooltip");
    offsetLabel.setToolTipText(tooltip);
    bottomPanel1.add(offsetLabel);
    final IlvJSpinnerDecimalNumberField offsetField =
      new IlvJSpinnerDecimalNumberField(0,99,getOffset(),true,4,1);
    offsetField.setToolTipText(tooltip);
    bottomPanel1.add(offsetField);
    offsetField.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        final float value = (float)offsetField.getDoubleValue();
        setOffset(value);

        applyToAllLinkBundles(
          grapher,
          new IlvApplyObject() {
            public void apply(IlvGraphic obj, Object arg) {
              ((IlvLinkBundle)obj).setOffset(value);
            }
          });
      }});

    // -----------------------------------------------
    // Link Bundle Frame

    final JPanel bundleFramePanel = new JComfortPanel();

    final JLabel marginLabel = new JLabel(getString("frameMargin.Label"));
    tooltip = getString("frameMargin.Tooltip");
    marginLabel.setToolTipText(tooltip);
    final IlvJSpinnerDecimalNumberField marginField =
      new IlvJSpinnerDecimalNumberField(0,99,getFrameMargin(),true,4,1);
    marginField.setToolTipText(tooltip);
    marginField.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        final float value = (float)marginField.getDoubleValue();
        setFrameMargin(value);

        applyToAllLinkBundles(
          grapher,
          new IlvApplyObject() {
            public void apply(IlvGraphic obj, Object arg) {
              IlvLinkBundle linkBundle = (IlvLinkBundle)obj;
              IlvLinkBundleFrame frame = linkBundle.getFrame();
              if (frame instanceof IlvDefaultLinkBundleFrame)
                ((IlvDefaultLinkBundleFrame)frame).setMargin(value);
            }
          });
      }});

    final JCheckBox bundleFrameCheckbox =
      new JCheckBox(getString("bundleFrameCheckbox.Label"), true);
    bundleFrameCheckbox.setToolTipText(getString("bundleFrameCheckbox.Tooltip"));
    bundleFrameCheckbox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        // specify the new link style option
        if (e.getStateChange() == ItemEvent.SELECTED) {
          setLinkBundleFrameEnabled(true);
          marginLabel.setEnabled(true);
          marginField.setEnabled(true);
        } else if (e.getStateChange() == ItemEvent.DESELECTED) {
          setLinkBundleFrameEnabled(false);
          marginLabel.setEnabled(false);
          marginField.setEnabled(false);
        }

        // reapply the layout
        layout(linkLayout);
      }
    });

    bundleFramePanel.add(bundleFrameCheckbox);
    bundleFramePanel.add(marginLabel);
    bundleFramePanel.add(marginField);
    bottomPanel2.add(bundleFramePanel);

    // ----------------------------------------
    // Expand/collapse button

    bottomPanel2.add(b = new JButton(getString("collapseButton.Label")));
    refreshExpandCollapseButton(b, expandedState);
    final JButton expandCollapseButton = b;
    b.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        expandedState = !expandedState;
        refreshExpandCollapseButton(expandCollapseButton, expandedState);
        bundleFramePanel.setEnabled(expandedState);
        if (expandedState && !bundleFrameCheckbox.isSelected()) {
          marginLabel.setEnabled(false);
          marginField.setEnabled(false);
        }

        setCollapsed(!expandedState);

        grapher.reDraw();
      }});

    // -------------------------------------------
    // Top panel

    topPanel.add(new JLabel(getString("fileChooser.Label")));
    JComboBox chooser;
    topPanel.add(chooser = new JComboBox());
    chooser.setToolTipText(getString("fileChooser.Tooltip"));
    chooser.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == event.DESELECTED)
          return;
        String sampleName = (String)event.getItem();
        boolean withFrame = isLinkBundleFrameEnabled();

        if (sampleName.equals(getString("sample.CompositeLinks.Name")))
          createSampleGraph(true, true, false, false, withFrame);
        else if (sampleName.equals(getString("sample.SimpleLinks.Name")))
          createSampleGraph(false, false, false, false, withFrame);
        else if (sampleName.equals(getString("sample.Nested.Name")))
          createSampleGraph(false, false, true, false, withFrame);
        else if (sampleName.equals(getString("sample.Spline.Name")))
          createSampleGraph(false, false, true, true, withFrame);
        else
          throw new RuntimeException("Unsupported sample name: " + sampleName);

        expandedState = true;
        refreshExpandCollapseButton(expandCollapseButton, true);
        bundleFramePanel.setEnabled(true);
      }
    });

    chooser.addItem(getString("sample.CompositeLinks.Name"));        
    chooser.addItem(getString("sample.SimpleLinks.Name"));    
    chooser.addItem(getString("sample.Nested.Name"));
    chooser.addItem(getString("sample.Spline.Name"));

    // set the component orientation according to the locale
    Locale loc = IlvSwingUtil.getDefaultLocale();
    ComponentOrientation co = ComponentOrientation.getOrientation(loc);
    getContentPane().applyComponentOrientation(co);
  }

  /**
   * Called when this applet is being reclaimed in order to destroy
   * any resources that it has allocated.
   */
  public void destroy()
  {
    super.destroy();

    // This method is intended to workaround memory management issues
    // in the Sun JRE. Please refer to the method documentation for more
    // details and a description of the known issues.
    IlvSwingUtil.cleanupApplet();
  }

  // For debug only
  private String bbox(IlvGraphic obj)
  {
    IlvRect bbox = obj.boundingBox(null);
    return
     "x: min=" + bbox.x + " max=" + (bbox.x + bbox.width) +
      " y: min=" + bbox.y + " max=" + (bbox.y + bbox.height);
  }

  /**
   * Expands collapsed bundles and collapses expanded bundles.
   */
  private void setCollapsed(final boolean collapsed)
  {
    boolean autoLayout = linkLayout.isAutoLayout();

    // avoid that the link layout executes too early, before all the
    // bundles get expanded or collapsed
    linkLayout.setAutoLayout(false);

    applyToAllLinkBundles(
      grapher,
      new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          IlvLinkBundle linkBundle = (IlvLinkBundle)obj;
          linkBundle.setCollapsed(collapsed);
        }
      });
    
    // just to ensure the clipping, if any, is done towards the center of the nodes 
    connectLinks();
    
    layout(linkLayout);

    // restore autolayout flag
    linkLayout.setAutoLayout(autoLayout);
  }

  /**
   * Refreshes the button for expanding/collapsing link bundles.
   */
  private void refreshExpandCollapseButton(JButton button,
                                           boolean expand)
  {
    button.setText(expand ? getString("collapseButton.Label")
                          : getString("expandButton.Label"));
    button.setToolTipText(expand ? getString("collapseButton.Tooltip")
                                 : getString("expandButton.Tooltip"));
  }

  /**
   * Enables or disables the link bundle frame.
   */
  private void setLinkBundleFrameEnabled(final boolean enable)
  {
    linkBundleFrameEnabled = enable;

    applyToAllLinkBundles(
      grapher,
      new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          IlvLinkBundle linkBundle = (IlvLinkBundle)obj;
          IlvLinkBundleFrame frame = linkBundle.getFrame();
          if (enable) {
            if (frame == null)
              installLinkBundleFrame(
                linkBundle,
                (linkBundle.getGraphicBag() instanceof IlvLinkBundle) ?
                (subbundleFlag ? SUB_BUNDLE_FRAME_COLOR1 :
                 SUB_BUNDLE_FRAME_COLOR2) :
                TOP_BUNDLE_FRAME_COLOR);
            // else nothing to do
          } else {
            if (frame != null)
              linkBundle.setFrame(null);
            // else nothing to do
          }
        }
      });
  }

  private boolean isLinkBundleFrameEnabled()
  {
    return linkBundleFrameEnabled;
  }

  /**
   * Installs a frame on the link bundle.
   */
  private void installLinkBundleFrame(IlvLinkBundle linkBundle,
                                      Color color)
  {
    IlvDefaultLinkBundleFrame defFrame =
      new IlvDefaultLinkBundleFrame();
    color = new Color(color.getRed(), color.getGreen(), color.getBlue(), 160);
    defFrame.setBackground(color);
    defFrame.setMargin(getFrameMargin());
    if (linkBundle.getOverviewLink() instanceof IlvCompositeLink) {
      // no expand/collapse icon at composite to avoid too complex L&F
      defFrame.setExpandCollapseIcon(null);
    }
    linkBundle.setFrame(defFrame);
  }

  private void setLinkStyle(int style)
  {
    if (style == this.linkStyle)
      return;

    this.linkStyle = style;

    switch (style) {
      case ORTHOGONAL_LINK_STYLE:
        linkLayout.setAutoLayout(true);
        linkLayout.setGlobalLinkStyle(IlvLinkLayout.ORTHOGONAL_STYLE);
        break;
      case DIRECT_LINK_STYLE:
        linkLayout.setAutoLayout(true);
        linkLayout.setGlobalLinkStyle(IlvLinkLayout.DIRECT_STYLE);
        break;
      case STRAIGHT_LINK_STYLE:
        // no link layout is needed in this style
        linkLayout.setAutoLayout(false);
        deleteIntermediateLinkPoints(false);
        break;
      default:
        throw new IllegalArgumentException("Unsupported style: " + style);
    }
    
    installLinkConnectors();    

    layout(linkLayout);

    grapher.reDraw();
  }

  private void deleteIntermediateLinkPoints(boolean redraw)
  {
    // remove all intermediate points from all links
    final IlvGraphModel graphModel = linkLayout.getGraphModel();
    IlvGraphicEnumeration e = grapher.getObjects();
    IlvGraphic g;
    while (e.hasMoreElements()) {
      g = e.nextElement();
      if (grapher.isLink(g)) {
        IlvLinkImage link = (IlvLinkImage)g;
        try {
          graphModel.reshapeLink(link, null, null, 0, 0, null, redraw);
        } catch (IlvInappropriateLinkException ex) {
          ex.printStackTrace();
        }
      }
    }
  }

  private int getLinkStyle()
  {
    return linkStyle;
  }

  private void setSingleConnectionPoint(boolean single, boolean applyChange)
  {
    this.singleConnectionPoint = single;
    
    if (!applyChange)
      return;
    
    final boolean singleFinal = single;
    applyToAllLinkBundles(
      grapher,
      new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          ((IlvLinkBundle)obj).setSingleConnectionPoint(singleFinal);
        }
      });
       
    connectLinks();
   
    layout(linkLayout);

    /* For debug
     applyToAllLinkBundles(
      grapher,
      new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          IlvLinkBundle bundle = (IlvLinkBundle)obj;
          IlvGraphicEnumeration sublinks = bundle.getSublinksEnum();
          int counter = 0;
          IlvGraphic from = bundle.getFrom();
          IlvGraphic to = bundle.getTo();
          System.out.println("From " + bbox(from));
          System.out.println("  To " + bbox(to));
          IlvPoint[] bends = bundle.getLinkPoints(null);
          for (int i = 0; i < bends.length; i++) {
            IlvPoint point = bends[i];
            System.out.println("  p[" + i + "] = " +
                               point.x + ", " + point.y);
          }
          while (sublinks.hasMoreElements()) {
            IlvLinkImage sublink = (IlvLinkImage)sublinks.nextElement();
            bends = sublink.getLinkPoints(null);
            System.out.println("Sublink[" + counter++ + "]:");
            for (int i = 0; i < bends.length; i++) {
              IlvPoint point = bends[i];
              System.out.println("  p[" + i + "] = " +
                                 point.x + ", " + point.y);
            }
          }
        }
      }); */    
  }

  private boolean isSingleConnectionPoint()
  {
    return singleConnectionPoint;
  }

  private void setOffset(float offset)
  {
    this.offset = offset;
  }

  private float getOffset()
  {
    return offset;
  }

  private void setFrameMargin(float frameMargin)
  {
    this.frameMargin = frameMargin;
  }

  private float getFrameMargin()
  {
    return frameMargin;
  }

  /**
   * Add a toggle button to the input component.
   */
  JToggleButton addButton(JComponent component,
                          String imageName,
                          String toolTip,
                          boolean initialSelected,
                          ActionListener action)
  {
    JToggleButton b = new JToggleButton() {
      public Dimension getPreferredSize () { return new Dimension(26,26); }
      public Dimension getMinimumSize ()   { return new Dimension(26,26); }
      public Dimension getMaximumSize ()   { return new Dimension(26,26); }
    };
    Image image = getImageFromFile(imageName);
    if (image != null)
      b.setIcon(new ImageIcon(image));
    b.setToolTipText(toolTip);
    b.setSelected(initialSelected);
    b.addActionListener(action);
    component.add(b);
    return b;
  }

  private Image getImageFromFile(String imageName)
  {
    try {
      return IlvImageUtil.getImageFromFile(LinkBundleApplet.class,
                                      "/gif/" + imageName);
    } catch (Exception ex) {
      ex.printStackTrace();
      // System.err.println("Warning: error during read for image " + imageName);
      return null;
    }
  }

  /**
   * Applies a change recursivelly to all <code>IlvLinkBundle</code> objects
   * contained in <code>grapher</code>.
   */
  private void applyToAllLinkBundles(IlvGrapher graph,
                                     IlvApplyObject apply)
  {
    applyToAllLinkBundles(graph, apply, true);
  }

  /**
   * Applies a change to all <code>IlvLinkBundle</code> objects contained
   * in <code>grapher</code>.
   */
  private void applyToAllLinkBundles(IlvGraphicBag bag,
                                     IlvApplyObject apply,
                                     boolean recursive)
  {
    IlvGraphicEnumeration objects = bag.getObjects();
    while (objects.hasMoreElements()) {
      IlvGraphic obj = objects.nextElement();
      if (obj instanceof IlvLinkBundle) {
        // Apply on children first, because when applying to
        // a given link bundle the children we want the children
        // to be already processed. E.g. when switching on/off 
        // the link bundle frame, the layout of a given bundle
        // needs to take into account the frame of the nested bundles.
        if (recursive) {
          IlvLinkBundle linkBundle = (IlvLinkBundle)obj;
          applyToAllLinkBundles(linkBundle, apply, recursive);
        }
        bag.applyToObject(obj, apply, null, false);        
      }
    }

    if (bag instanceof IlvGrapher)
      ((IlvGrapher)bag).reDraw();
  }
 
  /**
   * Installs the appropriate link connectors on each link 
   * contained in <code>grapher</code>.
   */
  private void installAppropriateLinkConnector(IlvGrapher grapher)  
  {
    int clippingMode = -1;
    if (isSingleConnectionPoint()) {
      if (withNestedBundles || (getLinkStyle() == STRAIGHT_LINK_STYLE))
        clippingMode = CLIPPING_LINK_CONNECTOR_ON_NODES_AND_FREE_ON_SUBLINKS;
      else
        clippingMode = CLIPPING_LINK_CONNECTOR_ON_NODES;
    } else if (getLinkStyle() == STRAIGHT_LINK_STYLE)
      clippingMode = CLIPPING_LINK_CONNECTOR_ON_NODES;
    else
      clippingMode = FREE_LINK_CONNECTOR_ON_NODES;
        
    installAppropriateLinkConnector(grapher, grapher, clippingMode);    
  }
  
  private void installAppropriateLinkConnector(IlvGraphicBag bag,
      IlvGrapher topLevelGrapher,
      int clippingMode)  
  {
    if (bag == null)
      return;
    IlvGraphicEnumeration objects = bag.getObjects();
    while (objects.hasMoreElements()) {
      IlvGraphic obj = objects.nextElement();
      
      if (obj instanceof IlvLinkBundle) {
        IlvLinkBundle linkBundle = (IlvLinkBundle)obj;
        IlvLinkImage overview = linkBundle.getOverviewLink();
        
        boolean freeConnector = 
          clippingMode == CLIPPING_LINK_CONNECTOR_ON_NODES_AND_FREE_ON_SUBLINKS && 
          bag != topLevelGrapher;         
          
        // children BEFORE parent
        installAppropriateLinkConnector(linkBundle, topLevelGrapher, 
                                        clippingMode);     
        
        installAppropriateLinkConnector(linkBundle, freeConnector);  
        
        linkBundle.invalidateLayout();
        
      } else if (obj instanceof IlvLinkImage) {
        boolean freeConnector = 
          clippingMode == CLIPPING_LINK_CONNECTOR_ON_NODES_AND_FREE_ON_SUBLINKS && 
          bag != topLevelGrapher;         
          
        installAppropriatePerLinkLinkConnector((IlvLinkImage)obj, freeConnector);
        
        if (obj instanceof IlvCompositeLink) {
          IlvLinkImage overview = ((IlvCompositeLink)obj).getLink();
          if (overview != null)
            installAppropriatePerLinkLinkConnector(overview, freeConnector);
        }
      } else // it's a node 
        installAppropriateNodeLinkConnector(obj, clippingMode);
      
    }

    if (bag instanceof IlvGrapher)
      ((IlvGrapher)bag).reDraw();
  }  
  
  private void installAppropriateLinkConnector(IlvLinkBundle linkBundle, 
      boolean freeConnector)
  {
    IlvLinkImage overview = linkBundle.getOverviewLink();
    installAppropriatePerLinkLinkConnector(linkBundle, freeConnector); 
    if (overview != null)
      installAppropriatePerLinkLinkConnector(overview, freeConnector);       
  }
  
  private void installAppropriatePerLinkLinkConnector(IlvLinkImage link, 
      boolean freeConnector)
  {
    installAppropriatePerLinkLinkConnector(link, freeConnector, true);
    installAppropriatePerLinkLinkConnector(link, freeConnector, false);
  }
  
  private void installAppropriatePerLinkLinkConnector(IlvLinkImage link, 
      boolean freeConnector, boolean origin)
  {
    IlvLinkConnector lc = IlvLinkConnector.GetAttached(link, origin);
    if (freeConnector) {
      if (lc == null || 
          !(lc instanceof IlvFreeLinkConnector) ||
          (lc instanceof IlvClippingLinkConnector))
        new IlvFreeLinkConnector(link, origin);           
    } else {
      if (lc != null)
        lc.detach(link, origin, false/*redraw*/);            
    }
  }
  
  private void installAppropriateNodeLinkConnector(IlvGraphic node, int clippingMode)
  {
    IlvLinkConnector lc = IlvLinkConnector.GetAttached(node);
    switch (clippingMode) {
      case FREE_LINK_CONNECTOR_ON_NODES:
        if (lc == null || 
            (!(lc instanceof IlvFreeLinkConnector) ||
              (lc instanceof IlvClippingLinkConnector)))
                new IlvFreeLinkConnector(node);
        break;
      case CLIPPING_LINK_CONNECTOR_ON_NODES:
      case CLIPPING_LINK_CONNECTOR_ON_NODES_AND_FREE_ON_SUBLINKS:
        // for both clipping modes:
        if (lc == null ||             
            !(lc instanceof IlvClippingLinkConnector))
                new IlvClippingLinkConnector(node);
        break;
      default:
        // nothing
    }
  }
  
  /**
   * Allows you to run the demo as a standalone application.
   */
  public static void main(String[] arg)
  {
    // Sun recommends that to put the entire GUI initialization into the
    // AWT thread
    SwingUtilities.invokeLater(
      new Runnable() {
        public void run() {
          LinkBundleApplet applet = new LinkBundleApplet();
          applet.init();       

          JFrame frame = new JFrame(getString("Frame.Label"));
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.setSize(550, 650);
          frame.getContentPane().add(applet);
          frame.setVisible(true);
        }
      });
  }

  /**
   * Installs the link connectors on all nodes.
   */
  private void installLinkConnectors()
  {
    applyToAllNodes(
      grapher,
      new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          installLinkConnector(obj);
        }
      });

    connectLinks();
  }
  
  private void connectLinks()
  {
    // Installing or reinstalling the link connectors requires
    // the internal layout of each bundle to be redone, hence:
    applyToAllLinkBundles(
      grapher,
      new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          IlvLinkBundle linkBundle = (IlvLinkBundle)obj;
          connectLink(linkBundle);
          linkBundle.invalidateLayout();
        }
      });
    installAppropriateLinkConnector(grapher);
  }
  
  /**
   * Installs the appropriate link connectors on a node.
   */
  private void installLinkConnector(IlvGraphic node)
  {
    if (linkStyle == STRAIGHT_LINK_STYLE)
      new IlvClippingLinkConnector(node);
    else
      new IlvFreeLinkConnector(node); 
  }

  /**
   * Applies something to all nodes.
   */
  private void applyToAllNodes(IlvGrapher graph, IlvApplyObject f)
  {
    IlvGraphicEnumeration e = graph.getObjects();
    IlvGraphic g;
    while (e.hasMoreElements()) {
      g = e.nextElement();
      if (graph.isNode(g))
        graph.applyToObject(g, f, null, true);
    }
  }

  /**
   * Performs the layout of the graph.
   */
  private void layout(IlvGraphLayout layout)
  {
    if (getLinkStyle() == STRAIGHT_LINK_STYLE)
      return; // nothing to do in this case

    try {
      // perform the layout
      layout.performLayout(false, true);
    }
    catch (IlvGraphLayoutException ex) {
      ex.printStackTrace();
    }
  }

  private IlvCompositeLink createCompositeLink(IlvGraphic origin,
                                               IlvGraphic dest,
                                               IlvLinkImage overviewLink,
                                               String label,
                                               float lineWidth,
                                               Color color)
  {
    IlvCompositeLink clink = new IlvCompositeLink(origin, dest, false);
    if (overviewLink != null)
      clink.setLink(overviewLink);
    clink.setLineWidth(lineWidth);
    if (color != null)
      clink.setForeground(color);
    clink.setEndCap(IlvStroke.CAP_BUTT);
    clink.setLineJoin(IlvStroke.JOIN_ROUND);
    // grapher.addLink(clink,false);

    // Creates a plinth rectangle with a label
    IlvCompositeGraphic plinth = new IlvCompositeGraphic();
    plinth.setLayout(new IlvCenteredLayout(new Insets(4,10,4,10)));

    IlvRectangle rect = new IlvReliefRectangle(new IlvRect(0,0,40,40));
    rect.setBackground(Color.red);
    rect.setToolTipText(getString("rect.Tooltip"));
    plinth.setChildren(0, rect);
    if (label != null) {
      IlvZoomableLabel name =
        new IlvZoomableLabel(new IlvPoint(), label, false);
      plinth.setChildren(1, name);
    }

    // Adds the plinth to the link
    clink.setChildren(0, plinth);
    clink.setConstraints(0,
                         new IlvAttachmentConstraint(
                           IlvAttachmentLocation.CenterPostOrderFirst,
                           IlvLinkAttachmentLocation.MiddleLink));

    // Creates a stack of small rectangles
    IlvCompositeGraphic stacker = new IlvCompositeGraphic();
    stacker.setLayout(new IlvStackerLayout());

    // Creates a tiny blue rectangle
    IlvRectangle r1 = new IlvRectangle(new IlvRect(0,0,10,10));
    r1.setToolTipText(getString("r1.Tooltip"));
    r1.setBackground(Color.blue);
    r1.setForeground(Color.blue);
    r1.setFillOn(true);
    stacker.setChildren(0,r1);

    // Creates a tiny red rectangle
    IlvRectangle r2 = new IlvRectangle(new IlvRect(0,0,10,10));
    r2.setBackground(Color.red);
    r2.setForeground(Color.red);
    r2.setFillOn(true);
    stacker.setChildren(1,r2);

    // Creates a tiny green rectangle
    IlvRectangle r3 = new IlvRectangle(new IlvRect(0,0,10,10));
    r3.setBackground(Color.green);
    r3.setForeground(Color.green);
    r3.setFillOn(true);
    stacker.setChildren(2,r3);

    // Adds the stack of rectangles
    clink.setChildren(1,stacker);
    // IlvLinkAttachmentLocation.NearFromLink --> hotSpot, IlvLinkAttachmentLocation.FromLink --> anchor
    clink.setConstraints(1,
                         new IlvAttachmentConstraint(
                           IlvLinkAttachmentLocation.NearFromLink,
                           IlvLinkAttachmentLocation.FromLink));


    IlvIcon ad_hoc = new IlvIcon(getImageFromFile("ad-hoc-marker.gif"),
                                 new IlvRect(0, 0, 16,7));
    clink.setChildren(2, ad_hoc);
    clink.setConstraints(2,
                         new IlvAttachmentConstraint(
                           IlvLinkAttachmentLocation.NearToLink,
                           IlvLinkAttachmentLocation.ToLink));

    return clink;
  }

  /**
   * Creates the sample graph containing link bundles.
   * @param withCompositeSublinks If true, composite links are used for
   *    some sublinks.
   * @param withCompositeOverview If true, composite links are used for
   *    the overview links of the link bundles.
   */
  private void createSampleGraph(boolean withCompositeSublinks,
                                 boolean withCompositeOverview,
                                 boolean withNestedBundles,
                                 boolean withSplines,
                                 boolean withFrame)
  {
    this.withNestedBundles = withNestedBundles;
    
    // Clean the existing content (if any)
    grapher.deleteAll(false);

    // Creates the nodes
    IlvGraphic node1 = createNode(50, 50);
    IlvGraphic node2 = createNode(350, 50);
    IlvGraphic node3 = createNode(300, 300);

    // Add the nodes to the grapher
    grapher.addNode(node1, NODE_LAYER, false);
    grapher.addNode(node2, NODE_LAYER, false);
    grapher.addNode(node3, NODE_LAYER, false);

    // Create the link bundles
    IlvLinkBundle linkBundle1 =
      createLinkBundle(node1, node2,
                       withCompositeSublinks,
                       withCompositeOverview,
                       withNestedBundles ?
                       N_SUBLINKS_IN_SUB_BUNDLE : N_SUBLINKS_IN_TOP_BUNDLE,
                       withNestedBundles,
                       withSplines,
                       withFrame,
                       TOP_BUNDLE_FRAME_COLOR);

    IlvLinkBundle linkBundle2 =
      createLinkBundle(node2, node3,
                       withCompositeSublinks,
                       withCompositeOverview,
                       N_SUBLINKS_IN_TOP_BUNDLE,
                       false,
                       withSplines,
                       withFrame,
                       TOP_BUNDLE_FRAME_COLOR);

    IlvLinkBundle linkBundle3 =
      createLinkBundle(node1, node3,
                       withCompositeSublinks,
                       withCompositeOverview,
                       withNestedBundles ?
                       N_SUBLINKS_IN_SUB_BUNDLE : N_SUBLINKS_IN_TOP_BUNDLE,
                       withNestedBundles,
                       withSplines,
                       withFrame,
                       TOP_BUNDLE_FRAME_COLOR);

    // Add the link bundles to the grapher
    grapher.addLink(linkBundle1, LINK_LAYER, false);
    grapher.addLink(linkBundle2, LINK_LAYER, false);
    grapher.addLink(linkBundle3, LINK_LAYER, false);

    grapher.reDraw();
 
    connectLinks();
  }

  private IlvLinkBundle createLinkBundle(IlvGraphic origin,
                                         IlvGraphic dest,
                                         boolean withCompositeSublinks,
                                         boolean withCompositeOverview,
                                         int nSublinks,
                                         boolean withNestedBundles,
                                         boolean withSplines,
                                         boolean withFrame,
                                         Color frameColor)
  {
    // Creates the link responsible for the rendering
    IlvLinkImage overviewLink =
      createOverviewLink(origin, dest, withCompositeOverview, withSplines);
    overviewLink.setPopupMenuName("EXPAND_BUNDLE");

    IlvLinkBundle linkBundle =
      new IlvLinkBundle(origin, dest,
                        overviewLink.isOriented(),
                        null,
                        overviewLink);

    linkBundle.setToolTipText(getString("linkBundle.Tooltip"));
    
    if (withFrame)
      installLinkBundleFrame(linkBundle, frameColor);

    linkBundle.setPopupMenuName("COLLAPSE_BUNDLE");
    linkBundle.setOffset(getOffset());
    linkBundle.setSingleConnectionPoint(isSingleConnectionPoint());
    
    // -------------

    IlvGraphicVector vectLinks = new IlvGraphicVector();

    IlvLinkImage sublink;
    for (int i = 0; i < nSublinks; i++) {
      // if appropriate, create a composite link for half
      // of the sublinks
      sublink = withNestedBundles ?
        createLinkBundle(origin, dest,
                         withCompositeSublinks, withCompositeOverview,
                         N_SUBLINKS_IN_SUB_BUNDLE,
                         false,
                         withSplines,
                         withFrame,
                         (i == 0 ?
                          SUB_BUNDLE_FRAME_COLOR1 : SUB_BUNDLE_FRAME_COLOR2)) :
        createSubLink(origin, dest,
                      withCompositeSublinks && i % 2 == 1,
                      withSplines,
                      SUBLINKS_WIDTH);

      vectLinks.addElement(sublink);

      Object[] args = { new Integer(i) };
      sublink.setToolTipText(
        MessageFormat.format(getString("sublink.Tooltip"), args));
      linkBundle.addSublink(sublink);
      connectLink(sublink);
    }
    
    connectLink(overviewLink);

    return linkBundle;
  }

  private IlvLinkImage createOverviewLink(IlvGraphic origin,
                                          IlvGraphic dest,
                                          boolean composite,
                                          boolean spline)
  {
    IlvLinkImage overviewLink =
      createOverviewLink(origin, dest, false, spline, OVERVIEW_LINK_WIDTH);

    if (composite)
      overviewLink =
        createCompositeLink(origin, dest, overviewLink,
                            "Overview link", OVERVIEW_LINK_WIDTH,
                            OVERVIEW_LINK_COLOR);

    // overviewLink.setToolTipText("Overview link");
    return overviewLink;
  }

  private IlvLinkImage createSubLink(IlvGraphic origin,
                                     IlvGraphic dest,
                                     boolean composite,
                                     boolean spline,
                                     float lineWidth)
  {
    return createLink(origin, dest, composite, spline,
                      lineWidth, SUBLINK_COLOR);
  }

  private IlvLinkImage createOverviewLink(IlvGraphic origin,
                                          IlvGraphic dest,
                                          boolean composite,
                                          boolean spline,
                                          float lineWidth)
  {
    return createLink(origin, dest, composite, spline,
                      lineWidth, OVERVIEW_LINK_COLOR);
  }

  private IlvLinkImage createLink(IlvGraphic origin,
                                  IlvGraphic dest,
                                  boolean composite,
                                  boolean spline,
                                  float lineWidth,
                                  Color color)
  {
    IlvLinkImage link;
    if (spline) {
      link = new IlvSplineLinkImage(origin, dest, false, null);
    } else {
      link = new IlvPolylineLinkImage(origin, dest, false, null);
    } 
    if (composite) {
      link =
        createCompositeLink(origin, dest, link,
                            " ", // null, // "Sublink",
                            SUBLINKS_WIDTH,
                            color);
    }
    link.setLineWidth(lineWidth);
    link.setEndCap(IlvStroke.CAP_BUTT);
    link.setLineJoin(IlvStroke.JOIN_ROUND);
    if (color != null)
      link.setForeground(color);
    return link;
  }

  private void connectLink(IlvLinkImage link)
  {
    // recurse inside bundles
    if (link instanceof IlvLinkBundle) {
      IlvGraphicEnumeration sublinks = ((IlvLinkBundle)link).getSublinksEnum();
      while (sublinks.hasMoreElements())
        connectLink((IlvLinkImage)sublinks.nextElement());
    }
    if (link instanceof IlvCompositeLink) {
      IlvLinkImage overviewLink = ((IlvCompositeLink)link).getLink();      
      connectLink(overviewLink);
    }    
    connectLink(IlvLinkConnector.Get(link, true),
                IlvLinkConnector.Get(link, false),
                link,
                link.getFrom().boundingBox(null),
                link.getTo().boundingBox(null));
  }

  private void connectLink(IlvLinkConnector lcFrom,
                           IlvLinkConnector lcTo,
                           IlvLinkImage link,
                           IlvRect bboxOrigin, IlvRect bboxDest)
  {
    if (lcFrom != null)
      lcFrom.connectLink(link, new IlvPoint(bboxOrigin.x + bboxOrigin.width*0.5f,
                                            bboxOrigin.y + bboxOrigin.height*0.5f),
                         true, null);
    if (lcTo != null)
      lcTo.connectLink(link, new IlvPoint(bboxDest.x + bboxDest.width*0.5f,
                                          bboxDest.y + bboxDest.height*0.5f),
                       true, null);
  }

  private IlvGraphic createNode(float x, float y)
  {

    IlvGraphic node =
      new IlvReliefRectangle(new IlvRect(x, y, NODE_WIDTH, NODE_HEIGHT));
    node.setBackground(NODE_COLOR);
    node.setToolTipText(getString("node.Tooltip"));
  
    installLinkConnector(node);

    return node;
  }

  private JPopupMenu createCollapseMenu()
  {
    return createMenu(true);
  }

  private JPopupMenu createExpandMenu()
  {
    return createMenu(false);
  }


  private JPopupMenu createMenu(boolean collapse)
  {
    // create the action listener for the pop-up menu
    ActionListener actionListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {

        // retrieve the selected menu item
        JMenuItem m = (JMenuItem)e.getSource();

        // retrieve the graphic that has this pop-up menu
        IlvPopupMenuContext context = IlvPopupMenuManager.getPopupMenuContext(m);
        IlvGraphic graphic = context.getGraphic();

        // do the action
        if (m.getText().equals(getString("collapseButton.Label"))) {
          if (graphic instanceof IlvLinkBundle) {
            IlvLinkBundle bundle = (IlvLinkBundle)graphic;
            if (bundle.getGraphicBag() != null) {
              bundle.getGraphicBag().applyToObject(bundle, new IlvApplyObject() {
                  public void apply(IlvGraphic g, Object arg) {
                    ((IlvLinkBundle)g).setCollapsed(true);
                  }
              }, null, true);
            } else {
              bundle.setCollapsed(true);
            }
          } 
        }
        if (m.getText().equals(getString("expandButton.Label"))) {
          // the expand menu is on the overview link
          if (graphic.getGraphicBag() instanceof IlvLinkBundle) {
            IlvLinkBundle ownerBundle = (IlvLinkBundle)graphic.getGraphicBag();
            if (ownerBundle.getOverviewLink() == graphic) {
              graphic = ownerBundle;
            }
          }
          if (graphic instanceof IlvLinkBundle) {
            IlvLinkBundle bundle = (IlvLinkBundle)graphic;
            if (bundle.getGraphicBag() != null) {
              bundle.getGraphicBag().applyToObject(bundle, new IlvApplyObject() {
                  public void apply(IlvGraphic g, Object arg) {
                    ((IlvLinkBundle)g).setCollapsed(false);
                  }
              }, null, true);
            } else {
              bundle.setCollapsed(false);
            }
          } 
        }
      }
    };

    IlvSimplePopupMenu menu =
      new IlvSimplePopupMenu(
              "Menu1",
              collapse ? getString("collapseButton.Label") : getString("expandButton.Label"),
              null,
              actionListener) {

        // configure checkbox of the pop-up before it gets displayed
        protected void beforeDisplay(IlvPopupMenuContext context) {
          // always call super.beforeDisplay first
          super.beforeDisplay(context);
          // retrieve the graphic
          IlvGraphic graphic = context.getGraphic();
          // deselect all objects in the manager
          IlvGraphicBag bag = graphic.getGraphicBag();
          while (bag != null && !(bag instanceof IlvManager)) {
            bag = ((IlvGraphic)bag).getGraphicBag();
          }
          if (bag instanceof IlvManager)
            ((IlvManager)bag).deSelectAll(true, true);
          // select this graphic;
          bag = graphic.getGraphicBag();
          if (bag instanceof IlvLinkBundle) {
            IlvLinkBundle ownerBundle = (IlvLinkBundle)graphic.getGraphicBag();
            if (ownerBundle.getOverviewLink() == graphic) {
              graphic = ownerBundle;
              bag = graphic.getGraphicBag();
            }
          }
          if (bag instanceof IlvObjectWithSelection) {
            ((IlvObjectWithSelection)bag).setSelected(graphic, true, true);
          }
        }
    };

    return menu;

  }

  /**
   * Returns a string.
   */
  static String getString(String key)
  {
    return IlvResourceUtil.getString(key, LinkBundleApplet.class,
                                     IlvLocaleUtil.getCurrentLocale());
  }

  private static class JComfortPanel
    extends JPanel
  {
    public void setEnabled(boolean enabled)
    {
      Component[] components = getComponents();
      for (int i = 0; i < components.length; i++)
        components[i].setEnabled(enabled);
    }
  }
}