/**
* Copyright (C) 2001-2017 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.tablepanel.cells.implementations;
import java.awt.Color;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JMenuItem;
import javax.swing.JRadioButton;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellType;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeTextFieldDefault;
import com.rapidminer.gui.properties.tablepanel.cells.interfaces.CellTypeTextFieldNumerical;
import com.rapidminer.gui.properties.tablepanel.model.TablePanelModel;
import com.rapidminer.gui.tools.ScrollableJPopupMenu;
import com.rapidminer.tools.I18N;
/**
* This class contains some helper methods for the cell type implementations.
*
* @author Marco Boeck
*
*/
public final class CellTypeImplHelper {
/**
* Adds content assist to the given field. Does not validate the model, so make sure this call
* works!
*
* @param model
* @param rowIndex
* @param columnIndex
* @param field
* @param button
* @param cellClass
*/
static void addContentAssist(final TablePanelModel model, final int rowIndex, final int columnIndex,
final JFormattedTextField field, final JButton button, final Class<? extends CellType> cellClass) {
List<String> valuesList = model.getPossibleValuesForCellOrNull(rowIndex, columnIndex);
if (valuesList == null) {
valuesList = Collections.<String> emptyList();
}
final boolean multipleValuesAllowed = model.canCellHaveMultipleValues(rowIndex, columnIndex);
final List<String> possibleValues = valuesList;
// add ctrl+space shortcut for content assist
field.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
"contentAssistAction");
field.getActionMap().put("contentAssistAction", new AbstractAction() {
private static final long serialVersionUID = 7930480602861798587L;
@Override
public void actionPerformed(final ActionEvent e) {
button.doClick();
}
});
// sort content assist values if they are for a string textfield
if (CellTypeTextFieldDefault.class.isAssignableFrom(cellClass)) {
Collections.sort(possibleValues);
}
// add mouse listener to show content assist popup
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
showContentAssistPopup();
}
/**
* Shows the content assist popup.
*
*/
private void showContentAssistPopup() {
final ScrollableJPopupMenu popupMenu = createContentAssistPopup(field, possibleValues, multipleValuesAllowed,
cellClass);
popupMenu.show(field, field.getX(), field.getY() + field.getHeight());
popupMenu.requestFocusInWindow();
}
/**
* Creates the content assist popup.
*
* @param field
* @param possibleValues
* @param multipleValuesAllowed
* @param cellClass
*/
private ScrollableJPopupMenu createContentAssistPopup(final JFormattedTextField field,
final List<String> possibleValues, final boolean multipleValuesAllowed,
final Class<? extends CellType> cellClass) {
final ScrollableJPopupMenu popupMenu = new ScrollableJPopupMenu(ScrollableJPopupMenu.SIZE_SMALL);
// customize content assist look
popupMenu.setBackground(Color.WHITE);
popupMenu.setCustomWidth(field.getWidth());
// no values available, so show user there is nothing
if (possibleValues == null || possibleValues.size() <= 0) {
JMenuItem emptyItem = new JMenuItem(
I18N.getMessage(I18N.getGUIBundle(), "gui.label.table_panel.no_content_assist_items.title"));
emptyItem.setToolTipText(
I18N.getMessage(I18N.getGUIBundle(), "gui.label.table_panel.no_content_assist_items.tip"));
emptyItem.setOpaque(false);
popupMenu.add(emptyItem);
return popupMenu;
}
// either add checkboxes or radiobuttons depending on whether multiple values are
// allowed
if (multipleValuesAllowed) {
fillMultipleValuesSelectionPopup(field, possibleValues, popupMenu, cellClass);
} else {
fillSingleValueSelectionPopup(field, possibleValues, popupMenu, cellClass);
}
return popupMenu;
}
/**
* Fills the given popup menu for single value selection.
*
* @param field
* @param possibleValues
* @param popupMenu
* @param cellClass
*/
private void fillSingleValueSelectionPopup(final JFormattedTextField field, final List<String> possibleValues,
final ScrollableJPopupMenu popupMenu, final Class<? extends CellType> cellClass) {
ButtonGroup group = new ButtonGroup();
String existingValue = field.getText();
for (String item : possibleValues) {
final JRadioButton radioButton = new JRadioButton(item);
radioButton.setOpaque(false);
// pre-select the corresponding radiobutton for the text in the field
if (item.split(" ")[0].equals(existingValue)) {
radioButton.setSelected(true);
}
// on click set selected item to textfield
radioButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e1) {
// radio button, only one selection allowed anyway
String text = radioButton.getText();
// numerical values may have an annotation behind the value, split after
// " " and just add
// the first (aka number) value if applicable
if (CellTypeTextFieldNumerical.class.isAssignableFrom(cellClass)) {
text = text.split(" ")[0];
}
field.setText(text);
}
});
group.add(radioButton);
popupMenu.add(radioButton);
}
}
/**
* Fills the given popup menu for multiple value selection.
*
* @param field
* @param possibleValues
* @param popupMenu
* @param cellClass
*/
private void fillMultipleValuesSelectionPopup(final JFormattedTextField field, final List<String> possibleValues,
final ScrollableJPopupMenu popupMenu, final Class<? extends CellType> cellClass) {
String encodedValue = field.getText();
List<String> currentValuesList = model.convertEncodedStringValueToList(encodedValue);
// iterate over all possible items, create checkbox for each
for (String item : possibleValues) {
final JCheckBox checkbox = new JCheckBox(item);
checkbox.setOpaque(false);
// pre-select the corresponding checkboxes
if (currentValuesList.contains(item)) {
checkbox.setSelected(true);
}
// on click set currently selected checkbox items to textfield
checkbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e1) {
List<String> selectedItems = new LinkedList<>();
for (Component comp : popupMenu.getComponentsInsideScrollpane()) {
if (comp instanceof JCheckBox) {
JCheckBox checkbox = (JCheckBox) comp;
// add all selected checkboxes to the selected items list
if (checkbox.isSelected()) {
selectedItems.add(checkbox.getText());
}
}
}
// set the new text according to selected items
field.setText(model.encodeListOfStringsToValue(selectedItems));
}
});
popupMenu.add(checkbox);
}
}
});
}
/**
* Creates a {@link JFormattedTextField} for the specified cell. Does not validate the model, so
* make sure this call works!
*
* @param model
* @param rowIndex
* @param columnIndex
* @return
*/
public static JFormattedTextField createFormattedTextField(final TablePanelModel model, final int rowIndex,
final int columnIndex) {
final JFormattedTextField field = new JFormattedTextField();
field.setToolTipText(model.getHelptextAt(rowIndex, columnIndex));
// either add document listener (editable) or disable editing (not editable)
if (model.isCellEditable(rowIndex, columnIndex)) {
field.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(final DocumentEvent e) {
handleChange(e);
}
@Override
public void insertUpdate(final DocumentEvent e) {
handleChange(e);
}
@Override
public void changedUpdate(final DocumentEvent e) {
handleChange(e);
}
private void handleChange(final DocumentEvent e) {
try {
String newValue = e.getDocument().getText(0, e.getDocument().getLength());
model.setValueAt(newValue, rowIndex, columnIndex);
} catch (BadLocationException e1) {
} // should not happen, if it does ignore it
}
});
} else {
field.setEnabled(false);
}
return field;
}
}