/*
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.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FocusTraversalPolicy;
import java.awt.FontMetrics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.ItemSelectable;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.Format;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ComboBoxEditor;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.InputMap;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.Document;
import com.servoy.base.util.ITagResolver;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.IScriptExecuter;
import com.servoy.j2db.component.ComponentFormat;
import com.servoy.j2db.dataprocessing.IDisplayData;
import com.servoy.j2db.dataprocessing.IDisplayRelatedData;
import com.servoy.j2db.dataprocessing.IEditListener;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.dataprocessing.IValueList;
import com.servoy.j2db.dataprocessing.SortColumn;
import com.servoy.j2db.persistence.Column;
import com.servoy.j2db.persistence.IColumnTypes;
import com.servoy.j2db.ui.IDataRenderer;
import com.servoy.j2db.ui.IEventExecutor;
import com.servoy.j2db.ui.IFieldComponent;
import com.servoy.j2db.ui.IFormattingComponent;
import com.servoy.j2db.ui.ILabel;
import com.servoy.j2db.ui.ISupportCachedLocationAndSize;
import com.servoy.j2db.ui.ISupportOnRender;
import com.servoy.j2db.ui.ISupportSpecialClientProperty;
import com.servoy.j2db.ui.ISupportValueList;
import com.servoy.j2db.ui.scripting.RuntimeDataCombobox;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.HtmlUtils;
import com.servoy.j2db.util.ISkinnable;
import com.servoy.j2db.util.RoundHalfUpDecimalFormat;
import com.servoy.j2db.util.ScopesUtils;
import com.servoy.j2db.util.StateFullSimpleDateFormat;
import com.servoy.j2db.util.Text;
import com.servoy.j2db.util.UIUtils;
import com.servoy.j2db.util.Utils;
import com.servoy.j2db.util.model.ComboModelListModelWrapper;
/**
* Runtime swing combo box component
* @author jblok
*/
public class DataComboBox extends JComboBox implements IDisplayData, IDisplayRelatedData, IFieldComponent, ISkinnable, ItemListener,
ISupportCachedLocationAndSize, ISupportSpecialClientProperty, ISupportValueList, IFormattingComponent, ISupportOnRender
{
private String dataProviderID;
private final ComboModelListModelWrapper list;
private Format format;
private Border marginBorder;
protected IValueList vl;
protected IApplication application;
private final EventExecutor eventExecutor;
private String tooltip;
private MouseAdapter rightclickMouseAdapter = null;
// the call to super() on winXP L&F (probably others as well) will set the foreground color
// of the popup to what getForeground() returns; the members are not yet
// initialized in this stage - so booleans are not set yet to default values (they are false);
// so this is why I changed isValueValid to invalid (so the initial value is false and getForeground
// does not return Color.RED)
private boolean invalid = false;
private Object previousValidValue;
private FormattedComboBoxEditor formattedComboEditor;
private final AccessibleStateHolder accesibleStateHolder;
private final DocumentListener closePopupDocumentListener;
private int keyReleaseToBeIgnored = -1;
private final RuntimeDataCombobox scriptable;
private boolean showingPopup;
private static final int MAXIMUM_ROWS = 20;
private int lastPopupHighlightRequested = -1;
public DataComboBox(IApplication application, RuntimeDataCombobox scriptable, IValueList vl)
{
super();
setHorizontalAlignment(SwingConstants.LEFT);
hackDefaultPopupWidthBehavior();
this.application = application;
this.vl = vl;
eventExecutor = new EventExecutor(this);
list = new ComboModelListModelWrapper(vl, false, true);
setModel(list);
accesibleStateHolder = new AccessibleStateHolder(new AccessibleStateApplier()
{
public void setEditable(boolean editable)
{
DataComboBox.this.setComboEditable(editable);
}
public void setEnabled(boolean enabled)
{
if (isEnabled() != enabled)
{
DataComboBox.this.setEnabled(enabled);
}
}
public void setLabelsEnabled(boolean labelsEnabled)
{
if (labels != null)
{
for (int i = 0; i < labels.size(); i++)
{
ILabel label = labels.get(i);
label.setComponentEnabled(labelsEnabled);
}
}
}
});
//DISABLED:for 1.4
// setPrototypeDisplayValue(new Integer(0));
setMaximumRowCount(MAXIMUM_ROWS);
// addPopupMenuListener(this);
// setLightWeightPopupEnabled(false);
// if we have an editable combo, close popup if user starts to type;
// otherwise we will not know what value to use when ENTER is pressed - the one from the popup
// or the one from the text field
closePopupDocumentListener = new DocumentListener()
{
public void changedUpdate(DocumentEvent e)
{
if (isPopupVisible())
{
hidePopup();
}
}
public void insertUpdate(DocumentEvent e)
{
if (isPopupVisible())
{
hidePopup();
}
}
public void removeUpdate(DocumentEvent e)
{
if (isPopupVisible())
{
hidePopup();
}
}
};
// When drop-down is visible, key_up and key_down must not fire action/selection events. Only final selection should trigger these events.
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4199622
// for 1.3
putClientProperty("JComboBox.lightweightKeyboardNavigation", "Lightweight"); //$NON-NLS-1$ //$NON-NLS-2$
// for >= 1.4
if (UIManager.getLookAndFeel().getClass().getName().toUpperCase().indexOf("AQUA") < 0) //$NON-NLS-1$
{
// this in not needed on MAC as default L&F overrides this behavior...
// this would also cause appearance problems for comboboxes on MAC (JVM bug(s))
putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); //$NON-NLS-1$
}
// this property says whether you can select items from the drop-down with ENTER or not in an editable combobox (since 1.5xx - visible bug effect in 1.6)
UIManager.put("ComboBox.isEnterSelectablePopup", Boolean.TRUE); //$NON-NLS-1$
setRenderer(new DividerListCellRenderer(getRenderer()));
this.scriptable = scriptable;
Object o = getUI().getAccessibleChild(this, 0);
if (o instanceof ComboPopup)
{
((ComboPopup)o).getList().setSelectionModel(new DefaultListSelectionModel()
{
@Override
public void setSelectionInterval(int index0, int index1)
{
int leftInd = index0;
int rightInd = index1;
Object x = getListModelWrapper().get(index0);
if (x == IValueList.SEPARATOR)
{
if (lastPopupHighlightRequested != index0)
{
int index = getMinSelectionIndex();
if (index < index0)
{
leftInd++;
rightInd++;
}
else
{
leftInd--;
rightInd--;
}
super.setSelectionInterval(leftInd, rightInd);
} // else do nothing
}
else super.setSelectionInterval(leftInd, rightInd);
lastPopupHighlightRequested = index0;
}
});
}
AbstractAction navigatingViaKeyboardAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
lastPopupHighlightRequested = -1; // to make sure separators are skipped when pressing up/down and still avoid flicker when just moving mouse over the separator
}
};
// the following 2 will also consume (besides the intended effect) the up/down press events in combos for parent hierarchy and window level actions but
// I guess that shouldn't be a problem as combos seem to have done this before as well, because of already registered keystrokes
prependKeyPressAction(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "keyNavS", navigatingViaKeyboardAction); //$NON-NLS-1$
prependKeyPressAction(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "keyNavS", navigatingViaKeyboardAction); //$NON-NLS-1$
}
/**
* Adds a pre-action to a keystroke, keeping the old action as well if present.
*/
private void prependKeyPressAction(int scope, KeyStroke keyStroke, Object actionKey, final Action action)
{
InputMap im = getInputMap(scope);
Object actionKeyImpl = im.get(keyStroke);
if (actionKeyImpl == null)
{
actionKeyImpl = actionKey;
im.put(keyStroke, actionKeyImpl);
}
ActionMap am = getActionMap();
final Action oldAction = am.get(actionKeyImpl);
am.put(actionKeyImpl, new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
action.actionPerformed(e);
if (oldAction != null) oldAction.actionPerformed(e);
}
});
}
public final RuntimeDataCombobox getScriptObject()
{
return scriptable;
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComboBox#setPopupVisible(boolean)
*/
@Override
public void setPopupVisible(boolean v)
{
if (v)
{
showingPopup = true;
int width = getWidth();
try
{
setSize(getExtendedWidth(), getHeight());
super.setPopupVisible(v);
}
finally
{
showingPopup = false;
setSize(width, getHeight());
}
}
else
{
super.setPopupVisible(v);
}
}
private int getExtendedWidth()
{
int width = (int)getSize().getWidth();
FontMetrics fontMetrics = getFontMetrics(getFont());
for (int i = 0; i < getItemCount(); i++)
{
Object obj = getItemAt(i);
if (obj == null) continue;
String formatted = null;
// use the formater if the object is not a string (same compare as in getListCellRenderer())
if (format != null && !(obj instanceof String))
{
try
{
formatted = format.format(obj);
}
catch (IllegalArgumentException ex)
{
Debug.trace("Error formatting value for combobox " + dataProviderID + ", value: '" + obj + "', " + ex);
}
}
if (formatted == null)
{
formatted = obj.toString();
}
int textWidth = fontMetrics.stringWidth(formatted);
int addedWidth = 10;
if (getItemCount() > MAXIMUM_ROWS)
{
// for scrollbars
addedWidth += 20;
if (Utils.isAppleMacOS())
{
// for checkbox
addedWidth += 25;
}
}
width = Math.max(width, textWidth + addedWidth); // add offset 10
}
return width;
}
private void hackDefaultPopupWidthBehavior()
{
// Apple doesn't need this, its already bigger and in java 7 it will not open at all.
if (!Utils.isAppleMacOS())
{
addPopupMenuListener(new PopupMenuListener()
{
public void popupMenuWillBecomeVisible(PopupMenuEvent e)
{
// if not show through our setPopupVisible method.. (but by a mouse click in the ui classes)
if (!showingPopup)
{
firePopupMenuCanceled();
// show the popup again a bit later
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
if (isShowing())
{
// first close the one that was just being shown, but only through the ui classes
setPopupVisible(false);
// then show the popup through our own method so that the size is correct.
setPopupVisible(true);
}
}
});
}
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
{
}
public void popupMenuCanceled(PopupMenuEvent e)
{
}
});
}
}
/**
* @return the list
*/
private ComboModelListModelWrapper getListModelWrapper()
{
return (ComboModelListModelWrapper)getModel();
}
/*
* _____________________________________________________________ Methods for event handling
*/
public void addScriptExecuter(IScriptExecuter el)
{
eventExecutor.setScriptExecuter(el);
// if actionListner then fire action command on item change
addItemListener(this);
}
public IEventExecutor getEventExecutor()
{
return eventExecutor;
}
public void setEnterCmds(String[] ids, Object[][] args)
{
eventExecutor.setEnterCmds(ids, args);
}
public void setLeaveCmds(String[] ids, Object[][] args)
{
eventExecutor.setLeaveCmds(ids, args);
}
public boolean isValueValid()
{
return !invalid;
}
public void setValueValid(boolean valid, Object oldVal)
{
if (isEditable())
{
((FormattedComboBoxEditor)getEditor()).editor.setValueValid(valid, oldVal);
}
invalid = !valid;
if (invalid)
{
previousValidValue = oldVal;
if (!isEditable())
{
application.invokeLater(new Runnable()
{
public void run()
{
requestFocus();
}
});
} // else the editor will grab focus itself
}
else
{
previousValidValue = null;
}
}
@Override
public Color getForeground()
{
if (isValueValid())
{
return super.getForeground();
}
return Color.RED;
}
public void notifyLastNewValueWasChange(Object oldVal, Object newVal)
{
if (previousValidValue != null)
{
eventExecutor.fireChangeCommand(previousValidValue, newVal, false, this);
}
else
{
eventExecutor.fireChangeCommand(oldVal, newVal, false, this);
}
// if change cmd is not succeeded also don't call action cmd?
if (isValueValid())
{
eventExecutor.fireActionCommand(false, this);
}
}
public void setChangeCmd(String id, Object[] args)
{
eventExecutor.setChangeCmd(id, args);
}
public void setActionCmd(String id, Object[] args)
{
eventExecutor.setActionCmd(id, args);
}
public void setRightClickCommand(String rightClickCmd, Object[] args)
{
eventExecutor.setRightClickCmd(rightClickCmd, args);
if (rightClickCmd != null && rightclickMouseAdapter == null)
{
rightclickMouseAdapter = new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
if (e.isPopupTrigger()) handle(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
if (e.isPopupTrigger()) handle(e);
}
private void handle(MouseEvent e)
{
if (isEnabled())
{
eventExecutor.fireRightclickCommand(true, DataComboBox.this, e.getModifiers(), e.getPoint());
}
}
};
if (isEditable())
{
FormattedComboBoxEditor ed = (FormattedComboBoxEditor)getEditor();
ed.getEditorComponent().addMouseListener(rightclickMouseAdapter);
ed.getEditorComponent().addKeyListener(eventExecutor);
}
else
{
addMouseListener(rightclickMouseAdapter);
// as this is a composite control, also add listener to its parts
int nrComboParts = getComponentCount();
for (int i = 0; i < nrComboParts; i++)
getComponent(i).addMouseListener(rightclickMouseAdapter);
addKeyListener(eventExecutor);
}
}
}
protected boolean wasEditable;
public void setValidationEnabled(boolean b)
{
if (eventExecutor.getValidationEnabled() == b) return;
if (dataProviderID != null && ScopesUtils.isVariableScope(dataProviderID)) return;
eventExecutor.setValidationEnabled(b);
if (vl.getFallbackValueList() != null)
{
if (b)
{
getListModelWrapper().register(vl);
}
else
{
getListModelWrapper().register(vl.getFallbackValueList());
}
}
accesibleStateHolder.blockEditStateChange(true);
if (b)
{
setEditable(wasEditable);
accesibleStateHolder.applyState();
}
else
{
wasEditable = isEditable();
setComboEditable(!getListModelWrapper().hasRealValues());
if (!isEnabled() && accesibleStateHolder.isAccessible() &&
!Boolean.TRUE.equals(application.getClientProperty(IApplication.LEAVE_FIELDS_READONLY_IN_FIND_MODE))) setEditable(true);
setEnabled(true);
}
if (isEditable())
{
// changing validation enabled state on the editor will change it's document;
// so register the listener to the correct document again
formattedComboEditor.editor.getDocument().removeDocumentListener(closePopupDocumentListener);
formattedComboEditor.setValidationEnabled(b);
formattedComboEditor.editor.getDocument().addDocumentListener(closePopupDocumentListener);
formattedComboEditor.setItem(getSelectedItem());
}
accesibleStateHolder.blockEditStateChange(false);
}
public void setSelectOnEnter(boolean b)
{
eventExecutor.setSelectOnEnter(b);
}
//_____________________________________________________________
public void destroy()
{
getListModelWrapper().deregister();
}
class DividerListCellRenderer implements ListCellRenderer
{
ListCellRenderer dividerRenderer;
JLabel cachedLabel;
Border cachedBorder;
public DividerListCellRenderer(ListCellRenderer renderer)
{
super();
this.dividerRenderer = renderer;
}
public Component getListCellRendererComponent(JList jlist, Object listValue, int index, boolean isSelected, boolean cellHasFocus)
{
if (IValueList.SEPARATOR.equals(listValue))
{
return new VariableSizeJSeparator(SwingConstants.HORIZONTAL, 15);
}
else
{
Object formattedValue = listValue;
if (!"".equals(formattedValue) && format != null && formattedValue != null && !(formattedValue instanceof String)) //$NON-NLS-1$
{
try
{
formattedValue = format.format(formattedValue);
}
catch (IllegalArgumentException ex)
{
Debug.error("Error formatting value for combobox " + dataProviderID + ", " + ex); //$NON-NLS-1$//$NON-NLS-2$
}
}
if (cachedLabel != null)
{
cachedLabel.setBorder(cachedBorder);
}
Component comp = dividerRenderer.getListCellRendererComponent(jlist, formattedValue, index, isSelected, cellHasFocus);
//nothing does work we cannot get the collapst combobox transparent
// if (comp instanceof JComponent)
// {
// ((JComponent)list.getParent()).setOpaque(DataComboBox.this.isOpaque());
// list.setOpaque(DataComboBox.this.isOpaque());
// ((JComponent)comp).setOpaque(DataComboBox.this.isOpaque());
// }
if (comp instanceof JLabel)
{
((JLabel)comp).setHorizontalAlignment(getHorizontalAlignment());
if (marginBorder != null)
{
cachedLabel = (JLabel)comp;
cachedBorder = cachedLabel.getBorder();
if (index >= 0)
{
((JLabel)comp).setBorder(marginBorder);
}
else
{
((JLabel)comp).setBorder(BorderFactory.createCompoundBorder(((JLabel)comp).getBorder(), marginBorder));
}
}
}
return comp;
}
}
}
/**
* A JSeparator substitute that is able to take up more then two pixels of space and draw the separator graphics in the middle of the occupied space.
*/
public static class VariableSizeJSeparator extends JPanel
{
private final int space;
private final int orientation;
public VariableSizeJSeparator(int orientation, int space)
{
super(new GridBagLayout());
this.space = space;
this.orientation = orientation;
setOpaque(false);
GridBagConstraints c = new GridBagConstraints();
int row = 0, col = 0;
JLabel l = new JLabel();
c.gridy = row;
c.gridx = col;
if (orientation == SwingConstants.HORIZONTAL)
{
c.weighty = 1;
row++;
}
else
{
c.weightx = 1;
col++;
}
add(l, c);
c = new GridBagConstraints();
JSeparator separator = new JSeparator(orientation);
c.gridy = row;
c.gridx = col;
if (orientation == SwingConstants.HORIZONTAL)
{
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
row++;
}
else
{
c.fill = GridBagConstraints.VERTICAL;
c.weighty = 1;
col++;
}
add(separator, c);
c = new GridBagConstraints();
l = new JLabel();
c.gridy = row;
c.gridx = col;
if (orientation == SwingConstants.HORIZONTAL)
{
c.weighty = 1;
}
else
{
c.weightx = 1;
}
add(l, c);
}
@Override
public Dimension getPreferredSize()
{
if (orientation == SwingConstants.HORIZONTAL)
{
return new Dimension(0, space);
}
else
{
return new Dimension(space, 0);
}
}
}
@Override
public void setUI(ComponentUI ui)
{
super.setUI(ui);
}
public void setMaxLength(int i)
{
ComboBoxEditor cbe = getEditor();
if (cbe instanceof FormattedComboBoxEditor)
{
((FormattedComboBoxEditor)cbe).setMaxLength(i);
}
}
public void setMargin(Insets m)
{
ComboBoxEditor cbe = getEditor();
if (cbe instanceof FormattedComboBoxEditor)
{
((FormattedComboBoxEditor)cbe).setMargin(m);
}
else
{
marginBorder = BorderFactory.createEmptyBorder(m.top, m.left, m.bottom, m.right);
// setBorder(BorderFactory.createCompoundBorder(getBorder(), ));
}
}
public Insets getMargin()
{
return null;
}
public Document getDocument()
{
ComboBoxEditor cbe = getEditor();
if (cbe instanceof FormattedComboBoxEditor)
{
return ((FormattedComboBoxEditor)cbe).getDocument();
}
return null;
}
/*
* (non-Javadoc)
*
* @see com.servoy.j2db.dataui.IFieldComponent#setOpaque(boolean)
*/
@Override
public void setOpaque(boolean b)
{
super.setOpaque(b);
ComboBoxEditor cbe = getEditor();
if (cbe != null && cbe.getEditorComponent() != null && cbe.getEditorComponent() instanceof JComponent)
{
((JComponent)cbe.getEditorComponent()).setOpaque(b);
}
}
// implements a Servoy IFieldComponent interface method, but also overrides JComboBox method - and we
// must differentiate between the two
@Override
public void setEditable(boolean editable)
{
// never allow combos with real values to be editable
accesibleStateHolder.setEditable(editable && (!getListModelWrapper().hasRealValues()));
}
@Override
public void contentsChanged(ListDataEvent e)
{
super.contentsChanged(e);
boolean editable = accesibleStateHolder.isEditable();
if (editable && getListModelWrapper().hasRealValues())
{
// happens when set valuelist items at runtime
setEditable(false);
}
}
private boolean isEditorTransferHandlerDisabled;
public void disableEditorTransferHandler()
{
isEditorTransferHandlerDisabled = true;
if (formattedComboEditor != null) formattedComboEditor.editor.setTransferHandler(null);
}
private void setComboEditable(boolean editable)
{
boolean wasEditable = isEditable();
if (editable)
{
if (!wasEditable || formattedComboEditor == null)
{
if (formattedComboEditor == null)
{
formattedComboEditor = new FormattedComboBoxEditor(application, getEditor(), getModel());
formattedComboEditor.editor.setDataProviderID(getDataProviderID());
formattedComboEditor.getEditorComponent().setName(getName());
formattedComboEditor.editor.setOpaque(isOpaque());
formattedComboEditor.editor.setHorizontalAlignment(getHorizontalAlignment());
formattedComboEditor.editor.setBorder(null);
formattedComboEditor.editor.getDocument().addDocumentListener(closePopupDocumentListener);
if (isEditorTransferHandlerDisabled) formattedComboEditor.editor.setTransferHandler(null);
if (marginBorder instanceof EmptyBorder)
{
formattedComboEditor.editor.setMargin(((EmptyBorder)marginBorder).getBorderInsets());
}
if (!Utils.isAppleMacOS())
{
// on MAC default L&F, if you type something in an editable combo while the dropdown is visible
// and then press ENTER - the typed content will dissapear - so it can be annoying to
// show the dropdown when the combo gets focus (the user would need to press ESC in order to type...)
formattedComboEditor.getEditorComponent().addFocusListener(new FocusListener()
{
//dipatch to my listers
public void focusGained(FocusEvent e)
{
if (!DataComboBox.this.isPopupVisible() && DataComboBox.this.isVisible() && DataComboBox.this.isEnabled() &&
Boolean.TRUE.equals(UIUtils.getUIProperty(DataComboBox.this, IApplication.COMBOBOX_SHOW_POPUP_ON_FOCUS_GAIN, Boolean.TRUE)))
{
try
{
showPopup();
}
catch (Exception ex)
{
}
}
}
//dipatch to my listers
public void focusLost(FocusEvent e)
{
if (DataComboBox.this.isPopupVisible() && DataComboBox.this.isVisible())
{
try
{
// Fix for 1.4.1 focus goes to a window (the popup itself)
if (!(e.getOppositeComponent() instanceof Window))
{
hidePopup();
}
}
catch (Exception ex)
{
}
}
}
});
}
}
setEditor(formattedComboEditor);
eventExecutor.setEnclosedComponent(getEditor().getEditorComponent());
super.setEditable(true);
if (editProvider != null)
{
removeFocusListener(editProvider);
formattedComboEditor.getEditorComponent().addFocusListener(editProvider);
}
repaint();
}
}
else if (wasEditable)
{
if (!eventExecutor.mustFireFocusGainedCommand())
{
// event executor in bad state because focus lost was not called yet, have to fix this
eventExecutor.skipNextFocusLost();
eventExecutor.resetFireFocusGainedCommand();
eventExecutor.fireLeaveCommands(this, false, IEventExecutor.MODIFIERS_UNSPECIFIED);
}
setEditor(formattedComboEditor.getDefaultEditor());
eventExecutor.setEnclosedComponent(this);
super.setEditable(false);
if (editProvider != null)
{
addFocusListener(editProvider);
formattedComboEditor.getEditorComponent().removeFocusListener(editProvider);
}
repaint();
}
}
public boolean isComponentEnabled()
{
return accesibleStateHolder.isEnabled();
}
@Override
protected void processFocusEvent(FocusEvent e)
{
super.processFocusEvent(e);
if (!isEditable())
{
if (e.getID() == FocusEvent.FOCUS_GAINED && !isPopupVisible() && isVisible() && isEnabled() &&
Boolean.TRUE.equals(UIUtils.getUIProperty(this, IApplication.COMBOBOX_SHOW_POPUP_ON_FOCUS_GAIN, Boolean.TRUE)))
{
try
{
showPopup();
}
catch (Exception ex)
{
}
}
else if (e.getID() == FocusEvent.FOCUS_LOST && isPopupVisible() && isVisible())
{
try
{
// Fix for 1.4.1 focus goes to a window (the popup itself)
if (!(e.getOppositeComponent() instanceof Window))
{
hidePopup();
}
}
catch (Exception ex)
{
}
}
}
}
private boolean tabKeyEvent = false;
@Override
public void processKeyEvent(KeyEvent e)
{
tabKeyEvent = (e.getKeyCode() == KeyEvent.VK_TAB);
super.processKeyEvent(e);
tabKeyEvent = false;
}
@Override
public void hidePopup()
{
// do not hide if it comes from processfocusevent behavior (open popup on focus gain)
boolean popupShown = tabKeyEvent && !isEditable() && isEnabled() && isVisible() &&
Boolean.TRUE.equals(UIUtils.getUIProperty(this, IApplication.COMBOBOX_SHOW_POPUP_ON_FOCUS_GAIN, Boolean.TRUE));
if (!popupShown)
{
super.hidePopup();
}
}
@Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed)
{
// in find mode, ESC or ENTER while popup is open should not trigger search (should not reach
// the perform/cancel search key bindings in parent components; if popup was closed on keypress
// then consider keyReleased on the same key used also
boolean popupWasOpen = isPopupVisible();
boolean keyEventWasUsed = super.processKeyBinding(ks, e, condition, pressed);
if ((e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP) && e.getModifiers() == InputEvent.ALT_MASK)
{
keyReleaseToBeIgnored = -1;
keyEventWasUsed = false;
}
else if (e.getKeyCode() == KeyEvent.VK_F && e.getModifiers() == InputEvent.CTRL_MASK)
{
keyReleaseToBeIgnored = -1;
keyEventWasUsed = false;
}
else if (popupWasOpen && !isPopupVisible() && ks.getKeyEventType() == KeyEvent.KEY_PRESSED &&
(ks.getKeyCode() == KeyEvent.VK_ESCAPE || ks.getKeyCode() == KeyEvent.VK_ENTER))
{
keyReleaseToBeIgnored = ks.getKeyCode();
}
else if (keyReleaseToBeIgnored == ks.getKeyCode() && ks.getKeyEventType() == KeyEvent.KEY_RELEASED)
{
keyReleaseToBeIgnored = -1;
return true;
}
else if (ks.getKeyEventType() != KeyEvent.KEY_TYPED)
{
keyReleaseToBeIgnored = -1; // get rid of this value, because this in only implemented for simple scenarios
// and it shouldn't cause trouble
}
return keyEventWasUsed;
}
/*
* (non-Javadoc)
*
* @see java.awt.Component#setName(java.lang.String)
*/
@Override
public void setName(String name)
{
super.setName(name);
}
private int dataType;
public void installFormat(ComponentFormat componentFormat)
{
this.dataType = componentFormat.uiType;
if (isEditable())
{
FormattedComboBoxEditor ed = (FormattedComboBoxEditor)getEditor();
ed.setFormat(componentFormat);
}
if (!componentFormat.parsedFormat.isEmpty())
{
String displayFormat = componentFormat.parsedFormat.getDisplayFormat();
try
{
switch (Column.mapToDefaultType(dataType))
{
case IColumnTypes.NUMBER :
format = new RoundHalfUpDecimalFormat(displayFormat, application.getLocale());
break;
case IColumnTypes.INTEGER :
format = new RoundHalfUpDecimalFormat(displayFormat, application.getLocale());
break;
case IColumnTypes.DATETIME :
format = new StateFullSimpleDateFormat(displayFormat, Boolean.TRUE.equals(UIUtils.getUIProperty(this,
IApplication.DATE_FORMATTERS_LENIENT, Boolean.TRUE)));
// format = new SimpleDateFormat(formatString);
break;
default :
// TODO Jan/Johan what to do here? Should we create our own MaskFormatter? Where we can insert a mask??
break;
}
}
catch (Exception ex)
{
Debug.error(ex);
}
}
}
private int halign;
private boolean selectingItem;
public void setHorizontalAlignment(int a)
{
if (isEditable())
{
FormattedComboBoxEditor ed = (FormattedComboBoxEditor)getEditor();
ed.setHorizontalAlignment(a);
}
halign = a;
}
private int getHorizontalAlignment()
{
return halign;
}
public void itemStateChanged(ItemEvent e)
{
if (adjusting || getListModelWrapper().isValueListChanging()) return;
if (e.getStateChange() == ItemEvent.SELECTED)
{
if (editProvider != null) editProvider.itemStateChanged(e);
// Do not call the action command here, it will be called in notifyLastNewValueWasChange when on change was successful
}
else if (e.getStateChange() == ItemEvent.DESELECTED && getModel().getSelectedItem() == null)
{
if (editProvider != null) editProvider.itemStateChanged(new ItemEvent((ItemSelectable)e.getSource(), e.getID(), e.getItem(), ItemEvent.SELECTED));
// Do not call the action command here, it will be called in notifyLastNewValueWasChange when on change was successful
}
}
@Override
public void setBackground(Color bg)
{
if (isEditable())
{
FormattedComboBoxEditor ed = (FormattedComboBoxEditor)getEditor();
ed.getEditorComponent().setBackground(bg);
}
super.setBackground(bg);
}
@Override
public void setForeground(Color fg)
{
if (isEditable())
{
FormattedComboBoxEditor ed = (FormattedComboBoxEditor)getEditor();
ed.getEditorComponent().setForeground(fg);
}
super.setForeground(fg);
}
/*
* (non-Javadoc)
*
* @see com.servoy.j2db.dataui.IFieldComponent#setToolTipText(java.lang.String)
*/
@Override
public void setToolTipText(String tip)
{
if (tip != null && tip.indexOf("%%") != -1) //$NON-NLS-1$
{
tooltip = tip;
}
else if (!Utils.stringIsEmpty(tip))
{
if (!Utils.stringContainsIgnoreCase(tip, "<html")) //$NON-NLS-1$
{
super.setToolTipText(tip);
}
else if (HtmlUtils.hasUsefulHtmlContent(tip))
{
super.setToolTipText(tip);
}
}
else
{
super.setToolTipText(null);
}
}
/**
* Overwritten for handling statefull dates. So we can merge dates from the selected element in the list with the orignal date
*/
@Override
public void setSelectedIndex(int anIndex)
{
int size = dataModel.getSize();
if (anIndex == -1)
{
setSelectedItem(null);
}
else if (anIndex >= size)
{
throw new IllegalArgumentException("setSelectedIndex: " + anIndex + " out of bounds"); //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
Object value = dataModel.getElementAt(anIndex);
// this part should be moved to setSelectedItem() as well
if (value instanceof Date && format instanceof StateFullSimpleDateFormat)
{
// original date is set in setValueObject, do not use getValueObject() here because when an editable combo is made empty,
// the underlying value is set to null, but date merging should continue from last merged date (see NullDateFormatter)
StateFullSimpleDateFormat sfsd = (StateFullSimpleDateFormat)format;
String stringRep = sfsd.format(value);
try
{
sfsd.parse(stringRep);
value = sfsd.getMergedDate();
}
catch (ParseException e)
{
Debug.error(e);
}
}
setSelectedItem(value);
}
}
@Override
public void setSelectedItem(Object anObject)
{
Object oldSelection = selectedItemReminder;
if (oldSelection == null || !oldSelection.equals(anObject))
{
if (IValueList.SEPARATOR.equals(anObject))
{
// here we can't know the actual index to choose a nearby item, maybe there are multiple separators, so just ignore it (it's probably nicer anyway);
// reset the editor's value, cause it might have been set as well by an Enter key press
getEditor().setItem(oldSelection);
return;
}
if (anObject != null && !isEditable())
{
// For non editable combo boxes, an invalid selection
// will be rejected.
boolean found = false;
// Test if it is a date, so that we only compare the format string (what the user sees)
// as the equals value and not the complete date, that can have seperate input fields.
if (anObject instanceof Date && format instanceof StateFullSimpleDateFormat)
{
StateFullSimpleDateFormat sfsd = (StateFullSimpleDateFormat)format;
String selectedFormat = sfsd.format(anObject);
for (int i = 0; i < dataModel.getSize(); i++)
{
try
{
Object element = dataModel.getElementAt(i);
if (!(element instanceof Date)) continue;
String elementFormat = sfsd.format(element);
if (selectedFormat.equals(elementFormat))
{
found = true;
break;
}
}
catch (RuntimeException e)
{
Debug.error(e);
}
}
}
else
{
if (dataModel instanceof ComboModelListModelWrapper)
{
found = ((ComboModelListModelWrapper)dataModel).indexOf(anObject) != -1;
}
else for (int i = 0; i < dataModel.getSize(); i++)
{
if (anObject.equals(dataModel.getElementAt(i)))
{
found = true;
break;
}
}
}
if (!found)
{
return;
}
}
// Must toggle the state of this flag since this method
// call may result in ListDataEvents being fired.
selectingItem = true;
dataModel.setSelectedItem(anObject);
selectingItem = false;
if (selectedItemReminder != dataModel.getSelectedItem())
{
// in case a users implementation of ComboBoxModel
// doesn't fire a ListDataEvent when the selection
// changes.
selectedItemChanged();
}
}
fireActionEvent();
}
@Override
protected void fireActionEvent()
{
if (!selectingItem)
{
super.fireActionEvent();
}
}
public Object getValueObject()
{
if (isEditable())
{
// get the selected item from the editor (or else editable combo's in portals won't set the value)
return ((FormattedComboBoxEditor)getEditor()).getItem();
}
if (dataModel instanceof ComboModelListModelWrapper< ? >)
{
int index = ((ComboModelListModelWrapper< ? >)dataModel).indexOf(dataModel.getSelectedItem());
if (index != -1) return getListModelWrapper().getRealElementAt(index);
}
if (getSelectedIndex() < 0)
{
return getSelectedItem();//editted value
}
else
{
return getListModelWrapper().getRealElementAt(getSelectedIndex());
}
}
/**
* @see javax.swing.JComponent#getToolTipText()
*/
@Override
public String getToolTipText()
{
if (resolver != null && tooltip != null)
{
String oldValue = tooltip;
tooltip = null;
super.setToolTipText(Text.processTags(oldValue, resolver));
tooltip = oldValue;
}
return super.getToolTipText();
}
/**
* @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
*/
@Override
public String getToolTipText(MouseEvent event)
{
return getToolTipText();
}
boolean adjusting = false;
protected ITagResolver resolver;
public void setTagResolver(ITagResolver resolver)
{
this.resolver = resolver;
}
public void setValueObject(Object data)
{
try
{
if (format instanceof StateFullSimpleDateFormat && (data instanceof Date || data == null))
{
// set original date for date merging in StateFullSimpleDateFormat, see DataField.NullDateFormatter.stringToValue()
((StateFullSimpleDateFormat)format).setOriginal((Date)data);
}
adjusting = true;
if (editProvider != null) editProvider.setAdjusting(true);
if (needEntireState)
{
if (tooltip != null)
{
super.setToolTipText(""); //$NON-NLS-1$
}
}
else
{
if (tooltip != null)
{
super.setToolTipText(tooltip);
}
}
if (getSelectedItem() == null || !Utils.equalObjects(selectedItemReminder, data))
{
int index = getListModelWrapper().realValueIndexOf(data);
if (index == -1)
{
if (isEditable() && accesibleStateHolder.isEditable())
{
setSelectedItem(data);
}
else
{
if (data == null || "".equals(data) || getListModelWrapper().hasRealValues()) //$NON-NLS-1$
{
setSelectedItem(null);
// data does not resolve now, when underlying data changes real selected data may map.
if (getListModelWrapper().hasRealValues()) getListModelWrapper().setRealSelectedObject(data);
}
else
{
getModel().setSelectedItem(data);
}
}
}
else if (dataType == IColumnTypes.DATETIME)
{
setSelectedItem(data);
}
else
{
setSelectedIndex(index);
}
setValueValid(true, null);
}
}
finally
{
adjusting = false;
if (editProvider != null) editProvider.setAdjusting(false);
}
fireOnRender(false);
}
public void fireOnRender(boolean force)
{
if (scriptable != null)
{
if (force) scriptable.getRenderEventExecutor().setRenderStateChanged();
scriptable.getRenderEventExecutor().fireOnRender(hasFocus());
}
}
public boolean needEditListener()
{
return true;
}
private EditProvider editProvider = null;
public void addEditListener(IEditListener l)
{
if (editProvider == null)
{
editProvider = new EditProvider(this);
//addItemListener(editProvider);
editProvider.addEditListener(l);
if (isEditable())
{
formattedComboEditor.getEditorComponent().addFocusListener(editProvider);
}
else
{
addFocusListener(editProvider);
}
}
}
public boolean needEntireState()
{
return needEntireState;
}
private boolean needEntireState;
public void setNeedEntireState(boolean b)
{
needEntireState = b;
}
public String getDataProviderID()
{
return dataProviderID;
}
public void setDataProviderID(String id)
{
dataProviderID = id;
getListModelWrapper().setDataProviderID(id);
if (isEditable())//for debugging so component has name
{
Component comp = getEditor().getEditorComponent();
comp.setName(id);
if (comp instanceof IDisplayData)
{
((IDisplayData)comp).setDataProviderID(dataProviderID);
}
}
}
/*
* _____________________________________________________________ Methods for IDisplayRelatedData
*/
public void setRecord(IRecordInternal state, boolean stopEditing)
{
try
{
adjusting = true;
if (editProvider != null) editProvider.setAdjusting(true);
Object selected = getSelectedItem();
getListModelWrapper().fill(state);
if (isEditable() && !Utils.equalObjects(selected, getSelectedItem()))
{
setSelectedItem(selected);
}
}
finally
{
adjusting = false;
if (editProvider != null) editProvider.setAdjusting(false);
}
}
public String getSelectedRelationName()
{
if (relationName == null && getListModelWrapper() != null)
{
relationName = getListModelWrapper().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 };
}
}
public List<SortColumn> getDefaultSort()
{
return null;
}
public boolean stopUIEditing(boolean looseFocus)
{
if (isEditable())
{
FormattedComboBoxEditor cbe = (FormattedComboBoxEditor)getEditor();
DataField editorComponent = (DataField)cbe.getEditorComponent();
boolean isEditing = editorComponent.hasFocus();
// we really want to check here an invalid state from datafield
if (!editorComponent.isValueValid() && !Utils.equalObjects(editorComponent.getValue(), getValueObject())) editorComponent.setValueValid(true, null);
boolean stopAllowed = cbe.stopEditing(looseFocus);
if (stopAllowed && isEditing)
{
// make sure the value change events are triggered if needed;
// there was a problem in list view - editing value with the editor, then click on the
// combo on another row => the new value would not be applied, although stopUIEditing() is
// called before the selected row is changed. FocusLost - which is used by comboUI to trigger value change events
// is triggered after the selected row is changed in this scenario - and because of this the new value is not applied.
getModel().setSelectedItem(editorComponent.getValue());
}
return stopAllowed && isValueValid();
}
if (!isValueValid())
{
application.invokeLater(new Runnable()
{
public void run()
{
requestFocus();
}
});
return false;
}
return true;
}
public void notifyVisible(boolean b, List<Runnable> invokeLaterRunnables)
{
//ignore
}
// If component not shown or not added yet
// and request focus is called it should wait for the component
// to be created.
boolean wantFocus = false;
private List<ILabel> labels;
@Override
public void addNotify()
{
super.addNotify();
if (isEditable)
{
// in java 1.5, ending cell editing of a editable combo in tableview results in focus
// being given to the header part... this fixes it
Container nearestRoot = (isFocusCycleRoot()) ? this : getFocusCycleRootAncestor();
FocusTraversalPolicy policy = nearestRoot.getFocusTraversalPolicy();
if (policy.getClass().getName().indexOf("LegacyGlueFocusTraversalPolicy") >= 0) //$NON-NLS-1$
{
DataField editorComponent = (DataField)getEditor().getEditorComponent();
nearestRoot = (editorComponent.isFocusCycleRoot()) ? this : editorComponent.getFocusCycleRootAncestor();
policy = nearestRoot.getFocusTraversalPolicy();
if (policy.getClass().getName().indexOf("LegacyGlueFocusTraversalPolicy") >= 0) //$NON-NLS-1$
{
editorComponent.setNextFocusableComponent(getNextFocusableComponent());
}
}
}
if (wantFocus)
{
wantFocus = false;
requestFocus();
}
}
/*
* _____________________________________________________________ Methods for javascript
*/
public void requestFocusToComponent()
{
// if (!hasFocus()) Don't test on hasFocus (it can have focus,but other component already did requestFocus)
{
if (isDisplayable())
{
// Must do it in a runnable or else others after a script can get focus first again..
application.invokeLater(new Runnable()
{
public void run()
{
requestFocus();
}
});
}
else
{
wantFocus = true;
}
}
}
public IValueList getValueList()
{
return vl;
}
public void setValueList(IValueList vl)
{
this.vl = vl;
getListModelWrapper().register(vl);
setEditable(isEditable());
}
public ListDataListener getListener()
{
return null;
}
public void setComponentVisible(boolean b_visible)
{
if (viewable || !b_visible)
{
setVisible(b_visible);
}
}
@Override
public void setVisible(boolean flag)
{
super.setVisible(flag);
if (labels != null)
{
for (int i = 0; i < labels.size(); i++)
{
ILabel label = labels.get(i);
label.setComponentVisible(flag);
}
}
}
public void addLabelFor(ILabel label)
{
if (labels == null) labels = new ArrayList<ILabel>(3);
labels.add(label);
}
public List<ILabel> getLabelsFor()
{
return labels;
}
public void setComponentEnabled(boolean b)
{
accesibleStateHolder.setEnabled(b);
}
public void setAccessible(boolean b)
{
accesibleStateHolder.setAccessible(b);
}
private boolean viewable = true;
public void setViewable(boolean b)
{
if (!b) setComponentVisible(b);
this.viewable = b;
}
public boolean isViewable()
{
return viewable;
}
/*
* readonly/editable---------------------------------------------------
*/
public boolean isReadOnly()
{
return !isEnabled();
}
public void setReadOnly(boolean b)
{
accesibleStateHolder.setReadOnly(b);
}
/*
* titleText---------------------------------------------------
*/
private String titleText = null;
public void setTitleText(String title)
{
this.titleText = title;
}
public String getTitleText()
{
return Text.processTags(titleText, resolver);
}
public int getAbsoluteFormLocationY()
{
Container parent = getParent();
while ((parent != null) && !(parent instanceof IDataRenderer))
{
parent = parent.getParent();
}
if (parent != null)
{
return ((IDataRenderer)parent).getYOffset() + getLocation().y;
}
return getLocation().y;
}
private Point cachedLocation;
public Point getCachedLocation()
{
return cachedLocation;
}
public void setClientProperty(Object key, Object value)
{
if (IApplication.DATE_FORMATTERS_LENIENT.equals(key) || IApplication.DATE_FORMATTERS_ROLL_INSTEAD_OF_ADD.equals(key))
{
if (IApplication.DATE_FORMATTERS_LENIENT.equals(key) && format instanceof StateFullSimpleDateFormat)
{
((StateFullSimpleDateFormat)format).setLenient(Boolean.TRUE.equals(UIUtils.getUIProperty(this, IApplication.DATE_FORMATTERS_LENIENT,
Boolean.TRUE)));
}
if (formattedComboEditor != null)
{
formattedComboEditor.putClientProperty(key, value);
}
}
}
private Dimension cachedSize;
public Dimension getCachedSize()
{
return cachedSize;
}
public void setCachedLocation(Point location)
{
this.cachedLocation = location;
}
public void setCachedSize(Dimension size)
{
this.cachedSize = size;
}
@Override
public String toString()
{
return scriptable.toString();
}
public String getId()
{
return (String)getClientProperty("Id"); //$NON-NLS-1$
}
public static class FormattedComboBoxEditor implements ComboBoxEditor, FocusListener
{
protected DataField editor;//use datafield here because the processfocus is fixed and validation can be disabled
private final ComboBoxEditor defaultEditor;
public FormattedComboBoxEditor(IApplication app, ComboBoxEditor oldEditor, final ComboBoxModel model)
{
this.defaultEditor = oldEditor;
editor = new DataField(app, null /* no scriptable */)
{
// text field should show its readonly-state when not editable
@Override
public boolean isOpaque()
{
return isEditable() ? super.isOpaque() : true;
}
@Override
public void restorePreviousValidValue()
{
super.restorePreviousValidValue();
model.setSelectedItem(getValue());
}
@Override
public String toString()
{
// super uses scriptable
return "DataField for " + FormattedComboBoxEditor.this.toString(); //$NON-NLS-1$
}
};
// workaround for MAC OS X default L&F - combobox behavior declared in the editor component was lost when using our
// custom editor (normally that behavior should be added to the new editor when it is changed...); anyway, what follows makes
// up / down / select work in editable comboboxes on MAC...
if (oldEditor != null && Utils.isAppleMacOS())
{
Component oldEditorComponent = oldEditor.getEditorComponent();
if ((oldEditorComponent instanceof JComponent) && (oldEditorComponent.getClass().getName().toUpperCase().indexOf("CUIAQUA") >= 0)) //$NON-NLS-1$
{
InputMap oldInputMap = ((JComponent)oldEditorComponent).getInputMap();
InputMap inputmap = editor.getInputMap();
inputmap.put(KeyStroke.getKeyStroke("DOWN"), oldInputMap.get(KeyStroke.getKeyStroke("DOWN"))); //$NON-NLS-1$ //$NON-NLS-2$
inputmap.put(KeyStroke.getKeyStroke("KP_DOWN"), oldInputMap.get(KeyStroke.getKeyStroke("KP_DOWN"))); //$NON-NLS-1$ //$NON-NLS-2$
inputmap.put(KeyStroke.getKeyStroke("UP"), oldInputMap.get(KeyStroke.getKeyStroke("UP"))); //$NON-NLS-1$ //$NON-NLS-2$
inputmap.put(KeyStroke.getKeyStroke("KP_UP"), oldInputMap.get(KeyStroke.getKeyStroke("KP_UP"))); //$NON-NLS-1$ //$NON-NLS-2$
inputmap.put(KeyStroke.getKeyStroke("ENTER"), oldInputMap.get(KeyStroke.getKeyStroke("ENTER"))); //$NON-NLS-1$ //$NON-NLS-2$
}
}
// editor.setBorder(null);
}
public ComboBoxEditor getDefaultEditor()
{
return defaultEditor;
}
public void setMaxLength(int i)
{
editor.setMaxLength(i);
}
public void setMargin(Insets i)
{
editor.setMargin(i);
}
public void putClientProperty(Object key, Object value)
{
// editor has no scriptObject
editor.putClientProperty(key, value);
editor.setClientProperty(key, value);
}
public void setFormat(ComponentFormat componentFormat)
{
editor.installFormat(componentFormat);
}
public void setHorizontalAlignment(int a)
{
editor.setHorizontalAlignment(a);
}
public Component getEditorComponent()
{
return editor;
}
public Document getDocument()
{
return editor.getDocument();
}
public void setValidationEnabled(boolean b)//for find mode
{
editor.setValidationEnabled(b);
}
public boolean stopEditing(boolean looseFocus)
{
return editor.stopUIEditing(looseFocus);
}
/**
* Sets the item that should be edited.
*
* @param anObject the displayed value of the editor
*/
public void setItem(Object anObject)
{
editor.setValueObject(anObject);
}
public Object getItem()
{
try
{
editor.commitEdit();
}
catch (ParseException e)
{
Debug.error(e);
}
Object newValue = editor.getValue();
/*
* if (oldValue != null && !(oldValue instanceof String)) { // The original value is not a string. Should return the value in it's // original type. if
* (newValue.equals(oldValue.toString())) { return oldValue; } else { // Must take the value from the editor and get the value and cast it to the new type.
* Class cls = oldValue.getClass(); try { Method method = cls.getMethod("valueOf", new Class[]{String.class}); newValue = method.invoke(oldValue, new Object[] {
* editor.getText()}); } catch (Exception ex) { // Fail silently and return the newValue (a String object) } } }
*/
return newValue;
}
public void selectAll()
{
editor.selectAll();
editor.requestFocus();
}
// This used to do something but now it doesn't. It couldn't be
// removed because it would be an API change to do so.
public void focusGained(FocusEvent e)
{
}
// This used to do something but now it doesn't. It couldn't be
// removed because it would be an API change to do so.
public void focusLost(FocusEvent e)
{
}
public void addActionListener(ActionListener l)
{
editor.addActionListener(l);
}
public void removeActionListener(ActionListener l)
{
editor.removeActionListener(l);
}
}
/**
* Calculates actual JComboBox-like component editable and enabled values based on Servoy accessible, read-only, editable, and enabled values. It then sets them back (when
* changed) to the component (through AccesibleStateApplier).
*/
public static class AccessibleStateHolder
{
private boolean accessible = true;
private boolean editable = false;
private boolean readOnly = false;
private boolean enabled = true;
private final AccessibleStateApplier applier;
public AccessibleStateHolder(AccessibleStateApplier applier)
{
if (applier == null) throw new NullPointerException();
this.applier = applier;
}
public void applyState()
{
boolean jComboEnabled = accessible && enabled && !readOnly;
applier.setEnabled(editable ? true : jComboEnabled);
applier.setEditable(editable);
applier.setLabelsEnabled(accessible && enabled);
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
applyState();
}
private boolean editState;
private boolean blockEditStateChange;
public void blockEditStateChange(boolean blockEditStateChanges)
{
this.blockEditStateChange = blockEditStateChanges;
}
public void setEditable(boolean editable)
{
this.editable = editable;
if (!blockEditStateChange) editState = editable;
applyState();
}
public void setReadOnly(boolean readOnly)
{
this.readOnly = readOnly;
if (readOnly && !isEditable())
{
applyState();
return;
}
if (readOnly)
{
setEditable(false);
if (!blockEditStateChange) editState = true;
}
else
{
setEditable(editState);
}
}
public void setAccessible(boolean accessible)
{
this.accessible = accessible;
applyState();
}
public boolean isEditable()
{
return editable;
}
public boolean isAccessible()
{
return accessible;
}
public boolean isEnabled()
{
return enabled;
}
}
/**
* Classes that implement this interface are supposed to apply the editable and enabled values to a JComboBox-like component, with the meaning that component gives to them.
*/
public static interface AccessibleStateApplier
{
void setEnabled(boolean enabled);
void setEditable(boolean editable);
void setLabelsEnabled(boolean labelsEnabled);
}
}