void keyPressed(KeyEvent e) {
  /*
  String keyS = str(key);
  if ("5".equals(keyS)) {
    println("\n\nrunningSimulation = " + runningSimulation);

    println("simulationMode = " + simulationMode);
    println("rigidHelices = " + rigidHelices);
    println("screenshot = " + screenshot);
    println("capturingScreen = " + capturingScreen);
    println("menuVisible = " + menuVisible);
    println("");
    for (Slider slid : sliders) {
      print(slid.label + ": isPressed=" + slid.isPressed + ", updating=" + slid.updating);
      println("");
    }
    println("");
    for (CheckBox cb : checkBoxes.values()) {
      print(cb.label + ": checked=" + cb.checked);
      println("");
    }
  }
  */

  if (!runningSimulation) { return; }

  double angleUnit = PI / 360.0;
  double angleShift = 25;

  String keyS = str(key); // Using String instead of just checking against char because Processing.js does not support the char type
  SphereList spheres = currentState.getSpheres();

  if (keyS.equals("a")) { // toggle select-all
    //if (!movieMode) {
      if (attachedIds.size() == currentState.sim.lenTot) { // deselect all
        attachedIds.clear();

      } else { // select all
        for (int i = 0; i < spheres.size(); ++i) {
          attachedIds.put(i, 1);
        }
      }
    //}

  } else if (keyS.equals("b")) { // make sphere radii bigger
    if (!sliders[0].isPressed) {
      ff.radius *= 1.1;
      if (ff.radius > ff.minRadius + ff.radiusScale) { ff.radius = ff.minRadius + ff.radiusScale; }
      sliders[0].setValue( (float)((ff.radius - ff.minRadius) / ff.radiusScale) );
    }

  } else if (keyS.equals("B")) { // make sphere radius smaller
    if (!sliders[0].isPressed) {
      ff.radius /= 1.1;
      if (ff.radius < ff.minRadius) { ff.radius = ff.minRadius; }
      sliders[0].setValue( (float)((ff.radius - ff.minRadius) / ff.radiusScale) );
    }


  } else if (keyS.equals("c")) { // change color mode
    if (!sliders[3].isPressed) {
      ++ds.colorMode;
      if (ds.colorMode >= DisplaySettings.COLOR_MODE_MAX) {
        ds.colorMode = 1;
      }
      displayColorMode();
      sliders[3].setValue( ((float)ds.colorMode - 1)/((float)DisplaySettings.COLOR_MODE_MAX - 1) );
    }


  } else if (keyS.equals("f")) { // flip helix
    if (attachedIds.size() > 0) {
      saveStateForUndo();
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        int id = it.next();
        Sphere2D s1 = spheres.get(id);
        if (s1.pairedWForce != null) {
          Sphere2D s2 = s1.pairedWForce;

          // Do not flip twice if both sides of the helix are selected
          if (attachedIds.containsKey(s2.id)) {
            if (s1.id > s2.id) {
              continue;
            }
          }

          // flip coordinates
          double hx = s1.x; // Temporary helper vairables
          double hy = s1.y;
          s1.x = s2.x;
          s1.y = s2.y;
          s2.x = hx;
          s2.y = hy;

          // Set velocities to 0
          s1.vx = 0.0;
          s1.vy = 0.0;
          s2.vx = 0.0;
          s2.vy = 0.0;
        }
      }
    }

  } else if (keyS.equals("l")) { // toggle labels
    ds.labelMode = ! ds.labelMode;
    checkBoxes.get("Labels").checked = !checkBoxes.get("Labels").checked;

  } else if (keyS.equals("v")) {
    ds.displayStrandInfo = !ds.displayStrandInfo;

  } else if (keyS.equals("s")) { // toggle simulation
    saveStateForUndo();
    simulationMode = !simulationMode;
    checkBoxes.get("Simulation Mode").checked = !checkBoxes.get("Simulation Mode").checked;
    if (!simulationMode) {
      textDisplay = "FREEZE";
      spheres.stopVelocities();
    } else {
      textDisplay = "UNFREEZE";
    }
    textDisplayCount = 0;

  } else if (keyS.equals("S")) { // toggle simulation type
    simulateAllMode = !simulateAllMode;
    checkBoxes.get("Sim. Selected Only").checked = !checkBoxes.get("Sim. Selected Only").checked;
    if (!simulateAllMode) {
      textDisplay = "SIMULATE SELECTED ONLY";
    } else {
      textDisplay = "SIMULATE ALL";
    }
    textDisplayCount = 0;
    
  } else if (keyS.equals("i")) { // toggle display of residue characters
    ds.textMode = !ds.textMode;

  } else if (keyS.equals("o")) { // toggle outline of residues
    ds.outlineMode = !ds.outlineMode;
    checkBoxes.get("Outlines").checked = !checkBoxes.get("Outlines").checked;
    

  } else if (keyS.equals("p")) { // screenshot
    screenshot = true;
    capturingScreen = true;

  } else if (keyS.equals("z") || keyS.equals("Z")) {
    simulationMode = false;
    checkBoxes.get("Simulation Mode").checked = false;
    //movieMode = false;
    if (keyS.equals("z")) {
      undoState();
    } else {
      redoState();
    }

  } else if (keyS.equals("y")) { // reflect over y-axis
    if (attachedIds.size() > 0) {
      saveStateForUndo();
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        Sphere2D sphere = spheres.get(it.next());
        sphere.x = width - sphere.x;
        sphere.vx = 0;
        sphere.vy = 0;
      }
    }

  } else if (keyS.equals("x")) { // reflect over x-axis
    if (attachedIds.size() > 0) {
      saveStateForUndo();
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        Sphere2D sphere = spheres.get(it.next());
        sphere.y = height - sphere.y;
        sphere.vx = 0;
        sphere.vy = 0;
      }
    }

  } else if (key == CODED) { // arrow keys
    double keyShift = 18;
    double keyShiftZoom = 80;
    /* Does not work in processing.js
    if (e.isShiftDown()) {
      keyShift = 2;
    } else {
      keyShift = 20;
    }
    */

    if (!menuVisible && attachedIds.size() == 0) { // nothing selected; use arrow keys to transform coordinate system"
     if (keyCode == LEFT) {
      ds.zoomTranslateX += keyShiftZoom;
     } else if (keyCode == RIGHT) {
      ds.zoomTranslateX -= keyShiftZoom;
     } else if (keyCode == UP) {
      ds.zoomTranslateY += keyShiftZoom;
     } else if (keyCode == DOWN) {
      ds.zoomTranslateY -= keyShiftZoom;
     }

    } else {

    if (keyCode == LEFT) {
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        int id = it.next();
        spheres.get(id).x = spheres.get(id).x - keyShift;
      }
    } else if (keyCode == RIGHT) {
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        int id = it.next();
        spheres.get(id).x = spheres.get(id).x + keyShift;
      }
    } else if (keyCode == UP) {
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        int id = it.next();
        spheres.get(id).y = spheres.get(id).y - keyShift;
      }
    } else if (keyCode == DOWN) {
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        int id = it.next();
        spheres.get(id).y = spheres.get(id).y + keyShift;
      }
    }
  } // else if ... in case some residues have been selected
  /*
  } else if (keyS.equals("j")) { // left
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
       int id = it.next();
       spheres.get(id).x = spheres.get(id).x - smallShift;
    }
  } else if (keyS.equals("l")) { // right
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
       int id = it.next();
       spheres.get(id).x = spheres.get(id).x + smallShift;
    }
  } else if (keyS.equals("i")) { // up
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
       int id = it.next();
       spheres.get(id).y = spheres.get(id).y - smallShift;
    }
  } else if (keyS.equals("k")) { // down
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
       int id = it.next();
       spheres.get(id).y = spheres.get(id).y + smallShift;
    }
  */

  } else if (keyS.equals("n")) { // rotate CC
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
      int id = it.next();
      spheres.get(id).rotateSphere(-angleUnit * angleShift, AbsMouseX(mouseX), AbsMouseY(mouseY));
    }
  } else if (keyS.equals("m")) { // rotate clockwise
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
      int id = it.next();
      spheres.get(id).rotateSphere(angleUnit * angleShift, AbsMouseX(mouseX), AbsMouseY(mouseY));
    }
  } else if (keyS.equals("N")) { // rotate CC small
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
      int id = it.next();
      spheres.get(id).rotateSphere(-angleUnit, AbsMouseX(mouseX), AbsMouseY(mouseY));
    }
  } else if (keyS.equals("M")) { // rotate clockwise small
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
      int id = it.next();
      spheres.get(id).rotateSphere(angleUnit, AbsMouseX(mouseX), AbsMouseY(mouseY));
  }

  } else if (keyS.equals("r")) { // toggle relaxed state, meaning certain forces are not computed on it
    saveStateForUndo();
    if (attachedIds.size() == 0) { // clear relaxed
      spheres.relaxedIds.clear();
    }
    else {
      for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
        int id = it.next();
        if (!spheres.relaxedIds.containsKey(id)) {
          spheres.relaxedIds.put(id, 1);
          spheres.get(id).vx = 0;
          spheres.get(id).vy = 0;
        } else {
          spheres.relaxedIds.remove(id);
        }
      }
    }

  } else if (keyS.equals("1")) { // bring to front
    if (attachedIds.size() > 0) {
      saveStateForUndo();
      for (int index = 0, i = 0; i < spheres.displayOrder.size(); ++i) {
        if ( attachedIds.containsKey(spheres.displayOrder.get(index)) ) {
          int id = spheres.displayOrder.remove(index);
          spheres.displayOrder.add(id);
        } else {
          ++index;
        }
      }
    }

  } else if (keyS.equals("2")) { // sent to back
    if (attachedIds.size() > 0) {
      saveStateForUndo();
      for (int index = spheres.displayOrder.size()-1, i = 0; i < spheres.displayOrder.size(); ++i) {
        if ( attachedIds.containsKey(spheres.displayOrder.get(index)) ) {
          int id = spheres.displayOrder.remove(index);
          spheres.displayOrder.add(0, id);
        } else {
          index--;
        }
      }
    }
  } else if (keyS.equals("d")) {
    deletingBond = true;
    addingBond = false;
    bondSphereId = -1;

  } else if (keyS.equals(" ")) {
    deletingBond = false;

  } else if (keyS.equals("k")) {
    viewingBase = !viewingBase;


  //} else if (keyS.equals("q")) { // movieMode
    /* TODO
    saveStateForUndo();
    if (!movieMode) {
      currentState.sim.initFoldSteps();
      // deleteAllForceBonds();

      attachedIds.clear();
      simulationMode = true;
      checkBoxes.get("Simulation Mode").checked = true;
      textDisplay = "MOVIE MODE";
      textDisplayCount = 0;
    } else {
      // finish re-bonding all force pairs
    }

    movieMode = !movieMode;
    */

  /*
  } else if (keyS.equals("e")) { // Interpolate positions of consecutive selected residues
    int startId = -1;
    int endId = -1;

    for (int i = 0, l = spheres.size(); i < l; ++i) {
      if (attachedIds.containsKey(i)) {
        endId = i;
        break;
      }
    }
    for (int i = lenTot -1; i >= 0; --i) {
      if (attachedIds.containsKey(i)) {
        startId = i;
        break;
      }
    }
    println("Interpolating between residues " + str(startId+1) + " and " + str(endId+1));
    if ((endId - startId) < 2) {
      println("There must be at least one intermediate residue for position equalization!");
      return;
    }

    for (int i = startId; i <= endId; ++i) {
      if (!attachedIds.containsKey(i)) {
        println("Cannot equalize positions of selected residues, because not all intermediate residues were selected.");
        return;
      }
    }
    double xMin = spheres.get(startId).x;
    double yMin = spheres.get(startId).y;
    double zMin = spheres.get(startId).z;
    double xMax = spheres.get(endId).x;
    double yMax = spheres.get(endId).y;
    double zMax = spheres.get(endId).z;
    double dx = (xMax - xMin)/(endId-startId);
    double dy = (yMax - yMin)/(endId-startId);
    double dz = (zMax - zMin)/(endId-startId);
    for (int i = startId + 1; i < endId; ++i) {
      double x = xMin + (i - startId) * dx;
      double y = yMin + (i - startId) * dy;
      double z = zMin + (i - startId) * dz;
      spheres.get(i).x = x;
      spheres.get(i).y = y;
      spheres.get(i).z = z;
      // println("Setting interpolated position to " + str(x) + " " + str(y) + " " + str(z));
    }
    */
  }  else if (!menuVisible && (keyS.equals("+") || keyS.equals("="))) { // zoom in
   if (ds.zoomFactor < 16) {
    // we want that AbsMouseX(screenResX/2) remains unchanged!
    //    (mx - ds.zoomTranslateXorig)/ds.zoomFactorOrig == (mx - ds.zoomTranslateXorigNew)/ds.zoomFactorNew;
    // ds.zoomFactorNew = ds.zoomFactor*1.5
    //    (mx - ds.zoomTranslateXorig)/ds.zoomFactorOrig == (mx - ds.zoomTranslateXorigNew)/(1.5*ds.zoomFactorOrig);
    //    mx - ds.zoomTranslateXorig == (mx - ds.zoomTranslateXorigNew)/1.5;
    //    1.5*(mx - ds.zoomTranslateXorig) == mx - ds.zoomTranslateXorigNew;
    // ds.zoomTranslateXNew = mx - 1.5 * (mx - ds.zoomTranslateXorig)
    // ds.zoomTranslateXNew = (1-1.5)*mx + 1.5 * ds.zoomTranslateXorig
    // mx = screenResX/2
    // ds.zoomTranslateXNew = (1-1.5)*(screenResX/2) + 1.5 * ds.zoomTranslateXorig
    // ds.zoomFactor *= ds.zoomMul;
    // ds.zoomTranslateX = (1-ds.zoomMul)*(screenResX/2) + ds.zoomMul * ds.zoomTranslateX;
    // ds.zoomTranslateY = (1-ds.zoomMul)*(screenResY/2) + ds.zoomMul * ds.zoomTranslateY;
    ds.applyZoom(ds.zoomMul);
   }
   // TODO set center to mouse coordinates
  } else if (!menuVisible && keyS.equals("-")) { // zoom out
   if (1/ds.zoomFactor < 16) {
     ds.applyZoom(1.0/ds.zoomMul);
   }
  } else if (keyS.equals("0")) { // reset zoom
   ds.resetZoom();
  }
}

void keyReleased() {
  if (addingBond) {
    addingBond = false;
    bondSphereId = -1;
  }
  if (deletingBond) { deletingBond = false; }
  //  if (viewingBase) { viewingBase = false; }
}

void displayColorMode() {
  if (ds.colorMode == DisplaySettings.COLOR_MODE_FEW) {
    textDisplay = "PASTEL";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_WHITE) {
    textDisplay = "WHITE";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_BYG) {
    textDisplay = "BRIGHT";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_GREY) {
    textDisplay = "GREY";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_RAINBOW) {
    textDisplay = "RAINBOW";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_RESIDUE) {
    textDisplay = "BASE TYPE";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_CUSTOM) {
    textDisplay = "CUSTOM";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_STRUCTURE) {
    textDisplay = "STRUCTURE";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_LIGHT) {
    textDisplay = "LIGHT";
  } else if (ds.colorMode == DisplaySettings.COLOR_MODE_RNADNA) {
    textDisplay = "RNADNA";
  }
  textDisplayCount = 0;
}