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

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.text.MessageFormat;
import java.util.Locale;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;

import ilog.views.IlvAccelerator;
import ilog.views.IlvApplyObject;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicBag;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvGraphicVector;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvLinkConnector;
import ilog.views.IlvLinkImage;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvObjectWithSelection;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvStroke;
import ilog.views.graphic.IlvIcon;
import ilog.views.graphic.IlvPolylineLinkImage;
import ilog.views.graphic.IlvRectangle;
import ilog.views.graphic.IlvReliefRectangle;
import ilog.views.graphic.IlvSplineLinkImage;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.graphic.composite.IlvCompositeGraphic;
import ilog.views.graphic.composite.IlvCompositeLink;
import ilog.views.graphic.composite.layout.IlvAttachmentConstraint;
import ilog.views.graphic.composite.layout.IlvAttachmentLocation;
import ilog.views.graphic.composite.layout.IlvCenteredLayout;
import ilog.views.graphic.composite.layout.IlvLinkAttachmentLocation;
import ilog.views.graphic.composite.layout.IlvStackerLayout;
import ilog.views.graphic.linkbundle.IlvDefaultLinkBundleFrame;
import ilog.views.graphic.linkbundle.IlvLinkBundle;
import ilog.views.graphic.linkbundle.IlvLinkBundleFrame;
import ilog.views.graphlayout.IlvGraphLayout;
import ilog.views.graphlayout.IlvGraphLayoutException;
import ilog.views.graphlayout.IlvGraphModel;
import ilog.views.graphlayout.IlvGrapherAdapter;
import ilog.views.graphlayout.IlvInappropriateLinkException;
import ilog.views.graphlayout.link.IlvLinkLayout;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.linkconnector.IlvClippingLinkConnector;
import ilog.views.linkconnector.IlvFreeLinkConnector;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.swing.IlvPopupMenuContext;
import ilog.views.swing.IlvPopupMenuManager;
import ilog.views.swing.IlvSimplePopupMenu;
import ilog.views.swing.IlvToolTipManager;
import ilog.views.util.IlvImageUtil;
import ilog.views.util.IlvLocaleUtil;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.swing.IlvJSpinnerDecimalNumberField;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * The main class LinkBundleApp. This demonstrates the class
 * <code>ilog.views.graphic.linkbundle.IlvLinkBundle</code>.
 */
public class LinkBundleApp extends JRootPane {
  static {
    // 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 double offset = 5;
  private double frameMargin = 3;

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

  private static final double NODE_WIDTH = 100;
  private static final double NODE_HEIGHT = 100;
  private static final double SUBLINKS_WIDTH = 5;
  private static final double 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 application.
   */
  public void 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) {
      Override
      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<String> linkStyleChooser = new JComboBox<String>();
    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() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.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() {
      Override
      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() {
      Override
      public void actionPerformed(ActionEvent event) {
        final double value = offsetField.getDoubleValue();
        setOffset(value);

        applyToAllLinkBundles(grapher, new IlvApplyObject() {
          Override
          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() {
      Override
      public void actionPerformed(ActionEvent event) {
        final double value = marginField.getDoubleValue();
        setFrameMargin(value);

        applyToAllLinkBundles(grapher, new IlvApplyObject() {
          Override
          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() {
      Override
      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() {
      Override
      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<String> chooser;
    topPanel.add(chooser = new JComboBox<String>());
    chooser.setToolTipText(getString("fileChooser.Tooltip"));
    chooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.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);
  }

  // For debug only
  SuppressWarnings("unused")
  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() {
      Override
      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() {
      Override
      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() {
      Override
      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(double offset) {
    this.offset = offset;
  }

  private double getOffset() {
    return offset;
  }

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

  private double 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() {
      Override
      public Dimension getPreferredSize() {
        return new Dimension(26, 26);
      }

      Override
      public Dimension getMinimumSize() {
        return new Dimension(26, 26);
      }

      Override
      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(LinkBundleApp.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() {
      Override
      public void run() {
        LinkBundleApp app = new LinkBundleApp();
        app.init();

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

  /**
   * Installs the link connectors on all nodes.
   */
  private void installLinkConnectors() {
    applyToAllNodes(grapher, new IlvApplyObject() {
      Override
      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() {
      Override
      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, double 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 = { Integer.valueOf(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,
      double lineWidth) {
    return createLink(origin, dest, composite, spline, lineWidth, SUBLINK_COLOR);
  }

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

  private IlvLinkImage createLink(IlvGraphic origin, IlvGraphic dest, boolean composite, boolean spline,
      double 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.5, bboxOrigin.y + bboxOrigin.height * 0.5), true, null);
    if (lcTo != null)
      lcTo.connectLink(link, new IlvPoint(bboxDest.x + bboxDest.width * 0.5, bboxDest.y + bboxDest.height * 0.5),
          true, null);
  }

  private IlvGraphic createNode(double x, double 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() {
      Override
      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() {
                Override
                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() {
                Override
                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
      Override
      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, LinkBundleApp.class, IlvLocaleUtil.getCurrentLocale());
  }

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