content: /*
   * 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();
  		}
  	}
  }
  