/*
 * 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.IlvAccelerator;
import ilog.views.IlvManager;
import ilog.views.IlvDefaultManagerFrame;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvManagerView;
import ilog.views.IlvLinkImage;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.interactor.IlvSelectInteractor;
import ilog.views.accelerator.IlvCycleSelectAccelerator;
import ilog.views.graphic.IlvZoomableLabel;
import ilog.views.swing.IlvJScrollManagerView;
//import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.swing.IlvSwingUtil;
import ilog.views.util.swing.IlvJComboBox;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.util.*;

/**
 * This is a very simple applet/application that shows how to implement
 * a selection mechanism that uses the keyboard.
 */
public class KeyboardSelApplet extends JApplet
{
  static {
    // This applet is designed to run only with default resource bundle
    // and various selected other resource bundles.
    // Setting the available resource suffixes avoids the applet
    // trying to load resource bundles for other locales over the network,
    // 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 
    // a Rogue Wave JViews Diagrammer Deployment license.
//    IlvProductUtil.DeploymentLicenseRequired(
//        IlvProductUtil.JViews_Diagrammer_Deployment);
  }

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

  IlvGrapher grapher;
  IlvManagerView mgrview;
  IlvZoomableLabel node;
  boolean animate = true;
  IlvCycleSelectAccelerator[] accelerators;

  // comparator for the traversal order: x has higher priority
  Comparator<IlvGraphic> xThenY = new Comparator<IlvGraphic>() {
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvManager ma = (IlvManager)a.getGraphicBag();
      IlvManager mb = (IlvManager)b.getGraphicBag();
      IlvTransformer ta = ma.getTopLevelTransformer();
      IlvTransformer tb = mb.getTopLevelTransformer();
      IlvRect ra = a.boundingBox(ta);
      IlvRect rb = b.boundingBox(tb);
      if (ra.x < rb.x) return -1;
      if (ra.x > rb.x) return 1;
      if (ra.width < rb.width) return -1;
      if (ra.width > rb.width) return 1;
      if (ra.y < rb.y) return -1;
      if (ra.y > rb.y) return 1;
      if (ra.height < rb.height) return -1;
      if (ra.height > rb.height) return 1;
      return 0;
    }
  };

  // comparator for the traversal order: y has higher priority
  Comparator<IlvGraphic> yThenX = new Comparator<IlvGraphic>() {
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvManager ma = (IlvManager)a.getGraphicBag();
      IlvManager mb = (IlvManager)b.getGraphicBag();
      IlvTransformer ta = ma.getTopLevelTransformer();
      IlvTransformer tb = mb.getTopLevelTransformer();
      IlvRect ra = a.boundingBox(ta);
      IlvRect rb = b.boundingBox(tb);
      if (ra.y < rb.y) return -1;
      if (ra.y > rb.y) return 1;
      if (ra.height < rb.height) return -1;
      if (ra.height > rb.height) return 1;
      if (ra.x < rb.x) return -1;
      if (ra.x > rb.x) return 1;
      if (ra.width < rb.width) return -1;
      if (ra.width > rb.width) return 1;
      return 0;
    }
  };

  // comparator for the traversal order: nesting has highest priority, then x
  Comparator<IlvGraphic> nestedThenXThenY = new Comparator<IlvGraphic>() {
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvGrapher commonBag = IlvGrapher.getLowestCommonGrapher(a, b);
      if (commonBag != null) {
        while (a.getGraphicBag() != commonBag)
          a = (IlvGraphic)a.getGraphicBag();
        while (b.getGraphicBag() != commonBag)
          b = (IlvGraphic)b.getGraphicBag();
      }
      IlvRect ra = a.boundingBox();
      IlvRect rb = b.boundingBox();
      if (ra.x < rb.x) return -1;
      if (ra.x > rb.x) return 1;
      if (ra.width > rb.width) return -1;
      if (ra.width < rb.width) return 1;
      if (ra.y < rb.y) return -1;
      if (ra.y > rb.y) return 1;
      if (ra.height > rb.height) return -1;
      if (ra.height < rb.height) return 1;
      return 0;
    }
  };

  // comparator for the traversal order: nesting has highest priority, then y
  Comparator<IlvGraphic> nestedThenYThenX = new Comparator<IlvGraphic>() {
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvGrapher commonBag = IlvGrapher.getLowestCommonGrapher(a, b);
      if (commonBag != null) {
        while (a.getGraphicBag() != commonBag)
          a = (IlvGraphic)a.getGraphicBag();
        while (b.getGraphicBag() != commonBag)
          b = (IlvGraphic)b.getGraphicBag();
      }
      IlvRect ra = a.boundingBox();
      IlvRect rb = b.boundingBox();
      if (ra.y < rb.y) return -1;
      if (ra.y > rb.y) return 1;
      if (ra.height > rb.height) return -1;
      if (ra.height < rb.height) return 1;
      if (ra.x < rb.x) return -1;
      if (ra.x > rb.x) return 1;
      if (ra.width > rb.width) return -1;
      if (ra.width < rb.width) return 1;
      return 0;
    }
  };


  /**
   * Initializes the applet/application.
   */
  public void init()
  {
    super.init();

    // create a grapher
    grapher = new IlvGrapher();

    // create a manager view
    mgrview = new IlvManagerView(grapher);

    // create the scroll manager view 
    IlvJScrollManagerView scrollManView = new IlvJScrollManagerView(mgrview);

    // Some settings on the manager view and on the scroll manager view
    mgrview.setAntialiasing(true);
    mgrview.setKeepingAspectRatio(true);
    mgrview.setBackground(Color.white);
    mgrview.setForeground(SystemColor.windowText);   
    Color xc = SystemColor.windowText;
    xc = new Color(255-xc.getRed(), 255-xc.getGreen(), 255-xc.getBlue());
    mgrview.setDefaultXORColor(xc);
    mgrview.setDefaultGhostColor(SystemColor.windowText);
    mgrview.setZoomFactorRange(0.02, 10.0);

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

    // Install the select interactor
    mgrview.setInteractor(new IlvSelectInteractor());

    // ensure that the manager view receives the TAB key events
    mgrview.setFocusable(true);
    mgrview.setFocusCycleRoot(true);
    mgrview.setFocusTraversalKeysEnabled(false);

    // add the accelerators
    accelerators = new IlvCycleSelectAccelerator[4];
    IlvCycleSelectAccelerator acc;

    acc = new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED,
                                        KeyEvent.VK_TAB, 
                                        0) {
            protected boolean endOfSequence(IlvGraphic g) {
              KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle();
              return true;
            }
          };
    acc.setForward(true);
    accelerators[0] = acc;
    acc = new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED,
                                        'F', 
                                        0);
    acc.setForward(true);
    accelerators[1] = acc;
    acc = new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED,
                                        KeyEvent.VK_TAB, 
                                        KeyEvent.SHIFT_MASK) {
            protected boolean endOfSequence(IlvGraphic g) {
              KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle();
              KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent();
              return true;
            }
          };
    acc.setForward(false);
    accelerators[2] = acc;
    acc = new IlvCycleSelectAccelerator(KeyEvent.KEY_PRESSED,
                                        'B', 
                                        0);
    acc.setForward(false);
    accelerators[3] = acc;

    for (int i = 0; i < accelerators.length; i++) 
      grapher.addAccelerator(accelerators[i]);

    IlvAccelerator acc2 = new IlvAccelerator(KeyEvent.KEY_PRESSED,
                                             KeyEvent.VK_TAB,
                                             KeyEvent.CTRL_MASK) {
      protected boolean handleEvent(IlvManagerView view) {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle();
        return true;
      }
    };
    grapher.addAccelerator(acc2);

    // Now, fill the grapher
    createGraph();

    // create a check box that allows the selection of expanded subgraphs
    JCheckBox expandedSubgraphsCheckBox =
      new JCheckBox("Subgraphs", true);
    expandedSubgraphsCheckBox.setToolTipText(
        "Allow the selection of expanded subgraphs");
    expandedSubgraphsCheckBox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i].setSelectExpandedSubgraphsAllowed(
            e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a check box that allows the selection of nodes
    JCheckBox nodesCheckBox =
      new JCheckBox("Nodes", true);
    nodesCheckBox.setToolTipText(
        "Allow the selection of nodes");
    nodesCheckBox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i].setSelectNodesAllowed(
            e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a check box that allows the selection of links
    JCheckBox linksCheckBox =
      new JCheckBox("Links", false);
    linksCheckBox.setToolTipText(
        "Allow the selection of links");
    linksCheckBox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i].setSelectLinksAllowed(
            e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a check box that allows you to descend into subgraphs 
    JCheckBox descendCheckBox =
      new JCheckBox("Descend into subgraphs", true);
    descendCheckBox.setToolTipText(
        "Allow the selection cycle to descend into subgraphs");
    descendCheckBox.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        for (int i = 0; i < accelerators.length; i++)
          accelerators[i].setDescendIntoExpandedSubgraphsAllowed(
            e.getStateChange() == ItemEvent.SELECTED);
      }
    });

    // create a combination box for the order mode
    JComboBox orderingBox = new IlvJComboBox();
    orderingBox.addItem("unordered");
    orderingBox.addItem("x then y");
    orderingBox.addItem("y then x");
    orderingBox.addItem("nested then x");
    orderingBox.addItem("nested then y");
    orderingBox.setToolTipText("Select the selection traversal order");
    orderingBox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent event) {
          JComboBox comboBox = (JComboBox)event.getSource();
          String mode = (String)comboBox.getSelectedItem();
          Comparator<IlvGraphic> c = null;
          if ("unordered".equals(mode)) {
            c = null;
          } else if ("x then y".equals(mode)) {
            c = xThenY;
          } else if ("y then x".equals(mode)) {
            c = yThenX;
          } else if ("nested then x".equals(mode)) {
            c = nestedThenXThenY;
          } else if ("nested then y".equals(mode)) {
            c = nestedThenYThenX;
          }
          for (int i = 0; i < accelerators.length; i++)
            accelerators[i].setComparator(c);
        }
      });

    JPanel aux = new JPanel();
    aux.setLayout(new FlowLayout(FlowLayout.LEADING));
    aux.add(new JLabel("Ordering: "));
    aux.add(orderingBox);

    // create the top panel with the toolbar and the pop-up checkbox
    JPanel topPanel = new JPanel();
    topPanel.setLayout(new FlowLayout(FlowLayout.LEADING));

    final Dimension d1 = (new JLabel("Allow Selection of")).getPreferredSize();

    JPanel leadingPanel = new JPanel() {
      public Dimension getMinimumSize() {
        return getPreferredSize();
      }
      public Dimension getPreferredSize() {
        Dimension d0 = super.getPreferredSize();
        Dimension dd = new Dimension(d1);
        dd.width += 40;
        if (d0.width > dd.width) dd.width = d0.width;
        if (d0.height > dd.height) dd.height = d0.height;
        return dd;
      }
    };

    final Dimension d2 = (new JLabel("Further Options")).getPreferredSize();

    JPanel trailingPanel = new JPanel() {
      public Dimension getMinimumSize() {
        return getPreferredSize();
      }
      public Dimension getPreferredSize() {
        Dimension d0 = super.getPreferredSize();
        Dimension dd = new Dimension(d2);
        dd.width += 40;
        if (d0.width > dd.width) dd.width = d0.width;
        if (d0.height > dd.height) dd.height = d0.height;
        return dd;
      }
    };

    leadingPanel.setBorder(new TitledBorder("Allow Selection of"));
    leadingPanel.setLayout(new GridLayout(3,1));
    leadingPanel.add(expandedSubgraphsCheckBox);
    leadingPanel.add(nodesCheckBox);
    leadingPanel.add(linksCheckBox);

    trailingPanel.setBorder(new TitledBorder("Further Options"));
    trailingPanel.setLayout(new GridLayout(2,1));
    trailingPanel.add(descendCheckBox);
    trailingPanel.add(aux);

    topPanel.add(leadingPanel);
    topPanel.add(trailingPanel);

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

    mgrview.setDoubleBuffering(true);
    mgrview.setTransformer(new IlvTransformer(1, 0, 0, 1, 0, 0));
  }

  /**
   * Called when this applet is being reclaimed in order to destroy
   * any resources that it has allocated.
   */
  public void destroy()
  {
    super.destroy();

    // This method is intended to workaround memory management issues
    // in the Sun JRE. See the method documentation for more
    // details and a description of the known issues.
    IlvSwingUtil.cleanupApplet();
  }

  /**
   * Creates a sample graph.
   */
  private void createGraph()
  {
    IlvGraphic[] inner1 = createSubGraph(grapher);
    IlvGraphic sg1 = (IlvGraphic)inner1[0].getGraphicBag();

    IlvGraphic[] inner2 = createSubGraph(grapher);
    IlvGraphic sg2 = (IlvGraphic)inner2[0].getGraphicBag();

    grapher.moveObject(sg1, 220, 20, true);
    grapher.moveObject(sg2, 221, 140, true);

    IlvGraphic[] nodes = new IlvGraphic[7];
    IlvPoint[] positions = { new IlvPoint(20,0),
                             new IlvPoint(90, 0),
                             new IlvPoint(90, 60),
                             new IlvPoint(160, 0),
                             new IlvPoint(160, 60),
                             new IlvPoint(160, 120),
                             new IlvPoint(160, 180) };

    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = new IlvZoomableLabel(positions[i], "N", false);
      setNodeAttributes(nodes[i]);
      grapher.addNode(nodes[i], 0, true);
    }

    alignVertical(nodes[3], inner1[0]);
    alignVertical(nodes[4], inner1[2]);
    alignVertical(nodes[5], inner2[0]);
    alignVertical(nodes[6], inner2[2]);

    centerVertical(nodes[1], nodes[3], nodes[4]);
    centerVertical(nodes[2], nodes[5], nodes[6]);
    centerVertical(nodes[0], nodes[1], nodes[2]);

    int[] linksFrom = { 0, 0, 1, 1, 2, 2, 3, 4, 5, 6 };
    int[] linksTo   = { 1, 2, 3, 4, 5, 6, 7, 9, 11, 13 };

    for (int i = 0; i < linksFrom.length; i++) {
      int fi = linksFrom[i];
      IlvGraphic from = nodes[fi];
      int ti = linksTo[i];
      IlvGraphic to;
      if (ti < nodes.length)
        to = nodes[ti];
      else if (ti < nodes.length + inner1.length)
        to = inner1[ti - nodes.length];
      else 
        to = inner2[ti - nodes.length - inner1.length];
      IlvLinkImage link = new IlvLinkImage(from, to, true);
      grapher.addLink(link, 1, true);
    }
  }

  /**
   * Centers a node between two nodes in y direction.
   */
  private void centerVertical(IlvGraphic node, IlvGraphic a, IlvGraphic b)
  {
    IlvRect bbox = a.boundingBox();
    float min = bbox.y;
    bbox = b.boundingBox();
    float max = bbox.y + bbox.height;
    bbox = node.boundingBox();
    grapher.moveObject(node, bbox.x, (max + min - bbox.height) / 2, true);
  }

  /**
   * Aligns two nodes in y direction.
   */
  private void alignVertical(IlvGraphic node, IlvGraphic ref)
  {
    IlvManager m = (IlvManager)ref.getGraphicBag();
    IlvTransformer t = m.getTopLevelTransformer();
    IlvRect bbox = ref.boundingBox(t);
    float y = bbox.y;
    bbox = node.boundingBox();
    grapher.moveObject(node, bbox.x, y, true);
  }

  /**
   * Creates a double nested subgraph.
   */
  private IlvGraphic[] createSubGraph(IlvGrapher parent)
  {
    IlvGrapher grapher = new IlvGrapher();
    grapher.setSelectionInvariantSubManagerBounds(true);
    IlvGraphic[] nodes = new IlvGraphic[4];
    IlvPoint[] positions = { new IlvPoint(20,0),
                             new IlvPoint(230, 60),
                             new IlvPoint(20, 60),
                             new IlvPoint(230, 0) };
    int[] linksFrom = { 0, 2, 5, 7, 0, 3 };
    int[] linksTo   = { 4, 6, 1, 3, 2, 1 };

    IlvGraphic[] inner = createSimpleSubGraph(grapher);

    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = new IlvZoomableLabel(positions[i], "N", false);
      setNodeAttributes(nodes[i]);
      grapher.addNode(nodes[i], 0, true);
    }

    for (int i = 0; i < linksFrom.length; i++) {
      int fi = linksFrom[i];
      IlvGraphic from = (fi < nodes.length ? nodes[fi] 
                                           : inner[fi - nodes.length]); 
      int ti = linksTo[i];
      IlvGraphic to = (ti < nodes.length ? nodes[ti] 
                                         : inner[ti - nodes.length]); 
      IlvLinkImage link = new IlvLinkImage(from, to, true);
      grapher.addLink(link, 1, true);
    }

    IlvDefaultManagerFrame frame = (IlvDefaultManagerFrame)grapher.getFrame();
    frame.setOpaque(true);
    frame.setBackground(new Color(100, 255, 100));

    parent.addNode(grapher, 0, true);
    return nodes;
  }

  /**
   * Creates a subgraph.
   */
  private IlvGraphic[] createSimpleSubGraph(IlvGrapher parent)
  {
    IlvGrapher grapher = new IlvGrapher();
    grapher.setSelectionInvariantSubManagerBounds(true);
    IlvGraphic[] nodes = new IlvGraphic[4];
    IlvPoint[] positions = { new IlvPoint(90,0),
                             new IlvPoint(160, 60),
                             new IlvPoint(90, 60),
                             new IlvPoint(160, 0) };
    int[] linksFrom = { 0, 2, 0, 3 };
    int[] linksTo   = { 3, 1, 2, 1 };

    for (int i = 0; i < nodes.length; i++) {
      nodes[i] = new IlvZoomableLabel(positions[i], "N", false);
      setNodeAttributes(nodes[i]);
      grapher.addNode(nodes[i], 0, true);
    }

    for (int i = 0; i < linksFrom.length; i++) {
      IlvGraphic from = nodes[linksFrom[i]];
      IlvGraphic to = nodes[linksTo[i]];
      IlvLinkImage link = new IlvLinkImage(from, to, true);
      grapher.addLink(link, 1, true);
    }

    IlvDefaultManagerFrame frame = (IlvDefaultManagerFrame)grapher.getFrame();
    frame.setOpaque(true);
    frame.setBackground(new Color(255, 200, 200));

    parent.addNode(grapher, 0, true);
    return nodes;
  }

  /**
   * Sets the attributes of a node.
   */
  private void setNodeAttributes(IlvGraphic node)
  {
    IlvZoomableLabel zl = (IlvZoomableLabel)node;
    zl.setAntialiasing(true);
    zl.setLeftMargin(6);
    zl.setRightMargin(6);
    zl.setTopMargin(4);
    zl.setBottomMargin(4);
    zl.setBorderOn(true);
    zl.setBackgroundOn(true);
    zl.setBackgroundPaint(Color.yellow);
  }

  /**
   * Allows you to run the demo as a standalone application.
   */
  public static void main(String[] arg)
  {
    // Sun recommends putting the entire GUI initialization into the
    // AWT thread
    SwingUtilities.invokeLater(
      new Runnable() {
        public void run() {
          KeyboardSelApplet applet = new KeyboardSelApplet();
          applet.init();

          JFrame frame = new JFrame("Keyboard Selection Example");
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.setSize(500, 500);
          frame.getContentPane().add(applet);
          frame.setVisible(true);
        }
      });
  }

}