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

import ilog.views.*;
import ilog.views.io.IlvReadFileException;
import ilog.views.interactor.*;
import ilog.views.event.*;
import ilog.views.graphic.*;
import ilog.views.swing.*;
import ilog.views.graphlayout.labellayout.*;
import ilog.views.graphlayout.labellayout.random.*;
import ilog.views.graphlayout.labellayout.annealing.*;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.swing.IlvSwingUtil;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.IOException;
import javax.swing.*;

/**
 * The main class PointLabelLayoutApplet.
 */
public class PointLabelLayoutApplet 
  extends JApplet
  implements ManagerContentChangedListener,
             ManagerSelectionListener,
             TransformerListener
{
  static {
    // This applet is designed to run only with default resource bundle
    // and various selected other resource bundles.
    // Setting the available resource suffixes avoids that the applet
    // tries to load resource bundles for other locales over the net,
    // even if the current locale of the browser is different.
    if (IlvResourceUtil.isInApplet())
      IlvResourceUtil.setAvailableResourceSuffixes("", "_ja");
  }

  {
    // 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 */
  IlvManager manager = new IlvManager();

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

  /** The view of the manager */
  IlvManagerView mgrview = new PointLabelingManagerView(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 */
  PointLabelingListener layoutListener = new PointLabelingListener(this);

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

  /** The preferred direction of all labels */
  int preferredDirection = IlvDirection.Right;

  /** Offset distances */
  static final float minDistToOtherObstacles = 5f;
  float prefDistFromOwnObstacle = 1f;

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

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

  /**
   * In this demo, the labels are assigned to circles (IlvEllipse)
   * or rectangles (IlvRectangle) representing cities. The labels
   * should be placed close to the cities.
   * Which label belongs to which city 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 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 applet/application.
   */
  public void init()
  {
    showMessage("layout sample initialization...");

    JPanel panel;
    super.init();

    // 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,357));
    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 applet as transformer listener, because
    // the default labeling model will register itself as transformer listener
    // as well when using nonzoomable labels. The aux lines mechanism of this
    // applet requires that the applet receives the event at 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 sample"));    
    JComboBox chooser;
    panel.add(chooser = new JComboBox());
    chooser.setToolTipText("Select a sample");
    chooser.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == event.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");
    chooser.addItem("huge");

    // 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() {
      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() {
      public void actionPerformed(ActionEvent evt) {
        try {
          float value;
          // validate the min offset
          value = Float.valueOf(minOffset.getText().trim()).floatValue();
          setMinimumOffset(value);
          // validate the preferred offset
          value = Float.valueOf(prefOffset.getText().trim()).floatValue();
          setPreferredOffset(value);
        }
        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 obstacles
        layout.setAutoUpdate(true);
      }});

    // create the layout parameters panel
    JPanel layoutParameters = new JPanel();
    layoutParameters.setLayout(new BorderLayout());
    bottomPanel.add("Center", layoutParameters);

    JPanel layoutParameters1 = new JPanel();
    layoutParameters1.setLayout(new GridLayout(2, 1));
    JPanel layoutParameters2 = new JPanel();
    layoutParameters2.setLayout(new GridLayout(2, 2));


    // pref direction parameter
    JPanel auxPanel = new JPanel();
    auxPanel.setLayout(new BorderLayout(0,0));
    layoutParameters1.add(auxPanel);
    JComboBox prefDirectionChooser = new JComboBox();
    prefDirectionChooser.setToolTipText(
        "Select the preferred direction for all labels");
    auxPanel.add("West", new JLabel("Preferred Direction:"));
    auxPanel.add("Center", prefDirectionChooser);
    prefDirectionChooser.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent event) {
        if (event.getStateChange() == event.DESELECTED)
          return;
        int oldDir = preferredDirection;
        if ((String)event.getItem() == "top") {
          preferredDirection = IlvDirection.Top;
        }
        if ((String)event.getItem() == "bottom") {
          preferredDirection = IlvDirection.Bottom;
        }
        if ((String)event.getItem() == "left") {
          preferredDirection = IlvDirection.Left;
        }
        if ((String)event.getItem() == "right") {
          preferredDirection = IlvDirection.Right;
        }
        if (oldDir != preferredDirection)
          setLayoutParameters();
    }});
    prefDirectionChooser.addItem("top");
    prefDirectionChooser.addItem("bottom");
    prefDirectionChooser.addItem("left");
    prefDirectionChooser.addItem("right");
    prefDirectionChooser.setSelectedIndex(3);

    // quadtree 
    auxPanel = new JPanel();
    auxPanel.setLayout(new BorderLayout(0,0));
    layoutParameters1.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() {
      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);
    showLabelBox.setToolTipText(
        "Show or hide the bounding box of the labels");
    auxPanel.add("Center", showLabelBox);
    showLabelBox.addItemListener(new ItemListener() {
      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() {
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED)
          showLabelAssignment();
        else if (e.getStateChange() == ItemEvent.DESELECTED)
          hideLabelAssignment();
      }
    });

    // minimum offset to other obstacles 
    layoutParameters2.add(new JLabel("Min. Offset:"));
    layoutParameters2.add(minOffset);
    minOffset.setToolTipText("Set the minimum offset to unrelated obstacles");
    minOffset.addActionListener(new ActionListener() {
      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());
        }
      }
    });

    // preferred offset to own obstacle 
    layoutParameters2.add(new JLabel("Pref. Offset:"));
    layoutParameters2.add(prefOffset);
    prefOffset.setToolTipText(
       "Set the preferred offset between label and related obstacle");
    prefOffset.addActionListener(new ActionListener() {
      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());
        }
      }
    });

    layoutParameters.add("West", layoutParameters1);
    layoutParameters.add("East", layoutParameters2);

    // 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() {
        public void run() {
          PointLabelLayoutApplet applet = new PointLabelLayoutApplet();
          applet.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(applet);
          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.");
  }

  /**
   * 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 rectangle
   * or ellipse obstacle (representing for instance a city).
   * Polyline 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 numUsableObstacles = 0;
    int i = 0;
    while (e.hasMoreElements()) {
      obstacles[i] = e.nextElement();
      if (obstacles[i] instanceof IlvIcon ||
          obstacles[i] instanceof IlvEllipse || 
          obstacles[i] instanceof IlvRectangle)
         numUsableObstacles++;
      labelsPerObstacle[i] = 0;
      i++;
    }

    int maxLabelsPerObstacle;
    if (numLabels <= numUsableObstacles)
      maxLabelsPerObstacle = 1;
    else if (numLabels <= 3 * numUsableObstacles)
      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(0f,0f,0f,0f);
      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();
    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 IlvIcon ||
             obstacles[i] instanceof IlvEllipse || 
             obstacles[i] instanceof IlvRectangle) &&
            labelsPerObstacle[i] < maxLabelsPerObstacle) {
          assignmentTable.put(label, obstacles[i]);
          labelsPerObstacle[i]++;
          label = null;
        } 
      }
    }

    // if necessary, make the labels 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 {
      Object label;
      Object obstacle;
      int shape;
      for (int i = 0; i < labels.length; i++) {
        label = labels[i];
        obstacle = assignmentTable.get(label);
        if (obstacle instanceof IlvEllipse)
          shape = IlvAnnealingPointLabelDescriptor.ELLIPTIC;
        else 
          shape = IlvAnnealingPointLabelDescriptor.RECTANGULAR;
        layout.setLabelDescriptor(label,
          new IlvAnnealingPointLabelDescriptor(label, obstacle, null, shape,
              0f, 0f, prefDistFromOwnObstacle, prefDistFromOwnObstacle,
              preferredDirection));
      }
      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(float value)
  {
    if (value > 100.0f)
      value = 100.0f;
    if (value < 0.0f)
      value = 0.0f;

    // 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.2f * value);
  }

  /**
   * Set the preferred offset for all labels.
   */
  void setPreferredOffset(float value)
  {
    if (value > 30.0f)
      value = 30.0f;
    if (value < 0.0f)
      value = 0.0f;
        
    // write again to be sure the right value is shown
    prefOffset.setText("" + value);
    
    // set the new value of the parameter
    float oldval = prefDistFromOwnObstacle;
    prefDistFromOwnObstacle = 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);
      IlvRect or = labelingModel.boundingBox(obstacle);
      updateLabelAssignment(line, lr, or);
    }
  }

  /**
   * Update the auxiliary line showing label assignment for one label.
   */
  void updateLabelAssignment(
    IlvLine line, IlvRect labelRect, IlvRect obstacleRect)
  {
    float ox = obstacleRect.x + 0.5f * obstacleRect.width;
    float oy = obstacleRect.y + 0.5f * obstacleRect.height;
    float bestX, bestY;

    if (noLabelBackground) {
      // for transparent labels, show the aux line from label center
      bestX = labelRect.x + 0.5f * labelRect.width;
      bestY = labelRect.y + 0.5f * labelRect.height;
    }
    else {
      // for nonstransparent labels, show the aux line from the closest
      // corner of the label to (ox, oy)

      double bestDistSquare;
      double distSquare;
      float x, y;
    
      bestX = labelRect.x;
      bestY = labelRect.y;
      bestDistSquare = (bestX - ox) * (bestX - ox) + (bestY - oy) * (bestY - oy);   

      x = labelRect.x + labelRect.width;
      y = labelRect.y;
      distSquare = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSquare < bestDistSquare) {
        bestX = x;
        bestY = y;
        bestDistSquare = distSquare;
      }

      x = labelRect.x + labelRect.width;
      y = labelRect.y + labelRect.height;
      distSquare = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSquare < bestDistSquare) {
        bestX = x;
        bestY = y;
        bestDistSquare = distSquare;
      }

      x = labelRect.x;
      y = labelRect.y + labelRect.height;
      distSquare = (x - ox) * (x - ox) + (y - oy) * (y - oy);
      if (distSquare < bestDistSquare) {
        bestX = x;
        bestY = y;
        bestDistSquare = distSquare;
      }
    }

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

  /**
   * Show or hide the background of all labels.
   */
  void setNoLabelBackground(boolean flag)
  {
    IlvZoomableLabel label;
    IlvApplyObject apply;
    if (flag)
      apply = new IlvApplyObject() {
                    public void apply(IlvGraphic obj, Object arg) {
                      IlvZoomableLabel l = (IlvZoomableLabel)obj;
                      l.setBorderOn(false);
                      l.setBackgroundOn(false);
                    }
                 };
    else
      apply = new IlvApplyObject() {
                    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;
    float 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.0f);
        maxOverlap += 0.5 * (double)r1.width * (double)r1.height;
        maxOverlap += 0.5 * (double)r2.width * (double)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.0f);
            maxOverlap += 0.5 * (double)r1.width * (double)r1.height;
            maxOverlap += 0.5 * (double)r2.width * (double)r2.height;
          }
          else {
            r2 = labelingModel.boundingBox(obstacles[j]); 
            overlap += labelingModel.getObstacleOverlap(
                               labels[i], r1, obstacles[j], r2, 0.0f);
            maxOverlap += 0.5 * (double)r1.width * (double)r1.height;
            maxOverlap += 0.5 * (double)r2.width * (double)r2.height;
          }
        }
      }
    }

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

    // calculate the absolute overlap, with 2 digits after dot.
    overlap = (double)((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 = (float)((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();
    float minX = java.lang.Float.MAX_VALUE;
    float maxX = - java.lang.Float.MAX_VALUE;
    float minY = java.lang.Float.MAX_VALUE;
    float maxY = - java.lang.Float.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.
   */
  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.
   */
  public void selectionChanged(ManagerSelectionChangedEvent event)
  {
    if (labelingModel != null && layout.isAutoUpdate()) {
      // to make sure a deselected object is always moveable
      if (event.getGraphic() != null)
        manager.setMovable(event.getGraphic(), true);

      // we need to make sure that, if a label and its related obstacle are
      // both selected, that the label is registered nonmovable. Otherwise
      // we get weird effects because the label tries to follow the obstacle
      // by itself.

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

  /**
   * Listen to changes of the transformer.
   */
  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 PointLabelingListener 
  implements LabelLayoutEventListener 
{
  private float oldPercentage;
  private PointLabelLayoutApplet applet;
  
  PointLabelingListener(PointLabelLayoutApplet applet) 
  {
    this.applet = applet;
  }

  /** 
   * This method is automatically called by the layout algorithm.
   */
  public void layoutStepPerformed(LabelLayoutEvent event) 
  { 
    float percentage = event.getLayoutReport().getPercentageComplete();
    if (percentage != oldPercentage) {
      applet.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 PointLabelingManagerView
  extends IlvManagerView
{
  boolean insideVerifyTransformer = false;

  public PointLabelingManagerView(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.
   */
  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;
    }
  }
}