/* Copyright (c) 2002-2012 The University of the West Indies
 *
 * Contact: robert.lancashire@uwimona.edu.jm
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */

// CHANGES to 'JSVPanel.java'
// University of the West Indies, Mona Campus
//
// 25-06-2007 rjl - bug in ReversePlot for non-continuous spectra fixed
//                - previously, one point less than npoints was displayed
// 25-06-2007 cw  - show/hide/close modified
// 10-02-2009 cw  - adjust for non zero baseline in North South plots
// 24-08-2010 rjl - check coord output is not Internationalised and uses decimal point not comma
// 31-10-2010 rjl - bug fix for drawZoomBox suggested by Tim te Beek
// 01-11-2010 rjl - bug fix for drawZoomBox
// 05-11-2010 rjl - colour the drawZoomBox area suggested by Valery Tkachenko
// 23-07-2011 jak - Added feature to draw the x scale, y scale, x units and y units
//					independently of each other. Added independent controls for the font,
//					title font, title bold, and integral plot color.
// 24-09-2011 jak - Altered drawGraph to fix bug related to reversed highlights. Added code to
//					draw integration ratio annotations
// 03-06-2012 rmh - Full overhaul; code simplification; added support for Jcamp 6 nD spectra

package jspecview.common;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jmol.api.EventManager;
import org.jmol.api.GenericGraphics;
import org.jmol.awtjs.Event;
import org.jmol.util.Font;
import org.jmol.util.Logger;

import javajs.api.GenericColor;
import javajs.util.CU;
import javajs.util.Lst;
import jspecview.api.AnnotationData;
import jspecview.api.JSVPanel;
import jspecview.api.PanelListener;
import jspecview.api.VisibleInterface;
import jspecview.common.Annotation.AType;
import jspecview.common.Spectrum.IRMode;
import jspecview.dialog.JSVDialog;

/**
 * JSVPanel class draws a plot from the data contained a instance of one or more
 * <code>GraphSet</code> objects.
 * 
 * A graph set includes one or more spectra of the same type -- 13C or 1H, for
 * example. These are linked so that their expansions synchronize and, if linked
 * to a 2D spectrum, so that their cursors reflect the cross-hairs of the 2D
 * spectrum.
 * 
 * So there is a stack of graph sets.
 * 
 * The mouse is active only in one of these sets (PanelData.mouseSet), and
 * within that, a specific spectrum (GraphSet.iSpectrumMovedTo). Clicking on the
 * graph sets these as well
 * 
 * @author Debbie-Ann Facey
 * @author Khari A. Bryan
 * @author Craig A.D. Walters
 * @author Prof Robert J. Lancashire
 * @author Bob Hanson hansonr@stolaf.edu
 */

public class PanelData implements EventManager {

  public GenericGraphics g2d, g2d0;
  JSViewer vwr;

  public PanelData(JSVPanel panel, JSViewer viewer) {
    this.vwr = viewer;
    this.jsvp = panel;
    this.g2d = this.g2d0 = viewer.g2d;
    BLACK = g2d.getColor1(0);
    highlightColor = g2d.getColor4(255, 0, 0, 200);
    zoomBoxColor = g2d.getColor4(150, 150, 100, 130);
    zoomBoxColor2 = g2d.getColor4(150, 100, 100, 130);
  }

  // Critical fields

  private Lst<PanelListener> listeners = new Lst<PanelListener>();

  public void addListener(PanelListener listener) {
    if (!listeners.contains(listener)) {
      listeners.addLast(listener);
    }
  }

  private GraphSet currentGraphSet;

  public GraphSet getCurrentGraphSet() {
    return currentGraphSet;
  }

  public Hashtable<ScriptToken, Object> options = new Hashtable<ScriptToken, Object>();
  public JSVPanel jsvp;
  public Lst<GraphSet> graphSets;
  public int currentSplitPoint;
  public PlotWidget thisWidget;
  public Coordinate coordClicked;
  public Coordinate[] coordsClicked;

  public void dispose() {
    jsvp = null;
    for (int i = 0; i < graphSets.size(); i++)
      graphSets.get(i).dispose();
    graphSets = null;
    currentFont = null;
    currentGraphSet = null;
    coordClicked = null;
    coordsClicked = null;
    thisWidget = null;
    options = null;
    listeners = null;
  }

  // plot parameters

  public static final int defaultPrintHeight = 450, defaultPrintWidth = 280;
  public static final int topMargin = 40, bottomMargin = 50, leftMargin = 60,
      rightMargin = 50;

  private static final int PRINT_SCALE_FACTOR = 10;

  public boolean ctrlPressed;
  public boolean shiftPressed;
  public boolean drawXAxisLeftToRight;
  public boolean isIntegralDrag;
  public boolean xAxisLeftToRight = true;

  public int scalingFactor = 1; // will be 10 for printing
  public int integralShiftMode;
  public int left = leftMargin;
  public int right = rightMargin;

  public String coordStr = "";
  public String startupPinTip = "Click to set.";
  private String title;

  int clickCount;
  private int nSpectra;
  int thisWidth;
  private int thisHeight;
  private int startIndex, endIndex;

  private String commonFilePath;
  private String viewTitle;

  void setViewTitle(String title) {
    viewTitle = title;
  }

  String getViewTitle() {
    return (viewTitle == null ? getTitle() : viewTitle);
  }

  Map<String, Object> getInfo(boolean selectedOnly, String key) {
    Map<String, Object> info = new Hashtable<String, Object>();
    Lst<Map<String, Object>> sets = null;
    if (selectedOnly)
      return currentGraphSet.getInfo(key, getCurrentSpectrumIndex());
    Set<Entry<ScriptToken, Object>> entries = options.entrySet();
    if ("".equals(key)) {
      String val = "type title nSets ";
      for (Entry<ScriptToken, Object> entry : entries)
        val += entry.getKey().name() + " ";
      info.put("KEYS", val);
    } else {
      for (Entry<ScriptToken, Object> entry : entries)
        Parameters.putInfo(key, info, entry.getKey().name(), entry.getValue());
      Parameters.putInfo(key, info, "type", getSpectrumAt(0).getDataType());
      Parameters.putInfo(key, info, "title", title);
      Parameters.putInfo(key, info, "nSets", Integer.valueOf(graphSets.size()));
    }
    sets = new Lst<Map<String, Object>>();
    for (int i = graphSets.size(); --i >= 0;)
      sets.addLast(graphSets.get(i).getInfo(key, -1));
    info.put("sets", sets);
    return info;
  }

  void setBooleans(Parameters parameters, ScriptToken st) {
    if (st == null) {
      Map<ScriptToken, Boolean> booleans = parameters.getBooleans();
      for (Map.Entry<ScriptToken, Boolean> entry : booleans.entrySet())
        setBooleans(parameters, entry.getKey());
      return;
    }
    setBoolean(st, parameters.getBoolean(st));
  }

  @SuppressWarnings("incomplete-switch")
  public void setBoolean(ScriptToken st, boolean isTrue) {
    setTaintedAll();
    if (st == ScriptToken.REVERSEPLOT) {
      currentGraphSet.setReversePlot(isTrue);
      return;
    }
    options.put(st, Boolean.valueOf(isTrue));
    switch (st) {
    case DISPLAY1D:
    case DISPLAY2D:
      doReset = true;
      break;
    }
  }

  public boolean getBoolean(ScriptToken st) {
    if (st == ScriptToken.REVERSEPLOT)
      return currentGraphSet.reversePlot;
    if (options == null)
      return false;
    Object b = options.get(st);
    return (b != null && (b instanceof Boolean)
        && ((Boolean) b) == Boolean.TRUE);
  }

  // //////// settable colors and fonts //////////

  private String displayFontName;
  @SuppressWarnings("unused")
  private String titleFontName;

  @SuppressWarnings("incomplete-switch")
  private void setFontName(ScriptToken st, String fontName) {
    switch (st) {
    case DISPLAYFONTNAME:
      displayFontName = fontName;
      break;
    case TITLEFONTNAME:
      titleFontName = fontName;
      break;
    }
    if (fontName != null)
      options.put(st, fontName);
  }

  // ///////// print parameters ///////////

  public boolean isPrinting;
  boolean creatingImage;

  private boolean doReset = true;

  private String printingFontName;
  private String printGraphPosition = "default";
  boolean titleDrawn;
  boolean display1D;
  boolean isLinked;
  private String printJobTitle;

  public boolean getDisplay1D() {
    return display1D;
  }

  // //// initialization - from AwtPanel

  private Lst<Spectrum> spectra;
  private boolean taintedAll = true;
  private boolean testingJavaScript; // set TRUE to see taintedAll the way it will be in JavaScript

  public void setTaintedAll() {
    taintedAll = true;
  }

  public void initOne(Spectrum spectrum) {
    spectra = new Lst<Spectrum>();
    spectra.addLast(spectrum);
    initMany(spectra, 0, 0);
  }

  public enum LinkMode {

    ALL, NONE, AB, ABC;

    public static LinkMode getMode(String abc) {
      if (abc.equals("*"))
        return ALL;
      for (LinkMode mode : values())
        if (mode.name().equalsIgnoreCase(abc))
          return mode;
      return NONE;
    }
  }

  public void initMany(Lst<Spectrum> spectra, int startIndex, int endIndex) {
    this.startIndex = startIndex;
    this.endIndex = endIndex;
    nSpectra = spectra.size();
    this.spectra = spectra;
    commonFilePath = spectra.get(0).getFilePath();
    for (int i = 0; i < nSpectra; i++)
      if (!commonFilePath.equalsIgnoreCase(spectra.get(i).getFilePath())) {
        commonFilePath = null;
        break;
      }
    setGraphSets(LinkMode.NONE);
  }

  private void setGraphSets(LinkMode linkMode) {
    graphSets = GraphSet.createGraphSetsAndSetLinkMode(this, jsvp, spectra,
        startIndex, endIndex, linkMode);
    currentGraphSet = graphSets.get(0);
    setTitle(getSpectrum().getTitleLabel());
  }

  public PeakInfo findMatchingPeakInfo(PeakInfo pi) {
    PeakInfo pi2 = null;
    for (int i = 0; i < graphSets.size(); i++)
      if ((pi2 = graphSets.get(i).findMatchingPeakInfo(pi)) != null)
        break;
    return pi2;
  }

  void integrateAll(ColorParameters parameters) {
    for (int i = graphSets.size(); --i >= 0;)
      graphSets.get(i).integrate(-1, parameters);
  }

  /**
   * Returns the Number of Graph sets
   * 
   * @return the Number of graph sets
   */
  public int getNumberOfGraphSets() {
    return graphSets.size();
  }

  /**
   * Returns the title displayed on the graph
   * 
   * @return the title displayed on the graph
   */
  public String getTitle() {
    return title;
  }

  public void refresh() {
    doReset = true;
  }

  public void setTitle(String title) {
    this.title = title;
  }
  
  public void addAnnotation(Lst<String> tokens) {
    String title = currentGraphSet.addAnnotation(tokens, getTitle());
    if (title != null)
      setTitle(title);
  }

  public void addPeakHighlight(PeakInfo peakInfo) {
    for (int i = 0; i < graphSets.size(); i++)
      graphSets.get(i).addPeakHighlight(peakInfo);
  }

  public PeakInfo selectPeakByFileIndex(String filePath, String index,
                                        String atomKey) {
    PeakInfo pi = currentGraphSet.selectPeakByFileIndex(filePath, index,
        atomKey);
    if (pi == null)
      for (int i = graphSets.size(); --i >= 0;)
        if (graphSets.get(i) != currentGraphSet && (pi = graphSets.get(i)
            .selectPeakByFileIndex(filePath, index, atomKey)) != null)
          break;
    return pi;
  }

  public void setPlotColors(GenericColor[] colors) {
    for (int i = graphSets.size(); --i >= 0;)
      graphSets.get(i).setPlotColors(colors);
  }

  public void selectSpectrum(String filePath, String type, String model,
                             boolean andCurrent) {
    if (andCurrent)
      currentGraphSet.selectSpectrum(filePath, type, model);
    if ("ID".equals(type)) {
      jumpToSpectrumIndex(getCurrentSpectrumIndex(), true);
      return;
    }

    for (int i = 0; i < graphSets.size(); i++)
      if (graphSets.get(i) != currentGraphSet)
        graphSets.get(i).selectSpectrum(filePath, type, model);
  }

  public boolean hasFileLoaded(String filePath) {
    for (int i = graphSets.size(); --i >= 0;)
      if (graphSets.get(i).hasFileLoaded(filePath))
        return true;
    return false;
  }

  /**
   * Clears all views in the zoom list
   */
  public void clearAllView() {
    for (int i = graphSets.size(); --i >= 0;)
      graphSets.get(i).resetViewCompletely();//.clearViews();
  }

  /*----------------------- JSVPanel PAINTING METHODS ---------------------*/
  /**
   * Draws the Spectrum to the panel
   * 
   * @param gMain
   *        the main <code>Graphics</code> object
   * @param gFront
   *        the <code>Graphics</code> object for top-object writing
   * @param gRear
   * @param width
   *        the width to be drawn in pixels
   * @param height
   *        the height to be drawn in pixels
   * @param addFilePath
   * @param addBorder 
   */
  public synchronized void drawGraph(Object gMain, Object gFront, Object gRear,
                                     int width, int height,
                                     boolean addFilePath, boolean addBorder) {
    boolean withCoords;
    this.gMain = gMain;
    display1D = !isLinked && getBoolean(ScriptToken.DISPLAY1D);
    int top = topMargin;
    int bottom = bottomMargin;
    boolean isResized = (isPrinting || doReset || thisWidth != width
        || thisHeight != height);
    if (isResized)
      setTaintedAll();
    if (taintedAll)
      g2d.fillBackground(gRear, bgcolor);
    if (gFront != gMain) {
      g2d.fillBackground(gFront, null);
      if (gMain != gRear)
        g2d.fillBackground(gMain, null);
      g2d.setStrokeBold(gMain, false);
    }
    scalingFactor = 1;
    if (isPrinting) {
      scalingFactor = PRINT_SCALE_FACTOR; // for high resolution (zooming in within PDF)
      withCoords = false;
    } else if (addBorder) {
      bottom *= 2;
      withCoords = false;
    } else {
      withCoords = !creatingImage && getBoolean(ScriptToken.COORDINATESON);
      titleOn = getBoolean(ScriptToken.TITLEON);
      gridOn = getBoolean(ScriptToken.GRIDON);
      peakTabsOn = getBoolean(ScriptToken.PEAKTABSON);

    }
    boolean pointsOnly = getBoolean(ScriptToken.POINTSONLY);
    doReset = false;
    titleDrawn = false;
    unitsDrawn = false;
    thisWidth = width;
    thisHeight = height;

    for (int i = graphSets.size(); --i >= 0;) {
      graphSets.get(i).drawGraphSet(gMain, gFront, gRear, width, height, left,
          right, top, bottom, isResized, taintedAll, pointsOnly);
    }
    if (isPrinting || creatingImage)
      titleDrawn = true;

    if (isPrinting && titleOn && !titleDrawn && taintedAll) {
      drawTitle(gMain, (height + 20) * scalingFactor, width * scalingFactor,
          getDrawTitle(true, -1));
    }
    if (withCoords && coordStr != null)
      drawCoordinates(gFront, top, thisWidth - right, top - 20);
    if (addFilePath && taintedAll) {
      printFilePath(gMain, left, height,
          commonFilePath != null ? commonFilePath
              : graphSets.size() == 1 && currentGraphSet.getTitle(true) != null
                  ? getSpectrum().getFilePath()
                  : null);
    }
    if (isPrinting) {
      printVersion(gMain, height);
    }
    if (!testingJavaScript && (isPrinting || gMain == gFront))
      setTaintedAll();
    else
      taintedAll = false;
  }

  /**
   * @param g
   * @param top
   * @param x
   * @param y
   */
  public void drawCoordinates(Object g, int top, int x, int y) {
    g2d.setGraphicsColor(g, coordinatesColor);
    Font font = setFont(g, jsvp.getWidth(), Font.FONT_STYLE_BOLD, 14, true);
    g2d.drawString(g, coordStr, x - font.stringWidth(coordStr), y);
  }

  public Font setFont(Object g, int width, int style, float size,
                      boolean isLabel) {
    return g2d.setFont(g, getFont(g, width, style, size, isLabel));
  }

  public void printFilePath(Object g, int x, int y, String s) {
    if (s == null)
      return;
    x *= scalingFactor;
    y *= scalingFactor;
    if (s.indexOf("?") > 0)
      s = s.substring(s.indexOf("?") + 1);
    s = s.substring(s.lastIndexOf("/") + 1);
    s = s.substring(s.lastIndexOf("\\") + 1);
    g2d.setGraphicsColor(g, BLACK);
    Font font = setFont(g, 1000, Font.FONT_STYLE_PLAIN, 9, true);
    if (x != left * scalingFactor)
      x -= font.stringWidth(s);
    g2d.drawString(g, s, x, y - font.getHeight());
  }

  public void printVersion(Object g, int pageHeight) {
    g2d.setGraphicsColor(g, BLACK);
    Font font = setFont(g, 100, Font.FONT_STYLE_PLAIN, 12, true);
    String s = jsvp.getApiPlatform().getDateFormat(null) + " JSpecView "
        + JSVersion.VERSION_SHORT;
    int w = font.stringWidth(s);
    g2d.drawString(g, s, (thisWidth - right) * scalingFactor - w,
        pageHeight * scalingFactor + font.getHeight() * 3);
  }

  /**
   * Draws Title
   * 
   * @param g
   * 
   * @param y
   *        the height to be drawn in pixels -- after scaling
   * @param pageWidth
   *        the width to be drawn in pixels -- after scaling
   * @param title
   */
  public void drawTitle(Object g, int y, int pageWidth, String title) {
    Font font = getFont(g, pageWidth,
        isPrinting || getBoolean(ScriptToken.TITLEBOLDON) ? Font.FONT_STYLE_BOLD
            : Font.FONT_STYLE_PLAIN,
        14, true);
    int nPixels = font.stringWidth(title);
    if (nPixels > pageWidth * 0.5) {
      int size = (int) (14.0 * pageWidth * 0.5 / nPixels);
      if (size < 10)
        size = 10;
      font = getFont(g, pageWidth,
          isPrinting || getBoolean(ScriptToken.TITLEBOLDON)
              ? Font.FONT_STYLE_BOLD
              : Font.FONT_STYLE_PLAIN,
          size, true);
    }
    g2d.setGraphicsColor(g, titleColor);
    setCurrentFont(g2d.setFont(g, font));
    y -= (int) (font.getHeight() * (isPrinting ? -0.2 : creatingImage ? 1.2 : 0.4));
    g2d.drawString(g, title, (isPrinting ? left * scalingFactor : 5), y);
  }

  private Font currentFont;

  private void setCurrentFont(Font font) {
    currentFont = font;
  }

  int getFontHeight() {
    return currentFont.getAscent();
  }

  int getStringWidth(String s) {
    return currentFont.stringWidth(s);
  }

  /**
   * sets bsSelected to the specified pointer from "select 3.1*1"
   * 
   * @param iSpec
   */
  public void selectFromEntireSet(int iSpec) {
    // note that iSpec is over the entire set
    for (int i = 0, pt = 0; i < graphSets.size(); i++) {
      if (iSpec == Integer.MIN_VALUE) {
        graphSets.get(i).setSelected(-1);
        continue;
      }
      Lst<Spectrum> specs = graphSets.get(i).spectra;
      for (int j = 0; j < specs.size(); j++, pt++)
        if (iSpec < 0 || iSpec == pt)
          graphSets.get(i).setSelected(j);
    }
  }

  public void addToList(int iSpec, Lst<Spectrum> list) {
    for (int i = 0; i < spectra.size(); i++)
      if (iSpec < 0 || i == iSpec)
        list.addLast(spectra.get(i));
  }

  public void scaleSelectedBy(double f) {
    for (int i = graphSets.size(); --i >= 0;)
      graphSets.get(i).scaleSelectedBy(f);

  }

  // //// currentGraphSet methods

  boolean setCurrentGraphSet(GraphSet gs, int yPixel) {
    // Need to check for nSplit > 1 here because this could be a 
    // mass spec that has been selected by clicking on a GC peak.
    // In that case, there is no split, and we should not be changing
    // the selected spectrum to 0 just because the split point is 0.
    int splitPoint = (gs.nSplit > 1 ? gs.getSplitPoint(yPixel)
        : gs.getCurrentSpectrumIndex());
    boolean isNewSet = (currentGraphSet != gs);
    boolean isNewSplitPoint = (isNewSet || currentSplitPoint != splitPoint);
    currentGraphSet = gs;
    currentSplitPoint = splitPoint;
    if (isNewSet || gs.nSplit > 1 && isNewSplitPoint)
      setSpectrum(splitPoint, true);
    if (!isNewSet) {
      isNewSet = gs.checkSpectrumClickedEvent(mouseX, mouseY, clickCount);
      if (!isNewSet)
        return false;
      currentSplitPoint = splitPoint = gs.getCurrentSpectrumIndex();
      setSpectrum(splitPoint, true);
    }

    // new set (so also new split point)
    // or nSplit > 1 and new split point
    // or nSplit == 1 and showAllStacked and isClick and is a spectrum click)

    jumpToSpectrumIndex(splitPoint,
        isNewSet || gs.nSplit > 1 && isNewSplitPoint);
    return isNewSet;
  }

  public void jumpToSpectrum(Spectrum spec) {
    int index = currentGraphSet.getSpectrumIndex(spec);
    jumpToSpectrumIndex(index, true);
  }

  public void jumpToSpectrumIndex(int index, boolean doSetSpec) {
    if (index < 0 || index >= currentGraphSet.nSpectra)
      return;
    currentSplitPoint = index;
    if (doSetSpec)
      setSpectrum(currentSplitPoint, currentGraphSet.nSplit > 1);
    Spectrum spec = getSpectrum();
    notifySubSpectrumChange(spec.getSubIndex(), spec);

  }

  public void splitStack(boolean doSplit) {
    currentGraphSet.splitStack(doSplit);
  }

  public int getNumberOfSpectraInCurrentSet() {
    return currentGraphSet.nSpectra;
  }

  public String getSourceID() {
    String id = getSpectrum().sourceID;
    return (id == null ? getSpectrumAt(0).sourceID : id);
  }

  public int getStartingPointIndex(int index) {
    return currentGraphSet.viewData.getStartingPointIndex(index);
  }

  public int getEndingPointIndex(int index) {
    return currentGraphSet.viewData.getEndingPointIndex(index);
  }

  public boolean haveSelectedSpectrum() {
    return currentGraphSet.haveSelectedSpectrum();
  }

  public boolean getShowAnnotation(AType type) {
    return currentGraphSet.getShowAnnotation(type, -1);
  }

  public void showAnnotation(AType type, Boolean tfToggle) {
    currentGraphSet.setShowAnnotation(type, tfToggle);
  }

  public void setYStackOffsetPercent(int offset) {
    currentGraphSet.yStackOffsetPercent = offset;
  }

  public void setSpectrum(int iSpec, boolean isSplit) {
    currentGraphSet.setSpectrum(iSpec, isSplit);

    // repaint();
  }

  public Spectrum getSpectrum() {
    return currentGraphSet.getSpectrum();
  }

  public void setSpecForIRMode(Spectrum spec) {
    setTaintedAll();
    Spectrum spec0 = currentGraphSet.getSpectrum();
    currentGraphSet.setSpectrumJDX(spec);
    for (int i = 0; i < spectra.size(); i++)
      if (spectra.get(i) == spec0)
        spectra.set(i, spec);
  }

  public boolean isShowAllStacked() {
    return currentGraphSet.showAllStacked;
  }

  public int getCurrentSpectrumIndex() {
    return currentGraphSet.getCurrentSpectrumIndex();
  }

  public Spectrum getSpectrumAt(int index) {
    if (currentGraphSet == null)
      return null;
    return currentGraphSet.getSpectrumAt(index);
  }

  /**
   * Add information about a region of the displayed spectrum to be highlighted
   * applet only right now
   * 
   * @param gs
   * 
   * @param x1
   *        the x value of the coordinate where the highlight should start
   * @param x2
   *        the x value of the coordinate where the highlight should end
   * @param spec
   * @param r
   * @param a
   * @param b
   * @param g
   */
  public void addHighlight(GraphSet gs, double x1, double x2, Spectrum spec,
                           int r, int g, int b, int a) {
    (gs == null ? currentGraphSet : gs).addHighlight(x1, x2, spec,
        g2d.getColor4(r, g, b, a));
  }

  /**
   * Remove the highlight specified by the starting and ending x value
   * 
   * @param x1
   *        the x value of the coordinate where the highlight started
   * @param x2
   *        the x value of the coordinate where the highlight ended
   */
  public void removeHighlight(double x1, double x2) {
    currentGraphSet.removeHighlight(x1, x2);
  }

  /**
   * Removes all highlights from the display
   */
  public void removeAllHighlights() {
    currentGraphSet.removeAllHighlights();
  }

  public void setZoom(double x1, double y1, double x2, double y2) {
    currentGraphSet.setZoom(x1, y1, x2, y2);
    doReset = true;
    setTaintedAll();
    notifyListeners(new ZoomEvent());//x1, y1, x2, y2));
  }

  /**
   * Resets the spectrum to it's original view
   */
  public void resetView() {
    currentGraphSet.resetView();
  }

  /**
   * Displays the previous view zoomed
   */
  public void previousView() {
    currentGraphSet.previousView();
  }

  /**
   * Displays the next view zoomed
   */
  public void nextView() {
    currentGraphSet.nextView();
  }

  public Integral getSelectedIntegral() {
    return currentGraphSet.getSelectedIntegral();
  }

  public void advanceSubSpectrum(int dir) {
    currentGraphSet.advanceSubSpectrum(dir);
  }

  public void setSelectedIntegral(double val) {
    currentGraphSet.setSelectedIntegral(val);
  }

  public void scaleYBy(double f) {
    currentGraphSet.scaleYBy(f);
  }

  public void toPeak(int i) {
    currentGraphSet.toPeak(i);
  }

  /*------------------------- Javascript call functions ---------------------*/

  /**
   * Returns the spectrum coordinate of the point on the display that was
   * clicked
   * 
   * @return the spectrum coordinate of the point on the display that was
   *         clicked
   */
  public Coordinate getClickedCoordinate() {
    return coordClicked;
  }

  /**
   * click event processing
   * 
   * @param coord
   * @param actualCoord
   * @return true if a coordinate was picked and fills in coord and actualCoord
   */
  public boolean getPickedCoordinates(Coordinate coord,
                                      Coordinate actualCoord) {
    return Coordinate.getPickedCoordinates(coordsClicked, coordClicked, coord,
        actualCoord);
  }

  /**
   * shifts xyCoords for a spectrum by the specified amount
   * 
   * @param mode
   * @param xOld
   *        old position or NaN
   * @param xNew
   *        NaN or new position
   * 
   * @return true if successful
   */
  public boolean shiftSpectrum(int mode, double xOld, double xNew) {
    return currentGraphSet.shiftSpectrum(mode, xOld, xNew);

  }

  public void findX(Spectrum spec, double d) {
    currentGraphSet.setXPointer(spec, d);
    // repaint();
  }

  public void setXPointers(Spectrum spec, double x1, Spectrum spec2,
                           double x2) {
    currentGraphSet.setXPointer(spec, x1);
    currentGraphSet.setXPointer2(spec2, x2);
  }

  // called by GraphSet

  public boolean isCurrentGraphSet(GraphSet graphSet) {
    return graphSet == currentGraphSet;
  }

  public void repaint() {
    jsvp.doRepaint(false);
  }

  public void setToolTipText(String s) {
    jsvp.setToolTipText(s);
  }

  public void setHighlightColor(GenericColor color) {
    setColor(ScriptToken.HIGHLIGHTCOLOR, color);
  }

  String getInput(String message, String title, String sval) {
    return jsvp.getInput(message, title, sval);
  }

  private Font getFont(Object g, int width, int style, float size,
                       boolean isLabel) {
    size *= scalingFactor;
    if (isLabel) {
      if (width < 400)
        size = ((width * size) / 400);
    } else {
      if (width < 250)
        size = ((width * size) / 250);
    }
    int face = jsvp
        .getFontFaceID(isPrinting ? printingFontName : displayFontName);
    return currentFont = Font.createFont3D(face, style, size, size, 0,
        jsvp.getApiPlatform(), g);
  }

  // listeners to handle various events, from GraphSet or AwtPanel

  /**
   * 
   * @param isub
   *        -1 indicates direction if no subspectra or subspectrum index if
   *        subspectra
   * @param spec
   *        null indicates no subspectra
   */
  public void notifySubSpectrumChange(int isub, Spectrum spec) {
    notifyListeners(new SubSpecChangeEvent(isub,
        (spec == null ? null : spec.getTitleLabel())));
  }

  /**
   * Notifies CoordinatePickedListeners
   * 
   * @param p
   * 
   */
  public void notifyPeakPickedListeners(PeakPickEvent p) {
    if (p == null) {
      p = new PeakPickEvent(jsvp, coordClicked,
          getSpectrum().getAssociatedPeakInfo(xPixelClicked, coordClicked));
    }
    // PeakInfo pi = p.getPeakInfo();
    // if (pi.getAtoms() == null) {
    // // find matching file/type/model in other panels
    // String filePath = pi.getFilePath();
    // String type = pi.getType();
    // String model = pi.getModel();
    // for (int i = 0; i < graphSets.size(); i++) {
    // if (graphSets.get(i) == currentGraphSet)
    // continue;
    // // just first spectrum for now -- presumed to be GC/MS
    // PeakInfo pi2 = graphSets.get(i).getSpectrumAt(0)
    // .selectPeakByFilePathTypeModel(filePath, type, model);
    // if (pi2 != null)
    // graphSets.get(i).addPeakHighlight(pi2);
    // }
    // }
    // problem is that you cannot have two highlights for the same model.
    // only problem is gc/ms, where we want to select a GC peak based on
    // an MS peak.

    notifyListeners(p);
  }

  public void notifyListeners(Object eventObj) {
    for (int i = 0; i < listeners.size(); i++)
      if (listeners.get(i) != null)
        listeners.get(i).panelEvent(eventObj);
  }

  /*--------------the rest are all mouse and keyboard interface -----------------------*/

  public void escapeKeyPressed(boolean isDEL) {
    currentGraphSet.escapeKeyPressed(isDEL);
  }

  private enum Mouse {
    UP, DOWN;
  }

  private Mouse mouseState;
  public boolean gridOn = true;
  public boolean titleOn = true;
  public boolean peakTabsOn = true;

  public boolean hasFocus() {
    return jsvp.hasFocus();
  }

  public boolean isMouseUp() {
    return (mouseState == Mouse.UP);
  }

  public int mouseX, mouseY;

  private GraphSet mouseSet;
  private PeakInfo mousePeak;
  

  public void clearMouseSet(GraphSet gs) {
    if (gs == null) {
      mousePeak = null;
    }
    if (mouseSet != null && mouseSet != gs) {
      mouseSet.mouseMovedEvent(Integer.MAX_VALUE, 0);
    }
    mouseSet = gs;
    if (gs == null && graphSets.size() > 0) {
    	// newly selected Panel defaults to bottom spectrum titled
      (mouseSet = gs = graphSets.get(graphSets.size() - 1)).setSpectrumMovedTo(gs.nSpectra - 1);
    }
  }

  public void doMouseMoved(int xPixel, int yPixel) {
    mouseX = xPixel;
    mouseY = yPixel;
    mouseState = Mouse.UP;
    clickCount = 0;
    GraphSet gs = GraphSet.findGraphSet(graphSets, xPixel, yPixel);
    if (gs == null)
      return;
    clearMouseSet(gs);
    PeakInfo peak = gs.mouseMovedEvent(xPixel, yPixel);
    if (mousePeak != peak) {
      mousePeak = peak;
      setTaintedAll();
    }
  }

  public void doMouseClicked(int xPixel, int yPixel, boolean isControlDown) {
    GraphSet gs = GraphSet.findGraphSet(graphSets, xPixel, yPixel);
    if (gs == null)
      return;
    setCurrentGraphSet(gs, yPixel);
    gs.mouseClickedEvent(xPixel, yPixel, clickCount, isControlDown);
    setTaintedAll();
    repaint();
  }

  public void doMousePressed(int xPixel, int yPixel) {
    mouseState = Mouse.DOWN;
    GraphSet gs = GraphSet.findGraphSet(graphSets, xPixel, yPixel);
    if (gs == null)
      return;
    clearMouseSet(gs);
    setCurrentGraphSet(gs, yPixel);
    clickCount = (++clickCount % 3);
    currentGraphSet.mousePressedEvent(xPixel, yPixel, clickCount);
  }

  public void doMouseDragged(int xPixel, int yPixel) {
    isIntegralDrag |= ctrlPressed;
    mouseState = Mouse.DOWN;
    if (GraphSet.findGraphSet(graphSets, xPixel, yPixel) != currentGraphSet)
      return;
    if (currentGraphSet.checkWidgetEvent(xPixel, yPixel, false))
      setTaintedAll();
    currentGraphSet.mouseMovedEvent(xPixel, yPixel);
  }

  public void doMouseReleased(int xPixel, int yPixel, boolean isButton1) {
    mouseState = Mouse.UP;
    if (thisWidget == null && currentGraphSet.pendingMeasurement == null
        || !isButton1)
      return;
    currentGraphSet.mouseReleasedEvent(xPixel, yPixel);
    thisWidget = null;
    isIntegralDrag = false;
    integralShiftMode = 0;
    // repaint();
  }

  public boolean hasCurrentMeasurements(AType type) {
    return currentGraphSet.hasCurrentMeasurement(type);
  }

  public AnnotationData getDialog(AType type) {
    return currentGraphSet.getDialog(type, -1);
  }

  public void addDialog(int iSpec, AType type, AnnotationData dialog) {
    currentGraphSet.addDialog(iSpec, type, dialog);
  }

  public void getPeakListing(Parameters p, Boolean tfToggle) {
    if (p != null)
      currentGraphSet.getPeakListing(-1, p, true);
    currentGraphSet.setPeakListing(tfToggle);
  }

  public void checkIntegral(Parameters parameters, String value) {
    currentGraphSet.checkIntegralParams(parameters, value);
  }

  /**
   * DEPRECATED
   * 
   * Sets the integration ratios that will be displayed
   * 
   * @param value
   */
  public void setIntegrationRatios(String value) {
    currentGraphSet.setIntegrationRatios(value);
  }

  public ScaleData getView() {
    return currentGraphSet.getCurrentView();
  }

  public void closeAllDialogsExcept(AType type) {
    for (int i = graphSets.size(); --i >= 0;)
      graphSets.get(i).closeDialogsExcept(type);
  }

  public void removeDialog(JSVDialog dialog) {
    currentGraphSet.removeDialog(dialog);
  }

  public void normalizeIntegral() {
    Integral integral = getSelectedIntegral();
    if (integral == null)
      return;
    String sValue = integral.text;
    if (sValue.length() == 0)
      sValue = "" + integral.getValue();
    String newValue = getInput("Enter a new value for this integral",
        "Normalize Integral", sValue);
    try {
      setSelectedIntegral(Double.parseDouble(newValue));
    } catch (Exception e) {
    }
  }

  public String getDrawTitle(boolean isPrinting, int iSpec) {
    String title = null;
    //System.out.println("PD " + iSpec + " " + nSpectra + " " + viewTitle + " " );
    if (isPrinting) {
      title = printJobTitle;
    } else if (nSpectra == 1) {
      title = getSpectrum().getPeakTitle();
    } else if (iSpec >= 0) {
      title = getSpectrumAt(iSpec).getPeakTitle();
    } else if (viewTitle != null) {
      if (currentGraphSet.getTitle(false) != null) // check if common title
        title = getSpectrum().getPeakTitle();
      if (title == null)
        title = viewTitle; // "View 1"
    } else {
      title = jsvp.getTitle().trim();
    }
    if (title.indexOf("\n") >= 0)
      title = title.substring(0, title.indexOf("\n")).trim();
    return title;
  }

  // abandoned
  //	public String getPrintJobTitle(boolean isPrinting) {
  //		String title = null;
  //		if (nSpectra == 1) {
  //			title = getSpectrum().getTitle();
  //		} else if (viewTitle != null) {
  //			if (graphSets.size() == 1)
  //				title = currentGraphSet.getTitle(isPrinting);
  //			if (title == null)
  //				title = viewTitle; // "View 1"
  //		} else {
  //			title = jsvp.getTitle().trim();
  //		}
  //		if (title.indexOf("\n") >= 0)
  //			title = title.substring(0, title.indexOf("\n")).trim();
  //		else if (title.startsWith("$"))
  //			title = title.substring(1);
  //		return title;
  //	}

  public synchronized void linkSpectra(LinkMode mode) {
    if (mode == LinkMode.ALL)
      mode = (nSpectra == 2 ? LinkMode.AB
          : nSpectra == 3 ? LinkMode.ABC : LinkMode.NONE);
    if (mode != LinkMode.NONE && mode.toString().length() != nSpectra)
      return;
    setGraphSets(mode);
  }

  private boolean linking;
  public int xPixelClicked;

  public void doZoomLinked(GraphSet graphSet, double initX, double finalX,
                           boolean addZoom, boolean checkRange, boolean is1d) {
    if (linking)
      return;
    linking = true;
    Spectrum spec = graphSet.getSpectrumAt(0);
    for (int i = graphSets.size(); --i >= 0;) {
      GraphSet gs = graphSets.get(i);
      if (gs != graphSet && Spectrum.areXScalesCompatible(spec,
          graphSets.get(i).getSpectrumAt(0), false, true))
        gs.doZoom(initX, 0, finalX, 0, is1d, false, checkRange, false, addZoom);
    }
    linking = false;
  }

  public void clearLinkViews(GraphSet graphSet) {
    if (linking)
      return;
    linking = true;
    Spectrum spec = graphSet.getSpectrum();
    for (int i = graphSets.size(); --i >= 0;) {
      GraphSet gs = graphSets.get(i);
      if (gs != graphSet && Spectrum.areXScalesCompatible(spec,
          graphSets.get(i).getSpectrum(), false, true))
        gs.clearViews();
    }
    linking = false;
  }

  public void setlinkedXMove(GraphSet graphSet, double x, boolean isX2) {
    if (linking)
      return;
    linking = true;
    Spectrum spec = graphSet.getSpectrum();
    for (int i = graphSets.size(); --i >= 0;) {
      GraphSet gs = graphSets.get(i);
      if (gs != graphSet && Spectrum.areXScalesCompatible(spec,
          graphSets.get(i).getSpectrum(), false, true)) {
        if (gs.imageView == null)
          if (isX2) {
            gs.setXPixelMovedTo(Double.MAX_VALUE, x, 0, 0);
          } else {
            gs.setXPixelMovedTo(x, Double.MAX_VALUE, 0, 0);
          }
      }
    }
    linking = false;
  }

  public void set2DCrossHairsLinked(GraphSet graphSet, double x, double y,
                                    boolean isLocked) {
    //if (Math.abs(x - y) < 0.1)
    //x = y = Double.MAX_VALUE;
    for (int i = graphSets.size(); --i >= 0;) {
      GraphSet gs = graphSets.get(i);
      if (gs != graphSet)
        gs.set2DXY(x, y, isLocked);
    }
  }

  public void dialogsToFront(Spectrum spec) {
    currentGraphSet.dialogsToFront(spec);
  }

  // //////// settable colors //////////

  public GenericColor coordinatesColor;
  public GenericColor gridColor;
  public GenericColor integralPlotColor;
  public GenericColor peakTabColor;
  public GenericColor peakOverColor;
  public GenericColor plotAreaColor;
  public GenericColor scaleColor;
  public GenericColor titleColor;
  public GenericColor unitsColor;
  // potentially settable;

  public GenericColor highlightColor;
  public GenericColor zoomBoxColor;
  public GenericColor zoomBoxColor2;
  public GenericColor BLACK;
  public GenericColor bgcolor;

  public void setColor(ScriptToken st, GenericColor color) {
    if (color != null)
      options.put(st, CU.toRGBHexString(color));
    // "return" is because these are front/back pane operations
    switch (st) {
    case COORDINATESCOLOR:
      coordinatesColor = color;
      return;
    case HIGHLIGHTCOLOR:
      highlightColor = color.addAlpha(150);
      return;
    case ZOOMBOXCOLOR:
      zoomBoxColor = color;
      return;
    case ZOOMBOXCOLOR2:
      zoomBoxColor2 = color;
      return;
    case BACKGROUNDCOLOR:
      jsvp.setBackgroundColor(bgcolor = color);
      break;
    case GRIDCOLOR:
      gridColor = color;
      break;
    case INTEGRALPLOTCOLOR:
      integralPlotColor = color;
      break;
    case PEAKOVERCOLOR:      
      peakOverColor = color.addAlpha(30);
      break;
    case PEAKTABCOLOR:
      peakTabColor = color;
      break;
    case PLOTCOLOR:
      for (int i = graphSets.size(); --i >= 0;)
        graphSets.get(i).setPlotColor0(color);
      break;
    case PLOTAREACOLOR:
      plotAreaColor = color;
      break;
    case SCALECOLOR:
      scaleColor = color;
      break;
    case TITLECOLOR:
      titleColor = color;
      break;
    case UNITSCOLOR:
      unitsColor = color;
      break;
    default:
      Logger.warn("AwtPanel --- unrecognized color: " + st);
      return;
    }
    taintedAll = true;
  }

  public GenericColor getColor(ScriptToken whatColor) {
    switch (whatColor) {
    default:
      Logger.error("awtgraphset missing color " + whatColor);
      return BLACK;
    case ZOOMBOXCOLOR2:
      return zoomBoxColor2;
    case ZOOMBOXCOLOR:
      return zoomBoxColor;
    case HIGHLIGHTCOLOR:
      return highlightColor;
    case INTEGRALPLOTCOLOR:
      return integralPlotColor;
    case GRIDCOLOR:
      return gridColor;
    case PEAKOVERCOLOR:
      return peakOverColor;
    case PEAKTABCOLOR:
      return peakTabColor;
    case PLOTAREACOLOR:
      return plotAreaColor;
    case SCALECOLOR:
      return scaleColor;
    case TITLECOLOR:
      return titleColor;
    case UNITSCOLOR:
      return unitsColor;
    }
  }

  public Object[][] getOverlayLegendData() {
    int numSpectra = currentGraphSet.nSpectra;
    Object[][] data = new Object[numSpectra][];
    String f1 = getSpectrumAt(0).getFilePath();
    String f2 = getSpectrumAt(numSpectra - 1).getFilePath();
    boolean useFileName = f1 != null && f2 != null && !f1.equals(f2);

    for (int index = 0; index < numSpectra; index++) {
      Object[] cols = new Object[3];
      Spectrum spectrum = getSpectrumAt(index);
      String title = spectrum.getTitle();
      if (useFileName)
        title = JSVFileManager.getTagName(spectrum.getFilePath()) + " - "
            + title;
      GenericColor plotColor = getCurrentPlotColor(index);
      cols[0] = new Integer(index + 1);
      cols[1] = plotColor;
      cols[2] = " " + title;
      data[index] = cols;
    }
    return data;
  }

  @SuppressWarnings("incomplete-switch")
  public void setColorOrFont(ColorParameters params, ScriptToken st) {
    if (st == null) {
      Map<ScriptToken, GenericColor> colors = params.elementColors;
      for (Map.Entry<ScriptToken, GenericColor> entry : colors.entrySet())
        setColorOrFont(params, entry.getKey());
      setColorOrFont(params, ScriptToken.DISPLAYFONTNAME);
      setColorOrFont(params, ScriptToken.TITLEFONTNAME);
      return;
    }
    switch (st) {
    case DISPLAYFONTNAME:
      setFontName(st, params.displayFontName);
      return;
    case TITLEFONTNAME:
      setFontName(st, params.titleFontName);
      return;
    }
    setColor(st, params.getElementColor(st));
  }

  public GenericColor getCurrentPlotColor(int i) {
    return currentGraphSet.getPlotColor(i);
  }

  public Hashtable<ScriptToken, Object> optionsSaved;
  private Object gMain;
  public boolean unitsDrawn;

  public void setPrint(PrintLayout pl, String fontName) {
    if (pl == null) {
      options.putAll(optionsSaved);
      optionsSaved = null;
      return;
    }
    printJobTitle = pl.title;
    // Set Graph Properties
    printingFontName = fontName;
    printGraphPosition = pl.position;

    optionsSaved = new Hashtable<ScriptToken, Object>();
    optionsSaved.putAll(options);

    gridOn = pl.showGrid;
    titleOn = pl.showTitles;
    setBoolean(ScriptToken.XSCALEON, pl.showXScale);
    setBoolean(ScriptToken.XUNITSON, pl.showXScale);
    setBoolean(ScriptToken.YSCALEON, pl.showYScale);
    setBoolean(ScriptToken.YUNITSON, pl.showYScale);

    // TODO Auto-generated method stub

  }

  public void setDefaultPrintOptions(PrintLayout pl) {
    pl.showGrid = gridOn;
    pl.showXScale = getBoolean(ScriptToken.XSCALEON);
    pl.showYScale = getBoolean(ScriptToken.YSCALEON);
    pl.showTitles = titleOn;
  }

  public JSVDialog showDialog(AType type) {
    AnnotationData ad = getDialog(type);
    closeAllDialogsExcept(type);
    if (ad != null && ad instanceof JSVDialog)
      return ((JSVDialog) ad).reEnable();
    int iSpec = getCurrentSpectrumIndex();
    if (iSpec < 0) {
      jsvp.showMessage(
          "To enable " + type + " first select a spectrum by clicking on it.",
          "" + type);
      return null;
    }
    Spectrum spec = getSpectrum();
    JSVDialog dialog = vwr.getDialog(type, spec);
    if (ad == null && type == AType.Measurements)
      ad = new MeasurementData(AType.Measurements, spec);
    if (ad != null)
      dialog.setData(ad);
    addDialog(iSpec, type, dialog);
    dialog.reEnable();
    return dialog;
  }

  /**
   * from jspecview.export.PDFCreator
   * 
   * -- does not use iText --
   * 
   * 
   * @param pdfCreator
   * @param pl
   */
  public void printPdf(GenericGraphics pdfCreator, PrintLayout pl) {
    boolean isPortrait = !pl.layout.equals("landscape");
    print(pdfCreator, (isPortrait ? pl.imageableHeight : pl.imageableWidth),
        (isPortrait ? pl.imageableWidth : pl.imageableHeight), pl.imageableX,
        pl.imageableY, pl.paperHeight, pl.paperWidth, isPortrait, 0);
  }

  public int print(Object g, double height, double width, double x, double y,
                   double paperHeight, double paperWidth, boolean isPortrait,
                   int pageIndex) {
    if (pageIndex != 0) {
      return JSViewer.PDF_NO_SUCH_PAGE;
    }
    g2d = g2d0;
    isPrinting = true;
    boolean addFilePath = false;
    if (g instanceof GenericGraphics) {// JsPdfCreator
      g2d = (GenericGraphics) g; // PDFWriter
      g = gMain;
    }
    if (printGraphPosition.equals("default")) {
      if (isPortrait) {
        height = defaultPrintHeight;
        width = defaultPrintWidth;
      } else {
        height = defaultPrintWidth;
        width = defaultPrintHeight;
      }
    } else if (printGraphPosition.equals("fit to page")) {
      //addFilePath = true;
    } else { // center
      if (isPortrait) {
        height = defaultPrintHeight;
        width = defaultPrintWidth;
        x = (int) (paperWidth - width) / 2;
        y = (int) (paperHeight - height) / 2;
      } else {
        height = defaultPrintWidth;
        width = defaultPrintHeight;
        y = (int) (paperWidth - defaultPrintWidth) / 2;
        x = (int) (paperHeight - defaultPrintHeight) / 2;
      }
    }
    setTaintedAll();
    g2d.translateScale(null, x, y, 1.0 / PRINT_SCALE_FACTOR);
    if (g == null)
      g = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB).getGraphics();
    drawGraph(g, g, g, (int) width, (int) height, addFilePath, false);
    if (g2d != g2d0 && g != gMain)
      ((Graphics2D) g).dispose();
    g2d = g2d0;
    isPrinting = false;
    return JSViewer.PDF_PAGE_EXISTS;
  }

  /*--------------mouse and keyboard interface -----------------------*/

  @Override
  public boolean keyPressed(int code, int modifiers) {
    if (isPrinting)
      return false;
    checkKeyControl(code, true);
    // should be only in panel region, though.
    switch (code) {
    case Event.VK_ESCAPE:
    case Event.VK_DELETE:
    case Event.VK_BACK_SPACE: // Mac
      escapeKeyPressed(code != Event.VK_ESCAPE);
      isIntegralDrag = false;
      setTaintedAll();
      repaint();
      return true;
    }
    double scaleFactor = 0;
    boolean doConsume = false;
    if (modifiers == 0) {
      switch (code) {
      case Event.VK_LEFT:
      case Event.VK_RIGHT:
        doMouseMoved((code == Event.VK_RIGHT ? ++mouseX : --mouseX), mouseY);
        repaint();
        doConsume = true;
        break;
      case Event.VK_PAGE_UP:
      case Event.VK_PAGE_DOWN:
        scaleFactor = (code == Event.VK_PAGE_UP ? GraphSet.RT2
            : 1 / GraphSet.RT2);
        doConsume = true;
        break;
      case Event.VK_DOWN:
      case Event.VK_UP:
        int dir = (code == Event.VK_DOWN ? -1 : 1);
        if (getSpectrumAt(0).getSubSpectra() == null) {
          notifySubSpectrumChange(dir, null);
        } else {
          advanceSubSpectrum(dir);
          setTaintedAll();
          repaint();
        }
        doConsume = true;
        break;
      }
    } else if (checkMod(code, Event.CTRL_MASK)) {
      switch (code) {
      case Event.VK_DOWN:
      case Event.VK_UP:
      case 45: // '-'
      case 61: // '=/+'
        scaleFactor = (code == 61 || code == Event.VK_UP ? GraphSet.RT2
            : 1 / GraphSet.RT2);
        doConsume = true;
        break;
      case Event.VK_LEFT:
      case Event.VK_RIGHT:
        toPeak(code == Event.VK_RIGHT ? 1 : -1);
        doConsume = true;
        break;
      }
    }
    if (scaleFactor != 0) {
      scaleYBy(scaleFactor);
      setTaintedAll();
      repaint();
    }
    return doConsume;
  }

  @Override
  public void keyReleased(int keyCode) {
    if (isPrinting)
      return;
    checkKeyControl(keyCode, false);
  }

  @Override
  public boolean keyTyped(int ch, int mods) {
    if (isPrinting)
      return false;
    switch (ch) {
    case 'n':
      if (mods != 0)
        break;
      normalizeIntegral();
      return true;
    case 26: // ctrl-Z
      if (mods != Event.CTRL_MASK)
        break;
      previousView();
      setTaintedAll();
      repaint();
      return true;
    case 25: // ctrl-y
      if (mods != Event.CTRL_MASK)
        break;
      nextView();
      setTaintedAll();
      repaint();
      return true;
    }
    return false;
  }

  @Override
  public void mouseAction(int mode, long time, int x, int y, int countIgnored,
                          int buttonMods) {
    if (isPrinting)
      return;
    switch (mode) {
    case Event.PRESSED:
      if (!checkMod(buttonMods, Event.MOUSE_LEFT))
        return;
      doMousePressed(x, y);
      break;
    case Event.RELEASED:
      doMouseReleased(x, y, checkMod(buttonMods, Event.MOUSE_LEFT));
      setTaintedAll();
      repaint();
      break;
    case Event.DRAGGED:
      doMouseDragged(x, y);
      repaint();
      break;
    case Event.MOVED:
      jsvp.getFocusNow(false);
      if ((buttonMods & Event.BUTTON_MASK) != 0) {
        doMouseDragged(x, y);
        repaint();
        return;
      }
      doMouseMoved(x, y);
      if (coordStr != null)
        repaint();
      break;
    case Event.CLICKED:
      if (checkMod(buttonMods, Event.MOUSE_RIGHT)) {
        jsvp.showMenu(x, y);
        return;
      }
      ctrlPressed = false;
      doMouseClicked(x, y, updateControlPressed(buttonMods));
      break;
    }
  }

  public boolean checkMod(int buttonMods, int mask) {
    return ((buttonMods & mask) == mask);
  }

  public void checkKeyControl(int keyCode, boolean isPressed) {
    switch (keyCode) {
    case Event.VK_CONTROL:
    case Event.VK_META:
      ctrlPressed = isPressed;
      break;
    case Event.VK_SHIFT:
      shiftPressed = isPressed;
      break;
    //default:
    //ctrlPressed = updateControlPressed(keyCode);
    //shiftPressed = checkMod(keyCode, Event.SHIFT_MASK);
    }
  }

  public boolean updateControlPressed(int mods) {
    // Mac does not allow Ctrl-drag. The CMD key is indicated using code 157
    return (ctrlPressed |= checkMod(mods, Event.CTRL_MASK)
        || checkMod(mods, Event.MAC_COMMAND));
  }

  @Override
  public void mouseEnterExit(long time, int x, int y, boolean isExit) {
    if (isExit) {
      thisWidget = null;
      isIntegralDrag = false;
      integralShiftMode = 0;
    } else {
      try {
        jsvp.getFocusNow(false);
      } catch (Exception e) {
        System.out.println("pd " + this + " cannot focus");
      }
    }
  }

  public void setSolutionColor(String what) {
    boolean isNone = (what.indexOf("none") >= 0);
    boolean asFitted = (what.indexOf("false") < 0);
    if (what.indexOf("all") < 0) {
      int color = (isNone ? -1 : vwr.getSolutionColor(asFitted));
      getSpectrum()
          .setFillColor(color == -1 ? null : vwr.parameters.getColor1(color));
    } else {
      VisibleInterface vi = (VisibleInterface) JSViewer
          .getInterface("jspecview.common.Visible");
      for (int i = graphSets.size(); --i >= 0;)
        graphSets.get(i).setSolutionColor(vi, isNone, asFitted);
    }

    // TODO Auto-generated method stub

  }

  public void setIRMode(IRMode mode, String type) {
    for (int i = graphSets.size(); --i >= 0;)
      graphSets.get(i).setIRMode(mode, type);
  }

  public void closeSpectrum() {
    vwr.close("views");
    vwr.close(getSourceID());
    vwr.execView("*", true);
  }

}
