blob: bb47f833fb19f21c8e4874f0fcd41ff81c30b780 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2011 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are 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:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.commons.workbench.search;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.mylyn.internal.commons.workbench.CommonsWorkbenchPlugin;
import org.eclipse.mylyn.internal.commons.workbench.Messages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleControlAdapter;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleEvent;
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.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.plugin.AbstractUIPlugin;
/**
* @author Shawn Minto
* @since 3.7
*/
public class TextSearchControl extends Composite {
private static final String FIND_TEXT_MEMENTO_ELEMENT = "text"; //$NON-NLS-1$
private static final String FIND_MEMENTO_ELEMENT = "historyItem"; //$NON-NLS-1$
private static final String FIND_MEMENTO_TYPE = "filteredTreeFindHistory"; //$NON-NLS-1$
private static final int MAX_HISTORY_ITEMS = 50;
/**
* Image descriptor for enabled clear button.
*/
private static final String CLEAR_ICON = "org.eclipse.ui.internal.dialogs.CLEAR_ICON"; //$NON-NLS-1$
/**
* Image descriptor for enabled clear button.
*/
private static final String FIND_ICON = "org.eclipse.ui.internal.dialogs.FIND_ICON"; //$NON-NLS-1$
/* SWT STYLE BIT AVAILABLE ON 3.5 AND HIGHER */
public static final int ICON_SEARCH = 1 << 9;
/* SWT STYLE BIT AVAILABLE ON 3.5 AND HIGHER */
public static final int ICON_CANCEL = 1 << 8;
/**
* Get image descriptors for the clear button.
*/
static {
ImageDescriptor descriptor = AbstractUIPlugin.imageDescriptorFromPlugin(PlatformUI.PLUGIN_ID,
"$nl$/icons/full/etool16/clear_co.gif"); //$NON-NLS-1$
if (descriptor != null) {
JFaceResources.getImageRegistry().put(CLEAR_ICON, descriptor);
}
descriptor = AbstractUIPlugin.imageDescriptorFromPlugin(CommonsWorkbenchPlugin.ID_PLUGIN,
"$nl$/icons/etool16/find.png"); //$NON-NLS-1$
if (descriptor != null) {
JFaceResources.getImageRegistry().put(FIND_ICON, descriptor);
}
}
private final Text textControl;
private Control clearControl;
private Control findControl;
private final boolean automaticFind;
private final Set<SelectionListener> selectionListeners = new HashSet<SelectionListener>();
private Collection<String> searchHistory = new LinkedHashSet<String>();
private boolean hasHistorySupport;
private static Boolean useNativeSearchField;
@SuppressWarnings("restriction")
public TextSearchControl(Composite parent, boolean automaticFind, SearchHistoryPopupDialog historyDialog) {
super(parent, getCompositeStyle(automaticFind, parent));
this.automaticFind = automaticFind;
int textStyle = SWT.SINGLE;
int numColumns = 1;
if (useNativeSearchField(automaticFind, parent)) {
if (automaticFind) {
textStyle |= SWT.SEARCH | ICON_CANCEL;
} else {
textStyle |= SWT.SEARCH | ICON_SEARCH | ICON_CANCEL;
}
} else {
super.setBackground(getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
super.setFont(parent.getFont());
numColumns = 2;
if (!automaticFind) {
numColumns += 1;
}
}
GridLayoutFactory.swtDefaults()
.margins(0, 0)
.extendedMargins(0, 0, 0, 0)
.spacing(0, 1)
.numColumns(numColumns)
.applyTo(this);
textControl = new Text(this, textStyle);
GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
textControl.setLayoutData(gridData);
if (useNativeSearchField == null || !useNativeSearchField) {
findControl = createLabelButtonControl(this, textControl,
JFaceResources.getImageRegistry().getDescriptor(FIND_ICON),
Messages.TextControl_AccessibleListenerFindButton, Messages.TextControl_FindToolTip, ICON_SEARCH);
clearControl = createLabelButtonControl(this, textControl,
JFaceResources.getImageRegistry().getDescriptor(CLEAR_ICON),
WorkbenchMessages.FilteredTree_ClearToolTip,//FilteredTree_AccessibleListenerClearButton,
WorkbenchMessages.FilteredTree_ClearToolTip, ICON_CANCEL);
addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
updateButtonVisibilityAndEnablement();
}
});
updateButtonVisibilityAndEnablement();
}
if (historyDialog != null) {
historyDialog.attach(this);
hasHistorySupport = true;
}
registerListeners();
}
public boolean hasHistorySupport() {
return hasHistorySupport;
}
private void registerListeners() {
addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
if (textControl != null && !textControl.isDisposed()) {
addToSearchHistory(textControl.getText());
}
}
});
textControl.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
if (textControl != null && !textControl.isDisposed()) {
addToSearchHistory(textControl.getText());
}
}
});
}
private static int getCompositeStyle(boolean automaticFind, Composite parent) {
if (useNativeSearchField(automaticFind, parent)) {
return SWT.NONE;
}
return SWT.BORDER;
}
private static boolean useNativeSearchField(boolean automaticFind, Composite parent) {
if (parent != null) {
if (useNativeSearchField == null) {
useNativeSearchField = Boolean.FALSE;
Text testText = null;
try {
int style = SWT.SEARCH | ICON_CANCEL;
if (automaticFind) {
style |= ICON_SEARCH;
}
testText = new Text(parent, style);
useNativeSearchField = Boolean.valueOf((testText.getStyle() & ICON_CANCEL) != 0
&& (!automaticFind || (testText.getStyle() & ICON_SEARCH) != 0));
} finally {
if (testText != null) {
testText.dispose();
}
}
}
} else {
useNativeSearchField = Boolean.FALSE;
}
return useNativeSearchField.booleanValue();
}
private Control createLabelButtonControl(Composite parent, final Text textControl,
ImageDescriptor activeImageDescriptor, final String accessibilityText, String toolTipText, final int detail) {
final Image nativeImage = parent.getDisplay().getSystemImage(detail);
final Image activeImage = nativeImage != null ? nativeImage : activeImageDescriptor.createImage();
final Image inactiveImage = new Image(parent.getDisplay(), activeImage, SWT.IMAGE_GRAY);
final Image pressedImage = inactiveImage;
final Label labelButton = new Label(parent, SWT.NONE);
labelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
labelButton.setImage(inactiveImage);
labelButton.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
labelButton.setToolTipText(toolTipText);
labelButton.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if (nativeImage == null && activeImage != null && !activeImage.isDisposed()) {
activeImage.dispose();
}
if (inactiveImage != null && !inactiveImage.isDisposed()) {
inactiveImage.dispose();
}
if (pressedImage != null && !pressedImage.isDisposed()) {
pressedImage.dispose();
}
}
});
labelButton.addMouseListener(new MouseAdapter() {
private MouseMoveListener fMoveListener;
@Override
public void mouseDown(MouseEvent e) {
labelButton.setImage(pressedImage);
fMoveListener = new MouseMoveListener() {
private boolean fMouseInButton = true;
public void mouseMove(MouseEvent e) {
boolean mouseInButton = isMouseInButton(e);
if (mouseInButton != fMouseInButton) {
fMouseInButton = mouseInButton;
labelButton.setImage(mouseInButton ? pressedImage : inactiveImage);
}
}
};
labelButton.addMouseMoveListener(fMoveListener);
}
@Override
public void mouseUp(MouseEvent e) {
if (fMoveListener != null) {
labelButton.removeMouseMoveListener(fMoveListener);
fMoveListener = null;
boolean mouseInButton = isMouseInButton(e);
labelButton.setImage(mouseInButton ? activeImage : inactiveImage);
if (mouseInButton) {
if (textControl.isEnabled() && textControl.getText().length() > 0) {
notifySelectionChanged(detail);
}
}
}
}
private boolean isMouseInButton(MouseEvent e) {
Point buttonSize = labelButton.getSize();
return 0 <= e.x && e.x < buttonSize.x && 0 <= e.y && e.y < buttonSize.y;
}
});
labelButton.addMouseTrackListener(new MouseTrackListener() {
public void mouseEnter(MouseEvent e) {
if (labelButton.getImage() != activeImage) {
labelButton.setImage(activeImage);
}
}
public void mouseExit(MouseEvent e) {
if (labelButton.getImage() != inactiveImage) {
labelButton.setImage(inactiveImage);
}
}
public void mouseHover(MouseEvent e) {
}
});
labelButton.getAccessible().addAccessibleListener(new AccessibleAdapter() {
@Override
public void getName(AccessibleEvent e) {
e.result = accessibilityText;
}
});
labelButton.getAccessible().addAccessibleControlListener(new AccessibleControlAdapter() {
@Override
public void getRole(AccessibleControlEvent e) {
e.detail = ACC.ROLE_PUSHBUTTON;
}
});
return labelButton;
}
@Override
public void addTraverseListener(TraverseListener listener) {
textControl.addTraverseListener(listener);
}
@Override
public void removeTraverseListener(TraverseListener listener) {
textControl.removeTraverseListener(listener);
}
public void addSelectionListener(SelectionListener listener) {
textControl.addSelectionListener(listener);
selectionListeners.add(listener);
}
public void removeSelectionListener(SelectionListener listener) {
textControl.removeSelectionListener(listener);
selectionListeners.remove(listener);
}
public void addModifyListener(ModifyListener listener) {
textControl.addModifyListener(listener);
}
public void removeModifyListener(ModifyListener listener) {
textControl.removeModifyListener(listener);
}
private void notifySelectionChanged(int detail) {
if (useNativeSearchField != null && useNativeSearchField) {
// notification should happen via the widgets selection listener
return;
}
Event event = new Event();
event.detail = detail;
event.widget = textControl;
event.display = textControl.getDisplay();
SelectionEvent e = new SelectionEvent(event);
for (Object element : selectionListeners) {
((SelectionListener) element).widgetDefaultSelected(e);
}
}
private void updateButtonVisibilityAndEnablement() {
if (textControl != null && !textControl.isDisposed()) {
boolean hasText = textControl.getText().length() > 0;
setFindButtonVisibility(!(hasText && automaticFind));
setClearButtonVisibility(hasText);
}
}
private void setFindButtonVisibility(boolean visible) {
if (findControl != null && !findControl.isDisposed()) {
findControl.setVisible(visible);
if (findControl.getLayoutData() instanceof GridData) {
((GridData) findControl.getLayoutData()).exclude = !visible;
findControl.getParent().layout();
}
}
}
private void setClearButtonVisibility(boolean visible) {
if (clearControl != null && !clearControl.isDisposed()) {
clearControl.setVisible(visible);
if (clearControl.getLayoutData() instanceof GridData) {
((GridData) clearControl.getLayoutData()).exclude = !visible;
clearControl.getParent().layout();
}
}
}
public Text getTextControl() {
return textControl;
}
public String getText() {
if (textControl != null && !textControl.isDisposed()) {
return textControl.getText();
}
return ""; //$NON-NLS-1$
}
public void setText(String text) {
if (textControl != null && !textControl.isDisposed()) {
textControl.setText(text);
}
}
@Override
public void setBackground(Color color) {
if (useNativeSearchField != null && useNativeSearchField) {
super.setBackground(color);
}
}
public Collection<String> getSearchHistory() {
return Collections.unmodifiableCollection(searchHistory);
}
void addToSearchHistory(String searchString) {
if (searchString != null && searchString.trim().length() > 0) {
searchHistory.remove(searchString);
searchHistory.add(searchString);
}
}
public void saveState(IMemento memento) {
if (memento == null) {
return;
}
IMemento rootMemento = memento.createChild(FIND_MEMENTO_TYPE);
int i = 0;
for (String text : searchHistory) {
IMemento child = rootMemento.createChild(FIND_MEMENTO_ELEMENT);
child.putString(FIND_TEXT_MEMENTO_ELEMENT, text);
i++;
if (i > MAX_HISTORY_ITEMS) {
break;
}
}
}
public void restoreState(IMemento memento) {
if (memento == null) {
return;
}
List<String> history = new ArrayList<String>();
IMemento rootMemento = memento.getChild(FIND_MEMENTO_TYPE);
if (rootMemento != null) {
IMemento[] children = rootMemento.getChildren(FIND_MEMENTO_ELEMENT);
if (children != null && children.length > 0) {
for (IMemento child : children) {
String historyItem = child.getString(FIND_TEXT_MEMENTO_ELEMENT);
// add to the end of the list
history.add(history.size(), historyItem);
}
}
}
if (history != null) {
searchHistory = history;
}
}
}