package org.marketcetera.photon.commons.ui.workbench; import java.util.EventObject; import net.miginfocom.swt.MigLayout; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; 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.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.marketcetera.util.misc.ClassVersion; /* $License$ */ /** * Text box that emulates SWT.SEARCH | SWT.CANCEL on platforms that don't * support it. This was based on code from the enhanced Eclipse 3.5 * org.eclipse.ui.dialogs.FilteredTree. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: FilterBox.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: FilterBox.java 16154 2012-07-14 16:34:05Z colin $") public class FilterBox extends Composite { /** * Image descriptor for enabled clear button. */ private static final String CLEAR_ICON = "org.marketcetera.photon.internal.commons.ui.CLEAR_ICON"; //$NON-NLS-1$ /** * Image descriptor for disabled clear button. */ private static final String DCLEAR_ICON = "org.marketcetera.photon.internal.commons.ui.DCLEAR_ICON"; //$NON-NLS-1$ /** * Indicates if the platform natively supports a cancelable search widget */ protected static Boolean useNativeSearchField; private static void testNativeSearchField(Composite composite) { if (useNativeSearchField == null) { useNativeSearchField = false; Text testText = null; try { testText = new Text(composite, SWT.SEARCH | SWT.CANCEL); if ((testText.getStyle() & SWT.CANCEL) != 0) { useNativeSearchField = Boolean.TRUE; } else { ImageDescriptor descriptor = AbstractUIPlugin .imageDescriptorFromPlugin( "org.marketcetera.photon.commons.ui", //$NON-NLS-1$ "$nl$/icons/full/etool16/clear_co.gif"); //$NON-NLS-1$ if (descriptor != null) { JFaceResources.getImageRegistry().put(CLEAR_ICON, descriptor); } descriptor = AbstractUIPlugin.imageDescriptorFromPlugin( "org.marketcetera.photon.commons.ui", //$NON-NLS-1$ "$nl$/icons/full/dtool16/clear_co.gif"); //$NON-NLS-1$ if (descriptor != null) { JFaceResources.getImageRegistry().put(DCLEAR_ICON, descriptor); } } } finally { if (testText != null) { testText.dispose(); } } } } /** * The Composite on which the filter controls are created. This is used to * set the background color of the filter controls to match the surrounding * controls. */ protected Composite filterComposite; /** * The filter text widget to be used by this tree. This value may be * <code>null</code> if there is no filter widget, or if the controls have * not yet been created. */ protected Text filterText; /** * The control representing the clear button for the filter text entry. This * value may be <code>null</code> if no such button exists, or if the * controls have not yet been created. * <p> * <strong>Note:</strong> This is only used if the platform does not * natively support cancel text boxes. * </p> */ protected Control clearButtonControl; /** * The text to initially show in the filter text control. */ protected String initialText = ""; //$NON-NLS-1$ /** * Constructor. * * @param parent * parent composite in which to create the control */ public FilterBox(Composite parent) { super(parent, SWT.NONE); testNativeSearchField(this); init(parent); } /** * Create the control. */ protected void init(Composite parent) { setFont(parent.getFont()); setLayout(new MigLayout("ins 0, fill")); //$NON-NLS-1$ createFilterControls(this).setLayoutData("dock center"); //$NON-NLS-1$ } protected Composite createFilterControls(Composite parent) { Composite composite; if (useNativeSearchField) { composite = new Composite(parent, SWT.NONE); } else { composite = new Composite(parent, SWT.BORDER); composite.setBackground(getDisplay().getSystemColor( SWT.COLOR_LIST_BACKGROUND)); } composite.setLayout(new MigLayout("ins 0, fill")); //$NON-NLS-1$ filterText = createFilterText(composite); filterText.setLayoutData("w 150"); //$NON-NLS-1$ // only create the button if the text widget doesn't support one // natively if ((filterText.getStyle() & SWT.CANCEL) == 0) { clearButtonControl = createClearControl(composite); // initially there is no text to clear clearButtonControl.setVisible(false); } return composite; } protected Text createFilterText(Composite parent) { final Text text = useNativeSearchField ? new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.CANCEL) : new Text(parent, SWT.SINGLE); text.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { /* * Running in an asyncExec because the selectAll() does not * appear to work when using mouse to give focus to text. */ Display display = filterText.getDisplay(); display.asyncExec(new Runnable() { public void run() { if (!filterText.isDisposed()) { if (getInitialText().equals( filterText.getText().trim())) { filterText.selectAll(); } } } }); } }); text.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { textChanged(); } }); // if we're using a field with built in cancel we need to listen for // default selection changes (which tell us the cancel button has been // pressed) if ((text.getStyle() & SWT.CANCEL) != 0) { text.addSelectionListener(new SelectionAdapter() { @Override public void widgetDefaultSelected(SelectionEvent e) { if (e.detail == SWT.CANCEL) clearText(); } }); } return text; } protected Control createClearControl(Composite composite) { final Image inactiveImage = JFaceResources.getImageRegistry() .getDescriptor(DCLEAR_ICON).createImage(); final Image activeImage = JFaceResources.getImageRegistry() .getDescriptor(CLEAR_ICON).createImage(); final Label clearButton = new Label(composite, SWT.NONE); clearButton.setImage(inactiveImage); clearButton.setBackground(composite.getDisplay().getSystemColor( SWT.COLOR_LIST_BACKGROUND)); clearButton.setToolTipText(Messages.FILTER_BOX_CLEAR_BUTTON_TOOLTIP.getText()); clearButton.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { clearText(); } }); clearButton.addMouseTrackListener(new MouseTrackAdapter() { @Override public void mouseEnter(MouseEvent e) { clearButton.setImage(activeImage); } @Override public void mouseExit(MouseEvent e) { clearButton.setImage(inactiveImage); } }); clearButton.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { inactiveImage.dispose(); activeImage.dispose(); } }); return clearButton; } /** * Update the receiver after the text has changed. */ protected void textChanged() { String text = getFilterString(); if (text != null && text.length() > 0 && !text.equals(initialText)) { clearButtonControl.setVisible(true); fireFilterChange(new FilterChangeEvent(text)); } else { clearButtonControl.setVisible(false); fireFilterChange(new FilterChangeEvent("")); //$NON-NLS-1$ } } public String getFilterText() { String text = getFilterString(); if (text != null && text.length() > 0 && !text.equals(initialText)) { return text; } else { return ""; //$NON-NLS-1$ } } /** * Set the text that will be shown until the first focus. A default value is * provided, so this method only need be called if overriding the default * initial text is desired. * * @param text * initial text to appear in text field */ public void setInitialText(String text) { initialText = text; setFilterText(initialText); textChanged(); } /** * Clears the text in the filter text widget. */ protected void clearText() { setFilterText(""); //$NON-NLS-1$ textChanged(); } /** * Set the text in the filter control. * * @param string * the text to set */ protected void setFilterText(String string) { if (filterText != null) { filterText.setText(string); selectAll(); } } /** * Select all text in the filter text field. */ protected void selectAll() { if (filterText != null) { filterText.selectAll(); } } /** * Get the filter text for the receiver, if it was created. Otherwise return * <code>null</code>. * * @return the filter Text, or null if it was not created */ public Text getFilterControl() { return filterText; } /** * Convenience method to return the text of the filter control. If the text * widget is not created, then null is returned. * * @return String in the text, or null if the text does not exist */ protected String getFilterString() { return filterText != null ? filterText.getText() : null; } /** * Get the initial text for the receiver. * * @return String */ protected String getInitialText() { return initialText; } /** * Set the background for the widgets that support the filter text area. * * @param background * background <code>Color</code> to set */ public void setBackground(Color background) { super.setBackground(background); if (filterComposite != null && useNativeSearchField) { filterComposite.setBackground(background); } } // Boiler plate listener code /** * Keeps track of listeners. */ private final ListenerList mFilterChangeListeners = new ListenerList( ListenerList.IDENTITY); /** * Fire a change event to all listeners. * * @param event * the change event */ private void fireFilterChange(FilterChangeEvent event) { Object[] listeners = mFilterChangeListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { ((FilterChangeListener) listeners[i]).filterChanged(event); } } /** * Removes a listener. This has no effect if an identical listener already * exists. * * @param listener * the listener to add */ public void addListener(FilterChangeListener listener) { mFilterChangeListeners.add(listener); } /** * Removes a listener. This has no effect if the listener does not exist. * * @param listener * the listener to remove */ public void removeListener(FilterChangeListener listener) { mFilterChangeListeners.remove(listener); } /** * Interface to notify listeners of changes to the filter. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: FilterBox.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: FilterBox.java 16154 2012-07-14 16:34:05Z colin $") public interface FilterChangeListener { /** * Callback for change notification. * * @param event * event describing the change */ void filterChanged(FilterChangeEvent event); } /** * Event object for {@link FilterChangeListener}. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: FilterBox.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: FilterBox.java 16154 2012-07-14 16:34:05Z colin $") public class FilterChangeEvent extends EventObject { private static final long serialVersionUID = 1L; private String filterText; /** * Constructor. * * @param filterText */ private FilterChangeEvent(String filterText) { super(FilterBox.this); this.filterText = filterText; } /** * Returns the filter text that was a result of this event. * * @return the filter text as a result of this event */ public String getFilterText() { return filterText; } } }