public ResourceCard(String key, String i18nKey) {

    this.key = key;
    String titleName = I18N.getGUIMessageOrNull("gui.cards." + i18nKey + ".title");
    if (titleName != null) {
      this.title =
          "<html><div style=\"text-align: center;\"><body>"
              + titleName.replaceFirst(" ", "<br>")
              + "</html></body>";
      this.tip = I18N.getGUIMessage("gui.cards." + i18nKey + ".tip");
    } else {
      // default case if no name and icon are defined:
      key = key.replace("_", " ");
      char[] stringArray = key.toCharArray();
      stringArray[0] = Character.toUpperCase(stringArray[0]);
      String defaultName = new String(stringArray);

      this.title =
          "<html><div style=\"text-align: center;\"><body>"
              + defaultName.replaceFirst(" ", "<br>")
              + "</html></body>";
      this.tip = defaultName;
    }

    String iconName = I18N.getGUIMessageOrNull("gui.cards." + i18nKey + ".icon");
    if (iconName != null) {
      this.icon = SwingTools.createIcon("32/" + iconName);
    } else {
      this.icon = SwingTools.createIcon("32/data_information.png"); // default icon
    }
  }
/** @author Sebastian Land */
public class CollectionTreeCellRenderer extends DefaultTreeCellRenderer {

  private static final long serialVersionUID = 1L;

  private final Icon ICON_FOLDER_OPEN = SwingTools.createIcon("16/folder.png");
  private final Icon ICON_FOLDER_CLOSED = SwingTools.createIcon("16/folder_closed.png");

  private final Map<IOObject, String> childNames = new HashMap<IOObject, String>();

  public CollectionTreeCellRenderer(IOObject collection) {
    if (collection instanceof MetaModel) {
      MetaModel mm = (MetaModel) collection;
      for (int i = 0; i < mm.getModels().size(); i++) {
        childNames.put(mm.getModels().get(i), mm.getModelNames().get(i));
      }
    }
  }

  @Override
  public Component getTreeCellRendererComponent(
      JTree tree,
      Object value,
      boolean selected,
      boolean expanded,
      boolean leaf,
      int row,
      boolean hasFocus) {
    JLabel label =
        (JLabel)
            super.getTreeCellRendererComponent(
                tree, value, selected, expanded, leaf, row, hasFocus);
    if (value instanceof ResultObject) {
      ResultObject ro = (ResultObject) value;
      String name = childNames.get(ro);
      if (name == null) {
        name = ro.getName();
      }
      label.setText("<html>" + name + " (<small>" + ro.getSource() + "</small>)</html>");
      if (ro instanceof IOObjectCollection) {
        label.setIcon(expanded ? ICON_FOLDER_OPEN : ICON_FOLDER_CLOSED);
      } else {
        Icon resultIcon = ro.getResultIcon();
        label.setIcon(resultIcon);
      }
    } else if (value instanceof IOObject) {
      IOObject ioo = (IOObject) value;
      label.setText(ioo.getClass().getSimpleName());
    }
    return label;
  }
}
 static {
   resultIcon = SwingTools.createIcon("16/" + RESULT_ICON_NAME);
 }
/**
 * This class handles event hooks and draw decorators registered to the {@link ProcessRendererView}
 * for workflow annotations.
 *
 * @author Marco Boeck
 * @since 6.4.0
 */
public final class AnnotationsDecorator {

  /** name of the paste action (context menu) */
  private static final String PASTE_ACTION_NAME = "paste";

  /** name of the paste from clipboard action (ctrl+v) */
  private static final String PASTE_FROM_CLIPBOARD_ACTION_NAME = DefaultEditorKit.pasteAction;

  /** icon depicting annotations on an operator */
  private static final ImageIcon IMAGE_ANNOTATION = SwingTools.createIcon("16/note_pinned.png");

  /** the width of the edit panel above/below the annotation editor */
  private static final int EDIT_PANEL_WIDTH = 190;

  /** the height of the edit panel above/below the annotation editor */
  private static final int EDIT_PANEL_HEIGHT = 30;

  /** the width of the color panel above/below the annotation editor */
  private static final int EDIT_COLOR_PANEL_WIDTH = 215;

  /** the height of the color panel above/below the annotation editor */
  private static final int EDIT_COLOR_PANEL_HEIGHT = EDIT_PANEL_HEIGHT;

  /** the pane which can be used to edit the text */
  private JEditorPane editPane;

  /** the panel which can be used to edit color and alignment during editing */
  private JPanel editPanel;

  /** the dialog panel which can be used to edit color while editing */
  private JDialog colorOverlay;

  /** the button opening the color overlay */
  private JButton colorButton;

  /** the process renderer */
  private final ProcessRendererView view;

  /** the process renderer model */
  private final ProcessRendererModel rendererModel;

  /** the annotation visualizer instance */
  private final AnnotationsVisualizer visualizer;

  /** the model backing this decorator */
  private final AnnotationsModel model;

  /** the drawer for the annotations */
  private final AnnotationDrawer drawer;

  /** the event handling for annotations */
  private final AnnotationEventHook hook;

  /** draws process (free-flowing) annotations behind operators */
  private ProcessDrawDecorator processAnnotationDrawer =
      new ProcessDrawDecorator() {

        @Override
        public void draw(
            final ExecutionUnit process,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel) {
          draw(process, g2, rendererModel, false);
        }

        @Override
        public void print(
            final ExecutionUnit process,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel) {
          draw(process, g2, rendererModel, true);
        }

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

          // background annotations
          WorkflowAnnotations annotations = rendererModel.getProcessAnnotations(process);
          if (annotations != null) {
            for (WorkflowAnnotation anno : annotations.getAnnotationsDrawOrder()) {
              // selected is drawn by highlight decorator
              if (anno.equals(model.getSelected())) {
                continue;
              }

              // paint the annotation itself
              Graphics2D g2P = (Graphics2D) g2.create();
              drawer.drawAnnotation(anno, g2P, printing);
              g2P.dispose();
            }
          }
        }
      };

  /** draws operator annotations */
  private ProcessDrawDecorator operatorAnnotationDrawer =
      new ProcessDrawDecorator() {

        @Override
        public void draw(
            final ExecutionUnit process,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel) {
          draw(process, g2, rendererModel, false);
        }

        @Override
        public void print(
            final ExecutionUnit process,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel) {
          draw(process, g2, rendererModel, true);
        }

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

          // operator attached annotations
          List<Operator> selectedOperators = rendererModel.getSelectedOperators();
          for (Operator operator : process.getOperators()) {
            if (selectedOperators.contains(operator)) {
              continue;
            }
            drawOpAnno(operator, g2, rendererModel, printing);
          }
          // selected operators annotations need to be drawn over non selected ones
          for (Operator selOp : selectedOperators) {
            if (process.equals(selOp.getExecutionUnit())) {
              drawOpAnno(selOp, g2, rendererModel, printing);
            }
          }
        }

        /** Draws the annotation for the given operator (if he has one). */
        private void drawOpAnno(
            final Operator operator,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel,
            final boolean printing) {
          WorkflowAnnotations annotations = rendererModel.getOperatorAnnotations(operator);
          if (annotations == null) {
            return;
          }
          for (WorkflowAnnotation anno : annotations.getAnnotationsDrawOrder()) {
            // selected is drawn by highlight decorator
            if (anno.equals(model.getSelected())) {
              continue;
            }

            // paint the annotation itself
            Graphics2D g2P = (Graphics2D) g2.create();
            drawer.drawAnnotation(anno, g2P, printing);
            g2P.dispose();
          }
        }
      };

  /** draws process (free-flowing) annotations which are selected. Drawn over operators */
  private ProcessDrawDecorator workflowAnnotationDrawerHighlight =
      new ProcessDrawDecorator() {

        @Override
        public void draw(
            final ExecutionUnit process,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel) {
          draw(process, g2, rendererModel, false);
        }

        @Override
        public void print(ExecutionUnit process, Graphics2D g2, ProcessRendererModel model) {
          draw(process, g2, rendererModel, true);
        }

        /** 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);
                }
              }
            }
          }
        }
      };

  /** draws annotation icons on operators */
  private OperatorDrawDecorator opAnnotationIconDrawer =
      new OperatorDrawDecorator() {

        @Override
        public void draw(
            final Operator operator,
            final Graphics2D g2,
            final ProcessRendererModel rendererModel) {
          draw(operator, g2, rendererModel, true);
        }

        @Override
        public void print(Operator operator, Graphics2D g2, ProcessRendererModel model) {
          draw(operator, g2, rendererModel, true);
        }

        /** 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));
        }
      };

  /** listener which triggers color panel moving if required */
  private ComponentListener colorPanelMover =
      new ComponentAdapter() {

        @Override
        public void componentResized(ComponentEvent e) {
          updateColorPanelPosition();
        }

        @Override
        public void componentMoved(ComponentEvent e) {
          updateColorPanelPosition();
        }
      };

  /**
   * Creates a new workflow annotation decorator
   *
   * @param view the process renderer instance
   * @param visualizer the annotation visualizer instance
   * @param model the model backing this instance
   */
  public AnnotationsDecorator(
      final ProcessRendererView view,
      final AnnotationsVisualizer visualizer,
      final AnnotationsModel model) {
    this.view = view;
    this.model = model;
    this.rendererModel = view.getModel();
    this.drawer = new AnnotationDrawer(model, rendererModel);
    this.hook = new AnnotationEventHook(this, model, visualizer, drawer, view, rendererModel);
    this.visualizer = visualizer;
  }

  /**
   * Start inline editing of the selected annotation. If no annotation is selected, does nothing.
   */
  public void editSelected() {
    if (model.getSelected() == null) {
      return;
    }
    // editor to actually edit comment string
    removeEditor();
    createEditor();

    // panel to edit alignment and color
    createEditPanel();

    editPane.requestFocusInWindow();
    view.repaint();
  }

  /** Stop all editing and remove editors. */
  public void reset() {
    drawer.reset();
    removeEditor();
  }

  /** Registers the event hooks and draw decorators to the process renderer. */
  void registerEventHooks() {
    view.addDrawDecorator(processAnnotationDrawer, RenderPhase.ANNOTATIONS);
    view.addDrawDecorator(operatorAnnotationDrawer, RenderPhase.OPERATOR_ANNOTATIONS);
    view.addDrawDecorator(workflowAnnotationDrawerHighlight, RenderPhase.OVERLAY);
    view.addDrawDecorator(opAnnotationIconDrawer);

    view.getOverviewPanelDrawer().addDecorator(processAnnotationDrawer, RenderPhase.ANNOTATIONS);
    view.getOverviewPanelDrawer()
        .addDecorator(operatorAnnotationDrawer, RenderPhase.OPERATOR_ANNOTATIONS);

    hook.registerDecorators();

    // this listener makes the color edit panel move when required
    view.addComponentListener(colorPanelMover);
    ApplicationFrame.getApplicationFrame().addComponentListener(colorPanelMover);
  }

  /** Removes the event hooks and draw decorators from the process renderer. */
  void unregisterDecorators() {
    view.removeDrawDecorator(processAnnotationDrawer, RenderPhase.ANNOTATIONS);
    view.removeDrawDecorator(operatorAnnotationDrawer, RenderPhase.OPERATOR_ANNOTATIONS);
    view.removeDrawDecorator(workflowAnnotationDrawerHighlight, RenderPhase.OVERLAY);
    view.removeDrawDecorator(opAnnotationIconDrawer);

    view.getOverviewPanelDrawer().removeDecorator(processAnnotationDrawer, RenderPhase.ANNOTATIONS);
    view.getOverviewPanelDrawer()
        .removeDecorator(operatorAnnotationDrawer, RenderPhase.OPERATOR_ANNOTATIONS);

    hook.unregisterEventHooks();

    view.removeComponentListener(colorPanelMover);
    ApplicationFrame.getApplicationFrame().removeComponentListener(colorPanelMover);
  }

  /**
   * 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();
  }

  /**
   * Creates and adds the edit panel for the currently selected annotation to the process renderer.
   */
  private void createEditPanel() {
    final WorkflowAnnotation selected = model.getSelected();
    Rectangle2D loc = selected.getLocation();

    // panel containing buttons
    editPanel = new JPanel();
    editPanel.setCursor(Cursor.getDefaultCursor());
    editPanel.setLayout(new BoxLayout(editPanel, BoxLayout.LINE_AXIS));
    updateEditPanelPosition(loc, false);
    editPanel.setOpaque(true);
    editPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
    // consume mouse events so focus is not lost
    editPanel.addMouseListener(
        new MouseAdapter() {

          @Override
          public void mouseReleased(MouseEvent e) {
            e.consume();
          }

          @Override
          public void mousePressed(MouseEvent e) {
            e.consume();
          }

          @Override
          public void mouseClicked(MouseEvent e) {
            e.consume();
          }
        });

    // add alignment controls
    final List<JButton> alignmentButtonList = new LinkedList<JButton>();
    for (AnnotationAlignment align : AnnotationAlignment.values()) {
      final Action action = align.makeAlignmentChangeAction(model, model.getSelected());
      final JButton alignButton = new JButton();
      alignButton.setIcon((Icon) action.getValue(Action.SMALL_ICON));
      alignButton.setBorderPainted(false);
      alignButton.setBorder(null);
      alignButton.setFocusable(false);
      if (align == selected.getStyle().getAnnotationAlignment()) {
        alignButton.setBackground(Color.LIGHT_GRAY);
      }
      alignButton.addActionListener(
          new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
              removeColorPanel();
              colorButton.setBackground(null);
              for (JButton button : alignmentButtonList) {
                button.setBackground(null);
              }
              alignButton.setBackground(Color.LIGHT_GRAY);

              int caretPos = editPane.getCaretPosition();
              // remember if we were at last position because doc length can change after 1st
              // save
              boolean lastPos = caretPos == editPane.getDocument().getLength();
              int selStart = editPane.getSelectionStart();
              int selEnd = editPane.getSelectionEnd();
              // change alignment and save current comment
              action.actionPerformed(e);
              saveEdit(selected);
              // reload edit pane with changes
              editPane.setText(AnnotationDrawUtils.createStyledCommentString(selected));
              // special handling for documents of length 1 to avoid not being able to type
              if (editPane.getDocument().getLength() == 1) {
                caretPos = 1;
              } else if (lastPos) {
                caretPos = editPane.getDocument().getLength();
              } else {
                caretPos = Math.min(editPane.getDocument().getLength(), caretPos);
              }
              editPane.setCaretPosition(caretPos);
              if (selEnd - selStart > 0) {
                editPane.setSelectionStart(selStart);
                editPane.setSelectionEnd(selEnd);
              }
              editPane.requestFocusInWindow();
            }
          });
      editPanel.add(alignButton);
      alignmentButtonList.add(alignButton);
    }

    // add small empty space
    editPanel.add(Box.createHorizontalStrut(2));

    // add color controls
    colorOverlay = new JDialog(ApplicationFrame.getApplicationFrame());
    colorOverlay.setCursor(Cursor.getDefaultCursor());
    colorOverlay
        .getRootPane()
        .setLayout(new BoxLayout(colorOverlay.getRootPane(), BoxLayout.LINE_AXIS));
    colorOverlay.setUndecorated(true);
    colorOverlay.getRootPane().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
    colorOverlay.setFocusable(false);
    colorOverlay.setAutoRequestFocus(false);
    // consume mouse events so focus is not lost
    colorOverlay.addMouseListener(
        new MouseAdapter() {

          @Override
          public void mouseReleased(MouseEvent e) {
            e.consume();
          }

          @Override
          public void mousePressed(MouseEvent e) {
            e.consume();
          }

          @Override
          public void mouseClicked(MouseEvent e) {
            e.consume();
          }
        });

    for (final AnnotationColor color : AnnotationColor.values()) {
      final Action action = color.makeColorChangeAction(model, selected);
      JButton colChangeButton = new JButton();
      colChangeButton.setText(null);
      colChangeButton.setBorderPainted(false);
      colChangeButton.setBorder(null);
      colChangeButton.setFocusable(false);
      final Icon icon =
          SwingTools.createIconFromColor(
              color.getColor(), Color.BLACK, 16, 16, new Rectangle2D.Double(1, 1, 14, 14));
      colChangeButton.setIcon(icon);
      colChangeButton.addActionListener(
          new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
              // change color and save current comment
              action.actionPerformed(e);
              saveEdit(selected);
              // set edit pane bg color
              editPane.requestFocusInWindow();
              if (color == AnnotationColor.TRANSPARENT) {
                editPane.setBackground(Color.WHITE);
              } else {
                editPane.setBackground(color.getColorHighlight());
              }

              // adapt color of main button, remove color panel
              colorButton.setIcon(icon);
              if (removeColorPanel()) {
                colorButton.setBackground(null);
                view.repaint();
              }
            }
          });

      colorOverlay.getRootPane().add(colChangeButton);
    }

    colorButton = new JButton("\u25BE");
    colorButton.setBorderPainted(false);
    colorButton.setFocusable(false);
    AnnotationColor color = selected.getStyle().getAnnotationColor();
    colorButton.setIcon(
        SwingTools.createIconFromColor(
            color.getColor(), Color.BLACK, 16, 16, new Rectangle2D.Double(1, 1, 14, 14)));
    colorButton.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            if (removeColorPanel()) {
              colorButton.setBackground(null);
              view.repaint();
              return;
            }

            updateColorPanelPosition();
            colorOverlay.setVisible(true);
            colorButton.setBackground(Color.LIGHT_GRAY);
            editPane.requestFocusInWindow();
            view.repaint();
          }
        });
    editPanel.add(colorButton);

    // add separator
    JLabel separator =
        new JLabel() {

          private static final long serialVersionUID = 1L;

          @Override
          public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;

            g2.setColor(Color.LIGHT_GRAY);
            g2.drawLine(2, 0, 2, 20);
          }
        };
    separator.setText(" "); // dummy text to show label
    editPanel.add(separator);

    // add delete button
    final JButton deleteButton =
        new JButton(
            I18N.getMessage(I18N.getGUIBundle(), "gui.action.workflow.annotation.delete.label"));
    deleteButton.setForeground(Color.RED);
    deleteButton.setContentAreaFilled(false);
    deleteButton.setFocusable(false);
    deleteButton.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            model.deleteAnnotation(selected);
            removeEditor();
          }
        });
    deleteButton.addMouseListener(
        new MouseAdapter() {

          @Override
          @SuppressWarnings({"unchecked", "rawtypes"})
          public void mouseExited(MouseEvent e) {
            Font font = deleteButton.getFont();
            Map attributes = font.getAttributes();
            attributes.put(TextAttribute.UNDERLINE, -1);
            deleteButton.setFont(font.deriveFont(attributes));
          }

          @SuppressWarnings({"unchecked", "rawtypes"})
          @Override
          public void mouseEntered(MouseEvent e) {
            Font font = deleteButton.getFont();
            Map attributes = font.getAttributes();
            attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
            deleteButton.setFont(font.deriveFont(attributes));
          }
        });
    editPanel.add(deleteButton);

    // add panel to view
    view.add(editPanel);
  }

  /**
   * Saves the content of the comment editor as the new comment for the given {@link
   * WorkflowAnnotation}.
   *
   * @param selected the annotation for which the content of the editor pane should be saved as new
   *     comment
   */
  private void saveEdit(final WorkflowAnnotation selected) {
    if (editPane == null) {
      return;
    }
    HTMLDocument document = (HTMLDocument) editPane.getDocument();
    StringWriter writer = new StringWriter();
    try {
      editPane.getEditorKit().write(writer, document, 0, document.getLength());
    } catch (IndexOutOfBoundsException | IOException | BadLocationException e1) {
      // should not happen
      LogService.getRoot()
          .log(
              Level.WARNING,
              "com.rapidminer.gui.flow.processrendering.annotations.AnnotationsDecorator.cannot_save");
    }
    String comment = writer.toString();
    comment = AnnotationDrawUtils.removeStyleFromComment(comment);
    Rectangle2D loc = selected.getLocation();
    Rectangle2D newLoc =
        new Rectangle2D.Double(
            loc.getX(),
            loc.getY(),
            editPane.getBounds().getWidth(),
            editPane.getBounds().getHeight());
    selected.setLocation(newLoc);

    boolean overflowing = false;
    int prefHeight =
        AnnotationDrawUtils.getContentHeight(
            AnnotationDrawUtils.createStyledCommentString(comment, selected.getStyle()),
            (int) newLoc.getWidth());
    if (prefHeight > newLoc.getHeight()) {
      overflowing = true;
    }
    selected.setOverflowing(overflowing);

    model.setAnnotationComment(selected, comment);
  }

  /**
   * Reloads the editor pane content to match editor and annotation styling. After this call, the
   * editor pane displays the annotation in the same way as it is displayed in the process renderer.
   */
  void updateEditorContent() {
    SwingUtilities.invokeLater(
        new Runnable() {

          @Override
          public void run() {
            if (editPane == null || model.getSelected() == null) {
              return;
            }
            HTMLDocument document = (HTMLDocument) editPane.getDocument();
            StringWriter writer = new StringWriter();
            try {
              editPane.getEditorKit().write(writer, document, 0, document.getLength());
            } catch (IndexOutOfBoundsException | IOException | BadLocationException e1) {
              // should not happen
              LogService.getRoot()
                  .log(
                      Level.WARNING,
                      "com.rapidminer.gui.flow.processrendering.annotations.AnnotationsDecorator.cannot_save");
            }
            String comment = writer.toString();
            comment = AnnotationDrawUtils.removeStyleFromComment(comment);
            int caretPos = editPane.getCaretPosition();
            boolean lastPos = caretPos == editPane.getDocument().getLength();
            editPane.setText(
                AnnotationDrawUtils.createStyledCommentString(
                    comment, model.getSelected().getStyle()));
            if (lastPos) {
              caretPos = editPane.getDocument().getLength();
            }
            caretPos = Math.min(caretPos, editPane.getDocument().getLength());
            editPane.setCaretPosition(caretPos);
            editPane.requestFocusInWindow();
          }
        });
  }

  /** Removes the annotation editor from the process renderer. */
  private void removeEditor() {
    if (editPane != null) {
      view.remove(editPane);
      editPane = null;
    }
    if (editPanel != null) {
      view.remove(editPanel);
      editPanel = null;
    }
    removeColorPanel();

    // this makes sure that pressing F2 afterwards works
    // otherwise nothing is focused until the next click
    view.requestFocusInWindow();
    view.repaint();
  }

  /** Tries to remove the color panel. If not found or not showing, does nothing. */
  private boolean removeColorPanel() {
    if (colorOverlay != null && colorOverlay.isShowing()) {
      colorOverlay.dispose();
      return true;
    }

    return false;
  }

  /**
   * 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);
    }
  }

  /**
   * Makes sure the current editor height matches its content if the annotation was never resized.
   * If the annotation has been manually resized before, does nothing.
   *
   * @param anno the annotation currently in the editor
   */
  private void updateEditorHeight(final WorkflowAnnotation anno) {
    if (anno.wasResized()) {
      return;
    }

    Rectangle bounds = editPane.getBounds();
    // height is either the pref height or the current height, depending on what is bigger
    int prefHeight;
    if (anno instanceof ProcessAnnotation) {
      prefHeight =
          (int) Math.max(getContentHeightOfEditor((int) bounds.getWidth()), bounds.getHeight());
    } else {
      prefHeight =
          Math.max(
              getContentHeightOfEditor((int) bounds.getWidth()), OperatorAnnotation.MIN_HEIGHT);
    }
    Rectangle newBounds =
        new Rectangle(
            (int) bounds.getX(), (int) bounds.getY(), (int) bounds.getWidth(), prefHeight);
    if (!bounds.equals(newBounds)) {
      editPane.setBounds(newBounds);
      updateEditPanelPosition(newBounds, true);
      view.getModel().fireAnnotationMiscChanged(anno);
    }
  }

  /** Updates the location of the color edit panel (if shown). */
  private void updateColorPanelPosition() {
    if (editPanel != null && colorOverlay != null) {
      int colorPanelX = (int) editPanel.getLocationOnScreen().getX() + colorButton.getX();
      int colorPanelY =
          (int) (editPanel.getLocationOnScreen().getY() + editPanel.getBounds().getHeight());
      colorOverlay.setBounds(
          colorPanelX, colorPanelY, EDIT_COLOR_PANEL_WIDTH, EDIT_COLOR_PANEL_HEIGHT);
    }
  }

  /**
   * Calculates the preferred height of the editor pane with the given fixed width.
   *
   * @param width the width of the pane
   * @return the preferred height given the current editor pane content or {@code -1} if there was a
   *     problem. Value will never exceed {@link WorkflowAnnotation#MAX_HEIGHT}
   */
  private int getContentHeightOfEditor(final int width) {
    HTMLDocument document = (HTMLDocument) editPane.getDocument();
    StringWriter writer = new StringWriter();
    try {
      editPane.getEditorKit().write(writer, document, 0, document.getLength());
    } catch (IndexOutOfBoundsException | IOException | BadLocationException e1) {
      // should not happen
      return -1;
    }
    String comment = writer.toString();
    comment = AnnotationDrawUtils.removeStyleFromComment(comment);

    int maxHeight =
        model.getSelected() instanceof ProcessAnnotation
            ? ProcessAnnotation.MAX_HEIGHT
            : OperatorAnnotation.MAX_HEIGHT;
    return Math.min(
        AnnotationDrawUtils.getContentHeight(
            AnnotationDrawUtils.createStyledCommentString(comment, model.getSelected().getStyle()),
            width),
        maxHeight);
  }
}
/** @author Simon Fischer */
public class ErrorTable extends JPanel implements Dockable, ProcessEditor {

  private static final long serialVersionUID = -954934789614113138L;

  private static final ImageIcon IMAGE_WARNING = SwingTools.createIcon("16/sign_warning.png");

  private static final ImageIcon IMAGE_ERROR = SwingTools.createIcon("16/error.png");

  private static final ImageIcon IMAGE_NO_QUICKFIX =
      SwingTools.createIcon(
          "16/"
              + I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.no_quickfix_available.icon"));

  private static final ImageIcon IMAGE_QUICKFIX =
      SwingTools.createIcon(
          "16/" + I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.choose_quickfix.icon"));

  private static final String[] COLUMN_NAMES = {
    I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.header.message.label"),
    I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.header.fixes.label"),
    I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.header.location.label")
  };

  private static final String[] COLUMN_TOOLTIPS = {
    I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.header.message.tip"),
    I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.header.fixes.tip"),
    I18N.getMessage(I18N.getGUIBundle(), "gui.errortable.header.location.tip")
  };

  private final MainFrame mainFrame;

  private final TableCellRenderer iconRenderer =
      new DefaultTableCellRenderer() {
        private static final long serialVersionUID = 1L;

        @Override
        public Component getTableCellRendererComponent(
            JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
          if (value instanceof ProcessSetupError) {
            JLabel label =
                (JLabel)
                    super.getTableCellRendererComponent(
                        table,
                        ((ProcessSetupError) value).getMessage(),
                        isSelected,
                        hasFocus,
                        row,
                        column);
            switch (((ProcessSetupError) value).getSeverity()) {
              case WARNING:
                label.setIcon(IMAGE_WARNING);
                break;
              case ERROR:
                label.setIcon(IMAGE_ERROR);
                break;
              default:
                label.setIcon(null); // cannot happen				
            }

            return label;
          } else if (value instanceof Port) {
            JLabel label =
                (JLabel)
                    super.getTableCellRendererComponent(
                        table, ((Port) value).getSpec(), isSelected, hasFocus, row, column);
            label.setIcon(
                ((Port) value)
                    .getPorts()
                    .getOwner()
                    .getOperator()
                    .getOperatorDescription()
                    .getSmallIcon());
            return label;
          } else if (value instanceof Operator) {
            JLabel label =
                (JLabel)
                    super.getTableCellRendererComponent(
                        table, ((Operator) value).getName(), isSelected, hasFocus, row, column);
            label.setIcon(((Operator) value).getOperatorDescription().getSmallIcon());
            return label;
          } else {
            if (column == 1) {
              JLabel label =
                  (JLabel)
                      super.getTableCellRendererComponent(
                          table, value, isSelected, hasFocus, row, column);
              if (value == null) {
                label.setIcon(IMAGE_NO_QUICKFIX);
                label.setText(
                    I18N.getMessage(
                        I18N.getGUIBundle(), "gui.errortable.no_quickfix_available.label"));
              }
              if (value instanceof List) {
                label.setIcon(IMAGE_QUICKFIX);
                label.setText(
                    I18N.getMessage(
                        I18N.getGUIBundle(),
                        "gui.errortable.choose_quickfix.label",
                        ((List) value).size()));
              }
              if (value instanceof QuickFix) {
                QuickFix quickFix = (QuickFix) value;
                label.setIcon((Icon) quickFix.getAction().getValue(Action.SMALL_ICON));
                label.setText(quickFix.toString());
              }
              return label;
            } else {
              JLabel label =
                  (JLabel)
                      super.getTableCellRendererComponent(
                          table, value, isSelected, hasFocus, row, column);
              label.setIcon(null);
              return label;
            }
          }
        }
      };

  private final ExtendedJTable table =
      new ExtendedJTable() {
        private static final long serialVersionUID = 3731781319040565353L;

        @Override
        public TableCellRenderer getCellRenderer(int row, int column) {
          return iconRenderer;
        }

        @Override
        public void populatePopupMenu(JPopupMenu menu) {
          List<? extends QuickFix> fixes = errors.get(getSelectedRow()).getQuickFixes();
          if (!fixes.isEmpty()) {
            JMenu fixMenu = new ResourceMenu("quick_fixes");
            for (QuickFix fix : fixes) {
              fixMenu.add(fix.getAction());
            }
            menu.add(fixMenu);
            menu.addSeparator();
          }
          super.populatePopupMenu(menu);
        }

        @Override
        protected JTableHeader createDefaultTableHeader() {
          return new JTableHeader(columnModel) {
            private static final long serialVersionUID = -2000774622129683602L;

            @Override
            public String getToolTipText(MouseEvent e) {
              java.awt.Point p = e.getPoint();
              int index = columnModel.getColumnIndexAtX(p.x);
              int realIndex = columnModel.getColumn(index).getModelIndex();
              return COLUMN_TOOLTIPS[realIndex];
            };
          };
        };

        @Override
        public String getToolTipText(MouseEvent e) {
          Point p = e.getPoint();
          int realColumnIndex = convertColumnIndexToModel(columnAtPoint(p));
          int rowIndex = rowAtPoint(p);
          if (rowIndex >= 0 && rowIndex < getRowCount() && realColumnIndex == 1) {
            Object value = getModel().getValueAt(rowIndex, realColumnIndex);
            if (value == null) {
              return I18N.getMessage(
                  I18N.getGUIBundle(), "gui.errortable.no_quickfix_available.tip");
            }
            if (value instanceof List) {
              return I18N.getMessage(
                  I18N.getGUIBundle(), "gui.errortable.choose_quickfix.tip", ((List) value).size());
            }
            if (value instanceof QuickFix) {
              return ((QuickFix) value).toString();
            }
          }
          return super.getToolTipText(e);
        }
      };
  private final JLabel headerLabel = new JLabel();

  private final JToggleButton onlyCurrent =
      new JToggleButton(
          new ResourceAction(true, "error_table_only_current") {
            private static final long serialVersionUID = -1454330266199555397L;

            @Override
            public void actionPerformed(ActionEvent e) {
              updateErrors();
            }
          });

  private List<ProcessSetupError> errors = new LinkedList<ProcessSetupError>();

  private final AbstractTableModel model =
      new AbstractTableModel() {
        private static final long serialVersionUID = 1L;

        @Override
        public String getColumnName(int col) {
          return COLUMN_NAMES[col];
        }

        @Override
        public boolean isCellEditable(int row, int col) {
          return false;
        }

        @Override
        public int getColumnCount() {
          return 3;
        }

        @Override
        public int getRowCount() {
          return errors.size();
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
          ProcessSetupError error = errors.get(rowIndex);
          switch (columnIndex) {
            case 0:
              return error;
            case 1:
              List<? extends QuickFix> fixes = error.getQuickFixes();
              if (fixes.size() > 1) {
                return fixes;
              }
              if (fixes.size() == 1) {
                return fixes.get(0);
              }
              return null;
            case 2:
              if (error instanceof MetaDataError) {
                return ((MetaDataError) error).getPort();
              } else {
                return error.getOwner().getOperator();
              }
            default:
              return null;
          }
        }
      };
  private Process currentProcess;

  public ErrorTable(final MainFrame mainFrame) {
    super(new BorderLayout());
    this.mainFrame = mainFrame;
    onlyCurrent.setSelected(false);
    table.setShowVerticalLines(false);
    table.setModel(model);
    table.installToolTip();

    table.getColumnModel().getColumn(0).setPreferredWidth(400);
    table.getColumnModel().getColumn(1).setPreferredWidth(200);
    table.getColumnModel().getColumn(2).setPreferredWidth(150);

    headerLabel.setHorizontalAlignment(SwingConstants.CENTER);
    headerLabel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));

    table.setBorder(null);
    JScrollPane scrollPane = new ExtendedJScrollPane(table);
    scrollPane.setBorder(null);
    add(scrollPane, BorderLayout.CENTER);
    ViewToolBar toolBar = new ViewToolBar();
    toolBar.add(onlyCurrent);
    onlyCurrent.setText(null);
    toolBar.add(headerLabel);
    add(toolBar, BorderLayout.NORTH);

    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    table.addMouseListener(
        new MouseAdapter() {
          @Override
          public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() == 2) {
              switch (table.getSelectedColumn()) {
                  // quick fixes
                case 1:
                  List<? extends QuickFix> quickFixes =
                      errors.get(table.getSelectedRow()).getQuickFixes();
                  if (quickFixes.size() == 1) {
                    quickFixes.get(0).apply();
                  }
                  if (quickFixes.size() > 1) {
                    new QuickFixDialog(quickFixes).setVisible(true);
                  }
                  break;
                default:
                  ProcessSetupError error = errors.get(table.getSelectedRow());
                  Operator op = error.getOwner().getOperator();
                  ErrorTable.this.mainFrame.selectOperator(op);
                  // other
              }
            }
          }
        });
  }

  private void updateErrors() {
    if ((currentOperator != null) && onlyCurrent.isSelected()) {
      fill(currentOperator);
    } else {
      if (currentProcess != null) {
        fill(currentProcess.getRootOperator());
      }
    }
  }

  private void fill(Operator root) {
    int numTotal = root.getProcess().getRootOperator().getErrorList().size();
    errors = root.getErrorList();
    String errorString;
    switch (errors.size()) {
      case 0:
        errorString = "No problems found";
        break;
      case 1:
        errorString = "One potential problem";
        break;
      default:
        errorString = errors.size() + " potential problems";
        break;
    }
    if (errors.size() != numTotal) {
      errorString = errorString + " (" + (numTotal - errors.size()) + " Filtered)";
    }
    headerLabel.setText(errorString);
    model.fireTableDataChanged();
  }

  @Override
  public void processChanged(Process process) {
    currentProcess = process;
    updateErrors();
  }

  @Override
  public void processUpdated(Process process) {
    currentProcess = process;
    updateErrors();
  }

  @Override
  public void setSelection(List<Operator> selection) {
    this.currentOperator = selection.isEmpty() ? null : selection.get(0);
    updateErrors();
  }

  public static final String ERROR_TABLE_DOCK_KEY = "error_table";
  private final DockKey DOCK_KEY = new ResourceDockKey(ERROR_TABLE_DOCK_KEY);

  {
    DOCK_KEY.setDockGroup(MainFrame.DOCK_GROUP_ROOT);
  }

  private Operator currentOperator;

  @Override
  public Component getComponent() {
    return this;
  }

  @Override
  public DockKey getDockKey() {
    return DOCK_KEY;
  }
}
  /** Setup the GUI. */
  private void setupGUI() {
    JPanel mainPanel = new JPanel();
    this.setContentPane(mainPanel);

    // start layout
    mainPanel.setLayout(new GridBagLayout());
    GridBagConstraints gbc = new GridBagConstraints();

    gbc.gridx = 0;
    gbc.gridy = 0;
    gbc.fill = GridBagConstraints.NONE;
    gbc.weightx = 0;
    gbc.anchor = GridBagConstraints.WEST;
    gbc.insets = new Insets(5, 5, 2, 5);
    JLabel valueLabel =
        new JLabel(
            I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.value.label"));
    this.add(valueLabel, gbc);

    gbc.gridx = 1;
    gbc.gridy = 0;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.weightx = 1;
    valueField = new JTextField();
    valueField.setInputVerifier(
        new InputVerifier() {

          @Override
          public boolean verify(JComponent input) {
            return verifyValueInput(input);
          }
        });
    valueField.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.value.tip"));
    this.add(valueField, gbc);

    gbc.gridx = 0;
    gbc.gridy = 1;
    gbc.fill = GridBagConstraints.NONE;
    gbc.weightx = 0;
    JLabel colorLabel =
        new JLabel(
            I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.color.label"));
    this.add(colorLabel, gbc);

    gbc.gridx = 1;
    gbc.gridy = 1;
    lineColorButton =
        new JButton(
            new ResourceAction(true, "edit_parallel_line.select_line_color") {

              private static final long serialVersionUID = 1L;

              @Override
              public void actionPerformed(ActionEvent e) {
                createLineColorDialog();
              }
            });
    this.add(lineColorButton, gbc);

    gbc.gridx = 0;
    gbc.gridy = 2;
    JLabel widthLabel =
        new JLabel(
            I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.width.label"));
    this.add(widthLabel, gbc);

    gbc.gridx = 1;
    gbc.gridy = 2;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.weightx = 1;
    widthField = new JTextField();
    widthField.setInputVerifier(
        new InputVerifier() {

          @Override
          public boolean verify(JComponent input) {
            return verifyWidthInput(input);
          }
        });
    widthField.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.width.tip"));
    this.add(widthField, gbc);

    gbc.gridx = 0;
    gbc.gridy = 3;
    gbc.fill = GridBagConstraints.NONE;
    gbc.weightx = 0;
    JLabel styleLabel =
        new JLabel(
            I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.line_style.label"));
    this.add(styleLabel, gbc);

    gbc.gridx = 1;
    gbc.gridy = 3;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.weightx = 1;
    lineStyleCombobox = new JComboBox(LineStyle.values());
    ((DefaultComboBoxModel) lineStyleCombobox.getModel()).removeElement(LineStyle.NONE);
    lineStyleCombobox.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.line_style.tip"));
    lineStyleCombobox.setSelectedItem(LineStyle.SOLID);
    this.add(lineStyleCombobox, gbc);

    gbc.gridx = 0;
    gbc.gridy = 4;
    gbc.gridwidth = 2;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.weightx = 1;
    gbc.weighty = 0;
    gbc.anchor = GridBagConstraints.CENTER;
    gbc.insets = new Insets(15, 5, 5, 5);
    this.add(new JSeparator(), gbc);

    gbc.gridx = 0;
    gbc.gridy = 5;
    gbc.gridwidth = 1;
    gbc.fill = GridBagConstraints.NONE;
    gbc.anchor = GridBagConstraints.WEST;
    gbc.insets = new Insets(5, 5, 5, 5);
    okButton =
        new JButton(I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.ok.label"));
    okButton.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.ok.tip"));
    okButton.setIcon(
        SwingTools.createIcon(
            "24/" + I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.ok.icon")));
    okButton.setMnemonic(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.ok.mne")
            .toCharArray()[0]);
    okButton.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            boolean successful = editLine();
            // don't dispose dialog if not successful
            if (!successful) {
              return;
            }

            EditParallelLineDialog.this.dispose();
          }
        });
    okButton.addKeyListener(
        new KeyAdapter() {

          @Override
          public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
              okButton.doClick();
            }
          }
        });
    this.add(okButton, gbc);

    gbc.gridx = 1;
    gbc.gridy = 5;
    gbc.fill = GridBagConstraints.NONE;
    gbc.anchor = GridBagConstraints.EAST;
    cancelButton =
        new JButton(
            I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.cancel.label"));
    cancelButton.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.cancel.tip"));
    cancelButton.setIcon(
        SwingTools.createIcon(
            "24/"
                + I18N.getMessage(
                    I18N.getGUIBundle(), "gui.action.edit_parallel_line.cancel.icon")));
    cancelButton.setMnemonic(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.cancel.mne")
            .toCharArray()[0]);
    cancelButton.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            // cancel requested, close dialog
            EditParallelLineDialog.this.dispose();
          }
        });
    this.add(cancelButton, gbc);

    // misc settings
    this.setMinimumSize(new Dimension(275, 225));
    // center dialog
    this.setLocationRelativeTo(null);
    this.setTitle(
        I18N.getMessage(I18N.getGUIBundle(), "gui.action.edit_parallel_line.title.label"));
    this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
    this.setModal(true);
    this.addWindowListener(
        new WindowAdapter() {

          @Override
          public void windowActivated(WindowEvent e) {
            cancelButton.requestFocusInWindow();
          }
        });
  }
  private class BreakpointButton extends ToggleDropDownButton implements ToggleActionListener {

    private static final long serialVersionUID = 7364886954405951709L;

    private final Icon IMAGE_BREAKPOINTS = SwingTools.createIcon("16/breakpoints.png");
    private final Icon IMAGE_BREAKPOINT_BEFORE = SwingTools.createIcon("16/breakpoint_up.png");
    private final Icon IMAGE_BREAKPOINT_AFTER = SwingTools.createIcon("16/breakpoint_down.png");

    {
      for (int i = 0; i < mainFrame.getActions().TOGGLE_BREAKPOINT.length; i++) {
        mainFrame.getActions().TOGGLE_BREAKPOINT[i].addToggleActionListener(this);
      }
    }

    public BreakpointButton() {
      super(
          new ResourceAction(true, "breakpoint_after") {
            private static final long serialVersionUID = -8913366165786891652L;

            @Override
            public void actionPerformed(ActionEvent e) {
              if (mainFrame.getActions().TOGGLE_BREAKPOINT[0].isSelected()
                  || mainFrame.getActions().TOGGLE_BREAKPOINT[1].isSelected()) {
                mainFrame.getActions().TOGGLE_BREAKPOINT[0].resetAction(false);
                mainFrame.getActions().TOGGLE_BREAKPOINT[1].resetAction(false);
                return;
              }
              mainFrame.getActions().TOGGLE_BREAKPOINT[1].actionPerformed(null);
            }
          });
    }

    @Override
    public void setSelected(boolean selected) {
      Icon breakpointIcon;
      if (operator != null && operator.hasBreakpoint()) {
        super.setSelected(true);
        if (operator.getNumberOfBreakpoints() == 1) {
          if (operator.hasBreakpoint(BreakpointListener.BREAKPOINT_BEFORE)) {
            breakpointIcon = IMAGE_BREAKPOINT_BEFORE;
          } else {
            breakpointIcon = IMAGE_BREAKPOINT_AFTER;
          }
        } else {
          breakpointIcon = IMAGE_BREAKPOINTS;
        }
      } else {
        super.setSelected(false);
        breakpointIcon = IMAGE_BREAKPOINT_AFTER;
      }
      setIcon(breakpointIcon);
    }

    @Override
    protected JPopupMenu getPopupMenu() {
      JPopupMenu menu = new JPopupMenu();
      for (int i = 0; i < mainFrame.getActions().TOGGLE_BREAKPOINT.length; i++) {
        menu.add(mainFrame.getActions().TOGGLE_BREAKPOINT[i].createMenuItem());
      }
      return menu;
    }
  }
/**
 * This panel displays parameters of an operator. It refreshes in either of these cases:
 *
 * <ul>
 *   <li>A new operator is selected.
 *   <li>The {@link Parameters} of the current operator (which are observed) change in a way such
 *       that the parameter value differs from the one displayed by the editor. This should only
 *       happen if a parameter value is changed programmatically, e.g. by an operator.
 *   <li>{@link #processUpdated(Process)} is called and {@link #getProperties()} returns a different
 *       list than the one returned during the last {@link #setupComponents()}.
 *   <li>When changing to expert mode.
 * </ul>
 *
 * @author Simon Fischer, Tobias Malbrecht
 */
public class OperatorPropertyPanel extends PropertyPanel implements Dockable, ProcessEditor {

  private static final long serialVersionUID = 6056794546696461864L;

  private class BreakpointButton extends ToggleDropDownButton implements ToggleActionListener {

    private static final long serialVersionUID = 7364886954405951709L;

    private final Icon IMAGE_BREAKPOINTS = SwingTools.createIcon("16/breakpoints.png");
    private final Icon IMAGE_BREAKPOINT_BEFORE = SwingTools.createIcon("16/breakpoint_up.png");
    private final Icon IMAGE_BREAKPOINT_AFTER = SwingTools.createIcon("16/breakpoint_down.png");

    {
      for (int i = 0; i < mainFrame.getActions().TOGGLE_BREAKPOINT.length; i++) {
        mainFrame.getActions().TOGGLE_BREAKPOINT[i].addToggleActionListener(this);
      }
    }

    public BreakpointButton() {
      super(
          new ResourceAction(true, "breakpoint_after") {
            private static final long serialVersionUID = -8913366165786891652L;

            @Override
            public void actionPerformed(ActionEvent e) {
              if (mainFrame.getActions().TOGGLE_BREAKPOINT[0].isSelected()
                  || mainFrame.getActions().TOGGLE_BREAKPOINT[1].isSelected()) {
                mainFrame.getActions().TOGGLE_BREAKPOINT[0].resetAction(false);
                mainFrame.getActions().TOGGLE_BREAKPOINT[1].resetAction(false);
                return;
              }
              mainFrame.getActions().TOGGLE_BREAKPOINT[1].actionPerformed(null);
            }
          });
    }

    @Override
    public void setSelected(boolean selected) {
      Icon breakpointIcon;
      if (operator != null && operator.hasBreakpoint()) {
        super.setSelected(true);
        if (operator.getNumberOfBreakpoints() == 1) {
          if (operator.hasBreakpoint(BreakpointListener.BREAKPOINT_BEFORE)) {
            breakpointIcon = IMAGE_BREAKPOINT_BEFORE;
          } else {
            breakpointIcon = IMAGE_BREAKPOINT_AFTER;
          }
        } else {
          breakpointIcon = IMAGE_BREAKPOINTS;
        }
      } else {
        super.setSelected(false);
        breakpointIcon = IMAGE_BREAKPOINT_AFTER;
      }
      setIcon(breakpointIcon);
    }

    @Override
    protected JPopupMenu getPopupMenu() {
      JPopupMenu menu = new JPopupMenu();
      for (int i = 0; i < mainFrame.getActions().TOGGLE_BREAKPOINT.length; i++) {
        menu.add(mainFrame.getActions().TOGGLE_BREAKPOINT[i].createMenuItem());
      }
      return menu;
    }
  }

  private final BreakpointButton breakpointButton;

  private final MainFrame mainFrame;

  private static final Icon WARNING_ICON = SwingTools.createIcon("16/sign_warning.png");

  private final JLabel headerLabel = new JLabel("");

  private final Font selectedFont = headerLabel.getFont().deriveFont(Font.BOLD);

  private final Font unselectedFont = headerLabel.getFont();

  private final JLabel expertModeHintLabel = new JLabel("");

  private Operator operator;

  private final Observer<String> parameterObserver =
      new Observer<String>() {
        @Override
        public void update(Observable<String> observable, String key) {
          PropertyValueCellEditor editor = getEditorForKey(key);
          if (editor != null) {
            ParameterType type = operator.getParameters().getParameterType(key);
            String editorValue = type.toString(editor.getCellEditorValue());
            String opValue = operator.getParameters().getParameterOrNull(key);
            if (((opValue != null) && (editorValue == null))
                || ((opValue == null) && (editorValue != null))
                || ((opValue != null) && (editorValue != null) && !opValue.equals(editorValue))) {
              editor.getTableCellEditorComponent(null, opValue, false, 0, 1);
            }
          } else {
            setupComponents();
          }
        }
      };

  private JSpinner compatibilityLevelSpinner = new JSpinner(new CompatibilityLevelSpinnerModel());
  private ResourceLabel compatibilityLabel = new ResourceLabel("compatibility_level");
  private JPanel compatibilityPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));;

  public OperatorPropertyPanel(final MainFrame mainFrame) {
    super();
    this.mainFrame = mainFrame;
    breakpointButton = new BreakpointButton();
    headerLabel.setHorizontalAlignment(SwingConstants.CENTER);
    expertModeHintLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
    expertModeHintLabel.setIcon(WARNING_ICON);
    expertModeHintLabel.addMouseListener(
        new MouseListener() {
          public void mouseReleased(MouseEvent e) {
            mainFrame.TOGGLE_EXPERT_MODE_ACTION.actionPerformed(null);
          }

          public void mouseClicked(MouseEvent e) {}

          public void mouseEntered(MouseEvent e) {}

          public void mouseExited(MouseEvent e) {}

          public void mousePressed(MouseEvent e) {}
        });
    expertModeHintLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
    expertModeHintLabel.setHorizontalAlignment(SwingConstants.LEFT);
    setupComponents();

    compatibilityLevelSpinner.addChangeListener(
        new ChangeListener() {
          @Override
          public void stateChanged(ChangeEvent e) {
            // compatibility level
            OperatorVersion[] versionChanges = operator.getIncompatibleVersionChanges();
            if (versionChanges.length != 0) {
              OperatorVersion latestChange = versionChanges[versionChanges.length - 1];
              if (latestChange.isAtLeast(operator.getCompatibilityLevel())) {
                compatibilityLabel.setIcon(WARNING_ICON);
              } else {
                compatibilityLabel.setIcon(SwingTools.createIcon("16/ok.png"));
              }
            }
          }
        });
  }

  @Override
  protected String getValue(ParameterType type) {
    return operator.getParameters().getParameterOrNull(type.getKey());
  }

  @Override
  protected void setValue(Operator operator, ParameterType type, String value) {
    if (value.length() == 0) {
      value = null;
    }
    operator.setParameter(type.getKey(), value);
  }

  @Override
  protected List<ParameterType> getProperties() {
    List<ParameterType> visible = new LinkedList<ParameterType>();
    int hidden = 0;
    if (operator != null) {
      for (ParameterType type : operator.getParameters().getParameterTypes()) {
        if (type.isHidden()) {
          continue;
        }
        if (!isExpertMode() && type.isExpert()) {
          hidden++;
          continue;
        }
        visible.add(type);
      }
    }

    if (hidden > 0) {
      expertModeHintLabel.setText(hidden + " hidden expert parameter" + (hidden == 1 ? "" : "s"));
      expertModeHintLabel.setVisible(true);
    } else {
      expertModeHintLabel.setVisible(false);
    }
    return visible;
  }

  @Override
  public void processChanged(Process process) {}

  @Override
  public void processUpdated(Process process) {
    setNameFor(operator);
    // check if we have editors for the current parameters. If not, refresh.
    int count = 0; // count hits. If we have to many, also refresh
    List<ParameterType> properties = getProperties();
    if (properties.size() != getNumberOfEditors()) {
      setupComponents();
      return;
    }
    for (ParameterType type : properties) {
      if (hasEditorFor(type)) {
        count++;
      } else {
        setupComponents();
        return;
      }
    }
    if (count != properties.size()) {
      setupComponents();
    }
  }

  @Override
  public void setSelection(List<Operator> selection) {
    Operator operator = selection.isEmpty() ? null : selection.get(0);
    if (operator == this.operator) {
      return;
    }
    if (this.operator != null) {
      this.operator.getParameters().removeObserver(parameterObserver);
    }
    this.operator = operator;
    if (operator != null) {
      this.operator.getParameters().addObserver(parameterObserver, true);
      breakpointButton.setEnabled(true);

      // compatibility level
      OperatorVersion[] versionChanges = operator.getIncompatibleVersionChanges();
      if (versionChanges.length == 0) {
        // no incompatible versions exist
        compatibilityLevelSpinner.setVisible(false);
        compatibilityLabel.setVisible(false);
      } else {
        compatibilityLevelSpinner.setVisible(true);
        compatibilityLabel.setVisible(true);
        ((CompatibilityLevelSpinnerModel) compatibilityLevelSpinner.getModel())
            .setOperator(operator);
      }

    } else {
      breakpointButton.setEnabled(false);
    }
    setNameFor(operator);
    setupComponents();
  }

  private void setNameFor(Operator operator) {
    if (operator != null) {
      headerLabel.setFont(selectedFont);
      if (operator.getName().equals(operator.getOperatorDescription().getName())) {
        headerLabel.setText(operator.getName());
      } else {
        headerLabel.setText(
            operator.getName() + " (" + operator.getOperatorDescription().getName() + ")");
      }
      headerLabel.setIcon(operator.getOperatorDescription().getSmallIcon());

    } else {
      headerLabel.setFont(unselectedFont);
      headerLabel.setText("No Operator Selected");
      headerLabel.setIcon(null);
    }
  }

  @Override
  public Component getComponent() {
    if (dockableComponent == null) {
      JScrollPane scrollPane = new ExtendedJScrollPane(this);
      scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
      scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
      scrollPane.setBorder(null);

      dockableComponent = new JPanel(new BorderLayout());

      JPanel toolBarPanel = new JPanel(new BorderLayout());
      ViewToolBar toolBar = new ViewToolBar();
      JToggleButton toggleExpertModeButton =
          mainFrame.TOGGLE_EXPERT_MODE_ACTION.createToggleButton();
      toggleExpertModeButton.setText(null);
      toolBar.add(toggleExpertModeButton);
      Action infoOperatorAction =
          new InfoOperatorAction() {
            private static final long serialVersionUID = 6758272768665592429L;

            @Override
            protected Operator getOperator() {
              return mainFrame.getFirstSelectedOperator();
            }
          };
      toolBar.add(infoOperatorAction);
      JToggleButton enableOperatorButton =
          new ToggleActivationItem(mainFrame.getActions()).createToggleButton();
      enableOperatorButton.setText(null);
      toolBar.add(enableOperatorButton);
      Action renameOperatorAction =
          new ResourceAction(true, "rename_in_processrenderer") {
            {
              setCondition(OPERATOR_SELECTED, MANDATORY);
            }

            private static final long serialVersionUID = -3104160320178045540L;

            @Override
            public void actionPerformed(ActionEvent e) {
              Operator operator = mainFrame.getFirstSelectedOperator();
              String name = SwingTools.showInputDialog("rename_operator", operator.getName());
              if (name != null && name.length() > 0) {
                operator.rename(name);
              }
            }
          };
      toolBar.add(renameOperatorAction);
      toolBar.add(new DeleteOperatorAction());
      breakpointButton.addToToolBar(toolBar);
      //			toolBar.add(mainFrame.getActions().MAKE_DIRTY_ACTION);
      toolBarPanel.add(toolBar, BorderLayout.NORTH);

      JPanel headerPanel = new JPanel();
      headerPanel.setBackground(SwingTools.LIGHTEST_BLUE);
      headerPanel.add(headerLabel);
      headerPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY));
      toolBarPanel.add(headerPanel, BorderLayout.SOUTH);

      dockableComponent.add(toolBarPanel, BorderLayout.NORTH);
      dockableComponent.add(scrollPane, BorderLayout.CENTER);

      // compatibility level and warnings
      JPanel southPanel = new JPanel(new BorderLayout());
      southPanel.add(expertModeHintLabel, BorderLayout.CENTER);
      compatibilityLabel.setLabelFor(compatibilityLevelSpinner);
      compatibilityLevelSpinner.setPreferredSize(
          new Dimension(80, (int) compatibilityLevelSpinner.getPreferredSize().getHeight()));
      compatibilityPanel.add(compatibilityLabel);
      compatibilityPanel.add(compatibilityLevelSpinner);
      southPanel.add(compatibilityPanel, BorderLayout.SOUTH);

      dockableComponent.add(southPanel, BorderLayout.SOUTH);
    }
    return dockableComponent;
  }

  // implements Dockable

  public static final String PROPERTY_EDITOR_DOCK_KEY = "property_editor";
  private final DockKey DOCK_KEY = new ResourceDockKey(PROPERTY_EDITOR_DOCK_KEY);

  {
    DOCK_KEY.setDockGroup(MainFrame.DOCK_GROUP_ROOT);
  }

  private JPanel dockableComponent;

  @Override
  public DockKey getDockKey() {
    return DOCK_KEY;
  }

  public boolean isExpertMode() {
    return mainFrame.TOGGLE_EXPERT_MODE_ACTION.isSelected();
  }

  @Override
  protected Operator getOperator() {
    return operator;
  }
}
예제 #9
0
 static {
   int counter = 0;
   for (IconSize size : IconSize.values()) {
     ICONS[counter++] = SwingTools.createIcon(size.getSize() + "/" + ICON_NAME);
   }
 }
예제 #10
0
 static {
   icon = SwingTools.createIcon("48/" + ICON_NAME);
 }
/**
 * A dialog to create and edit regular expressions. Can be created with a given predefined regular
 * expression (normally a previously set value). A collection of item strings can be given to the
 * dialog which are then available as shortcuts. Additionally, a list shows which of these items
 * match the regular expression. If the item collection is null, both lists will not be visible.
 *
 * <p>The dialog shows an inline preview displaying where the given pattern matches. It also shows a
 * list of matches, together with their matching groups.
 *
 * @author Tobias Malbrecht, Dominik Halfkann, Simon Fischer
 */
public class RegexpPropertyDialog extends ButtonDialog {

  private static final long serialVersionUID = 5396725165122306231L;

  private RegexpSearchStyledDocument inlineSearchDocument = null;
  private RegexpReplaceStyledDocument inlineReplaceDocument = null;

  private JTabbedPane testExp = null;

  private DefaultListModel<RegExpResult> resultsListModel = new DefaultListModel<RegExpResult>();

  private static String[][] regexpConstructs = {
    {
      ".",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.any_character")
    },
    {
      "[]",
      I18N.getMessage(
          I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.bracket_expression")
    },
    {
      "[^]",
      I18N.getMessage(
          I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.not_bracket_expression")
    },
    {
      "()",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.capturing_group")
    },
    {
      "?",
      I18N.getMessage(
          I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.zero_one_quantifier")
    },
    {
      "*",
      I18N.getMessage(
          I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.zero_more_quantifier")
    },
    {
      "+",
      I18N.getMessage(
          I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.one_more_quantifier")
    },
    {
      "{n}",
      I18N.getMessage(
          I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.exact_quantifier")
    },
    {
      "{min,}",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.min_quantifier")
    },
    {
      "{min,max}",
      I18N.getMessage(
          I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.min_max_quantifier")
    },
    {
      "|",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.constructs.disjunction")
    },
  };

  // adjust the caret by this amount upon insertion
  private static int[] regexpConstructInsertionCaretAdjustment = {
    0, -1, -1, -1, 0, 0, 0, -1, -2, -1, -5, 0,
  };

  // select these construct characters upon insertion
  private static int[][] regexpConstructInsertionSelectionIndices = {
    {1, 1}, {1, 1}, {2, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 2}, {1, 4}, {1, 8}, {1, 1},
  };

  // enclose selected by construct
  private static boolean[] regexpConstructInsertionEncloseSelected = {
    false, true, true, true, false, false, false, false, false, false, false,
  };

  private static String[][] regexpShortcuts = {
    {".*", I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.arbitrary")},
    {
      "[a-zA-Z]",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.letter")
    },
    {
      "[a-z]",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.lowercase_letter")
    },
    {
      "[A-Z]",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.uppercase_letter")
    },
    {"[0-9]", I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.digit")},
    {"\\w", I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.word")},
    {"\\W", I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.non_word")},
    {
      "\\s",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.whitespace")
    },
    {
      "\\S",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.non_whitespace")
    },
    {
      "[-!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]_`{|}~]",
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.shortcuts.punctuation")
    },
  };

  private static final String ERROR_MESSAGE =
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.error.label");

  private static final Icon ERROR_ICON =
      SwingTools.createIcon(
          "16/" + I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.error.icon"));

  private static final String NO_ERROR_MESSAGE =
      I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.no_error.label");

  private static final Icon NO_ERROR_ICON =
      SwingTools.createIcon(
          "16/"
              + I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.no_error.icon"));

  private String infoText;

  private final JTextField regexpTextField;

  private final JTextField replacementTextField;

  private JList<String> itemShortcutsList;

  private DefaultListModel<String> matchedItemsListModel;

  private final Collection<String> items;

  private boolean supportsItems = false;

  private final JLabel errorMessage;

  private JButton okButton;

  JCheckBox cbCaseInsensitive;
  JCheckBox cbComments;
  JCheckBox cbMultiline;
  JCheckBox cbDotall;
  JCheckBox cbUnicodeCase;

  /** Class representing a single regexp search result. * */
  private class RegExpResult {

    private String match;
    private String[] groups;
    private int number;
    private boolean empty = false;

    public RegExpResult(String match, String[] groups, int number) {
      this.match = match;
      this.groups = groups;
      this.number = number;
    }

    public RegExpResult() {
      // empty result
      empty = true;
    }

    @Override
    public String toString() {
      String output = "";
      if (!empty) {
        output +=
            "<html>"
                + "<span style=\"font-size:11px;margin:2px 0 2px 4px;\">"
                +
                // "Match "+number+": <b>'"+match+"'</b>" +
                I18N.getMessage(
                    I18N.getGUIBundle(),
                    "gui.dialog.parameter.regexp.regular_expression.result_list.match",
                    number,
                    "<b>'" + Tools.escapeHTML(match) + "'</b>")
                + "</span>";
        if (groups.length > 0) {
          output += "<ol style=\"margin:1px 0 0 24px\">";
          for (int i = 0; i < groups.length; i++) {
            // output += "<li>Group matches: <b>'" + groups[i] +"'</b></li>";
            output +=
                "<li>"
                    + I18N.getMessage(
                        I18N.getGUIBundle(),
                        "gui.dialog.parameter.regexp.regular_expression.result_list.group_match",
                        "<b>'" + Tools.escapeHTML(groups[i]) + "'</b>")
                    + "</li>";
          }
          output += "</ul>";
        }
        output += "</html>";
      } else {
        output +=
            "<html>"
                + "<span style=\"font-size:11px;margin:2px 0 2px 4px;\">"
                + I18N.getMessage(
                    I18N.getGUIBundle(),
                    "gui.dialog.parameter.regexp.regular_expression.result_list.empty")
                + "</span>";
        output += "</html>";
      }
      return output;
    }
  }

  /** A StyledDocument providing a live regexp search * */
  private class RegexpSearchStyledDocument extends DefaultStyledDocument {

    private static final long serialVersionUID = 1L;

    private Matcher matcher = Pattern.compile("").matcher("");

    Style keyStyle;
    Style rootStyle;

    {
      rootStyle = addStyle("root", null);

      keyStyle = addStyle("key", rootStyle);
      StyleConstants.setBackground(keyStyle, Color.YELLOW);
    }

    @Override
    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
      super.insertString(offs, str, a);
      checkDocument();
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {
      super.remove(offs, len);
      checkDocument();
    }

    private void checkDocument() {

      setCharacterAttributes(0, getLength(), rootStyle, true);
      try {
        matcher.reset(getText(0, getLength()));
        int count = 0;
        resultsListModel.clear();
        while (matcher.find()) {
          if (matcher.end() <= matcher.start()) {
            continue;
          }
          setCharacterAttributes(matcher.start(), matcher.end() - matcher.start(), keyStyle, true);

          String[] groups = new String[matcher.groupCount()];
          for (int i = 1; i <= matcher.groupCount(); i++) {
            groups[i - 1] = matcher.group(i);
          }
          resultsListModel.addElement(
              new RegExpResult(
                  this.getText(matcher.start(), matcher.end() - matcher.start()),
                  groups,
                  count + 1));
          count++;
        }

        if (count == 0) {
          // add empty element
          resultsListModel.addElement(new RegExpResult());
        }

        testExp.setTitleAt(
            1,
            I18N.getMessage(
                    I18N.getGUIBundle(),
                    "gui.dialog.parameter.regexp.regular_expression.result_list.title")
                + " ("
                + count
                + ")");
        inlineReplaceDocument.setText(matcher.replaceAll(replacementTextField.getText()));
        updateRegexpOptions();
      } catch (BadLocationException ex) {
        LogService.getRoot()
            .log(Level.WARNING, RegexpPropertyDialog.class.getName() + ".bad_location", ex);
      }
    }

    public void updatePattern(String pattern) {
      this.matcher = Pattern.compile(pattern).matcher("");
      checkDocument();
    }

    public void clearResults() {
      resultsListModel.clear();
      resultsListModel.addElement(new RegExpResult());
      testExp.setTitleAt(
          1,
          I18N.getMessage(
                  I18N.getGUIBundle(),
                  "gui.dialog.parameter.regexp.regular_expression.result_list.title")
              + " (0)");
      setCharacterAttributes(0, getLength(), rootStyle, true);
    }
  }

  /** A StyledDocument with an added setText() method for interting the replaced text * */
  private class RegexpReplaceStyledDocument extends DefaultStyledDocument {

    private static final long serialVersionUID = 1L;

    public RegexpReplaceStyledDocument() {
      super();
    }

    public void setText(String text) {
      try {
        remove(0, getLength());
        insertString(0, text, null);
      } catch (BadLocationException e) {
        LogService.getRoot()
            .log(Level.WARNING, RegexpPropertyDialog.class.getName() + ".bad_location", e);
      }
    }
  }

  public RegexpPropertyDialog(
      final Collection<String> items, String predefinedRegexp, String description) {
    super(
        ApplicationFrame.getApplicationFrame(),
        "parameter.regexp",
        ModalityType.APPLICATION_MODAL,
        new Object[] {});
    this.items = items;
    this.supportsItems = items != null;
    this.infoText =
        "<html>"
            + I18N.getMessage(I18N.getGUIBundle(), getKey() + ".title")
            + ": <br/>"
            + description
            + "</html>";
    Dimension size = new Dimension(420, 500);
    this.setMinimumSize(size);
    this.setPreferredSize(size);

    JPanel panel = new JPanel(createGridLayout(1, supportsItems ? 2 : 1));

    // create regexp text field
    regexpTextField = new JTextField(predefinedRegexp);
    regexpTextField.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.tip"));
    regexpTextField.addKeyListener(
        new KeyListener() {

          @Override
          public void keyPressed(KeyEvent e) {}

          @Override
          public void keyReleased(KeyEvent e) {
            fireRegularExpressionUpdated();
          }

          @Override
          public void keyTyped(KeyEvent e) {}
        });
    regexpTextField.requestFocus();

    // create replacement text field
    replacementTextField = new JTextField();
    replacementTextField.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.replacement.tip"));
    replacementTextField.addKeyListener(
        new KeyListener() {

          @Override
          public void keyPressed(KeyEvent e) {}

          @Override
          public void keyReleased(KeyEvent e) {
            fireRegularExpressionUpdated();
          }

          @Override
          public void keyTyped(KeyEvent e) {}
        });

    // create inline search documents
    inlineSearchDocument = new RegexpSearchStyledDocument();
    inlineReplaceDocument = new RegexpReplaceStyledDocument();

    // create search results list
    DefaultListCellRenderer resultCellRenderer =
        new DefaultListCellRenderer() {

          private static final long serialVersionUID = 1L;

          @Override
          public Component getListCellRendererComponent(
              JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            setBackground(list.getBackground());
            setForeground(list.getForeground());
            setBorder(getNoFocusBorder());
            return this;
          }

          private Border getNoFocusBorder() {
            Border border = BorderFactory.createMatteBorder(0, 0, 1, 0, Color.gray);
            return border;
          }
        };

    JList<RegExpResult> regexpFindingsList = new JList<RegExpResult>(resultsListModel);
    regexpFindingsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    regexpFindingsList.setLayoutOrientation(JList.VERTICAL);
    regexpFindingsList.setCellRenderer(resultCellRenderer);

    // regexp panel on left side of dialog
    JPanel regexpPanel = new JPanel(new GridBagLayout());
    regexpPanel.setBorder(
        createTitledBorder(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.border")));
    GridBagConstraints c = new GridBagConstraints();
    c.insets = new Insets(4, 4, 4, 0);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.fill = GridBagConstraints.BOTH;
    regexpPanel.add(regexpTextField, c);

    // make shortcut button
    final Action nullAction = new DefaultAction();
    PlainArrowDropDownButton autoWireDropDownButton =
        PlainArrowDropDownButton.makeDropDownButton(nullAction);

    for (String[] popupItem : (String[][]) ArrayUtils.addAll(regexpConstructs, regexpShortcuts)) {
      String shortcut =
          popupItem[0].length() > 14 ? popupItem[0].substring(0, 14) + "..." : popupItem[0];
      autoWireDropDownButton.add(
          new InsertionAction(
              "<html><table border=0 cellpadding=0 cellspacing=0><tr><td width=100>"
                  + shortcut
                  + "</td><td>"
                  + popupItem[1]
                  + "</td></tr></table></html>",
              popupItem[0]));
    }
    c.insets = new Insets(4, 0, 4, 0);
    c.gridx = 1;
    c.weightx = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    regexpPanel.add(autoWireDropDownButton.getDropDownArrowButton(), c);

    // make delete button
    c.insets = new Insets(4, 0, 4, 4);
    c.gridx = 2;
    c.weightx = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    JButton clearRegexpTextFieldButton = new JButton(SwingTools.createIcon("16/delete2.png"));
    clearRegexpTextFieldButton.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            regexpTextField.setText("");
            fireRegularExpressionUpdated();
            regexpTextField.requestFocusInWindow();
          }
        });

    regexpPanel.add(clearRegexpTextFieldButton, c);

    errorMessage = new JLabel(NO_ERROR_MESSAGE, NO_ERROR_ICON, SwingConstants.LEFT);
    errorMessage.setFocusable(false);
    c.insets = new Insets(4, 8, 4, 4);
    c.gridx = 0;
    c.gridy = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.gridwidth = GridBagConstraints.REMAINDER;
    regexpPanel.add(errorMessage, c);

    // create replacement panel
    JPanel replacementPanel = new JPanel(new GridBagLayout());
    replacementPanel.setBorder(
        createTitledBorder(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.replacement.border")));

    JPanel testerPanel = new JPanel(new GridBagLayout());

    c.insets = new Insets(4, 4, 4, 0);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.fill = GridBagConstraints.HORIZONTAL;
    replacementPanel.add(replacementTextField, c);

    // create inline search panel
    JPanel inlineSearchPanel = new JPanel(new GridBagLayout());

    c.insets = new Insets(8, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    inlineSearchPanel.add(
        new JLabel(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.inline_search.search")),
        c);

    c.insets = new Insets(0, 0, 0, 0);
    c.gridx = 0;
    c.gridy = 1;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;
    inlineSearchPanel.add(new JScrollPane(new JTextPane(inlineSearchDocument)), c);

    c.insets = new Insets(8, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 2;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    inlineSearchPanel.add(
        new JLabel(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.inline_search.replaced")),
        c);

    c.insets = new Insets(0, 0, 0, 0);
    c.gridx = 0;
    c.gridy = 3;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;
    JTextPane replaceTextPane = new JTextPane(inlineReplaceDocument);
    replaceTextPane.setEditable(false);
    inlineSearchPanel.add(new JScrollPane(replaceTextPane), c);

    // create regexp options panel
    ItemListener defaultOptionListener =
        new ItemListener() {

          @Override
          public void itemStateChanged(ItemEvent e) {
            fireRegexpOptionsChanged();
          }
        };

    cbCaseInsensitive =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.case_insensitive"));
    cbCaseInsensitive.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.case_insensitive.tip"));
    cbCaseInsensitive.addItemListener(defaultOptionListener);

    cbMultiline =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.multiline_mode"));
    cbMultiline.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.multiline_mode.tip"));
    cbMultiline.addItemListener(defaultOptionListener);

    cbDotall =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.dotall_mode"));
    cbDotall.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.dotall_mode.tip"));
    cbDotall.addItemListener(defaultOptionListener);

    cbUnicodeCase =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.unicode_case"));
    cbUnicodeCase.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.unicode_case.tip"));
    cbUnicodeCase.addItemListener(defaultOptionListener);

    JPanel regexpOptionsPanelWrapper = new JPanel(new BorderLayout());
    JPanel regexpOptionsPanel = new JPanel(new GridBagLayout());
    regexpOptionsPanelWrapper.add(regexpOptionsPanel, BorderLayout.NORTH);

    c.insets = new Insets(12, 4, 0, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    regexpOptionsPanel.add(cbMultiline, c);
    c.insets = new Insets(8, 4, 0, 4);
    c.gridy = 1;
    regexpOptionsPanel.add(cbCaseInsensitive, c);
    c.gridy = 2;
    regexpOptionsPanel.add(cbUnicodeCase, c);
    c.gridy = 3;
    regexpOptionsPanel.add(cbDotall, c);

    // create tabbed panel
    c.insets = new Insets(8, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 1;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.fill = GridBagConstraints.BOTH;
    testExp = new JTabbedPane();
    testExp.add(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.inline_search.title"),
        new JScrollPane(inlineSearchPanel));
    testExp.add(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.result_list.title"),
        new JScrollPane(regexpFindingsList));
    testExp.add(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.title"),
        regexpOptionsPanelWrapper);
    testerPanel.add(testExp, c);

    JPanel groupPanel = new JPanel(new GridBagLayout());
    c.insets = new Insets(4, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    groupPanel.add(regexpPanel, c);

    c.insets = new Insets(4, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    groupPanel.add(replacementPanel, c);

    c.insets = new Insets(4, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 2;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;
    groupPanel.add(testerPanel, c);

    panel.add(groupPanel, 1, 0);

    if (supportsItems) {
      // item shortcuts list
      itemShortcutsList = new JList<String>(items.toArray(new String[items.size()]));
      itemShortcutsList.setToolTipText(
          I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.item_shortcuts.tip"));
      itemShortcutsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      itemShortcutsList.addMouseListener(
          new MouseListener() {

            @Override
            public void mouseClicked(MouseEvent e) {
              if (e.getClickCount() == 2) {
                String text = regexpTextField.getText();
                int cursorPosition = regexpTextField.getCaretPosition();
                int index = itemShortcutsList.getSelectedIndex();
                if (index > -1 && index < itemShortcutsList.getModel().getSize()) {
                  String insertionString =
                      itemShortcutsList.getModel().getElementAt(index).toString();
                  String newText =
                      text.substring(0, cursorPosition)
                          + insertionString
                          + (cursorPosition < text.length() ? text.substring(cursorPosition) : "");
                  regexpTextField.setText(newText);
                  regexpTextField.setCaretPosition(cursorPosition + insertionString.length());
                  regexpTextField.requestFocus();
                  fireRegularExpressionUpdated();
                }
              }
            }

            @Override
            public void mouseEntered(MouseEvent e) {}

            @Override
            public void mouseExited(MouseEvent e) {}

            @Override
            public void mousePressed(MouseEvent e) {}

            @Override
            public void mouseReleased(MouseEvent e) {}
          });
      JScrollPane itemShortcutsPane = new JScrollPane(itemShortcutsList);
      itemShortcutsPane.setBorder(
          createTitledBorder(
              I18N.getMessage(
                  I18N.getGUIBundle(), "gui.dialog.parameter.regexp.item_shortcuts.border")));

      // matched items list
      matchedItemsListModel = new DefaultListModel<String>();
      JList<String> matchedItemsList = new JList<String>(matchedItemsListModel);
      matchedItemsList.setToolTipText(
          I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.matched_items.tip"));
      // add custom cell renderer to disallow selections
      matchedItemsList.setCellRenderer(
          new DefaultListCellRenderer() {

            private static final long serialVersionUID = -5795848004756768378L;

            @Override
            public Component getListCellRendererComponent(
                JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
              return super.getListCellRendererComponent(list, value, index, false, false);
            }
          });
      JScrollPane matchedItemsPanel = new JScrollPane(matchedItemsList);
      matchedItemsPanel.setBorder(
          createTitledBorder(
              I18N.getMessage(
                  I18N.getGUIBundle(), "gui.dialog.parameter.regexp.matched_items.border")));

      // item panel on right side of dialog
      JPanel itemPanel = new JPanel(createGridLayout(1, 2));
      itemPanel.add(itemShortcutsPane, 0, 0);
      itemPanel.add(matchedItemsPanel, 0, 1);

      panel.add(itemPanel, 0, 1);
    }

    okButton = makeOkButton("regexp_property_dialog_apply");
    fireRegularExpressionUpdated();

    layoutDefault(panel, supportsItems ? NORMAL : NARROW, okButton, makeCancelButton());
  }

  private void updateRegexpOptions() {
    boolean multiline = cbMultiline.isSelected();
    boolean caseInsensitive = cbCaseInsensitive.isSelected();
    boolean dotall = cbDotall.isSelected();
    boolean unicodeCase = cbUnicodeCase.isSelected();

    String flags = getFlags(regexpTextField.getText());

    if (multiline != flags.contains("m")) {
      cbMultiline.setSelected(flags.contains("m"));
    }
    if (caseInsensitive != flags.contains("i")) {
      cbCaseInsensitive.setSelected(flags.contains("i"));
    }
    if (dotall != flags.contains("s")) {
      cbDotall.setSelected(flags.contains("s"));
    }
    if (unicodeCase != flags.contains("u")) {
      cbUnicodeCase.setSelected(flags.contains("u"));
    }
  }

  private String getFlags(String pattern) {
    if (!pattern.startsWith("(?")) {
      return "";
    }
    if (pattern.startsWith("(?-")) {
      return "";
    }
    if (pattern.indexOf(")") == -1) {
      return "";
    }
    String flags = pattern.substring(2, pattern.indexOf(")"));
    return flags.split("-")[0];
  }

  private void fireRegexpOptionsChanged() {
    boolean multiline = cbMultiline.isSelected();
    boolean caseInsensitive = cbCaseInsensitive.isSelected();
    boolean dotall = cbDotall.isSelected();
    boolean unicodeCase = cbUnicodeCase.isSelected();

    String pattern = regexpTextField.getText();
    String flags = getFlags(pattern);
    if (flags.contains("m")) {
      if (!multiline) {
        flags = flags.replace("m", "");
      }
    } else {
      if (multiline) {
        flags += "m";
      }
    }

    if (flags.contains("i")) {
      if (!caseInsensitive) {
        flags = flags.replace("i", "");
      }
    } else {
      if (caseInsensitive) {
        flags += "i";
      }
    }

    if (flags.contains("u")) {
      if (!unicodeCase) {
        flags = flags.replace("u", "");
      }
    } else {
      if (unicodeCase) {
        flags += "u";
      }
    }

    if (flags.contains("s")) {
      if (!dotall) {
        flags = flags.replace("s", "");
      }
    } else {
      if (dotall) {
        flags += "s";
      }
    }

    if (!flags.equals("") || pattern.startsWith("(?") && getFlags(pattern).equals("")) {
      flags = "(?" + flags + ")";
    }

    if (pattern.startsWith("(?") && !pattern.startsWith("(?-")) {
      int oldFlagsEnd = pattern.indexOf(")");
      if (oldFlagsEnd == -1) {
        oldFlagsEnd = 1;
      }
      oldFlagsEnd++;
      pattern = flags + pattern.substring(oldFlagsEnd);
    } else {
      pattern = flags + pattern;
    }
    int caretPosition = regexpTextField.getCaretPosition();
    regexpTextField.setText(pattern);
    if (caretPosition < pattern.length()) {
      regexpTextField.setCaretPosition(caretPosition);
    } else {
      regexpTextField.setCaretPosition(pattern.length());
    }
    fireRegularExpressionUpdated();
  }

  private void fireRegularExpressionUpdated() {
    boolean regularExpressionValid = false;
    Pattern pattern = null;
    try {
      pattern = Pattern.compile(regexpTextField.getText());
      regularExpressionValid = true;
    } catch (PatternSyntaxException e) {
      regularExpressionValid = false;
    }
    if (supportsItems) {
      matchedItemsListModel.clear();
      if (regularExpressionValid && pattern != null) {
        for (String previewString : items) {
          if (pattern.matcher(previewString).matches()) {
            matchedItemsListModel.addElement(previewString);
          }
        }
      }
    }
    if (regularExpressionValid) {
      errorMessage.setText(NO_ERROR_MESSAGE);
      errorMessage.setIcon(NO_ERROR_ICON);
      okButton.setEnabled(true);
      inlineSearchDocument.updatePattern(regexpTextField.getText());

    } else {
      errorMessage.setText(ERROR_MESSAGE);
      errorMessage.setIcon(ERROR_ICON);
      okButton.setEnabled(false);
      inlineSearchDocument.clearResults();
    }
  }

  private class InsertionAction extends AbstractAction {

    private static final long serialVersionUID = -5185173378762191200L;
    private final String insertionString;

    public InsertionAction(String title, String insertionString) {
      putValue(Action.NAME, title);
      this.insertionString = insertionString;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      String text = regexpTextField.getText();

      // is shortcut a construct?
      boolean isConstruct = false;
      int row = -1;
      for (int i = 0; i < regexpConstructs.length; i++) {
        if (regexpConstructs[i][0].equals(insertionString)) {
          isConstruct = true;
          row = i;
          break;
        }
      }
      if (isConstruct) {
        if (regexpConstructInsertionEncloseSelected[row]
            && regexpTextField.getSelectedText() != null) {
          int selectionStart = regexpTextField.getSelectionStart();
          int selectionEnd = regexpTextField.getSelectionEnd();
          String newText =
              text.substring(0, selectionStart)
                  + insertionString.substring(0, regexpConstructInsertionSelectionIndices[row][0])
                  + text.substring(selectionStart, selectionEnd)
                  + insertionString.substring(
                      regexpConstructInsertionSelectionIndices[row][0], insertionString.length())
                  + text.substring(selectionEnd, text.length());
          regexpTextField.setText(newText);
          regexpTextField.setCaretPosition(
              selectionEnd - regexpConstructInsertionCaretAdjustment[row]);
          regexpTextField.setSelectionStart(
              selectionStart + regexpConstructInsertionSelectionIndices[row][0]);
          regexpTextField.setSelectionEnd(
              selectionEnd + regexpConstructInsertionSelectionIndices[row][1]);
        } else {
          int cursorPosition = regexpTextField.getCaretPosition();
          String newText =
              text.substring(0, cursorPosition)
                  + insertionString
                  + (cursorPosition < text.length() ? text.substring(cursorPosition) : "");
          regexpTextField.setText(newText);
          regexpTextField.setCaretPosition(
              cursorPosition
                  + insertionString.length()
                  + regexpConstructInsertionCaretAdjustment[row]);
          regexpTextField.setSelectionStart(
              cursorPosition + regexpConstructInsertionSelectionIndices[row][0]);
          regexpTextField.setSelectionEnd(
              cursorPosition + regexpConstructInsertionSelectionIndices[row][1]);
        }
      } else {
        int cursorPosition = regexpTextField.getCaretPosition();
        String newText =
            text.substring(0, cursorPosition)
                + insertionString
                + (cursorPosition < text.length() ? text.substring(cursorPosition) : "");
        regexpTextField.setText(newText);
        regexpTextField.setCaretPosition(cursorPosition + insertionString.length());
      }
      regexpTextField.requestFocus();
      fireRegularExpressionUpdated();
    }
  }

  /**
   * Sets the text of the search field.
   *
   * @param text
   */
  public void setSearchFieldText(String text) {
    try {
      this.inlineSearchDocument.insertString(0, text, new SimpleAttributeSet());
    } catch (BadLocationException e) {
    }
  }

  public String getRegexp() {
    return regexpTextField.getText();
  }

  @Override
  protected String getInfoText() {
    return infoText;
  }
}
  public RegexpPropertyDialog(
      final Collection<String> items, String predefinedRegexp, String description) {
    super(
        ApplicationFrame.getApplicationFrame(),
        "parameter.regexp",
        ModalityType.APPLICATION_MODAL,
        new Object[] {});
    this.items = items;
    this.supportsItems = items != null;
    this.infoText =
        "<html>"
            + I18N.getMessage(I18N.getGUIBundle(), getKey() + ".title")
            + ": <br/>"
            + description
            + "</html>";
    Dimension size = new Dimension(420, 500);
    this.setMinimumSize(size);
    this.setPreferredSize(size);

    JPanel panel = new JPanel(createGridLayout(1, supportsItems ? 2 : 1));

    // create regexp text field
    regexpTextField = new JTextField(predefinedRegexp);
    regexpTextField.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.tip"));
    regexpTextField.addKeyListener(
        new KeyListener() {

          @Override
          public void keyPressed(KeyEvent e) {}

          @Override
          public void keyReleased(KeyEvent e) {
            fireRegularExpressionUpdated();
          }

          @Override
          public void keyTyped(KeyEvent e) {}
        });
    regexpTextField.requestFocus();

    // create replacement text field
    replacementTextField = new JTextField();
    replacementTextField.setToolTipText(
        I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.replacement.tip"));
    replacementTextField.addKeyListener(
        new KeyListener() {

          @Override
          public void keyPressed(KeyEvent e) {}

          @Override
          public void keyReleased(KeyEvent e) {
            fireRegularExpressionUpdated();
          }

          @Override
          public void keyTyped(KeyEvent e) {}
        });

    // create inline search documents
    inlineSearchDocument = new RegexpSearchStyledDocument();
    inlineReplaceDocument = new RegexpReplaceStyledDocument();

    // create search results list
    DefaultListCellRenderer resultCellRenderer =
        new DefaultListCellRenderer() {

          private static final long serialVersionUID = 1L;

          @Override
          public Component getListCellRendererComponent(
              JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            setBackground(list.getBackground());
            setForeground(list.getForeground());
            setBorder(getNoFocusBorder());
            return this;
          }

          private Border getNoFocusBorder() {
            Border border = BorderFactory.createMatteBorder(0, 0, 1, 0, Color.gray);
            return border;
          }
        };

    JList<RegExpResult> regexpFindingsList = new JList<RegExpResult>(resultsListModel);
    regexpFindingsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    regexpFindingsList.setLayoutOrientation(JList.VERTICAL);
    regexpFindingsList.setCellRenderer(resultCellRenderer);

    // regexp panel on left side of dialog
    JPanel regexpPanel = new JPanel(new GridBagLayout());
    regexpPanel.setBorder(
        createTitledBorder(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.border")));
    GridBagConstraints c = new GridBagConstraints();
    c.insets = new Insets(4, 4, 4, 0);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.fill = GridBagConstraints.BOTH;
    regexpPanel.add(regexpTextField, c);

    // make shortcut button
    final Action nullAction = new DefaultAction();
    PlainArrowDropDownButton autoWireDropDownButton =
        PlainArrowDropDownButton.makeDropDownButton(nullAction);

    for (String[] popupItem : (String[][]) ArrayUtils.addAll(regexpConstructs, regexpShortcuts)) {
      String shortcut =
          popupItem[0].length() > 14 ? popupItem[0].substring(0, 14) + "..." : popupItem[0];
      autoWireDropDownButton.add(
          new InsertionAction(
              "<html><table border=0 cellpadding=0 cellspacing=0><tr><td width=100>"
                  + shortcut
                  + "</td><td>"
                  + popupItem[1]
                  + "</td></tr></table></html>",
              popupItem[0]));
    }
    c.insets = new Insets(4, 0, 4, 0);
    c.gridx = 1;
    c.weightx = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    regexpPanel.add(autoWireDropDownButton.getDropDownArrowButton(), c);

    // make delete button
    c.insets = new Insets(4, 0, 4, 4);
    c.gridx = 2;
    c.weightx = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    JButton clearRegexpTextFieldButton = new JButton(SwingTools.createIcon("16/delete2.png"));
    clearRegexpTextFieldButton.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            regexpTextField.setText("");
            fireRegularExpressionUpdated();
            regexpTextField.requestFocusInWindow();
          }
        });

    regexpPanel.add(clearRegexpTextFieldButton, c);

    errorMessage = new JLabel(NO_ERROR_MESSAGE, NO_ERROR_ICON, SwingConstants.LEFT);
    errorMessage.setFocusable(false);
    c.insets = new Insets(4, 8, 4, 4);
    c.gridx = 0;
    c.gridy = 1;
    c.weightx = 0;
    c.weighty = 0;
    c.gridwidth = GridBagConstraints.REMAINDER;
    regexpPanel.add(errorMessage, c);

    // create replacement panel
    JPanel replacementPanel = new JPanel(new GridBagLayout());
    replacementPanel.setBorder(
        createTitledBorder(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.replacement.border")));

    JPanel testerPanel = new JPanel(new GridBagLayout());

    c.insets = new Insets(4, 4, 4, 0);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.fill = GridBagConstraints.HORIZONTAL;
    replacementPanel.add(replacementTextField, c);

    // create inline search panel
    JPanel inlineSearchPanel = new JPanel(new GridBagLayout());

    c.insets = new Insets(8, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    inlineSearchPanel.add(
        new JLabel(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.inline_search.search")),
        c);

    c.insets = new Insets(0, 0, 0, 0);
    c.gridx = 0;
    c.gridy = 1;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;
    inlineSearchPanel.add(new JScrollPane(new JTextPane(inlineSearchDocument)), c);

    c.insets = new Insets(8, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 2;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    inlineSearchPanel.add(
        new JLabel(
            I18N.getMessage(
                I18N.getGUIBundle(), "gui.dialog.parameter.regexp.inline_search.replaced")),
        c);

    c.insets = new Insets(0, 0, 0, 0);
    c.gridx = 0;
    c.gridy = 3;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;
    JTextPane replaceTextPane = new JTextPane(inlineReplaceDocument);
    replaceTextPane.setEditable(false);
    inlineSearchPanel.add(new JScrollPane(replaceTextPane), c);

    // create regexp options panel
    ItemListener defaultOptionListener =
        new ItemListener() {

          @Override
          public void itemStateChanged(ItemEvent e) {
            fireRegexpOptionsChanged();
          }
        };

    cbCaseInsensitive =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.case_insensitive"));
    cbCaseInsensitive.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.case_insensitive.tip"));
    cbCaseInsensitive.addItemListener(defaultOptionListener);

    cbMultiline =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.multiline_mode"));
    cbMultiline.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.multiline_mode.tip"));
    cbMultiline.addItemListener(defaultOptionListener);

    cbDotall =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.dotall_mode"));
    cbDotall.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.dotall_mode.tip"));
    cbDotall.addItemListener(defaultOptionListener);

    cbUnicodeCase =
        new JCheckBox(
            I18N.getMessage(
                I18N.getGUIBundle(),
                "gui.dialog.parameter.regexp.regular_expression.regexp_options.unicode_case"));
    cbUnicodeCase.setToolTipText(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.unicode_case.tip"));
    cbUnicodeCase.addItemListener(defaultOptionListener);

    JPanel regexpOptionsPanelWrapper = new JPanel(new BorderLayout());
    JPanel regexpOptionsPanel = new JPanel(new GridBagLayout());
    regexpOptionsPanelWrapper.add(regexpOptionsPanel, BorderLayout.NORTH);

    c.insets = new Insets(12, 4, 0, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    regexpOptionsPanel.add(cbMultiline, c);
    c.insets = new Insets(8, 4, 0, 4);
    c.gridy = 1;
    regexpOptionsPanel.add(cbCaseInsensitive, c);
    c.gridy = 2;
    regexpOptionsPanel.add(cbUnicodeCase, c);
    c.gridy = 3;
    regexpOptionsPanel.add(cbDotall, c);

    // create tabbed panel
    c.insets = new Insets(8, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 1;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.fill = GridBagConstraints.BOTH;
    testExp = new JTabbedPane();
    testExp.add(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.inline_search.title"),
        new JScrollPane(inlineSearchPanel));
    testExp.add(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.result_list.title"),
        new JScrollPane(regexpFindingsList));
    testExp.add(
        I18N.getMessage(
            I18N.getGUIBundle(),
            "gui.dialog.parameter.regexp.regular_expression.regexp_options.title"),
        regexpOptionsPanelWrapper);
    testerPanel.add(testExp, c);

    JPanel groupPanel = new JPanel(new GridBagLayout());
    c.insets = new Insets(4, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 0;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    groupPanel.add(regexpPanel, c);

    c.insets = new Insets(4, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 1;
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.HORIZONTAL;
    groupPanel.add(replacementPanel, c);

    c.insets = new Insets(4, 4, 4, 4);
    c.gridx = 0;
    c.gridy = 2;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;
    groupPanel.add(testerPanel, c);

    panel.add(groupPanel, 1, 0);

    if (supportsItems) {
      // item shortcuts list
      itemShortcutsList = new JList<String>(items.toArray(new String[items.size()]));
      itemShortcutsList.setToolTipText(
          I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.item_shortcuts.tip"));
      itemShortcutsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      itemShortcutsList.addMouseListener(
          new MouseListener() {

            @Override
            public void mouseClicked(MouseEvent e) {
              if (e.getClickCount() == 2) {
                String text = regexpTextField.getText();
                int cursorPosition = regexpTextField.getCaretPosition();
                int index = itemShortcutsList.getSelectedIndex();
                if (index > -1 && index < itemShortcutsList.getModel().getSize()) {
                  String insertionString =
                      itemShortcutsList.getModel().getElementAt(index).toString();
                  String newText =
                      text.substring(0, cursorPosition)
                          + insertionString
                          + (cursorPosition < text.length() ? text.substring(cursorPosition) : "");
                  regexpTextField.setText(newText);
                  regexpTextField.setCaretPosition(cursorPosition + insertionString.length());
                  regexpTextField.requestFocus();
                  fireRegularExpressionUpdated();
                }
              }
            }

            @Override
            public void mouseEntered(MouseEvent e) {}

            @Override
            public void mouseExited(MouseEvent e) {}

            @Override
            public void mousePressed(MouseEvent e) {}

            @Override
            public void mouseReleased(MouseEvent e) {}
          });
      JScrollPane itemShortcutsPane = new JScrollPane(itemShortcutsList);
      itemShortcutsPane.setBorder(
          createTitledBorder(
              I18N.getMessage(
                  I18N.getGUIBundle(), "gui.dialog.parameter.regexp.item_shortcuts.border")));

      // matched items list
      matchedItemsListModel = new DefaultListModel<String>();
      JList<String> matchedItemsList = new JList<String>(matchedItemsListModel);
      matchedItemsList.setToolTipText(
          I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.matched_items.tip"));
      // add custom cell renderer to disallow selections
      matchedItemsList.setCellRenderer(
          new DefaultListCellRenderer() {

            private static final long serialVersionUID = -5795848004756768378L;

            @Override
            public Component getListCellRendererComponent(
                JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
              return super.getListCellRendererComponent(list, value, index, false, false);
            }
          });
      JScrollPane matchedItemsPanel = new JScrollPane(matchedItemsList);
      matchedItemsPanel.setBorder(
          createTitledBorder(
              I18N.getMessage(
                  I18N.getGUIBundle(), "gui.dialog.parameter.regexp.matched_items.border")));

      // item panel on right side of dialog
      JPanel itemPanel = new JPanel(createGridLayout(1, 2));
      itemPanel.add(itemShortcutsPane, 0, 0);
      itemPanel.add(matchedItemsPanel, 0, 1);

      panel.add(itemPanel, 0, 1);
    }

    okButton = makeOkButton("regexp_property_dialog_apply");
    fireRegularExpressionUpdated();

    layoutDefault(panel, supportsItems ? NORMAL : NARROW, okButton, makeCancelButton());
  }
 static {
   icon =
       SwingTools.createIcon(
           "48/" + I18N.getMessage(I18N.getGUIBundle(), "gui.action.welcome.tutorial.icon"));
 }