/*
 * BILinkedViewPanel.java
 *
 * Copyright © 1994-2004, Oracle. All rights reserved. Oracle grants
 * you ("Licensee") a non-exclusive, royalty-free, worldwide license to use,
 * modify and redistribute this software in source and binary code form, provided
 * that (i) this copyright notice and license appear on all copies of the
 * software; (ii) Licensee does not utilize the software in a manner disparaging
 * to Oracle; and (iii) Licensee does not utilize the software in a manner that
 * violates the terms of any Licensee agreement with Oracle.
 */
package samples.javaclient;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Rectangle;

import java.lang.CloneNotSupportedException;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.border.BevelBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.SoftBevelBorder;

import oracle.bali.ewt.border.GrayPane;

import oracle.dss.crosstab.Crosstab;

import oracle.dss.dataSource.client.QueryClient;

import oracle.dss.dataSource.common.Query;
import oracle.dss.dataSource.common.QueryException;

import oracle.dss.datautil.QueryAccess;

import oracle.dss.dataView.ComponentHandle;
import oracle.dss.dataView.Dataview;
import oracle.dss.dataView.DrillEvent;
import oracle.dss.dataView.PageEvent;
import oracle.dss.dataView.PivotEvent;
import oracle.dss.dataView.PopupManager;

import oracle.dss.dataView.managers.ViewFormat;
import oracle.dss.dataView.managers.ViewStyle;

import oracle.dss.graph.Graph;

import oracle.dss.metadataManager.common.MetadataManagerException;

import oracle.dss.rules.DiscriminatorRule;
import oracle.dss.rules.RuleBundle;

import oracle.dss.rules.discriminator.QDRDiscriminator;

import oracle.dss.selection.Selection;
import oracle.dss.selection.SelectionException;
import oracle.dss.selection.step.AllStep;
import oracle.dss.selection.step.FirstLastStep;
import oracle.dss.selection.step.InvalidStepArgException;
import oracle.dss.selection.step.Step;

import oracle.dss.table.Table;

import oracle.dss.util.ColumnComponentInfo;
import oracle.dss.util.ComponentInfo;
import oracle.dss.util.DataAccess;
import oracle.dss.util.DataChangedEvent;
import oracle.dss.util.DataDirector;
import oracle.dss.util.DataDirectorException;
import oracle.dss.util.EdgeComponentInfo;
import oracle.dss.util.EdgeOutOfRangeException;
import oracle.dss.util.GroupComponentInfo;
import oracle.dss.util.LayerMetadataMap;
import oracle.dss.util.LayerOutOfRangeException;
import oracle.dss.util.PageItemComponentInfo;
import oracle.dss.util.QDR;
import oracle.dss.util.QDRMember;
import oracle.dss.util.SeriesComponentInfo;
import oracle.dss.util.SliceOutOfRangeException;

/**
 * This class is designed to allow multiple views such as crosstabs, tables and
 * graphs to be linked.
 * <P>
 * This includes the ability to :
 * <ol>
 * <li> specify various combinations of crosstab, table and graph </li>
 * <li> specify a Bi-Directional or Master/Detail relationship between
 *      views</li>
 * <li> specify whether all dimensions (query) or specified dimensions
 *      (selections) are linked </li>
 * </ol>
 *
 * @see javax.swing.JPanel
 * @see oracle.dss.dataView.PopupManager
 */

public class BILinkedViewPanel extends JPanel implements PopupManager {

  /////////////////////
  //
  // Constants
  //
  /////////////////////

  // Linked views
  public static final int CROSSTAB_TABLE        = 0;
  public static final int CROSSTAB_GRAPH        = 1;
  public static final int GRAPH_TABLE           = 2;
  public static final int CROSSTAB_TABLE_GRAPH  = 3;

  // Relationship between linked views
  public static final int RELATIONSHIP_BIDIRECTIONAL = 0;
  public static final int RELATIONSHIP_MASTER_DETAIL = 1;
  public static final int RELATIONSHIP_NONE          = 2;

  /////////////////////
  //
  // Member Variables
  //
  /////////////////////

  // Supported view objects
  private Crosstab m_crosstab = null;
  private Graph m_graph = null;
  private Table m_table = null;

  // Panels for each supported view
  private JPanel m_crosstabPanel;
  private JPanel m_tablePanel;
  private JPanel m_graphPanel;

  // Parent frame for this panel
  private Frame m_frameParent;

  // Horizontal and vertical split panes
  private JSplitPane m_splitPaneV;
  private JSplitPane m_splitPaneH;

  // Component handle used to retrieve position selected in a view
  private ComponentHandle m_componentHandle;

  // Popup menu used to select link options based on view selection
  private JPopupMenu m_jPopupMenu;

  // Current selected dataview
  private Dataview m_dataview;

  /**
   * Current linked views which consist of various combinations of crosstab,
   * graph and table.
   *
   * @see #CROSSTAB_TABLE
   * @see #CROSSTAB_GRAPH
   * @see #GRAPH_TABLE
   * @see #CROSSTAB_TABLE_GRAPH
   */
  private int m_nLinkedViews;

  /**
   * The master dataview which 'drives' any associated detail views.
   */
  private Dataview m_dataviewMaster;

  /**
   * The relationship between views.  These include bi-directional,
   * master/detail and removal of any existing relationships.
   *
   * @see #RELATIONSHIP_BIDIRECTIONAL
   * @see #RELATIONSHIP_MASTER_DETAIL
   * @see #RELATIONSHIP_NONE
   */
  private int m_nRelationship;

  /**
   * The list of dimensions to link.  If this value is null, it is assumed that
   * all dimensions should be linked (that is the entire query).
   */
  private Vector m_vstrDimensions = null;

  // List of current dataviews
  private Vector m_vDataviews;

  // Hashtable containing a list of each query and its associated query listener
  private Hashtable m_hashTableQueryListeners = new Hashtable();

  /////////////////////
  //
  // Constructors
  //
  /////////////////////

  /**
   * Constructor for <code>BILinkedViewPanel</code>.
   *
   */
  public BILinkedViewPanel() {
    super();
  }

  /**
   * Creates linked views based on the <code>Query</code> and linked view type.
   *
   * @param frameParent   a <code>Frame</code> that the panel is inserted into.
   * @param query         a <code>Query</code> value associated with the linked
   *                      view.
   * @param nLinkedViews  a <code>int</code> value which represents the views to
   *                      display and link.  Use the constants
   *                      <code>CROSSTAB_TABLE</code>,
   *                      <code>CROSSTAB_GRAPH </code>,
   *                      <code>GRAPH_TABLE</code>,
   *                      <code>CROSSTAB_TABLE_GRAPH</code>.
   *
   *
   * @throws Exception
   *
   * @see #CROSSTAB_TABLE
   * @see #CROSSTAB_GRAPH
   * @see #GRAPH_TABLE
   * @see #COLUMN_EDGE
   * @see #CROSSTAB_TABLE_GRAPH
   */
  public BILinkedViewPanel(Frame frameParent, Query query, int nLinkedViews)
    throws Exception {
    super();

    // Remember our initial frame and linked views
    setFrameParent(frameParent);
    setLinkedViews(nLinkedViews);

    // Initialize our panel based on the specified query
    try {
      // Use the wait cursor until we are done initializing
      getFrameParent().setCursor(new Cursor(Cursor.WAIT_CURSOR));
      init(query);

      updateLinks(m_dataview, RELATIONSHIP_BIDIRECTIONAL, null);
    }

    catch (Exception exception) {
      // Restore the default cursor
      getFrameParent().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));

      // Rethrow exception
      throw exception;
    }

      // Restore the default cursor
    getFrameParent().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
  }

  /////////////////////
  //
  // Public Methods
  //
  /////////////////////

  /**
   * Frees up resources used by this <code>BILinkedViewPanel</code>.
   */
  public void free() {
    // Clean up the listener list
    if (m_hashTableQueryListeners != null) {
      Enumeration enumeration = m_hashTableQueryListeners.elements();
      while (enumeration.hasMoreElements()) {
        BILinkedQueryListener queryListener = (BILinkedQueryListener) enumeration.nextElement();
        queryListener.removeDataDirectorListener();
      }

      m_hashTableQueryListeners = null;
    }

    // Clean up the dimensionsion list
    if (getDimensions() != null) {
      getDimensions().removeAllElements();
      setDimensions(null);
    }

    // Panel clean up
    if (m_crosstabPanel != null) {
      m_crosstabPanel.removeAll();
      m_crosstabPanel = null;
    }

    if (m_tablePanel != null) {
      m_tablePanel.removeAll();
      m_tablePanel = null;
    }

    if (m_graphPanel != null) {
      m_graphPanel.removeAll();
      m_graphPanel = null;
    }

    // General variable cleanup
    m_crosstab = null;
    m_table = null;
    m_graph = null;

    m_splitPaneV = null;
    m_splitPaneH = null;

    setFrameParent(null);

    m_componentHandle = null;
    m_dataview = null;
    m_jPopupMenu = null;

    m_dataviewMaster = null;
  }

  // Start implementation of PopupManager

  /**
   * Return a complete <code>JPopupMenu</code>.  The implementor
   * of this method is responsible for processing its own calls
   * from this popup menu.
   *
   * @param  dataview a <code>Dataview</code> value that represents
   *         the Dataview associated with this popup.
   * @param  componentHandle a <code>ComponentHandle</code> value that
   *         represents the component handle associated with this
   *         popup.
   *
   * @return an entire <code>JPopupMenu</code> structure, or
   * <code>null</code>, in which case <code>getPopupStrings</code>
   * will be called.
   *
   */
  public JPopupMenu getPopupMenu(Dataview dataview,
                                 ComponentHandle componentHandle) {
    // Save the current menu information selected
    m_componentHandle = componentHandle;
    m_dataview = dataview;

    // Create popup if not already around
    if (m_jPopupMenu == null) {
      m_jPopupMenu = new JPopupMenu();
    }

    // Default the current view to the master
    setDataviewMaster(dataview);

    // Remove any existing mouse entries
    m_jPopupMenu.removeAll();

    // Update the menu based on our current dataview position
    updateMenu(dataview, m_jPopupMenu, componentHandle);
    return m_jPopupMenu;
  }

  /**
   * Called on a manager to inform it the data source
   * has changed.
   */
  public void dataSourceChanged() {
  }

  // End implementation of PopupManager


  /**
   * Updates the query associated with each detail dataview based on the
   * specified master dataview.
   *
   * @param  dataviewMaster a <code>Dataview</code> value that represents
   *         the master Dataview associated with this <code>BILinkedViewPanel
   *         </code>.
   * @param  dataChangedEvent a <code>DataChangedEvent</code> that contains
   *         information about the change in the data.
   * @param  bClone a <code>boolean</code> value that is <code>true</code> when
   *         the master dataview should be cloned (not linked) and <code>false
   *         </code> it should not (linked).
   *
   * @throws CloneNotSupportedException
   * @throws DataDirectorException
   * @throws EdgeOutOfRangeException
   * @throws SliceOutOfRangeException
   */
  public void updateDetailDataSources(Dataview dataviewMaster,
    DataChangedEvent dataChangedEvent, boolean bClone)
    throws CloneNotSupportedException, DataDirectorException,
           EdgeOutOfRangeException, SliceOutOfRangeException {

    // Retrieve the master dataview
    if (dataviewMaster == null) {
      dataviewMaster = getDataviewMaster();
    }

    if (dataviewMaster != null) {
      // Retrieve the query associated with the master dataview
      Query queryMaster = (QueryClient)dataviewMaster.getDataSource();

      // Retrieve a list of detail dataviews
      Vector vDataviewsDetail = getDataviewsDetail();

      // Copy dataview master datasource to each detail dataview datasource
      Enumeration enumeration = vDataviewsDetail.elements();
      while (enumeration.hasMoreElements()) {
        // Retrieve the current detail dataview
        Dataview dataviewCurrent = (Dataview)enumeration.nextElement();

        // Determine whether detailed dataview should get a 'linked' or cloned
        // copy of the master dataview's datasource
        if (bClone) {
          Query queryClone = (Query)queryMaster.clone();
          setDataSource(dataviewCurrent, queryClone);

          // Disable Master/Detail based detailed views
          if (getRelationship() == RELATIONSHIP_MASTER_DETAIL) {
            dataviewCurrent.setEnabled(false);
          }
        }
        else {
          setDataSource(dataviewCurrent, queryMaster);
        }
      }
    }
  }

  /**
   * Retrieves the <code>BILinkedQueryListener</code> associated with the currently
   * selected <code>Dataview</code>.
   *
   * @return <code>BILinkedQueryListener</code> associated with the currently selected
   *         <code>Dataview</code>.
   */
  public BILinkedQueryListener getQueryListener() {
    return (getQueryListener(m_dataview));
  }

  /**
   * Retrieves the <code>BILinkedQueryListener</code> associated with the specified
   * <code>Dataview</code>.
   *
   * @param  dataview a <code>Dataview</code> value that represents
   *         the Dataview to retrieve <code>BILinkedQueryListener</code> from.
   *
   * @return <code>BILinkedQueryListener</code> associated with the specified
   *         <code>Dataview</code>.
   */
  public BILinkedQueryListener getQueryListener(Dataview dataview) {
    BILinkedQueryListener queryListener = null;

    if (dataview != null) {
      // Retrieve the query associated with the dataview
      Query query = (Query) dataview.getDataSource();

      // Look up the BILinkedQueryListener based on the Query
      if (query != null) {
        queryListener = (BILinkedQueryListener) m_hashTableQueryListeners.get(query);
      }
    }

    return queryListener;
  }

  /**
   * Retrieves the <code>Crosstab</code> associated with the
   * <code>BILinkedViewPanel</code>.
   *
   * @return <code>Crosstab</code> associated with the <code>BILinkedViewPanel
   * </code>.
   */
  public Crosstab getCrosstab() {
      return m_crosstab;
  }

  /**
   * Retrieves the <code>Table</code> associated with the
   * <code>BILinkedViewPanel</code>.
   *
   * @return <code>Table</code> associated with the <code>BILinkedViewPanel
   * </code>.
   */
  public Table getTable() {
    return m_table;
  }

  /**
   * Retrieves the <code>Graph</code> associated with the
   * <code>BILinkedViewPanel</code>.
   *
   * @return <code>Graph</code> associated with the <code>BILinkedViewPanel
   * </code>.
   */
  public Graph getGraph() {
    return m_graph;
  }

  /**
   * Updates the <code>Dataview</code> with the specified <code>Query</code>,
   * creating BILinkedQueryListeners as necessary.
   *
   * @param dataview a <code>Dataview</code> value that represents
   *        the Dataview to update.
   * @param query a <code>Query</code> to associate with the dataview.
   */
  public void setDataSource(Dataview dataview, Query query) {
    // Check to make sure that we have a non-null query
    if (query != null) {
      // Determine if a query listener exists for this query
      if (!m_hashTableQueryListeners.containsKey(query)) {
        // Create a query listener and associate it with this query
        BILinkedQueryListener queryListener = new BILinkedQueryListener(query, this);
        m_hashTableQueryListeners.put(query, queryListener);
        query.addQueryListener(queryListener);
      }
    }

    if (dataview != null) {
      // Update the dataview
      dataview.setDataSource(query);
    }
  }

  /**
   * Specifies the master <code>Dataview</code> associated with the
   * <code>BILinkedViewPanel</code>.
   *
   * @param  dataview a <code>Dataview</code> value that represents
   *         the master Dataview.
   *
   * @return <code>Dataview</code> representing the new master <code>Dataview
   * </code>.
   */
  public Dataview setDataviewMaster(Dataview dataview) {
    m_dataviewMaster = dataview;
    return dataview;
  }

  /**
   * Retrieves the master <code>Dataview</code> associated with the
   * <code>BILinkedViewPanel</code>.
   *
   * @return <code>Dataview</code> representing the master <code>Dataview
   * </code>.
   */
  public Dataview getDataviewMaster() {
    return m_dataviewMaster;
  }

  /**
   * Retrieves the current linked views which consist of various combinations of
   * crosstab, graph and table.
   *
   * These include bi-directional, master/detail and removal of any existing
   * relationships.
   *
   * @return <code>int</code> representing the linked <code>Dataview</code>
   * objects.
   *
   * @see #CROSSTAB_TABLE
   * @see #CROSSTAB_GRAPH
   * @see #GRAPH_TABLE
   * @see #CROSSTAB_TABLE_GRAPH
   */
  public int getLinkedViews() {
    return m_nLinkedViews;
  }

  /**
   * Specifies the current linked views which consist of various combinations of
   * crosstab, graph and table.
   *
   * These include bi-directional, master/detail and removal of any existing
   * relationships.
   *
   * @param nLinkedViews <code>int</code> specifying the linked dataviews.
   *
   * @see #CROSSTAB_TABLE
   * @see #CROSSTAB_GRAPH
   * @see #GRAPH_TABLE
   * @see #CROSSTAB_TABLE_GRAPH
   */
  public void setLinkedViews(int nLinkedViews) {
    m_nLinkedViews = nLinkedViews;
  }

  /**
   * Retrieves the relationship between the linked <code>Dataview</code>
   * objects. These include bi-directional, master/detail and removal of any
   * existing relationships.
   *
   * @return <code>int</code> representing the <code>Dataview</code>
   * relationships.
   *
   * @see #RELATIONSHIP_BIDIRECTIONAL
   * @see #RELATIONSHIP_MASTER_DETAIL
   * @see #RELATIONSHIP_NONE
   */
  public int getRelationship() {
    return m_nRelationship;
  }

  /**
   * Specifies the relationship between the linked <code>Dataview</code>
   * objects. These include bi-directional, master/detail and removal of any
   * existing relationships.
   *
   * @param nRelationship <code>int</code> specifying the linked relationship.
   *
   *
   * @return <code>int</code> representing the previous <code>Dataview</code>
   *         relationships.
   *
   * @see #RELATIONSHIP_BIDIRECTIONAL
   * @see #RELATIONSHIP_MASTER_DETAIL
   * @see #RELATIONSHIP_NONE
   */
  public int setRelationship(int nRelationship) {
    int nOldRelationship = getRelationship();
    m_nRelationship = nRelationship;
    return nOldRelationship;
  }

  /**
   * Retrieves a <code>Frame</code> value that represents the parent frame for
   * this panel.
   *
   * @return <code>Frame</code> representing the parent frame of this panel.
   */
  public Frame getFrameParent() {
    return m_frameParent;
  }

  /**
   * Specifies a <code>Frame</code> value that represents the parent frame for
   * this panel.
   *
   * @param frameParent <code>Frame</code> for this panel.
   */
  public void setFrameParent(Frame frameParent) {
    m_frameParent = frameParent;
  }

  /**
   * Retrieves a <code>Vector</code> value that represents the list of
   * dimensions to link.  If this value is null, it is assumed that all
   * dimensions should be linked (that is the entire query).
   *
   * @return <code>String</code><code>Vector</code> representing the list of
   *         dimensions to link.
   */
  public Vector getDimensions() {
    return m_vstrDimensions;
  }

  /**
   * Specifies a <code>Vector</code> value that represents the list of
   * dimensions to link.  If this value is null, it is assumed that all
   * dimensions should be linked (that is the entire query).
   *
   * @param vstrDimensions <code>Vector</code> of dimensions to link.
   */
  public void setDimensions(Vector vstrDimensions) {
    m_vstrDimensions = vstrDimensions;
  }

  /**
   * Retrieves a <code>Vector</code> value that represents the list of all
   * <code>Dataview</code> objects associated with the <code>BILinkedViewPanel
   * </code>.
   *
   * @return <code>Dataview</code><code>Vector</code> representing the list of
   *         dataviews associated with the <code>BILinkedViewPanel</code>.
   */
  public Vector getDataviews() {
    return m_vDataviews;
  }

  /**
   * Retrieves a <code>Vector</code> of detail <code>Dataview</code> objects
   * associated with the <code>BILinkedViewPanel</code>.
   *
   * @return <code>Vector</code> representing the list of detail
   *         <code>Dataview</code> objects.
   */
  public Vector getDataviewsDetail() {
    Vector vDataviewsDetail = null;

    // Retrieve the current master dataview
    Dataview dataviewMaster = getDataviewMaster();

    if (dataviewMaster != null) {
      Vector vDataviews = getDataviews();

      // Iterate over all dataviews, returning a list that contains all
      // dataviews but the master
      Enumeration enumeration = vDataviews.elements();
      while (enumeration.hasMoreElements()) {
        Dataview dataview = (Dataview)enumeration.nextElement();
        if ((dataviewMaster != dataview) && (dataview != null)) {
          if (vDataviewsDetail == null) {
            vDataviewsDetail = new Vector();
          }
          vDataviewsDetail.addElement(dataview);
        }
      }
    }

    return vDataviewsDetail;
  }

  /**
   * Retrieves a <code>Vector</code> of <code>Dataview</code> objects
   * associated with the <code>BILinkedViewPanel</code> filtered by the specified
   * <code>Dataview</code>.
   *
   * @param  dataViewFilter a <code>Dataview</code> value that is associated
   *         with the dataview to filter out.
   * @return <code>Vector</code> representing the list of filtered
   *         <code>Dataview</code> objects.
   */
  public Vector getDataviews(Dataview dataviewFilter) {
    Vector vDataviewsFiltered = null;

    // Retrieve the current list if dataviews
    Vector vDataviews = getDataviews();

    // Iterate over all dataviews
    if ((vDataviews != null) && (!vDataviews.isEmpty())) {
      // Iterate over all dataviews, returning a list that contains all
      // dataviews but the master
      Enumeration enumeration = vDataviews.elements();
      while (enumeration.hasMoreElements()) {
        Dataview dataview = (Dataview)enumeration.nextElement();

        if ((dataview != null) && (dataview != dataviewFilter)) {
          if (vDataviewsFiltered == null) {
            vDataviewsFiltered = new Vector();
          }
          vDataviewsFiltered.addElement(dataview);
        }
      }
    }

    return vDataviewsFiltered;
  }

  /**
   * Retrieves a <code>Dataview</code> associated with the specified
   * <code>Query</code>.
   *
   * @param  queryFilter a <code>Query</code> value that is associated with
   *         the dataview to filter out.
   * @return <code>Vector</code> representing the list of filtered
   *         <code>Dataview</code> objects.
   */
  public Dataview getDataview(Query queryFilter) {
    // Retrieve the current list if dataviews
    Vector vDataviews = getDataviews();

    // Iterate over all dataviews
    if ((vDataviews != null) && (!vDataviews.isEmpty())) {
      // Iterate over all dataviews, returning a list that contains all
      // dataviews but the master
      Enumeration enumeration = vDataviews.elements();
      while (enumeration.hasMoreElements()) {
        Dataview dataview = (Dataview)enumeration.nextElement();
        Query query = (Query) dataview.getDataSource();

        if ((query != null) && (query == queryFilter)) {
          return dataview;
        }
      }
    }

    return null;
  }

  /**
   * Retrieves a <code>Vector</code> of <code>Dataview</code> objects
   * associated with the <code>BILinkedViewPanel</code> filtered by the specified
   * <code>Query</code>.
   *
   * @param  queryFilter a <code>Query</code> value that is associated with
   *         the dataview to filter out.
   * @return <code>Vector</code> representing the list of filtered
   *         <code>Dataview</code> objects.
   */
  public Vector getDataviews (Query queryFilter) {
    Vector vDataviewsFiltered = null;

    // Retrieve the current list if dataviews
    Vector vDataviews = getDataviews();

    // Iterate over all dataviews
    if ((vDataviews != null) && (!vDataviews.isEmpty())) {
      // Iterate over all dataviews, returning a list that contains all
      // dataviews but the master
      Enumeration enumeration = vDataviews.elements();
      while (enumeration.hasMoreElements()) {
        Dataview dataview = (Dataview)enumeration.nextElement();
        Query query = (Query) dataview.getDataSource();

        if ((query != null) && (query != queryFilter)) {
          if (vDataviewsFiltered == null) {
            vDataviewsFiltered = new Vector();
          }
          vDataviewsFiltered.addElement(dataview);
        }
      }
    }

    return vDataviewsFiltered;
  }

  /**
   * Apply or remove formatting in the <code>QDR</code> specified layer
   * in the header.
   *
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format metadata for the specified <code>QDR</code>layer in the
   * header, and adds the Rule to the GridViewHeaderStyleManager.
   *
   * If <code>bApply</code> is <code>false</code>, this method removes from the
   * GridViewHeaderStyleManager the previously created Rule to format the
   * specified <code>QDR</code>.
   *
   * The Rule will format the metadata with an italic font if the dimension
   * is linked.  A bold font will also be added if the link is bi-directional or
   * the dimension is part of a master view.
   *
   * The GridViewHeaderStyleManager is used to specify style formatting
   * in the column, row, and page headers.
   *
   * @param vDataviews <code>Vector</code> of <code>Dataview</code> objects to
   *        apply header formatting to
   * @param vstrDimensions <code>Vector</code> of dimensions to apply header
   *        formatting to
   * @param nRelationship <code>int</code> representing dataview relationship,
   * @param bApply <code>true</code> to apply header formatting, <code>false
   *        </code> to remove it
   */
  protected void applyFormatting(Vector vDataviews, Vector vstrDimensions,
    int nRelationship, boolean bApply) {

    // If we don't have any dataviews specified, return
    if ((vDataviews == null) && vDataviews.isEmpty()) {
      return;
    }

    // If we are removing formatting or we have no dimensions, a relationship
    // and we are formatting retrieve dimensionality of entire query
    boolean bAllDimensions = false;
    if (!bApply || ((getDimensions() == null)
      && (nRelationship != RELATIONSHIP_NONE) && (bApply))) {
      bAllDimensions = true;
    }

    if (((vstrDimensions != null) && !vstrDimensions.isEmpty())
      || bAllDimensions) {
      // Apply formatting information to all linked views
      Enumeration enumDataview = vDataviews.elements();
      while (enumDataview.hasMoreElements()) {
        Dataview dataviewCurrent = (Dataview)enumDataview.nextElement();

        // Apply formatting information to all dimensions
        if (bApply) {
          Enumeration enumDimensions = null;
          if (bAllDimensions) {
            enumDimensions = getDimensions(dataviewCurrent).elements();
          }
          else {
            enumDimensions = vstrDimensions.elements();
          }

          while (enumDimensions.hasMoreElements()) {
            String strDimension = (String) enumDimensions.nextElement();
            QDR qdr = new QDR("MDM!MEASUREDIMENSION");
            qdr.addDimMemberPair(strDimension, new QDRMember(QDRMember.VARIES));

            // Apply formatting to the dataview and qdr specified
            applyFormatting(dataviewCurrent, nRelationship, bApply, qdr);
          }
        }
        else {
          // Remove formatting
          applyFormatting(dataviewCurrent, nRelationship, bApply, null);
        }
      }
    }
  }

  /**
   * Apply or remove formatting in the <code>QDR</code> specified layer
   * in the header.
   *
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format metadata for the specified <code>QDR</code>layer in the
   * header, and adds the Rule to the GridViewHeaderStyleManager.
   *
   * If <code>bApply</code> is <code>false</code>, this method removes from the
   * GridViewHeaderStyleManager the previously created Rule to format the
   * specified <code>QDR</code>.
   *
   * The Rule will format the metadata with an italic font if the dimension
   * is linked.  A bold font will also be added if the link is bi-directional or
   * the dimension is part of a master view.
   *
   * The GridViewHeaderStyleManager is used to specify style formatting
   * in the column, row, and page headers.
   *
   * @param dataview <code>Dataview</code> to apply header formatting to
   * @param nRelationship <code>int</code> representing dataview relationship,
   * @param bApply <code>true</code> to apply header formatting,
   *               <code>false</code> to remove it
   * @param qdr    <code>QDR</code> to apply header formatting to
   */
  protected void applyFormatting(Dataview dataview, int nRelationship,
    boolean bApply, QDR qdr) {
    Vector ruleBundles = null;

    // Get the Vector of RuleBundles from the GridViewHeaderStyleManager.
    //
    // Note: Rule bundles are currently not supported for Graph
    if (dataview instanceof Crosstab) {
      ruleBundles = ((Crosstab)dataview).getGridViewHeaderStyleManager()
        .getBundles();
    }
    else {
      if (dataview instanceof Table) {
        ruleBundles = ((Table)dataview).getGridViewHeaderStyleManager()
          .getBundles();
      }
    }

    // If applying formatting, create a Rule, add it to a RuleBundle, and
    // add the RuleBundle to the Vector.
    if (bApply) {
      // If the Vector is null, create a new one.
      if (ruleBundles == null) {
        ruleBundles = new Vector();
      }

      // Create a new RuleBundle which will be added to the Vector.
      // For the purposes of this sample application, there will be only one
      // Rule in one RuleBundle in the Vector on the GridViewHeaderStyleManager.
      RuleBundle bundle = new RuleBundle();

      // Key Concept
      // Create a QDRDiscriminator based on the specified QDR.  The operator for
      // the discriminator is set to EQUALS.  This discriminator will apply
      // to any cell associated which is a member of the specified dimension
      QDRDiscriminator qdrDisc1 =
        new QDRDiscriminator(qdr, QDRDiscriminator.EQUALS);

      // Key Concept
      // Create the ViewStyle that will affect the cells for which the
      // discriminator applies.  Only the properties that should take affect
      // in the Rule should be set.

      // This ViewStyle will format linked dimensions with a italic font.
      ViewStyle viewStyle = new ViewStyle();
      viewStyle.setFontItalic(true);

      // A linked dimension that is bi-directional or a master view will also
      // be formatted with a bold font.
      if ((nRelationship == RELATIONSHIP_BIDIRECTIONAL) ||
          (getDataviewMaster() == dataview)) {
        // Make the master relationship bold
        viewStyle.setFontBold(true);
      }

      // Key Concept
      // Create the DiscriminatorRule and set its discriminator and Mergeable.
      // The ViewStyle is a Mergeable, which means that it can be
      // merged with other ViewStyles and used in Rules.
      // Only properties that have been explicitly set on
      // the ViewStyle will be merged as part of the rule resolution process.
      // ViewStyle is the only type of Mergeable that may be used with Rules
      // in the GridViewHeaderStyleManager.
      DiscriminatorRule discRule = new DiscriminatorRule();
      discRule.setDiscriminator(qdrDisc1);
      discRule.setFixedMergeable(viewStyle);

      // Key Concept
      // Add the Rule to the RuleBundle.  Every Rule must belong to a
      // RuleBundle, and the RuleBundle added to the Vector on the manager,
      // in order for the Rule to run.
      bundle.addRule(discRule);

      // Key Concept
      // Add the RuleBundle to the Vector, which will be set on the manager.
      ruleBundles.addElement(bundle);
    }
    // If removing formatting, remove the whole RuleBundle containing the
    else {
      if (ruleBundles != null && !ruleBundles.isEmpty()) {
        Enumeration enumeration = ruleBundles.elements();
        while (enumeration.hasMoreElements()) {
          ruleBundles.removeElementAt(0);
        }
      }
    }

    // Key Concept
    // Set the updated Vector of RuleBundles on the GridViewHeaderStyleManager.
    // Since the Vector is passed by value, no modifications have been made
    // to the instance maintained by the manager so far.
    // It is necessary to call setBundles() to update the manager
    // with the new Vector.

    // Note: Rule bundles are currently not supported for Graph
    if (dataview instanceof Crosstab) {
      ((Crosstab)dataview).getGridViewHeaderStyleManager().setBundles(dataview,
        ruleBundles);
    }
    else {
      // Note: Measures associated with Tables are not formatted correctly
      if (dataview instanceof Table) {
        ((Table)dataview).getGridViewHeaderStyleManager().setBundles(dataview,
          ruleBundles);
      }
    }
  }

  /////////////////////
  //
  // Protected Methods
  //
  /////////////////////

  /////////////////////
  //
  // Private Methods
  //
  /////////////////////

  /**
   * Initialize the linked views based on the specified<code>Query</code>.
   *
   * @param query The Query associated with the linked view.
   * @throws Exception if linked views cannot be initialized
   */
  private void init(Query query) throws Exception {
    setLayout(new BorderLayout());

    switch(getLinkedViews()) {

      // Link Table and Crosstab together
      case CROSSTAB_TABLE:
        m_vDataviews = new Vector();
        m_vDataviews.addElement(initCrosstab(query));
        m_vDataviews.addElement(initTable(query));

        m_splitPaneV = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
          m_crosstabPanel, m_tablePanel);
        m_splitPaneV.setOneTouchExpandable(true);
        m_splitPaneV.setContinuousLayout(true);
        m_splitPaneV.setBorder(new EmptyBorder(6, 6, 6, 6));
        add(m_splitPaneV, BorderLayout.CENTER);
        break;

      // Link Table and Graph together
      case CROSSTAB_GRAPH:
        m_vDataviews = new Vector();
        m_vDataviews.addElement(initCrosstab(query));
        m_vDataviews.addElement(initGraph(query));

        m_splitPaneV = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
          m_crosstabPanel, m_graphPanel);
        m_splitPaneV.setOneTouchExpandable(true);
        m_splitPaneV.setContinuousLayout(true);
        m_splitPaneV.setBorder(new EmptyBorder(6, 6, 6, 6));
        add(m_splitPaneV, BorderLayout.CENTER);
        break;

      // Link Table and Graph together
      case GRAPH_TABLE:
        m_vDataviews = new Vector();
        m_vDataviews.addElement(initGraph(query));
        m_vDataviews.addElement(initTable(query));

        m_splitPaneV =
          new JSplitPane(JSplitPane.VERTICAL_SPLIT, m_tablePanel, m_graphPanel);
        m_splitPaneV.setOneTouchExpandable(true);
        m_splitPaneV.setContinuousLayout(true);
        m_splitPaneV.setBorder(new EmptyBorder(6, 6, 6, 6));
        add(m_splitPaneV, BorderLayout.CENTER);
        break;

      // Link Table and Crosstab and Graph together
      case CROSSTAB_TABLE_GRAPH:
        m_vDataviews = new Vector();
        m_vDataviews.addElement(initCrosstab(query));
        m_vDataviews.addElement(initTable(query));
        m_vDataviews.addElement(initGraph(query));

        m_splitPaneV = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
          m_crosstabPanel, m_tablePanel);
        m_splitPaneH = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, m_splitPaneV,
          m_graphPanel);
        m_splitPaneV.setOneTouchExpandable(true);
        m_splitPaneH.setOneTouchExpandable(true);
        m_splitPaneV.setContinuousLayout(true);
        m_splitPaneH.setContinuousLayout(true);
        m_splitPaneV.setBorder(new EmptyBorder(6, 6, 6, 6));
        m_splitPaneH.setBorder(new EmptyBorder(6, 6, 6, 6));
        add(m_splitPaneH, BorderLayout.CENTER);
        break;

      default:
        break;
      }
    }

  /**
   * Initialize the <code>Crosstab</code> based on the specified<code>Query
   * </code>.
   *
   * @param query The Query associated with the crosstab.
   * @return <code>Dataview</code> which represents the newly created crosstab.
   */
  private Dataview initCrosstab(Query query) {
    // Create a panel that contains a Crosstab
    m_crosstabPanel = new JPanel(new BorderLayout());

    if (m_crosstab == null) {
      m_crosstab = new Crosstab();
      m_crosstab.setCellEditingAllowed(true);
      m_crosstab.setPagingControlVisible(true);

      // Add right mouse menu support to allow link selection
      m_crosstab.setPopupManager(this);

      // Remember the query
      setDataSource(m_crosstab, query);
    }

    m_crosstabPanel.add(m_crosstab, BorderLayout.CENTER);

    return m_crosstab;
  }

  /**
   * Initialize the <code>Table</code> based on the specified<code>Query</code>.
   *
   * @param query The Query associated with the table.
   * @return <code>Dataview</code> which represents the newly created table.
   */
  private Dataview initTable(Query query) {
    // Create a panel that contains a Table
    m_tablePanel = new JPanel(new BorderLayout());
    if (m_table == null) {
      m_table = new Table();
      m_table.setPagingControlVisible(true);

      // Add right mouse menu support to allow link selection
      m_table.setPopupManager(this);

      // Remember the query
      setDataSource(m_table, query);
    }

    m_tablePanel.add(m_table, BorderLayout.CENTER);

    return m_table;
  }

  /**
   * Initialize the <code>Graph</code> based on the specified<code>Query</code>.
   *
   * @param query The Query associated with the graph.
   * @return <code>Dataview</code> which represents the newly created graph.
   */
  private Dataview initGraph(Query query) {
    // Create a panel that contains a Graph
    m_graphPanel = new JPanel(new BorderLayout());
    m_graphPanel.setBorder(new SoftBevelBorder(BevelBorder.LOWERED));

    if (m_graph == null) {
      m_graph = new Graph();

      // Enable drilling (defaults to false)
      m_graph.setDrillingEnabled(true);

      // Enable 'paging items' controls (defaults to false)
      m_graph.getPagingControl().setVisible(true);

      // Add right mouse menu support to allow link selection
      m_graph.setPopupManager(this);

      // Remember the query
      setDataSource(m_graph, query);
    }

    m_graphPanel.add(m_graph, BorderLayout.CENTER);

    return m_graph;
  }

  /**
   * Updates the selection associated with the specified <code>Dataview</code>.
   *
   * @param  dataviewMaster a <code>Dataview</code> value that represents
   *         the dataview to update.
   */
  public void updateSelection(Dataview dataview)
    throws InvalidStepArgException {
    // Verify that the dataview is not null
    if (dataview != null) {
      // Retrieve the query associated with the dataview
      Query query = (QueryClient)dataview.getDataSource();

      // Verify that we have a non-null query
      if (query != null) {
        // Retrieve a QueryAccess so that we can update query
        QueryAccess queryAccess = query.createQueryAccess();
        if (queryAccess == null) {
          return;
        }

        // Retrieve the list of dimensions associated with the query
        Vector vstrDimensions = getDimensions(dataview);

        // Verify that we can retrieve the dimensions associated with the
        // query
        if ((vstrDimensions != null) && (!vstrDimensions.isEmpty())) {

          // Retrieve the selection associated with the first dimension
          String strDimension = (String)vstrDimensions.elementAt(0);
          if (strDimension == null) {
            return;
          }

          // Retrieve the selection associated with the dimension
          Selection selection = queryAccess.getSelection(strDimension);
          if (selection == null) {
            return;
          }

          // Determine the number of steps associated with the selection
          int nCount = selection.getStepCount();

          // Verify that we have at least one step
          if (nCount != 0) {
            // Retrieve the first step
            Step step = selection.getStep(0);

            // Verify that the step is not null
            if (step != null) {
              // Check to see if this is an AllStep
              if (step instanceof AllStep) {
                // Remove existing step
                selection.removeStep(0);

                // Replace existing step with FirstLastStep
                FirstLastStep firstLastStep =
                  new FirstLastStep(selection.getDimension());
                firstLastStep.setHierarchy(selection.getHierarchy());

                // Select first two values
                firstLastStep.setNumValues(new Integer(2));
                firstLastStep.setFirstLastType(FirstLastStep.FIRST);
                selection.addStep(firstLastStep);
              }
              else {
                // Check to see if this is an FirstLastStep
                if (step instanceof FirstLastStep) {

                  // Remove existing step
                  selection.removeStep(0);

                  // Replace existing step with AllStep
                  AllStep allStep = new AllStep(selection.getDimension());
                  allStep.setHierarchy(selection.getHierarchy());
                  selection.addStep(allStep);
                }
              }
            }
          }

          // Update the query's selection
      	  queryAccess.setSelection(selection);

          // Update the datasource based on the new selection
          query.applyQueryAccess(queryAccess, true);

          // Free up the query access
          queryAccess.release();
        }
      }
    }
  }

  /**
   * Returns a <code>String</code> <code>Vector</code> of dimensions associated
   * with specified <code>Dataview</code>, including the measure dimension.
   *
   * @param  dataview a <code>Dataview</code> value that represents the
   *         dataview to retrieve dimensions for.
   * @return <code>Vector</code> which represents the dimensions associated with
   *          the dataview, including the measure dimension.
   */
  private Vector getDimensions(Dataview dataview) {
    Vector vstrDimensions = null;

    if (dataview != null) {
      // Retrieve the query associated with the dataview
      Query query = (QueryClient)dataview.getDataSource();

      if (query != null) {
        QueryAccess queryAccess = query.createQueryAccess();

        // Retrieve the dimensions
        vstrDimensions =
          queryAccess.getDimensions(LayerMetadataMap.LAYER_METADATA_NAME);

        // Retrieve the measure dimension
        String strMeasureDimension =
          queryAccess.getMeasureDimension(LayerMetadataMap.LAYER_METADATA_NAME);

        // Add measure dimension to list of dimensions
        if (strMeasureDimension != null) {
          vstrDimensions.addElement(strMeasureDimension);
        }

        // Free up the query access
        queryAccess.release();
      }
    }

    return vstrDimensions;
  }

  /**
   * Returns a <code>String</code> <code>Vector</code> of dimensions associated
   * with specfied <code>QDR</code>.
   *
   * @param qdr The <code>QDR</code> to process.
   * @return <code>Vector</code> which represents the newly created graph.
   */
  private Vector getDimensions(QDR qdr) {
    Vector vstrDimensions = null;

    // Iterate over the entire QDR to retrieve all associated dimensions
    if (qdr != null) {
      Enumeration enumeration = qdr.getDimensions();
      while (enumeration.hasMoreElements()) {
        String strDimension = (String)enumeration.nextElement();

        if (strDimension != null) {
          if (vstrDimensions == null) {
            vstrDimensions = new Vector();
          }

          vstrDimensions.addElement(strDimension);
        }
      }
    }

    return vstrDimensions;
  }

  /**
   * Retrieves the dimensions associated with the specified
   * component information.
   *
   * @param  componentInfo a <code>ComponentInfo</code> value that
   *         represents the component we wish to interrogate.
   * @return <code>Vector</code> representing the dimensions associated with
   *         the specified component information or null.
   *
   * @throws EdgeOutOfRangeException
   * @throws LayerOutOfRangeException
   */
  private Vector getDimensions(ComponentInfo componentInfo)
      throws EdgeOutOfRangeException, LayerOutOfRangeException {

    Vector vstrDimensions = null;
    QDR qdr = null;

    if (componentInfo == null) {
      return null;
    }

    // Crosstab
    if (componentInfo instanceof EdgeComponentInfo) {
      EdgeComponentInfo edgeComponentInfo = (EdgeComponentInfo)componentInfo;

      // Attempt to retrieve the dimension associated with this
      // component.
      DataAccess dataAccess = getQueryListener().getDataAccess();
      String strDimension =
        (String) dataAccess.getLayerMetadata(edgeComponentInfo.getEdge(),
          edgeComponentInfo.getDepth(), LayerMetadataMap.LAYER_METADATA_NAME);

      if (strDimension != null) {
        vstrDimensions = new Vector();
        vstrDimensions.addElement(strDimension);
        return vstrDimensions;
      }
    }
    else {
      // Crosstab and Table
      if (componentInfo instanceof PageItemComponentInfo) {
        PageItemComponentInfo pageItemComponentInfo =
          (PageItemComponentInfo)componentInfo;

        // Attempt to retrieve the dimension associated with this
        // component.
        DataAccess dataAccess = getQueryListener().getDataAccess();
        qdr = pageItemComponentInfo.getQDR(dataAccess,
          DataAccess.QDR_WITHOUT_PAGE);
      }
      else {
        // Table
        if (componentInfo instanceof ColumnComponentInfo) {
          ColumnComponentInfo columnComponentInfo =
            (ColumnComponentInfo)componentInfo;
          // Attempt to retrieve the dimension associated with this
          // component.
          DataAccess dataAccess = getQueryListener().getDataAccess();
          qdr = columnComponentInfo.getQDR(dataAccess,
            DataAccess.QDR_WITHOUT_PAGE);
        }
        else {
          // Graph
          if (componentInfo instanceof GroupComponentInfo) {
            GroupComponentInfo groupComponentInfo =
              (GroupComponentInfo)componentInfo;
            // Attempt to retrieve the dimension associated with this
            // component.
            DataAccess dataAccess = getQueryListener().getDataAccess();
            qdr = groupComponentInfo.getQDR(dataAccess,
              DataAccess.QDR_WITHOUT_PAGE);
          }
          else {
            // Graph
            if (componentInfo instanceof SeriesComponentInfo) {
              SeriesComponentInfo seriesComponentInfo =
                (SeriesComponentInfo)componentInfo;
              // Attempt to retrieve the dimension associated with this
              // component.
              DataAccess dataAccess = getQueryListener().getDataAccess();
              qdr = seriesComponentInfo.getQDR(dataAccess,
                DataAccess.QDR_WITHOUT_PAGE);
            }
          }
        }
      }
    }

    // Retrieve the dimensions assciated with the QDR
    if (qdr != null) {
      vstrDimensions = getDimensions(qdr);
    }

    return vstrDimensions;
  }

  /**
   *  Retrieves the selection associated with the specified component
   *  information.
   *
   *  @param  componentInfo a <code>ComponentInfo</code> value that
   *          represents the component we wish to interrogate.
   *  @return <code>Selection</code> or null.
   *  @throws EdgeOutOfRangeException
   *  @throws LayerOutOfRangeException
   */
  private Selection getSelection(ComponentInfo componentInfo)
      throws EdgeOutOfRangeException, LayerOutOfRangeException {
    Selection selection = null;
    String strDimension = null;

    if (componentInfo == null) {
      return null;
    }

    if (componentInfo instanceof EdgeComponentInfo) {
      EdgeComponentInfo edgeComponentInfo =
        (EdgeComponentInfo)componentInfo;

      // Attempt to retrieve the dimension associated with this
      // component.
      DataAccess dataAccess = getQueryListener().getDataAccess();
      strDimension =
        (String) dataAccess.getLayerMetadata(edgeComponentInfo.getEdge(),
          edgeComponentInfo.getDepth(),
          LayerMetadataMap.LAYER_METADATA_NAME);
    }
    else {
      if (componentInfo instanceof GroupComponentInfo) {
        GroupComponentInfo groupComponentInfo =
          (GroupComponentInfo)componentInfo;
      }
    }

    // Attempt to retrieve the selection associated with this
    // dimension.
    if (strDimension != null) {
      selection = getQueryListener().getQuery().findSelection(strDimension);
    }

    return selection;
  }

  /**
   * Creates a menu of <code>BILinkedViewPanel</code> options based on the
   * specified <code>Dataview</code> <code>ComponentHandle</code> position.
   *
   * @param  dataview a <code>Dataview</code> value that represents
   *         the Dataview associated with this popup.
   * @param  componentHandle a <code>ComponentHandle</code> value that
   *         represents the component handle associated with this
   *         popup.
   * @return <code>JMenu</code> representing the generated menu.
   */
  private JMenu getMenu(Dataview dataview, ComponentHandle componentHandle) {

    // Update the current view
    m_dataview = dataview;

    // Top Link Views menu
    final String strLink = "Link Views";
    JMenu jMenu = new JMenu(strLink);

    // Bi-Directional menu
    final String strBiDirectional = "Bi-Directional";
    JMenu jMenuBiDirectional = new JMenu(strBiDirectional);
    jMenu.add(jMenuBiDirectional);

    // Bi-Directional & All Dimensions menu item
    final String strAllDimensions = "All Dimensions";
    JMenuItem jMenuItemAllDimensions = new JMenuItem(strAllDimensions);
    jMenuBiDirectional.add(jMenuItemAllDimensions);

    // Bi-Directional & All Dimensions menu item listener
    jMenuItemAllDimensions.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent menuEvent) {
        try {
          updateLinks(m_dataview, RELATIONSHIP_BIDIRECTIONAL, null);
        }

        catch (Exception e) {
          e.printStackTrace();
        }
      }
     });

    // Bi-Directional & Selected Dimension(s) item menu
    String strDimensions = "Selected Dimension(s)";
    JMenuItem jMenuItemSelectedDimensions = new JMenuItem(strDimensions);
    jMenuBiDirectional.add(jMenuItemSelectedDimensions);

    // Bi-Directional & Selected Dimension(s) menu item listener
    jMenuItemSelectedDimensions.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent menuEvent) {
        try {
          ComponentInfo componentInfo = m_componentHandle.getComponentInfo();
          updateLinks(m_dataview, RELATIONSHIP_BIDIRECTIONAL,
            getDimensions(componentInfo));
         }

        catch (Exception e) {
          e.printStackTrace();
        }
      }
    });

    // Master/Detail menu
    final String strMasterDetail = "Master/Detail";
    JMenu jMenuMasterDetail = new JMenu(strMasterDetail);
    jMenu.add(jMenuMasterDetail);

    // Master/Detail & All Dimensions item menu
    jMenuItemAllDimensions = new JMenuItem(strAllDimensions);
    jMenuMasterDetail.add(jMenuItemAllDimensions);

    // Master/Detail & All Dimensions item menu listener
    jMenuItemAllDimensions.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent menuEvent) {
        ComponentInfo componentInfo = m_componentHandle.getComponentInfo();

        try {
          updateLinks(m_dataview, RELATIONSHIP_MASTER_DETAIL, null);
        }

        catch (Exception e) {
          e.printStackTrace();
        }
      }
    });

    // Master/Detail & Selected Dimensions item menu
    jMenuItemSelectedDimensions = new JMenuItem(strDimensions);
    jMenuMasterDetail.add(jMenuItemSelectedDimensions);

    // Master/Detail & Selected Dimensions item menu listener
    jMenuItemSelectedDimensions.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent menuEvent) {
        try {
          ComponentInfo componentInfo = m_componentHandle.getComponentInfo();
          updateLinks(m_dataview, RELATIONSHIP_MASTER_DETAIL,
            getDimensions(componentInfo));
        }

        catch (Exception e) {
          e.printStackTrace();
        }
      }
    });

    // Remove All item menu listener
    final String strRemoveAll = "Remove All";
    JMenuItem jMenuItemRemoveAll = new JMenuItem(strRemoveAll);
    jMenu.add(jMenuItemRemoveAll);

    jMenuItemRemoveAll.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent menuEvent) {
        try {
          updateLinks(m_dataview, RELATIONSHIP_NONE, null);
        }

        catch (Exception e) {
          e.printStackTrace();
        }
      }
    });

    return jMenu;
  }

  /**
   * Enables or disables the specified DataViews.
   *
   * @param vDataviews a <code>Vector</code> value that represents
   *        the list of <code>Dataview</code> objects to enable or disable.
   * @param bEnable a <code>boolean</code> value that is <code>true</code> when
   *        the dataviews should be enabled and <code>false</code> otherwise.
   *
   */
  public void enableDataviews(Vector vDataviews, boolean bEnable) {
    if (vDataviews != null) {
      Enumeration enumeration = vDataviews.elements();
      while (enumeration.hasMoreElements()) {
        Dataview dataview = (Dataview)enumeration.nextElement();

        // Enable/Disable each DataView
        dataview.setEnabled(bEnable);
      }
    }
  }

  /**
   * Updates the links between dataviews based on the specified relationship and
   * linked dimensions.
   *
   * @param dataview a <code>Dataview</code> value that represents
   *        the master Dataview associated with this <code>BILinkedViewPanel
   *        </code>.
   * @param nRelationship a <code>int</code> value that represents the
   *        relationship between views.  These include bi-directional,
   *        master/detail and removal of any existing relationships.
   * @param vstrDimensions a <code>Vector</code> value that represents the list
   *        of dimensions to link.  If this value is null, it is assumed that
   *        all dimensions should be linked (that is the entire query).
   *
   * @throws CloneNotSupportedException
   * @throws EdgeOutOfRangeException
   * @throws InvalidStepArgException
   * @throws MetadataManagerException
   * @throws QueryException
   * @throws SelectionException
   */
  private void updateLinks(Dataview dataview, int nRelationship,
                            Vector vstrDimensions)
    throws CloneNotSupportedException, DataDirectorException,
           EdgeOutOfRangeException, InvalidStepArgException,
           MetadataManagerException, QueryException, SelectionException,
           SliceOutOfRangeException {

    // Remove all previous formatting
    applyFormatting(getDataviews(), null, RELATIONSHIP_NONE, false);

    // Remember the master dataview, relationship and selections
    Dataview dataviewMaster = setDataviewMaster(dataview);
    int nOldRelationship = setRelationship(nRelationship);
    setDimensions(vstrDimensions);

    // Apply current formatting
    applyFormatting(getDataviews(), getDimensions(), nRelationship, true);

    // Remove any current links
    removeLinks();

    // Determine what to do based on relationship chosen
    switch (nRelationship) {
      case RELATIONSHIP_BIDIRECTIONAL:
        if (dataviewMaster != null) {
          if (getDimensions() == null) {
            // Bi-Directional & All Dimension(s)
            updateDetailDataSources(dataviewMaster, null, false);
          }
          else {
          // Bi-Directional & Selected Dimension(s)
          updateDetailDataSources(dataviewMaster, null, true);
          }
        }
        break;

      case RELATIONSHIP_MASTER_DETAIL:
        if (dataviewMaster != null) {
          if (getDimensions() == null) {
            // Master/Detail & All Dimension(s)
            // Handled by BILinkedQueryListener

            if (nOldRelationship == RELATIONSHIP_BIDIRECTIONAL) {
              // Remove links
              Vector vDataviews = getDataviews();
              if (vDataviews != null) {
                Enumeration enumeration = vDataviews.elements();
                while (enumeration.hasMoreElements()) {
                  Dataview dataviewCurrent =
                    (Dataview)enumeration.nextElement();
                  Query query = (QueryClient)dataviewCurrent.getDataSource();
                  setDataSource(dataviewCurrent, (Query)query.clone());
                }
              }
            }

            // Disable each detail view
            enableDataviews(getDataviewsDetail(), false);
          }
          else {
            // Master/Detail & Selected Dimension(s)
            Vector vDataviewsDetail = getDataviewsDetail();
            Enumeration enumeration = vDataviewsDetail.elements();
            while (enumeration.hasMoreElements()) {
              Dataview dataviewCurrent = (Dataview)enumeration.nextElement();
              applySelections((Query)dataviewMaster.getDataSource(),
                              (Query)dataviewCurrent.getDataSource(),
                               getDimensions());

              // Disable each detail view
              // dataviewCurrent.setEnabled (false);
            }
          }
        }
        break;

      case RELATIONSHIP_NONE:
        // Any existing links have been removed above by removeLinks method
        break;

      default:
        break;
    }
  }

  /**
   * Removes all links between dataviews.
   *
   * @throws CloneNotSupportedException
   */
  private void removeLinks() throws CloneNotSupportedException {

    // Remove links
    Vector vDataviews = getDataviews();
    if (vDataviews != null) {

      // Iterate over all dataviews
      Enumeration enumeration = vDataviews.elements();
      while (enumeration.hasMoreElements()) {
        Dataview dataviewCurrent = (Dataview)enumeration.nextElement();

        // Retrieve the datasource associated with the current dataview and
        // clone it to remove any links
        Query query = (QueryClient)dataviewCurrent.getDataSource();
        setDataSource(dataviewCurrent, (Query)query.clone());
      }
    }

    // Reenable all dataviews
    enableDataviews(vDataviews, true);
  }



  /**
   * Updates the menu of <code>BILinkedViewPanel</code> options based on the
   * specified <code>Dataview</code> <code>ComponentHandle</code> position.
   *
   * @param  dataview a <code>Dataview</code> value that represents
   *         the Dataview associated with this popup.
   * @param  jPopupMenu a <code>JPopupMenu</code> value that represents
   *         the menu to update.
   * @param  componentHandle a <code>ComponentHandle</code> value that
   *         represents the component handle associated with this
   *         popup.
   * @return <code>JMenu</code> representing the generated menu.
   */
  private void updateMenu(Dataview dataview, JPopupMenu jPopupMenu,
    ComponentHandle componentHandle) {
    JMenu jMenu = getMenu(dataview, componentHandle);
    jPopupMenu.add(jMenu);
  }


  /**
   * Applies the selections associated with the specified dimensions from the
   * query source to the query destination.
   *
   * @param  querySource a <code>Query</code> value that represents where the
   *         selections will be copied from.
   * @param  queryDestination a <code>Query</code> value that represents where
   *         the selections will be copied to.
   * @param  vstrDimensions a <code>Vector</code> value that represents the list
   *         of dimensions to retrieve selections for.
   *
   */
  private void applySelections(Query querySource, Query queryDestination,
                               Vector vstrDimensions)
    throws MetadataManagerException, QueryException,
           SelectionException, InvalidStepArgException {

    // Check for non-null parameters
    if ((querySource != null) && (queryDestination != null)
        && (vstrDimensions != null)) {
      if (!vstrDimensions.isEmpty()) {
        int nSelection = 0;
        int nDimensions = vstrDimensions.size();

        // Create new selections
        Selection[] selections = new Selection[nDimensions];
        for (int nIndex = 0; nIndex < nDimensions; nIndex++) {
          // Attempt to retrieve the selection associated with this
          // dimension.
          if (vstrDimensions.elementAt(nIndex) != null) {
            Selection selection =
              querySource.findSelection((String)vstrDimensions
                .elementAt(nIndex));

            // If we have retrieved a selection, update the list
            if (selection != null) {
              // Key Concept
              // 1) When copying selections, we want to keep the current drill
              //    state
              // 2) Selections are cloned before they are applied
              selection.setClearDrillStep(false);
              selections[nSelection++] = selection;
            }
          }

          // Update the datasource with the new selection
          queryDestination.applySelections(selections);
        }
      }
    }
  }
}




