Skip to main content
aboutsummaryrefslogblamecommitdiffstats
blob: b7f5417cc706804edd5b4f993b9f53fe8f0e6aae (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                                                                                
                                                       

                                                                        
                                                           

                                            


                                                                                 


                                                   


                                
                                                         

                                                  
                                        
                                  
                                         
                                  

                                                  


                                                         

                                                      
                                   
                                 




                                              

                                         
                        
                             
                                               



                                                                         
                                                                               
                                                                                  







































                                                                                                                   
                                                                          


                                                                                                                       
      
                                                                                          
                                                                                              
                                                                                          
                                                                                      
                                                                



                          
                                                                                                                                     
 





                                                                               
















                                                                                          








                                                                                                    
                
                                                                                                        



                                                           
                                                      


                                                             
                                             
                                












                                                               
                                             
                                           
 
                                      















                                                                                                                                                 










                                                                                                                                                 
         
 









                                                                                        
 
                                                                     

                                                                                     
 















                                                                                                                    
                
                                                          
                                                 


                                                         
                                                      




                                                                                                            





                                                 
                                                                                                        
                                              
                 








                                                                                                                               






                                                                                                                              

                            


                                                              
                                                                    








                                                            











                                                     
                                  





                                   
                                  









































                                                                                  

                                                                                                    









                                                                                
                                         
                              
                                                                                               














































































































                                                                                                                           
                                
                                                                                   









                                                                                                        

                                                              






                                                                    
                            
        




                                                                     

                                                                                                      
                                  



                                  








                                                                                  
                                                
         











                                                                                   

           
                                                                                   
                                                                                                                                  
                                                                                       

                     
                                                                               
                                                                  
 
                                                                 

                                                        

















                                                                                                          
                













                                                                                       

                                                                                                                                                     










                                                                                 


                                                                                       
                                                                



                                 


                                                                                       
                                                                

                                 
                  

                         
         
 











                                                                                            

                                                                                 






                                                                                                          







                                                                  
 
                                  
                                               
                                                                    
                                                                                   

                                                                                                              
                                                                     


                                                                                                          
                                                                    




                                                                                                  

                                                                             

                                                                     
                         





                                                                                                                












                                                                                                                   



                                                                                    





                                                                                             
                                                                  
                                                                                           



                                                                                                        
                                                                                   
                                                                                                                  


























                                                                                                        
                                                                                   


                                                                                    
                                                                                   











                                                                                
                                                                                                        
                                            
                                                                                                   





                                 






                                                                      


























                                                                                   
                                              




























                                                                                                         


                                                                                                       


                                                                                                     
                                                                                                      












                                                                                                         


                                                                                                       



                                                                                                     
                                                                                                               


























                                                                                                                    
                                                                                                   






                                                                               






                                                                  
                                                                 




                                      







                                                                                                              





                                                                                                                                  



                                                             
                                                                     



                                                     
                                                                     








                                                                                                                                        

                                                    





                                                                                    

                                                       






                                                        
                                                                                                              


                                                           
                                               







                                                                       
                                                                                                                   
                                

                     
                                                                                
                
                                      
 
                              
         



                                                    

                     
                                                               





                                                          
                
                                                                                      
                                   
                                                                                                        

                                                    

                 





                                                                                                                                                                                                                 
                                                                          






                                                                                                                                                                                                                    
                                                                             





                                                                                                                                                             
                                                                        
         




                                                                                        
                                          











                                                                                          
 



                                                                     

                                                                    


                                                                    
                                  





                                                                         

                                                                                


                                                                  
                                      

         
                        






                                                                        




                                                                                                                                     

                                                           




                                                                       





                                                                 
                                            
                                            
                                          









                                                                                           
         









                                                                      

           
                                                                        





                                                                                  
                     
           
                                      

                             












                                                                 






























                                                                           
                                                                    

                                                 










                                                                         























                                                                                    








                                                                  











































                                                                                                                                                             








                                                            
        

 
/*******************************************************************************
 * Copyright (c) 2000, 2007 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.compare; 

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.ResourceBundle;

import org.eclipse.compare.contentmergeviewer.IFlushable;
import org.eclipse.compare.internal.*;
import org.eclipse.compare.structuremergeviewer.*;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.*;
import org.eclipse.ui.part.*;
import org.eclipse.ui.services.IServiceLocator;


/**
 * A compare operation which can present its results in a special editor.
 * Running the compare operation and presenting the results in a compare editor
 * are combined in one class because it allows a client to keep the implementation
 * all in one place while separating it from the innards of a specific UI implementation of compare/merge.
 * <p> 
 * A <code>CompareEditorInput</code> defines methods for the following sequence steps:
 * <UL>
 * <LI>running a lengthy compare operation under progress monitor control,
 * <LI>creating a UI for displaying the model and initializing the some widgets with the compare result,
 * <LI>tracking the dirty state of the model in case of merge,
 * <LI>saving the model.
 * </UL>
 * The Compare plug-in's <code>openCompareEditor</code> method takes an <code>ICompareEditorInput</code>
 * and starts sequencing through the above steps. If the compare result is not empty a new compare editor
 * is opened and takes over the sequence until eventually closed.
 * <p>
 * The <code>prepareInput</code> method should contain the
 * code of the compare operation. It is executed under control of a progress monitor
 * and can be canceled. If the result of the compare is not empty, that is if there are differences
 * that needs to be presented, the <code>ICompareEditorInput</code> should hold onto them and return them with
 * the <code>getCompareResult</code> method.
 * If the value returned from <code>getCompareResult</code> is not <code>null</code>
 * a compare editor is opened on the <code>ICompareEditorInput</code> with title and title image initialized by the
 * corresponding methods of the <code>ICompareEditorInput</code>.
 * <p>
 * Creation of the editor's SWT controls is delegated to the <code>createContents</code> method.
 * Here the SWT controls must be created and initialized  with the result of the compare operation.
 * <p>
 * If merging is allowed, the modification state of the compared constituents must be tracked and the dirty
 * state returned from method <code>isSaveNeeded</code>. The value <code>true</code> triggers a subsequent call
 * to <code>save</code> where the modified resources can be saved.
 * <p>
 * The most important part of this implementation is the setup of the compare/merge UI.
 * The UI uses a simple browser metaphor to present compare results.
 * The top half of the layout shows the structural compare results (e.g. added, deleted, and changed files),
 * the bottom half the content compare results (e.g. textual differences between two files).
 * A selection in the top pane is fed to the bottom pane. If a content viewer is registered
 * for the type of the selected object, this viewer is installed in the pane.
 * In addition if a structure viewer is registered for the selection type the top pane
 * is split horizontally to make room for another pane and the structure viewer is installed
 * in it. When comparing Java files this second structure viewer would show the structural
 * differences within a Java file, e.g. added, deleted or changed methods and fields.
 * <p>
 * Subclasses provide custom setups, e.g. for a Catch-up/Release operation
 * by passing a subclass of <code>CompareConfiguration</code> and by implementing the <code>prepareInput</code> method.
 * If a subclass cannot use the <code>DiffTreeViewer</code> which is installed by default in the
 * top left pane, method <code>createDiffViewer</code> can be overridden.
 * <p>
 * If subclasses of this class implement {@link ISaveablesSource}, the compare editor will
 * pass these models through to the workbench. The editor will still show the dirty indicator 
 * if one of these underlying models is dirty. It is the responsibility of subclasses that
 * implement this interface to call {@link #setDirty(boolean)} when the dirty state of
 * any of the models managed by the subclass change dirty state.
 * 
 * @see CompareUI
 * @see CompareEditorInput
 */
public abstract class CompareEditorInput implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress, ICompareContainer {

	private static final boolean DEBUG= false;

	/**
	 * The name of the "dirty" property (value <code>"DIRTY_STATE"</code>).
	 */
	public static final String DIRTY_STATE= "DIRTY_STATE"; //$NON-NLS-1$
	
	/**
	 * The name of the "title" property. This property is fired when the title
	 * of the compare input changes. Clients should also re-obtain the tool tip
	 * when this property changes.
	 * @see #getTitle()
	 * @since 3.3
	 */
	public static final String PROP_TITLE= ICompareUIConstants.PROP_TITLE;
	
	/**
	 * The name of the "title image" property. This property is fired when the title
	 * image of the compare input changes.
	 * @see #getTitleImage()
	 * @since 3.3
	 */
	public static final String PROP_TITLE_IMAGE= ICompareUIConstants.PROP_TITLE_IMAGE;
	
	/**
	 * The name of the "selected edition" property. This property is fired when the selected
	 * edition of the compare input changes.
	 * @see #isEditionSelectionDialog()
	 * @see #getSelectedEdition()
	 * @since 3.3
	 */
	public static final String PROP_SELECTED_EDITION= ICompareUIConstants.PROP_SELECTED_EDITION;
		
	private static final String COMPARE_EDITOR_IMAGE_NAME= "eview16/compare_view.gif"; //$NON-NLS-1$
	private static Image fgTitleImage;
	
	private Splitter fComposite;
	private CompareConfiguration fCompareConfiguration;
	private CompareViewerPane fStructureInputPane;
	private CompareViewerSwitchingPane fStructurePane1;
	private CompareViewerSwitchingPane fStructurePane2;
	private CompareViewerSwitchingPane fContentInputPane;
	private CompareViewerPane fFocusPane;
	private String fMessage;
	private Object fInput;
	private String fTitle;
	private ListenerList fListenerList= new ListenerList();
	private CompareNavigator fNavigator;
	private boolean fDirty= false;
	private ArrayList fDirtyViewers= new ArrayList();
	private IPropertyChangeListener fDirtyStateListener;

	private IgnoreWhiteSpaceAction fIgnoreWhitespace;
	private ShowPseudoConflicts fShowPseudoConflicts;
	
	boolean fStructureCompareOnSingleClick= true;

	private ICompareContainer fContainer;
	private boolean fContainerProvided;

	private String fHelpContextId;
	private InternalOutlineViewerCreator fOutlineView;
	
	private class InternalOutlineViewerCreator extends OutlineViewerCreator {
		public Viewer findStructureViewer(Viewer oldViewer,
				ICompareInput input, Composite parent,
				CompareConfiguration configuration) {
			if (fContentInputPane != null) {
				Viewer v = fContentInputPane.getViewer();
				if (v != null) {
					OutlineViewerCreator creator = (OutlineViewerCreator)Utilities.getAdapter(v, OutlineViewerCreator.class);
					if (creator != null)
						return creator.findStructureViewer(oldViewer, input, parent, configuration);
				}
			}
			return null;
		}

		public boolean hasViewerFor(Object input) {
			if (fContentInputPane != null) {
				Viewer v = fContentInputPane.getViewer();
				if (v != null) {
					OutlineViewerCreator creator = (OutlineViewerCreator)Utilities.getAdapter(v, OutlineViewerCreator.class);
					return (creator != null);
				}
			}
			return false;
		}
	}

	/**
	 * Creates a <code>CompareEditorInput</code> which is initialized with the given
	 * compare configuration.
	 * The compare configuration is passed to subsequently created viewers.
	 *
	 * @param configuration the compare configuration 
	 */
	public CompareEditorInput(CompareConfiguration configuration) {
		fCompareConfiguration= configuration;
		Assert.isNotNull(configuration);

		ResourceBundle bundle= CompareUI.getResourceBundle();
		fIgnoreWhitespace= new IgnoreWhiteSpaceAction(bundle, configuration);
		fShowPseudoConflicts= new ShowPseudoConflicts(bundle, configuration);

		fDirtyStateListener= new IPropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent e) {
				String propertyName= e.getProperty();
				if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) {
					boolean changed= false;
					Object newValue= e.getNewValue();
					if (newValue instanceof Boolean)
						changed= ((Boolean)newValue).booleanValue();
					setDirty(e.getSource(), changed);
				}			
			}
		};

		IPreferenceStore ps= configuration.getPreferenceStore();
		if (ps != null)
			fStructureCompareOnSingleClick= ps.getBoolean(ComparePreferencePage.OPEN_STRUCTURE_COMPARE);
		
		fContainer = configuration.getContainer();
		configuration.setContainer(this);
	}
	
	private boolean structureCompareOnSingleClick() {
		return fStructureCompareOnSingleClick;
	}
	
	private boolean isShowStructureInOutlineView() {
		Object object= getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW);
		return (object instanceof Boolean && ((Boolean)object).booleanValue());
	}
		
	/* (non Javadoc)
	 * see IAdaptable.getAdapter
	 */
	public Object getAdapter(Class adapter) {
		if (ICompareNavigator.class.equals(adapter) || CompareNavigator.class.equals(adapter)) {
			return getNavigator();
		}
		if (adapter == IShowInSource.class) {
			final IFile file = (IFile)Utilities.getAdapter(this, IFile.class);
			if (file != null)
				return new IShowInSource() {
					public ShowInContext getShowInContext() {
						return new ShowInContext(new FileEditorInput(file), StructuredSelection.EMPTY);
					}
				};
		}
		if (adapter == OutlineViewerCreator.class && getCompareResult() != null && !hasChildren(getCompareResult())) {
			synchronized (this) {
				if (fOutlineView == null)
					fOutlineView = new InternalOutlineViewerCreator();
				return fOutlineView;
			}
		}
		return null;
	}

	public synchronized ICompareNavigator getNavigator() {
		if (fNavigator == null)
			fNavigator= new CompareEditorInputNavigator(
				new Object[] {
					fStructureInputPane,
					fStructurePane1,
					fStructurePane2,
					fContentInputPane
				}
			);
		return fNavigator;
	}
	
	/* (non Javadoc)
	 * see IEditorInput.getImageDescriptor
	 */
	public ImageDescriptor getImageDescriptor() {
		return null;
	}
	
	/* (non Javadoc)
	 * see IEditorInput.getToolTipText
	 */
	public String getToolTipText() {
		return getTitle();
	}
	
	/* (non Javadoc)
	 * see IEditorInput.getName
	 */
	public String getName() {
		return getTitle();
	}
			
	/**
	 * Returns <code>null</code> since this editor cannot be persisted.
	 *
	 * @return <code>null</code> because this editor cannot be persisted
	 */
	public IPersistableElement getPersistable() {
		return null;
	}
		
	/**
	 * Returns <code>false</code> to indicate that this input
	 * should not appear in the "File Most Recently Used" menu.
	 *
	 * @return <code>false</code>
	 */
	public boolean exists() {
		return false;
	}
	
	/*
	 * FIXME!
 	 */
	protected void setMessage(String message) {
		fMessage= message;
	}
	
	/*
	 * FIXME!
 	 */
	public String getMessage() {
		return fMessage;
	}
				
	/**
	 * Returns the title which will be used in the compare editor's title bar.
	 * It can be set with <code>setTitle</code>.
	 *
	 * @return the title
	 */
	public String getTitle() {
		if (fTitle == null)
			return Utilities.getString("CompareEditorInput.defaultTitle"); //$NON-NLS-1$
		return fTitle;
	}
	
	/**
	 * Sets the title which will be used when presenting the compare result.
	 * This method must be called before the editor is opened.
	 * 
	 * @param title the title to use for the CompareEditor
	 */
	public void setTitle(String title) {
		String oldTitle = fTitle;
		fTitle= title;
		Utilities.firePropertyChange(fListenerList, this, PROP_TITLE, oldTitle, title);
	}
	
	/**
	 * Returns the title image which will be used in the compare editor's title bar.
	 * Returns the title image which will be used when presenting the compare result.
	 * This implementation returns a generic compare icon.
	 * Subclasses can override.
	 *
	 * @return the title image, or <code>null</code> if none
	 */
	public Image getTitleImage() {
		if (fgTitleImage == null) {
			fgTitleImage= CompareUIPlugin.getImageDescriptor(COMPARE_EDITOR_IMAGE_NAME).createImage();
			CompareUI.disposeOnShutdown(fgTitleImage);
		}
		return fgTitleImage;
	}
	
	/**
	 * Returns the configuration object for the viewers within the compare editor.
	 * Returns the configuration which was passed to the constructor.
	 *
	 * @return the compare configuration
	 */
	public CompareConfiguration getCompareConfiguration() {
		return fCompareConfiguration;
	}

	/**
	 * Adds standard actions to the given <code>ToolBarManager</code>.
	 * <p>
	 * Subclasses may override to add their own actions.
	 * </p>
	 *
	 * @param toolBarManager the <code>ToolBarManager</code> to which to contribute
	 */
	public void contributeToToolBar(ToolBarManager toolBarManager) {
		
		toolBarManager.add(new Separator());
		toolBarManager.add(fIgnoreWhitespace);
		toolBarManager.add(fShowPseudoConflicts);
	}
	
	/**
	 * Runs the compare operation and stores the compare result.
	 *
	 * @param monitor the progress monitor to use to display progress and receive
	 *   requests for cancelation
	 * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
	 * 	it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
	 *  wrapped in an <code>InvocationTargetException</code> by the calling context
	 * @exception InterruptedException if the operation detects a request to cancel, 
	 *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing 
	 *  <code>InterruptedException</code>
	 */
	public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
		fInput= prepareInput(monitor);
	}

	/**
	 * Runs the compare operation and returns the compare result.
	 * If <code>null</code> is returned no differences were found and no compare editor needs to be opened.
	 * Progress should be reported to the given progress monitor.
	 * A request to cancel the operation should be honored and acknowledged 
	 * by throwing <code>InterruptedException</code>.
	 * <p>
	 * Note: this method is typically called in a modal context thread which doesn't have a Display assigned.
	 * Implementors of this method shouldn't therefore allocated any SWT resources in this method.
	 * </p>
	 *
	 * @param monitor the progress monitor to use to display progress and receive
	 *   requests for cancelation
	 * @return the result of the compare operation, or <code>null</code> if there are no differences
	 * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
	 * 	it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
	 *  wrapped in an <code>InvocationTargetException</code> by the calling context
	 * @exception InterruptedException if the operation detects a request to cancel, 
	 *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing 
	 *  <code>InterruptedException</code>
	 */
	protected abstract Object prepareInput(IProgressMonitor monitor)
				throws InvocationTargetException, InterruptedException;
	 
	/**
	 * Returns the compare result computed by the most recent call to the
	 * <code>run</code> method. Returns <code>null</code> if no
	 * differences were found.
	 *
	 * @return the compare result prepared in method <code>prepareInput</code>
	 *   or <code>null</code> if there were no differences
	 */
	public Object getCompareResult() {
		return fInput;
	}
	
	/**
	 * Create the SWT controls that are used to display the result of the compare operation.
	 * Creates the SWT Controls and sets up the wiring between the individual panes.
	 * This implementation creates all four panes but makes only the necessary ones visible.
	 * Finally it feeds the compare result into the top left structure viewer
	 * and the content viewer.
	 * <p>
	 * Subclasses may override if they need to change the layout or wiring between panes.
	 *
	 * @param parent the parent control under which the control must be created
	 * @return the SWT control hierarchy for the compare editor
	 */
	public Control createContents(Composite parent) {

		fComposite= new Splitter(parent, SWT.VERTICAL);
		fComposite.setData(this);
				
		Control outline= createOutlineContents(fComposite, SWT.HORIZONTAL);
					
		fContentInputPane= new CompareViewerSwitchingPane(fComposite, SWT.BORDER | SWT.FLAT) {
			protected Viewer getViewer(Viewer oldViewer, Object input) {
				if (input instanceof ICompareInput)
					return findContentViewer(oldViewer, (ICompareInput)input, this);
				return null;
			}
		};
		if (fFocusPane == null)
			fFocusPane= fContentInputPane;
		if (outline != null)
			fComposite.setVisible(outline, false);
		fComposite.setVisible(fContentInputPane, true);
		
		if (fStructureInputPane != null)
			fComposite.setWeights(new int[] { 30, 70 });
		
		fComposite.layout();

		feedInput();
	
		fComposite.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				handleDispose();
			}
		});
		if (fHelpContextId != null)
			PlatformUI.getWorkbench().getHelpSystem().setHelp(fComposite, fHelpContextId);
		contentsCreated();
		return fComposite;
	}
	
	/**
	 * Callback that occurs when the UI associated with this compare editor
	 * input is disposed. This method will only be invoked if the UI has been
	 * created (i.e. after the call to {@link #createContents(Composite)}.
	 * Subclasses can extend this method but ensure that the overridden method
	 * is invoked.
	 * 
	 * @since 3.3
	 */
	protected void handleDispose() {
		fCompareConfiguration.dispose();
	}
	
	/**
	 * Callback that occurs after the control for the input has
	 * been created. If this method gets invoked then {@link #handleDispose()}
	 * will be invoked when the control is disposed. Subclasses may extend this
	 * method to register any listeners that need to be de-registered when the
	 * input is disposed.
	 * @since 3.3
	 */
	protected void contentsCreated() {
		// Default is to do nothing
	}

	/**
	 * @param parent the parent control under which the control must be created
	 * @param direction the layout direction of the contents, either </code>SWT.HORIZONTAL<code> or </code>SWT.VERTICAL<code> 
	 * @return the SWT control hierarchy for the outline part of the compare editor
	 * @since 3.0
	 */
	public Control createOutlineContents(Composite parent, int direction) {
		final Splitter h= new Splitter(parent, direction);

		fStructureInputPane= createStructureInputPane(h);
		if (hasChildren(getCompareResult()))
			fFocusPane= fStructureInputPane;
		
		fStructurePane1= new CompareViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true) {
			protected Viewer getViewer(Viewer oldViewer, Object input) {
				if (input instanceof ICompareInput)
					return findStructureViewer(oldViewer, (ICompareInput)input, this);
				return null;
			}
		};
		h.setVisible(fStructurePane1, false);
		
		fStructurePane2= new CompareViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true) {
			protected Viewer getViewer(Viewer oldViewer, Object input) {
				if (input instanceof ICompareInput)
					return findStructureViewer(oldViewer, (ICompareInput)input, this);
				return null;
			}
		};
		h.setVisible(fStructurePane2, false);
		
		// setup the wiring for top left pane
		fStructureInputPane.addOpenListener(
			new IOpenListener() {
				public void open(OpenEvent oe) {
					feed1(oe.getSelection());
				}
			}
		);
		fStructureInputPane.addSelectionChangedListener(
			new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent e) {
					ISelection s= e.getSelection();
					if (s == null || s.isEmpty())
						feed1(s);
					if (isEditionSelectionDialog())
						firePropertyChange(new PropertyChangeEvent(this, PROP_SELECTED_EDITION, null, getSelectedEdition()));
				}
			}
		);
		fStructureInputPane.addDoubleClickListener(
			new IDoubleClickListener() {
				public void doubleClick(DoubleClickEvent event) {
					feedDefault1(event.getSelection());
				}
			}
		);
		
		fStructurePane1.addSelectionChangedListener(
			new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent e) {
					feed2(e.getSelection());
				}
			}
		);

		fStructurePane2.addSelectionChangedListener(
			new ISelectionChangedListener() {
				public void selectionChanged(SelectionChangedEvent e) {
					feed3(e.getSelection());
				}
			}
		);

		return h;
	}

	/**
	 * Create the pane that will contain the structure input pane (upper left).
	 * By default, a {@link CompareViewerSwitchingPane} is returned. Subclasses
	 * may override to provide an alternate pane.
	 * @param parent the parent composite
	 * @return the structure input pane
	 * @since 3.3
	 */
	protected CompareViewerPane createStructureInputPane(
			final Composite parent) {
		return new CompareViewerSwitchingPane(parent, SWT.BORDER | SWT.FLAT, true) {
			protected Viewer getViewer(Viewer oldViewer, Object input) {
				if (CompareEditorInput.this.hasChildren(input)) {
					return createDiffViewer(this);
				}
				if (input instanceof ICompareInput)
					return findStructureViewer(oldViewer, (ICompareInput)input, this);
				return null;
			}
		};
	}
	
	/* private */ boolean hasChildren(Object input) {
		if (input instanceof IDiffContainer) {
			IDiffContainer dn= (IDiffContainer) input;
			return (dn.hasChildren());
		}
		return false;
	}

	private void feedInput() {
		if (fStructureInputPane != null
				&& (fInput instanceof ICompareInput 
						|| isCustomStructureInputPane())) {
			if (hasChildren(fInput) || isCustomStructureInputPane()) {
				// The input has multiple entries so set the input of the structure input pane
				fStructureInputPane.setInput(fInput);
			} else if (!structureCompareOnSingleClick() || isShowStructureInOutlineView()) {
				// We want to avoid showing the structure in the editor if we can so first
				// we'll set the content pane to see if we need to provide a structure
				internalSetContentPaneInput(fInput);
				// If the content viewer is unusable
				if (hasUnusableContentViewer() 
						|| (structureCompareOnSingleClick() 
								&& isShowStructureInOutlineView() 
								&& !hasOutlineViewer(fInput))) {
					fStructureInputPane.setInput(fInput);
				}
			} else {
				fStructureInputPane.setInput(fInput);
			}
			ISelection sel= fStructureInputPane.getSelection();
			if (sel == null || sel.isEmpty())
				feed1(sel);	// we only feed downstream viewers if the top left pane is empty
		}
	}

	private boolean hasOutlineViewer(Object input) {
		if (!isShowStructureInOutlineView())
			return false;
		OutlineViewerCreator creator = (OutlineViewerCreator)getAdapter(OutlineViewerCreator.class);
		if (creator != null)
			return creator.hasViewerFor(input);
		return false;
	}

	private boolean hasUnusableContentViewer() {
		return fContentInputPane.isEmpty() || fContentInputPane.getViewer() instanceof BinaryCompareViewer;
	}
	
	private boolean isCustomStructureInputPane() {
		return !(fStructureInputPane instanceof CompareViewerSwitchingPane);
	}

	private void feed1(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			new Runnable() {
				public void run() {
					if (selection == null || selection.isEmpty()) {
						Object input= fStructureInputPane.getInput();
						if (input != null)
							internalSetContentPaneInput(input);
						fStructurePane2.setInput(null); // clear downstream pane
						fStructurePane1.setInput(null);
					} else {
						Object input= getElement(selection);
						internalSetContentPaneInput(input);
						if (structureCompareOnSingleClick() || hasUnusableContentViewer())
							fStructurePane1.setInput(input);
						fStructurePane2.setInput(null); // clear downstream pane
						if (fStructurePane1.getInput() != input)
							fStructurePane1.setInput(null);
					}
				}
			}
		);
	}
	
	private void feedDefault1(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			new Runnable() {
				public void run() {
					if (!selection.isEmpty())
						fStructurePane1.setInput(getElement(selection));
				}
			}
		);
	}
	
	private void feed2(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			new Runnable() {
				public void run() {
					if (selection.isEmpty()) {
						Object input= fStructurePane1.getInput();
						internalSetContentPaneInput(input);
						fStructurePane2.setInput(null);
					} else {
						Object input= getElement(selection);
						internalSetContentPaneInput(input);
						fStructurePane2.setInput(input);
					}
				}
			}
		);
	}
	
	private void feed3(final ISelection selection) {
		BusyIndicator.showWhile(fComposite.getDisplay(),
			new Runnable() {
				public void run() {
					if (selection.isEmpty())
						internalSetContentPaneInput(fStructurePane2.getInput());
					else
						internalSetContentPaneInput(getElement(selection));
				}
			}
		);
		
	}
	
	private void internalSetContentPaneInput(Object input) {
		Object oldInput = fContentInputPane.getInput();
		fContentInputPane.setInput(input);
		if (fOutlineView != null)
			fOutlineView.fireInputChange(oldInput, input);
	}
	
	/**
	 * Returns the first element of the given selection if the selection 
	 * is a <code>IStructuredSelection</code> with exactly one element. Returns
	 * <code>null</code> otherwise.
	 *
	 * @param selection the selection
	 * @return the first element of the selection, or <code>null</code>
	 */
	private static Object getElement(ISelection selection) {
		if (selection instanceof IStructuredSelection) {
			IStructuredSelection ss= (IStructuredSelection) selection;
			if (ss.size() == 1)
				return ss.getFirstElement();
		}
		return null;
	}
	
	/**
	 * Asks this input to take focus within its container (editor).
	 * <p>
	 * Clients should not call this method but they may
	 * override if they implement a different layout with different visual
	 * components. Clients are free to call the inherited method.
	 * </p>
	 */
	public void setFocus() {
		if (fFocusPane != null) {
			fFocusPane.setFocus();
		} else if (fComposite != null)
			fComposite.setFocus();
	}
	
	/**
	 * Factory method for creating a differences viewer for the top left pane.
	 * It is called from <code>createContents</code> and returns a <code>DiffTreeViewer</code>.
	 * <p>
	 * Subclasses may override if they need a different viewer.
	 * </p>
	 *
	 * @param parent the SWT parent control under which to create the viewer's SWT controls
	 * @return a compare viewer for the top left pane
	 */
	public Viewer createDiffViewer(Composite parent) {
		return new DiffTreeViewer(parent, fCompareConfiguration);
	}

	/**
	 * Implements the dynamic viewer switching for structure viewers.
	 * The method must return a compare viewer based on the old (or current) viewer
	 * and a new input object. If the old viewer is suitable for showing the new input the old viewer
	 * can be returned. Otherwise a new viewer must be created under the given parent composite or
	 * <code>null</code> can be returned to indicate that no viewer could be found.
	 * <p>
	 * This implementation forwards the request to <code>CompareUI.findStructureViewer</code>.
	 * <p>
	 * Subclasses may override to implement a different strategy.
	 * </p>
	 * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
	 * @param input the input object for which to find a structure viewer
	 * @param parent the SWT parent composite under which the new viewer is created
	 * @return a compare viewer which is suitable for the given input object or <code>null</code>
	 */
	public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) {
		return CompareUI.findStructureViewer(oldViewer, input, parent, fCompareConfiguration);
	}

	/**
	 * Implements the dynamic viewer switching for content viewers.
	 * The method must return a compare viewer based on the old (or current) viewer
	 * and a new input object. If the old viewer is suitable for showing the new input the old viewer
	 * can be returned. Otherwise a new viewer must be created under the given parent composite or
	 * <code>null</code> can be returned to indicate that no viewer could be found.
	 * <p>
	 * This implementation forwards the request to <code>CompareUI.findContentViewer</code>.
	 * <p>
	 * Subclasses may override to implement a different strategy.
	 * </p>
	 * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
	 * @param input the input object for which to find a structure viewer
	 * @param parent the SWT parent composite under which the new viewer is created
	 * @return a compare viewer which is suitable for the given input object or <code>null</code>
	 */
	public Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent) {

		Viewer newViewer= CompareUI.findContentViewer(oldViewer, input, parent, fCompareConfiguration);
		
		boolean isNewViewer= newViewer != oldViewer;
		if (DEBUG) System.out.println("CompareEditorInput.findContentViewer: " + isNewViewer); //$NON-NLS-1$
		
		if (isNewViewer && newViewer instanceof IPropertyChangeNotifier) {
			final IPropertyChangeNotifier dsp= (IPropertyChangeNotifier) newViewer;
			dsp.addPropertyChangeListener(fDirtyStateListener);
			
			Control c= newViewer.getControl();
			c.addDisposeListener(
				new DisposeListener() {
					public void widgetDisposed(DisposeEvent e) {
						dsp.removePropertyChangeListener(fDirtyStateListener);
					}
				}
			);
		}
		
		return newViewer;
	}
	
	/**
	 * Returns <code>true</code> if there are unsaved changes.
	 * The value returned is the value of the <code>DIRTY_STATE</code> property of this input object.
	 
	 * Returns <code>true</code> if this input has unsaved changes,
	 * that is if <code>setDirty(true)</code> has been called.
	 * Subclasses don't have to override if the functionality provided by <code>setDirty</code>
	 * is sufficient.
	 *
	 * @return <code>true</code> if there are changes that need to be saved
	 */
	public boolean isSaveNeeded() {
		return fDirty || fDirtyViewers.size() > 0;
	}
	
	/**
	 * Returns <code>true</code> if there are unsaved changes.
	 * The method should be called by any parts or dialogs
	 * that contain the input.
	 * By default, this method calls {@link #isSaveNeeded()} 
	 * but subclasses may extend.
	 * @return <code>true</code> if there are unsaved changes
	 * @since 3.3
	 */
	public boolean isDirty() {
		return isSaveNeeded();
	}
		
	/**
	 * Sets the dirty state of this input to the given
	 * value and sends out a <code>PropertyChangeEvent</code> if the new value differs from the old value.
	 *
	 * @param dirty the dirty state for this compare input
	 */
	public void setDirty(boolean dirty) {
		boolean oldDirty = fDirty || fDirtyViewers.size() > 0;
		fDirty= dirty;
		if (!fDirty)
			fDirtyViewers.clear();
		if (oldDirty != dirty)
			Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, new Boolean(oldDirty), new Boolean(dirty));
	}
	
	private void setDirty(Object source, boolean dirty) {
		Assert.isNotNull(source);
		boolean oldDirty= fDirty || fDirtyViewers.size() > 0;
		if (dirty)
			fDirtyViewers.add(source);
		else
			fDirtyViewers.remove(source);
		boolean newDirty= fDirty || fDirtyViewers.size() > 0;
		if (DEBUG) System.out.println("setDirty("+source+", "+dirty+"): " + newDirty); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		if (oldDirty != newDirty)
			Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, new Boolean(oldDirty), new Boolean(newDirty));
	}	
	
	/* (non Javadoc)
	 * see IPropertyChangeNotifier.addListener
	 */
	public void addPropertyChangeListener(IPropertyChangeListener listener) {
		if (listener != null)
			fListenerList.add(listener);
	}

	/* (non Javadoc)
	 * see IPropertyChangeNotifier.removeListener
	 */
	public void removePropertyChangeListener(IPropertyChangeListener listener) {
		if (listener != null)
			fListenerList.remove(listener);
	}

	/**
	 * Save any unsaved changes.
	 * Empty implementation.
	 * Subclasses must override to save any changes.
	 *
	 * @param pm an <code>IProgressMonitor</code> that the implementation of save may use to show progress
	 * @deprecated Override method saveChanges instead.
	 */
	public void save(IProgressMonitor pm) {
		// empty default implementation
	}
	
	/**
	 * Save any unsaved changes.
	 * Subclasses must override to save any changes.
	 * This implementation tries to flush changes in all viewers by
	 * calling <code>ISavable.save</code> on them.
	 *
	 * @param monitor an <code>IProgressMonitor</code> that the implementation of save may use to show progress
	 * @throws CoreException
	 * @since 2.0
	 */
	public void saveChanges(IProgressMonitor monitor) throws CoreException {
		
		flushViewers(monitor);

		save(monitor);
	}

	/**
	 * Flush the viewer contents into the input.
	 * @param monitor a progress monitor
	 * @since 3.3
	 */
	protected void flushViewers(IProgressMonitor monitor) {
		// flush changes in any dirty viewer
		flushViewer(fStructureInputPane, monitor);
		flushViewer(fStructurePane1, monitor);
		flushViewer(fStructurePane2, monitor);
		flushViewer(fContentInputPane, monitor);
	}
		
	private static void flushViewer(CompareViewerPane pane, IProgressMonitor pm) {
		if (pane != null) {
			IFlushable flushable = (IFlushable)Utilities.getAdapter(pane, IFlushable.class);
			if (flushable != null)
				flushable.flush(pm);
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.compare.ICompareContainer#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
	 */
	public void addCompareInputChangeListener(ICompareInput input,
			ICompareInputChangeListener listener) {
		fContainer.addCompareInputChangeListener(input, listener);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.compare.ICompareContainer#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
	 */
	public void removeCompareInputChangeListener(ICompareInput input,
			ICompareInputChangeListener listener) {
		fContainer.removeCompareInputChangeListener(input, listener);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.compare.ICompareContainer#registerContextMenu(org.eclipse.jface.action.MenuManager, org.eclipse.jface.viewers.ISelectionProvider)
	 */
	public void registerContextMenu(MenuManager menu, ISelectionProvider selectionProvider) {
		fContainer.registerContextMenu(menu, selectionProvider);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String)
	 */
	public void setStatusMessage(String message) {
		if (!fContainerProvided) {
			// Try the action bars directly
			IActionBars actionBars= getActionBars();
			if (actionBars != null) {
				IStatusLineManager slm= actionBars.getStatusLineManager();
				if (slm != null) {
					slm.setMessage(message);
				}
			}
		} else {
			fContainer.setStatusMessage(message);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.compare.ICompareContainer#getActionBars()
	 */
	public IActionBars getActionBars() {
		IActionBars actionBars = fContainer.getActionBars();
		if (actionBars == null && !fContainerProvided) {
			// The old way to find the action bars
			return Utilities.findActionBars(fComposite);
		}
		return actionBars;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.compare.ICompareContainer#getServiceLocator()
	 */
	public IServiceLocator getServiceLocator() {
		IServiceLocator serviceLocator = fContainer.getServiceLocator();
		if (serviceLocator == null && !fContainerProvided) {
			// The old way to find the service locator
			return Utilities.findSite(fComposite);
		}
		return serviceLocator;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.compare.ICompareContainer#getWorkbenchPart()
	 */
	public IWorkbenchPart getWorkbenchPart() {
		return fContainer.getWorkbenchPart();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.operation.IRunnableContext#run(boolean, boolean, org.eclipse.jface.operation.IRunnableWithProgress)
	 */
	public void run(boolean fork, boolean cancelable,
			IRunnableWithProgress runnable) throws InvocationTargetException,
			InterruptedException {
		fContainer.run(fork, cancelable, runnable);
	}
	
	public void runAsynchronously(IRunnableWithProgress runnable) {
		fContainer.runAsynchronously(runnable);
	}
	
	/**
	 * Set the container of this input to the given container
	 * @param container the container
	 * @since 3.3
	 */
	public void setContainer(ICompareContainer container) {
		Assert.isNotNull(container);
		this.fContainer = container;
		fContainerProvided = true;
	}

	/**
	 * Return the container of this input or <code>null</code> if there is no container
	 * set. 
	 * @return the container of this input or <code>null</code>
	 * @since 3.3
	 */
	public final ICompareContainer getContainer() {
		return fContainer;
	}
	
	/**
	 * Fire the given property change event to all listeners 
	 * registered with this compare editor input.
	 * @param event the property change event
	 * @since 3.3
	 */
	protected void firePropertyChange(PropertyChangeEvent event) {
		Utilities.firePropertyChange(fListenerList, event);
	}
	
	/**
	 * Return whether this compare editor input can be run as a job.
	 * By default, <code>false</code> is returned since traditionally inputs
	 * were prepared in the foreground (i.e the UI was blocked when the 
	 * {@link #run(IProgressMonitor)} method (and indirectly the 
	 * {@link #prepareInput(IProgressMonitor)} method) was invoked. Subclasses
	 * may override.
	 * @return whether this compare editor input can be run in the background
	 * @since 3.3
	 */
	public boolean canRunAsJob() {
		return false;
	}

	/**
	 * Return whether this input belongs to the given family
	 * when it is run as a job.
	 * @see #canRunAsJob()
	 * @see Job#belongsTo(Object)
	 * @param family the job family
	 * @return whether this input belongs to the given family
	 * @since 3.3
	 */
	public boolean belongsTo(Object family) {
		return family == this;
	}
	
	/**
	 * Return whether this input is intended to be used to select
	 * a particular edition of an element in a dialog. The result
	 * of this method is only consider if neither sides of the
	 * input are editable. By default, <code>false</code> is returned.
	 * @return whether this input is intended to be used to select
	 * a particular edition of an element in a dialog
	 * @see #getOKButtonLabel()
	 * @see #okPressed()
	 * @see #getSelectedEdition()
	 * @since 3.3
	 */
	public boolean isEditionSelectionDialog() {
		return false;
	}
	
	/**
	 * Return the label to be used for the <code>OK</code>
	 * button when this input is displayed in a dialog.
	 * By default, different labels are used depending on
	 * whether the input is editable or is for edition selection
	 * (see {@link #isEditionSelectionDialog()}.
	 * @return the label to be used for the <code>OK</code>
	 * button when this input is displayed in a dialog
	 * @since 3.3
	 */
	public String getOKButtonLabel() {
		if (isEditable())
			return CompareMessages.CompareDialog_commit_button;
		if (isEditionSelectionDialog())
			return CompareMessages.CompareEditorInput_0;
		return IDialogConstants.OK_LABEL;
	}
	
	/**
	 * Return the label used for the <code>CANCEL</code>
	 * button when this input is shown in a compare dialog
	 * using {@link CompareUI#openCompareDialog(CompareEditorInput)}.
	 * @return the label used for the <code>CANCEL</code> button
	 * @since 3.3
	 */
	public String getCancelButtonLabel() {
		return IDialogConstants.CANCEL_LABEL;
	}

	private boolean isEditable() {
		return getCompareConfiguration().isLeftEditable() 
			|| getCompareConfiguration().isRightEditable();
	}
	
	/**
	 * The <code>OK</code> button was pressed in a dialog. If one or both of
	 * the sides of the input is editable then any changes will be saved. If the
	 * input is for edition selection (see {@link #isEditionSelectionDialog()}),
	 * it is up to subclasses to override this method in order to perform the
	 * appropriate operation on the selected edition.
	 * 
	 * @return whether the dialog should be closed or not.
	 * @since 3.3
	 */
	public boolean okPressed() {
		if (isEditable()) {
			if (!saveChanges())
				return false;
		}
		return true;
	}
	
	/**
	 * The <code>CANCEL</code> button was pressed in a dialog.
	 * By default, nothing is done. Subclasses may override.
	 * @since 3.3
	 */
	public void cancelPressed() {
		// Do nothing
	}
	
	private boolean saveChanges() {
		try {
			PlatformUI.getWorkbench().getProgressService().run(true, true, new IRunnableWithProgress() {
				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
					try {
						saveChanges(monitor);
					} catch (CoreException e) {
						throw new InvocationTargetException(e);
					}
				}
			
			});
			return true;
		} catch (InterruptedException x) {
			// Ignore
		} catch (OperationCanceledException x) {
			// Ignore
		} catch (InvocationTargetException x) {
			ErrorDialog.openError(fComposite.getShell(), CompareMessages.CompareDialog_error_title, null, 
				new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, 
					NLS.bind(CompareMessages.CompareDialog_error_message, x.getTargetException().getMessage()), x.getTargetException()));
		}
		return false;
	}
	
	/**
	 * Return the selected edition or <code>null</code> if no edition is selected.
	 * The result of this method should only be considered if {@link #isEditionSelectionDialog()}
	 * returns <code>true</code>.
	 * @return the selected edition or <code>null</code>
	 * @since 3.3
	 */
	public Object getSelectedEdition() {
		if (fStructureInputPane != null) {
			ISelection selection = fStructureInputPane.getSelection();
			if (selection instanceof IStructuredSelection) {
				IStructuredSelection ss = (IStructuredSelection) selection;
				if (!ss.isEmpty())
					return ss.getFirstElement();
				
			}
		}
		return null;
	}
	
	/**
	 * Set the help context id for this input.
	 * @param helpContextId the help context id.
	 * @since 3.3
	 */
	public void setHelpContextId(String helpContextId) {
		this.fHelpContextId = helpContextId;	
	}
	
}

Back to the top