/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV 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 or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.smart.dataui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.text.ParseException; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javax.swing.DefaultListCellRenderer; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.JWindow; import javax.swing.KeyStroke; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.text.Document; import com.servoy.j2db.IApplication; import com.servoy.j2db.dataprocessing.CustomValueList; import com.servoy.j2db.dataprocessing.CustomValueList.DisplayString; import com.servoy.j2db.dataprocessing.IDisplayDependencyData; import com.servoy.j2db.dataprocessing.IDisplayRelatedData; import com.servoy.j2db.dataprocessing.IFoundSetInternal; import com.servoy.j2db.dataprocessing.IRecordInternal; import com.servoy.j2db.dataprocessing.IValueList; import com.servoy.j2db.dataprocessing.LookupListChangeListener; import com.servoy.j2db.dataprocessing.LookupListModel; import com.servoy.j2db.dataprocessing.LookupValueList; import com.servoy.j2db.dataprocessing.SortColumn; import com.servoy.j2db.ui.ISupportsDoubleBackground; import com.servoy.j2db.ui.IWindowVisibleChangeListener; import com.servoy.j2db.ui.IWindowVisibleChangeNotifier; import com.servoy.j2db.ui.scripting.RuntimeDataLookupField; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.ScopesUtils; import com.servoy.j2db.util.UIUtils; import com.servoy.j2db.util.Utils; import com.servoy.j2db.util.text.FixedMaskFormatter; /** * The Typeahead/DataLookup field that pops up a list and filters that list as you type. * * @author jcompagner */ public class DataLookupField extends DataField implements IDisplayRelatedData, IDisplayDependencyData, ISupportsDoubleBackground { private static final long serialVersionUID = 1L; protected JWindow popup; protected JScrollPane scroller; protected JList jlist; protected LookupListModel dlm; protected LookupListChangeListener changeListener; private boolean focusGainedOrValidationChange; /** maximum height for the popup that displays the jlist */ protected final static int MAX_POPUP_HEIGHT = 150; /** maximum width; note that horizontal scroll bar is set to WHEN_NEEDED */ protected final static int MAX_POPUP_WIDTH = 500; private IRecordInternal parentState; private boolean keyBindingChangedValBeforeFocusEvent = false; private boolean consumeEnterReleased; private boolean useBackgroundListColor = false; private boolean useForegroundListColor = false; private Color listBackgroundColor = null; private Color listForegroundColor = null; private static Timer timer; private final IWindowVisibleChangeListener popupParentVisibleChangeListener = new IWindowVisibleChangeListener() { public void beforeVisibleChange(final IWindowVisibleChangeNotifier component, boolean newVisibleState) { if (!newVisibleState && popup != null) { popup.setVisible(false); } } }; public DataLookupField(IApplication app, RuntimeDataLookupField scriptable, CustomValueList list) { super(app, scriptable, list); init(app); createCustomListModel(list); } public DataLookupField(IApplication app, RuntimeDataLookupField scriptable, final LookupValueList list) { super(app, scriptable, list); init(app); createLookupListModel(list); } public DataLookupField(IApplication application, RuntimeDataLookupField scriptable, String serverName, String tableName, String dataProviderID) { super(application, scriptable); init(application); dlm = new LookupListModel(application, serverName, tableName, dataProviderID); } private void init(IApplication application) { this.application = application; super.setEditable(true); registerKeyboardAction(new HidePopup(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); } protected void createCustomListModel(CustomValueList vList) { if (dlm != null && changeListener != null) { dlm.getValueList().removeListDataListener(changeListener); } dlm = new LookupListModel(application, vList); if (changeListener == null) changeListener = new LookupListChangeListener(this); vList.addListDataListener(changeListener); } protected void createLookupListModel(LookupValueList vlist) { if (dlm != null && changeListener != null) { dlm.getValueList().removeListDataListener(changeListener); } dlm = new LookupListModel(application, vlist); if (dlm.isShowValues() != dlm.isReturnValues()) { try { if (changeListener == null) changeListener = new LookupListChangeListener(this); vlist.addListDataListener(changeListener); } catch (Exception e) { Debug.error("Error registering table listener for smart lookup"); //$NON-NLS-1$ Debug.error(e); } } } @Override public void setValueList(IValueList vl) { super.setValueList(vl); if (list instanceof CustomValueList) { createCustomListModel((CustomValueList)list); if (jlist != null) { jlist.setModel(dlm); } } setValue(getValue()); // force update the display value } @Override public ListDataListener getListener() { return changeListener; } /* * @see javax.swing.JFormattedTextField#setDocument(javax.swing.text.Document) */ @Override public void setDocument(Document doc) { super.setDocument(doc); } private LookupDocumentListener listener; @Override protected void processFocusEvent(FocusEvent e) { if (listener == null) //only needed on renderers { getDocument().addDocumentListener(listener = new LookupDocumentListener()); } focusGainedOrValidationChange = true; try { boolean showOnEmptyUIProp = ((Boolean)UIUtils.getUIProperty(this, IApplication.TYPE_AHEAD_SHOW_POPUP_WHEN_EMPTY, Boolean.TRUE)).booleanValue(); boolean showOnFocusUIProp = ((Boolean)UIUtils.getUIProperty(this, IApplication.TYPE_AHEAD_SHOW_POPUP_ON_FOCUS_GAIN, Boolean.TRUE)).booleanValue(); boolean valueIsEmptyOrNull = (getValue() == null || ("".equals(getValue()))); //$NON-NLS-1$ boolean valueIsNotEmpty = (getValue() != null && getValue().toString().length() > 0); boolean show = (showOnEmptyUIProp && (showOnFocusUIProp || (!showOnFocusUIProp && valueIsEmptyOrNull))) || (!showOnEmptyUIProp && showOnFocusUIProp && valueIsNotEmpty); super.processFocusEvent(e); if (e.getID() == FocusEvent.FOCUS_LOST && !e.isTemporary() && popup != null) { popup.setVisible(false); } else if (e.getID() == FocusEvent.FOCUS_GAINED && !e.isTemporary() && e.getOppositeComponent() != this && show) { // do not call fillValueList(true) directly because we need to work around this problem: // Tableview, tab to a type-ahead and type a char (for example "a"). Then type-ahead cell enters edit mode, // key bindings get processed on the type-ahead (changing the content) and only after that the focus gain event arrives. // In this case we must avoid showing all available options (instead of the ones starting with "a"), because char typing // behavior should have precedence on focus gain. fillValueList(!keyBindingChangedValBeforeFocusEvent); } } finally { focusGainedOrValidationChange = false; keyBindingChangedValBeforeFocusEvent = false; } } @Override protected boolean canLooseFocus() { return hasFocus(); } /* * @see javax.swing.text.JTextComponent#removeNotify() */ @Override public void removeNotify() { super.removeNotify(); if (popup != null) { popup.setVisible(false); } } @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { String prev = getText(); boolean processed = super.processKeyBinding(ks, e, condition, pressed); String current = getText(); if ((prev != null && !prev.equals(current)) || (prev == null && current != null)) { keyBindingChangedValBeforeFocusEvent = true; } return processed; } /* * (non-Javadoc) * * @see javax.swing.JComponent#processKeyEvent(java.awt.event.KeyEvent) */ @Override protected void processKeyEvent(KeyEvent e) { if (list instanceof LookupValueList) { ((LookupValueList)list).setDoNotQuery(true); } try { if (e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ENTER) { boolean doFill = false; synchronized (this) { if (task != null) { task.cancel(); task = null; doFill = true; } } if (doFill) fillValueListImpl(false); Object value = getValue(); commitSelectedValue(); super.processKeyEvent(e); if (!eventExecutor.getValidationEnabled() && !Utils.equalObjects(value, getValue()) && popup != null && popup.isVisible()) { // consume the enter release key in find mode. consumeEnterReleased = true; } } else if (e.getID() == KeyEvent.KEY_PRESSED && jlist != null && popup.isVisible()) { if (e.getKeyCode() == KeyEvent.VK_DOWN) { int index = jlist.getSelectedIndex() + 1; if (index < dlm.getSize()) { jlist.setSelectedIndex(index); } e.consume(); } else if (e.getKeyCode() == KeyEvent.VK_UP) { int index = jlist.getSelectedIndex() - 1; if (index >= 0) { jlist.setSelectedIndex(index); } e.consume(); } else { super.processKeyEvent(e); } } else if (e.getID() == KeyEvent.KEY_RELEASED && consumeEnterReleased) { // consume the enter release key in find mode. consumeEnterReleased = false; e.consume(); super.processKeyEvent(e); } else { super.processKeyEvent(e); } } finally { if (list instanceof LookupValueList) { ((LookupValueList)list).setDoNotQuery(false); } } } /** * */ private void commitSelectedValue() { if (jlist == null) return; int index = jlist.getSelectedIndex(); if (index >= 0) { Object real = dlm.getRealElementAt(index); Object display = real; if (dlm.getSize() != 0) { display = dlm.getElementAt(index); } if (list instanceof LookupValueList) { ((LookupValueList)list).addRow(real, display); } Object currentValue = getValue(); setValueObject(real); if (Utils.equalObjects(currentValue, real)) { // for example you have entry "aabb" as current value, and "aab" in field resulting in the selection of "aabb" in list; // so in this case although the current value is the same as the one seleced in the list, the text in the field is not up to date; // commit selected value must make sure the text is updated as well in this case... AbstractFormatter formatter = getFormatter(); if (formatter != null) { try { setText(formatter.valueToString(currentValue)); } catch (ParseException e) { Debug.error("Cannot put back text for already selected value when commiting value from list in lookup field: ", e); //$NON-NLS-1$ } } } if (editProvider != null) { editProvider.commitData(); } popup.setVisible(false); } } private TimerTask task = null; protected void fillValueList(final boolean firstTime) { synchronized (this) { if (task != null) { task.cancel(); task = null; } } if (firstTime) { fillValueListImpl(firstTime); } else { synchronized (DataLookupField.class) { if (timer == null) { timer = new Timer("Lookup ValueList Timer", true); //$NON-NLS-1$ } } synchronized (this) { task = new TimerTask() { @Override public void run() { application.invokeLater(new Runnable() { public void run() { synchronized (DataLookupField.this) { task = null; } fillValueListImpl(firstTime); } }); } }; timer.schedule(task, 500); } } } /** * @param firstTime */ private void fillValueListImpl(final boolean firstTime) { try { if (list != null && changeListener != null) list.removeListDataListener(changeListener); String txt = getText(); //if this Typeahead is using a masked formatter remove the mask characters before looking up the string if (getFormatter() != null && (getFormatter() instanceof FixedMaskFormatter)) { FixedMaskFormatter formatter = (FixedMaskFormatter)getFormatter(); int invalidOffset = formatter.getInvalidOffset(txt, true); invalidOffset = (invalidOffset == -1 ? 0 : invalidOffset); txt = txt.substring(0, invalidOffset); } if (txt.length() > 0 || Boolean.TRUE.equals(UIUtils.getUIProperty(this, IApplication.TYPE_AHEAD_SHOW_POPUP_WHEN_EMPTY, Boolean.TRUE))) { dlm.fill(parentState, dataProviderID, txt, firstTime); if (dlm.getSize() == 0 && firstTime && dlm.getValueList().hasRealValues()) { dlm.fill(parentState, dataProviderID, null, firstTime); } if (jlist != null) { jlist.setModel(dlm); jlist.setSelectedValue(txt, true); jlist.setFont(getFont()); if (isOpaque()) jlist.setBackground(getListBackgroundColor()); //use bgcolor only for opaque popup jlist.setForeground(getListForegroundColor()); } showPopup(); } else if (popup != null) { popup.setVisible(false); } } catch (Exception e) { Debug.error(e); } finally { if (list != null && changeListener != null) list.addListDataListener(changeListener); } } protected void searchValueList() { dlm.filter(getText()); showPopup(); } /* * @see javax.swing.text.JTextComponent#selectAll() */ @Override public void selectAll() { if (!eventExecutor.getSelectOnEnter() && popup != null && popup.isVisible()) return; super.selectAll(); } protected void showPopup() { if (!isEnabled() || !isEditable() || !hasFocus() || !isDisplayable() || dlm.getSize() == 0 || (dlm.getSize() == 1 && "".equals(dlm.getElementAt(0)))) //$NON-NLS-1$ { if (popup != null && popup.isVisible()) { popup.setVisible(false); } return; } final Window windowParent = SwingUtilities.getWindowAncestor(this); if (windowParent == null) { return; } if (popup != null && popup.getParent() != windowParent) { popup.dispose(); popup = null; } if (popup == null) { popup = new JWindow(windowParent) { @Override public void setVisible(boolean b) { super.setVisible(b); if (windowParent instanceof IWindowVisibleChangeNotifier) { if (b) ((IWindowVisibleChangeNotifier)windowParent).addWindowVisibleChangeListener(popupParentVisibleChangeListener); else ((IWindowVisibleChangeNotifier)windowParent).removeWindowVisibleChangeListener(popupParentVisibleChangeListener); } } }; popup.setFocusable(false); popup.getContentPane().setLayout(new BorderLayout()); addAncestorListener(new AncestorListener() { public void ancestorAdded(AncestorEvent event) { /* not used */ } public void ancestorMoved(AncestorEvent event) { if (popup != null && popup.isVisible()) { Rectangle visibleRect = new Rectangle(); DataLookupField.this.computeVisibleRect(visibleRect); if (visibleRect.isEmpty()) { popup.setVisible(false); // type-ahead probably scrolled outside of visible area } else { setPopupLocation(); } } } public void ancestorRemoved(AncestorEvent event) { /* not used */ } }); popup.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { if (popup != null && popup.isVisible()) { setPopupLocation(); } } }); if (jlist == null) { jlist = new JList(dlm); jlist.setCellRenderer(new FixedListCellRenderer()); jlist.addMouseListener(new ListMouseListener()); jlist.setFont(getFont()); if (isOpaque()) jlist.setBackground(getListBackgroundColor()); //use bgcolor only for opaque popup jlist.setForeground(getListForegroundColor()); jlist.setFocusable(false); jlist.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { jlist.ensureIndexIsVisible(jlist.getSelectedIndex()); } }); jlist.setSelectedValue(getText(), true); } scroller = new JScrollPane(jlist, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); scroller.setFocusable(false); scroller.getVerticalScrollBar().setFocusable(false); popup.getContentPane().add(scroller, BorderLayout.CENTER); } int popupHeight = (int)jlist.getPreferredSize().getHeight() + scroller.getInsets().bottom + scroller.getInsets().top; int popupWidth = (int)jlist.getPreferredSize().getWidth() + scroller.getInsets().right + scroller.getInsets().left; Border viewportBorder = scroller.getViewportBorder(); if (viewportBorder != null) { Insets viewportBorderInsets = viewportBorder.getBorderInsets(this.scroller); popupWidth += viewportBorderInsets.left + viewportBorderInsets.right; popupHeight += viewportBorderInsets.top + viewportBorderInsets.bottom; } boolean heightLimited = false; if (popupHeight > MAX_POPUP_HEIGHT) { // height was limited => we will have vertical scrollbar; make with larger so that we avoid horiz. scroll bar if possible popupHeight = MAX_POPUP_HEIGHT; popupWidth += 25; heightLimited = true; } int fieldWidth = this.getWidth(); if (popupWidth > fieldWidth) { if (popupWidth > MAX_POPUP_WIDTH) { popupWidth = MAX_POPUP_WIDTH; // width limited - so we will have horiz. scrollbar; if we have more space to grow in height, do so to avoid vertical scrollbar if (!heightLimited && (popupHeight + 25) <= MAX_POPUP_HEIGHT) { popupHeight += 25; } } } else { popupWidth = fieldWidth; } scroller.setPreferredSize(new Dimension(popupWidth, popupHeight)); popup.pack(); if (dlm.getSize() > 0 && jlist.getSelectedIndex() < 0) { jlist.setSelectedIndex(0); } if (!popup.isVisible() && this.getParent() != null) { setPopupLocation(); popup.setVisible(true); } } private void setPopupLocation() { Point p = this.getLocation(); SwingUtilities.convertPointToScreen(p, this.getParent()); Window window = SwingUtilities.getWindowAncestor(this); Rectangle screenBounds = window.getGraphicsConfiguration().getBounds(); p.y = p.y + this.getHeight(); if (screenBounds != null) { // decide based on screen (try to make pop-up fit inside screen) if (((p.y + popup.getHeight()) > (screenBounds.getY() + screenBounds.getHeight())) && ((p.y - popup.getHeight() - this.getHeight()) >= screenBounds.getY())) { p.y = p.y - popup.getHeight() - this.getHeight(); } if (((p.x + popup.getWidth()) > (screenBounds.getX() + screenBounds.getWidth())) && ((p.x - popup.getWidth() + this.getWidth()) >= screenBounds.getX())) { p.x = p.x - popup.getWidth() + this.getWidth(); } } else { // decide based on window (try to make pop-up fit inside window) - this is just a back-up case that shouldn't happen if (((p.y + popup.getHeight()) > (window.getHeight() + window.getY())) && ((p.y - popup.getHeight() - this.getHeight()) >= window.getY())) { p.y = p.y - popup.getHeight() - this.getHeight(); } if (((p.x + popup.getWidth()) > (window.getWidth() + window.getX())) && ((p.x - popup.getWidth() + this.getWidth()) >= window.getX())) { p.x = p.x - popup.getWidth() + this.getWidth(); } } popup.setLocation(p); } /* * (non-Javadoc) * * @see com.servoy.j2db.dataprocessing.IDisplayRelatedData#setFoundSet(com.servoy.j2db.dataprocessing.IRecord, com.servoy.j2db.dataprocessing.IFoundSet, * boolean) */ public void setRecord(IRecordInternal parentState, boolean stopEditing) { if (this.parentState == parentState) return; dependencyChanged(parentState); } public void dependencyChanged(IRecordInternal record) { this.parentState = record; if (list != null) { Object o = getValue(); int index = -1; if (dataProviderID != null && !ScopesUtils.isVariableScope(dataProviderID)) { index = dataProviderID.lastIndexOf('.'); } if (index == -1 || parentState == null || dataProviderID == null) { list.fill(parentState); } else { IFoundSetInternal relatedFoundSet = parentState.getRelatedFoundSet(dataProviderID.substring(0, index)); if (relatedFoundSet == null) { list.fill(parentState.getParentFoundSet().getPrototypeState()); } else if (relatedFoundSet.getSize() == 0) { list.fill(relatedFoundSet.getPrototypeState()); } else { IRecordInternal relatedRecord = relatedFoundSet.getRecord(relatedFoundSet.getSelectedIndex()); list.fill(relatedRecord); } } if (editProvider != null) { editProvider.setAdjusting(true); } try { setValue(null); setValue(o); } finally { if (editProvider != null) { editProvider.setAdjusting(false); } } } } public String getSelectedRelationName() { if (relationName == null && list != null) { relationName = list.getRelationName(); } return relationName; } private String relationName = null; public String[] getAllRelationNames() { String selectedRelationName = getSelectedRelationName(); if (selectedRelationName == null) { return new String[0]; } else { return new String[] { selectedRelationName }; } } /** * @see com.servoy.j2db.smart.dataui.DataField#setValidationEnabled(boolean) */ @Override public void setValidationEnabled(boolean b) { if (eventExecutor.getValidationEnabled() == b) return; if (dataProviderID != null && ScopesUtils.isVariableScope(dataProviderID)) return; if (list != null && list.getFallbackValueList() != null) { IValueList vlist = list; if (!b) { vlist = list.getFallbackValueList(); } if (vlist instanceof CustomValueList) { createCustomListModel((CustomValueList)vlist); } else { createLookupListModel((LookupValueList)vlist); } if (jlist != null) { jlist.setModel(dlm); } } try { focusGainedOrValidationChange = true; eventExecutor.setValidationEnabled(b); consumeEnterReleased = false; boolean prevEditState = editState; if (b) { setEditable(wasEditable); if (editProvider != null) { editProvider.setAdjusting(true); } try { setValue(null);//prevent errors } finally { if (editProvider != null) { editProvider.setAdjusting(false); } } } else { wasEditable = isEditable(); if (!Boolean.TRUE.equals(application.getClientProperty(IApplication.LEAVE_FIELDS_READONLY_IN_FIND_MODE))) { setEditable(true); } } editState = prevEditState; } finally { focusGainedOrValidationChange = false; } } /* * (non-Javadoc) * * @see com.servoy.j2db.dataprocessing.IDisplayRelatedData#getDefaultSort() */ public List<SortColumn> getDefaultSort() { return dlm.getDefaultSort(); } public void notifyVisible(boolean b, List<Runnable> invokeLaterRunnables) { //ignore } /* * (non-Javadoc) * * @see com.servoy.j2db.dataprocessing.IDisplayRelatedData#deregister() */ @Override public void destroy() { super.destroy(); if (popup != null) { popup.dispose(); popup = null; jlist = null; } if (list != null && changeListener != null) list.removeListDataListener(changeListener); } private class HidePopup implements ActionListener { public void actionPerformed(ActionEvent e) { if (popup != null) { popup.setVisible(!popup.isVisible()); } } } private class LookupDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { if (focusGainedOrValidationChange || skipPropertyChange) return; if (dlm != null && !dlm.hasMoreRows() && getText().length() != 1 && (e.getOffset() + e.getLength()) == e.getDocument().getLength() && !getText().contains("%")) { if (editProvider == null || !editProvider.isAdjusting()) { searchValueList(); } } else { if (editProvider == null || !editProvider.isAdjusting()) { fillValueList(false); } } } public void removeUpdate(DocumentEvent e) { if (focusGainedOrValidationChange || skipPropertyChange) return; if (editProvider == null || !editProvider.isAdjusting()) { fillValueList(false); } } public void changedUpdate(DocumentEvent e) { if (focusGainedOrValidationChange || skipPropertyChange) return; if (editProvider == null || !editProvider.isAdjusting()) { fillValueList(false); } } } private class ListMouseListener extends MouseAdapter { @Override public void mouseReleased(MouseEvent e) { commitSelectedValue(); } } private class FixedListCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList lst, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (IValueList.SEPARATOR_DESIGN_VALUE.equals(value)) { return super.getListCellRendererComponent(lst, null, index, isSelected, cellHasFocus); } Object val = value; if (val instanceof DisplayString) { val = val.toString(); } // if the value is not a string try to format it. if (!(val instanceof String)) { AbstractFormatter formatter = getFormatter(); if (formatter != null) { try { val = formatter.valueToString(val); } catch (ParseException e) { // ignore } } } if (val instanceof String && ((String)val).trim().length() == 0) { setText("A"); //$NON-NLS-1$ Dimension size = getPreferredSize(); setText(""); //$NON-NLS-1$ setPreferredSize(size); } else { setPreferredSize(null); // let UI decide } return super.getListCellRendererComponent(lst, val, index, isSelected, cellHasFocus); } } @Override public void setBackground(Color color1, Color color2) { if (!Utils.equalObjects(color1, color2)) { useBackgroundListColor = true; this.listBackgroundColor = color2; } else { // if the same, still use background useBackgroundListColor = false; } setBackground(color1); } private Color getListBackgroundColor() { if (useBackgroundListColor) { return listBackgroundColor; } return getBackground(); } @Override public void setForeground(Color color1, Color color2) { if (!Utils.equalObjects(color1, color2)) { useForegroundListColor = true; this.listForegroundColor = color2; } else { // if the same, still use background useForegroundListColor = false; } setForeground(color1); } private Color getListForegroundColor() { if (useForegroundListColor) { return listForegroundColor; } return getForeground(); } }