/*
 * 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.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
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.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JCheckBox;
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.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicEnumeration;
import ilog.views.IlvHandlesSelection;
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.event.ManagerContentChangedEvent;
import ilog.views.event.ManagerContentChangedListener;
import ilog.views.event.ManagerSelectionChangedEvent;
import ilog.views.event.ManagerSelectionListener;
import ilog.views.event.TransformerChangedEvent;
import ilog.views.event.TransformerListener;
import ilog.views.graphic.IlvLine;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.graphlayout.labellayout.IlvDefaultLabelingModel;
import ilog.views.graphlayout.labellayout.IlvLabelLayout;
import ilog.views.graphlayout.labellayout.IlvLabelLayoutReport;
import ilog.views.graphlayout.labellayout.LabelLayoutEvent;
import ilog.views.graphlayout.labellayout.LabelLayoutEventListener;
import ilog.views.graphlayout.labellayout.annealing.IlvAnnealingLabelLayout;
import ilog.views.graphlayout.labellayout.annealing.IlvAnnealingPolylineLabelDescriptor;
import ilog.views.graphlayout.labellayout.random.IlvRandomLabelLayout;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.io.IlvReadFileException;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * The main class LinkLabelLayoutApp.
 */
public class LinkLabelLayoutApp extends JRootPane
    implements ManagerContentChangedListener, ManagerSelectionListener, TransformerListener {
  
  {
    // 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 manager: because we deal with links in this sample, we use a grapher
   * instead of a general purpose manager. However, it doesn't matter for the
   * labeling algorithm, only for the redraw refresh on interactions.
   */
  IlvManager manager = new IlvGrapher();

  /** The labeling model for the manager */
  IlvDefaultLabelingModel labelingModel;

  /** The view of the manager */
  IlvManagerView mgrview = new LinkLabelingManagerView(manager);

  /** An instance of the Simulates Annealing Label Layout algorithm */
  IlvAnnealingLabelLayout layout = new IlvAnnealingLabelLayout();

  /** An instance of the Random Label Layout algorithm */
  IlvRandomLabelLayout randomLayout = new IlvRandomLabelLayout();

  /** A label layout event listener */
  LinkLabelingListener layoutListener = new LinkLabelingListener(this);

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

  /** The side association */
  int sideAssociation = IlvAnnealingPolylineLabelDescriptor.LOCAL;
  JComboBox<String> sideAssocChooser = new JComboBox<String>();
  boolean duringAssociationChange = false;

  /** The preferred side of all labels */
  int preferredSide = IlvDirection.Left;
  JComboBox<String> preferredSideChooser = new JComboBox<String>();

  /** The allowed side of all labels */
  int allowedSide = 0;
  JComboBox<String> allowedSideChooser = new JComboBox<String>();

  /** Offset distances */
  static final double minDistToOtherObstacles = 10.0d;
  double prefDistFromOwnObstacle = 1.0;
  double allowedOverlapOffset = 0.0;

  /** Text fields for the offset parameters */
  JTextField minOffset = new JTextField("" + minDistToOtherObstacles, 5);
  JTextField prefOffset = new JTextField("" + prefDistFromOwnObstacle, 5);
  JTextField ovlpOffset = new JTextField("" + allowedOverlapOffset, 5);

  /**
   * Whether we want to see the label bounding box or not.
   */
  boolean noLabelBackground = true;

  /**
   * In this demo, the labels are assigned to links (IlvLinkImage). The labels
   * should be placed close to the links. Which label belongs to which link is
   * in this sample randomly decided, and stored in the assignment table, such
   * that we can show the assignment by the auxiliry links when necessary.
   */
  Hashtable<Object, IlvLinkImage> assignmentTable;
  Object[] obstacles;
  Object[] labels;
  int[] labelsPerObstacle;

  /** The assignment lines */
  IlvLine[] assignmentLines;
  boolean labelAssignmentVisible = false;

  /**
   * A counter to block the event handling of the auxiliary lines. Otherise, the
   * updating of the auxiliary lines on movements would cause cycles of events.
   */
  int blockEventsCounter = 0;

  /**
   * Initializes the application.
   */
  public void init() {
    showMessage("layout sample initialization...");

    JPanel panel;
  
    // register this application as listener to the manager
    manager.addManagerContentChangedListener(this);

    // register this application as selection listener to the manager
    manager.addManagerSelectionListener(this);

    // 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.setSize(new Dimension(488, 329));
    mgrview.addTransformerListener(this);

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

    // create a new labeling model
    // We do this AFTER adding the app as transformer listener, because
    // the default labeling model will register itself as transformer listener
    // as well. The aux lines mechanism of this app requires that the
    // app receives the event last.
    labelingModel = new IlvDefaultLabelingModel(manager);

    // attach the manager to the layout instances
    layout.attach(labelingModel);
    randomLayout.attach(labelingModel);
    layout.setObstacleOffset(minDistToOtherObstacles);
    layout.setAutoUpdate(false);

    // install the layout iteration listener on the layout instance
    layout.addLabelLayoutEventListener(layoutListener);

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

    // fit so far all together
    getContentPane().add("Center", scrollManView);
    getContentPane().add("North", panel = new JPanel());
    panel.setLayout(new FlowLayout(FlowLayout.LEFT));

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

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

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

    // create the graph selector
    panel.add(new JLabel("Select graph"));
    JComboBox<String> chooser;
    panel.add(chooser = new JComboBox<String>());
    chooser.setToolTipText("Select a sample graph");
    chooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        try {
          // disable autoupdate until we did at least one layout
          layout.setAutoUpdate(false);
          // just to be sure: store where the aux lines are visible and
          // hide the aux lines
          boolean f = labelAssignmentVisible;
          hideLabelAssignment();
          // refill the manager from file
          manager.deleteAll(false);
          readManager((String) event.getItem());
          // restore whether the aux lines were visible
          labelAssignmentVisible = f;
          // postprocessing after reading a file
          afterRead();
        } catch (Exception e) {
          // e.printStackTrace();
          showMessage(e.getMessage());
        }
      }
    });
    chooser.addItem("small1");
    chooser.addItem("small2");
    chooser.addItem("medium");
    chooser.addItem("large");

    // create the panel on bottom
    JPanel bottomPanel = new JPanel();
    bottomPanel.setLayout(new BorderLayout());
    getContentPane().add("South", bottomPanel);

    // create the layout buttons
    JButton b;
    JPanel layoutPanel = new JPanel();
    bottomPanel.add("North", layoutPanel);
    layoutPanel.add(b = new JButton("Random"));
    b.setToolTipText("Randomize the label positions");
    b.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        preprocess();
        layout(randomLayout, manager);
      }
    });

    layoutPanel.add(b = new JButton("Layout"));
    b.setToolTipText("Perform a label layout");
    b.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        try {
          // validate the min offset
          double dvalue = Double.valueOf(minOffset.getText().trim()).doubleValue();
          setMinimalOffset(dvalue);
          // validate the preferred offset
          dvalue = Double.valueOf(prefOffset.getText().trim()).doubleValue();
          setPreferredOffset(dvalue);
          // validate the allowed overlap offset
          dvalue = Double.valueOf(ovlpOffset.getText().trim()).doubleValue();
          setAllowedOverlapOffset(dvalue);
        } catch (Exception ex) {
          // ex.printStackTrace();
          showMessage("Illegal offset: " + ex.getMessage());
          return;
        }
        // now the layout
        preprocess();
        layout(layout, manager);
        // from now on, the labels follow the links
        layout.setAutoUpdate(true);
      }
    });

    // quadtree
    JPanel auxPanel = new JPanel();
    auxPanel.setLayout(new BorderLayout(0, 0));
    layoutPanel.add(auxPanel);
    JCheckBox useQuadtree = new JCheckBox("Quadtree", true);
    useQuadtree.setToolTipText("Enable or disable the quadtree as speedup mechanism");
    auxPanel.add("West", useQuadtree);
    useQuadtree.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          layout.setUseQuadtree(true);
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          layout.setUseQuadtree(false);
      }
    });

    // label box
    JCheckBox showLabelBox = new JCheckBox("Label Box", false);
    auxPanel.add("Center", showLabelBox);
    showLabelBox.setToolTipText("Show or hide the bounding box of the labels");
    showLabelBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          noLabelBackground = false;
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          noLabelBackground = true;
        setNoLabelBackground(noLabelBackground);
        // corner may have changed
        if (labelAssignmentVisible)
          showLabelAssignment();
        manager.reDraw();
      }
    });

    // auxiliary lines
    JCheckBox showAuxLines = new JCheckBox("Auxiliary Lines", false);
    showAuxLines.setToolTipText("Show or hide the auxiliary lines of the labels");
    auxPanel.add("East", showAuxLines);
    showAuxLines.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          showLabelAssignment();
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          hideLabelAssignment();
      }
    });

    // create the layout parameters panel
    JPanel layoutParameters = new JPanel();
    layoutParameters.setLayout(new GridBagLayout());
    bottomPanel.add("Center", layoutParameters);
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.HORIZONTAL;
    c.anchor = GridBagConstraints.EAST;

    // side association parameter
    c.gridx = 0;
    c.gridy = 0;
    layoutParameters.add(new JLabel("Side Association: "), c);
    c.gridx = 1;
    c.gridy = 0;
    layoutParameters.add(sideAssocChooser, c);
    sideAssocChooser.setToolTipText("Select whether 'left', 'right', etc. has a local or a global meaning");
    sideAssocChooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        int oldAssoc = sideAssociation;
        duringAssociationChange = true;
        if ((String) event.getItem() == "local") {
          sideAssociation = IlvAnnealingPolylineLabelDescriptor.LOCAL;
          if (oldAssoc != sideAssociation) {
            makePreferredSideChooserLocal();
            makeAllowedSideChooserLocal();
          }
        }
        if ((String) event.getItem() == "global") {
          sideAssociation = IlvAnnealingPolylineLabelDescriptor.GLOBAL;
          if (oldAssoc != sideAssociation) {
            makePreferredSideChooserGlobal();
            makeAllowedSideChooserGlobal();
          }
        }
        duringAssociationChange = false;
        if (oldAssoc != sideAssociation)
          setLayoutParameters();
      }
    });
    sideAssocChooser.addItem("local");
    sideAssocChooser.addItem("global");
    sideAssocChooser.setSelectedIndex(0);

    // preferred side parameter
    c.gridx = 0;
    c.gridy = 1;
    layoutParameters.add(new JLabel("Preferred Side: "), c);
    c.gridx = 1;
    c.gridy = 1;
    layoutParameters.add(preferredSideChooser, c);
    // just to determine the preferred size of the largest possible item
    preferredSideChooser.addItem("bottomright");
    preferredSideChooser.setSize(preferredSideChooser.getPreferredSize());
    // later, we need this size to make sure the chooser doesn't jitter
    // when changing from local to global side association
    preferredSideChooser.setToolTipText("Select the preferred side for all labels");
    preferredSideChooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        int oldSide = preferredSide;
        if ((String) event.getItem() == "left")
          preferredSide = IlvDirection.Left;
        if ((String) event.getItem() == "right")
          preferredSide = IlvDirection.Right;
        if ((String) event.getItem() == "top")
          preferredSide = IlvDirection.Top;
        if ((String) event.getItem() == "bottom")
          preferredSide = IlvDirection.Bottom;
        if ((String) event.getItem() == "topleft")
          preferredSide = IlvDirection.TopLeft;
        if ((String) event.getItem() == "topright")
          preferredSide = IlvDirection.TopRight;
        if ((String) event.getItem() == "bottomleft")
          preferredSide = IlvDirection.BottomLeft;
        if ((String) event.getItem() == "bottomright")
          preferredSide = IlvDirection.BottomRight;
        if (oldSide != preferredSide && !duringAssociationChange)
          setLayoutParameters();
      }
    });
    if (sideAssociation == IlvAnnealingPolylineLabelDescriptor.LOCAL)
      makePreferredSideChooserLocal();
    else
      makePreferredSideChooserGlobal();

    // allowed side parameter
    c.gridx = 0;
    c.gridy = 2;
    layoutParameters.add(new JLabel("Allowed Side: "), c);
    c.gridx = 1;
    c.gridy = 2;
    layoutParameters.add(allowedSideChooser, c);
    // just to determine the preferred size of the largest possible item
    allowedSideChooser.addItem("bottomright");
    allowedSideChooser.setSize(allowedSideChooser.getPreferredSize());
    // later, we need this size to make sure the chooser doesn't jitter
    // when changing from local to global side association
    allowedSideChooser.setToolTipText("Select the allowed side for all labels");
    allowedSideChooser.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == ItemEvent.DESELECTED)
          return;
        int oldSide = allowedSide;
        if ((String) event.getItem() == "all")
          allowedSide = 0;
        if ((String) event.getItem() == "left")
          allowedSide = IlvDirection.Left;
        if ((String) event.getItem() == "right")
          allowedSide = IlvDirection.Right;
        if ((String) event.getItem() == "top")
          allowedSide = IlvDirection.Top;
        if ((String) event.getItem() == "bottom")
          allowedSide = IlvDirection.Bottom;
        if ((String) event.getItem() == "topleft")
          allowedSide = IlvDirection.TopLeft;
        if ((String) event.getItem() == "topright")
          allowedSide = IlvDirection.TopRight;
        if ((String) event.getItem() == "bottomleft")
          allowedSide = IlvDirection.BottomLeft;
        if ((String) event.getItem() == "bottomright")
          allowedSide = IlvDirection.BottomRight;
        if (oldSide != allowedSide && !duringAssociationChange)
          setLayoutParameters();
      }
    });
    if (sideAssociation == IlvAnnealingPolylineLabelDescriptor.LOCAL)
      makeAllowedSideChooserLocal();
    else
      makeAllowedSideChooserGlobal();

    // minimal offset to other obstacles
    c.gridx = 3;
    c.gridy = 0;
    layoutParameters.add(minOffset, c);
    minOffset.setToolTipText("Set the minimal offset to unrelated obstacles");
    minOffset.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        try {
          JTextField textfield = (JTextField) e.getSource();
          double value = Double.valueOf(textfield.getText().trim()).doubleValue();
          setMinimalOffset(value);
        } catch (Exception ex) {
          showMessage("Illegal minimal offset: " + ex.getMessage());
        }
      }
    });

    // preferred offset to own obstacle
    c.gridx = 3;
    c.gridy = 1;
    layoutParameters.add(prefOffset, c);
    prefOffset.setToolTipText("Set the preferred offset between label and related link");
    prefOffset.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        try {
          JTextField textfield = (JTextField) e.getSource();
          float value = Float.valueOf(textfield.getText().trim()).floatValue();
          setPreferredOffset(value);
        } catch (Exception ex) {
          showMessage("Illegal preferred offset: " + ex.getMessage());
        }
      }
    });

    // overlap offset to own obstacle
    c.gridx = 3;
    c.gridy = 2;
    layoutParameters.add(ovlpOffset, c);
    ovlpOffset.setToolTipText("Set the allowed overlap offset between label and related link");
    ovlpOffset.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        try {
          JTextField textfield = (JTextField) e.getSource();
          float value = Float.valueOf(textfield.getText().trim()).floatValue();
          setAllowedOverlapOffset(value);
        } catch (Exception ex) {
          showMessage("Illegal allowed overlap offset: " + ex.getMessage());
        }
      }
    });

    c.insets = new Insets(0, 25, 0, 0);
    c.gridx = 2;
    c.gridy = 0;
    layoutParameters.add(new JLabel("Minimal Offset: "), c);
    c.gridx = 2;
    c.gridy = 1;
    layoutParameters.add(new JLabel("Preferred Offset: "), c);
    c.gridx = 2;
    c.gridy = 2;
    layoutParameters.add(new JLabel("Overlap Offset: "), c);

    // add the message line to the bottom panel
    bottomPanel.add("South", msgLine);
    msgLine.setEditable(false);

    // load a sample manager
    try {
      manager.deleteAll(false);
      readManager("small1");
      afterRead();
    } catch (Exception e) {
      // e.printStackTrace();
      showMessage(e.getMessage());
    }
  }

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

        JFrame frame = new JFrame("Label Layout Demo");
        // EXIT_ON_CLOSE is not defined in jdk1.2, hence this way:
        frame.setDefaultCloseOperation(3 /* EXIT_ON_CLOSE, >= jdk1.2.2 */);
        frame.setSize(600, 600);
        frame.getContentPane().add(app);
        frame.setVisible(true);
      }
    });
  }

  /**
   * Read the grapher.
   */
  private void readManager(String fileNameBase) throws IOException, IlvReadFileException {
    showMessage("Reading " + fileNameBase + ".ivl ...");
    manager.read(IlvSwingUtil.getRelativeURL(this, "data/" + fileNameBase + ".ivl"));
    showMessage("Reading " + fileNameBase + ".ivl done.");
  }

  /**
   * Prepare the preferred side chooser for local side association.
   */
  private void makePreferredSideChooserLocal() {
    if ((preferredSide & IlvDirection.Left) != 0)
      preferredSide = IlvDirection.Left;
    else
      preferredSide = IlvDirection.Right;
    int auxPreferredSide = preferredSide;
    Dimension d = preferredSideChooser.getSize();
    preferredSideChooser.removeAllItems();
    preferredSideChooser.addItem("left");
    preferredSideChooser.addItem("right");
    preferredSideChooser.setSelectedIndex(auxPreferredSide == IlvDirection.Left ? 0 : 1);
    preferredSideChooser.setPreferredSize(d);
  }

  /**
   * Prepare the preferred side chooser for global side association.
   */
  private void makePreferredSideChooserGlobal() {
    int auxPreferredSide = preferredSide;
    preferredSideChooser.removeAllItems();
    preferredSideChooser.addItem("left");
    preferredSideChooser.addItem("right");
    preferredSideChooser.addItem("top");
    preferredSideChooser.addItem("bottom");
    preferredSideChooser.addItem("topleft");
    preferredSideChooser.addItem("topright");
    preferredSideChooser.addItem("bottomleft");
    preferredSideChooser.addItem("bottomright");
    // current choice didn't change. But just to be sure.
    if (auxPreferredSide == IlvDirection.Left)
      preferredSideChooser.setSelectedIndex(0);
    if (auxPreferredSide == IlvDirection.Right)
      preferredSideChooser.setSelectedIndex(1);
  }

  /**
   * Prepare the allowed side chooser for local side association.
   */
  private void makeAllowedSideChooserLocal() {
    if ((allowedSide & IlvDirection.Left) != 0)
      allowedSide = IlvDirection.Left;
    else if ((allowedSide & IlvDirection.Right) != 0)
      allowedSide = IlvDirection.Right;
    else
      allowedSide = 0;
    int auxAllowedSide = allowedSide;
    Dimension d = allowedSideChooser.getSize();
    allowedSideChooser.removeAllItems();
    allowedSideChooser.addItem("all");
    allowedSideChooser.addItem("left");
    allowedSideChooser.addItem("right");
    allowedSideChooser.setSelectedIndex(auxAllowedSide == 0 ? 0 : (auxAllowedSide == IlvDirection.Left ? 1 : 2));
    allowedSideChooser.setPreferredSize(d);
  }

  /**
   * Prepare the allowed side chooser for global side association.
   */
  private void makeAllowedSideChooserGlobal() {
    int auxAllowedSide = allowedSide;
    allowedSideChooser.removeAllItems();
    allowedSideChooser.addItem("all");
    allowedSideChooser.addItem("left");
    allowedSideChooser.addItem("right");
    allowedSideChooser.addItem("top");
    allowedSideChooser.addItem("bottom");
    allowedSideChooser.addItem("topleft");
    allowedSideChooser.addItem("topright");
    allowedSideChooser.addItem("bottomleft");
    allowedSideChooser.addItem("bottomright");
    // current choice didn't change. But just to be sure.
    if (auxAllowedSide == 0)
      allowedSideChooser.setSelectedIndex(0);
    if (auxAllowedSide == IlvDirection.Left)
      allowedSideChooser.setSelectedIndex(1);
    if (auxAllowedSide == IlvDirection.Right)
      allowedSideChooser.setSelectedIndex(2);
  }

  /**
   * Performs the layout of the graph.
   */
  private void layout(IlvLabelLayout layout, IlvManager manager) {
    // the layout report instance; this is an object in which
    // the layout algorithm stores information about its behavior
    IlvLabelLayoutReport layoutReport = null;

    showMessage("layout started...");

    // hide the label assignment lines because they should not be obstacles
    boolean labelAssignmentWasVisible = labelAssignmentVisible;
    hideLabelAssignment();

    // initialize the iteration listener
    layoutListener.initialize();

    try {
      // perform the layout and get the layout report
      layoutReport = layout.performLayout(false, false);

      // print the code from the layout report
      showMessage(layoutReport.codeToString(layoutReport.getCode()) + "." + " Time: "
          + (0.001f * layoutReport.getLayoutTime()) + " sec." + " Overlap: " + getOverlapAsString());
    } catch (Exception e) {
      // e.printStackTrace();
      showMessage(e.getMessage());
    } finally {
      if (layoutReport != null && layoutReport.getCode() != IlvLabelLayoutReport.NOT_NEEDED) {
        if (labelAssignmentWasVisible)
          showLabelAssignment();
        mgrview.repaint();
      }
    }
  }

  /**
   * 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());
  }

  /**
   * Some special handling after read. Here we determine dynamically which label
   * belongs to which link. Other obstacles have no labels in this sample.
   */
  void afterRead() {
    int numObstacles = labelingModel.getObstaclesCount();
    int numLabels = labelingModel.getLabelsCount();
    obstacles = new Object[numObstacles];
    labels = new Object[numLabels];
    labelsPerObstacle = new int[numObstacles];
    assignmentLines = new IlvLine[numLabels];

    // initialize the table of obstacles
    Enumeration<?> e = labelingModel.getObstacles();
    int numLinks = 0;
    int i = 0;
    while (e.hasMoreElements()) {
      obstacles[i] = e.nextElement();
      if (obstacles[i] instanceof IlvLinkImage)
        numLinks++;
      labelsPerObstacle[i] = 0;
      i++;
    }

    int maxLabelsPerObstacle;
    if (numLabels <= numLinks)
      maxLabelsPerObstacle = 1;
    else if (numLabels <= 3 * numLinks)
      maxLabelsPerObstacle = 3;
    else
      maxLabelsPerObstacle = 10000;

    // initialize the table of labels
    e = labelingModel.getLabels();
    i = 0;
    while (e.hasMoreElements()) {
      labels[i] = e.nextElement();
      assignmentLines[i] = new IlvLine(0, 0, 0, 0);
      i++;
    }

    // assign to each label any obstacle randomly. But no obstacle should get
    // more than maxLabelsPerObstacle labels.

    Random rand = new Random(0);

    assignmentTable = new Hashtable<Object, IlvLinkImage>();
    e = labelingModel.getLabels();
    while (e.hasMoreElements()) {
      Object label = e.nextElement();
      while (label != null) {
        i = rand.nextInt();
        if (i < 0)
          i = -i;
        i = (i % numObstacles);
        if (obstacles[i] instanceof IlvLinkImage && labelsPerObstacle[i] < maxLabelsPerObstacle) {
          assignmentTable.put(label, (IlvLinkImage) obstacles[i]);
          labelsPerObstacle[i]++;
          label = null;
        }
      }
    }

    // if necessary, make the label background transparent
    if (noLabelBackground)
      setNoLabelBackground(true);

    setLayoutParameters();

    if (labelAssignmentVisible)
      showLabelAssignment();
    mgrview.fitTransformerToContent();
    manager.reDraw();
  }

  /**
   * Set the layout parameters for simulated annealing.
   */
  void setLayoutParameters() {
    // now set the label descriptors.
    try {
      IlvZoomableLabel label;
      IlvLinkImage link;
      int anchor;

      for (int i = 0; i < labels.length; i++) {
        label = (IlvZoomableLabel) labels[i];
        link = assignmentTable.get(label);

        // in this sample, it is a good idea to look at the label text to
        // decide what should be the anchor. This just for demonstration
        // purposes.

        if (label.getLabel().indexOf("Center") >= 0)
          anchor = IlvAnnealingPolylineLabelDescriptor.CENTER;
        else if (label.getLabel().indexOf("Start") >= 0)
          anchor = IlvAnnealingPolylineLabelDescriptor.START;
        else if (label.getLabel().indexOf("End") >= 0)
          anchor = IlvAnnealingPolylineLabelDescriptor.END;
        else
          anchor = IlvAnnealingPolylineLabelDescriptor.FREE;

        layout.setLabelDescriptor(label,
            new IlvAnnealingPolylineLabelDescriptor(label, link, link.getFrom(), link.getTo(), anchor,
                prefDistFromOwnObstacle, prefDistFromOwnObstacle, preferredSide, allowedSide, sideAssociation,
                allowedOverlapOffset, allowedOverlapOffset, allowedOverlapOffset, allowedOverlapOffset));
      }
      layout.setAllowedTime(240000);
    } catch (Exception ex) {
      // ex.printStackTrace();
      showMessage("Illegal label descriptor: " + ex.getMessage());
    }
  }

  /**
   * Set the minimal offset between label and obstacle. We also take 20% of this
   * value as the minimal offset between pairs of labels.
   */
  void setMinimalOffset(double value) {
    if (value > 100)
      value = 100;
    if (value < 0)
      value = 0;

    // write again to be sure the right value is shown
    minOffset.setText("" + value);
    // set the new value of the parameter
    layout.setObstacleOffset(value);
    // also, increase the offset between the labels a little bit
    layout.setLabelOffset(0.2d * value);
  }

  /**
   * Set the preferred offset for all labels.
   */
  void setPreferredOffset(double value) {
    if (value > 30.0)
      value = 30.0;
    if (value < 0.0)
      value = 0.0;

    // write again to be sure the right value is shown
    prefOffset.setText("" + value);

    // set the new value of the parameter
    double oldval = prefDistFromOwnObstacle;
    prefDistFromOwnObstacle = value;
    // need to update the layout parameters only if something changed
    if (oldval != value)
      setLayoutParameters();
  }

  /**
   * Set the allowed overlap offset.
   */
  void setAllowedOverlapOffset(double value) {
    if (value > 30.0)
      value = 30.0;
    if (value < 0.0)
      value = 0.0;

    // write again to be sure the right value is shown
    ovlpOffset.setText("" + value);

    // set the new value of the parameter
    double oldval = allowedOverlapOffset;
    allowedOverlapOffset = value;
    // need to update the layout parameters only if something changed
    if (oldval != value)
      setLayoutParameters();
  }

  /**
   * Update the label assignment for all labels. This means, actually it update
   * the auxiliary line that shows the label assignment.
   */
  void updateLabelAssignment() {
    for (int i = 0; i < labels.length; i++) {
      IlvLine line = assignmentLines[i];
      IlvZoomableLabel label = (IlvZoomableLabel) labels[i];
      Object obstacle = assignmentTable.get(label);
      IlvRect lr = labelingModel.boundingBox(label);
      updateLabelAssignment(line, lr, obstacle);
    }
  }

  /**
   * Update the auxiliary line showing label assignment for one label.
   */
  void updateLabelAssignment(IlvLine line, IlvRect labelRect, Object obstacle) {
    // first test point is the middle of the polyline
    IlvPoint[] pts = labelingModel.getPolylinePoints(obstacle);
    IlvPoint p = getPolylineMiddle(pts);
    double ox = p.x;
    double oy = p.y;

    double bestX, bestY, tx, ty;
    double bestDistSqr;
    double distSqr;

    if (noLabelBackground) {
      // for transparent labels, show the aux line from label center
      bestX = labelRect.x + 0.5 * labelRect.width;
      bestY = labelRect.y + 0.5 * labelRect.height;
      bestDistSqr = (bestX - ox) * (bestX - ox) + (bestY - oy) * (bestY - oy);

      // check whether there is a better point if we take a vertical line
      tx = getPolylineX(bestY, bestX, pts);
      distSqr = (bestX - tx) * (bestX - tx);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = tx;
        oy = bestY;
      }

      // check whether there is a better point if we take a horizontal line
      ty = getPolylineY(bestX, bestY, pts);
      distSqr = (bestY - ty) * (bestY - ty);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = bestX;
        oy = ty;
      }
    } else {
      // for nonstransparent labels, show the aux line from the closest
      // corner of the label to (ox, oy)

      double x, y;

      // first, try to the center

      // top left corner
      bestX = labelRect.x;
      bestY = labelRect.y;
      bestDistSqr = (bestX - ox) * (bestX - ox) + (bestY - oy) * (bestY - oy);

      // top right corner
      x = labelRect.x + labelRect.width;
      y = labelRect.y;
      distSqr = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        bestX = x;
        bestY = y;
      }

      // bottom right corner
      x = labelRect.x + labelRect.width;
      y = labelRect.y + labelRect.height;
      distSqr = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        bestX = x;
        bestY = y;
      }

      // bottom left corner
      x = labelRect.x;
      y = labelRect.y + labelRect.height;
      distSqr = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        bestX = x;
        bestY = y;
      }

      // now try vertical projection

      // top left corner
      x = labelRect.x;
      y = labelRect.y;
      tx = getPolylineX(y, x, pts);
      distSqr = (x - tx) * (x - tx);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = tx;
        oy = y;
        bestX = x;
        bestY = y;
      }

      // top right corner
      x = labelRect.x + labelRect.width;
      y = labelRect.y;
      tx = getPolylineX(y, x, pts);
      distSqr = (x - tx) * (x - tx);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = tx;
        oy = y;
        bestX = x;
        bestY = y;
      }

      // bottom right corner
      x = labelRect.x + labelRect.width;
      y = labelRect.y + labelRect.height;
      tx = getPolylineX(y, x, pts);
      distSqr = (x - tx) * (x - tx);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = tx;
        oy = y;
        bestX = x;
        bestY = y;
      }

      // bottom left corner
      x = labelRect.x;
      y = labelRect.y + labelRect.height;
      tx = getPolylineX(y, x, pts);
      distSqr = (x - tx) * (x - tx);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = tx;
        oy = y;
        bestX = x;
        bestY = y;
      }

      // now try horizontal projection

      // top left corner
      x = labelRect.x;
      y = labelRect.y;
      ty = getPolylineY(x, y, pts);
      distSqr = (y - ty) * (y - ty);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = x;
        oy = ty;
        bestX = x;
        bestY = y;
      }

      // top right corner
      x = labelRect.x + labelRect.width;
      y = labelRect.y;
      ty = getPolylineY(x, y, pts);
      distSqr = (y - ty) * (y - ty);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = x;
        oy = ty;
        bestX = x;
        bestY = y;
      }

      // bottom right corner
      x = labelRect.x + labelRect.width;
      y = labelRect.y + labelRect.height;
      ty = getPolylineY(x, y, pts);
      distSqr = (y - ty) * (y - ty);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = x;
        oy = ty;
        bestX = x;
        bestY = y;
      }

      // bottom left corner
      x = labelRect.x;
      y = labelRect.y + labelRect.height;
      ty = getPolylineY(x, y, pts);
      distSqr = (y - ty) * (y - ty);
      if (distSqr < bestDistSqr) {
        bestDistSqr = distSqr;
        ox = x;
        oy = ty;
        bestX = x;
        bestY = y;
      }

    }

    // set coordinates and color of the line
    line.setFrom(new IlvPoint(ox, oy));
    line.setTo(new IlvPoint(bestX, bestY));
    line.setForeground(Color.red);
  }

  /**
   * Show the label assignment.
   */
  void showLabelAssignment() {
    blockEventsCounter++;
    try {
      manager.setContentsAdjusting(true);

      // always update the label assignment when the lines are hidden
      if (labelAssignmentVisible) {
        hideLabelAssignment();
      }

      updateLabelAssignment();

      if (!labelAssignmentVisible) {
        for (int i = 0; i < labels.length; i++) {
          manager.addObject(assignmentLines[i], 0, false);
          manager.setSelectable(assignmentLines[i], false);
        }
        labelAssignmentVisible = true;
      }

      manager.setContentsAdjusting(false);
      manager.reDraw();
    } finally {
      blockEventsCounter--;
    }
  }

  /**
   * Hide the label assignment.
   */
  void hideLabelAssignment() {
    if (labelAssignmentVisible) {
      blockEventsCounter++;
      try {
        manager.setContentsAdjusting(true);
        for (int i = 0; i < labels.length; i++)
          if (assignmentLines[i].getGraphicBag() != null)
            manager.removeObject(assignmentLines[i], false);
        manager.setContentsAdjusting(false);
        manager.reDraw();
        labelAssignmentVisible = false;
      } finally {
        blockEventsCounter--;
      }
    }
  }

  /**
   * Calculate the middle point of the polyline.
   */
  IlvPoint getPolylineMiddle(IlvPoint[] pts) {
    // first, calculate the line length;
    double lineLength = 0.0;
    double dx, dy;
    int i;

    for (i = 1; i < pts.length; i++) {
      dx = pts[i].x - pts[i - 1].x;
      dy = pts[i].y - pts[i - 1].y;
      lineLength += Math.sqrt(dx * dx + dy * dy);
    }

    // now go to the middle
    double wishLength = lineLength / 2;
    double dist;

    for (i = 1; i < pts.length; i++) {
      dx = pts[i].x - pts[i - 1].x;
      dy = pts[i].y - pts[i - 1].y;
      dist = Math.sqrt(dx * dx + dy * dy);
      if (wishLength <= dist) {
        return new IlvPoint(pts[i - 1].x + wishLength / dist * dx,
                            pts[i - 1].y + wishLength / dist * dy);
      }
      wishLength -= dist;
    }

    return pts[0];
  }

  /**
   * Calculates the x coord of a point on the polyline to a given y coord. It
   * returns on ambiguity the point that is closest to the reference x point. It
   * returns POSITIVE_INFINITY if the polyline has no point at the given y
   * coordinate.
   */
  double getPolylineX(double y, double refX, IlvPoint[] pts) {
    double bestX = Double.POSITIVE_INFINITY;
    double bestDist = Double.MAX_VALUE;
    double x, d;
    double dx, dy;

    for (int i = 1; i < pts.length; i++) {
      if (pts[i - 1].y == pts[i].y && pts[i].y == y) {
        d = Math.abs(pts[i - 1].x - refX);
        if (d < bestDist) {
          bestDist = d;
          bestX = pts[i - 1].x;
        }
        d = Math.abs(pts[i].x - refX);
        if (d < bestDist) {
          bestDist = d;
          bestX = pts[i].x;
        }
      } else if ((pts[i - 1].y <= y && y <= pts[i].y) || (pts[i].y <= y && y <= pts[i - 1].y)) {
        dx = pts[i].x - pts[i - 1].x;
        dy = pts[i].y - pts[i - 1].y;
        x = pts[i - 1].x + (y - pts[i - 1].y) * dx / dy;
        d = Math.abs(x - refX);
        if (d < bestDist) {
          bestDist = d;
          bestX = x;
        }
      }
    }

    return bestX;
  }

  /**
   * Calculates the y coord of a point on the polyline to a given x coord. It
   * returns on ambiguity the point that is closest to the reference y point. It
   * returns POSITIVE_INFINITY if the polyline has no point at the given x
   * coordinate.
   */
  double getPolylineY(double x, double refY, IlvPoint[] pts) {
    double bestY = Double.POSITIVE_INFINITY;
    double bestDist = Double.MAX_VALUE;
    double y, d;
    double dx, dy;

    for (int i = 1; i < pts.length; i++) {
      if (pts[i - 1].x == pts[i].x && pts[i].x == x) {
        d = Math.abs(pts[i - 1].y - refY);
        if (d < bestDist) {
          bestDist = d;
          bestY = pts[i - 1].y;
        }
        d = Math.abs(pts[i].y - refY);
        if (d < bestDist) {
          bestDist = d;
          bestY = pts[i].y;
        }
      } else if ((pts[i - 1].x <= x && x <= pts[i].x) || (pts[i].x <= x && x <= pts[i - 1].x)) {
        dx = pts[i].x - pts[i - 1].x;
        dy = pts[i].y - pts[i - 1].y;
        y = pts[i - 1].y + (x - pts[i - 1].x) * dy / dx;
        d = Math.abs(y - refY);
        if (d < bestDist) {
          bestDist = d;
          bestY = y;
        }
      }
    }

    return bestY;
  }

  /**
   * Show or hide the background of all labels.
   */
  void setNoLabelBackground(boolean flag) {
    IlvZoomableLabel label;
    IlvApplyObject apply;
    if (flag)
      apply = new IlvApplyObject() {
        Override
        public void apply(IlvGraphic obj, Object arg) {
          IlvZoomableLabel l = (IlvZoomableLabel) obj;
          l.setBorderOn(false);
          l.setBackgroundOn(false);
        }
      };
    else
      apply = new IlvApplyObject() {
        Override
        public void apply(IlvGraphic obj, Object arg) {
          IlvZoomableLabel l = (IlvZoomableLabel) obj;
          l.setBorderOn(true);
          l.setBackgroundOn(true);
        }
      };

    for (int i = 0; i < labels.length; i++) {
      label = (IlvZoomableLabel) labels[i];
      manager.applyToObject(label, apply, null, true);
    }
  }

  /**
   * Calculate the overlap amount.
   */
  String getOverlapAsString() {
    double overlap = 0.0;
    double maxOverlap = 0.0;
    int i, j;
    IlvRect r1, r2;
    IlvPoint[] pts;
    double w;
    Object ownerObstacle;

    // calculate the actual overlap value, and the hypothetical maximal
    // overlap. The hypothetical maximal overlap is used to print some
    // percentage value.

    for (i = 0; i < labels.length; i++) {
      r1 = labelingModel.boundingBox(labels[i]);
      for (j = i + 1; j < labels.length; j++) {
        r2 = labelingModel.boundingBox(labels[j]);
        overlap += labelingModel.getLabelOverlap(labels[i], r1, labels[j], r2, 0.0);
        maxOverlap += 0.5 * r1.width * r1.height;
        maxOverlap += 0.5 * r2.width * r2.height;
      }
    }

    for (i = 0; i < labels.length; i++) {
      r1 = labelingModel.boundingBox(labels[i]);
      ownerObstacle = assignmentTable.get(labels[i]);
      for (j = 0; j < obstacles.length; j++) {
        if (obstacles[j] != ownerObstacle) {
          if (labelingModel.isPolylineObstacle(obstacles[j])) {
            r2 = labelingModel.boundingBox(obstacles[j]);
            pts = labelingModel.getPolylinePoints(obstacles[j]);
            w = labelingModel.getPolylineWidth(obstacles[j]);
            overlap += labelingModel.getPolylineObstacleOverlap(labels[i], r1, obstacles[j], pts, w, 0.0);
            maxOverlap += 0.5 * r1.width * r1.height;
            maxOverlap += 0.5 * r2.width * r2.height;
          } else {
            r2 = labelingModel.boundingBox(obstacles[j]);
            overlap += labelingModel.getObstacleOverlap(labels[i], r1, obstacles[j], r2, 0.0);
            maxOverlap += 0.5 * r1.width * r1.height;
            maxOverlap += 0.5 * r2.width * r2.height;
          }
        }
      }
    }

    // the percentage of overlap
    double perc = 100 * (overlap / maxOverlap);

    // calculate the absolute overlap, with 2 digits after dot.
    overlap = ((int) (overlap * 100)) / 100.0;

    // calculate the logarithmic overlap percentage (with 2 digits after dot)
    // The logarithmic percentage is here more useful because it represents
    // better how much the overlaps disturb the user.
    perc = 100 / Math.log(101) * Math.log(perc + 1);
    float logPerc = ((int) (100 * perc)) / 100.0f;

    return ("" + overlap + " (Log. Perc.: " + logPerc + "%)");
  }

  /**
   * Preprocess graph: Add some local parameters for demonstration purpose.
   * Here, it tells the random layout about the layout region.
   */
  void preprocess() {
    IlvGraphic obj;

    // calculate the layout region
    IlvGraphicEnumeration selectedObjects = manager.getObjects();
    double minX = Double.MAX_VALUE;
    double maxX = -Double.MAX_VALUE;
    double minY = Double.MAX_VALUE;
    double maxY = -Double.MAX_VALUE;

    while (selectedObjects.hasMoreElements()) {
      obj = selectedObjects.nextElement();
      if (labelingModel.isObstacle(obj)) {
        IlvRect r = labelingModel.boundingBox(obj);
        if (r.x < minX)
          minX = r.x;
        if (r.x + r.width > maxX)
          maxX = r.x + r.width;
        if (r.y < minY)
          minY = r.y;
        if (r.y + r.height > maxY)
          maxY = r.y + r.height;
      }
    }

    randomLayout.setLayoutRegion(new IlvRect(minX, minY, (maxX - minX), (maxY - minY)));
  }

  /**
   * Listen to changes of the manager.
   */
  Override
  public void contentsChanged(ManagerContentChangedEvent evt) {
    // the label assignment, if visible, should be kept up to date
    if (!evt.isAdjusting() && blockEventsCounter == 0) {
      if (labelAssignmentVisible)
        showLabelAssignment();
    }
  }

  /**
   * Listen to selections of the manager.
   */
  Override
  public void selectionChanged(ManagerSelectionChangedEvent event) {
    if (labelingModel != null && layout.isAutoUpdate()) {
      // to make sure a deselected object is always moveable
      if (event.getGraphic() != null && !(event.getGraphic() instanceof IlvLinkImage))
        manager.setMovable(event.getGraphic(), true);

      // we need to make sure that, if a label and its related link (or
      // one of its end nodes) are both selected, that the label is
      // registered nonmovable. Otherwise we get weird effects because
      // the label tries to follow the link by itself.

      IlvGraphicEnumeration e = manager.getSelectedObjects(false);
      while (e.hasMoreElements()) {
        IlvGraphic g = e.nextElement();
        if (labelingModel.isLabel(g)) {
          IlvLinkImage link = assignmentTable.get(g);
          if (link != null
              && (manager.isSelected(link) || manager.isSelected(link.getFrom()) || manager.isSelected(link.getTo())))
            manager.setMovable(g, false);
          else
            manager.setMovable(g, true);
        }
      }
    }
  }

  /**
   * Listen to changes of the transformer.
   */
  Override
  public void transformerChanged(TransformerChangedEvent event) {
    IlvTransformer t = event.getNewValue();
    // limit the zoom factor. Otherwise, we may run into serious rounding
    // problems in the arithmetic formulas of the auxiliary lines.
    if (t.zoomFactor() < 0.01) {
      t.setValues(0.01, t.getx12(), t.getx21(), 0.01);
      mgrview.setTransformer(t);
    } else if (t.zoomFactor() > 20.0) {
      t.setValues(20.0, t.getx12(), t.getx21(), 20.0);
      mgrview.setTransformer(t);
    } else if (labelAssignmentVisible) {
      // update the label assignment just for sure.
      showLabelAssignment();
      manager.reDraw();
    }
  }
}

/**
 * A label layout iteration listener. Implementing the interface
 * LabelLayoutEventListener gives you the possibility to receive during the
 * layout information about the behavior of the layout algorithm. This
 * information is contained in the label layout event.
 * 
 * In our case, we will simply print the percentage completed by the layout each
 * time the method layoutStepPerformed is called, that is after each iteration
 * of the Annealing Label Layout algorithm.
 */
class LinkLabelingListener implements LabelLayoutEventListener {
  private float oldPercentage;
  private LinkLabelLayoutApp app;

  LinkLabelingListener(LinkLabelLayoutApp app) {
    this.app = app;
  }

  /**
   * This method is automatically called by the layout algorithm.
   */
  Override
  public void layoutStepPerformed(LabelLayoutEvent event) {
    float percentage = event.getLayoutReport().getPercentageComplete();
    if (percentage != oldPercentage) {
      app.showMessage("" + percentage + "%");
      oldPercentage = percentage;
    }
  }

  /**
   * Initialize the listener by reseting the old percentage variable. This
   * method must be called before the layout is started.
   */
  void initialize() {
    oldPercentage = -1.0f;
  }
}

/**
 * A manager view with limited range for zooming. It doesn't allow to set the
 * zoom level below 0.001 or above 30.0, so that the demo runs smoothly even if
 * the user stress-tests the zoomin and zoomout button.
 */
class LinkLabelingManagerView extends IlvManagerView {
  boolean insideVerifyTransformer = false;

  public LinkLabelingManagerView(IlvManager manager) {
    super(manager);
  }

  /**
   * Checks a transformer. This method is called every time the transformer of
   * the view is changed by calling <code>setTransformer</code> or
   * <code>addTransformer</code>. It makes sure that the zoomFactor is between
   * 0.001 and 30.0.
   */
  Override
  public void verifyTransformer() {
    super.verifyTransformer();
    if (insideVerifyTransformer)
      return;
    IlvTransformer t = getTransformer();
    double zoomFactor = t.zoomFactor();
    if (zoomFactor < 0.001) {
      double x0 = t.getx0() * 0.999 / (1 - zoomFactor);
      double y0 = t.gety0() * 0.999 / (1 - zoomFactor);
      t.setValues(0.001, t.getx12(), t.getx21(), 0.001, x0, y0);
      insideVerifyTransformer = true;
      setTransformer(t);
      insideVerifyTransformer = false;
    } else if (zoomFactor > 30.0) {
      double x0 = t.getx0() * 29.0 / (zoomFactor - 1);
      double y0 = t.gety0() * 29.0 / (zoomFactor - 1);
      t.setValues(30.0, t.getx12(), t.getx21(), 30.0, x0, y0);
      insideVerifyTransformer = true;
      setTransformer(t);
      insideVerifyTransformer = false;
    }
  }
}