blob: 5eed598d0b4dc9f6999089b02e2ba50330ded054 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2008 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.common.ui.internal.search.dialogs;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.osgi.util.TextProcessor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
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.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.wst.common.core.search.scope.SearchScope;
import org.eclipse.wst.common.ui.internal.Messages;
public class ComponentSearchListDialog extends Dialog {
private Display display = Display.getCurrent();
private String dialogTitle;
protected ComponentSearchListDialogConfiguration configuration;
private List componentTableViewerInput;
private List masterComponentList;
// widgets
protected Composite topComposite;
protected Composite bottomComposite;
private Text textFilter;
protected TableViewer componentTableViewer;
protected String fileLocationLabel = Messages._UI_LABEL_DECLARATION_LOCATION;
protected ViewForm fileLocationView;
protected CLabel locationLabel;
// keep track of the item previously selected in the table
private TableItem prevItem;
private String prevItemText;
protected Object componentSelection;
protected Object qualifierTextSelection;
protected ToolBar filterToolBar;
protected ToolItem toolItem;
protected MenuManager fMenuManager;
protected HashMap TableDecoratorTrackingTool = new HashMap();
private Button newButton;
public ComponentSearchListDialog(Shell shell, String dialogTitle, ComponentSearchListDialogConfiguration configuration) {
super(shell);
setShellStyle(getShellStyle() | SWT.RESIZE);
this.dialogTitle = dialogTitle;
this.configuration = configuration;
componentTableViewerInput = new ArrayList();
masterComponentList = new ArrayList();
configuration.init(this);
}
public void create() {
super.create();
getButton(IDialogConstants.OK_ID).setEnabled(false);
setTextFilterFocus();
}
protected void setTextFilterFocus() {
textFilter.setFocus();
}
protected Control createDialogArea(Composite parent) {
getShell().setText(dialogTitle);
Composite mainComposite = (Composite) super.createDialogArea(parent);
GridData gData = (GridData) mainComposite.getLayoutData();
gData.heightHint = 500;
configuration.createWidgetAboveQualifierBox(mainComposite);
// Subclasses may use this Composite to add desired widgets
//topComposite = new Composite(mainComposite, SWT.NONE);
//topComposite.setLayoutData(new GridData());
//topComposite.setLayout(new GridLayout());
// do we need to introduce a method here to contain this
// so we can add different parent other than 'topComposite'
Composite filterLabelAndText = new Composite(mainComposite, SWT.NONE);
GridLayout layoutFilterLabelAndText = new GridLayout(2, false);
layoutFilterLabelAndText.marginWidth = 0;
layoutFilterLabelAndText.marginHeight = 0;
filterLabelAndText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
filterLabelAndText.setLayout(layoutFilterLabelAndText);
// Create Text textFilter
Label filterLabel = new Label(filterLabelAndText, SWT.NONE);
filterLabel.setText(configuration.getFilterLabelText());// + "(? = any character, * = any string):");
GridData filterLabelData = new GridData();
filterLabelData.horizontalSpan = 2;
filterLabel.setLayoutData(filterLabelData);
textFilter = new Text(filterLabelAndText, SWT.SINGLE | SWT.BORDER);
textFilter.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
textFilter.addModifyListener(new TextFilterModifyAdapter());
GridData textFilterData = new GridData();
textFilterData.horizontalAlignment = GridData.FILL;
textFilterData.grabExcessHorizontalSpace = true;
textFilter.setLayoutData(textFilterData);
final INewComponentHandler handler = configuration.getNewComponentHandler();
if (handler != null)
{
newButton = new Button(filterLabelAndText, SWT.NONE);
newButton.setText(Messages._UI_LABEL_New);
newButton.addSelectionListener(new SelectionListener()
{
public void widgetDefaultSelected(SelectionEvent e)
{
handler.openNewComponentDialog();
}
public void widgetSelected(SelectionEvent e)
{
handler.openNewComponentDialog();
}
});
}
// Create Component TableViewer
createComponentTableViewer(mainComposite);
configuration.createWidgetAboveQualifierBox(mainComposite);
// Create Qualifier List widget
Label qualifierLabel = new Label(mainComposite, SWT.NONE);
qualifierLabel.setText(Messages._UI_LABEL_QUALIFIER);
qualifierLabel.setText(fileLocationLabel);
fileLocationView = new ViewForm(mainComposite, SWT.BORDER | SWT.FLAT );
GridData data = new GridData();
data.horizontalAlignment = GridData.FILL;
data.grabExcessHorizontalSpace = true;
data.heightHint = 22;
fileLocationView.setLayoutData(data);
locationLabel = new CLabel(fileLocationView, SWT.FLAT);
fileLocationView.setContent(locationLabel);
locationLabel.setFont(fileLocationView.getFont());
configuration.createWidgetBelowQualifierBox(mainComposite);
bottomComposite = new Composite(mainComposite, SWT.NONE);
bottomComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
bottomComposite.setLayout(new GridLayout());
// Populate the Component TableViewer via the provider
// TODO: Is this the right way to set/get the ContentProvider?
componentTableViewer.setContentProvider(new ComponentTableContentProvider());
componentTableViewer.setLabelProvider(configuration.getDescriptionProvider().getLabelProvider());
componentTableViewer.setSorter(new ViewerSorter());
componentTableViewer.setInput(componentTableViewerInput);
// TODO (cs) need to do some work to make the default search scope
// more well defined, currently the default behaviour is to pass a null
// argument in to populateMasterComponentList but we should provide
// getters/setters to allow the default to be controlled
populateMasterComponentList(null);
refreshTableViewer("");
return mainComposite;
}
/*
* Creates the Component TableViewer.
*/
private void createComponentTableViewer(Composite base) {
componentTableViewer = createFilterMenuAndTableViewer(base);
componentTableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
//IStructuredSelection structuredSelection = (IStructuredSelection) event.getSelection();
//List qualifiers = searchListProvider.getQualifiers(structuredSelection.getFirstElement());
//updateQualifierList(qualifiers);
updateCanFinish();
}
});
componentTableViewer.getTable().addSelectionListener(new SelectionListener(){
// Changing the text for the component selected and display its source
// file in the box under the table viewer
IComponentDescriptionProvider descriptionProvider = configuration.getDescriptionProvider();
public void widgetSelected(SelectionEvent e) {
run();
}
public void widgetDefaultSelected(SelectionEvent e) {
// bug 144548 - unnecessary
// run();
}
private void run() {
// restores the text of previous item
if (prevItem != null && !prevItem.isDisposed()){
prevItem.setText(prevItemText);
}
TableItem[] items = componentTableViewer.getTable().getSelection();
Object component = items[0].getData();
prevItem = items[0];
prevItemText = items[0].getText();
// add clarification for the first selected item
items[0].setText( descriptionProvider.getName(component) + " - " +
descriptionProvider.getQualifier(component));
updateLocationView(component, descriptionProvider);
}
});
componentTableViewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
okPressed();
}
});
}
protected TableViewer createFilterMenuAndTableViewer(Composite comp) {
Composite labelAndFilter = new Composite(comp, SWT.NONE);
labelAndFilter.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout layout= new GridLayout();
layout.numColumns= 2;
layout.marginWidth= 0; layout.marginHeight= 0;
labelAndFilter.setLayout(layout);
Label tableLabel = new Label(labelAndFilter, SWT.NONE);
tableLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
tableLabel.setText(configuration.getListLabelText());
filterToolBar = new ToolBar(labelAndFilter,SWT.FLAT);
configuration.createToolBarItems(filterToolBar);
TableViewer tableViewer = new TableViewer(new Table(labelAndFilter, SWT.SINGLE | SWT.BORDER));
Control TableWidget = tableViewer.getTable();
GridData gd = new GridData(GridData.FILL_BOTH);
gd.horizontalSpan = 2;
TableWidget.setLayoutData(gd);
return tableViewer;
}
private void updateLocationView(Object component, IComponentDescriptionProvider lp) {
IFile file = lp.getFile(component);
if ( file == null ){
locationLabel.setText("");
locationLabel.setImage(null);
return;
}
String filePath = "";
filePath = file.getFullPath().toString();
//locationView.redraw();
locationLabel.setText(TextProcessor.process(filePath));
locationLabel.setImage(lp.getFileIcon(component));
}
/*
* Returns the processed filter text for the Text field. Inserts a "."
* before each supported meta-character.
*/
protected String getProcessedFilterString() {
return processFilterString(textFilter.getText());
}
/*
* If supported metacharacters are used in the filter string, we need to
* insert a "." before each metacharacter.
*/
private String processFilterString(String inputString) {
if (!(inputString.equals(""))) {
inputString = insertString("*", ".", inputString);
inputString = insertString("?", ".", inputString);
inputString = inputString + ".*";
} else {
inputString = ".*";
}
return inputString.toLowerCase();
}
/*
* Helper method to insert a "." before each metacharacter in the
* search/filter string.
*/
private String insertString(String target, String newString, String string) {
StringBuffer stringBuffer = new StringBuffer(string);
int index = stringBuffer.indexOf(target);
while (index != -1) {
stringBuffer = stringBuffer.insert(index, newString);
index = stringBuffer.indexOf(target, index + newString.length() + target.length());
}
return stringBuffer.toString();
}
/*
* Listens to changes made in the text filter widget
*/
private class TextFilterModifyAdapter implements ModifyListener {
public void modifyText(ModifyEvent e) {
if (e.widget == textFilter) {
if (delayedEvent != null) {
delayedEvent.CANCEL = true;
}
delayedEvent = new DelayedEvent();
Display.getCurrent().timerExec(400, delayedEvent);
}
}
}
//TODO... do we really need one instance?
private DelayedEvent delayedEvent;
/*
* Update the component TableViewer when the text filter is modified.
* Use a DelayedEvent so we don't update on every keystroke.
*/
private class DelayedEvent implements Runnable {
public boolean CANCEL = false;
public void run() {
if (!CANCEL) {
refreshTableViewer(getProcessedFilterString());
// Select first match
if (componentTableViewer.getTable().getItemCount() > 0) {
TableItem item = componentTableViewer.getTable().getItems()[0];
TableItem items[] = new TableItem[1];
items[0] = item;
componentTableViewer.getTable().setSelection(items);
}
// Update qualifierList
//IStructuredSelection structuredSelection = (IStructuredSelection) componentTableViewer.getSelection();
// TODO ... manage qualifiers
//List qualifiers = searchListProvider.getQualifiers(structuredSelection.getFirstElement());
//updateQualifierList(qualifiers);
updateCanFinish();
}
}
}
class ComponentList implements IComponentList {
private Vector objectVector = new Vector();
private long currentChangeCounter = 0;
private long lastUpdateTime = 0;
public void add(Object o) {
objectVector.add(o);
currentChangeCounter++;
doViewerUpdate();
}
public void addAll(Collection collection)
{
objectVector.addAll(collection);
currentChangeCounter += collection.size();
doViewerUpdate();
}
private void doViewerUpdate() {
// TODO: Investigate if we should also add a timer condition??
// if (currentChangeCounter >= 10) {
// currentChangeCounter = 0;
// fireUpdateList(this);
// }
// cs: yep I think we really do need to use a time based approach
//
long time = System.currentTimeMillis();
if (time - lastUpdateTime > 300)
{
lastUpdateTime = time;
fireUpdateList(ComponentList.this);
}
}
public int size() {
return objectVector.size();
}
public List subList(int startIndex, int endIndex) {
return objectVector.subList(startIndex, endIndex);
}
public Iterator iterator() {
return objectVector.iterator();
}
}
// this method gets called from a non-ui thread so needs to call
// asyncExec to ensure the UI updates happen on the UI thread
//
protected void fireUpdateList(final ComponentList list) {
Runnable runnable = new Runnable() {
public void run(){
// add new objects
int growingListSize = list.size();
int currentSize = masterComponentList.size();
if (growingListSize > currentSize) {
masterComponentList.addAll(list.subList(currentSize, growingListSize));
}
refreshTableViewer(getProcessedFilterString());
}
};
display.asyncExec(runnable);
}
public void updateForFilterChange()
{
populateMasterComponentList(null);
refreshTableViewer(getProcessedFilterString());
}
/*
* Populate the Component TreeViewer with items.
*/
protected void populateMasterComponentList(final SearchScope searchScope) {
masterComponentList.clear();
final ComponentList componentList = new ComponentList();
// TODO (cs) it doesn't seem to make sennse to do any of the work on the UI thread
// I've change the behaviour here to do all of the work in the background
//
//searchListProvider._populateComponentListQuick(componentList, 0);
Job job = new Job("read components") {
protected IStatus run(IProgressMonitor monitor) {
try {
// this stuff gets executed on a non-UI thread
//
configuration.getSearchListProvider().populateComponentList(componentList, searchScope, null);
// Do a final update of our Input for the component tree viewer.
fireUpdateList(componentList);
}
catch (Exception e) {
e.printStackTrace();
}
return Status.OK_STATUS;
}
};
job.schedule();
}
protected void refreshTableViewer(String filterText) {
componentTableViewerInput.clear();
ILabelProvider labelProvider = configuration.getDescriptionProvider().getLabelProvider();
Pattern regex = Pattern.compile(filterText);
Iterator it = masterComponentList.iterator();
while (it.hasNext()) {
Object item = it.next();
String itemString = labelProvider.getText(item);
Matcher m = regex.matcher(itemString.toLowerCase());
if (itemString.toLowerCase().startsWith(filterText) || m.matches()) {
componentTableViewerInput.add(item);
}
}
componentTableViewer.refresh();
decorateTable();
}
/**
* Looking at each item in the Table. If there are other items with same name
* , then add extra info (namespace, file) into the the text label of all these
* duplicated items.
* - This should be called everytime the Table viewer is refreshed..
*/
protected void decorateTable(){
TableDecoratorTrackingTool.clear();
IComponentDescriptionProvider lp = configuration.getDescriptionProvider();
// init the name-duplicates counter
for (int i = 0; i < componentTableViewerInput.size(); i++){
Object currentItem = componentTableViewerInput.get(i);
String name = lp.getName(currentItem);
Integer count = (Integer) TableDecoratorTrackingTool.get(name);
if ( count == null){
TableDecoratorTrackingTool.put(name, new Integer(1));
}
else{
TableDecoratorTrackingTool.put(
name, new Integer(count.intValue() + 1));
}
}
// Modify/decorate those items in the Table that have duplicated name
TableItem[] items = componentTableViewer.getTable().getItems();
for (int i =0 ; i < items.length; i++){
Object currentItem = items[i].getData();
Integer count = (Integer) TableDecoratorTrackingTool.get(lp.getName(currentItem));
if ( count != null && count.intValue() > 1){
items[i].setText(lp.getName(currentItem) + " - " +
lp.getQualifier(currentItem));
}
}
}
/*
* If there is a selection in the ComponentTreeViewer, enable OK
*/
protected void updateCanFinish() {
IStructuredSelection selection = (IStructuredSelection) componentTableViewer.getSelection();
if (selection.getFirstElement() != null) {
getButton(IDialogConstants.OK_ID).setEnabled(true);
}
else {
getButton(IDialogConstants.OK_ID).setEnabled(false);
}
}
protected void okPressed() {
IStructuredSelection selection = (IStructuredSelection) componentTableViewer.getSelection();
componentSelection = selection.getFirstElement();
super.okPressed();
}
private class ComponentTableContentProvider implements ITreeContentProvider {
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof List) {
return ((List) parentElement).toArray();
}
return new Object[0];
}
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
public Object getParent(Object element) {
return null;
}
public boolean hasChildren(Object element) {
if (getChildren(element).length > 0) {
return true;
}
return false;
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
public void dispose() {
}
}
public ComponentSpecification getSelectedComponent()
{
ComponentSpecification result = null;
if (componentSelection != null)
{
result = new ComponentSpecification();
IComponentDescriptionProvider componentDescriptionProvider = configuration.getDescriptionProvider();
result.setName(componentDescriptionProvider.getName(componentSelection));
result.setQualifier(componentDescriptionProvider.getQualifier(componentSelection));
result.setFile(componentDescriptionProvider.getFile(componentSelection));
result.setObject(componentSelection);
}
return result;
}
}