public void mouseReleased(MouseEvent e) {
    lastInteractionTime = System.currentTimeMillis();
    if (enabled && !readOnly && lastPressEvent != null && dragInProgress) {

      if (enableMouseDrags && !e.getPoint().equals(lastPressEvent.getPoint())) {

        dragInProgress = false;

        // Generate the command string
        String s = "Mouse " + MouseCommand.MOUSE_DRAG;

        // Insert the button identifier if other than left button was pressed
        if (e.getButton() != MouseEvent.BUTTON1) {
          s += " " + MouseCommand.PARAM_BUTTON_SHORT + "=" + parser.buttonToString(e.getButton());
        }

        // Insert modifiers if there are any
        String modifiers = parser.modifiersToString(e.getModifiers());
        if (modifiers.length() > 0) {
          s += " " + MouseCommand.PARAM_MODIFIER + "=" + modifiers;
        }

        // Generate coordinates
        s += " " + MouseCommand.PARAM_FROM + "=" + parser.pointToString(lastPressEvent.getPoint());
        s += " " + MouseCommand.PARAM_TO + "=" + parser.pointToString(e.getPoint());

        // Insert the command to the current editor
        insertLine(s, false, true, false);
        insertEvent(e);
      }
    }
  }
 /**
  * Implementation of the ActionListener interface. It is only called by a timer which is
  * associated with mouse moves (see mouseMoved method).
  *
  * @param e an ActionEvent instance.
  */
 public void actionPerformed(ActionEvent e) {
   timer.stop();
   if (enabled && !readOnly && lastMouseMoveEvent != null) {
     String s =
         "Mouse "
             + MouseCommand.MOUSE_MOVE
             + " "
             + MouseCommand.PARAM_TO
             + "="
             + parser.pointToString(lastMouseMoveEvent.getPoint());
     insertLine(s, false, true, false);
     insertEvent(lastMouseMoveEvent);
     mouseMovesList.clear();
   }
 }
  public String createWaitForUpdate(
      RemoteDesktopServerEvent e, List events, UserConfiguration cfg) {

    boolean insertUpdateArea = this.insertUpdateArea;
    boolean insertUpdateExtent = this.insertUpdateExtent;
    float defaultUpdateExtent = this.defaultUpdateExtent;
    boolean insertUpdateTimeout = this.insertUpdateTimeout;
    float timeoutUpdateRatio = this.timeoutUpdateRatio;
    boolean useMinUpdateTimeout = this.useMinUpdateTimeout;
    long minUpdateTimeout = this.minUpdateTimeout;
    boolean useUpdateWait = this.useUpdateWait;
    boolean useMinUpdateWait = this.useMinUpdateWait;
    float waitUpdateRatio = this.waitUpdateRatio;
    long minUpdateWait = this.minUpdateWait;

    if (cfg != null) {
      insertUpdateArea = cfg.getBoolean("recording.waitfor.update.insertArea").booleanValue();
      insertUpdateExtent = cfg.getBoolean("recording.waitfor.update.insertExtent").booleanValue();
      defaultUpdateExtent = cfg.getInteger("recording.waitfor.update.defaultExtent").intValue();
      insertUpdateTimeout = cfg.getBoolean("recording.waitfor.update.insertTimeout").booleanValue();
      timeoutUpdateRatio = cfg.getDouble("recording.waitfor.update.timeoutRatio").floatValue();
      useMinUpdateTimeout = cfg.getBoolean("recording.waitfor.update.useMinTimeout").booleanValue();
      minUpdateTimeout = cfg.getInteger("recording.waitfor.update.minTimeout").intValue();
      useUpdateWait = cfg.getBoolean("recording.waitfor.update.useWait").booleanValue();
      useMinUpdateWait = cfg.getBoolean("recording.waitfor.update.useMinWait").booleanValue();
      waitUpdateRatio = cfg.getDouble("recording.waitfor.update.waitRatio").floatValue();
      minUpdateWait = cfg.getInteger("recording.waitfor.update.minWait").intValue();
    }

    String s = "Waitfor " + WaitforCommand.EVENT_UPDATE;
    if (insertUpdateArea) {
      s += " " + WaitforCommand.PARAM_AREA + "=" + parser.rectToString(e.getUpdateRect());
    }

    if (insertUpdateExtent) {
      float extent = defaultUpdateExtent;

      // If the 'area' param is not included, we must calculate a relative update compared to the
      // whole screen
      if (!insertUpdateArea) {
        RfbClient rfb = (RfbClient) e.getSource();
        extent =
            defaultUpdateExtent
                * ((float) (e.getUpdateRect().width * e.getUpdateRect().height)
                    / (rfb.getDesktopWidth() * rfb.getDesktopHeight()));
      }
      s += " " + WaitforCommand.PARAM_EXTENT + "=" + extent + "%";
    }

    long time = e.getWhen();
    //        System.out.println("base event, time="+e.getWhen());

    RemoteDesktopServerEvent evt;
    int count = 1;
    for (int i = 0; i < events.size(); i++) {
      evt = (RemoteDesktopServerEvent) events.get(i);
      if (!e.equals(evt)
          && isUpdateApplicable(e, evt, insertUpdateExtent ? defaultUpdateExtent : 100)) {
        count++;
        time = Math.max(time, evt.getWhen());
        //                System.out.println("applicable event #"+i+" found, time="+evt.getWhen());
      }
    }

    //        System.out.println("final time="+time);
    if (count > 1) {
      if (!useUpdateWait) {
        s += " " + WaitforCommand.PARAM_COUNT + "=" + count;
      } else {
        long wait = (long) ((e.getWhen() - lastEventListUpdateTime) * waitUpdateRatio);
        if (useMinUpdateWait) {
          wait = wait > minUpdateWait ? wait : minUpdateWait;
        }
        s += " " + WaitforCommand.PARAM_WAIT + "=" + wait;
      }
    }

    if (insertUpdateTimeout) {
      time = (long) ((time - lastEventListUpdateTime) * timeoutUpdateRatio);
      if (useMinUpdateTimeout) {
        time = time > minUpdateTimeout ? time : minUpdateTimeout;
      }
      s += " " + WaitforCommand.PARAM_TIMEOUT + "=" + time;
    }
    return s;
  }
  public void keyTyped(KeyEvent e) {
    if (debug) {
      System.out.println(
          "--- RecordingModule: key typed = "
              + e
              + "\n  > Key char->int = "
              + (int) e.getKeyChar());
      System.out.println(" -- isActionKey() = " + e.isActionKey());
      System.out.println(" -- isISOControl() = " + Character.isISOControl(e.getKeyChar()));
      System.out.println(" -- isWhitespace() = " + Character.isWhitespace(e.getKeyChar()));
    }

    if (isKeyReserved(e)) {
      return;
    }

    if (enabled && !readOnly && lastKeyPressEvent != null) {

      if (enableKeyboard) {
        boolean replace = false;
        String text = "";
        if (isControl(e)) {
          if (lastKeyPressEvent.getKeyCode() == KeyEvent.VK_ENTER) {

            // Change the Type cmd prior to Typeline if the delay from the last type key is less
            // than 1 sec
            if (useTypeline && e.getModifiers() == 0 && lastElement != null) {
              String s = DocumentUtils.getElementText(lastElement);
              if (s.startsWith("Type ")
                  && (System.currentTimeMillis() - lastInsertTime) < typelineDelay) {
                replace = true;
                text = s.replaceFirst("Type", "Typeline");
              }
            }
          }

          if ("".equals(text)) {
            int count = 1;
            KeyEvent e2;

            long lastEventTime = e.getWhen();

            // We go through the vector of events and check whether there are events corresponding
            // to a typed text.
            for (int i = 0; i < events.size() && events.get(i) instanceof KeyEvent; i++) {
              e2 = (KeyEvent) events.get(i);
              if (e.getID() == e2.getID()
                  && e.getKeyChar() == e2.getKeyChar()
                  && e.getKeyCode() == e2.getKeyCode()
                  && e.getModifiers() == e2.getModifiers()
                  && (lastEventTime - e2.getWhen() < keyMutiDelay)) {
                count++;
                replace = true;
                lastEventTime = e2.getWhen();
              } else {
                break;
              }
            }

            text = "Press ";
            //                    String modifiers = KeyEvent.getKeyModifiersText(e.getModifiers());
            String modifiers = parser.modifiersToString(e.getModifiers());
            if (!"".equals(modifiers)) {
              text += modifiers + "+";
            }
            String charText = KeyEvent.getKeyText(lastKeyPressEvent.getKeyCode());
            if (charText == null) {
              charText = "<unknown>";
            }
            text += charText;
            if (count > 1) {
              text += " " + PressCommand.PARAM_COUNT + "=" + count;
            }

            if (debug) {
              System.out.println("--- RecordingModule: Inserting '" + text + "'");
            }
          }

        } else {
          text = "" + e.getKeyChar();
          KeyEvent e2;

          // We go through the vector of events and check whether there are events corresponding to
          // a typed text.
          for (int i = 0; i < events.size() && events.get(i) instanceof KeyEvent; i++) {
            e2 = (KeyEvent) events.get(i);
            if (!isControl(e2) && !e2.isActionKey()) {
              text = e2.getKeyChar() + text;
              replace = true;
            } else {
              break;
            }
          }

          text = "Type \"" + Utils.escapeUnescapedDoubleQuotes(text) + "\"";
        }

        // Insert the command to the current editor
        insertLine(text, replace, true, false);
      }
      insertEvent(e);
    }
  }
  public void keyPressed(KeyEvent e) {

    if (debug) {
      System.out.println(
          "--- RecordingModule: key pressed = "
              + e
              + "\n  > Key char->int = "
              + (int) e.getKeyChar());
    }

    // Here we process just action keys because they do not generate KEY_TYPED events.
    // Other key events are handled by the keyTyped method.
    lastInteractionTime = System.currentTimeMillis();

    if (isKeyReserved(e)) {
      return;
    }

    if (enabled && !readOnly) {
      //        System.out.println("keyPressed (e.isActionKey()=)"+e.isActionKey()+":
      // "+e.toString());
      // TODO: implement text corrections in type like Delete, Backspace

      if (e.isActionKey()) {

        if (enableKeyboard) {
          int count = 1;
          KeyEvent e2;

          long lastEventTime = e.getWhen();

          // We go through the vector of events and check whether there are events corresponding to
          // a typed text.
          for (int i = 0; i < events.size() && events.get(i) instanceof KeyEvent; i++) {
            e2 = (KeyEvent) events.get(i);
            if (e.getID() == e2.getID()
                && e.getKeyChar() == e2.getKeyChar()
                && e.getKeyCode() == e2.getKeyCode()
                && e.getModifiers() == e2.getModifiers()
                && (lastEventTime - e2.getWhen() < keyMutiDelay)) {
              count++;
              lastEventTime = e2.getWhen();
            } else {
              break;
            }
          }

          String text = "Press ";
          //                String modifiers = KeyEvent.getKeyModifiersText(e.getModifiers());
          String modifiers = parser.modifiersToString(e.getModifiers());
          if (!"".equals(modifiers)) {
            text += modifiers + "+";
          }
          String charText = (String) keyCodes.get(new Integer(e.getKeyCode()));
          if (charText == null) {
            charText = "<unknown>";
          }
          text += charText;
          if (count > 1) {
            text += " " + PressCommand.PARAM_COUNT + "=" + count;
          }
          //                text += '\n';

          if (debug) {
            System.out.println("--- RecordingModule: Inserting '" + text + "'");
          }

          // Insert the command to the current editor
          insertLine(text, count > 1, true, false);
        }
        insertEvent(e);
      }
      lastKeyPressEvent = e;
    }
  }
  /**
   * This method gets called when user performs a mouse click. It decodes whether it is a single
   * click or part of a multiple click (double click, triple click etc.) and inserts appropriate
   * command to the current editor.
   *
   * @param e a MouseEvent describing the mouse click.
   */
  public void mouseClicked(MouseEvent e) {
    if (enabled && !readOnly) {
      if (timer != null && timer.isRunning()) {
        timer.stop();
      }
      if (enableMouseClicks) {
        int count = 1;
        MouseEvent e2;

        long lastEventTime = e.getWhen();

        // This cycle is to determine multiple clicks like double click, triple click etc.
        // We go through the vector of events and check whether there are events corresponding to
        // multiple clicks.
        for (int i = 0; i < events.size() && events.get(i) instanceof MouseEvent; i++) {
          e2 = (MouseEvent) events.get(i);

          // The events are considered to be a multiple click when:
          // 1. Coordinates are equal
          // 2. Modifiers are equal
          // 3. Button is equal
          // 4. Delay between two subsequent clicks is lower than given number of miliseconds
          if (e2.getX() == e.getX()
              && e2.getY() == e.getY()
              && e2.getModifiers() == e.getModifiers()
              && (lastEventTime - e2.getWhen() < mouseMultiDelay)
              && e.getButton() == e2.getButton()
              && e2.getID() == e.getID()) {
            count++;
            lastEventTime = e2.getWhen();
          } else {
            break;
          }
        }

        // Generate the command string
        String s = "Mouse " + MouseCommand.MOUSE_CLICK;

        // Insert the button identifier if other than left button was pressed
        if (e.getButton() != MouseEvent.BUTTON1) {
          s += " " + MouseCommand.PARAM_BUTTON_SHORT + "=" + parser.buttonToString(e.getButton());
        }

        // Insert modifiers if there are any
        String modifiers = parser.modifiersToString(e.getModifiers());
        if (modifiers.length() > 0) {
          s += " " + MouseCommand.PARAM_MODIFIER + "=" + modifiers;
        }

        // Generate the count parameter
        if (count > 1) {
          s += " " + MouseCommand.PARAM_COUNT + "=" + count;
        }

        // This will determine whether this click is preceded by a mouse
        // move command with the same coordinates.
        // It will be replaced if yes.
        boolean replaceLastMove = false;
        //                if (enableMouseMoves) {
        //                    if (events.size() > 0 && events.get(events.size() - 1) instanceof
        // MouseEvent) {
        //                        MouseEvent me = (MouseEvent) events.get(events.size() - 1);
        //                        if (me.getID() == MouseEvent.MOUSE_MOVED && e.getX() == me.getX()
        // && e.getY() == me.getY()) {
        //                            replaceLastMove = true;
        //                        }
        //                    }
        //                }

        // Generate coordinates
        s += " " + MouseCommand.PARAM_TO + "=" + parser.pointToString(e.getPoint());

        // Insert the command to the current editor
        insertLine(s, count > 1 || replaceLastMove, true, false);
        dragInProgress = false;
      }
      insertEvent(e);
    }
  }
  public void mouseWheelMoved(MouseWheelEvent e) {
    if (enabled && !readOnly) {
      if (timer != null && timer.isRunning()) {
        timer.stop();
      }
      if (enableMouseWheel) {
        int count = 1;
        MouseWheelEvent e2;

        long lastEventTime = e.getWhen();
        int steps = 0;
        int r;

        // This cycle is to determine multiple clicks like double wheel, triple wheel event etc.
        // We go through the vector of events and check whether there are events corresponding to
        // multiple events.
        for (int i = 0; i < events.size() && events.get(i) instanceof MouseWheelEvent; i++) {
          e2 = (MouseWheelEvent) events.get(i);

          // The events are considered to be a multiple click when:
          // 1. Coordinates are equal
          // 2. Modifiers are equal
          // 3. Both events are wheel up or down
          // 4. Delay between two subsequent clicks is lower than given number of miliseconds
          r = e2.getWheelRotation();
          if (e2.getX() == e.getX()
              && e2.getY() == e.getY()
              && e2.getModifiers() == e.getModifiers()
              && (lastEventTime - e2.getWhen() < mouseMultiDelay)
              && e2.getID() == e.getID()
              && ((r > 0 && e.getWheelRotation() > 0) || (r < 0 && e.getWheelRotation() < 0))) {
            count++;
            lastEventTime = e2.getWhen();
            steps += Math.abs(r);
          } else {
            break;
          }
        }

        steps += Math.abs(e.getWheelRotation());

        // Generate the command string
        String s =
            "Mouse "
                + (e.getWheelRotation() > 0
                    ? MouseCommand.MOUSE_WHEEL_DOWN
                    : MouseCommand.MOUSE_WHEEL_UP);

        // Insert modifiers if there are any
        String modifiers = parser.modifiersToString(e.getModifiers());
        if (modifiers.length() > 0) {
          s += " " + MouseCommand.PARAM_MODIFIER + "=" + modifiers;
        }

        // Generate the count parameter
        if (count > 1) {
          s += " " + MouseCommand.PARAM_COUNT + "=" + Math.abs(steps);
        }

        // This will determine whether this event is preceded by a mouse move command with the same
        // coordinates.
        // It will be replaced if yes.
        boolean replaceLastMove = false;
        if (enableMouseMoves) {
          if (events.size() > 0 && events.get(events.size() - 1) instanceof MouseEvent) {
            MouseEvent me = (MouseEvent) events.get(events.size() - 1);
            if (me.getID() == MouseEvent.MOUSE_MOVED
                && e.getX() == me.getX()
                && e.getY() == me.getY()) {
              replaceLastMove = true;
            }
          }
        }

        // Generate coordinates
        s += " " + MouseCommand.PARAM_TO + "=" + parser.pointToString(e.getPoint());

        // Insert the command to the current editor
        insertLine(s, count > 1 || replaceLastMove, true, false);
        dragInProgress = false;
      }
      insertEvent(e);
    }
  }