/*******************************************************************************
* Copyright (c) 2006, 2010 Spring IDE Developers
* 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:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.beans.ui.dialogs;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jface.dialogs.DialogSettings;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ViewForm;
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.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SelectionStatusDialog;
import org.eclipse.ui.internal.misc.StringMatcher;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.model.IBean;
import org.springframework.ide.eclipse.beans.core.model.IBeansModel;
import org.springframework.ide.eclipse.beans.ui.BeansUIActivationHistory;
import org.springframework.ide.eclipse.beans.ui.BeansUIPlugin;
import org.springframework.ide.eclipse.beans.ui.model.BeansModelLabelProvider;
import org.springframework.util.StringUtils;
/**
* Spring Bean selection dialog.
* @author Christian Dupuis
* @author Torsten Juergeleit
*/
@SuppressWarnings("restriction")
public class BeanListSelectionDialog extends SelectionStatusDialog {
/**
* Implements a {@link ViewFilter} based on content typed in the filter field
*/
private static class BeanFilter extends ViewerFilter {
private StringMatcher matcher;
private boolean isUpperCasePattern;
private String filterText;
private List<IBean> beanActivationHistory;
protected BeanFilter(List<IBean> historyBeans) {
this.beanActivationHistory = historyBeans;
}
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (matcher == null) {
return beanActivationHistory.contains(element);
}
if (element instanceof IBean) {
IBean bean = (IBean) element;
if (matcher.match(bean.getElementName())) {
return true;
}
if (matcher.match(bean.getClassName())) {
return true;
}
String[] aliases = bean.getAliases();
if (aliases != null) {
for (String alias : aliases) {
if (matcher.match(alias)) {
return true;
}
}
}
if (isUpperCasePattern) {
if (SearchPattern.camelCaseMatch(filterText, StringUtils.capitalize(bean.getElementName()))) {
return true;
}
String className = bean.getClassName();
if (className != null) {
int i = className.lastIndexOf('.');
if (i > 0 && i < className.length()) {
if (SearchPattern.camelCaseMatch(filterText, 0, filterText.length(), className, i + 1,
className.length())) {
return true;
}
}
else {
if (SearchPattern.camelCaseMatch(filterText, className)) {
return true;
}
}
}
}
}
return false;
}
public void setFilterText(String filterText) {
if (filterText.trim().equals("")) {
this.matcher = null;
this.isUpperCasePattern = false;
this.filterText = null;
}
else {
this.matcher = new StringMatcher(filterText + '*', true, false);
this.isUpperCasePattern = true;
for (char c : filterText.toCharArray()) {
if (Character.isLowerCase(c)) {
this.isUpperCasePattern = false;
break;
}
}
this.filterText = filterText;
}
}
}
private static final String DIALOG_SETTINGS = BeanListSelectionDialog.class.getName();
private static final String HEIGHT = "height";
private static final String WIDTH = "width";
private int fHeight = 18;
private CLabel fLabel;
private Point fLocation;
private IDialogSettings fSettings;
private Point fSize;
private int fWidth = 60;
private final LabelProvider labelProvider = new BeansModelLabelProvider(true);
private TableViewer viewer;
public BeanListSelectionDialog(Shell parent) {
super(parent);
setTitle(BeansUIPlugin.getResourceString("BeanListSelectionDialog.title"));
setStatusLineAboveButtons(true);
setShellStyle(getShellStyle() | SWT.RESIZE);
setBlockOnOpen(true);
IDialogSettings settings = BeansUIPlugin.getDefault().getDialogSettings();
fSettings = settings.getSection(DIALOG_SETTINGS);
if (fSettings == null) {
fSettings = new DialogSettings(DIALOG_SETTINGS);
settings.addSection(fSettings);
fSettings.put(WIDTH, 480);
fSettings.put(HEIGHT, 320);
}
}
@Override
public boolean close() {
writeSettings();
return super.close();
}
@Override
protected void computeResult() {
setResult(((IStructuredSelection) viewer.getSelection()).toList());
}
@Override
protected Control createDialogArea(Composite parent) {
readSettings();
Composite area = (Composite) super.createDialogArea(parent);
Label message = new Label(area, SWT.NONE);
message.setText(BeansUIPlugin.getResourceString("BeanListSelectionDialog.message"));
final Text filterText = new Text(area, SWT.SINGLE | SWT.BORDER);
filterText.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, false));
Label matches = new Label(area, SWT.NONE);
matches.setText("&Matching beans:");
viewer = new TableViewer(area, SWT.SINGLE | SWT.BORDER);
Control control = viewer.getControl();
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
gd.widthHint = convertWidthInCharsToPixels(fWidth);
gd.heightHint = convertHeightInCharsToPixels(fHeight);
gd.grabExcessVerticalSpace = true;
gd.grabExcessHorizontalSpace = true;
gd.horizontalAlignment = GridData.FILL;
gd.verticalAlignment = GridData.FILL;
control.setLayoutData(gd);
viewer.setLabelProvider(labelProvider);
viewer.setContentProvider(new ArrayContentProvider());
final Set<IBean> beanList = new LinkedHashSet<IBean>();
final List<IBean> historyList = new ArrayList<IBean>();
final List<IBean> historyBeans = new ArrayList<IBean>();
IRunnableWithProgress runnable = new IRunnableWithProgress() {
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
historyBeans.addAll(BeansUIActivationHistory.getBeanActivationHistory());
Collections.reverse(historyBeans);
historyList.addAll(historyBeans);
Set<IBean> beans = new HashSet<IBean>();
IBeansModel beansModel = BeansCorePlugin.getModel();
beans.addAll(BeansModelUtils.getBeans(beansModel, monitor));
beanList.addAll(historyBeans);
beanList.addAll(beans);
}
catch (OperationCanceledException e) {
throw new InterruptedException();
}
}
};
try {
IRunnableContext context = new ProgressMonitorDialog(getShell());
context.run(true, true, runnable);
}
catch (InvocationTargetException e) {
}
catch (InterruptedException e) {
}
viewer.setInput(beanList);
final BeanListSelectionDialog.BeanFilter filter = new BeanListSelectionDialog.BeanFilter(historyBeans);
viewer.addFilter(filter);
viewer.setComparator(new ViewerComparator() {
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
IBean t1 = getCorrespondingTask(e1);
IBean t2 = getCorrespondingTask(e2);
boolean isInHistory1 = historyList.contains(t1);
boolean isInHistory2 = historyList.contains(t2);
// Being on task history takes precedence...
if (isInHistory1 && !isInHistory2) {
return -1;
}
if (!isInHistory1 && isInHistory2) {
return 1;
}
// Both are in task history; who is more recent?
if (isInHistory1 && isInHistory2) {
return historyList.indexOf(t1) - historyList.indexOf(t2);
}
// Both are not in task history; sort by summary...
return labelProvider.getText(e1).compareTo(labelProvider.getText(e2));
}
private IBean getCorrespondingTask(Object o) {
if (o instanceof IBean) {
return (IBean) o;
}
return null;
}
});
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
handleSelectionChanged();
}
});
viewer.addOpenListener(new IOpenListener() {
public void open(OpenEvent event) {
if (getOkButton().getEnabled()) {
okPressed();
}
}
});
filterText.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_DOWN) {
viewer.getControl().setFocus();
}
}
});
filterText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
filter.setFilterText(filterText.getText());
viewer.refresh(false);
Object first = viewer.getElementAt(0);
if (first != null) {
viewer.setSelection(new StructuredSelection(first));
}
}
});
ViewForm fForm = new ViewForm(area, SWT.BORDER | SWT.FLAT);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
fForm.setLayoutData(gd);
fLabel = new CLabel(fForm, SWT.FLAT);
fLabel.setFont(fForm.getFont());
fForm.setContent(fLabel);
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
ISelection selection = window.getSelectionService().getSelection();
if (selection instanceof ITextSelection) {
String text = ((ITextSelection) selection).getText();
int n = text.indexOf('\n');
if (n > -1) {
text = text.substring(0, n);
}
filterText.setText(text);
filterText.setSelection(0, text.length());
}
return area;
}
@Override
protected Point getInitialLocation(Point initialSize) {
Point result = super.getInitialLocation(initialSize);
if (fLocation != null) {
result.x = fLocation.x;
result.y = fLocation.y;
Rectangle display = getShell().getDisplay().getClientArea();
int xe = result.x + initialSize.x;
if (xe > display.width) {
result.x -= xe - display.width;
}
int ye = result.y + initialSize.y;
if (ye > display.height) {
result.y -= ye - display.height;
}
}
return result;
}
@Override
protected Point getInitialSize() {
Point result = super.getInitialSize();
if (fSize != null) {
result.x = Math.max(result.x, fSize.x);
result.y = Math.max(result.y, fSize.y);
Rectangle display = getShell().getDisplay().getClientArea();
result.x = Math.min(result.x, display.width);
result.y = Math.min(result.y, display.height);
}
return result;
}
/**
* Handles a selection changed event:
* <ol>
* <li>validate the current selection
* <li>if an element is selected then show the element's resource (full path and icon)
* </ol>
*/
protected void handleSelectionChanged() {
validateCurrentSelection();
if (!viewer.getSelection().isEmpty()) {
ISelection sel = viewer.getSelection();
if (sel instanceof IStructuredSelection) {
IBean bean = (IBean) ((IStructuredSelection) sel).getFirstElement();
fLabel.setImage(labelProvider.getImage(bean.getElementParent()));
fLabel.setText(bean.getElementResource().getFullPath().toString().substring(1));
}
}
else {
fLabel.setImage(null);
fLabel.setText(null);
}
}
/**
* Initializes itself from the dialog settings with the same state as at the previous invocation.
*/
private void readSettings() {
try {
int x = fSettings.getInt("x");
int y = fSettings.getInt("y");
fLocation = new Point(x, y);
}
catch (NumberFormatException e) {
fLocation = null;
}
try {
int width = fSettings.getInt("width");
int height = fSettings.getInt("height");
fSize = new Point(width, height);
}
catch (NumberFormatException e) {
fSize = null;
}
}
/**
* Validates the current selection and updates the status line accordingly.
* @return boolean <code>true</code> if the current selection is valid.
*/
protected boolean validateCurrentSelection() {
IStatus status;
if (viewer.getSelection().isEmpty()) {
status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.ERROR, "", null);
}
else {
status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, "", //$NON-NLS-1$
null);
}
updateStatus(status);
return status.isOK();
}
/**
* Stores it current configuration in the dialog store.
*/
private void writeSettings() {
Point location = getShell().getLocation();
fSettings.put("x", location.x);
fSettings.put("y", location.y);
Point size = getShell().getSize();
fSettings.put("width", size.x);
fSettings.put("height", size.y);
}
}