/******************************************************************************* * 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); } } } }