/* * RapidMiner * * Copyright (C) 2001-2014 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.celleditors.value; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.util.List; import javax.swing.AbstractCellEditor; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.plaf.basic.BasicComboPopup; import com.rapidminer.gui.tools.ProgressThread; import com.rapidminer.operator.Operator; import com.rapidminer.parameter.ParameterType; import com.rapidminer.tools.I18N; import com.rapidminer.tools.ProgressListener; /** * Renders a combo box which can be filled with suggestions. * * @author Marcin Skirzynski, Nils Woehler */ public abstract class AbstractSuggestionBoxValueCellEditor extends AbstractCellEditor implements PropertyValueCellEditor { private static final long serialVersionUID = -771727412083431607L; /** * The model of the combo box which consist of the suggestions */ private final SuggestionComboBoxModel model; /** * The GUI element */ private final JComboBox comboBox; private final JPanel container; private Operator operator; private ParameterType type; private final String LOADING; public AbstractSuggestionBoxValueCellEditor(final ParameterType type) { this.type = type; this.model = new SuggestionComboBoxModel(); this.comboBox = new SuggestionComboBox(model); this.comboBox.setToolTipText(type.getDescription()); this.comboBox.setRenderer(new SuggestionComboBoxModelCellRenderer()); LOADING = I18N.getGUILabel("parameters.loading"); this.container = new JPanel(new GridBagLayout()); this.container.setToolTipText(type.getDescription()); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.weighty = 1; c.weightx = 1; container.add(comboBox, c); } public abstract List<Object> getSuggestions(Operator operator, ProgressListener progressListener); private String getValue() { String value = null; value = operator.getParameters().getParameterOrNull(type.getKey()); return value; } @Override public boolean rendersLabel() { return false; } @Override public boolean useEditorAsRenderer() { return true; } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { comboBox.setSelectedItem(value); return container; } @Override public Object getCellEditorValue() { return comboBox.getSelectedItem(); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { comboBox.setSelectedItem(value); return container; } @Override public void setOperator(Operator operator) { this.operator = operator; } public class SuggestionComboBoxModelCellRenderer extends DefaultListCellRenderer { private static final long serialVersionUID = 1L; @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component listCellRendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (LOADING.equals(value)) { listCellRendererComponent.setBackground(list.getBackground()); listCellRendererComponent.setForeground(UIManager.getColor("Label.disabledForeground")); listCellRendererComponent.setEnabled(false); } return listCellRendererComponent; } } class SuggestionComboBoxModel extends DefaultComboBoxModel { private static final long serialVersionUID = -2984664300141879731L; private Object lock = new Object(); public void updateModel(final SuggestionComboBox comboBox) { final Object selected = getValue(); ProgressThread t = new ProgressThread("fetching_suggestions") { @Override public void run() { try { getProgressListener().setTotal(100); getProgressListener().setCompleted(0); synchronized (lock) { removeAllElements(); insertElementAt(LOADING, 0); // fill list with stuff List<Object> suggestions = getSuggestions(operator, getProgressListener()); removeAllElements(); int index = 0; for (Object suggestion : suggestions) { insertElementAt(suggestion, index); ++index; } // resize popup Object child = comboBox.getAccessibleContext().getAccessibleChild(0); BasicComboPopup popup = (BasicComboPopup) child; JList list = popup.getList(); Dimension preferred = list.getPreferredSize(); preferred.width += 25; int itemCount = comboBox.getItemCount(); int rowHeight = 10; if(itemCount > 0) { rowHeight = preferred.height / itemCount; } int maxHeight = comboBox.getMaximumRowCount() * rowHeight; preferred.height = Math.min(preferred.height, maxHeight); Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, list); JScrollPane scrollPane = (JScrollPane) c; scrollPane.setPreferredSize(preferred); scrollPane.setMaximumSize(preferred); Dimension popupSize = popup.getSize(); popupSize.width = preferred.width; popupSize.height = preferred.height + 5; Component parent = popup.getParent(); if (parent != null) { parent.setSize(popupSize); parent.validate(); parent.repaint(); } } getProgressListener().setCompleted(100); if (getSelectedItem() == null) { if (model.getSize() == 0) { setSelectedItem(null); } else if (selected != null) { setSelectedItem(selected); } } } finally { getProgressListener().complete(); } } }; t.start(); } } class SuggestionComboBox extends JComboBox { private static final long serialVersionUID = 4000279412600950101L; private SuggestionComboBox(final SuggestionComboBoxModel model) { super(model); setEditable(true); addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fireEditingStopped(); } }); getEditor().getEditorComponent().addFocusListener(new FocusListener() { @Override public void focusLost(FocusEvent e) { if (!e.isTemporary()) { fireEditingStopped(); } } @Override public void focusGained(FocusEvent e) {} }); // add popup menu listener Object child = getAccessibleContext().getAccessibleChild(0); BasicComboPopup popup = (BasicComboPopup) child; popup.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { model.updateModel(SuggestionComboBox.this); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {} @Override public void popupMenuCanceled(PopupMenuEvent e) {} }); } @Override public void setSelectedItem(Object anObject) { if (!LOADING.equals(anObject)) { super.setSelectedItem(anObject); } } @Override public void setSelectedIndex(int anIndex) { if (!LOADING.equals(getModel().getElementAt(anIndex))) { super.setSelectedIndex(anIndex); } } } /** * @param button adds a button the the right side of the ComboBox. */ protected void addConfigureButton(JButton button) { GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.weighty = 1; c.weightx = 0; container.add(button, c); } }