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

/**
 * This examples shows illustrates how to use various
 * accessibility features inside a Rogue Wave JViews Diagrammer.
 */ 
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.diagrammer.IlvDiagrammerProduct;
import ilog.views.diagrammer.application.IlvDiagrammerAction;
import ilog.views.diagrammer.application.IlvDiagrammerApplication;
import ilog.views.diagrammer.application.IlvDiagrammerMenuBar;
import ilog.views.diagrammer.application.IlvDiagrammerMenu;
import ilog.views.diagrammer.application.IlvDiagrammerToolBar;
import ilog.views.diagrammer.project.IlvDiagrammerProject;
import ilog.views.sdm.IlvSDMEngine;
import ilog.views.sdm.IlvSDMView;
import ilog.views.sdm.renderer.IlvRendererUtil;
import ilog.views.util.swing.IlvSwingUtil;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.swing.IlvPopupMenuManager;
import ilog.views.swing.IlvPopupMenuContext;
import ilog.views.swing.IlvSimplePopupMenu;
import ilog.views.IlvAccelerator;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvRect;
import ilog.views.accelerator.*;
import ilog.views.util.internal.IlvSplash;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.net.URL;
import java.util.Comparator;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

/**
 * This is a very simple applet/application that shows how to use various
 * accessibility features inside an IlvDiagrammer.
 */
public class AccessibleDiagram extends IlvDiagrammerApplication
{
  static {
    // This applet is designed to run only with default resource bundle
    // and various selected other resource bundles.
    // Setting the available resource suffixes avoids that the applet
    // tries to load resource bundles for other locales over the net,
    // even if the current locale of the browser is different.
    if (IlvResourceUtil.isInApplet())
      IlvResourceUtil.setAvailableResourceSuffixes("", "_ja");
  }

  private ResourceBundle bundle;
  private URL backgroundStyleSheet;
  private URL normalBackgroundStyleSheet;
  private URL highContrastBackgroundStyleSheet;
  private URL varyShapesStyleSheet;
  private URL varyShapesOnStyleSheet;
  private URL varyShapesOffStyleSheet;

  /**
   * The color modes.
   */
  private static int NORMAL               = 0;
  private static int GRAYSCALE            = 1;
  private static int DEUTAN               = 2;

  private int colorMode = NORMAL;
  private boolean blockColorModeChange = false;

  private JMenu colorModeMenu;

  /**
   * Varying shapes modes.
   */
  private boolean varyingShapes = false;

  /**
   * High contrast mode.
   */
  private boolean highContrastMode = false;

  /**
   * High contrast colors, for the GUI only.
   */
  private Color lightColor = new Color(238,238,238);
  private Color darkColor =  new Color(51,51,51);

  /**
   * Fonts for normal contrast.
   */
  private HashMap normalContrastFonts = new HashMap();

  /**
   * Fonts for high contrast.
   */
  private HashMap highContrastFonts = new HashMap();

  /**
   * The popup menu.
   */
  private JPopupMenu popupMenu = null;

  /**
   * The about dialog.
   */
  private JDialog aboutDialog = null;

  /**
   * The label showing the keypress.
   */
  private JLabel statusLabel = new JLabel("No focus on view");

  /**
   * Comparator for the traversal order:
   * nesting has highest, then x has priority
   */
  Comparator<IlvGraphic> nestedThenXThenY = new Comparator<IlvGraphic>() {
    public int compare(IlvGraphic a, IlvGraphic b) {
      IlvGrapher commonBag = IlvGrapher.getLowestCommonGrapher(a, b);
      if (commonBag != null) {
        while (a.getGraphicBag() != commonBag)
          a = (IlvGraphic)a.getGraphicBag();
        while (b.getGraphicBag() != commonBag)
          b = (IlvGraphic)b.getGraphicBag();
      }
      // use model coordinates, not graphic coordinates to have a
      // predictive behavior
      IlvSDMEngine engine = IlvSDMEngine.getSDMEngine(a);
      if (IlvSDMEngine.getSDMEngine(b) != engine) return 0;
      Object ma = engine.getObject(a);
      Object mb = engine.getObject(b);
      if (ma == mb) return 0;
      if (ma == null) return -1;
      if (mb == null) return 1;

      // does not work when coordinates are not stored in the model
      // or when coordinates are stored as latitude/logitude
      float ax = IlvRendererUtil.getGraphicPropertyAsFloat(engine, ma, "x",
                                                           null, -1);
      float bx = IlvRendererUtil.getGraphicPropertyAsFloat(engine, mb, "x",
                                                           null, -1);

      if (ax < bx) return -1;
      if (ax > bx) return 1;

      float ay = IlvRendererUtil.getGraphicPropertyAsFloat(engine, ma, "y",
                                                           null, -1);
      float by = IlvRendererUtil.getGraphicPropertyAsFloat(engine, mb, "y",
                                                           null, -1);

      if (ay < by) return -1;
      if (ay > by) return 1;
      return 0;
    }
  };

  
  public static void main(final String[] args)
  {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        new AccessibleDiagram().init(args);        
      }
    });
  }
  
  public AccessibleDiagram()
  {
    super(new String[] {
      "-simple"
    });
  }

  /**
   * This method is overridden to change the size of the frame.
   */ 
  public JFrame createFrame()
  {
    JFrame frame = super.createFrame();
    frame.setSize(800, 400);
    return frame;
  }

  /**
   * Initializes the GUI.
   * @param contentPane The container of the application or applet.
   */ 
  public void init(Container contentPane)
  {
    // This sample uses JViews Diagrammer features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Rogue Wave JViews Diagrammer Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(
        IlvProductUtil.JViews_Diagrammer_Deployment);

    bundle = ResourceBundle.getBundle("accessibledemo");

    try {
      URL documentBase;

      if(isApplet()){
        documentBase = getDocumentBase();
      } else {
        documentBase = new URL("file:./");
      }

      normalBackgroundStyleSheet = new URL(documentBase, "data/background.css");
      highContrastBackgroundStyleSheet = new URL(documentBase, "data/backgroundHC.css");
      varyShapesOnStyleSheet = new URL(documentBase, "data/varyshapeon.css");
      varyShapesOffStyleSheet = new URL(documentBase, "data/varyshapeoff.css");
    } catch (Exception ex) {
      ex.printStackTrace();
    }

    super.init(contentPane);

    // remove the items that we don't need
    IlvDiagrammerMenuBar menuBar = getMenuBar();
    int idx = menuBar.getFileMenu().indexOf(IlvDiagrammerAction.printWithDialog);
    if (idx > 0) {
      try {
        idx--;
        // separator
        menuBar.getFileMenu().remove(idx);
        // the printWithDiaglog
        menuBar.getFileMenu().remove(idx);
      } catch (Exception ex) {
      }
    }

    // change the about dialog
    IlvDiagrammerMenu helpMenu = menuBar.getDiagrammerHelpMenu();

    final IlvDiagrammerAction aboutAction =
      new IlvDiagrammerAction("Diagrammer.Action.About") {

      protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
        return true;
      }
      public void perform(ActionEvent e, IlvDiagrammer diagrammer) throws Exception {
        if (aboutDialog == null) return;
        aboutDialog.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
        aboutDialog.setVisible(true);
      }

    };
    helpMenu.insertAction(aboutAction, 0);
    try {
      helpMenu.remove(1);
    } catch (Exception ex) {
    }

    // remove the properties and tree view
    JToolBar viewToolBar = getViewToolBar();
    try {
      viewToolBar.remove(8);
      viewToolBar.remove(7);
    } catch (Exception ex) {
    }

    // remove the properties and tree view
    IlvDiagrammerMenu viewMenu = menuBar.getViewMenu();
    try {
      viewMenu.remove(6);
      viewMenu.remove(5);
    } catch (Exception ex) {
    }

    // enhance the view menu with the high contrast mode 
    highContrastMode = IlvSwingUtil.isHighContrastMode();
    final IlvDiagrammerAction.ToggleAction hcAction =
      new IlvDiagrammerAction.ToggleAction("AccessibleDiagram.HighContrast", bundle) {

      protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
        return true;
      }
      protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception {
        return highContrastMode;
      }
      protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception {
        if (selected) {
          setHighContrast(true);
        } else {
          setHighContrast(false);
        }
      }
    };
    viewMenu.insertAction(hcAction, 0);
    viewMenu.insertSeparator(1);

    final IlvDiagrammerAction.ToggleAction vsAction =
      new IlvDiagrammerAction.ToggleAction("AccessibleDiagram.VaryShapes", bundle) {

      protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
        return true;
      }
      protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception {
        return varyingShapes;
      }
      protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception {
        setVaryShapesCSS(selected);
        varyingShapes = selected;
      }
    };
    viewMenu.insertAction(vsAction, 2);

    // submenu for the drawing mode
    colorModeMenu = new JMenu(getString("AccessibleDiagram.ColorMode.Name"));
    colorModeMenu.setOpaque(true);
    final JCheckBoxMenuItem[] colorModeChoices = new JCheckBoxMenuItem[3];

    colorModeChoices[0] = new JCheckBoxMenuItem(
                            getString("AccessibleDiagram.ColorMode.Normal"));
    colorModeChoices[1] = new JCheckBoxMenuItem(
                            getString("AccessibleDiagram.ColorMode.Grayscale"));
    colorModeChoices[2] = new JCheckBoxMenuItem(
                            getString("AccessibleDiagram.ColorMode.Deutan"));

    ActionListener action = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        if (blockColorModeChange) return;
        blockColorModeChange = true;
        try {
          Object src = evt.getSource();
          for (int i = 0; i < colorModeChoices.length; i++) {
            if (src == colorModeChoices[i])
              colorMode = i;
            colorModeChoices[i].setSelected(false);
          }
          colorModeChoices[colorMode].setSelected(true);
        } finally {
          blockColorModeChange = false;
        }
        getCurrentDiagrammer().getView().repaint();
      }
    };

    colorModeChoices[0].setSelected(true);
    for (int i = 0; i < colorModeChoices.length; i++) {
      colorModeChoices[i].addActionListener(action);
      colorModeMenu.add(colorModeChoices[i]);
    }
    viewMenu.add(colorModeMenu, 3);
    viewMenu.insertSeparator(4);

    // add the status label at the right side of the menu bar
    menuBar.add(Box.createHorizontalGlue());
    menuBar.add(statusLabel);

    // exchange the grapher with one that notifies the status label 
    IlvDiagrammer diagrammer = getCurrentDiagrammer();
    diagrammer.getEngine().setGrapher(new IlvGrapher() {
       protected boolean handleAccelerators(AWTEvent evt, IlvManagerView view) {
         if (evt instanceof KeyEvent) {
           KeyEvent kev = (KeyEvent)evt;
           if (kev.getID() == KeyEvent.KEY_PRESSED) {
             statusLabel.setText(getKeyEventLabel(kev));
           }
         }
         return super.handleAccelerators(evt, view);
       }
    });
    diagrammer.getView().setManager(diagrammer.getEngine().getGrapher());

    // set the manager view to revieve TAB events
    IlvManagerView mgrview = diagrammer.getView();
    mgrview.setFocusable(true);
    mgrview.setFocusCycleRoot(true);
    mgrview.setFocusTraversalKeysEnabled(false);
    mgrview.addFocusListener(new FocusListener() {
       public void focusGained(FocusEvent e) {
         statusLabel.setText("Key:0x----(-)---------");
       }
       public void focusLost(FocusEvent e) {
         statusLabel.setText("No focus on view");
       }
    });

    // install the accelerators
    installAccelerators(diagrammer.getEngine().getGrapher());

    // install the popup menu
    // register view view to the popup manager
    IlvPopupMenuManager.registerView(diagrammer.getView());

    // register the menu
    IlvPopupMenuManager.registerMenu("MENU", popupMenu = createPopupMenu());

    // allocate the about dialog
    aboutDialog = IlvSplash.createSplashDialog(diagrammer,
                                 null, null, null,
                                 "Rogue Wave JViews Diagrammer Demo",
                                 "",
                                 IlvDiagrammerProduct.getVersion(),
                                 IlvDiagrammerProduct.getMinorVersion(),
                                 IlvDiagrammerProduct.getSubMinorVersion(),
                                 IlvDiagrammerProduct.getPatchLevel(),
                                 IlvDiagrammerProduct.getBuildNumber(),
                                 IlvDiagrammerProduct.getReleaseDate());

    // check whether we started with high contrast node
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    Boolean highContrast = (Boolean)toolkit.getDesktopProperty("win.highContrast.on");
    if (highContrast != null && highContrast.booleanValue()) {
      setHighContrast(true);
    }
  }
  
  /**
   * Loads the first diagram.
   */ 
  protected void ready()
  {
    super.ready();
    loadDiagram();
    // fit to the initial part
    IlvSDMView view = getCurrentDiagrammer().getView();
    view.fitTransformerToArea(
              null,
              new IlvManagerView.FitAreaCalculator() {
                public IlvRect getAreaToFit(IlvManagerView view) {
                  return new IlvRect(-420,100,200,200);
                }
              },
              1,
              true);
  }

  /**
   * Loads a diagram.
   * @param project The diagram project to load.
   */ 
  private void loadDiagram()
  {
    try {
      URL documentBase;

      if(isApplet()){
        documentBase = getDocumentBase();
      } else {
        documentBase = new URL("file:./");
      }

      URL url = new URL(documentBase, "data/accsample.idpr");
      IlvDiagrammerProject project = new IlvDiagrammerProject(url);
      getCurrentDiagrammer().setProject(project, true);

      // add the style sheet for baclground
      backgroundStyleSheet = normalBackgroundStyleSheet;
      varyShapesStyleSheet = varyShapesOffStyleSheet;
      getCurrentDiagrammer().addStyleSheet(backgroundStyleSheet);
      getCurrentDiagrammer().addStyleSheet(varyShapesStyleSheet);
    } catch (Exception e1) {
      e1.printStackTrace();
    }

    setHighContrastCSS(getCurrentDiagrammer(), highContrastMode);
    setVaryShapesCSS(varyingShapes);
  }

  /**
   * Sets the high contrast code.
   */
  private void setHighContrast(boolean on)
  {
    if (on == highContrastMode) return;
    highContrastMode = on;
    setHighContrastCSS(getCurrentDiagrammer(), on);
    Container c = getCurrentDiagrammer();
    while (c.getParent() != null)
     c = c.getParent();
    setHighContrast(c, on);
    if (popupMenu != null) {
      setHighContrast(popupMenu, on);
    }
    if (aboutDialog != null) {
      setHighContrast(aboutDialog, on);
    }
    setHighContrast(getOverview(), on);
  }

  /**
   * Sets high contrast on a hierarchy of Swing components.
   * This is usually not needed, if the high contrast mode was set at
   * the operating level already when starting this demo. We do this code
   * in this demo only to illustrate and to be able to switch the high
   * contrast mode dynamically on the fly.
   */
  void setHighContrast(Container c, boolean on)
  {
    c.setForeground(on ? lightColor : darkColor);
    c.setBackground(on ? darkColor : lightColor);
    adaptFont(c, on);

    if (c instanceof JMenu) {
      Component[] cs = ((JMenu)c).getMenuComponents();
      if (cs == null) return;
      for (int i = 0; i < cs.length; i++) {
        if (cs[i] instanceof Container)
          setHighContrast((Container)cs[i], on);
      }
    } else {
      Component[] cs = c.getComponents();
      if (cs == null) return;
      for (int i = 0; i < cs.length; i++) {
        if (cs[i] instanceof Container)
          setHighContrast((Container)cs[i], on);
      }
    }
  }

  /**
   * Adapt the font to high contrast on a component.
   */
  void adaptFont(Component c, boolean highContrast)
  {
    if (c.isFontSet()) {
      Font oldFont = c.getFont();
      Font newFont = getAdapted(oldFont, c, highContrast);
      c.setFont(newFont);
    }
  }

  /**
   * Returns the high or low contrast font.
   */
  Font getAdapted(Font oldFont, Object key, boolean highContrast)
  {
    Font newFont = null;
    if (highContrast) {
      if (normalContrastFonts.get(key) == null)
        normalContrastFonts.put(key, oldFont);
      newFont = (Font)highContrastFonts.get(key);
      if (newFont == null) {
        newFont = new Font(oldFont.getName(),
                           oldFont.getStyle(),
                           (int)(oldFont.getSize() * 1.2));
        highContrastFonts.put(key, newFont);
      }
    } else {
      if (highContrastFonts.get(key) == null)
        highContrastFonts.put(key, oldFont);
      newFont = (Font)normalContrastFonts.get(key);
      if (newFont == null) {
        newFont = new Font(oldFont.getName(),
                           oldFont.getStyle(),
                           (int)(oldFont.getSize() / 1.2));
        normalContrastFonts.put(key, newFont);
      }
    }
    return newFont;
  }

  /**
   * Sets the CSS for high contrast.
   */
  private void setHighContrastCSS(IlvDiagrammer diagrammer, boolean on)
  {
    String theme = on ? IlvSDMEngine.HIGH_CONTRAST 
                      : IlvSDMEngine.NORMAL_CONTRAST;
    diagrammer.getEngine().setContrastAccessibilityTheme(theme);
    try {
      diagrammer.removeStyleSheet(backgroundStyleSheet);
      backgroundStyleSheet = on ? highContrastBackgroundStyleSheet
                                : normalBackgroundStyleSheet;
      diagrammer.addStyleSheet(backgroundStyleSheet);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  /**
   * Sets the CSS for varying shapes.
   */
  private void setVaryShapesCSS(boolean on)
  {
    IlvDiagrammer diagrammer = getCurrentDiagrammer();
    try {
      diagrammer.removeStyleSheet(varyShapesStyleSheet);
      varyShapesStyleSheet = on ? varyShapesOnStyleSheet
                                : varyShapesOffStyleSheet;
      diagrammer.addStyleSheet(varyShapesStyleSheet);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  /**
   * Install all accelerators.
   */
  private void installAccelerators(IlvGrapher grapher)
  {
    // F1                -> popup menu
    // P                 -> popup menu
    // TAB               -> select forward
    // Shift TAB         -> select backward
    // F                 -> select forward
    // B                 -> select backward
    // A                 -> select all
    // Ctrl A            -> deselect all
    // Ctrl 0            -> Identity zoom
    // Ctrl F            -> Fit zoom
    // Ctrl -            -> Zoom out
    // Ctrl +            -> Zoom in
    // Ctrl R            -> Rotate view
    // Arrow up          -> Scroll up
    // Arrow down        -> Scroll down
    // Arrow left        -> Scroll left
    // Arrow right       -> Scroll right
    // Shift Arrow up    -> Move selection up
    // Shift Arrow down  -> Move selection down
    // Shift Arrow left  -> Move selection left
    // Shift Arrow right -> Move selection right
    // Delete key        -> Delete selection
    // E                 -> Expand selection

    IlvPopupMenuAccelerator popupAcc;
    IlvCycleSelectAccelerator selAcc;
    IlvMoveSelectionAccelerator movAcc;
    IlvSelectAllAccelerator selAllAcc;
    IlvAccelerator acc;

    // just for shorter writing
    int keypress = KeyEvent.KEY_PRESSED;
    int keyrelease = KeyEvent.KEY_RELEASED;
    int shift = KeyEvent.SHIFT_MASK;
    int ctrl  = KeyEvent.CTRL_MASK;
    int ctsht = KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK;
    char unspecCode = KeyEvent.VK_UNDEFINED;

    // popup menu
    popupAcc = new IlvPopupMenuAccelerator(keypress, KeyEvent.VK_F1, 0, true);
    grapher.addAccelerator(popupAcc);
    popupAcc = new IlvPopupMenuAccelerator(keypress, unspecCode, 'p', 0, true);
    grapher.addAccelerator(popupAcc);
    popupAcc = new IlvPopupMenuAccelerator(keypress, unspecCode, 'P', shift, true);
    grapher.addAccelerator(popupAcc);

    // select all
    selAllAcc = new IlvSelectAllAccelerator(keypress, unspecCode, 'a', 0, true);
    selAllAcc.setTraverse(true);
    grapher.addAccelerator(selAllAcc);
    selAllAcc = new IlvSelectAllAccelerator(keypress, unspecCode, 'A', shift, true);
    selAllAcc.setTraverse(true);
    grapher.addAccelerator(selAllAcc);
    selAllAcc = new IlvSelectAllAccelerator(keypress, KeyEvent.VK_A, ctrl, true);
    selAllAcc.setTraverse(true);
    selAllAcc.setSelectAll(false);
    grapher.addAccelerator(selAllAcc);

    // select
    selAcc = new IlvCycleSelectAccelerator(keypress, KeyEvent.VK_TAB, 0, true);
    selAcc.setForward(true);
    selAcc.setComparator(nestedThenXThenY);
    grapher.addAccelerator(selAcc);
    selAcc = new IlvCycleSelectAccelerator(keypress, unspecCode, 'f', 0, true);
    selAcc.setForward(true);
    selAcc.setComparator(nestedThenXThenY);
    grapher.addAccelerator(selAcc);
    selAcc = new IlvCycleSelectAccelerator(keypress, unspecCode, 'F', shift, true);
    selAcc.setForward(true);
    selAcc.setComparator(nestedThenXThenY);
    grapher.addAccelerator(selAcc);
    selAcc = new IlvCycleSelectAccelerator(keypress, KeyEvent.VK_TAB, shift, true);
    selAcc.setForward(false);
    selAcc.setComparator(nestedThenXThenY);
    grapher.addAccelerator(selAcc);
    selAcc = new IlvCycleSelectAccelerator(keypress, unspecCode, 'b', 0, true);
    selAcc.setForward(false);
    selAcc.setComparator(nestedThenXThenY);
    grapher.addAccelerator(selAcc);
    selAcc = new IlvCycleSelectAccelerator(keypress, unspecCode, 'B', shift, true);
    selAcc.setForward(false);
    selAcc.setComparator(nestedThenXThenY);
    grapher.addAccelerator(selAcc);

    acc = new IlvDeleteSelectionAccelerator(keypress, KeyEvent.VK_DELETE, 0, true) {
        protected boolean handleEvent(IlvManagerView v) {
          // super.handleEvents deals only with the graphic objects, but
          // we also need to delete the model objects
          if (v instanceof IlvSDMView) {
            IlvSDMEngine engine = ((IlvSDMView)v).getSDMEngine();
            // delete the selected objects
            engine.delete();
          }
          // in case there are graphics that don't correspond to model objects
          return super.handleEvent(v);
        }
    };
    grapher.addAccelerator(acc);

    acc = new IlvIdentityAccelerator(keypress, KeyEvent.VK_HOME, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvIdentityAccelerator(keypress, '0', ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvIdentityAccelerator(keypress, KeyEvent.VK_NUMPAD0, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvIdentityAccelerator(keypress, KeyEvent.VK_INSERT, ctrl, true);
    grapher.addAccelerator(acc);

    acc = new IlvZoomOutAccelerator(keypress, KeyEvent.VK_SUBTRACT, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvZoomOutAccelerator(keypress, unspecCode, '-', ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvZoomInAccelerator(keypress, KeyEvent.VK_ADD, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvZoomInAccelerator(keypress, unspecCode, '+', ctrl, true);
    grapher.addAccelerator(acc);

    acc = new IlvFitToSizeAccelerator(keypress, KeyEvent.VK_NUMPAD5, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvFitToSizeAccelerator(keypress, KeyEvent.VK_BEGIN, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvFitToSizeAccelerator(keypress, KeyEvent.VK_F, ctrl, true);
    grapher.addAccelerator(acc);

    acc = new IlvRotateAccelerator(keypress, KeyEvent.VK_R, ctrl, true);
    grapher.addAccelerator(acc);

    // scroll with normal arrows
    acc = new IlvScrollUpAccelerator(keypress, KeyEvent.VK_UP, 0, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollDownAccelerator(keypress, KeyEvent.VK_DOWN, 0, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollRightAccelerator(keypress, KeyEvent.VK_RIGHT, 0, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollLeftAccelerator(keypress, KeyEvent.VK_LEFT, 0, true);
    grapher.addAccelerator(acc);
    // scroll with numpad 
    acc = new IlvScrollUpAccelerator(keypress, KeyEvent.VK_NUMPAD8, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollDownAccelerator(keypress, KeyEvent.VK_NUMPAD2, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollLeftAccelerator(keypress, KeyEvent.VK_NUMPAD4, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollRightAccelerator(keypress, KeyEvent.VK_NUMPAD6, ctrl, true);
    grapher.addAccelerator(acc);
    // scroll with arrows on numpad 
    acc = new IlvScrollUpAccelerator(keypress, KeyEvent.VK_KP_UP, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollDownAccelerator(keypress, KeyEvent.VK_KP_DOWN, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollLeftAccelerator(keypress, KeyEvent.VK_KP_LEFT, ctrl, true);
    grapher.addAccelerator(acc);
    acc = new IlvScrollRightAccelerator(keypress, KeyEvent.VK_KP_RIGHT, ctrl, true);
    grapher.addAccelerator(acc);

    // move with normal arrows
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_UP, shift, true);
    movAcc.setMoveVector(0, -10);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_DOWN, shift, true);
    movAcc.setMoveVector(0, 10);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_LEFT, shift, true);
    movAcc.setMoveVector(-10, 0);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_RIGHT, shift, true);
    movAcc.setMoveVector(10, 0);
    grapher.addAccelerator(movAcc);
    // move with numpad
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_NUMPAD8, shift, true);
    movAcc.setMoveVector(0, -5);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_NUMPAD2, shift, true);
    movAcc.setMoveVector(0, 5);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_NUMPAD4, shift, true);
    movAcc.setMoveVector(-5, 0);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_NUMPAD6, shift, true);
    movAcc.setMoveVector(5, 0);
    grapher.addAccelerator(movAcc);
    // move with arrows on numpad 
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_KP_UP, shift, true);
    movAcc.setMoveVector(0, -3);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_KP_DOWN, shift, true);
    movAcc.setMoveVector(0, 3);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_KP_LEFT, shift, true);
    movAcc.setMoveVector(-3, 0);
    grapher.addAccelerator(movAcc);
    movAcc = new IlvMoveSelectionAccelerator(keypress, KeyEvent.VK_KP_RIGHT, shift, true);
    movAcc.setMoveVector(3, 0);
    grapher.addAccelerator(movAcc);

    // expand selection
    acc = new IlvExpandSelectionAccelerator(keypress, KeyEvent.VK_E, 0, true);
    grapher.addAccelerator(acc);
    acc = new IlvExpandSelectionAccelerator(keypress, KeyEvent.VK_E, shift, true);
    grapher.addAccelerator(acc);
  }

  /**
   * Returns the indicator of a key event.
   */
  private String getKeyEventLabel(KeyEvent ev)
  {
    String digits = "0123456789ABCDEF";
    StringBuffer buf = new StringBuffer("Key:0x");
    int code = ev.getKeyCode();

    code = code & 0xffff;
    int d = code / 4096;
    buf.append(digits.charAt(d));
    code -= d * 4096;
    d = code / 256;
    buf.append(digits.charAt(d));
    code -= d * 256;
    d = code / 16;
    buf.append(digits.charAt(d));
    code -= d * 16;
    buf.append(digits.charAt(code));
    buf.append('(');
    if (ev.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
      // nothing
    } else if (Character.isISOControl(ev.getKeyChar())) {
      code = (int)ev.getKeyChar();
      code = code & 0xff;
      d = code / 16;
      buf.append(digits.charAt(d));
      code -= d * 16;
      buf.append(digits.charAt(code));
    } else {
      buf.append(ev.getKeyChar());
    }
    buf.append(')');

    int mod = ev.getModifiers();
    boolean shift = ((mod & InputEvent.SHIFT_MASK) != 0);
    boolean ctrl  = ((mod & InputEvent.CTRL_MASK) != 0);
    boolean alt   = ((mod & InputEvent.ALT_MASK) != 0);
    boolean meta  = ((mod & InputEvent.META_MASK) != 0);
    boolean altgr = ((mod & InputEvent.ALT_GRAPH_MASK) != 0);

    int count = 0;
    if (shift) count++;
    if (ctrl)  count++;
    if (alt)   count++;
    if (meta)  count++;
    if (altgr) count++;

    if (shift) {
      if (count > 4) buf.append("S");
      else if (count >= 3) buf.append("Sh");
      else if (count == 2) buf.append("Shft");
      else buf.append("Shift");
    }
    if (ctrl) {
      if (count > 4) buf.append("C");
      else if (count >= 3) buf.append("Ct");
      else if (count == 2) buf.append("Ctrl");
      else buf.append("Control");
    }
    if (alt) {
      if (count > 4) buf.append("A");
      else if (count >= 3) buf.append("Al");
      else buf.append("Alt");
    }
    if (meta) {
      if (count > 4) buf.append("M");
      else if (count >= 3) buf.append("Me");
      else buf.append("Meta");
    }
    if (altgr) {
      if (count > 4) buf.append("a");
      else if (count >= 3) buf.append("Ag");
      else if (count == 2) buf.append("Algr");
      else buf.append("Altgraph");
    }

    if (ev.isConsumed())
      buf.append("*");
    else
      buf.append(" ");

    return buf.toString();
  }

  /**
   * Creates the popup menu.
   */
  private JPopupMenu createPopupMenu()
  {
    // create the action listener for the popup menu
    ActionListener actionListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {

        // retrieve the selected menu item
        JMenuItem m = (JMenuItem)e.getSource();

        // retrieve the graphic that has this popup menu
        IlvPopupMenuContext context = IlvPopupMenuManager.getPopupMenuContext(m);
        IlvGraphic graphic = context.getGraphic();

        // do the action
        if (m.getText().equals("Shift left")) {
          IlvRect bbox = graphic.boundingBox();
          bbox.x -= 20;
          IlvManager manager = (IlvManager)graphic.getGraphicBag();
          // this will also notify the model
          manager.moveObject(graphic, bbox.x, bbox.y, true);
        } else if (m.getText().equals("Shift right")) {
          IlvRect bbox = graphic.boundingBox();
          bbox.x += 20;
          IlvManager manager = (IlvManager)graphic.getGraphicBag();
          // this will also notify the model
          manager.moveObject(graphic, bbox.x, bbox.y, true);
        }
      }
    };

    // create a simple popup menu with the following items
    //   - Make larger:  makes the graphic larger
    //   - Make smaller: makes the graphic smaller
    IlvSimplePopupMenu menu =
      new IlvSimplePopupMenu(
              "Menu1",
              "Shift left | Shift right",
              null,
              actionListener);

    return menu;
  }

  /**
   * Creates a new diagrammer.
   */
  protected IlvDiagrammer createDiagrammer()
  {
    IlvSDMView view = new IlvSDMView() {
        protected Image createDoubleBufferImage(int width, int height) {
          return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        }
        protected void doubleBufferedImageUpToDate(Image doubleBufferImage) {
          super.doubleBufferedImageUpToDate(doubleBufferImage);
          adaptBuffer(doubleBufferImage);
        }
    };
    // enable double buffering to allow the colorMode GRAYSCALE...
    view.setDoubleBuffering(true);
    // keep aspect ratio
    view.setKeepingAspectRatio(true);
    return new IlvDiagrammer(view);
  }

  /**
   * Adapts the double buffer to the color mode.
   */
  private void adaptBuffer(Image image)
  {
    if (colorMode == GRAYSCALE) {
      BufferedImage buffer = (BufferedImage)image;
      WritableRaster raster = buffer.copyData(null);
      int mx = raster.getMinX();
      int my = raster.getMinY();
      int w = raster.getWidth();
      int h = raster.getHeight();
      int[] data = null;
      for (int x = mx; x < mx + w; x++) {
        for (int y = my; y < my + h; y++) {
          data = raster.getPixel(x, y, data);
          if (data[0] == data[1] && data[1] == data[2]) continue;
          int g = (int)(0.299 * data[0] + 0.587 * data[1] + 0.114 * data[2]);
          if (g < 0) g = 0;
          if (g > 255) g = 255;
          data[0] = data[1] = data[2] = g;
          raster.setPixel(x, y, data);
        }
      }
      buffer.setData(raster);
    }
    else if (colorMode == DEUTAN) {
      // There are many variants of red green blind. This is just one of them.
      BufferedImage buffer = (BufferedImage)image;
      WritableRaster raster = buffer.copyData(null);
      int mx = raster.getMinX();
      int my = raster.getMinY();
      int w = raster.getWidth();
      int h = raster.getHeight();
      int[] data = null;
      for (int x = mx; x < mx + w; x++) {
        for (int y = my; y < my + h; y++) {
          data = raster.getPixel(x, y, data);
          if (data[0] == data[1]) continue;
          int g = (int)(0.337 * data[0] + 0.663 * data[1]);
          if (g < 0) g = 0;
          if (g > 255) g = 255;
          data[0] = data[1] = g;
          raster.setPixel(x, y, data);
        }
      }
      buffer.setData(raster);
    }
  }

  /**
   * Returns a string from the resource bundle.
   */
  private String getString(String key)
  {
    try {
      return bundle.getString(key);
    } catch (Exception ex) {
      return key;
    }
  }
}