/*
* Copyright (c) 2009 Andrejs Jermakovics.
*
* 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:
* Andrejs Jermakovics - initial implementation
*/
package it.unibz.instasearch.ui;
import it.unibz.instasearch.InstaSearchPlugin;
import it.unibz.instasearch.indexing.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.IControlContentAdapter;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyListener;
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.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
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.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE.SharedImages;
/**
*
*/
public class SearchViewControl implements DisposeListener {
private static final String PROPOSAL_KEYSTROKE = "Ctrl+Space";
private static final String SEARCH_TEXT_TIP = "search text " + Field.PROJ + ":MyProject " + Field.EXT + ":xml,java";
private TreeViewer resultViewer;
private StyledText searchText;
private ImageRegistry imageRegistry = new ImageRegistry();
private ToolBar filterBar;
private SearchContentProposalProvider proposalProvider;
private Composite parent;
private int itemInsertIndex = 0;
private ModifyListener modifyListener; //TODO: new interface
private Color searchTipColor = getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
private Color defaultSearchColor = getDisplay().getSystemColor(SWT.COLOR_BLACK);
/**
* @param parent
*/
public SearchViewControl(Composite parent, ModifyListener modifyListener) {
this.parent = parent;
this.modifyListener = modifyListener;
createControl(parent);
parent.addDisposeListener(this);
}
/**
* @return the searchText
*/
public StyledText getSearchText() {
return searchText;
}
/**
* @return the resultViewer
*/
public TreeViewer getResultViewer() {
return resultViewer;
}
/**
* @param parent
*/
private void createControl(Composite parent) {
GridLayout parentLayout = new GridLayout();
//parentLayout.marginTop = 2;
parentLayout.marginLeft = 0;
parentLayout.horizontalSpacing = 0;
parentLayout.verticalSpacing = 0;
parentLayout.marginWidth = 0;
parentLayout.marginHeight = 2;
parent.setLayout( parentLayout );
Composite textComposite = new Composite(parent, SWT.NONE);
GridData textCompositeGridData = new GridData(GridData.FILL, GridData.BEGINNING, true, false);
textComposite.setLayoutData( textCompositeGridData );
GridLayout layout = new GridLayout(2, false);
layout.marginLeft = 2;
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.marginWidth = 0;
layout.marginHeight = 0;
textComposite.setLayout( layout );
createSearchTextControl( textComposite );
createFilterBar(textComposite);
Composite viewComposite = new Composite(parent, SWT.NONE);
viewComposite.setLayoutData( new GridData(GridData.FILL, GridData.FILL, true, true) );
FillLayout fillLayout = new FillLayout();
fillLayout.marginHeight = 0;
fillLayout.marginWidth = 0;
fillLayout.spacing = 0;
viewComposite.setLayout( fillLayout );
resultViewer = new TreeViewer(viewComposite, SWT.FULL_SELECTION);
}
private void createFilterBar(Composite parent) {
filterBar = new ToolBar(parent, SWT.FLAT | SWT.RIGHT);
GridData gridData = new GridData(GridData.END, GridData.CENTER, false, false);
filterBar.setLayoutData(gridData);
ToolItem clearItem = new ToolItem(filterBar, SWT.NONE);
clearItem.setImage( getPluginImage("close") );
clearItem.setToolTipText("Clear search text");
clearItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
searchText.setText("");
searchText.setFocus();
}
});
new ToolItem(filterBar, SWT.SEPARATOR);
itemInsertIndex = filterBar.getItemCount(); // so we insert after separator
ToolItem filterItem = new ToolItem(filterBar, SWT.DROP_DOWN);
filterItem.setToolTipText("Filter by...");
filterItem.setImage( getPluginImage("filter") );
DropdownMenuProvider<FilterChangeAction> dropdown = new DropdownMenuProvider<FilterChangeAction>(filterItem);
dropdown.add( new ProjFilterAction(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(SharedImages.IMG_OBJ_PROJECT)) );
dropdown.add( new ExtensionFilterAction(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FILE)) );
dropdown.add( new FieldFilterAction(Field.WS, InstaSearchPlugin.getImageDescriptor("workingsets")) );
dropdown.add( new FieldFilterAction(Field.MODIFIED, InstaSearchPlugin.getImageDescriptor("dates")) );
filterBar.update();
}
private abstract class FilterChangeAction extends Action
{
public FilterChangeAction(String proposal, ImageDescriptor imageDescriptor) {
super(proposal, imageDescriptor);
}
public FilterChangeAction(String string) {
super(string);
}
@Override
final public void run() {
runAction();
updateSearch();
}
abstract protected void runAction();
}
private class FieldFilterAction extends FilterChangeAction
{
private Field field;
public FieldFilterAction(Field field, ImageDescriptor img) {
super(field.getTitle(), img);
this.field = field;
}
protected Image getImage(String proposal)
{
String key = field + ":" + proposal;
Image img = imageRegistry.get(key);
if( img == null ) img = imageRegistry.get(field + ":");
if( img == null )
{
imageRegistry.put(key, getImageDescriptor());
img = imageRegistry.get(key);
}
return img;
}
@Override
public void runAction() {
final ToolItem fieldItem = new ToolItem(filterBar, SWT.DROP_DOWN, itemInsertIndex);
fieldItem.setToolTipText("Filter by " + field.getTitle());
fieldItem.setImage( getImage("") );
fieldItem.setData(field);
DropdownMenuProvider<FilterChangeAction> dropdown = new DropdownMenuProvider<FilterChangeAction>(fieldItem);
addRemoveAction(fieldItem, dropdown);
dropdown.addSeparator();
List<String> proposals = proposalProvider.getFieldProposals(field, "");
FilterChangeAction firstAction = null;
for(String proposal: proposals)
{
FilterChangeAction action = addAction(dropdown, proposal);
if( firstAction == null ) firstAction = action;
}
if( "".equals(fieldItem.getText()) && firstAction != null )
firstAction.run();
parent.layout(true, true);
}
private void addRemoveAction(final ToolItem fieldItem,
DropdownMenuProvider<FilterChangeAction> dropdown)
{
FilterChangeAction removeAction = new FilterChangeAction("Remove") {
@Override
public void runAction() {
fieldItem.dispose();
parent.layout(true, true);
}
};
removeAction.setImageDescriptor(InstaSearchPlugin.getImageDescriptor("close"));
dropdown.add(removeAction);
}
/**
* @param dropdown
*
*/
protected FilterChangeAction addAction(final DropdownMenuProvider<FilterChangeAction> dropdown, final String proposal)
{
FilterChangeAction action = new FilterChangeAction(proposal, getImageDescriptor())
{
@Override
public void runAction()
{
dropdown.getToolItem().setText(getText());
dropdown.getToolItem().setImage(getImage(proposal));
parent.layout(true, true);
}
};
dropdown.add(action);
return action;
}
}
private class ProjFilterAction extends FieldFilterAction
{
public ProjFilterAction(ImageDescriptor img) {
super(Field.PROJ, img);
}
@Override
protected FilterChangeAction addAction(
DropdownMenuProvider<FilterChangeAction> dropdown,
String proposal) {
FilterChangeAction action = super.addAction(dropdown, proposal);
if( proposal.equals(InstaSearchUI.getActiveProject()) ) // active project
action.run(); // add it
return action;
}
}
private class ExtensionFilterAction extends FieldFilterAction
{
public ExtensionFilterAction(ImageDescriptor img) {
super(Field.EXT, img);
}
@Override
protected Image getImage(String proposal) {
getExtImageDescriptor(proposal);
return imageRegistry.get(Field.EXT + ":" + proposal);
}
@Override
protected FilterChangeAction addAction(DropdownMenuProvider<FilterChangeAction> dropdown, String proposal) {
FilterChangeAction action = super.addAction(dropdown, proposal);
ImageDescriptor imgDesc = getExtImageDescriptor(proposal);
if( imgDesc != null )
action.setImageDescriptor(imgDesc);
IEditorPart editor = InstaSearchUI.getActiveEditor();
if( editor != null )
{
IAdaptable adaptable = editor.getEditorInput();
if( adaptable != null )
{
// get current edited file
IResource res = (IResource) adaptable.getAdapter(IResource.class);
if( res != null && proposal.equals(res.getFileExtension()))
action.run(); // set as active extension
}
}
return action;
}
}
private void updateSearch()
{
modifyListener.modifyText(null); // notify view
}
private void createSearchTextControl(Composite textComposite) {
searchText = new StyledText(textComposite, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.CANCEL);
textComposite.setTabList(new Control[]{searchText});
GridData searchTextGridData = new GridData(GridData.FILL, GridData.CENTER, true, false);
searchText.setLayoutData( searchTextGridData );
FocusListener focusListener = new FocusListener() { // to set search tip
public void focusGained(FocusEvent e)
{
if (searchText.isDisposed())
return;
if( isShowingSearchTip() )
{
searchText.setForeground( defaultSearchColor );
if( SEARCH_TEXT_TIP.equals(searchText.getText()) )
searchText.setText("");
}
}
public void focusLost(FocusEvent e)
{
if( searchText.getText().length() == 0 )
{
searchText.setForeground( searchTipColor );
searchText.setText(SEARCH_TEXT_TIP);
}
}
};
searchText.addFocusListener(focusListener);
searchText.setToolTipText(SEARCH_TEXT_TIP + "\n(" + PROPOSAL_KEYSTROKE + " for autocompletion)");
}
public boolean isShowingSearchTip()
{
return searchText.getForeground().equals( searchTipColor );
}
/**
* @return display
*
*/
private Display getDisplay() {
return Display.getCurrent();
}
public void setContentProposalAdapter(SearchContentProposalProvider provider)
{
this.proposalProvider = provider;
KeyStroke keyStroke = null;
try {
keyStroke = KeyStroke.getInstance(PROPOSAL_KEYSTROKE);
} catch(Exception e) {
InstaSearchPlugin.log(e);
}
char[] autoActivationCharacters = new char[]{':', ','};
ContentProposalAdapter adapter = new SearchContentProposalAdapter(
searchText, new StyledTextContentAdapter(),
provider, keyStroke, autoActivationCharacters );
adapter.setPopupSize(new Point(300, 150));
adapter.setPropagateKeys(true);
adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
}
private Image getPluginImage(String name)
{
Image img = imageRegistry.get(name);
if( img == null )
{
imageRegistry.put(name, InstaSearchPlugin.getImageDescriptor(name));
img = imageRegistry.get(name);
}
return img;
}
private ImageDescriptor getExtImageDescriptor(String ext)
{
String key = Field.EXT + ":" + ext;
ImageDescriptor fileImage = imageRegistry.getDescriptor(key);
if( fileImage == null ) {
IEditorRegistry registry = PlatformUI.getWorkbench().getEditorRegistry();
fileImage = registry.getImageDescriptor("*." + ext);
imageRegistry.put(key, fileImage); // cache created images for extensions
}
return fileImage;
}
public void widgetDisposed(DisposeEvent e) {
imageRegistry.dispose();
}
private class SearchContentProposalAdapter extends ContentProposalAdapter
{
/**
* @param control
* @param controlContentAdapter
* @param proposalProvider
* @param keyStroke
* @param autoActivationCharacters
*/
public SearchContentProposalAdapter(Control control,
IControlContentAdapter controlContentAdapter,
IContentProposalProvider proposalProvider, KeyStroke keyStroke,
char[] autoActivationCharacters)
{
super(control, controlContentAdapter, proposalProvider, keyStroke,
autoActivationCharacters);
}
@Override
protected void closeProposalPopup() { // workaround to keep popup open
String content = getControlContentAdapter().getControlContents(getControl());
int pos = getControlContentAdapter().getCursorPosition(getControl());
if( content.length() == 0 || pos == 0 ) {
super.closeProposalPopup();
return;
}
String prefix = content.substring(0, pos);
if( prefix.length() == 0 || prefix.endsWith(" ") ) {
super.closeProposalPopup();
return;
}
}
@Override
public int getProposalAcceptanceStyle() { // workaround to have the popup at insert positon
return ContentProposalAdapter.PROPOSAL_INSERT;
}
}
public Map<Field, Set<String>> getFilter()
{
HashMap<Field, Set<String>> filters = new HashMap<Field, Set<String>>();
for(ToolItem item: filterBar.getItems())
{
Object data = item.getData();
if( data== null ) continue;
if( data instanceof Field )
{
Field field = (Field) data;
String value = item.getText();
if( "".equals(value) ) continue;
Set<String> values = filters.get(field);
if( values == null ) {
values = new TreeSet<String>();
filters.put(field, values);
}
values.add(value);
}
}
return filters;
}
/**
* @param filter
*/
public void setFilter(Map<Field, Set<String>> filter)
{
clearFilter();
}
private void clearFilter()
{
for(ToolItem item: filterBar.getItems())
{
Object data = item.getData();
if( data== null ) continue;
if( data instanceof Field )
item.dispose();
}
}
}