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

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;

import javax.swing.*;
import javax.swing.undo.*;
import javax.swing.event.*;

import ilog.views.chart.*;
import ilog.views.chart.graphic.*;
import ilog.views.chart.customizer.IlvChartCSSAdapter;
import ilog.views.chart.customizer.swing.*;
import ilog.views.chart.data.xml.IlvXMLDataSource;

import ilog.views.customizer.IlvCustomizerException;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.css.IlvCSS;
import ilog.views.util.swing.IlvSwingUtil;

// An internal undocumented class, only used for debugging.
import ilog.views.customizer.styling.internal.IlvRuleCustomizerManager;

/**
 * A simple example that illustrates how to use the customizers on a
 * chart component.
 */
public class ChartCustomizerExample extends JFrame {

    private static final String XML_FILE = "data/simple.xml";
    private static final String CSS_FILE = "data/simple.css";
    
    // The selector of the rule loaded initially 
    private static final String INITIAL_RULE_SELECTOR = "chart";

    protected IlvChart chart;
    protected IlvChartCSSCustomizerPanel customizer;
    protected String currentSelector;
    protected IlvXMLDataSource xmlDS = new IlvXMLDataSource();

    private JMenuItem undoMenuItem;
    private JMenuItem redoMenuItem;
    private JMenu rulesMenu;

    private Action addImageDecoAction;
    private Action removeImageDecoAction;
    private Action addXRangeDataIndicatorAction;
    private Action removeXRangeDataIndicatorAction;
    private Action addYRangeDataIndicatorAction;
    private Action removeYRangeDataIndicatorAction;
    private Action addXValueDataIndicatorAction;
    private Action removeXValueDataIndicatorAction;
    private Action addYValueDataIndicatorAction;
    private Action removeYValueDataIndicatorAction;
    private Action addDataWindowIndicatorAction;
    private Action removeDataWindowIndicatorAction;

    // Intermediate containers.
    JSplitPane wholePanel;
    static final double wholePanelDefaultDivision = 0.5;

    /** Creates a new <code>ChartCustomizerExample</code>. */
    public ChartCustomizerExample() {
        IlvSwingUtil.setDefaultAppropriateLookAndFeel();
        enableEvents(AWTEvent.WINDOW_EVENT_MASK);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("Chart Styling Customizer");
        
        // the chart
        this.chart = createChart();

        // the chart customizer
        this.customizer = new IlvChartCSSCustomizerPanel(chart);

        // Disable CSS expressions, since they are not enabled in the designer
        // either.
        try {
          customizer.setEditAsTextEnabled(false, true);
        } catch (IlvCustomizerException e) {
          e.printStackTrace();
        }

        // Possibly enable some debugging.
        if (false) {
          ((IlvRuleCustomizerManager)customizer.getAdapter().getInternal()).getCSS().setDebugMode(true);
          ((IlvRuleCustomizerManager)customizer.getAdapter().getInternal()).getCSS().getCSSBeans().setStyleSheetDebugMask(IlvCSS.BAD_PROP_WITH_STACK_MASK);
        }

        // enable undo at the chart customizer
        customizer.addUndoableEditListener(undoListener);

        // a split pane to show the chart in the upper half and the
        // customizer in the lower half
        wholePanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
        wholePanel.setDividerLocation(wholePanelDefaultDivision);
        wholePanel.setResizeWeight(wholePanelDefaultDivision);
        wholePanel.setTopComponent(chart);
        wholePanel.setBottomComponent(customizer);

        getContentPane().add(createMenuBar(), BorderLayout.NORTH);
        getContentPane().add(wholePanel, BorderLayout.CENTER);
    }

    /** Creates the chart. */
    protected IlvChart createChart()
    {
        IlvChart chart = new IlvChart();
        try {
            xmlDS.setFilename(XML_FILE);
        } catch (Exception x) {
            x.printStackTrace();
        }
        chart.setDataSource(xmlDS);
        try {
            chart.setStyleSheets(new String[] {CSS_FILE});
        } catch (ilog.views.util.styling.IlvStylingException x) {
            System.err.println("Cannot load style sheets: " + x.getMessage());
            x.printStackTrace();
        }
        return chart;
    }

    public static void main(String[] args)
    {
        // This sample uses JViews Charts features. When deploying an
        // application that includes this code, you need to be in possession
        // of a Perforce JViews Charts Deployment license.
        IlvProductUtil.DeploymentLicenseRequired(
            IlvProductUtil.JViews_Charts_Deployment);

        final ChartCustomizerExample example = new ChartCustomizerExample();
        example.setSize(700,700);
        SwingUtilities.invokeLater(
            new Runnable() {
                Override
                public void run() {
                    example.setVisible(true);
                    example.wholePanel
                           .setDividerLocation(wholePanelDefaultDivision);
                    example.setRule(INITIAL_RULE_SELECTOR);
                    example.updateRulesMenu();
                }
            });
    }

    // -----------------------------------------------------------------------
    // The undo mechanism

    /**
     * The undo manager.
     */
    private UndoManager undoManager = new UndoManager();

    /**
     * Listens to the undo events coming from the charts customizer.
     */
    private UndoableEditListener undoListener = new UndoableEditListener() {
        Override
        public void undoableEditHappened(UndoableEditEvent e) {
            undoManager.addEdit(e.getEdit());
            updateUndoRedoMenuItems();
        }
    };

    /**
     * Updates the undo menu entries.
     */
    private void updateUndoRedoMenuItems()
    {
        if (undoMenuItem != null && redoMenuItem != null) {
            undoMenuItem.setEnabled(undoManager.canUndo());
            redoMenuItem.setEnabled(undoManager.canRedo());
        }
    }
    
    // -----------------------------------------------------------------------
    // The menu bar

    /**
     * Create a simple menu bar with an undo/redo menu.
     */
    private JMenuBar createMenuBar()
    {   
        JMenuBar bar = new JMenuBar();

        rulesMenu = new JMenu("Style Rules");
        fillRulesMenu(rulesMenu);
        bar.add(rulesMenu);

        JMenu undoMenu = new JMenu("Undo");
        bar.add(undoMenu);

        JMenu decoMenu = new JMenu("Decorations");
        bar.add(decoMenu);

        // -- undo -------------------------

        Action undoAction = new AbstractAction("Undo") {
            Override
            public void actionPerformed(ActionEvent e) {
                try {
                    undoManager.undo();
                } catch (CannotUndoException ex) {
                    ex.printStackTrace();
                } finally {
                    updateUndoRedoMenuItems();
                }
            }
        };
        Action redoAction = new AbstractAction("Redo") {
            Override
            public void actionPerformed(ActionEvent e) {
                try {
                    undoManager.redo();
                } catch (CannotRedoException ex) {
                    ex.printStackTrace();
                } finally {
                    updateUndoRedoMenuItems();
                }
            }
        };
        
        undoMenuItem = new JMenuItem(undoAction);
        redoMenuItem = new JMenuItem(redoAction);
        
        undoMenu.add(undoMenuItem);
        undoMenu.add(redoMenuItem);
        
        updateUndoRedoMenuItems();

        // -- decorations -------------

        addImageDecoAction =
          new AbstractAction("Add Image Decoration") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvImageDecoration deco =
                  new IlvImageDecoration(new File("data/draft.gif").toURI().toURL(),
                                         IlvImageDecoration.ANCHORED,
                                         SwingConstants.CENTER);
                chart.addDecoration(deco);
                updateDataIndicatorActions();
                String selector = customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco);
                if (selector != null)
                  setRule(selector);
                updateRulesMenu();
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };
        removeImageDecoAction =
          new AbstractAction("Remove Image Decoration") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvChartDecoration designatedDeco = null;
                for (IlvChartDecoration deco : chart.getDecorations()) {
                  if (deco instanceof IlvImageDecoration
                      && currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco)))
                    designatedDeco = deco;
                }
                if (designatedDeco == null) {
                  for (IlvChartDecoration deco : chart.getDecorations()) {
                    if (deco instanceof IlvImageDecoration)
                      designatedDeco = deco;
                  }
                }
                if (designatedDeco != null) {
                  chart.removeDecoration(designatedDeco);
                  updateDataIndicatorActions();
                  if (currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(designatedDeco)))
                    setRule(INITIAL_RULE_SELECTOR);
                  updateRulesMenu();
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };

        // this is only example code how to add and remove a data indicator.
        // The CSS is updated automatically, i.e., a decoration rule is added.
        // Since the menu should reflect the current rules, we call
        // fillRulesMenu to update the rules menu.

        addXRangeDataIndicatorAction =
          new AbstractAction("Add X-Range Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvAxis axis = chart.getXAxis();
                IlvDataInterval range = axis.getVisibleRange();
                IlvDataIndicator deco =
                  new IlvDataIndicator(-1,
                                       new IlvDataInterval(range.getMiddle(), range.getMiddle()+0.2*range.getLength()),
                                       "Range 50%-70%");
                chart.addDecoration(deco);
                updateDataIndicatorActions();
                String selector = customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco);
                if (selector != null)
                  setRule(selector);
                updateRulesMenu();
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };
        removeXRangeDataIndicatorAction =
          new AbstractAction("Remove X-Range Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvChartDecoration designatedDeco = null;
                for (IlvChartDecoration deco : chart.getDecorations()) {
                  if (deco instanceof IlvDataIndicator
                      && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.X_RANGE
                      && currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco)))
                    designatedDeco = deco;
                }
                if (designatedDeco == null) {
                  for (IlvChartDecoration deco : chart.getDecorations()) {
                    if (deco instanceof IlvDataIndicator
                        && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.X_RANGE)
                      designatedDeco = deco;
                  }
                }
                if (designatedDeco != null) {
                  chart.removeDecoration(designatedDeco);
                  updateDataIndicatorActions();
                  if (currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(designatedDeco)))
                    setRule(INITIAL_RULE_SELECTOR);
                  updateRulesMenu();
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };

        addYRangeDataIndicatorAction =
          new AbstractAction("Add Y-Range Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvAxis axis = chart.getYAxis(0);
                IlvDataInterval range = axis.getVisibleRange();
                IlvDataIndicator deco =
                  new IlvDataIndicator(0,
                                       new IlvDataInterval(range.getMiddle(), range.getMiddle()+0.2*range.getLength()),
                                       "Range 50%-70%");
                chart.addDecoration(deco);
                updateDataIndicatorActions();
                String selector = customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco);
                if (selector != null)
                  setRule(selector);
                updateRulesMenu();
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };
        removeYRangeDataIndicatorAction =
          new AbstractAction("Remove Y-Range Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvChartDecoration designatedDeco = null;
                for (IlvChartDecoration deco : chart.getDecorations()) {
                  if (deco instanceof IlvDataIndicator
                      && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.Y_RANGE
                      && currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco)))
                    designatedDeco = deco;
                }
                if (designatedDeco == null) {
                  for (IlvChartDecoration deco : chart.getDecorations()) {
                    if (deco instanceof IlvDataIndicator
                        && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.Y_RANGE)
                      designatedDeco = deco;
                  }
                }
                if (designatedDeco != null) {
                  chart.removeDecoration(designatedDeco);
                  updateDataIndicatorActions();
                  if (currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(designatedDeco)))
                    setRule(INITIAL_RULE_SELECTOR);
                  updateRulesMenu();
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };

        addXValueDataIndicatorAction =
          new AbstractAction("Add X-Value Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvAxis axis = chart.getXAxis();
                IlvDataInterval range = axis.getVisibleRange();
                IlvDataIndicator deco =
                  new IlvDataIndicator(-1,
                                       range.getMin()+0.2*range.getLength(),
                                       "Value 20%");
                chart.addDecoration(deco);
                updateDataIndicatorActions();
                String selector = customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco);
                if (selector != null)
                  setRule(selector);
                updateRulesMenu();
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };
        removeXValueDataIndicatorAction =
          new AbstractAction("Remove X-Value Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvChartDecoration designatedDeco = null;
                for (IlvChartDecoration deco : chart.getDecorations()) {
                  if (deco instanceof IlvDataIndicator
                      && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.X_VALUE
                      && currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco)))
                    designatedDeco = deco;
                }
                if (designatedDeco == null) {
                  for (IlvChartDecoration deco : chart.getDecorations()) {
                    if (deco instanceof IlvDataIndicator
                        && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.X_VALUE)
                      designatedDeco = deco;
                  }
                }
                if (designatedDeco != null) {
                  chart.removeDecoration(designatedDeco);
                  updateDataIndicatorActions();
                  if (currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(designatedDeco)))
                    setRule(INITIAL_RULE_SELECTOR);
                  updateRulesMenu();
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };

        addYValueDataIndicatorAction =
          new AbstractAction("Add Y-Value Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvAxis axis = chart.getYAxis(0);
                IlvDataInterval range = axis.getVisibleRange();
                IlvDataIndicator deco =
                  new IlvDataIndicator(0,
                                       range.getMin()+0.2*range.getLength(),
                                       "Value 20%");
                chart.addDecoration(deco);
                updateDataIndicatorActions();
                String selector = customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco);
                if (selector != null)
                  setRule(selector);
                updateRulesMenu();
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };
        removeYValueDataIndicatorAction =
          new AbstractAction("Remove Y-Value Data Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvChartDecoration designatedDeco = null;
                for (IlvChartDecoration deco : chart.getDecorations()) {
                  if (deco instanceof IlvDataIndicator
                      && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.Y_VALUE
                      && currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco)))
                    designatedDeco = deco;
                }
                if (designatedDeco == null) {
                  for (IlvChartDecoration deco : chart.getDecorations()) {
                    if (deco instanceof IlvDataIndicator
                        && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.Y_VALUE)
                      designatedDeco = deco;
                  }
                }
                if (designatedDeco != null) {
                  chart.removeDecoration(designatedDeco);
                  updateDataIndicatorActions();
                  if (currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(designatedDeco)))
                    setRule(INITIAL_RULE_SELECTOR);
                  updateRulesMenu();
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };

        addDataWindowIndicatorAction =
          new AbstractAction("Add Data Window Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvDataInterval xRange = chart.getXAxis().getVisibleRange();
                IlvDataInterval yRange = chart.getYAxis(0).getVisibleRange();
                IlvDataWindow window =
                  new IlvDataWindow(xRange.getMiddle()-0.1*xRange.getLength(),
                                    xRange.getMiddle()+0.1*xRange.getLength(),
                                    yRange.getMiddle()-0.1*yRange.getLength(),
                                    yRange.getMiddle()+0.1*yRange.getLength());
                IlvDataIndicator deco =
                  new IlvDataIndicator(0, window, null);
                chart.addDecoration(deco);
                updateDataIndicatorActions();
                String selector = customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco);
                if (selector != null)
                  setRule(selector);
                updateRulesMenu();
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };
        removeDataWindowIndicatorAction =
          new AbstractAction("Remove Data Window Indicator") {
            Override
            public void actionPerformed(ActionEvent event) {
              try {
                IlvChartDecoration designatedDeco = null;
                for (IlvChartDecoration deco : chart.getDecorations()) {
                  if (deco instanceof IlvDataIndicator
                      && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.WINDOW
                      && currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(deco)))
                    designatedDeco = deco;
                }
                if (designatedDeco == null) {
                  for (IlvChartDecoration deco : chart.getDecorations()) {
                    if (deco instanceof IlvDataIndicator
                        && ((IlvDataIndicator)deco).getType() == IlvDataIndicator.WINDOW)
                      designatedDeco = deco;
                  }
                }
                if (designatedDeco != null) {
                  chart.removeDecoration(designatedDeco);
                  updateDataIndicatorActions();
                  if (currentSelector.equals(customizer.getAdapter().getSingleObjectCustomizableRuleSelector(designatedDeco)))
                    setRule(INITIAL_RULE_SELECTOR);
                  updateRulesMenu();
                }
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
          };

        decoMenu.add(new JMenuItem(addImageDecoAction));
        decoMenu.add(new JMenuItem(removeImageDecoAction));
        decoMenu.add(new JMenuItem(addXRangeDataIndicatorAction));
        decoMenu.add(new JMenuItem(removeXRangeDataIndicatorAction));
        decoMenu.add(new JMenuItem(addYRangeDataIndicatorAction));
        decoMenu.add(new JMenuItem(removeYRangeDataIndicatorAction));
        decoMenu.add(new JMenuItem(addXValueDataIndicatorAction));
        decoMenu.add(new JMenuItem(removeXValueDataIndicatorAction));
        decoMenu.add(new JMenuItem(addYValueDataIndicatorAction));
        decoMenu.add(new JMenuItem(removeYValueDataIndicatorAction));
        decoMenu.add(new JMenuItem(addDataWindowIndicatorAction));
        decoMenu.add(new JMenuItem(removeDataWindowIndicatorAction));

        updateDataIndicatorActions();

        // ---------------------------------------------
        // Options
        
        JMenu optionsMenu = new JMenu("Options");
        bar.add(optionsMenu);
        
        final JCheckBoxMenuItem statusIconMenuItem = 
          new JCheckBoxMenuItem();
        statusIconMenuItem.setAction(
            new AbstractAction("Show Rule Declaration Status Icon") {
              Override
              public void actionPerformed(ActionEvent e) {
                try {
                  customizer.setDeclarationStatusIconEnabled(
                               statusIconMenuItem.isSelected(), true);
                } catch (Exception ex) {
                  ex.printStackTrace();
                }
              }
            });
        statusIconMenuItem.setSelected(customizer.isDeclarationStatusIconEnabled());
        optionsMenu.add(statusIconMenuItem);
        
        final JCheckBoxMenuItem editAsTextMenuItem = 
          new JCheckBoxMenuItem();
        editAsTextMenuItem.setAction(
            new AbstractAction("Allow Entering CSS Expressions") {
              Override
              public void actionPerformed(ActionEvent e) {
                try {
                  customizer.setEditAsTextEnabled(
                               editAsTextMenuItem.isSelected(), true);
                } catch (Exception ex) {
                  ex.printStackTrace();
                }
              }
            });
        editAsTextMenuItem.setSelected(customizer.isEditAsTextEnabled());
        optionsMenu.add(editAsTextMenuItem);
        
        return bar;
    }

    /**
     * Update the enabled state of the data indicator actions.
     */
    private void updateDataIndicatorActions()
    {
        boolean haveImageDeco = false;
        boolean haveXRangeDataIndicator = false;
        boolean haveYRangeDataIndicator = false;
        boolean haveXValueDataIndicator = false;
        boolean haveYValueDataIndicator = false;
        boolean haveDataWindowIndicator = false;
        for (IlvChartDecoration deco : chart.getDecorations()) {
          if (deco instanceof IlvImageDecoration)
            haveImageDeco = true;
          else if (deco instanceof IlvDataIndicator) {
            switch (((IlvDataIndicator)deco).getType()) {
              case IlvDataIndicator.X_RANGE:
                haveXRangeDataIndicator = true;
                break;
              case IlvDataIndicator.Y_RANGE:
                haveYRangeDataIndicator = true;
                break;
              case IlvDataIndicator.X_VALUE:
                haveXValueDataIndicator = true;
                break;
              case IlvDataIndicator.Y_VALUE:
                haveYValueDataIndicator = true;
                break;
              case IlvDataIndicator.WINDOW:
                haveDataWindowIndicator = true;
                break;
              default:
                break;
            }
          }
        }
        addImageDecoAction.setEnabled(true);
        removeImageDecoAction.setEnabled(haveImageDeco);
        addXRangeDataIndicatorAction.setEnabled(true);
        removeXRangeDataIndicatorAction.setEnabled(haveXRangeDataIndicator);
        addYRangeDataIndicatorAction.setEnabled(true);
        removeYRangeDataIndicatorAction.setEnabled(haveYRangeDataIndicator);
        addXValueDataIndicatorAction.setEnabled(true);
        removeXValueDataIndicatorAction.setEnabled(haveXValueDataIndicator);
        addYValueDataIndicatorAction.setEnabled(true);
        removeYValueDataIndicatorAction.setEnabled(haveYValueDataIndicator);
        addDataWindowIndicatorAction.setEnabled(true);
        removeDataWindowIndicatorAction.setEnabled(haveDataWindowIndicator);
    }

    /**
     * Update the list of available rule customizers.
     */
    private void updateRulesMenu()
    {
      fillRulesMenu(rulesMenu);
    }

    /**
     * Fill the rules menu with the customizable rules.
     */
    private void fillRulesMenu(final JMenu menu)
    {
        // or each selector of a customizable rule, create a menu entry

        IlvChartCSSAdapter adapter = customizer.getAdapter();
        String[] ruleSelectors = adapter.getCustomizableRuleSelectors();

        menu.removeAll();
        ButtonGroup group = new ButtonGroup();
        for (int i = 0; i < ruleSelectors.length; i++) {
            final String selector = ruleSelectors[i];
            Action action = new AbstractAction(niceName(selector)) {
                Override
                public void actionPerformed(ActionEvent e) {
                  setRule(selector);
                }
          };
          JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(action);
          if (selector.equals(currentSelector))
            menuItem.setSelected(true);
          group.add(menuItem);
          menu.add(menuItem);
        }

        menu.validate();
    }

    final void setRule(String selector)
    {
      try {
        currentSelector = selector;
        customizer.setRule(selector);
        
        // When changing the target of the customizer, the undo
        // stack needs to be cleaned
        undoManager.discardAllEdits();
        updateUndoRedoMenuItems();
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }

    /**
     * Generate a nicer name for the menu items of the rules.
     */
    private String niceName(String selector)
    {
        if ("chartGrid[axisIndex=\"-1\"]".equals(selector))
            return "X Grid";
        if ("chartGrid[axisIndex=\"0\"]".equals(selector))
            return "Y Grid";
        if ("chartScale[axisIndex=\"-1\"]".equals(selector))
            return "X Scale";
        if ("chartScale[axisIndex=\"0\"]".equals(selector))
            return "Y Scale";
        if (selector.startsWith("series[name=\""))
            return "series " + selector.substring(13,selector.length()-2);
        return selector;
    }
}