com.rapidminer.gui.properties.RegexpPropertyDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.rapidminer.gui.properties.RegexpPropertyDialog.java

Source

/**
 * Copyright (C) 2001-2015 by RapidMiner and the contributors
 *
 * Complete list of developers available at our web site:
 *
 *      http://rapidminer.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package com.rapidminer.gui.properties;

import groovy.swing.impl.DefaultAction;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Collection;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;

import org.apache.commons.lang.ArrayUtils;

import com.rapidminer.gui.ApplicationFrame;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.components.PlainArrowDropDownButton;
import com.rapidminer.gui.tools.dialogs.ButtonDialog;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.Tools;

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