/* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.eclipse.compare; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.ArrayList; import java.util.ResourceBundle; import org.eclipse.swt.SWT; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.*; import org.eclipse.swt.graphics.Image; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.*; import org.eclipse.ui.IPersistableElement; import org.eclipse.ui.IEditorInput; import org.eclipse.jface.util.*; import org.eclipse.jface.action.Separator; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.jface.viewers.*; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.compare.contentmergeviewer.ContentMergeViewer; import org.eclipse.compare.internal.*; import org.eclipse.compare.structuremergeviewer.*; /** * A compare operation which can present its results in a special editor. * Running the compare operation and presentating the results in a compare editor * are combined in one interface 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. *

* A CompareEditorInput defines methods for the following sequence steps: *

* The Compare plug-in's openCompareEditor method takes an ICompareEditorInput * 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. *

* The prepareInput 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 ICompareEditorInput should hold onto them and return them with * the getCompareResult method. * If the value returned from getCompareResult is not null * a compare editor is opened on the ICompareEditorInput with title and title image initialized by the * corresponding methods of the ICompareEditorInput. *

* Creation of the editor's SWT controls is delegated to the createContents method. * Here the SWT controls must be created and initialized with the result of the compare operation. *

* If merging is allowed, the modification state of the compared constituents must be tracked and the dirty * state returned from method isSaveNeeded. The value true triggers a subsequent call * to save where the modified resources can be saved. *

* 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. *

* Subclasses provide custom setups, e.g. for a Catchup/Release operation * by passing a subclass of CompareConfiguration and by implementing the prepareInput method. * If a subclass cannot use the DiffTreeViewer which is installed by default in the * top left pane, method createDiffViewer can be overridden. * * @see CompareUI * @see CompareEditorInput */ public abstract class CompareEditorInput implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress { /** * The name of the "dirty" property. */ public static final String DIRTY_STATE= "DIRTY_STATE"; //$NON-NLS-1$ private static final String COMPARE_EDITOR_IMAGE_NAME= "cview16/compare_view.gif"; //$NON-NLS-1$ private static Image fgTitleImage; private Splitter fComposite; private CompareConfiguration fCompareConfiguration; private CompareViewerSwitchingPane fStructureInputPane; private CompareViewerSwitchingPane fStructurePane1; private CompareViewerSwitchingPane fStructurePane2; private CompareViewerSwitchingPane fContentInputPane; private CompareViewerSwitchingPane fFocusPane; private String fMessage; private ISelection fSelection2; private Object fInput; private String fTitle; private ListenerList fListenerList= new ListenerList(); private CompareNavigator fNavigator; private boolean fDirty= false; private IPropertyChangeListener fDirtyStateListener; private IgnoreWhiteSpaceAction fIgnoreWhitespace; private ShowPseudoConflicts fShowPseudoConflicts; boolean fStructureCompareOnSingleClick= false; /** * Creates a CompareEditorInput 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= CompareUIPlugin.getResourceBundle(); fIgnoreWhitespace= new IgnoreWhiteSpaceAction(bundle, configuration); fShowPseudoConflicts= new ShowPseudoConflicts(bundle, configuration); fDirtyStateListener= new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (Utilities.getValue(e, false)) setDirty(true); } }; } private boolean structureCompareOnSingleClick() { return fStructureCompareOnSingleClick; } /* (non Javadoc) * see IAdaptable.getAdapter */ public Object getAdapter(Class adapter) { if (CompareNavigator.class.equals(adapter)) { if (fNavigator == null) fNavigator= new CompareNavigator( new CompareViewerSwitchingPane[] { fStructureInputPane, fStructurePane1, fStructurePane2, fContentInputPane } ); return fNavigator; } return null; } /* (non Javadoc) * see IEditorInput.getImageDescriptor */ public ImageDescriptor getImageDescriptor() { return null; } /* (non Javadoc) * see IEditorInput.getToolTipText */ public String getToolTipText() { return fTitle; } /* (non Javadoc) * see IEditorInput.getName */ public String getName() { return fTitle; } /** * Returns null since this editor cannot be persisted. * * @return null because this editor cannot be persisted */ public IPersistableElement getPersistable() { return null; } /** * Returns false to indicate that this input * should not appear in the "File Most Recently Used" menu. * * @return false */ 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 setTitle. * * @return the title */ public String getTitle() { 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) { fTitle= 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 null 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 ToolBarManager. *

* Subclasses may override to add their own actions. *

* * @param toolBarManager the ToolBarManager 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 prepareInput method must propagate a checked exception, * it should wrap it inside an InvocationTargetException; runtime exceptions are automatically * wrapped in an InvocationTargetException by the calling context * @exception InterruptedException if the operation detects a request to cancel, * using IProgressMonitor.isCanceled(), it should exit by throwing * InterruptedException */ public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException { fInput= prepareInput(monitor); } /** * Runs the compare operation and returns the compare result. * If null 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 InterruptedException. * * @param monitor the progress monitor to use to display progress and receive * requests for cancelation * @return the result of the compare operation, or null if there are no differences * @exception InvocationTargetException if the prepareInput method must propagate a checked exception, * it should wrap it inside an InvocationTargetException; runtime exceptions are automatically * wrapped in an InvocationTargetException by the calling context * @exception InterruptedException if the operation detects a request to cancel, * using IProgressMonitor.isCanceled(), it should exit by throwing * InterruptedException */ protected abstract Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException; /** * Returns the compare result computed by the most recent call to the * run method. Returns null if no * differences were found. * * @return the compare result prepared in method prepareInput * or null 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. *

* 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); final Splitter h= new Splitter(fComposite, SWT.HORIZONTAL); fStructureInputPane= new CompareViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true) { protected Viewer getViewer(Viewer oldViewer, Object input) { if (input instanceof DiffNode) { DiffNode dn= (DiffNode) input; if (dn.hasChildren()) return createDiffViewer(this); } if (input instanceof ICompareInput) return findStructureViewer(oldViewer, (ICompareInput)input, this); return null; } }; 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); 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; } }; fComposite.setVisible(h, false); fComposite.setVisible(fContentInputPane, true); fComposite.setWeights(new int[] { 30, 70 }); fComposite.layout(); // setup the wiring for top left pane fStructureInputPane.addSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { feed1(e.getSelection()); } } ); if (!structureCompareOnSingleClick()) { fStructureInputPane.addDoubleClickListener( new IDoubleClickListener() { public void doubleClick(DoubleClickEvent e) { feedDefault1(e.getSelection()); } } ); } // setup the wiring for second pane fStructurePane1.addSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { feed2(e.getSelection()); } } ); // setup the wiring for third pane fStructurePane2.addSelectionChangedListener( new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { feed3(e.getSelection()); } } ); // now deal with activation Listener activationListener= new Listener() { public void handleEvent(Event event) { if (event.widget instanceof CompareViewerSwitchingPane) { fFocusPane= (CompareViewerSwitchingPane) event.widget; } } }; fStructureInputPane.addListener(SWT.Activate, activationListener); fStructurePane1.addListener(SWT.Activate, activationListener); fStructurePane2.addListener(SWT.Activate, activationListener); fContentInputPane.addListener(SWT.Activate, activationListener); if (fInput instanceof ICompareInput) { ICompareInput input2= (ICompareInput) fInput; fStructureInputPane.setInput(input2); feed1(fStructureInputPane.getSelection()); } fComposite.setData("Nav", //$NON-NLS-1$ new CompareViewerSwitchingPane[] { fStructureInputPane, fStructurePane1, fStructurePane2, fContentInputPane } ); return fComposite; } private void feed1(final ISelection selection) { BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() { public void run() { if (selection == null || selection.isEmpty()) { Object input= fStructureInputPane.getInput(); fContentInputPane.setInput(input); fStructurePane2.setInput(null); // clear downstream pane fStructurePane1.setInput(null); } else { Object input= getElement(selection); if (structureCompareOnSingleClick()) { fStructurePane1.setInput(input); //if (fStructurePane1.isEmpty()) fContentInputPane.setInput(input); } else { fContentInputPane.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(); fContentInputPane.setInput(input); fStructurePane2.setInput(null); } else { Object input= getElement(selection); fContentInputPane.setInput(input); fStructurePane2.setInput(input); } } } ); } private void feed3(final ISelection selection) { BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() { public void run() { if (selection.isEmpty()) fContentInputPane.setInput(fStructurePane2.getInput()); else fContentInputPane.setInput(getElement(selection)); } } ); } /** * Returns the first element of the given selection if the selection * is a IStructuredSelection with exactly one element. Returns * null otherwise. * * @param selection the selection * @return the first element of the selection, or null */ 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). *

* 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. *

*/ public void setFocus() { if (fFocusPane != null) { Viewer v= fFocusPane.getViewer(); if (v != null) { Control c= v.getControl(); if (c != null) c.setFocus(); } } else if (fComposite != null) fComposite.setFocus(); } /** * Factory method for creating a differences viewer for the top left pane. * It is called from createContents and returns a DiffTreeViewer. *

* Subclasses may override if they need a different viewer. *

* * @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 * null can be returned to indicate that no viewer could be found. *

* This implementation forwards the request to CompareUI.findStructureViewer. *

* Subclasses may override to implement a different strategy. *

* * @return a compare viewer which is suitable for the given input object or null */ public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) { return CompareUIPlugin.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 * null can be returned to indicate that no viewer could be found. *

* This implementation forwards the request to CompareUI.findContentViewer. *

* Subclasses may override to implement a different strategy. *

* * @return a compare viewer which is suitable for the given input object or null */ public Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent) { Viewer v= CompareUIPlugin.findContentViewer(oldViewer, input, parent, fCompareConfiguration); if (v instanceof IPropertyChangeNotifier) { final IPropertyChangeNotifier dsp= (IPropertyChangeNotifier) v; dsp.addPropertyChangeListener(fDirtyStateListener); Control c= v.getControl(); c.addDisposeListener( new DisposeListener() { public void widgetDisposed(DisposeEvent e) { dsp.removePropertyChangeListener(fDirtyStateListener); } } ); } return v; } /** * Returns true if there are unsaved changes. * The value returned is the value of the DIRTY_STATE property of this input object. * Returns true if this input has unsaved changes, * that is if setDirty(true) has been called. * Subclasses don't have to override if the functionality provided by setDirty * is sufficient. * * @return true if there are changes that need to be saved */ public boolean isSaveNeeded() { return fDirty; } /** * Sets the dirty state of this input to the given * value and sends out a PropertyChangeEvent if the new value differs from the old value. * * @param dirty the dirty state for this compare input */ public void setDirty(boolean dirty) { if (dirty != fDirty) { boolean old= fDirty; fDirty= dirty; Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, new Boolean(old), new Boolean(fDirty)); } } /* (non Javadoc) * see IPropertyChangeNotifier.addListener */ public void addPropertyChangeListener(IPropertyChangeListener listener) { fListenerList.add(listener); } /* (non Javadoc) * see IPropertyChangeNotifier.removeListener */ public void removePropertyChangeListener(IPropertyChangeListener listener) { fListenerList.remove(listener); } /** * Save any unsaved changes. * Empty implementation. * Subclasses must override to save any changes. * * @param progressMonitor an IProgressMonitor that the implementation of save may use to show progress * @deprecated Override method saveChanges instead. */ public void save(IProgressMonitor pm) { } /** * Save any unsaved changes. * Subclasses must override to save any changes. * This implementation tries to flush changes in all viewers by * calling setInput on them. * * @param progressMonitor an IProgressMonitor that the implementation of save may use to show progress */ public void saveChanges(IProgressMonitor pm) throws CoreException { // flush changes in any dirty viewer flushViewer(fStructureInputPane); flushViewer(fStructurePane1); flushViewer(fStructurePane2); flushViewer(fContentInputPane); save(pm); } private static void flushViewer(CompareViewerSwitchingPane pane) { if (pane != null) { Viewer v= pane.getViewer(); if (v != null) { // since we have already asked the user whether he wants to save // changes, we disable the confirmation alert in // ContentMergeViewer.inputChanged if (v instanceof ContentMergeViewer) ((ContentMergeViewer)v).setConfirmSave(false); Object input= pane.getInput(); v.setInput(input); } } } }