/*******************************************************************************
* Copyright (c) 2000, 2010 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.BinaryCompareViewer;
import org.eclipse.compare.internal.ChangePropertyAction;
import org.eclipse.compare.internal.CompareContentViewerSwitchingPane;
import org.eclipse.compare.internal.CompareEditorInputNavigator;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.CompareStructureViewerSwitchingPane;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.ICompareUIConstants;
import org.eclipse.compare.internal.OutlineViewerCreator;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.ViewerDescriptor;
import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
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.text.IFindReplaceTarget;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
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.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
/**
* 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>CompareEditorInput</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>ICompareInput</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>ICompareInput</code> with title and
* title image initialized by the corresponding methods of the <code>ICompareInput</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 vertically 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.
*
* @author Stas Negara - Added getContentViewer() (in order to be able to get to its contained
* viewers)
*
* @see CompareUI
* @see CompareEditorInput
*/
public abstract class CompareEditorInput extends PlatformObject 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;
boolean fStructureCompareOnSingleClick= true;
private ICompareContainer fContainer;
private boolean fContainerProvided;
private String fHelpContextId;
private InternalOutlineViewerCreator fOutlineView;
private ViewerDescriptor fContentViewerDescriptor;
private ViewerDescriptor fStructureViewerDescriptor;
private class InternalOutlineViewerCreator extends OutlineViewerCreator {
private OutlineViewerCreator getWrappedCreator() {
if (fContentInputPane != null) {
Viewer v= fContentInputPane.getViewer();
if (v != null) {
return (OutlineViewerCreator)Utilities.getAdapter(v, OutlineViewerCreator.class);
}
}
return null;
}
public Viewer findStructureViewer(Viewer oldViewer,
ICompareInput input, Composite parent,
CompareConfiguration configuration) {
OutlineViewerCreator creator= getWrappedCreator();
if (creator != null)
return creator.findStructureViewer(oldViewer, input, parent, configuration);
return null;
}
public boolean hasViewerFor(Object input) {
OutlineViewerCreator creator= getWrappedCreator();
return creator != null;
}
public Object getInput() {
OutlineViewerCreator creator= getWrappedCreator();
if (creator != null)
return creator.getInput();
return null;
}
}
//CODINGSPECTATOR: Added the method getContentViewer.
public Viewer getContentViewer() {
if (fContentInputPane != null) {
return fContentInputPane.getViewer();
}
return null;
}
/**
* 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);
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) {
synchronized (this) {
if (fOutlineView == null)
fOutlineView= new InternalOutlineViewerCreator();
return fOutlineView;
}
}
if (adapter == IFindReplaceTarget.class) {
if (fContentInputPane != null) {
Viewer v= fContentInputPane.getViewer();
if (v != null) {
return Utilities.getAdapter(v, IFindReplaceTarget.class);
}
}
}
if (adapter == IEditorInput.class) {
if (fContentInputPane != null) {
Viewer v= fContentInputPane.getViewer();
if (v != null) {
return Utilities.getAdapter(v, IEditorInput.class);
}
}
}
if (adapter == ITextEditorExtension3.class) {
if (fContentInputPane != null) {
Viewer v= fContentInputPane.getViewer();
if (v != null) {
return Utilities.getAdapter(v, ITextEditorExtension3.class);
}
}
}
return super.getAdapter(adapter);
}
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) {
ResourceBundle bundle= CompareUI.getResourceBundle();
ChangePropertyAction ignoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(bundle, getCompareConfiguration());
toolBarManager.getControl().addDisposeListener(ignoreWhitespace);
ChangePropertyAction showPseudoConflicts= ChangePropertyAction.createShowPseudoConflictsAction(bundle, getCompareConfiguration());
toolBarManager.getControl().addDisposeListener(showPseudoConflicts);
toolBarManager.add(new Separator());
toolBarManager.add(ignoreWhitespace);
toolBarManager.add(showPseudoConflicts);
}
/**
* 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= createContentViewerSwitchingPane(fComposite, SWT.BORDER | SWT.FLAT, this);
if (fFocusPane == null)
fFocusPane= fContentInputPane;
if (outline != null)
fComposite.setVisible(outline, false);
fComposite.setVisible(fContentInputPane, true);
if (fStructureInputPane != null && fComposite.getChildren().length == 2)
fComposite.setWeights(new int[] { 30, 70 });
fComposite.layout();
feedInput();
fComposite.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
/*
* When the UI associated with this compare editor input is
* disposed each composite being part of the UI releases its
* children first. A dispose listener is added to the last
* widget found in that structure. Therefore, compare editor
* input is disposed at the end making it possible to refer
* during widgets disposal.
*/
Composite composite= fComposite;
Control control= composite;
while (composite.getChildren().length > 0) {
control= composite.getChildren()[composite.getChildren().length - 1];
if (control instanceof Composite)
composite= (Composite)control;
else
break;
}
control.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent ev) {
handleDispose();
}
});
}
});
if (fHelpContextId != null)
PlatformUI.getWorkbench().getHelpSystem().setHelp(fComposite, fHelpContextId);
contentsCreated();
return fComposite;
}
/**
* @param parent the parent control under which the control must be created
* @param style the style of widget to construct
* @param cei the compare editor input for the viewer
* @return the pane displaying content changes
* @nooverride This method is not intended to be re-implemented or extended by clients.
* @noreference This method is not intended to be referenced by clients.
*/
protected CompareViewerSwitchingPane createContentViewerSwitchingPane(Splitter parent, int style, CompareEditorInput cei) {
return new CompareContentViewerSwitchingPane(parent, style, cei);
}
/**
* 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() {
fContainerProvided= false;
fContainer= null;
fComposite= null;
fStructureInputPane= null;
fStructurePane1= null;
fStructurePane2= null;
fContentInputPane= null;
fFocusPane= null;
fNavigator= null;
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 CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
h.setVisible(fStructurePane1, false);
fStructurePane2= new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
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 CompareStructureViewerSwitchingPane(parent, SWT.BORDER | SWT.FLAT, true, this) {
protected Viewer getViewer(Viewer oldViewer, Object input) {
if (CompareEditorInput.this.hasChildren(input)) {
return createDiffViewer(this);
}
return super.getViewer(oldViewer, input);
}
};
}
/* 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);
if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2))
return;
fStructurePane2.setInput(null); // clear downstream pane
fStructurePane1.setInput(null);
} else {
Object input= getElement(selection);
internalSetContentPaneInput(input);
if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2))
return;
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).
*
* @noreference 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.
*
* @deprecated Please use {@link #setFocus2()} instead.
*/
public void setFocus() {
setFocus2();
}
/**
* Asks this input to take focus within its container (editor).
*
* @noreference 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.
*
* @return <code>true</code> if the input got focus, and <code>false</code> if it was unable to.
* @since 3.5
*/
public boolean setFocus2() {
if (fFocusPane != null) {
return fFocusPane.setFocus();
} else if (fComposite != null)
return fComposite.setFocus();
return false;
}
/**
* 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 fStructureViewerDescriptor != null ? fStructureViewerDescriptor.createViewer(oldViewer, parent,
fCompareConfiguration) : 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= fContentViewerDescriptor != null ? fContentViewerDescriptor.createViewer(oldViewer, parent,
fCompareConfiguration) : 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;
}
/**
* @param vd the content viewer descriptor
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
*/
public void setContentViewerDescriptor(ViewerDescriptor vd) {
this.fContentViewerDescriptor= vd;
}
/**
* @return the content viewer descriptor set for the input
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
*/
public ViewerDescriptor getContentViewerDescriptor() {
return this.fContentViewerDescriptor;
}
/**
* @param vd the structure viewer descriptor
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
*/
public void setStructureViewerDescriptor(ViewerDescriptor vd) {
this.fStructureViewerDescriptor= vd;
}
/**
* @return the structure viewer descriptor set for the input
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
*/
public ViewerDescriptor getStructureViewerDescriptor() {
return this.fStructureViewerDescriptor;
}
/**
* 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, Boolean.valueOf(oldDirty), Boolean.valueOf(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, Boolean.valueOf(oldDirty), Boolean.valueOf(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 (fListenerList != 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) {
if (fContainer == null) {
input.addCompareInputChangeListener(listener);
} else {
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) {
if (fContainer == null) {
input.removeCompareInputChangeListener(listener);
} else {
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) {
if (fContainer != null)
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 if (fContainer != null) {
fContainer.setStatusMessage(message);
}
}
/* (non-Javadoc)
* @see org.eclipse.compare.ICompareContainer#getActionBars()
*/
public IActionBars getActionBars() {
if (fContainer != null) {
IActionBars actionBars= fContainer.getActionBars();
if (actionBars == null && !fContainerProvided) {
// The old way to find the action bars
return Utilities.findActionBars(fComposite);
}
return actionBars;
}
return null;
}
/* (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() {
if (fContainer != null)
return fContainer.getWorkbenchPart();
return null;
}
/* (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 {
if (fContainer != null)
fContainer.run(fork, cancelable, runnable);
}
public void runAsynchronously(IRunnableWithProgress runnable) {
if (fContainer != null)
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;
}
}