/*******************************************************************************
* Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Exadel, Inc. and Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.model.ui.attribute.editor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.SimpleContentProposalProvider;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
import org.jboss.tools.common.model.ui.IAttributeErrorProvider;
import org.jboss.tools.common.model.ui.IValueChangeListener;
import org.jboss.tools.common.model.ui.IValueProvider;
import org.jboss.tools.common.model.ui.actions.IActionProvider;
import org.jboss.tools.common.model.ui.attribute.AttributeContentProposalProviderFactory;
import org.jboss.tools.common.model.ui.attribute.IListContentProvider;
import org.jboss.tools.common.model.ui.attribute.adapter.DefaultValueAdapter;
import org.jboss.tools.common.model.ui.widgets.BorderedControl;
import org.jboss.tools.common.model.ui.widgets.IWidgetSettings;
import org.jboss.tools.common.model.ui.widgets.WhiteSettings;
import org.jboss.tools.common.model.ui.widgets.border.Border;
public class ComboBoxFieldEditor extends ExtendedFieldEditor implements IFieldEditor, IPropertyFieldEditor, IPropertyChangeListener, PropertyChangeListener {
IPropertyEditor propertyEditor;
// IValueEditor
IValueChangeListener valueChangeListener;
IValueProvider valueProvider;
// IListEditor
ILabelProvider labelProvider;
IListContentProvider listContentProvider;
private boolean dropDown = false;
private String stringValue = ""; //$NON-NLS-1$
private boolean isValid;
public static int UNLIMITED = -1;
private int textLimit = UNLIMITED;
private int widthInChars = UNLIMITED;
private Combo comboField;
private String[] tags = new String[0];
// private int style = SWT.NONE;
private static final int defaultStyle = SWT.BORDER;
boolean modifyLock = false;
private KeyAdapter keyAdapter;
private FocusAdapter focusAdapter;
private ModifyListener modifyListener;
private DisposeListener disposeListener;
private SimpleContentProposalProvider cpp;
public ComboBoxFieldEditor() {
// setStyle(SWT.DROP_DOWN | SWT.BORDER);
}
public ComboBoxFieldEditor(IWidgetSettings settings) {
super(settings);
// setStyle(SWT.DROP_DOWN | SWT.BORDER);
}
public ComboBoxFieldEditor(String name, String labelText, List<String> tags, Composite parent) {
init(name, labelText);
if (tags != null) {
this.tags = tags.toArray(new String[tags.size()]);
}
createControl(parent);
}
protected Combo getComboField() {
return comboField;
}
protected Combo getComboControl() {
return comboField;
}
protected void adjustForNumColumns(int numColumns) {
GridData gd = (GridData)comboField.getLayoutData();
gd.horizontalSpan = numColumns - 1;
// We only grab excess space if we have to
// If another field editor has more columns then
// we assume it is setting the width.
gd.grabExcessHorizontalSpace = gd.horizontalSpan == 1;
}
protected void doFillIntoGrid(Composite parent, int numColumns) {
getLabelComposite(parent);
comboField = getComboControl(parent);
GridData gd = new GridData();
gd.horizontalSpan = numColumns - 1;
if (widthInChars != UNLIMITED) {
GC gc = new GC(comboField);
try {
Point extent = gc.textExtent("X");//$NON-NLS-1$
gd.widthHint = widthInChars * extent.x;
} finally {
gc.dispose();
}
} else {
gd.horizontalAlignment = GridData.FILL;
gd.grabExcessHorizontalSpace = true;
}
comboField.setLayoutData(gd);
}
protected void doLoad() {
if (comboField != null) {
String value = getPreferenceStore().getString(getPreferenceName());
Object listValue = mapFromTo(listContentProvider.getElements(this),getTags(),value);
comboField.setText(listValue.toString());
stringValue = value;
}
}
protected void doLoadDefault() {
if (comboField != null) {
String value = getPreferenceStore().getDefaultString(getPreferenceName());
comboField.setText(value);
}
valueChanged();
}
protected void doStore() {
Object selectedValue = mapFromTo(getTags(),listContentProvider.getElements(this),comboField.getText());
getPreferenceStore().setValue(getPreferenceName(), selectedValue.toString());
}
public int getNumberOfControls() {
return 2;
}
protected Combo getComboControl(Composite parent) {
if (comboField == null) {
int style = getSettings().getStyle("Combo.Style"); //$NON-NLS-1$
Color bg = getSettings().getColor("Combo.Background"); //$NON-NLS-1$
if (bg==null) bg = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE);// bug with gray bg
Color fg = getSettings().getColor("Combo.Foreground"); //$NON-NLS-1$
Font font = getSettings().getFont("Combo.Font"); //$NON-NLS-1$
Border border = null; //$NON-NLS-1$
//Platform check has been added for not drawning black border for mac os
//added by Maksim Areshkau as fix for JBIDE-7382
if(!Platform.OS_MACOSX.equals(Platform.getOS())){
border = getSettings().getBorder("Combo.Border");
}
if (style == SWT.DEFAULT) style = defaultStyle;
if (isDropDown()) style = style | SWT.READ_ONLY;
if (border!=null) {
BorderedControl borderedControl = new BorderedControl(parent, SWT.NONE, border);
comboField = new Combo(borderedControl, style);
comboField.setBackground(bg);
} else {
comboField = new Combo(parent, style);
}
comboField.setFont(font);
comboField.setForeground(fg);
String[] tags = getTags();
comboField.setItems(tags);
if(tags != null && tags.length > 5) {
int k = tags.length > 10 ? 10 : tags.length;
comboField.setVisibleItemCount(k);
}
stringValue = valueProvider.getStringValue(true).toString();
comboField.setFont(parent.getFont());
keyAdapter = new KeyAdapter() {
public void keyReleased(KeyEvent e) {
valueChanged();
}
};
focusAdapter = new FocusAdapter() {
boolean isSettingFocus = false;
public void focusGained(FocusEvent e) {
if(isSettingFocus) return;
refreshValidState();
isSettingFocus = true;
try {
if(comboField != null && !comboField.isDisposed()) {
comboField.setFocus();
}
} finally {
isSettingFocus = false;
}
}
public void focusLost(FocusEvent e) {
clearErrorMessage();
}
};
modifyListener = new ModifyListener() {
public void modifyText(ModifyEvent e) {
if(modifyLock) return;
modifyLock = true;
try {
valueChanged();
} finally {
modifyLock = false;
}
}
};
disposeListener = new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
comboField = null;
}
};
comboField.addKeyListener(keyAdapter);
comboField.addFocusListener(focusAdapter);
setStringValue(stringValue);
comboField.addModifyListener(modifyListener);
comboField.addDisposeListener(disposeListener);
if(textLimit > 0){//Only set limits above 0 - see SWT spec
comboField.setTextLimit(textLimit);
}
String[] ts = getTags();
cpp = new SimpleContentProposalProvider(prepareProposals(ts, elements));
cpp.setFiltering(true);
KeyStroke ks = AttributeContentProposalProviderFactory.getCtrlSpaceKeyStroke();
ContentProposalAdapter adapter = new ContentProposalAdapter(
comboField,
new ComboContentAdapter(),
cpp,
ks,
null
);
adapter.setPropagateKeys(true);
adapter
.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
} else {
checkParent(comboField, parent);
}
return comboField;
}
private String[] prepareProposals(String[] tags, Object[] elements) {
Set<String> set = new TreeSet<String>();
for (int i = 0; i < tags.length; i++) set.add(tags[i]);
if(elements != null) for (int i = 0; i < elements.length; i++) {
set.add(elements[i].toString());
}
return set.toArray(new String[0]);
}
protected void valueChanged() {
/*added by Max Areshkau
*hack which was applied for fix JBIDE-1694
*/
if(comboField!=null&&Platform.OS_LINUX.equals(Platform.getOS())) {
/*
* Fix for JBIDE-1948
*/
Point point = comboField.getSelection();
comboField.setFocus();
comboField.setSelection(point);
}
setPresentsDefaultValue(false);
boolean oldState = isValid;
refreshValidState();
if (isValid != oldState)
fireStateChanged(IS_VALID, oldState, isValid);
String oldValue = stringValue;
int i = comboField.getSelectionIndex();
String newValue = (i < 0) ? comboField.getText() : elements[i].toString();
stringValue = mapFromTo(getTags(),listContentProvider.getElements(this),newValue).toString();
this.valueProvider.removeValueChangeListener(this);
PropertyChangeEvent event = new PropertyChangeEvent(this, IPropertyEditor.VALUE, oldValue, stringValue);
valueChangeListener.valueChange(event);
revalidateValue();
if(this.valueProvider == null) {
//disposed already
return;
}
this.valueProvider.addValueChangeListener(this);
}
/**
* Check that committed value is successfully assigned.
* It may be rejected if destination is final object rather than wizard data.
* In this case current value should be set to combo field.
*/
private void revalidateValue() {
if(valueProvider == null) return;
Object v = valueProvider.getValue();
if(v == null || !(propertyEditor.getInput() instanceof DefaultValueAdapter)) return;
String s = v.toString();
String sv = stringValue;
if(((DefaultValueAdapter)propertyEditor.getInput()).getAttribute().isTrimmable()) sv = sv.trim();
if(!s.equals(sv)) {
stringValue = s;
if(!s.equals(comboField.getText())) {
Point p = comboField.getSelection();
boolean end = p.x == comboField.getText().length();
comboField.setText(s);
if(end) comboField.setSelection(new Point(comboField.getText().length(), comboField.getText().length()));
// reset selection in the best way
}
}
}
Object[] elements;
protected String[] getTags() {
elements = listContentProvider.getElements(this);
tags = new String[elements.length];
for(int i=0;i<elements.length;++i){
tags[i] = labelProvider.getText(elements[i]);
}
return tags;
}
protected void setStringValue(String newValue) {
if(modifyLock) return;
String oldValue = this.stringValue;
stringValue = newValue;
if (comboField != null && !isSameValue(newValue)) {
modifyLock = true;
comboField.setText(mapFromTo(elements,tags,newValue).toString());
modifyLock = false;
}
PropertyChangeEvent event = new PropertyChangeEvent(this, IPropertyEditor.VALUE, oldValue, newValue);
valueChangeListener.valueChange(event);
}
boolean isSameValue(String newValue) {
if(comboField == null || comboField.isDisposed() || newValue == null) return false;
String oldTextValue = comboField.getText();
if(propertyEditor != null && propertyEditor.getInput() instanceof DefaultValueAdapter) {
DefaultValueAdapter a = (DefaultValueAdapter)propertyEditor.getInput();
if(a.getAttribute().isTrimmable()) {
return oldTextValue != null && oldTextValue.trim().equals(newValue.trim());
}
}
return oldTextValue != null && oldTextValue.equals(newValue);
}
static private Object mapFromTo(Object[] from, Object[] to,Object value) {
if(from==null || from.length==0 || to==null || to.length==0) return value;
int index = Arrays.asList(from).indexOf(value);
return index==-1?value:to[index];
}
// IPropertyFieldEditor
public void setPropertyEditor(IPropertyEditor propertyEditor) {
this.propertyEditor = propertyEditor;
if (propertyEditor!=null) {
valueProvider = (IValueProvider)propertyEditor.getAdapter(IValueProvider.class);
valueChangeListener = (IValueChangeListener)propertyEditor.getAdapter(IValueChangeListener.class);
labelProvider = (ILabelProvider)propertyEditor.getAdapter(ILabelProvider.class);
listContentProvider = (IListContentProvider)propertyEditor.getAdapter(IListContentProvider.class);
setErrorProvider((IAttributeErrorProvider)propertyEditor.getAdapter(IAttributeErrorProvider.class));
IActionProvider actionProvider = (IActionProvider)propertyEditor.getAdapter(IActionProvider.class);
if (actionProvider != null) {
if (getSettings() instanceof WhiteSettings) {
setLabelAction(actionProvider.getAction(StringButtonFieldEditorEx.LABEL_SELECTED));
} else {
// none
}
}
}
setPropertyChangeListener(this);
valueProvider.addValueChangeListener(this);
}
public void dispose() {
super.dispose();
if (comboField!=null) {
comboField.removeKeyListener(keyAdapter);
comboField.removeFocusListener(focusAdapter);
comboField.removeModifyListener(modifyListener);
comboField.removeDisposeListener(disposeListener);
keyAdapter = null;
focusAdapter = null;
modifyListener = null;
disposeListener = null;
if (!comboField.isDisposed()) comboField.dispose();
}
setPropertyChangeListener(null);
if (valueProvider!=null) valueProvider.removeValueChangeListener(this);
propertyEditor = null;
valueChangeListener = null;
valueProvider = null;
labelProvider = null;
listContentProvider = null;
}
// IPropertyChangeListener
public void propertyChange(org.eclipse.jface.util.PropertyChangeEvent event) {
if(ExtendedFieldEditor.VALUE.equals(event.getProperty())) {
setPropertyChangeListener(null);
PropertyChangeEvent e = new PropertyChangeEvent(this, IPropertyEditor.VALUE, mapFromTo(tags,elements,event.getOldValue()), mapFromTo(tags,elements,event.getNewValue()));
valueChangeListener.valueChange(e);
setPropertyChangeListener(this);
}
}
// IFieldEditor
public Control[] getControls(Composite parent) {
return new Control[] {getLabelComposite(parent), getComboControl(parent)};
}
// PropertyChangeListener
public void propertyChange(PropertyChangeEvent evt) {
super.propertyChange(evt);
if (IPropertyEditor.VALUE.equals(evt.getPropertyName())) {
Object v = evt.getNewValue();
valueProvider.removeValueChangeListener(this);
this.setStringValue((v == null) ? "" : v.toString()); //$NON-NLS-1$
valueProvider.addValueChangeListener(this);
}
if (IPropertyEditor.LIST_CONTENT.equals(evt.getPropertyName())) {
String v = comboField.getText();
valueProvider.removeValueChangeListener(this);
String[] tags = getTags();
cpp.setProposals(prepareProposals(tags, elements));
comboField.setItems(tags);
comboField.setText(v);
int i = comboField.getSelectionIndex();
valueProvider.addValueChangeListener(this);
if((propertyEditor.getInput() instanceof DefaultValueAdapter) && !((DefaultValueAdapter)propertyEditor.getInput()).getAttribute().getConstraint().accepts("anyRandomValue")) {
if(i < 0 && tags != null && tags.length > 0) {
comboField.setText(tags[0]);
} else if(i < 0) {
comboField.setText(""); //$NON-NLS-1$
}
}
}
}
public void setEnabled(boolean enabled){
super.setEnabled(enabled); // label
if (this.getComboControl()!=null) {
this.getComboControl().setEnabled(enabled);
Color bg;
if (enabled) {
bg = getSettings().getColor("Combo.Background"); //$NON-NLS-1$
if (bg==null) bg = Display.getDefault().getSystemColor(SWT.COLOR_WHITE);
} else {
bg = getSettings().getColor("Combo.Background.Disabled"); //$NON-NLS-1$
if (bg==null) bg = Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
}
/// this.getComboControl().setBackground(bg);
updateErrorState();
}
}
public void cut() {
if(comboField != null && !comboField.isDisposed() && comboField.isFocusControl()) {
Text text = getInnerText();
if(text != null) text.cut();
valueChanged();
}
}
public void copy() {
if(comboField != null && !comboField.isDisposed() && comboField.isFocusControl()) {
Text text = getInnerText();
if(text != null) text.copy();
}
}
public void paste() {
if(comboField != null && !comboField.isDisposed() && comboField.isFocusControl()) {
Text text = getInnerText();
if(text != null) text.paste();
valueChanged();
}
}
public void delete() {
}
private Text getInnerText() {
try {
Field f = comboField.getClass().getDeclaredField("text"); //$NON-NLS-1$
f.setAccessible(true);
Text text = (Text)f.get(comboField);
return (text != null && !text.isDisposed()) ? text : null;
} catch (NoSuchFieldException e) {
return null;
} catch (IllegalAccessException e1) {
return null;
}
}
/**
* @return
*/
public boolean isDropDown() {
return dropDown;
}
/**
* @param b
*/
public void setDropDown(boolean b) {
dropDown = b;
}
public void setFocus() {
if (comboField != null) {
comboField.getParent().setFocus();
comboField.setSelection(new Point(0, comboField.getText().length()));
comboField.setFocus();
}
}
public void setFocusAndKeepSelection() {
if (comboField != null) {
Point p = comboField.getSelection();
comboField.getParent().setFocus();
comboField.setFocus();
if(p != null) {
comboField.setSelection(p);
}
}
}
}