/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2017 
 * © 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.
 */
package accessibility;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.print.PageFormat;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import javax.swing.table.TableModel;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvChartRenderer;
import ilog.views.chart.IlvScale;
import ilog.views.chart.accessibility.IlvAccessibleDataObject;
import ilog.views.chart.accessibility.IlvAccessibleDataPoint;
import ilog.views.chart.accessibility.IlvAccessibleDataSet;
import ilog.views.chart.accessibility.IlvChartAreaAccessible;
import ilog.views.chart.accessibility.IlvChartAreaAccessibleHierarchy;
import ilog.views.chart.accessibility.IlvDefaultChartAreaAccessibleHierarchy;
import ilog.views.chart.data.IlvDataSet;
import ilog.views.chart.data.IlvDataSetPoint;
import ilog.views.chart.data.IlvTreeTableDataSource;
import ilog.views.chart.datax.IlvColumnUtilities;
import ilog.views.chart.datax.IlvDataColumnInfo;
import ilog.views.chart.datax.IlvDefaultDataColumnInfo;
import ilog.views.chart.datax.adapter.partition.IlvPartitionerFactory;
import ilog.views.chart.datax.adapter.partition.IlvStringPartitionerFactory;
import ilog.views.chart.datax.flat.table.IlvDefaultFlatTableModel;
import ilog.views.chart.graphic.IlvDataLabelAnnotation;
import ilog.views.chart.interactor.IlvChartAreaNavigationMode;
import ilog.views.chart.interactor.IlvChartAreaSelectionManager;
import ilog.views.chart.interactor.IlvChartCycleSelectInteractor;
import ilog.views.chart.print.IlvChartPrintableDocument;
import ilog.views.chart.renderer.IlvSimpleValueColorScheme;
import ilog.views.chart.renderer.IlvTreemapChartRenderer;
import ilog.views.util.IlvImageUtil;
import ilog.views.util.data.IlvCSVReader;
import ilog.views.util.internal.IlvURLUtil;
import ilog.views.util.styling.IlvStylingException;
import ilog.views.util.swing.border.IlvEtchedLineBorder;
import ilog.views.util.swing.layout.IlvBetterFlowLayout;
import shared.AbstractChartExample;
import shared.PDFGenerator;

/**
 * A class that demonstrates accessibility features, such as - keyboard
 * navigation, - redundant colors.
 */
public class AccessibilityDemo extends AbstractChartExample {
  /**
   * The chart at the center of the frame.
   */
  IlvChart chart;

  /**
   * True if keyboard navigation can select the x scale.
   */
  boolean xScaleSelectable;

  /**
   * True if keyboard navigation can select the y scale.
   */
  boolean yScaleSelectable;

  /**
   * The hierarchy of selectable elements of the chart area.
   */
  IlvDefaultChartAreaAccessibleHierarchy hierarchy;

  /**
   * The manager that keeps track of and displays the current selection.
   */
  IlvChartAreaSelectionManager selectionManager;

  /**
   * The chart interactor that reacts on keyboard events by moving around the
   * current selection or the focus.
   */
  IlvChartCycleSelectInteractor interactor;

  /**
   * The name of the project that the chart displays.
   */
  String project;

  /**
   * The status bar at the bottom of the frame. In this sample, it displays the
   * currently focused component and, inside the chart area, the currently
   * selected element.
   */
  JLabel statusBar;

  // ========================== Keyboard Navigation ==========================

  /**
   * Specifies whether keyboard navigation can select the x scale.
   */
  private void setXScaleSelectable(boolean selectable) {
    IlvScale scale = chart.getXScale();
    if (scale != null)
      hierarchy.setSelectable(scale, selectable);
  }

  /**
   * Specifies whether keyboard navigation can select the y scale.
   */
  private void setYScaleSelectable(boolean selectable) {
    IlvScale scale = chart.getYScale(0);
    if (scale != null)
      hierarchy.setSelectable(scale, selectable);
  }

  /**
   * Creates the keyboard navigation interactor that uses the given mode and
   * makes use of the <code>hierarchy</code> and <code>selectionManager</code>
   * objects.
   */
  private IlvChartCycleSelectInteractor createInteractor(IlvChartAreaNavigationMode mode) {
    // This sample uses a customized hierarchy.
    // Therefore, instead of letting the interactor create its own, new
    // hierarchy object, reuse the customized one.
    IlvChartCycleSelectInteractor interactor = new IlvChartCycleSelectInteractor(mode) {
      Override
      public IlvChartAreaAccessibleHierarchy createAccessibleHierarchy(IlvChart chart) {
        return hierarchy;
      }
    };
    interactor.setSelectionManager(selectionManager);
    return interactor;
  }

  /**
   * Installs a custom focus traversal on the given component. This means that:
   * - The user can assign the focus to the component, by repeatedly pressing
   * Tab or Shift-Tab. - The component has its own way of managing "focus"
   * inside itself. - The user needs to press Ctrl-Tab or Shift-Ctrl-Tab instead
   * of Tab or Shift-Tab in order to move the focus outside the component.
   */
  private void installCustomFocusTraversal(Component component) {
    // So that the user can assign the focus to the component, it must
    // be enabled.
    // This statement is not needed in this sample, because the components
    // are enabled anyway.
    // component.setEnabled(true);

    // So that the user can assign the focus to the component, it must
    // be focusable.
    component.setFocusable(true);

    // If the component is a Container, and the user should use Ctrl-Tab
    // or Shift-Ctrl-Tab instead of Tab or Shift-Tab in order to leave
    // the component, you would have to designate it as a "focus cycle root".
    // component.setFocusCycleRoot(true);

    // Disable the AWT default actions for Tab and Shift-Tab on the component.
    component.setFocusTraversalKeysEnabled(false);

    // Enable similar actions for Ctrl-Tab and Shift-Ctrl-Tab instead.
    component.addKeyListener(new KeyAdapter() {
      private KeyStroke CTRL_TAB = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_MASK);
      private KeyStroke SHIFT_CTRL_TAB = KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
          KeyEvent.SHIFT_MASK | KeyEvent.CTRL_MASK);

      // Override keyPressed, not keyTyped, because these two KeyStrokes
      // are defined through a key code, not through a key char.
      Override
      public void keyPressed(KeyEvent event) {
        KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
        if (CTRL_TAB.equals(key)) {
          // Moves the focus outside of the chart area component, to the
          // next focusable component.
          KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
          event.consume();
        } else if (SHIFT_CTRL_TAB.equals(key)) {
          // Moves the focus outside of the chart area component, to the
          // previous focusable component.
          KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent();
          event.consume();
        }
      }
    });
  }

  // ---------------------- Displaying the current focus ----------------------

  /**
   * Displays the currently focused GUI element in the status bar.
   */
  private void updateFocusOwnerDisplay() {
    Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
    String text = "<none>";
    if (focusOwner != null) {
      // First piece of text: The class name of the focus owner.
      Class<?> focusOwnerClass = focusOwner.getClass();
      while (focusOwnerClass.isAnonymousClass()) // such as
                                                 // javax.swing.JToolBar$1.class
        focusOwnerClass = focusOwnerClass.getSuperclass();
      text = focusOwnerClass.getName();
      text = text.substring(text.lastIndexOf('.') + 1).replace('$', '.');
      // More detailed information for buttons.
      if (focusOwner instanceof AbstractButton) {
        AbstractButton button = (AbstractButton) focusOwner;
        if (button.getText() != null) {
          text += "(\"" + button.getText() + "\")";
        } else if (button.getAction() != null) {
          String name = (String) button.getAction().getValue(Action.NAME);
          String shortDescription = (String) button.getAction().getValue(Action.SHORT_DESCRIPTION);
          text += "(\"" + (shortDescription != null ? shortDescription : name) + "\")";
        }
      }
      // For the chart area, add details about the selected element.
      else if (focusOwner instanceof IlvChart.Area) {
        IlvChartAreaAccessible selection = selectionManager.getSelection();
        if (selection != null) {
          if (selection instanceof IlvScale) {
            text += " " + (((IlvScale) selection).getAxis().isXAxis() ? "X scale" : "Y scale");
          } else if (selection instanceof IlvChartRenderer) {
            text += " " + selection;
          } else if (selection instanceof IlvAccessibleDataSet) {
            IlvDataSet ds = ((IlvAccessibleDataSet) selection).getDataSet();
            text += " Data Set: " + ds.getName();
          } else if (selection instanceof IlvAccessibleDataPoint) {
            IlvDataSetPoint dp = ((IlvAccessibleDataPoint) selection).getDataPoint();
            text += " Data Point: " + dp.getDataSet().getName() + ", Point #" + dp.getIndex() + " (" + dp.getXData()
                + "," + dp.getYData() + ")";
          } else if (selection instanceof IlvAccessibleDataObject) {
            text += " Data Object: " + ((IlvAccessibleDataObject) selection).getObject();
          } else {
            text += " " + selection;
          }
        }
      }
      statusBar.setText("Focus: " + System.currentTimeMillis() + " " + text);
    }
  }

  /**
   * Installs a notification that makes sure that the focus owner display gets
   * updated when the focus owner changes.
   */
  private void installFocusOwnerListener() {
    // Get notified when the focus owner changes.
    KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner",
        new PropertyChangeListener() {
          Override
          public void propertyChange(PropertyChangeEvent event) {
            updateFocusOwnerDisplay();
          }
        });
  }

  /**
   * Creates an IlvChartAreaSelectionManager that makes sure that the focus
   * owner (and selection) display gets updated when the selection in the chart
   * area changes.
   */
  private IlvChartAreaSelectionManager createSelectionManager() {
    return new IlvChartAreaSelectionManager() {
      Override
      public void setSelection(IlvChartAreaAccessible selection) {
        super.setSelection(selection);
        // After the selection has changed, update the focus owner display.
        updateFocusOwnerDisplay();
      }
    };
  }

  // ------------------- Keyboard Navigation Initialization -------------------

  /**
   * Initializes the keyboard navigation.
   */
  private void initKeyboardNavigation() {
    xScaleSelectable = true;
    yScaleSelectable = true;
    hierarchy = new IlvDefaultChartAreaAccessibleHierarchy(chart);
    setXScaleSelectable(xScaleSelectable);
    setYScaleSelectable(yScaleSelectable);
    selectionManager = createSelectionManager();
    interactor = createInteractor(IlvChartCycleSelectInteractor.TRAVERSE_ALL);
    chart.addInteractor(interactor);

    // Enable custom handling of Tab, Shift-Tab, etc. in the chart area.
    // Optionally you could do the same thing for the legend as well.
    // The chart component itself can stay non-focusable.
    installCustomFocusTraversal(chart.getChartArea());

    installFocusOwnerListener();
  }

  // ============================ Several Projects ============================

  /**
   * The list of projects that can be displayed in this sample.
   */
  static String[] allProjects = { "data/influenza-by-year.icpr -- polyline chart",
      // "../gallery/data/wheat-by-year.icpr -- polyline chart",
      "data/french-parliament.icpr -- clustered bar chart", "data/influenza-by-age.icpr -- stacked area chart",
      "data/wheat-by-state.icpr -- pie chart", "data/income-per-county.csv -- treemap chart",
      // "../gallery/data/countries.icpr -- radar chart",
      // "../gallery/data/currency-exchange.icpr -- high-low chart",
      // "../gallery/data/earthquakes-alaska.icpr -- bubble chart",
      // "../gallery/data/hertzsprung-russell.icpr -- scatter chart",
      // "../gallery/data/xor-circuit.icpr -- stair chart",
      // "../gallery/data/yagi-antenna.icpr -- polar chart",
  };

  /**
   * Loads the specified project.
   * 
   * @param projectLabel
   *          A string from <code>allProjects</code>.
   */
  private void loadProject(String projectLabel) {
    String filename = projectLabel.substring(0, projectLabel.indexOf(" -- "));
    URL url = getResourceURL(filename);
    URL projectUrl = (filename.endsWith(".icpr") ? url : null);
    try {
      // Put the chart's type into a default state. When the chart's
      // configuration was read from a CSS file, this is automatic.
      // But when we have changed the chart type through the API, we
      // have to do it explicitly.
      if (chart.getType() == IlvChart.TREEMAP)
        chart.setType(IlvChart.CARTESIAN);
      chart.setProject(projectUrl);
    } catch (IOException e) {
      System.err.println("Cannot load " + filename);
      e.printStackTrace();
    } catch (IlvStylingException e) {
      System.err.println("Cannot load the style of " + filename);
      e.printStackTrace();
    }
    // Treemap projects need special handling because here they are not
    // specified through a .icpr file.
    if (projectLabel.endsWith(" -- treemap chart"))
      loadTreemapProject(url);
    // Remember the chosen project, for the display of the menu.
    project = projectLabel;
  }

  /**
   * Loads the treemap project.
   * 
   * @param url
   *          The URL of the data file.
   */
  private void loadTreemapProject(URL url) {
    // Read the CSV file into memory.
    TableModel tableModel;
    try {
      InputStream stream = IlvURLUtil.openStream(url);
      Reader reader = new InputStreamReader(new BufferedInputStream(stream), "ASCII");
      try {
        tableModel = IlvCSVReader.getInstance(IlvCSVReader.COMMA, 0).read(reader);
      } finally {
        reader.close();
      }
    } catch (IOException e) {
      e.printStackTrace();
      chart.setDataSource(null);
      return;
    }

    // Information about the columns in the CSV file (name and type).
    String[] columnNames = { "State", "County code", "State Abbrev", "County name", "Returns", "Exemptions",
        "Adjusted gross income", "Wages and salaries", "Dividends", "Interest",
        // Extra, computed columns.
        "Wages and salaries percentage", "Dividends percentage", "Interest percentage" };
    Class<?>[] columnTypes = { String.class, String.class, String.class, String.class, Double.class, Double.class,
        Double.class, Double.class, Double.class, Double.class };
    final int County_code_COLUMN = 1;
    final int County_name_COLUMN = 3;
    final int Adjusted_gross_income_COLUMN = 6;
    final int Wages_and_salaries_COLUMN = 7;
    final int Dividends_COLUMN = 8;
    final int Interest_COLUMN = 9;
    final int Wages_and_salaries_percentage_COLUMN = 10;
    final int Dividends_percentage_COLUMN = 11;
    final int Interest_percentage_COLUMN = 12;

    // Copy the data into an IlvFlatTableModel, with appropriate
    // conversion of values.
    int numColumns = tableModel.getColumnCount();
    IlvDataColumnInfo[] columns = new IlvDataColumnInfo[columnNames.length];
    for (int col = 0; col < numColumns; col++) {
      columns[col] = new IlvDefaultDataColumnInfo(columnNames[col], columnTypes[col]);
    }
    for (int col = numColumns; col < columnNames.length; col++) {
      columns[col] = new IlvDefaultDataColumnInfo(columnNames[col], Double.class);
    }
    int numRows = tableModel.getRowCount();
    IlvDefaultFlatTableModel tableData = new IlvDefaultFlatTableModel(numRows, columns);
    for (int col = 0; col < numColumns; col++) {
      if (columnTypes[col] == Double.class) {
        for (int row = 0; row < numRows; row++) {
          double value = Double.parseDouble(tableModel.getValueAt(row, col).toString());
          tableData.setDoubleAt(value, row, col);
        }
      } else if (columnTypes[col] == Integer.class) {
        for (int row = 0; row < numRows; row++) {
          int value = (int) Double.parseDouble(tableModel.getValueAt(row, col).toString());
          tableData.setValueAt(new Integer(value), row, col);
        }
      } else if (columnTypes[col] == String.class) {
        for (int row = 0; row < numRows; row++) {
          String value = tableModel.getValueAt(row, col).toString();
          tableData.setValueAt(value, row, col);
        }
      } else {
        for (int row = 0; row < numRows; row++) {
          Object value = tableModel.getValueAt(row, col);
          tableData.setValueAt(value, row, col);
        }
      }
    }
    // Remove the suffix " County" from the county names.
    for (int row = 0; row < numRows; row++) {
      String countyName = (String) tableData.getValueAt(row, County_name_COLUMN);
      if (countyName.endsWith(" County"))
        tableData.setValueAt(countyName.substring(0, countyName.length() - 7), row, County_name_COLUMN);
    }
    // Fill the computed column values.
    for (int row = 0; row < numRows; row++) {
      double agi = ((Double) tableData.getValueAt(row, Adjusted_gross_income_COLUMN)).doubleValue();
      double wd = ((Double) tableData.getValueAt(row, Wages_and_salaries_COLUMN)).doubleValue();
      double d = ((Double) tableData.getValueAt(row, Dividends_COLUMN)).doubleValue();
      double i = ((Double) tableData.getValueAt(row, Interest_COLUMN)).doubleValue();
      tableData.setValueAt(new Double(wd / agi), row, Wages_and_salaries_percentage_COLUMN);
      tableData.setValueAt(new Double(d / agi), row, Dividends_percentage_COLUMN);
      tableData.setValueAt(new Double(i / agi), row, Interest_percentage_COLUMN);
    }

    // Remove the rows with county code 0, because these are per-state
    // summary rows.
    for (int row = 0; row < tableData.getRowCount();) {
      if ("0".equals(tableData.getValueAt(row, County_code_COLUMN)))
        tableData.removeRow(row);
      else
        row++;
    }

    // Create a data source taking the model as input.
    IlvTreeTableDataSource dataSource = new IlvTreeTableDataSource(tableData);
    dataSource.setPartitionerFactories(new IlvPartitionerFactory[] {
        new IlvStringPartitionerFactory(IlvColumnUtilities.getColumnByName(tableData, "State")) });

    /*
     * Customize the chart.
     */

    chart.setType(IlvChart.TREEMAP);

    // Add a header label.
    chart.setHeaderText("Income from interests in the U.S. 2008");
    chart.getHeader().setFont(new Font("Dialog", Font.PLAIN, 30));

    chart.setDataSource(dataSource);

    IlvTreemapChartRenderer renderer = (IlvTreemapChartRenderer) chart.getRenderer(0);

    renderer.setAreaColumnName("Adjusted gross income");

    // Customize the color of the objects.
    // Set the column which determines the color.
    renderer.setColorColumnName("Interest percentage");
    renderer.setColorScheme(
        IlvSimpleValueColorScheme.createAdjustedColorScheme(IlvTreemapChartRenderer.COLORSCHEME_AVERAGE_BLUE_YELLOW));

    // Add a label to each rectangle, and arrange to avoid label overlaps.
    renderer.setLabelColumnName("County name");
    renderer.setAnnotation(new IlvDataLabelAnnotation());
    renderer.setClippedAnnotationVisibility(false);
    renderer.setAnnotationVisibility(IlvTreemapChartRenderer.VISIBILITY_SMART);
  }

  // ================================ Printing ================================

  /**
   * Creates the document to be printed.
   */
  Override
  protected IlvChartPrintableDocument createPrintableDocument() {
    return new IlvChartPrintableDocument("accessibility", chart, PageFormat.LANDSCAPE, null);
  }

  // ============================= PDF Generation =============================

  /**
   * Creates the XSL-FO element to be shown in the PDF.
   */
  Override
  protected Element createFOElement(Document document, PDFGenerator pdfgen) {
    return createFOElementForChart(chart, document, pdfgen);
  }

  // =========================== GUI Initialization ===========================

  private AbstractAction exitAction;

  /**
   * Initializes the user interface of the sample's frame.
   */
  Override
  public void init(Container container) {
    super.init(container);

    // Allocate the components.

    chart = new IlvChart();
    initKeyboardNavigation();
    loadProject(allProjects[0]);

    JToolBar toolbar = createToolBar();

    exitAction = new AbstractAction("Exit") {
      Override
      public void actionPerformed(ActionEvent event) {
        quit();
        System.exit(0);
      }
    };
    JButton exitButton = new JButton(exitAction);
    
    statusBar = new JLabel(" ");
    statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));

    // Put them together.

    container.setLayout(new BorderLayout());
    container.add(toolbar, BorderLayout.NORTH);
    {
      JPanel middlePanel = new JPanel();
      middlePanel.setLayout(new BorderLayout());
      middlePanel.add(chart, BorderLayout.CENTER);
      {
        JPanel buttonBar = new JPanel();
        buttonBar.setLayout(new IlvBetterFlowLayout());
        buttonBar.setBorder(new IlvEtchedLineBorder());
        buttonBar.add(exitButton);
        middlePanel.add(buttonBar, BorderLayout.SOUTH);
      }
      container.add(middlePanel, BorderLayout.CENTER);
    }
    container.add(statusBar, BorderLayout.SOUTH);

    // Add a menu bar.

    {
      JMenuBar menuBar = new JMenuBar();
      menuBar.add(createFileMenu());
      menuBar.add(createSelectableElementsMenu());
      menuBar.add(createNavigationModeMenu());
      menuBar.add(createRenderingMenu());
      menuBar.add(createLAFMenu());
      setMenuBar(menuBar);
    }
  }

  // -------------------------------- Tool Bar --------------------------------

  /**
   * Populates the specified toolbar.
   */
  Override
  protected void populateToolBar(JToolBar toolbar) {
    super.populateToolBar(toolbar);
    addPrintingToToolBar(toolbar);
  }

  // ---------------------------------- Menu ----------------------------------

  private JMenu createFileMenu() {
    JMenu menu = new JMenu("File");
    menu = createProjectChooserMenu(menu);
    if (isPrintingConfigured()) {
      menu.addSeparator();
      ImageIcon icon;
      try {
        icon = new ImageIcon(IlvImageUtil.loadImageFromFile(AbstractChartExample.class, "PrintPreview24.gif"));
      } catch (java.io.IOException e) {
        System.err.println("Cannot load PrintPreview24.gif");
        icon = null;
      }
      initPrinting();
      Action action = new AbstractAction("Print...", icon) {
        Override
        public void actionPerformed(ActionEvent event) {
          printPreview();
        }
      };
      menu.add(action);
    }
    
    menu.addSeparator();
    menu.add(exitAction);
    
    return menu;
  }

  private JMenu createProjectChooserMenu(JMenu menu) {
    ButtonGroup group = new ButtonGroup();
    for (final String projectLabel : allProjects) {
      String labelWithoutDirectory = projectLabel.substring(projectLabel.lastIndexOf('/') + 1);
      JRadioButtonMenuItem item = new JRadioButtonMenuItem(labelWithoutDirectory);
      item.setSelected(projectLabel.equals(project));
      item.addItemListener(new ItemListener() {
        Override
        public void itemStateChanged(ItemEvent event) {
          if (((JRadioButtonMenuItem) event.getSource()).isSelected()) {
            loadProject(projectLabel);
          }
        }
      });
      group.add(item);
      menu.add(item);
    }
    return menu;
  }

  private JMenu createSelectableElementsMenu() {
    JMenu menu = new JMenu("Selectable Elements");
    {
      JCheckBoxMenuItem item = new JCheckBoxMenuItem("X Scale");
      item.setSelected(xScaleSelectable);
      item.addItemListener(new ItemListener() {
        Override
        public void itemStateChanged(ItemEvent event) {
          boolean checked = ((JCheckBoxMenuItem) event.getSource()).isSelected();
          setXScaleSelectable(checked);
        }
      });
      menu.add(item);
    }
    {
      JCheckBoxMenuItem item = new JCheckBoxMenuItem("Y Scale");
      item.setSelected(yScaleSelectable);
      item.addItemListener(new ItemListener() {
        Override
        public void itemStateChanged(ItemEvent event) {
          boolean checked = ((JCheckBoxMenuItem) event.getSource()).isSelected();
          setYScaleSelectable(checked);
        }
      });
      menu.add(item);
    }
    return menu;
  }

  private JMenu createNavigationModeMenu() {
    JMenu menu = new JMenu("Navigation Mode");
    ButtonGroup group = new ButtonGroup();
    IlvChartAreaNavigationMode[] modes = { IlvChartCycleSelectInteractor.TRAVERSE_ALL,
        IlvChartCycleSelectInteractor.BY_LEVEL };
    String[] labels = { "Traverse all", "By level" };
    for (int i = 0; i < modes.length; i++) {
      final IlvChartAreaNavigationMode mode = modes[i];
      JRadioButtonMenuItem item = new JRadioButtonMenuItem(labels[i]);
      item.setSelected(interactor.getMode() == mode);
      item.addItemListener(new ItemListener() {
        Override
        public void itemStateChanged(ItemEvent event) {
          if (((JRadioButtonMenuItem) event.getSource()).isSelected()) {
            // We cannot change the mode of a IlvChartCycleSelectInteractor.
            // Instead, we have to create a new instance.
            chart.removeInteractor(interactor);
            interactor = createInteractor(mode);
            chart.addInteractor(interactor);
            chart.repaint();
          }
        }
      });
      group.add(item);
      menu.add(item);
    }
    return menu;
  }

  private JMenu createRenderingMenu() {
    JMenu menu = new JMenu("Selection Rendering");
    ButtonGroup group = new ButtonGroup();
    int[] renderingModes = { IlvChartAreaSelectionManager.SHOW_HANDLES, IlvChartAreaSelectionManager.SHOW_HIGHLIGHT };
    String[] labels = { "Handles", "Highlight" };
    for (int i = 0; i < renderingModes.length; i++) {
      final int renderingMode = renderingModes[i];
      JRadioButtonMenuItem item = new JRadioButtonMenuItem(labels[i]);
      item.setSelected(selectionManager.getRenderingMode() == renderingMode);
      item.addItemListener(new ItemListener() {
        Override
        public void itemStateChanged(ItemEvent event) {
          if (((JRadioButtonMenuItem) event.getSource()).isSelected()) {
            selectionManager.setRenderingMode(renderingMode);
            chart.repaint();
          }
        }
      });
      group.add(item);
      menu.add(item);
    }
    return menu;
  }

  // ============================== Main Program ==============================

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        JFrame frame = new JFrame("Accessibility in a Chart");
        AccessibilityDemo demo = new AccessibilityDemo();
        demo.init(frame.getContentPane());
        demo.setFrameGeometry(800, 600, true);
        frame.setVisible(true);
      }
    });
  }
}