/** * Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below. * 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: * Initial implementation - itemis AG (http://www.itemis.eu * Cloudsmith - specialized for puppet * */ package org.cloudsmith.geppetto.pp.dsl.ui.search; import java.util.Collection; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.jface.internal.text.TableOwnerDrawSupport; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; 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.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; 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.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ListDialog; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.ui.search.EObjectDescriptionContentProvider; /** * @author Jan Koehnlein - Initial contribution and API * @author Knut Wannheden * @author Henrik Lindberg - Cloudsmith, adapted to Puppet */ public class PPObjectSearchDialog extends ListDialog { protected Text searchControl; private String initialPatternText; private Label messageLabel; private Label searchStatusLabel; private IteratorJob sizeCalculationJob; private Label matchingElementsLabel; /** @since 2.0 */ // protected Text typeSearchControl; private IPPEObjectSearch searchEngine; private final ILabelProvider labelProvider; private boolean enableStyledLabels; public PPObjectSearchDialog(Shell parent, IPPEObjectSearch searchEngine, ILabelProvider labelProvider) { super(parent); this.searchEngine = searchEngine; this.labelProvider = labelProvider; setTitle("Open Puppet Element"); setMessage("Enter element name prefix or pattern (*, ?, or camel case):"); setAddCancelButton(true); // super class needs an IStructuredContentProvider so we register this dummy and // register the lazy one later setContentProvider(new IStructuredContentProvider() { public void dispose() { } public Object[] getElements(Object inputElement) { return null; } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } }); setLabelProvider(labelProvider); } public PPObjectSearchDialog(Shell parent, IPPEObjectSearch searchEngine, ILabelProvider labelProvider, boolean enableStyledLabels) { this(parent, searchEngine, labelProvider); this.enableStyledLabels = enableStyledLabels; } /** * Called when the dialog is initially opened and whenever the input text changes. Applies the search filter as * specified by {@link #searchControl} and {@link #typeSearchControl} using {@link #getSearchEngine()} and updates * the result using {@link #startSizeCalculation(Iterable)}. * * @since 2.0 */ protected void applyFilter() { String searchPattern = searchControl.getText(); // TODO: get a set of accepted classes - null or empty collection means all/any // String typeSearchPattern = typeSearchControl.getText(); Collection<EClass> acceptedClasses = null; if(searchPattern != null) { Iterable<IEObjectDescription> matches = getSearchEngine().findMatches(searchPattern, acceptedClasses); startSizeCalculation(matches); } } @Override protected Control createDialogArea(Composite container) { Composite parent = (Composite) super.createDialogArea(container); if(enableStyledLabels && labelProvider instanceof IStyledLabelProvider) { final Table table = getTableViewer().getTable(); final IStyledLabelProvider styledLabelProvider = (IStyledLabelProvider) labelProvider; TableOwnerDrawSupport.install(table); Listener listener = new Listener() { public void handleEvent(Event event) { handleSetData(event); } protected void handleSetData(Event event) { TableItem item = (TableItem) event.item; IEObjectDescription description = (IEObjectDescription) item.getData(); if(description != null) { StyledString styledString = styledLabelProvider.getStyledText(description); String displayString = styledString == null ? description.toString() : styledString.toString(); StyleRange[] styleRanges = styledString.getStyleRanges(); item.setText(displayString); TableOwnerDrawSupport.storeStyleRanges(item, 0, styleRanges); } } }; table.addListener(SWT.SetData, listener); } messageLabel = new Label(parent, SWT.NONE); setDefaultGridData(messageLabel); EObjectDescriptionContentProvider contentProvider = new EObjectDescriptionContentProvider(); getTableViewer().setContentProvider(contentProvider); getTableViewer().addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { ISelection selection = event.getSelection(); if(selection instanceof IStructuredSelection) { IStructuredSelection structuredSelection = (IStructuredSelection) selection; if(!structuredSelection.isEmpty()) { Object firstElement = structuredSelection.getFirstElement(); if(firstElement instanceof IEObjectDescription) { IEObjectDescription eObjectDescription = (IEObjectDescription) firstElement; URI resourceURI = eObjectDescription.getEObjectURI().trimFragment(); if(resourceURI.isPlatform()) { messageLabel.setText(resourceURI.toPlatformString(true)); } else if(resourceURI.isFile()) { messageLabel.setText(resourceURI.toFileString()); } else { messageLabel.setText(resourceURI.toString()); } return; } } } messageLabel.setText(""); //$NON-NLS-1$ } }); applyFilter(); return parent; } @Override protected Label createMessageArea(Composite composite) { Label label = super.createMessageArea(composite); searchControl = new Text(composite, SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL); setDefaultGridData(searchControl); // TODO: NOT MEANINGFUL TO SEARCH FOR ANY ECLASS - OFFER A SELECTION INSTEAD // TODO: HANDLE (FUTURE) SEARCH FOR RESOURCE WHERE USER DOES NOT KNOW THE ENCODING OF THE FQN // // Label typePatternLabel = new Label(composite, SWT.NONE); // typePatternLabel.setText("EClass name prefix or pattern"); // setDefaultGridData(typePatternLabel); // typeSearchControl = new Text(composite, SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL); // setDefaultGridData(typeSearchControl); Composite labelComposite = new Composite(composite, SWT.NONE); setDefaultGridData(labelComposite); GridLayout labelCompositeLayout = new GridLayout(2, true); labelCompositeLayout.marginWidth = 0; labelComposite.setLayout(labelCompositeLayout); matchingElementsLabel = new Label(labelComposite, SWT.NONE); matchingElementsLabel.setText("Matching items:"); matchingElementsLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1)); searchStatusLabel = new Label(labelComposite, SWT.RIGHT); searchStatusLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1)); ModifyListener textModifyListener = new ModifyListener() { public void modifyText(ModifyEvent e) { applyFilter(); } }; searchControl.addModifyListener(textModifyListener); // typeSearchControl.addModifyListener(textModifyListener); // searchControl.addKeyListener(new KeyAdapter() { // @Override // public void keyPressed(KeyEvent e) { // if(e.keyCode == SWT.ARROW_DOWN) { // typeSearchControl.setFocus(); // } // } // }); if(initialPatternText != null) { searchControl.setText(initialPatternText); searchControl.selectAll(); } // typeSearchControl.addKeyListener(new KeyAdapter() { searchControl.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if(e.keyCode == SWT.ARROW_DOWN) { TableViewer tableViewer = getTableViewer(); tableViewer.getTable().setFocus(); if(tableViewer.getSelection().isEmpty()) { Object firstElement = tableViewer.getElementAt(0); if(firstElement != null) { tableViewer.setSelection(new StructuredSelection(firstElement)); } } } } }); return label; } /** * @since 2.0 */ protected String getInitialPattern() { return initialPatternText; } protected IPPEObjectSearch getSearchEngine() { return searchEngine; } @Override protected int getTableStyle() { return super.getTableStyle() | SWT.VIRTUAL; } @Override public int open() { if(getInitialPattern() == null) { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if(window != null) { ISelection selection = window.getSelectionService().getSelection(); if(selection instanceof ITextSelection) { String text = ((ITextSelection) selection).getText(); if(text != null) { text = text.trim(); // skip interpolation start if(text.startsWith("${")) text = text.substring(2); // skip interpolation end while(text.endsWith("}")) text = text.substring(0, text.length() - 1); // Skip a leading $ for variable if(text.startsWith("$")) text = text.substring(1); if(text.length() > 0) { setInitialPattern(text); } } } } } return super.open(); } private void setDefaultGridData(Control control) { control.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); } /** * @since 2.0 */ public void setInitialPattern(String text) { this.initialPatternText = text; } /** * Called by {@link #applyFilter()} and is responsible for calling {@link #updateMatches(Collection, boolean)} with * an appropriately sorted list of matches. * * @since 2.0 */ protected void startSizeCalculation(Iterable<IEObjectDescription> matches) { if(getTableViewer() != null) { if(sizeCalculationJob != null) { sizeCalculationJob.cancel(); try { sizeCalculationJob.join(); } catch(InterruptedException e) { sizeCalculationJob = new IteratorJob(this); } } else { sizeCalculationJob = new IteratorJob(this); } sizeCalculationJob.init(matches); sizeCalculationJob.schedule(); } } public void updateMatches(final Collection<IEObjectDescription> matches, final boolean isFinished) { Display.getDefault().asyncExec(new Runnable() { public void run() { if(getShell() != null) { if(getTableViewer() != null) { getTableViewer().setItemCount(matches.size()); getTableViewer().setInput(matches); } searchStatusLabel.setText((isFinished) ? "" : "Searching..."); //$NON-NLS-1$ matchingElementsLabel.setText("Matching items:" + " (" + matches.size() + " matches)"); //$NON-NLS-1$ } } }); } }