/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.uitools.cell;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTable;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import org.eclipse.persistence.tools.workbench.uitools.swing.CachingComboBoxModel;
import org.eclipse.persistence.tools.workbench.uitools.swing.EmptyIcon;
import org.eclipse.persistence.tools.workbench.uitools.swing.NonCachingComboBoxModel;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
/**
* Make the cell look like a combo-box.
*/
public class ComboBoxTableCellRenderer implements TableCellEditorAdapter.Renderer {
/* caching the combo box because we are caching the comboBoxModel.
* Everytime we rebuilt the comboBox we would set the model on it and not
* remove the model from the old combo box. This meant that new listeners
* kept being added to the comboBoxModel for every comboBox build.
* Not sure if there is a way to clear out the old combo box, or why
* we were buildig a new combo box every time so I went with caching it.
*/
private JComboBox comboBox;
/** the items used to populate the combo box */
private CachingComboBoxModel model;
private ListCellRenderer renderer;
Object value;
private static int height = -1;
private boolean fakeFocusFlag;
/** the listener to be notified on an immediate edit */
protected TableCellEditorAdapter.ImmediateEditListener immediateEditListener;
/** hold the original colors of the combo-box */
private static Color defaultForeground;
private static Color defaultBackground;
/** "normal" border - assume the default table "focus" border is 1 pixel thick */
private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
// ********** constructors/initialization **********
/**
* Default constructor.
*/
private ComboBoxTableCellRenderer() {
super();
initialize();
}
/**
* Construct a cell renderer that uses the specified combo-box model.
*/
public ComboBoxTableCellRenderer(ComboBoxModel model) {
this(new NonCachingComboBoxModel(model));
}
/**
* Construct a cell renderer that uses the specified caching combo-box model.
*/
public ComboBoxTableCellRenderer(CachingComboBoxModel model) {
this();
this.model = model;
}
/**
* Construct a cell renderer that uses the specified
* combo-box model and renderer.
*/
public ComboBoxTableCellRenderer(ComboBoxModel model, ListCellRenderer renderer) {
this(new NonCachingComboBoxModel(model), renderer);
}
/**
* Construct a cell renderer that uses the specified
* caching combo-box model and renderer.
*/
public ComboBoxTableCellRenderer(CachingComboBoxModel model, ListCellRenderer renderer) {
this(model);
this.renderer = renderer;
}
protected void initialize() {
// save the original colors of the combo-box, so we
// can use them to paint non-selected cells
if (height == -1) {
JComboBox comboBox = new JComboBox();
comboBox.addItem("m");
// add in space for the border top and bottom
height = comboBox.getPreferredSize().height + 2;
defaultForeground = comboBox.getForeground();
defaultBackground = comboBox.getBackground();
}
}
public static JLabel prototypeLabel = new JLabel("Prototype", new EmptyIcon(16), SwingConstants.LEADING);
protected JComboBox buildComboBox() {
final JComboBox result = new JComboBox() {
private boolean fakeFocus;
public boolean hasFocus() {
return fakeFocus || super.hasFocus();
}
public void paint(Graphics g) {
fakeFocus = ComboBoxTableCellRenderer.this.fakeFocusFlag;
super.paint(g);
fakeFocus = false;
}
//wrap the renderer to deal with the prototypeDisplayValue
public void setRenderer(final ListCellRenderer aRenderer) {
super.setRenderer(new ListCellRenderer(){
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value == prototypeLabel) {
return prototypeLabel;
}
return aRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
});
}
public int getSelectedIndex() {
boolean listNotCached = !listIsCached();
if (listNotCached) {
cacheList();
}
int index = super.getSelectedIndex();
if (listNotCached) {
uncacheList();
}
return index;
}
};
// see javax.swing.DefaultCellEditor for usage
result.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
result.addActionListener(this.buildActionListener());
result.addPopupMenuListener(this.buildPopupMenuListener());
//These are used to workaround problems with Swing trying to
//determine the size of a comboBox with a large model
result.setPrototypeDisplayValue(prototypeLabel);
getListBox(result).setPrototypeCellValue(prototypeLabel);
return result;
}
private JList getListBox(JComboBox result) {
return (JList) ClassTools.getFieldValue(result.getUI(), "listBox");
}
private ActionListener buildActionListener() {
return new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComboBox comboBox = (JComboBox) e.getSource();
Object selectedItem = comboBox.getSelectedItem();
// Only update the selected item and invoke immediateEdit() if the
// selected item actually changed, during the initialization of the
// editing, the model changes and causes this method to be invoked,
// it causes CR#3963675 to occur because immediateEdit() stop the
// editing, which is done at the wrong time
if (ComboBoxTableCellRenderer.this.value != selectedItem) {
ComboBoxTableCellRenderer.this.value = comboBox.getSelectedItem();
ComboBoxTableCellRenderer.this.immediateEdit();
}
}
};
}
void immediateEdit() {
if (this.immediateEditListener != null) {
this.immediateEditListener.immediateEdit();
}
}
private PopupMenuListener buildPopupMenuListener() {
return new PopupMenuListener() {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
if (listIsCached()) {
uncacheList();
}
cacheList();
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
if (listIsCached()) {
uncacheList();
}
}
public void popupMenuCanceled(PopupMenuEvent e) {
if (listIsCached()) {
uncacheList();
}
}
};
}
private void cacheList() {
this.model.cacheList();
}
private void uncacheList() {
this.model.uncacheList();
}
private boolean listIsCached() {
return this.model.isCached();
}
// ********** TableCellRenderer implementation **********
/**
* @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
*/
public Component getTableCellRendererComponent(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
this.fakeFocusFlag = selected || hasFocus;
if (this.comboBox == null) {
this.comboBox = this.buildComboBox();
this.comboBox.setComponentOrientation(table.getComponentOrientation());
this.comboBox.setModel(this.model);
if (this.renderer != null) {
this.comboBox.setRenderer(this.renderer);
}
this.comboBox.setFont(table.getFont());
this.comboBox.setEnabled(table.isEnabled());
this.comboBox.setBorder(this.border(table, val, selected, hasFocus, row, column));
}
// We need to go through the model since JComboBox might prevent us from
// selecting the value. This can happen when the value is not contained
// in the model, see CR#3950044 for an example
this.model.setSelectedItem(val);
return this.comboBox;
}
/**
* Return the cell's foreground color.
*/
protected Color foregroundColor(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
if (selected) {
if (hasFocus && table.isCellEditable(row, column)) {
return defaultForeground;
}
return table.getSelectionForeground();
}
return defaultForeground;
}
/**
* Return the cell's background color.
*/
protected Color backgroundColor(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
if (selected) {
if (hasFocus && table.isCellEditable(row, column)) {
return defaultBackground;
}
return table.getSelectionBackground();
}
return defaultBackground;
}
/**
* Return the cell's border.
*/
protected Border border(JTable table, Object val, boolean selected, boolean hasFocus, int row, int column) {
return hasFocus ?
UIManager.getBorder("Table.focusCellHighlightBorder")
:
NO_FOCUS_BORDER;
}
// ********** TableCellEditorAdapter.Renderer implementation **********
/**
* @see TableCellEditorAdapter#getValue()
*/
public Object getValue() {
return this.value;
}
/**
* @see TableCellEditorAdapter#setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener)
*/
public void setImmediateEditListener(TableCellEditorAdapter.ImmediateEditListener listener) {
this.immediateEditListener = listener;
}
public void commit() {
// Nothing to commit
}
// ********** public API **********
/**
* Return the renderer's preferred height. This allows you
* to set the row height to something the combo-box will look good in....
*/
public int getPreferredHeight() {
return height;
}
}