/** Draws the selected annotation. */
        private void draw(
            final ExecutionUnit process,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel,
            final boolean printing) {
          if (!visualizer.isActive()) {
            return;
          }

          // paint the selected annotation
          WorkflowAnnotation selected = model.getSelected();
          if (selected != null) {
            // only draw in correct execution unit
            if (selected.getProcess().equals(process)) {
              // only paint annotation if not editing
              if (editPane == null) {
                // paint the annotation itself
                Graphics2D g2P = (Graphics2D) g2.create();
                drawer.drawAnnotation(selected, g2P, printing);
                g2P.dispose();
              } else {
                // only paint shadow
                Rectangle2D loc = selected.getLocation();
                g2.draw(
                    new Rectangle2D.Double(
                        loc.getX() - 1,
                        loc.getY() - 1,
                        editPane.getBounds().getWidth() + 1,
                        editPane.getBounds().getHeight() + 1));
                Rectangle2D shadowFrameEditor =
                    new Rectangle2D.Double(
                        loc.getX(),
                        loc.getY(),
                        editPane.getBounds().getWidth() + 1,
                        editPane.getBounds().getHeight() + 1);
                ProcessDrawUtils.drawShadow(shadowFrameEditor, g2);
                if (editPanel != null) {
                  Point absolute = new Point(editPanel.getX(), editPanel.getY());
                  Point relative =
                      ProcessDrawUtils.convertToRelativePoint(
                          absolute, rendererModel.getProcessIndex(process), rendererModel);
                  Rectangle2D shadowFramePanel =
                      new Rectangle2D.Double(
                          relative.getX(), relative.getY(), EDIT_PANEL_WIDTH, EDIT_PANEL_HEIGHT);
                  ProcessDrawUtils.drawShadow(shadowFramePanel, g2);
                }
              }
            }
          }
        }
 /**
  * Updates the position and size of the edit panel relative to the given location.
  *
  * @param loc the location the edit panel should be relative to
  * @param absolute if {@code true} the loc is treated as absolute position on the process
  *     renderer; if {@code false} it is treated as relative to the current process
  */
 private void updateEditPanelPosition(final Rectangle2D loc, final boolean absolute) {
   int panelX = (int) loc.getCenterX() - EDIT_PANEL_WIDTH / 2;
   int panelY = (int) loc.getY() - EDIT_PANEL_HEIGHT - ProcessDrawer.PADDING;
   // if panel would be outside process renderer, fix it
   if (panelX < WorkflowAnnotation.MIN_X) {
     panelX = WorkflowAnnotation.MIN_X;
   }
   if (panelY < 0) {
     panelY = (int) loc.getMaxY() + ProcessDrawer.PADDING;
   }
   // last fallback is cramped to the bottom. If that does not fit either, don't care
   if (panelY + EDIT_PANEL_HEIGHT > view.getSize().getHeight() - ProcessDrawer.PADDING * 2) {
     panelY = (int) loc.getMaxY();
   }
   int index = view.getModel().getProcessIndex(model.getSelected().getProcess());
   if (absolute) {
     editPanel.setBounds(panelX, panelY, EDIT_PANEL_WIDTH, EDIT_PANEL_HEIGHT);
   } else {
     Point absoluteP =
         ProcessDrawUtils.convertToAbsoluteProcessPoint(
             new Point(panelX, panelY), index, rendererModel);
     editPanel.setBounds(
         (int) absoluteP.getX(), (int) absoluteP.getY(), EDIT_PANEL_WIDTH, EDIT_PANEL_HEIGHT);
   }
 }
 /** Draws the annotation icon on operators. */
 private void draw(
     final Operator operator,
     final Graphics2D g2,
     final ProcessRendererModel rendererModel,
     final boolean printing) {
   // Draw annotation icons regardless of active state
   WorkflowAnnotations annotations = rendererModel.getOperatorAnnotations(operator);
   if (annotations == null || annotations.isEmpty()) {
     return;
   }
   Rectangle2D frame = rendererModel.getOperatorRect(operator);
   int xOffset = (IMAGE_ANNOTATION.getIconWidth() + 2) * 2;
   ProcessDrawUtils.getIcon(operator, IMAGE_ANNOTATION)
       .paintIcon(
           null,
           g2,
           (int) (frame.getX() + frame.getWidth() - xOffset),
           (int) (frame.getY() + frame.getHeight() - IMAGE_ANNOTATION.getIconHeight() - 1));
 }
  /**
   * Creates and adds the JEditorPane for the currently selected annotation to the process renderer.
   */
  private void createEditor() {
    final WorkflowAnnotation selected = model.getSelected();
    Rectangle2D loc = selected.getLocation();

    // JEditorPane to edit the comment string
    editPane = new JEditorPane("text/html", "");
    editPane.setBorder(null);
    int paneX = (int) loc.getX();
    int paneY = (int) loc.getY();
    int index = view.getModel().getProcessIndex(selected.getProcess());
    Point absolute =
        ProcessDrawUtils.convertToAbsoluteProcessPoint(
            new Point(paneX, paneY), index, rendererModel);
    editPane.setBounds(
        (int) absolute.getX(), (int) absolute.getY(), (int) loc.getWidth(), (int) loc.getHeight());
    editPane.setText(AnnotationDrawUtils.createStyledCommentString(selected));
    // use proxy for paste actions to trigger reload of editor after paste
    Action pasteFromClipboard = editPane.getActionMap().get(PASTE_FROM_CLIPBOARD_ACTION_NAME);
    Action paste = editPane.getActionMap().get(PASTE_ACTION_NAME);
    if (pasteFromClipboard != null) {
      editPane
          .getActionMap()
          .put(
              PASTE_FROM_CLIPBOARD_ACTION_NAME,
              new PasteAnnotationProxyAction(pasteFromClipboard, this));
    }
    if (paste != null) {
      editPane.getActionMap().put(PASTE_ACTION_NAME, new PasteAnnotationProxyAction(paste, this));
    }
    // use proxy for transfer actions to convert e.g. HTML paste to plaintext paste
    editPane.setTransferHandler(new TransferHandlerAnnotationPlaintext(editPane));
    // IMPORTANT: Linebreaks do not work without the following!
    // this filter inserts a \r every time the user enters a newline
    // this signal is later used to convert newline to <br/>
    ((HTMLDocument) editPane.getDocument())
        .setDocumentFilter(
            new DocumentFilter() {

              @Override
              public void insertString(
                  DocumentFilter.FilterBypass fb, int offs, String str, AttributeSet a)
                  throws BadLocationException {
                // this is never called..
                super.insertString(
                    fb,
                    offs,
                    str.replaceAll("\n", "\n" + AnnotationDrawUtils.ANNOTATION_HTML_NEWLINE_SIGNAL),
                    a);
              }

              @Override
              public void replace(FilterBypass fb, int offs, int length, String str, AttributeSet a)
                  throws BadLocationException {
                if (selected instanceof OperatorAnnotation) {
                  // operator annotations have a character limit, enforce here
                  try {
                    int existingLength =
                        AnnotationDrawUtils.getPlaintextFromEditor(editPane, false).length()
                            - length;
                    if (existingLength + str.length() > OperatorAnnotation.MAX_CHARACTERS) {
                      // insert at beginning or end is fine, cut off excess characters
                      if (existingLength <= 0 || offs >= existingLength) {
                        int acceptableLength = OperatorAnnotation.MAX_CHARACTERS - existingLength;
                        int newLength = Math.max(acceptableLength, 0);
                        str = str.substring(0, newLength);
                      } else {
                        // inserting into middle, do NOT paste at all
                        return;
                      }
                    }
                  } catch (IOException e) {
                    // should not happen, if it does this is our smallest problem -> ignore
                  }
                }
                super.replace(
                    fb,
                    offs,
                    length,
                    str.replaceAll("\n", "\n" + AnnotationDrawUtils.ANNOTATION_HTML_NEWLINE_SIGNAL),
                    a);
              }
            });
    // set background color
    if (selected.getStyle().getAnnotationColor() == AnnotationColor.TRANSPARENT) {
      editPane.setBackground(Color.WHITE);
    } else {
      editPane.setBackground(selected.getStyle().getAnnotationColor().getColorHighlight());
    }
    editPane.addFocusListener(
        new FocusAdapter() {

          @Override
          public void focusLost(final FocusEvent e) {
            // right-click menu
            if (e.isTemporary()) {
              return;
            }
            if (editPane != null && e.getOppositeComponent() != null) {
              // style edit menu, no real focus loss
              if (SwingUtilities.isDescendingFrom(e.getOppositeComponent(), editPanel)) {
                return;
              }
              if (SwingUtilities.isDescendingFrom(e.getOppositeComponent(), colorOverlay)) {
                return;
              }
              if (colorOverlay.getParent() == e.getOppositeComponent()) {
                return;
              }

              saveEdit(selected);
              removeEditor();
            }
          }
        });
    editPane.addKeyListener(
        new KeyAdapter() {

          /** keep track of control down so Ctrl+Enter works but Enter+Ctrl not */
          private boolean controlDown;

          @Override
          public void keyPressed(final KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
              controlDown = true;
            }
            // consume so undo/redo etc are not passed to the process
            if (SwingTools.isControlOrMetaDown(e) && e.getKeyCode() == KeyEvent.VK_Z
                || e.getKeyCode() == KeyEvent.VK_Y) {
              e.consume();
            }
          }

          @Override
          public void keyReleased(final KeyEvent e) {
            switch (e.getKeyCode()) {
              case KeyEvent.VK_CONTROL:
                controlDown = false;
                break;
              case KeyEvent.VK_ENTER:
                if (!controlDown) {
                  updateEditorHeight(selected);
                } else {
                  // if control was down before Enter was pressed, save & exit
                  saveEdit(selected);
                  removeEditor();
                  model.setSelected(null);
                }
                break;
              case KeyEvent.VK_ESCAPE:
                // ignore changes on escape
                removeEditor();
                model.setSelected(null);
                break;
              default:
                break;
            }
          }
        });
    editPane
        .getDocument()
        .addDocumentListener(
            new DocumentListener() {

              @Override
              public void removeUpdate(DocumentEvent e) {
                updateEditorHeight(selected);
              }

              @Override
              public void insertUpdate(DocumentEvent e) {
                updateEditorHeight(selected);
              }

              @Override
              public void changedUpdate(DocumentEvent e) {
                updateEditorHeight(selected);
              }
            });
    view.add(editPane);
    editPane.selectAll();
  }