/*
 * 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.ComponentOrientation;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Locale;

import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import ilog.views.IlvApplyObject;
import ilog.views.IlvDirection;
import ilog.views.IlvExpandCollapseUtil;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvLinkImage;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.graphlayout.IlvGraphLayoutException;
import ilog.views.graphlayout.IlvGraphModel;
import ilog.views.graphlayout.IlvGrapherAdapter;
import ilog.views.graphlayout.IlvNodeSideFilter;
import ilog.views.graphlayout.hierarchical.IlvHierarchicalLayout;
import ilog.views.graphlayout.link.IlvLinkLayout;
import ilog.views.interactor.IlvExpandCollapseInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
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.IlvSwingUtil;

/**
 * This is a very simple application that shows various policies when
 * expanding and collapsing graphs. It shows how to use the expand utilities in
 * applications that are not based on CSS styling.
 */
public class ExpandCollapseApp 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);

  /** An instance of the Hierarchical Layout algorithm */
  IlvHierarchicalLayout hierarchicalLayout = new IlvHierarchicalLayout();

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

  /** A node side filter for the Link Layout algorithm */
  IlvNodeSideFilter nodeSideFilter = new IlvNodeSideFilter() {
    Override
    public boolean accept(IlvGraphModel graphModel, Object link, boolean origin, Object node,
        int side) {
      if (origin)
        return side == IlvDirection.Right;
      else
        return side == IlvDirection.Left;
    }
  };

  /** A text field to display messages */
  JTextField msgLine = new JTextField();

  /** The combo box for the link layout */
  JComboBox<String> linkLayoutSelector = new JComboBox<String>();

  /** The adjustment mode */
  String actModeString = "";
  int mode;

  /** Whether incremental layout is required */
  boolean incrementalLayout = false;

  /** Whether we allow link layout */
  boolean allowLinkLayout = true;

  /** Whether a separate link layout is required */
  String linkLayoutSelectionString = getString("linkLayoutSelector.NoneMode");

  /** A custom adjustment policy */
  PolicyWithLinkLayout customPolicy = new PolicyWithLinkLayout();

  /**
   * Initializes the application.
   */
  public void init() {
    showMessage(getString("InitMessage"));

    // attach the grapher to the layout instances
    IlvGrapherAdapter adapter = new IlvGrapherAdapter(grapher);
    hierarchicalLayout.attach(adapter);
    linkLayout.attach(adapter);

    // change the default option for the links style
    hierarchicalLayout.setGlobalLinkStyle(IlvHierarchicalLayout.ORTHOGONAL_STYLE);

    // change the default flow direction
    hierarchicalLayout.setFlowDirection(IlvDirection.Right);

    // 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.setZoomFactorRange(0.02, 10.0);
    mgrview.setWheelZoomingEnabled(true);
    scrollManView.setWheelScrollingEnabled(true);

    // create the standard control bar
    IlvJManagerViewControlBar controlBar = new IlvJManagerViewControlBar();
    controlBar.setView(mgrview);

    IlvExpandCollapseInteractor inter = new IlvExpandCollapseInteractor() {
      Override
      protected void expandOrCollapse(IlvGraphic obj) {
        if (obj instanceof IlvManager) {
          expandOrCollapseImpl((IlvManager) obj);
        } else {
          super.expandOrCollapse(obj);
        }
      }
    };

    controlBar.setSelectInteractor(inter);
    Image selectIconImage = getImage("gif/Expand.gif");
    controlBar.setSelectIcon(new ImageIcon(selectIconImage));
    controlBar.setSelectToolTipText(getString("expandButton.Tooltip"));

    // modify the interactors such that the demo looks better
    ((IlvZoomViewInteractor) controlBar.getZoomViewInteractor()).setPermanent(true);

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

    // create the bottom panel with the layout buttons and the message line
    JPanel bottomPanel = new JPanel();
    bottomPanel.setLayout(new BorderLayout());
    bottomPanel.add(createParameterPanel(), BorderLayout.CENTER);
    bottomPanel.add(msgLine, BorderLayout.SOUTH);
    msgLine.setEditable(false);

    // put manager view, top panel and bottom panel together
    getContentPane().setLayout(new BorderLayout(0, 0));
    getContentPane().add(scrollManView, BorderLayout.CENTER);
    getContentPane().add(controlBar, BorderLayout.NORTH);
    getContentPane().add(bottomPanel, BorderLayout.SOUTH);

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

    // show all
    mgrview.fitTransformerToContent(true);
  }

  /**
   * Creates the parameter panel.
   */
  private JPanel createParameterPanel() {
    JPanel panel = new JPanel();
    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.HORIZONTAL;
    c.anchor = GridBagConstraints.LINE_END;
    panel.setLayout(gridbag);

    // expansion adjustment mode parameter
    c.gridx = 0;
    c.gridy = 0;
    panel.add(new JLabel(getString("expandModeSelector.Label")), c);
    c.gridx = 1;
    panel.add(new JLabel(" "), c);
    c.gridx = 2;
    JComboBox<String> adjustmentModeSelector = new JComboBox<String>();
    panel.add(adjustmentModeSelector, c);
    adjustmentModeSelector.addItem(getString("expandModeSelector.NoneMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.OrthogonalWithLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.OrthogonalWithoutLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.OrthogonalRecWithLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.OrthogonalRecWithoutLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.OrthogonalStableWithLinksMode"));
    adjustmentModeSelector
        .addItem(getString("expandModeSelector.OrthogonalStableWithoutLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.RadialWithLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.RadialWithoutLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.RadialRecWithLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.RadialRecWithoutLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.RadialStableWithLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.RadialStableWithoutLinksMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.IncrLayoutMode"));
    adjustmentModeSelector.addItem(getString("expandModeSelector.CustomMode"));
    adjustmentModeSelector.setSelectedIndex(5);
    setAdjustmentMode((String) adjustmentModeSelector.getSelectedItem());
    adjustmentModeSelector.setToolTipText(getString("expandModeSelector.Tooltip"));
    adjustmentModeSelector.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        SuppressWarnings("unchecked")
        JComboBox<String> comboBox = (JComboBox<String>) event.getSource();
        String modeString = (String) comboBox.getSelectedItem();
        setAdjustmentMode(modeString);
      }
    });

    // link layout mode parameter
    c.gridx = 0;
    c.gridy = 1;
    panel.add(new JLabel(getString("linkLayoutSelector.Label")), c);
    c.gridx = 1;
    panel.add(new JLabel(" "), c);
    c.gridx = 2;
    panel.add(linkLayoutSelector, c);
    linkLayoutSelector.addItem(getString("linkLayoutSelector.NoneMode"));
    linkLayoutSelector.addItem(getString("linkLayoutSelector.HierMode"));
    linkLayoutSelector.addItem(getString("linkLayoutSelector.GenericMode"));
    linkLayoutSelector.setSelectedIndex(0);
    linkLayoutSelector.setToolTipText(getString("linkLayoutSelector.Tooltip"));
    linkLayoutSelector.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent event) {
        SuppressWarnings("unchecked")
        JComboBox<String> comboBox = (JComboBox<String>) event.getSource();
        String modeString = (String) comboBox.getSelectedItem();
        setLinkLayoutMode(modeString);
      }
    });

    return panel;
  }

  /**
   * Sets the adjustment mode.
   */
  private void setAdjustmentMode(String modeString) {
    if (actModeString.equals(modeString))
      return;

    actModeString = modeString;

    // to test a mode, it is better to start with a clean situation,
    // hence load the graph new.

    incrementalLayout = false;

    if (modeString.equals(getString("expandModeSelector.NoneMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.NONE;
      allowLinkLayout = true;
    } else if (modeString.equals(getString("expandModeSelector.OrthogonalWithLinksMode"))) {
      // load flatter variant
      loadGrapher("graph2");
      mode = IlvExpandCollapseUtil.ORTHOGONAL_WITH_LINKS;
      allowLinkLayout = false;
    } else if (modeString.equals(getString("expandModeSelector.OrthogonalWithoutLinksMode"))) {
      // load flatter variant
      loadGrapher("graph2");
      mode = IlvExpandCollapseUtil.ORTHOGONAL_WITHOUT_LINKS;
      allowLinkLayout = true;
    } else if (modeString.equals(getString("expandModeSelector.OrthogonalRecWithLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.ORTHOGONAL_RECURSIVE_WITH_LINKS;
      allowLinkLayout = false;
    } else if (modeString.equals(getString("expandModeSelector.OrthogonalRecWithoutLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.ORTHOGONAL_RECURSIVE_WITHOUT_LINKS;
      allowLinkLayout = true;
    } else if (modeString.equals(getString("expandModeSelector.OrthogonalStableWithLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.ORTHOGONAL_STABLE_WITH_LINKS;
      allowLinkLayout = false;
    } else if (modeString
        .equals(getString("expandModeSelector.OrthogonalStableWithoutLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.ORTHOGONAL_STABLE_WITHOUT_LINKS;
      allowLinkLayout = true;
    } else if (modeString.equals(getString("expandModeSelector.RadialWithLinksMode"))) {
      // load flatter variant
      loadGrapher("graph2");
      mode = IlvExpandCollapseUtil.RADIAL_WITH_LINKS;
      allowLinkLayout = false;
    } else if (modeString.equals(getString("expandModeSelector.RadialWithoutLinksMode"))) {
      // load flatter variant
      loadGrapher("graph2");
      mode = IlvExpandCollapseUtil.RADIAL_WITHOUT_LINKS;
      allowLinkLayout = true;
    } else if (modeString.equals(getString("expandModeSelector.RadialRecWithLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.RADIAL_RECURSIVE_WITH_LINKS;
      allowLinkLayout = false;
    } else if (modeString.equals(getString("expandModeSelector.RadialRecWithoutLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.RADIAL_RECURSIVE_WITHOUT_LINKS;
      allowLinkLayout = true;
    } else if (modeString.equals(getString("expandModeSelector.RadialStableWithLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.RADIAL_STABLE_WITH_LINKS;
      allowLinkLayout = false;
    } else if (modeString.equals(getString("expandModeSelector.RadialStableWithoutLinksMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.RADIAL_STABLE_WITHOUT_LINKS;
      allowLinkLayout = true;
    } else if (modeString.equals(getString("expandModeSelector.IncrLayoutMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.NONE;
      incrementalLayout = true;
      allowLinkLayout = false;
    } else if (modeString.equals(getString("expandModeSelector.CustomMode"))) {
      // load deeply nested variant
      loadGrapher("graph1");
      mode = IlvExpandCollapseUtil.STABLE;
      incrementalLayout = false;
      allowLinkLayout = false;
    }

    linkLayoutSelector.setEnabled(allowLinkLayout);

    showMessage(getString("ExpandModeStatusMessage"), modeString);
  }

  /**
   * Sets the link layout mode.
   */
  private void setLinkLayoutMode(String modeString) {
    linkLayoutSelectionString = modeString;
    showMessage(getString("LinkLayoutStatusMessage"), modeString);
  }

  /**
   * Expand or collapse a manager.
   */
  private void expandOrCollapseImpl(IlvManager manager) {
    if (manager.getParent() == null)
      return;

    boolean collapse = !manager.isCollapsed();

    // when expanding and using incremental layout, the node bounds get bigger.
    // The incremental layout works better if it knows about the smaller node
    // bounds before expanding.

    if (incrementalLayout && !collapse) {
      IlvManager node = manager;
      IlvManager parent = manager.getParent();
      while (parent != null) {
        IlvHierarchicalLayout layoutOfNode =
            (IlvHierarchicalLayout) hierarchicalLayout.getRecursiveLayout().getLayout(parent);
        IlvRect bbox = layoutOfNode.getGraphModel().boundingBox(node);
        layoutOfNode.setIncrementalNodeBoxForExpand(node, bbox);
        node = parent;
        parent = node.getParent();
      }
    }

    if (allowLinkLayout
        && !linkLayoutSelectionString.equals(getString("linkLayoutSelector.NoneMode"))) {
      // the expand/collapse behaves more stable if all links are straightened
      // before expand/collapse. Since there is a link layout afterwards anyway,
      // the links get orthogonal anyway.
      straightenAllLinks();
    }

    // now expand or collapse

    IlvTransformer t = manager.getParent().getDrawingTransformer(mgrview);
    if (mode == IlvExpandCollapseUtil.STABLE) {
      // custom adjustment strategy
      IlvExpandCollapseUtil.expandOrCollapse(manager, customPolicy, mode, collapse, t, true);
    } else {
      // default adjustment strategy
      IlvExpandCollapseUtil.expandOrCollapse(manager, mode, collapse, t, true);
    }

    // if the mode says we want an incremental hierarchical layout, do this now

    if (incrementalLayout) {
      hierarchicalLayout.setIncrementalMode(true);
      hierarchicalLayout.setIncrementalAbsoluteLevelPositioning(false);
      hierarchicalLayout.setGlobalIncrementalNodeMovementMode(IlvHierarchicalLayout.FREE_MODE);
      try {
        hierarchicalLayout.performLayout(true, true, true);
      } catch (IlvGraphLayoutException ex) {
        ex.printStackTrace();
      }
    }

    // if an additional link layout is enabled, do this now.

    if (allowLinkLayout) {
      // only if we do no incremental hierarchical layout, we might need
      // a link layout
      if (linkLayoutSelectionString.equals(getString("linkLayoutSelector.HierMode"))) {
        hierarchicalLayout.setIncrementalMode(true);
        hierarchicalLayout.setIncrementalAbsoluteLevelPositioning(true);
        hierarchicalLayout.setGlobalIncrementalNodeMovementMode(IlvHierarchicalLayout.FIXED_MODE);
        try {
          hierarchicalLayout.performLayout(true, true, true);
        } catch (IlvGraphLayoutException ex) {
          ex.printStackTrace();
        }
      } else if (linkLayoutSelectionString.equals(getString("linkLayoutSelector.GenericMode"))) {
        linkLayout.setIncrementalMode(true);
        linkLayout.setNodeSideFilter(nodeSideFilter);
        try {
          linkLayout.performLayout(true, true, true);
        } catch (IlvGraphLayoutException ex) {
          ex.printStackTrace();
        }
      }
    }
  }

  /**
   * Load a sample IVL file.
   * 
   * @param fileNameBase
   *          The base of the filename, excluding the path prefix and the
   *          extension suffix.
   */
  private void loadGrapher(String fileNameBase) {
    try {
      showMessage(getString("FileReadingStartMessage"), fileNameBase);
      grapher.deleteAll(false);

      try {
        grapher.read(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".ivl"));
      } catch (Exception ex1) {
        // This case occurs only when we pack the entire application into
        // one jar including the data files, and start as application
        grapher.read(getClass().getResource("data/" + fileNameBase + ".ivl"));
      }
      // the transformer may have been modified, so
      // we set the identity transformer
      mgrview.fitTransformerToContent();
      mgrview.repaint();
      showMessage(getString("FileReadingDoneMessage"), fileNameBase);
    } catch (Exception e) {
      // e.printStackTrace();
      showMessage(e.getMessage());
    }
  }

  /**
   * Displays a message.
   */
  void showMessage(String message) {
    // the message is displayed in the message line
    msgLine.setText(message);
    msgLine.paintImmediately(0, 0, msgLine.getWidth(), msgLine.getHeight());
  }

  void showMessage(String msgformat, Object val) {
    Object[] args = {val};
    showMessage(MessageFormat.format(msgformat, args));
  }

  void showMessage(String msgformat, Object val1, Object val2) {
    Object[] args = {val1, val2};
    showMessage(MessageFormat.format(msgformat, args));
  }

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

  /**
   * Reads an image. It first reads from the jar itself, and then tries to read
   * fromfile.
   */
  public Image getImage(String fileName) {
    Image image = null;
    // try to get it from jar
    try {
      image = IlvImageUtil.getImageFromFile(getClass(), fileName);
    } catch (IOException e) {
      // System.err.println("Image not found: " + fileName + " exception: " +
      // e.getMessage());
      showMessage(getString("ImageNotFoundErrorMessage") + "<br>", fileName, e.getMessage());
    }

    // if couldn't get it from the jar...
    if (image == null)
      image = getToolkit().getImage(fileName);

    // if still null...
    if (image == null) {
      // System.err.println("Could not get image " + fileName + "!");
      showMessage(getString("MissingImageErrorMessage") + "<br>", fileName);
    }
    return image;
  }

  /**
   * Straighten all links in the grapher.
   */
  private void straightenAllLinks() {
    IlvGraphicEnumeration e = grapher.getObjects(true);
    grapher.applyToObjects(e, new IlvApplyObject() {
      Override
      public void apply(IlvGraphic g, Object arg) {
        if (g instanceof IlvLinkImage) {
          IlvLinkImage link = (IlvLinkImage) g;
          link.setIntermediateLinkPoints(null, 0, 0);
        }
      }
    }, null, true);
  }

  /**
   * 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() {
        ExpandCollapseApp app = new ExpandCollapseApp();
        app.init();

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

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

  /**
   * A custom adjustment policy that runs a hierarchical link layout inside the
   * adjustment (instead of running it after the expand/collapse). If a link
   * layout is needed, this is in general the best approach that gives the best
   * results, but it is also the slowest approach in stable mode.
   */
  class PolicyWithLinkLayout implements IlvExpandCollapseUtil.CustomAdjustmentPolicy {
    Override
    public void apply(IlvManager parent, IlvManager child, IlvRect oldBounds, IlvRect newBounds,
        IlvTransformer t, boolean redraw) {
      // do an orthogonal expansion (very similar to orthWithoutLinksPolicy)

      // first move the child to the center of the old bounds. This is
      // already done by setCollapsed, most of the time, except that it is
      // done with respect to null transformer and we really need it with
      // respect to transformer t. Hence do it again.

      double deltaX = 0.5d * (oldBounds.width - newBounds.width);
      double deltaY = 0.5d * (oldBounds.height - newBounds.height);

      IlvPoint p = new IlvPoint(oldBounds.x + deltaX, oldBounds.y + deltaY);
      if (t != null)
        t.inverse(p);

      parent.moveObject(child, p.x, p.y, redraw);

      // shift all nodes in x or y direction

      double minX = oldBounds.x;
      double maxX = oldBounds.x + oldBounds.width;
      double centerX = oldBounds.x + 0.5 * oldBounds.width;
      double minY = oldBounds.y;
      double maxY = oldBounds.y + oldBounds.height;
      double centerY = oldBounds.y + 0.5 * oldBounds.height;

      IlvGraphicEnumeration e = parent.getObjects();
      while (e.hasMoreElements()) {
        IlvGraphic g = e.nextElement();
        if (g == child)
          continue;
        if (g instanceof IlvLinkImage)
          continue;
        IlvRect bbox = g.boundingBox(t);
        p.setLocation(bbox.x, bbox.y);
        double cx = bbox.x + 0.5 * bbox.width;
        double cy = bbox.y + 0.5 * bbox.height;
        if (cx > maxX)
          p.x -= deltaX;
        else if (cx < minX)
          p.x += deltaX;
        else
          p.x -= (deltaX * 2 * (cx - centerX) / oldBounds.width);
        if (cy > maxY)
          p.y -= deltaY;
        else if (cy < minY)
          p.y += deltaY;
        else
          p.y -= (deltaY * 2 * (cy - centerY) / oldBounds.height);
        if (t != null)
          t.inverse(p);
        parent.moveObject(g, p.x, p.y, redraw);
      }

      // do a link layout (using the hierarchical layout) on the parent
      IlvHierarchicalLayout layout = new IlvHierarchicalLayout();
      layout.attach((IlvGrapher) parent);
      layout.setGlobalLinkStyle(IlvHierarchicalLayout.ORTHOGONAL_STYLE);
      layout.setFlowDirection(IlvDirection.Right);
      layout.setIncrementalMode(true);
      layout.setIncrementalAbsoluteLevelPositioning(true);
      layout.setGlobalIncrementalNodeMovementMode(IlvHierarchicalLayout.FIXED_MODE);
      try {
        // perform layout only on parent, not recursively on children
        layout.performLayout(true, true);
      } catch (IlvGraphLayoutException ex) {
        ex.printStackTrace();
      }
      layout.detach();
    }
  }

}