/*
 * 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.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JCheckBox;
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.ManagerSelectionChangedEvent;
import ilog.views.event.ManagerSelectionListener;
import ilog.views.event.TransformerChangedEvent;
import ilog.views.event.TransformerListener;
import ilog.views.graphic.IlvReliefRectangle;
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.IlvLabelingModel;
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.interactor.IlvSelectInteractor;
import ilog.views.interactor.IlvZoomViewInteractor;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.IlvProductUtil;

/**
 * The main class LinkLabelLayoutApp.
 */
public class LinkLabelLayoutApp extends JRootPane implements 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 */
  LabelingModel labelingModel;

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

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

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

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

  /** Offset distances */
  static final double minDistToOtherObstacles = 10.0;

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

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

  /**
   * In this demo, the labels are assigned to links (IlvLabelLinkImage). The
   * labels should be placed close to the links. The link automatically rotates
   * the labels according to the link segment where the label attaches.
   */
  Hashtable<IlvGraphic, IlvLabelLinkImage> assignmentTable;
  Object[] obstacles;
  Object[] labels;

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

    JPanel panel;
  
    // 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
    labelingModel = new LabelingModel(manager);

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

    // links of class IlvLabelLinkImage update the label positions themselves.
    // Therefore the autoupdate feature of the label layout is not needed
    // for those links.
    layout.setAutoUpdate(false);

    // the rotation formulas of this example are only good for manager mode
    layout.setCoordinatesMode(IlvLabelLayout.MANAGER_COORDINATES);
    labelingModel.setCoordinatesMode(IlvLabelLayout.MANAGER_COORDINATES);

    // 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 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) {
        randomize(manager);
      }
    });

    layoutPanel.add(b = new JButton("Layout"));
    b.setToolTipText("Perform a label layout");
    b.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent evt) {
        try {
          float value;
          // validate the min offset
          value = Float.valueOf(minOffset.getText().trim()).floatValue();
          setMinimumOffset(value);
        } catch (Exception ex) {
          // ex.printStackTrace();
          showMessage("Illegal offset: " + ex.getMessage());
          return;
        }
        // now the layout
        layout(layout, manager);
      }
    });

    // 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", !noLabelBackground);
    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);
        manager.reDraw();
      }
    });

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

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

    c.insets = new Insets(0, 25, 0, 0);
    c.gridx = 0;
    c.gridy = 0;
    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);
      fillGrapher();
    } 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);
      }
    });
  }

  /**
   * 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...");

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

  /**
   * Fills the grapher with the example graph.
   */
  void fillGrapher() {
    // first create a couple of nodes
    IlvGraphic node1 = createNode(100, 10);
    IlvGraphic node2 = createNode(200, 10);
    IlvGraphic node3 = createNode(300, 205);
    IlvGraphic node4 = createNode(100, 280);
    IlvGraphic node5 = createNode(10, 390);
    IlvGraphic node6 = createNode(200, 390);

    // now create a couple of links with labels
    IlvPoint[] points;

    createLink(node1, node2, null, "Label");

    points = new IlvPoint[2];
    points[0] = new IlvPoint(10, 100);
    points[1] = new IlvPoint(10, 200);
    createLink(node1, node4, points, "Start");

    points = new IlvPoint[2];
    points[0] = new IlvPoint(220, 120);
    points[1] = new IlvPoint(220, 200);
    createLink(node1, node4, points, "Start");

    createLink(node2, node3, null, "Label");
    createLink(node3, node6, null, "Label");
    createLink(node4, node5, null, "End");
    createLink(node4, node6, null, "End");

    points = new IlvPoint[1];
    points[0] = new IlvPoint(120, 480);
    createLink(node5, node6, points, "Label");

    // register the labels for layout
    registerLabelsAndObstacles();
  }

  /**
   * Create a node.
   */
  IlvGraphic createNode(double x, double y) {
    IlvGrapher grapher = (IlvGrapher) manager;
    IlvRect rect = new IlvRect(x, y, 30, 30);
    IlvReliefRectangle node = new IlvReliefRectangle(rect, 4);
    node.setBackground(Color.gray);
    grapher.addNode(node, false);
    return node;
  }

  /**
   * Create a link.
   */
  void createLink(IlvGraphic node1, IlvGraphic node2, IlvPoint[] bends, String labelString) {
    IlvGrapher grapher = (IlvGrapher) manager;
    IlvZoomableLabel label = new IlvZoomableLabel(new IlvPoint(), labelString, false);
    label.setLeftMargin(4);
    label.setRightMargin(4);
    label.setTopMargin(2);
    label.setBottomMargin(2);
    label.setAntialiasing(true);
    grapher.addObject(label, 2, false);
    IlvLabelLinkImage link = new IlvLabelLinkImage(node1, node2, label, true, bends);
    grapher.addLink(link, false);
  }

  /**
   * Registers the labels and obstacles in this demo. This is needed to show the
   * assignment lines bw. label and related obstacle.
   */
  void registerLabelsAndObstacles() {
    int numObstacles = labelingModel.getObstaclesCount();
    int numLabels = labelingModel.getLabelsCount();
    obstacles = new Object[numObstacles];
    labels = new Object[numLabels];

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

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

    // build the assignment table that tells which label belongs to which
    // obstacle

    assignmentTable = new Hashtable<IlvGraphic, IlvLabelLinkImage>();
    labelingModel.assignmentTable = assignmentTable;
    for (i = 0; i < numObstacles; i++) {
      if (obstacles[i] instanceof IlvLabelLinkImage) {
        IlvGraphic label = ((IlvLabelLinkImage) obstacles[i]).getLabel();
        assignmentTable.put(label, (IlvLabelLinkImage) obstacles[i]);
      }
    }

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

    setLayoutParameters();

    mgrview.fitTransformerToContent();
    manager.reDraw();
  }

  /**
   * Set the layout parameters for simulated annealing.
   */
  void setLayoutParameters() {
    // now set the label descriptors.
    try {
      IlvZoomableLabel label;
      IlvLabelLinkImage 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;

        // IlvLabelLinkImage only supports labels at one side, so the
        // side association must be LOCAL and the allowed and preferred
        // side must match the setting in IlvLabelLinkImage.

        int side = (link.isRightSide() ? IlvDirection.Right : IlvDirection.Left);

        layout.setLabelDescriptor(label, new IlvAnnealingPolylineLabelDescriptor(label, link, link.getFrom(),
            link.getTo(), anchor, 0, 0, side, side, IlvAnnealingPolylineLabelDescriptor.LOCAL, 0, 0, 0, 0) {

          Override
          public double getRotation(IlvLabelingModel model, IlvRect labelRect) {
            IlvLabelLinkImage link = assignmentTable.get(getLabel());
            return link.getRotation(labelRect, null);
          }

        });
      }
      layout.setAllowedTime(240000);
    } catch (Exception ex) {
      // ex.printStackTrace();
      showMessage("Illegal label descriptor: " + ex.getMessage());
    }
  }

  /**
   * Set the minimum offset between label and obstacle. We also take 20% of this
   * value as the minimum offset between pairs of labels.
   */
  void setMinimumOffset(double value) {
    if (value > 100.0)
      value = 100.0;
    if (value < 0.0)
      value = 0.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.2 * value);
  }

  /**
   * 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 maximum
    // overlap. The hypothetical maximum 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 randomize(IlvManager manager) {
    Random rand = new Random();

    for (int i = 0; i < labels.length; i++) {
      IlvZoomableLabel label = (IlvZoomableLabel) labels[i];
      int x = rand.nextInt(500);
      int y = rand.nextInt(500);
      manager.moveObject(label, x, y, true);
    }
  }

  /**
   * Listen to selections of the manager.
   */
  Override
  public void selectionChanged(ManagerSelectionChangedEvent event) {
    // 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);
    }
  }
}

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

/**
 * The labeling model that understands the IlvLabelLinkImage with its rotation.
 */
class LabelingModel extends IlvDefaultLabelingModel {
  Hashtable<IlvGraphic, IlvLabelLinkImage> assignmentTable;

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

  /**
   * Returns the bounding rectangle of a label or obstacle in manager's
   * coordinates. We need to make sure that we get the rectangle of the
   * unrotated label, not its current rotated bounding box.
   */
  Override
  public IlvRect boundingBox(Object labelOrObstacle) {
    if (labelOrObstacle instanceof IlvZoomableLabel) {
      return IlvLabelLinkImage.getUnrotatedBoundingBox((IlvZoomableLabel) labelOrObstacle, null);
    }
    return super.boundingBox(labelOrObstacle);
  }

  /**
   * Moves the label. In our case, we have to translate the position into a
   * percentage from the start of the link, because this is the preferred way of
   * moving the label via the link.
   */
  Override
  public void moveLabel(Object label, double x, double y, boolean redraw) {
    if (label instanceof IlvZoomableLabel) {
      IlvZoomableLabel zlabel = (IlvZoomableLabel) label;
      IlvLabelLinkImage link = assignmentTable.get(zlabel);
      IlvRect labelBox = boundingBox(label);
      labelBox.x = x;
      labelBox.y = y;
      float percentage = link.getPercentageFromStart(labelBox, null);
      link.setPercentage(percentage);
      link.updateLabelPosition();
    } else
      super.moveLabel(label, x, y, redraw);
  }
}

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