/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.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 java.awt.Component; import java.awt.Font; 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.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Collection; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.operator.Operator; import com.rapidminer.parameter.ParameterTypeRegexp; import com.rapidminer.tools.I18N; /** * 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. * * @author Tobias Malbrecht */ public class RegexpPropertyDialog extends PropertyDialog { private static final long serialVersionUID = 5396725165122306231L; 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 final JTextField regexpTextField; private JList itemShortcutsList; private DefaultListModel matchedItemsListModel; private final Collection<String> items; private boolean supportsItems = false; private final JLabel errorMessage; private JButton okButton; public RegexpPropertyDialog(final ParameterTypeRegexp type, String predefinedRegexp, Operator operator) { super(type, "regexp"); this.items = type.getPreviewList(); this.supportsItems = (items != null); JPanel panel = new JPanel(createGridLayout(1, supportsItems ? 2 : 1)); // regexp text field regexpTextField = new JTextField(predefinedRegexp); regexpTextField.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.tip")); regexpTextField.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) {} public void keyReleased(KeyEvent e) { fireRegularExpressionUpdated(); } public void keyTyped(KeyEvent e) {} }); regexpTextField.requestFocus(); // constructs table final TableModel regexpConstructTableModel = new TableModel() { private static final long serialVersionUID = 3081581916411341948L; public Class<?> getColumnClass(int columnIndex) { return String.class; } public String getColumnName(int columnIndex) { switch (columnIndex) { case 0: return I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.constructs_table.construct_header"); case 1: return I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.constructs_table.description_header"); } return null; } public int getColumnCount() { return 2; } public int getRowCount() { return regexpConstructs.length; } public Object getValueAt(int rowIndex, int columnIndex) { return regexpConstructs[rowIndex][columnIndex]; } public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } public void addTableModelListener(TableModelListener l) {} public void removeTableModelListener(TableModelListener l) {} public void setValueAt(Object value, int rowIndex, int columnIndex) {} }; final JTable regexpConstructTable = new JTable(regexpConstructTableModel); regexpConstructTable.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.constructs_table.tip")); regexpConstructTable.setCellSelectionEnabled(true); regexpConstructTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); regexpConstructTable.addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { int row = regexpConstructTable.getSelectedRow(); String text = regexpTextField.getText(); String insertionString = regexpConstructTableModel.getValueAt(row, 0).toString(); 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]); regexpTextField.requestFocus(); } 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]); regexpTextField.requestFocus(); } fireRegularExpressionUpdated(); } } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} }); regexpConstructTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer() { private static final long serialVersionUID = -8024658831923934442L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (column == 1) { Component c = super.getTableCellRendererComponent(table, value, false, false, row, column); c.setFont(c.getFont().deriveFont(Font.ITALIC)); return c; } else { return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } } }); for (int i = 0; i < regexpConstructTable.getColumnCount(); i++) { TableColumn column = regexpConstructTable.getColumn(regexpConstructTable.getModel().getColumnName(i)); int width = 0; TableCellRenderer renderer = column.getHeaderRenderer(); if (renderer == null) { renderer = regexpConstructTable.getTableHeader().getDefaultRenderer(); } Component comp = renderer.getTableCellRendererComponent(regexpConstructTable, column.getHeaderValue(), false, false, 0, 0); width = comp.getPreferredSize().width; // Get maximum width of column data for (int r = 0; r < regexpConstructTable.getRowCount(); r++) { renderer = regexpConstructTable.getCellRenderer(r, i); comp = renderer.getTableCellRendererComponent(regexpConstructTable, regexpConstructTable.getValueAt(r, i), false, false, r, i); width = Math.max(width, comp.getPreferredSize().width); } // Set the width column.setPreferredWidth(width); } JScrollPane regexConstructTablePane = new JScrollPane(regexpConstructTable); // shortcuts table final TableModel regexpShortcutsTableModel = new TableModel() { private static final long serialVersionUID = 3081581916411341948L; public Class<?> getColumnClass(int columnIndex) { return String.class; } public String getColumnName(int columnIndex) { switch (columnIndex) { case 0: return I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.shortcuts_table.shortcuts_header"); case 1: return I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.shortcuts_table.description_header"); } return null; } public int getColumnCount() { return 2; } public int getRowCount() { return regexpShortcuts.length; } public Object getValueAt(int rowIndex, int columnIndex) { return regexpShortcuts[rowIndex][columnIndex]; } public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } public void addTableModelListener(TableModelListener l) {} public void removeTableModelListener(TableModelListener l) {} public void setValueAt(Object value, int rowIndex, int columnIndex) {} }; final JTable regexpShortcutsTable = new JTable(regexpShortcutsTableModel); regexpShortcutsTable.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.shortcuts_table.tip")); regexpShortcutsTable.setCellSelectionEnabled(true); regexpShortcutsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); regexpShortcutsTable.addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { String text = regexpTextField.getText(); int cursorPosition = regexpTextField.getCaretPosition(); String insertionString = regexpShortcutsTableModel.getValueAt(regexpShortcutsTable.getSelectedRow(), 0).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(); } } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} }); regexpShortcutsTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer() { private static final long serialVersionUID = -8024658831923934442L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (column == 1) { Component c = super.getTableCellRendererComponent(table, value, false, false, row, column); c.setFont(c.getFont().deriveFont(Font.ITALIC)); return c; } else { return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } } }); for (int i = 0; i < regexpShortcutsTable.getColumnCount(); i++) { TableColumn column = regexpShortcutsTable.getColumn(regexpShortcutsTable.getModel().getColumnName(i)); int width = 0; TableCellRenderer renderer = column.getHeaderRenderer(); if (renderer == null) { renderer = regexpShortcutsTable.getTableHeader().getDefaultRenderer(); } Component comp = renderer.getTableCellRendererComponent(regexpShortcutsTable, column.getHeaderValue(), false, false, 0, 0); width = comp.getPreferredSize().width; // Get maximum width of column data for (int r = 0; r < regexpShortcutsTable.getRowCount(); r++) { renderer = regexpShortcutsTable.getCellRenderer(r, i); comp = renderer.getTableCellRendererComponent(regexpShortcutsTable, regexpShortcutsTable.getValueAt(r, i), false, false, r, i); width = Math.max(width, comp.getPreferredSize().width); } // Set the width column.setPreferredWidth(width); } JScrollPane regexShortcutsTablePane = new JScrollPane(regexpShortcutsTable); // 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); c.insets = new Insets(4, 0, 4, 4); c.gridx = 1; 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, JLabel.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); c.insets = new Insets(20, 4, 4, 4); c.gridx = 0; c.gridy = 2; c.weightx = 0; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.BOTH; JTabbedPane tips = new JTabbedPane(); tips.add(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.constructs_table.title"), regexConstructTablePane); tips.add(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.regular_expression.shortcuts_table.title"), regexShortcutsTablePane); regexpPanel.add(tips, c); panel.add(regexpPanel, 0, 0); if (supportsItems) { // item shortcuts list itemShortcutsList = new JList(items.toArray()); itemShortcutsList.setToolTipText(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.parameter.regexp.item_shortcuts.tip")); itemShortcutsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); itemShortcutsList.addMouseListener(new MouseListener() { 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(); } } } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} 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(); JList matchedItemsList = new JList(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(); fireRegularExpressionUpdated(); layoutDefault(panel, supportsItems ? NORMAL : NARROW, okButton, makeCancelButton()); } 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); } else { errorMessage.setText(ERROR_MESSAGE); errorMessage.setIcon(ERROR_ICON); okButton.setEnabled(false); } } public String getRegexp() { return regexpTextField.getText(); } }